From 2d18ab4f285cf3bd4a5e9f3276b4679257a0a663 Mon Sep 17 00:00:00 2001 From: Pritesh Lahoti Date: Wed, 30 Oct 2024 12:37:15 +0530 Subject: [PATCH] [CC-30333] auth: use JWT for authentication This PR allows using the Terraform Provider via JWT authentication, in addition to API Keys. The JWT auth mechanism requires a CC_VANITY_NAME env capturing the vanity name of the org with the corresponding JWT Issuer. In case the JWT is issued against multiple identities, it also requires a CC_USERNAME env capturing the user / service account to impersonate. Eventually, we will add a CI stage for running acceptance tests via this auth mechanism. --- CHANGELOG.md | 2 ++ docs/index.md | 4 ++- internal/provider/models.go | 6 ++-- internal/provider/provider.go | 47 ++++++++++++++++++++++-------- internal/provider/provider_test.go | 8 ++--- 5 files changed, 46 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15457fce..2a4b5461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added support for authentication via JWT. + - Setting and fetching of `cidr_range` is now available for GCP Advanced tier clusters. diff --git a/docs/index.md b/docs/index.md index 77060d70..f96ccda2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -30,4 +30,6 @@ provider "cockroach" { ### Optional -- `apikey` (String, Sensitive) apikey to access cockroach cloud +- `apikey` (String, Sensitive) The API key to access CockroachDB Cloud. +It is either the API Key from a Service Account or a JWT from a JWT Issuer configured for the CockroachDB Cloud Organization. +In the case of JWT, the vanity name of the organization is required and can be provided using the `CC_VANITY_NAME` environment variable. If the JWT is mapped to multiple identities, the identity to impersonate should be provided using the `CC_USERNAME` environment variable, and should contain either a user email address or a service account ID. diff --git a/internal/provider/models.go b/internal/provider/models.go index 953d244d..ff48c2f9 100644 --- a/internal/provider/models.go +++ b/internal/provider/models.go @@ -26,6 +26,8 @@ const ( CockroachAPIKey string = "COCKROACH_API_KEY" APIServerURLKey string = "COCKROACH_SERVER" UserAgent string = "terraform-provider-cockroach" + CCVanityName string = "CC_VANITY_NAME" + CCUsername string = "CC_USERNAME" ) type Region struct { @@ -63,8 +65,8 @@ type ClusterBackupConfig struct { } type UsageLimits struct { - RequestUnitLimit types.Int64 `tfsdk:"request_unit_limit"` - StorageMibLimit types.Int64 `tfsdk:"storage_mib_limit"` + RequestUnitLimit types.Int64 `tfsdk:"request_unit_limit"` + StorageMibLimit types.Int64 `tfsdk:"storage_mib_limit"` ProvisionedVirtualCpus types.Int64 `tfsdk:"provisioned_virtual_cpus"` } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 2184fb65..43ee98b7 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -67,14 +67,14 @@ func (p *provider) Configure( return } - var apiKey string + var apiKeyOrJWT string if !IsKnown(config.ApiKey) { - apiKey = os.Getenv(CockroachAPIKey) + apiKeyOrJWT = os.Getenv(CockroachAPIKey) } else { - apiKey = config.ApiKey.ValueString() + apiKeyOrJWT = config.ApiKey.ValueString() } - if apiKey == "" { + if apiKeyOrJWT == "" { // Error vs warning - empty value must stop execution resp.Diagnostics.AddError( "Unable to find apikey", @@ -83,11 +83,7 @@ func (p *provider) Configure( return } - cfg := client.NewConfiguration(apiKey) - if server := os.Getenv(APIServerURLKey); server != "" { - cfg.ServerURL = server - } - cfg.UserAgent = UserAgent + cfg := getClientConfiguration(apiKeyOrJWT) logLevel := os.Getenv("TF_LOG") if logLevel == "DEBUG" || logLevel == "TRACE" { @@ -167,9 +163,17 @@ func (p *provider) Schema( resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "apikey": schema.StringAttribute{ - MarkdownDescription: "apikey to access cockroach cloud", - Optional: true, - Sensitive: true, + MarkdownDescription: "The API key to access CockroachDB Cloud.\n" + + "It is either the API Key from a Service Account or a JWT from a " + + "JWT Issuer configured for the CockroachDB Cloud Organization.\n" + + "In the case of JWT, the vanity name of the organization is required " + + "and can be provided using the `CC_VANITY_NAME` environment variable. " + + "If the JWT is mapped to multiple identities, the identity to " + + "impersonate should be provided using the `CC_USERNAME` environment " + + "variable, and should contain either a user email address or a " + + "service account ID.", + Optional: true, + Sensitive: true, }, }, } @@ -182,3 +186,22 @@ func New(version string) func() tf_provider.Provider { } } } + +func getClientConfiguration(apikeyOrJWT string) *client.Configuration { + var cfgOpts []client.ConfigurationOption + if vanityName := os.Getenv(CCVanityName); vanityName != "" { + cfgOpts = append(cfgOpts, client.WithVanityName(vanityName)) + } + if username := os.Getenv(CCUsername); username != "" { + cfgOpts = append(cfgOpts, client.WithUsername(username)) + } + + cfg := client.NewConfiguration(apikeyOrJWT, cfgOpts...) + if server := os.Getenv(APIServerURLKey); server != "" { + cfg.ServerURL = server + } + cfg.UserAgent = UserAgent + + return cfg + +} diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index 3b4f484f..555287f1 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -32,12 +32,8 @@ var testAccProvider tf_provider.Provider var cl *client.Client func init() { - apikey := os.Getenv(CockroachAPIKey) - cfg := client.NewConfiguration(apikey) - if server := os.Getenv(APIServerURLKey); server != "" { - cfg.ServerURL = server - } - cfg.UserAgent = UserAgent + apiKeyOrJWT := os.Getenv(CockroachAPIKey) + cfg := getClientConfiguration(apiKeyOrJWT) cl = client.NewClient(cfg) testAccProvider = New("test")() }