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 (
  55. errInvalidToken = fmt.Errorf("authorization header exists, but token is not valid")
  56. errInvalidAuthHeader = fmt.Errorf("invalid authorization header in request")
  57. )
  58. // getTokenFromRequest finds an `Authorization` header of the form `Bearer <token>`,
  59. // and returns a valid token if it exists.
  60. func (authn *AuthNStatic) validateStaticTokenFromRequest(r *http.Request) error {
  61. reqToken := r.Header.Get("Authorization")
  62. splitToken := strings.Split(reqToken, "Bearer")
  63. if len(splitToken) != 2 {
  64. return errInvalidAuthHeader
  65. }
  66. reqToken = strings.TrimSpace(splitToken[1])
  67. // check that request token matches static config token
  68. if err := ValidateStaticToken(authn.config, reqToken); err != nil {
  69. return err
  70. }
  71. return nil
  72. }
  73. func ValidateStaticToken(config *config.Config, reqToken string) error {
  74. if reqToken != config.ProvisionerConf.StaticAuthToken {
  75. return errInvalidToken
  76. }
  77. return nil
  78. }
  79. // AuthNPorterTokenFactory generates a middleware handler `AuthN`
  80. type AuthNPorterTokenFactory struct {
  81. config *config.Config
  82. }
  83. // NewAuthNPorterTokenFactory returns an `AuthNPorterTokenFactory` that uses the passed-in server
  84. // config
  85. func NewAuthNPorterTokenFactory(
  86. config *config.Config,
  87. ) *AuthNPorterTokenFactory {
  88. return &AuthNPorterTokenFactory{config}
  89. }
  90. // NewAuthenticated creates a new instance of `AuthN` that implements the http.Handler
  91. // interface.
  92. func (f *AuthNPorterTokenFactory) NewAuthenticated(next http.Handler) http.Handler {
  93. return &AuthNPorterToken{next, f.config}
  94. }
  95. // AuthNPorterToken implements the authentication middleware
  96. type AuthNPorterToken struct {
  97. next http.Handler
  98. config *config.Config
  99. }
  100. // ServeHTTP calls next if the authentication token is valid,
  101. // or serves a forbidden error.
  102. func (authn *AuthNPorterToken) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  103. // next we check for an issued token
  104. ceToken, err := authn.getPorterTokenFromRequest(r)
  105. if err == nil {
  106. // attach ce token to context
  107. ctx := r.Context()
  108. ctx = context.WithValue(ctx, "ce_token", ceToken)
  109. r = r.Clone(ctx)
  110. authn.next.ServeHTTP(w, r)
  111. return
  112. }
  113. authn.sendForbiddenError(err, w, r)
  114. return
  115. }
  116. // sendForbiddenError sends a 403 Forbidden error to the end user while logging a
  117. // specific error
  118. func (authn *AuthNPorterToken) sendForbiddenError(err error, w http.ResponseWriter, r *http.Request) {
  119. reqErr := apierrors.NewErrForbidden(err)
  120. apierrors.HandleAPIError(authn.config.Logger, authn.config.Alerter, w, r, reqErr, true)
  121. }
  122. func (authn *AuthNPorterToken) getPorterTokenFromRequest(r *http.Request) (*models.CredentialsExchangeToken, error) {
  123. porterToken := r.Header.Get("X-Porter-Token")
  124. if porterToken == "" {
  125. return nil, fmt.Errorf("X-Porter-Token header does not exist")
  126. }
  127. porterTokenID, err := strconv.ParseUint(r.Header.Get("X-Porter-Token-ID"), 10, 64)
  128. if err != nil {
  129. return nil, errInvalidToken
  130. }
  131. return ValidatePorterToken(authn.config, uint(porterTokenID), porterToken)
  132. }
  133. func ValidatePorterToken(config *config.Config, tokenID uint, token string) (*models.CredentialsExchangeToken, error) {
  134. // read the access token in the header, check against DB
  135. ceToken, err := config.Repo.CredentialsExchangeToken().ReadCredentialsExchangeToken(tokenID)
  136. if err != nil {
  137. return nil, err
  138. }
  139. // make sure the token is still valid and has not expired
  140. if ceToken.IsExpired() {
  141. return nil, fmt.Errorf("token is expired")
  142. }
  143. // make sure the token is correct
  144. if err := bcrypt.CompareHashAndPassword([]byte(ceToken.Token), []byte(token)); err != nil {
  145. return nil, fmt.Errorf("verify token failed: %s", err)
  146. }
  147. return ceToken, nil
  148. }