// +build ee package credentials import ( "fmt" "net/http" "strconv" "github.com/porter-dev/porter/api/server/handlers" "github.com/porter-dev/porter/api/server/shared" "github.com/porter-dev/porter/api/server/shared/apierrors" "github.com/porter-dev/porter/api/server/shared/config" "github.com/porter-dev/porter/ee/api/types" "github.com/porter-dev/porter/ee/integrations/vault" "github.com/porter-dev/porter/internal/models" "github.com/porter-dev/porter/internal/repository/credentials" "github.com/porter-dev/porter/internal/repository/gorm" "golang.org/x/crypto/bcrypt" ) type CredentialsGetHandler struct { handlers.PorterHandlerReadWriter } func NewCredentialsGetHandler( config *config.Config, decoderValidator shared.RequestDecoderValidator, writer shared.ResultWriter, ) http.Handler { return &CredentialsGetHandler{ PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, nil, writer), } } func (c *CredentialsGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // read the request to get the token id and hashed token req := &types.CredentialsExchangeRequest{} // populate the request from the headers req.CredExchangeToken = r.Header.Get("X-Porter-Token") tokID, err := strconv.ParseUint(r.Header.Get("X-Porter-Token-ID"), 10, 64) if err != nil { c.HandleAPIError(w, r, apierrors.NewErrForbidden(err)) return } req.CredExchangeID = uint(tokID) req.VaultToken = r.Header.Get("X-Vault-Token") // read the access token in the header, check against DB ceToken, err := c.Repo().CredentialsExchangeToken().ReadCredentialsExchangeToken(req.CredExchangeID) if err != nil { c.HandleAPIError(w, r, apierrors.NewErrForbidden(err)) return } // TODO: verify hashed token!! if valid, err := verifyToken(req.CredExchangeToken, ceToken); !valid { c.HandleAPIError(w, r, apierrors.NewErrForbidden(err)) return } resp := &types.CredentialsExchangeResponse{} repo := c.Repo() // if the request contains a vault token, use that vault token to construct a new repository // that will query vault using the passed in token if req.VaultToken != "" { // read the vault token in the header, create new vault client with this token conf := c.Config().DBConf vaultClient := vault.NewClient(conf.VaultServerURL, req.VaultToken, conf.VaultPrefix) var key [32]byte for i, b := range []byte(conf.EncryptionKey) { key[i] = b } // use this vault client for the repo repo = gorm.NewRepository(c.Config().DB, &key, vaultClient) } if ceToken.DOCredentialID != 0 { doInt, err := repo.OAuthIntegration().ReadOAuthIntegration(ceToken.ProjectID, ceToken.DOCredentialID) if err != nil { c.HandleAPIError(w, r, apierrors.NewErrForbidden(err)) return } resp.DO = &credentials.OAuthCredential{ ClientID: doInt.ClientID, AccessToken: doInt.AccessToken, RefreshToken: doInt.RefreshToken, } } else if ceToken.GCPCredentialID != 0 { gcpInt, err := repo.GCPIntegration().ReadGCPIntegration(ceToken.ProjectID, ceToken.GCPCredentialID) if err != nil { c.HandleAPIError(w, r, apierrors.NewErrForbidden(err)) return } resp.GCP = &credentials.GCPCredential{ GCPKeyData: gcpInt.GCPKeyData, } } else if ceToken.AWSCredentialID != 0 { awsInt, err := repo.AWSIntegration().ReadAWSIntegration(ceToken.ProjectID, ceToken.AWSCredentialID) if err != nil { c.HandleAPIError(w, r, apierrors.NewErrForbidden(err)) return } resp.AWS = &credentials.AWSCredential{ AWSAccessKeyID: awsInt.AWSAccessKeyID, AWSClusterID: awsInt.AWSClusterID, AWSSecretAccessKey: awsInt.AWSSecretAccessKey, AWSSessionToken: awsInt.AWSSessionToken, } } // return the decrypted credentials c.WriteResult(w, r, resp) } func verifyToken(reqToken string, ceToken *models.CredentialsExchangeToken) (bool, error) { // make sure the token is still valid and has not expired if ceToken.IsExpired() { return false, fmt.Errorf("token is expired") } // make sure the token is correct if err := bcrypt.CompareHashAndPassword([]byte(ceToken.Token), []byte(reqToken)); err != nil { return false, fmt.Errorf("verify token failed: %s", err) } return true, nil }