Skip to content

fix: codefresh_context disable decrypt when forbidDecrypt feature flag is set #156

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

Merged
merged 8 commits into from
Dec 17, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions codefresh/cfclient/client.go
Original file line number Diff line number Diff line change
@@ -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{}
40 changes: 35 additions & 5 deletions codefresh/cfclient/context.go
Original file line number Diff line number Diff line change
@@ -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) {
21 changes: 14 additions & 7 deletions codefresh/cfclient/current_account.go
Original file line number Diff line number Diff line change
@@ -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
}
}
2 changes: 1 addition & 1 deletion codefresh/context/storage.go
Original file line number Diff line number Diff line change
@@ -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)
}

2 changes: 1 addition & 1 deletion codefresh/provider.go
Original file line number Diff line number Diff line change
@@ -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
}
17 changes: 12 additions & 5 deletions codefresh/resource_context.go
Original file line number Diff line number Diff line change
@@ -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
}

4 changes: 2 additions & 2 deletions codefresh/resource_context_test.go
Original file line number Diff line number Diff line change
@@ -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
}
77 changes: 77 additions & 0 deletions docs/resources/context.md
Original file line number Diff line number Diff line change
@@ -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(<<EOF
{
"type": "service_account",
"project_id": "PROJECT_ID",
"private_key_id": "KEY_ID",
"private_key": "-----BEGIN PRIVATE KEY-----\nPRIVATE_KEY\n-----END PRIVATE KEY-----\n",
"client_email": "SERVICE_ACCOUNT_EMAIL",
"client_id": "CLIENT_ID",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/SERVICE_ACCOUNT_EMAIL"
}
EOF
)
}
}
}
}
}
```

<!-- schema generated by tfplugindocs -->
## Schema

77 changes: 77 additions & 0 deletions templates/resources/context.md.tmpl
Original file line number Diff line number Diff line change
@@ -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(<<EOF
{
"type": "service_account",
"project_id": "PROJECT_ID",
"private_key_id": "KEY_ID",
"private_key": "-----BEGIN PRIVATE KEY-----\nPRIVATE_KEY\n-----END PRIVATE KEY-----\n",
"client_email": "SERVICE_ACCOUNT_EMAIL",
"client_id": "CLIENT_ID",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/SERVICE_ACCOUNT_EMAIL"
}
EOF
)
}
}
}
}
}
```

{{ .SchemaMarkdown | trimspace }}