gcp.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. package integrations
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "github.com/porter-dev/porter/api/types"
  7. "golang.org/x/oauth2"
  8. "golang.org/x/oauth2/google"
  9. "gorm.io/gorm"
  10. )
  11. // GCPIntegration is an auth mechanism that uses a GCP service account to
  12. // authenticate
  13. type GCPIntegration struct {
  14. gorm.Model
  15. // The id of the user that linked this auth mechanism
  16. UserID uint `json:"user_id"`
  17. // The project that this integration belongs to
  18. ProjectID uint `json:"project_id"`
  19. // The GCP project id where the service account for this auth mechanism persists
  20. GCPProjectID string `json:"gcp_project_id"`
  21. // The GCP service account email for this credential
  22. GCPSAEmail string `json:"gcp_sa_email"`
  23. // The GCP user email that linked this service account
  24. GCPUserEmail string `json:"gcp-user-email"`
  25. // The GCP region, which may or may not be used by the integration
  26. GCPRegion string `json:"gcp_region"`
  27. // ------------------------------------------------------------------
  28. // All fields encrypted before storage.
  29. // ------------------------------------------------------------------
  30. // KeyData for a service account for GCP connectors
  31. GCPKeyData []byte `json:"gcp_key_data"`
  32. }
  33. func (g *GCPIntegration) ToGCPIntegrationType() *types.GCPIntegration {
  34. return &types.GCPIntegration{
  35. CreatedAt: g.CreatedAt,
  36. ID: g.ID,
  37. UserID: g.UserID,
  38. ProjectID: g.ProjectID,
  39. GCPProjectID: g.GCPProjectID,
  40. GCPSAEmail: g.GCPSAEmail,
  41. }
  42. }
  43. // GetBearerToken retrieves a bearer token for a GCP account
  44. func (g *GCPIntegration) GetBearerToken(
  45. ctx context.Context,
  46. getTokenCache GetTokenCacheFunc,
  47. setTokenCache SetTokenCacheFunc,
  48. scopes ...string,
  49. ) (*oauth2.Token, error) {
  50. cache, err := getTokenCache(ctx)
  51. // check the token cache for a non-expired token
  52. if cache != nil {
  53. if tok := cache.Token; err == nil && !cache.IsExpired() && len(tok) > 0 {
  54. return &oauth2.Token{
  55. AccessToken: string(cache.Token),
  56. Expiry: cache.Expiry,
  57. }, nil
  58. }
  59. }
  60. creds, err := google.CredentialsFromJSON(
  61. ctx,
  62. g.GCPKeyData,
  63. scopes...,
  64. )
  65. if err != nil {
  66. return nil, fmt.Errorf("failed to get credentials from json: %w", err)
  67. }
  68. tok, err := creds.TokenSource.Token()
  69. if err != nil {
  70. return nil, fmt.Errorf("failed to get token from credentials: %w", err)
  71. }
  72. // update the token cache
  73. err = setTokenCache(ctx, tok.AccessToken, tok.Expiry)
  74. if err != nil {
  75. return nil, fmt.Errorf("non-fatal error setting token cache: %w", err)
  76. }
  77. return tok, nil
  78. }
  79. // credentialsFile is the unmarshalled representation of a GCP credentials file.
  80. // Source; golang.org/x/oauth2/google
  81. type credentialsFile struct {
  82. Type string `json:"type"` // serviceAccountKey or userCredentialsKey
  83. // Service Account fields
  84. ClientEmail string `json:"client_email"`
  85. PrivateKeyID string `json:"private_key_id"`
  86. PrivateKey string `json:"private_key"`
  87. TokenURL string `json:"token_uri"`
  88. ProjectID string `json:"project_id"`
  89. // User Credential fields
  90. // (These typically come from gcloud auth.)
  91. ClientSecret string `json:"client_secret"`
  92. ClientID string `json:"client_id"`
  93. RefreshToken string `json:"refresh_token"`
  94. // External Account fields
  95. Audience string `json:"audience"`
  96. SubjectTokenType string `json:"subject_token_type"`
  97. TokenURLExternal string `json:"token_url"`
  98. TokenInfoURL string `json:"token_info_url"`
  99. ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
  100. // CredentialSource externalaccount.CredentialSource `json:"credential_source"`
  101. QuotaProjectID string `json:"quota_project_id"`
  102. }
  103. func GCPProjectIDFromJSON(jsonData []byte) (string, error) {
  104. var f credentialsFile
  105. if err := json.Unmarshal(jsonData, &f); err != nil {
  106. return "", err
  107. }
  108. return f.ProjectID, nil
  109. }
  110. // PopulateGCPMetadata uses the credentials file to get the GCP SA name and
  111. // project ID, and attaches it to the GCP credential
  112. func (g *GCPIntegration) PopulateGCPMetadata() {
  113. var f credentialsFile
  114. if err := json.Unmarshal(g.GCPKeyData, &f); err != nil {
  115. return
  116. }
  117. g.GCPProjectID = f.ProjectID
  118. g.GCPSAEmail = f.ClientEmail
  119. }