- 
                Notifications
    
You must be signed in to change notification settings  - Fork 208
 
chore: Implement credential type hierarchy #3738
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 35 commits
0658fe4
              bcb6cdb
              425a8b0
              be7f9a6
              af627fe
              1800c31
              8f796cc
              ccfca8a
              9d778df
              2420ece
              0aa6126
              7770785
              b61df5b
              e5c73bf
              e9c477e
              eeb1d5f
              809eb87
              d060712
              178b4b5
              1257c5d
              f63dcf3
              d700653
              9193eb2
              61b62c5
              104fb25
              8b2415f
              0dc3ab4
              b7e555b
              c818683
              2cd7c13
              7c56a75
              35353d3
              2afb3a3
              e37facc
              f837b8e
              9e2ce3a
              c1153b5
              f4bb882
              5b7ba2a
              5f2c411
              25c351f
              6df44c7
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| package config | ||
| 
     | 
||
| import ( | ||
| "os" | ||
| 
     | 
||
| "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion" | ||
| ) | ||
| 
     | 
||
| // Credentials has all the authentication fields, it also matches with fields that can be stored in AWS Secrets Manager. | ||
| type Credentials struct { | ||
| AccessToken string `json:"access_token"` | ||
| ClientID string `json:"client_id"` | ||
| ClientSecret string `json:"client_secret"` | ||
| PublicKey string `json:"public_key"` | ||
| PrivateKey string `json:"private_key"` | ||
| BaseURL string `json:"base_url"` | ||
| RealmBaseURL string `json:"realm_base_url"` | ||
| } | ||
| 
     | 
||
| // GetCredentials follows the order of AWS Secrets Manager, provider vars and env vars. | ||
| func GetCredentials(providerVars, envVars *Vars, getAWSCredentials func(*AWSVars) (*Credentials, error)) (*Credentials, error) { | ||
| if awsVars := CoalesceAWSVars(providerVars.GetAWS(), envVars.GetAWS()); awsVars != nil { | ||
| awsCredentials, err := getAWSCredentials(awsVars) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return awsCredentials, nil | ||
| } | ||
| if c := CoalesceCredentials(providerVars.GetCredentials(), envVars.GetCredentials()); c != nil { | ||
| return c, nil | ||
| } | ||
| return &Credentials{}, nil | ||
| } | ||
| 
     | 
||
| // AuthMethod follows the order of token, SA and PAK. | ||
| func (c *Credentials) AuthMethod() AuthMethod { | ||
| switch { | ||
| case c.HasAccessToken(): | ||
| return AccessToken | ||
| case c.HasServiceAccount(): | ||
| return ServiceAccount | ||
| case c.HasDigest(): | ||
| return Digest | ||
| default: | ||
| return Unknown | ||
| } | ||
| } | ||
| 
     | 
||
| func (c *Credentials) HasAccessToken() bool { | ||
| return c.AccessToken != "" | ||
| } | ||
| 
     | 
||
| func (c *Credentials) HasServiceAccount() bool { | ||
| return c.ClientID != "" || c.ClientSecret != "" | ||
| } | ||
| 
     | 
||
| func (c *Credentials) HasDigest() bool { | ||
| return c.PublicKey != "" || c.PrivateKey != "" | ||
| } | ||
| 
     | 
||
| func (c *Credentials) IsPresent() bool { | ||
| return c.AuthMethod() != Unknown | ||
| } | ||
| 
     | 
||
| func (c *Credentials) Warnings() string { | ||
| if !c.IsPresent() { | ||
| return "No credentials set" | ||
| } | ||
| // Prefer specific checks over generaric code as there are few combinations and code is clearer. | ||
                
      
                  lantoli marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| if c.HasAccessToken() && c.HasServiceAccount() && c.HasDigest() { | ||
| return "Access Token will be used although Service Account and API Keys are also set" | ||
| } | ||
| if c.HasAccessToken() && c.HasServiceAccount() { | ||
| return "Access Token will be used although Service Account is also set" | ||
| } | ||
| if c.HasAccessToken() && c.HasDigest() { | ||
| return "Access Token will be used although API Keys is also set" | ||
| } | ||
| if c.HasServiceAccount() && c.HasDigest() { | ||
| return "Service Account will be used although API Keys is also set" | ||
| } | ||
| return "" | ||
| } | ||
| 
     | 
||
| type AWSVars struct { | ||
| AssumeRoleARN string | ||
| SecretName string | ||
| Region string | ||
| AccessKeyID string | ||
| SecretAccessKey string | ||
| SessionToken string | ||
| Endpoint string | ||
| } | ||
| 
     | 
||
| func (a *AWSVars) IsPresent() bool { | ||
| return a.AssumeRoleARN != "" | ||
| } | ||
| 
     | 
