authn.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. package authn
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "strconv"
  7. "strings"
  8. "github.com/porter-dev/porter/api/server/shared/apierrors"
  9. "github.com/porter-dev/porter/internal/models"
  10. "github.com/porter-dev/porter/provisioner/server/config"
  11. "golang.org/x/crypto/bcrypt"
  12. )
  13. // AuthNFactory generates a middleware handler `AuthN`
  14. type AuthNStaticFactory struct {
  15. config *config.Config
  16. }
  17. // NewAuthNStaticFactory returns an `AuthNStaticFactory` that uses the passed-in server
  18. // config
  19. func NewAuthNStaticFactory(
  20. config *config.Config,
  21. ) *AuthNStaticFactory {
  22. return &AuthNStaticFactory{config}
  23. }
  24. // NewAuthenticated creates a new instance of `AuthN` that implements the http.Handler
  25. // interface.
  26. func (f *AuthNStaticFactory) NewAuthenticated(next http.Handler) http.Handler {
  27. return &AuthNStatic{next, f.config}
  28. }
  29. // AuthNStatic implements the authentication middleware
  30. type AuthNStatic struct {
  31. next http.Handler
  32. config *config.Config
  33. }
  34. // ServeHTTP calls next if the authentication token is valid,
  35. // or serves a forbidden error.
  36. func (authn *AuthNStatic) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  37. // first check for a static bearer token
  38. err := authn.validateStaticTokenFromRequest(r)
  39. // if the error is not an invalid auth error, the token was invalid, and we throw error
  40. // forbidden. If the error was an invalid auth error, we look for a cookie.
  41. if err == nil {
  42. authn.next.ServeHTTP(w, r)
  43. return
  44. }
  45. authn.sendForbiddenError(err, w, r)
  46. return
  47. }
  48. // sendForbiddenError sends a 403 Forbidden error to the end user while logging a
  49. // specific error
  50. func (authn *AuthNStatic) sendForbiddenError(err error, w http.ResponseWriter, r *http.Request) {
  51. reqErr := apierrors.NewErrForbidden(err)
  52. apierrors.HandleAPIError(authn.config.Logger, authn.config.Alerter, w, r, reqErr, true)
  53. }
  54. var errInvalidToken = fmt.Errorf("authorization header exists, but token is not valid")
  55. var errInvalidAuthHeader = fmt.Errorf("invalid authorization header in request")
  56. // getTokenFromRequest finds an `Authorization` header of the form `Bearer <token>`,
  57. // and returns a valid token if it exists.
  58. func (authn *AuthNStatic) validateStaticTokenFromRequest(r *http.Request) error {
  59. reqToken := r.Header.Get("Authorization")
  60. splitToken := strings.Split(reqToken, "Bearer")
  61. if len(splitToken) != 2 {
  62. return errInvalidAuthHeader
  63. }
  64. reqToken = strings.TrimSpace(splitToken[1])
  65. // check that request token matches static config token
  66. if err := ValidateStaticToken(authn.config, reqToken); err != nil {
  67. return err
  68. }
  69. return nil
  70. }
  71. func ValidateStaticToken(config *config.Config, reqToken string) error {
  72. if reqToken != config.ProvisionerConf.StaticAuthToken {
  73. return errInvalidToken
  74. }
  75. return nil
  76. }
  77. // AuthNPorterTokenFactory generates a middleware handler `AuthN`
  78. type AuthNPorterTokenFactory struct {
  79. config *config.Config
  80. }
  81. // NewAuthNPorterTokenFactory returns an `AuthNPorterTokenFactory` that uses the passed-in server
  82. // config
  83. func NewAuthNPorterTokenFactory(
  84. config *config.Config,
  85. ) *AuthNPorterTokenFactory {
  86. return &AuthNPorterTokenFactory{config}
  87. }
  88. // NewAuthenticated creates a new instance of `AuthN` that implements the http.Handler
  89. // interface.
  90. func (f *AuthNPorterTokenFactory) NewAuthenticated(next http.Handler) http.Handler {
  91. return &AuthNPorterToken{next, f.config}
  92. }
  93. // AuthNPorterToken implements the authentication middleware
  94. type AuthNPorterToken struct {
  95. next http.Handler
  96. config *config.Config
  97. }
  98. // ServeHTTP calls next if the authentication token is valid,
  99. // or serves a forbidden error.
  100. func (authn *AuthNPorterToken) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  101. // next we check for an issued token
  102. ceToken, err := authn.getPorterTokenFromRequest(r)
  103. if err == nil {
  104. // attach ce token to context
  105. ctx := r.Context()
  106. ctx = context.WithValue(ctx, "ce_token", ceToken)
  107. r = r.Clone(ctx)
  108. authn.next.ServeHTTP(w, r)
  109. return
  110. }
  111. authn.sendForbiddenError(err, w, r)
  112. return
  113. }
  114. // sendForbiddenError sends a 403 Forbidden error to the end user while logging a
  115. // specific error
  116. func (authn *AuthNPorterToken) sendForbiddenError(err error, w http.ResponseWriter, r *http.Request) {
  117. reqErr := apierrors.NewErrForbidden(err)
  118. apierrors.HandleAPIError(authn.config.Logger, authn.config.Alerter, w, r, reqErr, true)
  119. }
  120. func (authn *AuthNPorterToken) getPorterTokenFromRequest(r *http.Request) (*models.CredentialsExchangeToken, error) {
  121. porterToken := r.Header.Get("X-Porter-Token")
  122. if porterToken == "" {
  123. return nil, fmt.Errorf("X-Porter-Token header does not exist")
  124. }
  125. porterTokenID, err := strconv.ParseUint(r.Header.Get("X-Porter-Token-ID"), 10, 64)
  126. if err != nil {
  127. return nil, errInvalidToken
  128. }
  129. return ValidatePorterToken(authn.config, uint(porterTokenID), porterToken)
  130. }
  131. func ValidatePorterToken(config *config.Config, tokenID uint, token string) (*models.CredentialsExchangeToken, error) {
  132. // read the access token in the header, check against DB
  133. ceToken, err := config.Repo.CredentialsExchangeToken().ReadCredentialsExchangeToken(tokenID)
  134. if err != nil {
  135. return nil, err
  136. }
  137. // make sure the token is still valid and has not expired
  138. if ceToken.IsExpired() {
  139. return nil, fmt.Errorf("token is expired")
  140. }
  141. // make sure the token is correct
  142. if err := bcrypt.CompareHashAndPassword([]byte(ceToken.Token), []byte(token)); err != nil {
  143. return nil, fmt.Errorf("verify token failed: %s", err)
  144. }
  145. return ceToken, nil
  146. }