From 59f99a0d59804fbcd3e5a48c92f7c8d5c44d1912 Mon Sep 17 00:00:00 2001 From: ilia-medvedev-codefresh Date: Tue, 17 Dec 2024 16:37:22 +0200 Subject: [PATCH] fix: codefresh_context disable decrypt when forbidDecrypt feature flag is set (#156) --- codefresh/cfclient/client.go | 41 ++++++++++---- codefresh/cfclient/context.go | 40 ++++++++++++-- codefresh/cfclient/current_account.go | 21 +++++--- codefresh/context/storage.go | 2 +- codefresh/provider.go | 2 +- codefresh/resource_context.go | 17 ++++-- codefresh/resource_context_test.go | 4 +- docs/resources/context.md | 77 +++++++++++++++++++++++++++ templates/resources/context.md.tmpl | 77 +++++++++++++++++++++++++++ 9 files changed, 250 insertions(+), 31 deletions(-) diff --git a/codefresh/cfclient/client.go b/codefresh/cfclient/client.go index b8f4442..9b3afc5 100644 --- a/codefresh/cfclient/client.go +++ b/codefresh/cfclient/client.go @@ -11,11 +11,12 @@ import ( // Client token, host, htpp.Client type Client struct { - Token string - TokenHeader string - Host string - HostV2 string - Client *http.Client + Token string + TokenHeader string + Host string + HostV2 string + featureFlags map[string]bool + Client *http.Client } // RequestOptions path, method, etc @@ -35,11 +36,12 @@ func NewClient(hostname string, hostnameV2 string, token string, tokenHeader str tokenHeader = "Authorization" } return &Client{ - Host: hostname, - HostV2: hostnameV2, - Token: token, - TokenHeader: tokenHeader, - Client: &http.Client{}, + Host: hostname, + HostV2: hostnameV2, + Token: token, + TokenHeader: tokenHeader, + Client: &http.Client{}, + featureFlags: map[string]bool{}, } } @@ -112,6 +114,25 @@ func (client *Client) RequestApiXAccessToken(opt *RequestOptions) ([]byte, error return body, nil } +func (client *Client) isFeatureFlagEnabled(flagName string) (bool, error) { + + if len(client.featureFlags) == 0 { + currAcc, err := client.GetCurrentAccount() + + if err != nil { + return false, err + } + + client.featureFlags = currAcc.FeatureFlags + } + + if val, ok := client.featureFlags[flagName]; ok { + return val, nil + } + + return false, nil +} + // ToQS add extra parameters to path func ToQS(qs map[string]string) string { var arr = []string{} diff --git a/codefresh/cfclient/context.go b/codefresh/cfclient/context.go index 934e718..4a29dec 100644 --- a/codefresh/cfclient/context.go +++ b/codefresh/cfclient/context.go @@ -4,8 +4,17 @@ import ( "fmt" "log" "net/url" + + "golang.org/x/exp/slices" ) +var encryptedContextTypes = []string{ + "secret", + "secret-yaml", + "storage.s3", + "storage.azuref", +} + type ContextErrorResponse struct { Status int `json:"status,omitempty"` Message string `json:"message,omitempty"` @@ -17,9 +26,10 @@ type ContextMetadata struct { } type Context struct { - Metadata ContextMetadata `json:"metadata,omitempty"` - Spec ContextSpec `json:"spec,omitempty"` - Version string `json:"version,omitempty"` + Metadata ContextMetadata `json:"metadata,omitempty"` + Spec ContextSpec `json:"spec,omitempty"` + Version string `json:"version,omitempty"` + IsEncrypred bool `json:"isEncrypted,omitempty"` } type ContextSpec struct { @@ -32,7 +42,18 @@ func (context *Context) GetID() string { } func (client *Client) GetContext(name string) (*Context, error) { - fullPath := fmt.Sprintf("/contexts/%s?decrypt=true", url.PathEscape(name)) + fullPath := fmt.Sprintf("/contexts/%s", url.PathEscape(name)) + + forbidDecrypt, err := client.isFeatureFlagEnabled("forbidDecrypt") + + if err != nil { + forbidDecrypt = false + } + + if !forbidDecrypt { + fullPath += "?decrypt=true" + } + opts := RequestOptions{ Path: fullPath, Method: "GET", @@ -49,8 +70,17 @@ func (client *Client) GetContext(name string) (*Context, error) { return nil, err } - return &respContext, nil + // This is so not to break existing behavior while adding support for forbidDecrypt feature flag + // The provider used to always decrypt the contexts, hence we treat all contexts as decrypted unless forbidDecrypt is set + isEncryptedType := slices.Contains(encryptedContextTypes, respContext.Spec.Type) + respContext.IsEncrypred = false + + if forbidDecrypt && isEncryptedType { + respContext.IsEncrypred = true + } + + return &respContext, nil } func (client *Client) CreateContext(context *Context) (*Context, error) { diff --git a/codefresh/cfclient/current_account.go b/codefresh/cfclient/current_account.go index 6e5f00a..73da855 100644 --- a/codefresh/cfclient/current_account.go +++ b/codefresh/cfclient/current_account.go @@ -18,10 +18,11 @@ type CurrentAccountUser struct { // CurrentAccount spec type CurrentAccount struct { - ID string - Name string - Users []CurrentAccountUser - Admins []CurrentAccountUser + ID string + Name string + Users []CurrentAccountUser + Admins []CurrentAccountUser + FeatureFlags map[string]bool } // GetCurrentAccount - @@ -46,9 +47,10 @@ func (client *Client) GetCurrentAccount() (*CurrentAccount, error) { return nil, fmt.Errorf("GetCurrentAccount - cannot get activeAccountName") } currentAccount := &CurrentAccount{ - Name: activeAccountName, - Users: make([]CurrentAccountUser, 0), - Admins: make([]CurrentAccountUser, 0), + Name: activeAccountName, + Users: make([]CurrentAccountUser, 0), + Admins: make([]CurrentAccountUser, 0), + FeatureFlags: make(map[string]bool), } accountAdminsIDs := make([]string, 0) @@ -62,6 +64,11 @@ func (client *Client) GetCurrentAccount() (*CurrentAccount, error) { for _, adminI := range admins { accountAdminsIDs = append(accountAdminsIDs, adminI.(string)) } + featureFlags := accX.Get("features").ObjxMap() + + for k, v := range featureFlags { + currentAccount.FeatureFlags[k] = v.(bool) + } break } } diff --git a/codefresh/context/storage.go b/codefresh/context/storage.go index 99f8840..9d25084 100644 --- a/codefresh/context/storage.go +++ b/codefresh/context/storage.go @@ -54,7 +54,7 @@ func flattenStorageContextConfig(spec cfclient.ContextSpec, auth map[string]inte func FlattenJsonConfigStorageContextConfig(spec cfclient.ContextSpec) []interface{} { auth := make(map[string]interface{}) auth["json_config"] = spec.Data["auth"].(map[string]interface{})["jsonConfig"] - auth["type"] = spec.Data["type"] + auth["type"] = spec.Data["auth"].(map[string]interface{})["type"] return flattenStorageContextConfig(spec, auth) } diff --git a/codefresh/provider.go b/codefresh/provider.go index ad08cfc..fb30cad 100644 --- a/codefresh/provider.go +++ b/codefresh/provider.go @@ -5,7 +5,6 @@ import ( "github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "os" ) @@ -87,5 +86,6 @@ func configureProvider(d *schema.ResourceData) (interface{}, error) { if token == "" { token = os.Getenv(ENV_CODEFRESH_API_KEY) } + return cfclient.NewClient(apiURL, apiURLV2, token, ""), nil } diff --git a/codefresh/resource_context.go b/codefresh/resource_context.go index 7e9556c..7c12325 100644 --- a/codefresh/resource_context.go +++ b/codefresh/resource_context.go @@ -180,12 +180,14 @@ func resourceContextRead(d *schema.ResourceData, meta interface{}) error { } context, err := client.GetContext(contextName) + if err != nil { log.Printf("[DEBUG] Error while getting context. Error = %v", contextName) return err } err = mapContextToResource(*context, d) + if err != nil { log.Printf("[DEBUG] Error while mapping context to resource. Error = %v", err) return err @@ -225,14 +227,20 @@ func resourceContextDelete(d *schema.ResourceData, meta interface{}) error { func mapContextToResource(context cfclient.Context, d *schema.ResourceData) error { err := d.Set("name", context.Metadata.Name) + if err != nil { return err } - err = d.Set("spec", flattenContextSpec(context.Spec)) - if err != nil { - log.Printf("[DEBUG] Failed to flatten Context spec = %v", context.Spec) - return err + // Read spec from API if context is not encrypted or forbitDecrypt is not set + if !context.IsEncrypred { + + err = d.Set("spec", flattenContextSpec(context.Spec)) + + if err != nil { + log.Printf("[DEBUG] Failed to flatten Context spec = %v", context.Spec) + return err + } } return nil @@ -253,7 +261,6 @@ func flattenContextSpec(spec cfclient.ContextSpec) []interface{} { case contextAzureStorage: m[schemautil.MustNormalizeFieldName(currentContextType)] = storageContext.FlattenAzureStorageContextConfig(spec) default: - log.Printf("[DEBUG] Invalid context type = %v", currentContextType) return nil } diff --git a/codefresh/resource_context_test.go b/codefresh/resource_context_test.go index 4e634c1..8b1e630 100644 --- a/codefresh/resource_context_test.go +++ b/codefresh/resource_context_test.go @@ -204,7 +204,7 @@ resource "codefresh_context" "test" { spec { config { - data = { + data = { %q = %q %q = %q } @@ -223,7 +223,7 @@ resource "codefresh_context" "test" { spec { secret { - data = { + data = { %q = %q %q = %q } diff --git a/docs/resources/context.md b/docs/resources/context.md index 3a5a6cc..e51660e 100644 --- a/docs/resources/context.md +++ b/docs/resources/context.md @@ -88,6 +88,7 @@ YAML ```hcl resource "codefresh_context" "test-secret-yaml" { name = "my-shared-secret-yaml" + decrypt_spec = false spec { # NOTE: The `-` from secret-yaml is stripped because the character is not allowed in Field name # File passed MUST be a valid YAML @@ -96,6 +97,82 @@ resource "codefresh_context" "test-secret-yaml" { } ``` +#### AWS S3 storage context + +```hcl +resource "codefresh_context" "test-s3" { + name = "my-s3-context" + + decrypt_spec = false + + spec { + storages3 { + data { + auth { + type = "basic" + json_config = {accessKeyId = "key", secretAccessKey = "secret"} + } + } + } + } +} +``` + +#### Azure file storage context + +```hcl +resource "codefresh_context" "test-azure" { + name = "my-azure-file-context" + + decrypt_spec = false + + spec { + storageazuref { + data { + auth { + type = "basic" + account_name = "account" + account_key = "key" + } + } + } + } +} +``` + +#### Google cloud storage context + +```hcl +resource "codefresh_context" "test-google-cloud-storage" { + name = "my-gcs-context" + + spec { + storagegc { + data { + auth { + type = "basic" + json_config = jsondecode(< ## Schema diff --git a/templates/resources/context.md.tmpl b/templates/resources/context.md.tmpl index baee294..a1dcaab 100644 --- a/templates/resources/context.md.tmpl +++ b/templates/resources/context.md.tmpl @@ -88,6 +88,7 @@ YAML ```hcl resource "codefresh_context" "test-secret-yaml" { name = "my-shared-secret-yaml" + decrypt_spec = false spec { # NOTE: The `-` from secret-yaml is stripped because the character is not allowed in Field name # File passed MUST be a valid YAML @@ -96,4 +97,80 @@ resource "codefresh_context" "test-secret-yaml" { } ``` +#### AWS S3 storage context + +```hcl +resource "codefresh_context" "test-s3" { + name = "my-s3-context" + + decrypt_spec = false + + spec { + storages3 { + data { + auth { + type = "basic" + json_config = {accessKeyId = "key", secretAccessKey = "secret"} + } + } + } + } +} +``` + +#### Azure file storage context + +```hcl +resource "codefresh_context" "test-azure" { + name = "my-azure-file-context" + + decrypt_spec = false + + spec { + storageazuref { + data { + auth { + type = "basic" + account_name = "account" + account_key = "key" + } + } + } + } +} +``` + +#### Google cloud storage context + +```hcl +resource "codefresh_context" "test-google-cloud-storage" { + name = "my-gcs-context" + + spec { + storagegc { + data { + auth { + type = "basic" + json_config = jsondecode(<