||
| type Vars struct { | ||
| 
         There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: maybe rename this to a more meaningful name? providerVars maybe? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Vars can be created from provider or environment variables using: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as I see it all these are provider vars, but can be inputed as provider inputs or env vars, so an option could be to have  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think i understand the confusion, you call "provider inputs" when i say "provider". You're right that at the end of the day everything is about the provider :-) "provider" var means defined in the Terraform provider config, e.g.: and Vars can be obtained from the provider config or environment variables, credentials decision order is done here:  | 
||
| AccessToken string | ||
| ClientID string | ||
| ClientSecret string | ||
| PublicKey string | ||
| PrivateKey string | ||
| BaseURL string | ||
| RealmBaseURL string | ||
| AWSAssumeRoleARN string | ||
| AWSSecretName string | ||
| AWSRegion string | ||
| AWSAccessKeyID string | ||
| AWSSecretAccessKey string | ||
| AWSSessionToken string | ||
| AWSEndpoint string | ||
| } | ||
| 
     | 
||
| func NewEnvVars() *Vars { | ||
| return &Vars{ | ||
| AccessToken: getEnv("MONGODB_ATLAS_ACCESS_TOKEN", "TF_VAR_ACCESS_TOKEN"), | ||
| ClientID: getEnv("MONGODB_ATLAS_CLIENT_ID", "TF_VAR_CLIENT_ID"), | ||
| ClientSecret: getEnv("MONGODB_ATLAS_CLIENT_SECRET", "TF_VAR_CLIENT_SECRET"), | ||
| PublicKey: getEnv("MONGODB_ATLAS_PUBLIC_API_KEY", "MONGODB_ATLAS_PUBLIC_KEY", "MCLI_PUBLIC_API_KEY"), | ||
| PrivateKey: getEnv("MONGODB_ATLAS_PRIVATE_API_KEY", "MONGODB_ATLAS_PRIVATE_KEY", "MCLI_PRIVATE_API_KEY"), | ||
| BaseURL: getEnv("MONGODB_ATLAS_BASE_URL", "MCLI_OPS_MANAGER_URL"), | ||
| RealmBaseURL: getEnv("MONGODB_REALM_BASE_URL"), | ||
| AWSAssumeRoleARN: getEnv("ASSUME_ROLE_ARN", "TF_VAR_ASSUME_ROLE_ARN"), | ||
| AWSSecretName: getEnv("SECRET_NAME", "TF_VAR_SECRET_NAME"), | ||
| AWSRegion: getEnv("AWS_REGION", "TF_VAR_AWS_REGION"), | ||
| AWSAccessKeyID: getEnv("AWS_ACCESS_KEY_ID", "TF_VAR_AWS_ACCESS_KEY_ID"), | ||
| AWSSecretAccessKey: getEnv("AWS_SECRET_ACCESS_KEY", "TF_VAR_AWS_SECRET_ACCESS_KEY"), | ||
| AWSSessionToken: getEnv("AWS_SESSION_TOKEN", "TF_VAR_AWS_SESSION_TOKEN"), | ||
| AWSEndpoint: getEnv("STS_ENDPOINT", "TF_VAR_STS_ENDPOINT"), | ||
| } | ||
| } | ||
| 
     | 
||
| func (e *Vars) GetCredentials() *Credentials { | ||
| return &Credentials{ | ||
| AccessToken: e.AccessToken, | ||
| ClientID: e.ClientID, | ||
| ClientSecret: e.ClientSecret, | ||
| PublicKey: e.PublicKey, | ||
| PrivateKey: e.PrivateKey, | ||
| BaseURL: e.BaseURL, | ||
| RealmBaseURL: e.RealmBaseURL, | ||
| } | ||
| } | ||
| 
     | 
||
| // GetAWS returns variables in the format AWS expects, e.g. region in lowercase. | ||
| func (e *Vars) GetAWS() *AWSVars { | ||
| return &AWSVars{ | ||
| AssumeRoleARN: e.AWSAssumeRoleARN, | ||
| SecretName: e.AWSSecretName, | ||
| Region: conversion.MongoDBRegionToAWSRegion(e.AWSRegion), | ||
| AccessKeyID: e.AWSAccessKeyID, | ||
| SecretAccessKey: e.AWSSecretAccessKey, | ||
| SessionToken: e.AWSSessionToken, | ||
| Endpoint: e.AWSEndpoint, | ||
| } | ||
| } | ||
| 
     | 
||
| func getEnv(key ...string) string { | ||
| for _, k := range key { | ||
| if v := os.Getenv(k); v != "" { | ||
| return v | ||
| } | ||
| } | ||
| return "" | ||
| } | ||
| 
     | 
||
| func CoalesceAWSVars(awsVars ...*AWSVars) *AWSVars { | ||
| for _, awsVar := range awsVars { | ||
| if awsVar.IsPresent() { | ||
| return awsVar | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
| 
     | 
||
| func CoalesceCredentials(credentials ...*Credentials) *Credentials { | ||
| for _, credential := range credentials { | ||
| if credential.IsPresent() { | ||
| return credential | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very clear and easy to follow 💯