diff --git a/pkg/resource/sensitive.go b/pkg/resource/sensitive.go index 447668d6..72e77422 100644 --- a/pkg/resource/sensitive.go +++ b/pkg/resource/sensitive.go @@ -45,6 +45,8 @@ const ( // our ability to build tfstate back. prefixAttribute = "attribute." + pluralSuffix = "s" + errGetAdditionalConnectionDetails = "cannot get additional connection details" errFmtCannotOverrideExistingKey = "overriding a reserved connection key (%q) is not allowed" ) @@ -102,7 +104,7 @@ func GetConnectionDetails(attr map[string]interface{}, tr Terraformed, cfg *conf // GetSensitiveAttributes returns strings matching provided field paths in the // input data. // See the unit tests for examples. -func GetSensitiveAttributes(from map[string]interface{}, mapping map[string]string) (map[string][]byte, error) { +func GetSensitiveAttributes(from map[string]interface{}, mapping map[string]string) (map[string][]byte, error) { //nolint: gocyclo if len(mapping) == 0 { return nil, nil } @@ -124,23 +126,37 @@ func GetSensitiveAttributes(from map[string]interface{}, mapping map[string]stri if v == nil { continue } - s, ok := v.(string) - if !ok { + + switch s := v.(type) { + case []interface{}: + for i, e := range s { + // Note(turkenh): k8s secrets uses a strict regex to validate secret + // keys which does not allow having brackets inside. So, we need to + // do a conversion to be able to store as connection secret keys. + // See https://github.com/crossplane/terrajet/pull/94 for + // more details. + k, err := fieldPathToSecretKey(fp) + if err != nil { + return nil, errors.Wrapf(err, "cannot convert fieldpath %q to secret key", fp) + } + k = strings.TrimSuffix(k, pluralSuffix) + if vals == nil { + vals = map[string][]byte{} + } + vals[fmt.Sprintf("%s%s.%d", prefixAttribute, k, i)] = []byte(fmt.Sprintf("%v", e)) + } + case string: + k, err := fieldPathToSecretKey(fp) + if err != nil { + return nil, errors.Wrapf(err, "cannot convert fieldpath %q to secret key", fp) + } + if vals == nil { + vals = map[string][]byte{} + } + vals[fmt.Sprintf("%s%s", prefixAttribute, k)] = []byte(s) + default: return nil, errors.Errorf(errFmtCannotGetStringForFieldPath, fp) } - // Note(turkenh): k8s secrets uses a strict regex to validate secret - // keys which does not allow having brackets inside. So, we need to - // do a conversion to be able to store as connection secret keys. - // See https://github.com/crossplane/terrajet/pull/94 for - // more details. - k, err := fieldPathToSecretKey(fp) - if err != nil { - return nil, errors.Wrapf(err, "cannot convert fieldpath %q to secret key", fp) - } - if vals == nil { - vals = map[string][]byte{} - } - vals[fmt.Sprintf("%s%s", prefixAttribute, k)] = []byte(s) } } return vals, nil @@ -181,21 +197,47 @@ func GetSensitiveParameters(ctx context.Context, client SecretClient, from runti if v == nil { continue } - sel := &v1.SecretKeySelector{} - if err = pavedJSON.GetValueInto(expandedJSONPath, sel); err != nil { + + switch v.(type) { + case []map[string]interface{}: + sel := &[]v1.SecretKeySelector{} + if err = pavedJSON.GetValueInto(expandedJSONPath, sel); err != nil { + return errors.Wrapf(err, "cannot get SecretKeySelector from xp resource for fieldpath %q", expandedJSONPath) + } + var sensitives []interface{} + for _, s := range *sel { + sensitive, err = client.GetSecretValue(ctx, s) + if err != nil { + return errors.Wrapf(err, "cannot get secret value for %v", sel) + } + sensitives = append(sensitives, string(sensitive)) + } + expTF, err := expandedTFPath(expandedJSONPath, mapping) + if err != nil { + return err + } + if err = pavedTF.SetValue(expTF, sensitives); err != nil { + return errors.Wrapf(err, "cannot set string as terraform attribute for fieldpath %q", tfPath) + } + case map[string]interface{}: + sel := &v1.SecretKeySelector{} + if err = pavedJSON.GetValueInto(expandedJSONPath, sel); err != nil { + return errors.Wrapf(err, "cannot get SecretKeySelector from xp resource for fieldpath %q", expandedJSONPath) + } + sensitive, err = client.GetSecretValue(ctx, *sel) + if err != nil { + return errors.Wrapf(err, "cannot get secret value for %v", sel) + } + expTF, err := expandedTFPath(expandedJSONPath, mapping) + if err != nil { + return err + } + if err = pavedTF.SetString(expTF, string(sensitive)); err != nil { + return errors.Wrapf(err, "cannot set string as terraform attribute for fieldpath %q", tfPath) + } + default: return errors.Wrapf(err, "cannot get SecretKeySelector from xp resource for fieldpath %q", expandedJSONPath) } - sensitive, err = client.GetSecretValue(ctx, *sel) - if err != nil { - return errors.Wrapf(err, "cannot get secret value for %v", sel) - } - expTF, err := expandedTFPath(expandedJSONPath, mapping) - if err != nil { - return err - } - if err = pavedTF.SetString(expTF, string(sensitive)); err != nil { - return errors.Wrapf(err, "cannot set string as terraform attribute for fieldpath %q", tfPath) - } } } diff --git a/pkg/resource/sensitive_test.go b/pkg/resource/sensitive_test.go index cd977aff..a66bd38a 100644 --- a/pkg/resource/sensitive_test.go +++ b/pkg/resource/sensitive_test.go @@ -82,10 +82,6 @@ var ( ) func TestGetConnectionDetails(t *testing.T) { - testInput := map[string]interface{}{} - if err := json.JSParser.Unmarshal(testData, &testInput); err != nil { - t.Fatalf("cannot unmarshall test data: %v", err) - } type args struct { tr Terraformed cfg *config.Resource @@ -125,6 +121,32 @@ func TestGetConnectionDetails(t *testing.T) { }, }, }, + "SecretList": { + args: args{ + tr: &fake.Terraformed{ + MetadataProvider: fake.MetadataProvider{ + ConnectionDetailsMapping: map[string]string{ + "top_level_secrets": "status.atProvider.topLevelSecrets", + }, + }, + }, + cfg: config.DefaultResource("terrajet_resource", nil), + data: map[string]interface{}{ + "top_level_secrets": []interface{}{ + "val1", + "val2", + "val3", + }, + }, + }, + want: want{ + out: map[string][]byte{ + "attribute.top_level_secret.0": []byte("val1"), + "attribute.top_level_secret.1": []byte("val2"), + "attribute.top_level_secret.2": []byte("val3"), + }, + }, + }, "OnlyAdditionalConnectionDetails": { args: args{ tr: &fake.Terraformed{}, @@ -416,6 +438,61 @@ func TestGetSensitiveParameters(t *testing.T) { }, }, }, + "SingleNoWildcardWithSlice": { + args: args{ + clientFn: func(client *mocks.MockSecretClient) { + client.EXPECT().GetSecretValue(gomock.Any(), gomock.Eq(xpv1.SecretKeySelector{ + SecretReference: xpv1.SecretReference{ + Name: "db-passwords", + Namespace: "crossplane-system", + }, + Key: "admin", + })).Return([]byte("admin_pwd"), nil) + client.EXPECT().GetSecretValue(gomock.Any(), gomock.Eq(xpv1.SecretKeySelector{ + SecretReference: xpv1.SecretReference{ + Name: "db-passwords", + Namespace: "crossplane-system", + }, + Key: "system", + })).Return([]byte("system_pwd"), nil) + }, + from: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "spec": map[string]interface{}{ + "forProvider": map[string]interface{}{ + "passwordsSecretRef": []map[string]interface{}{ + { + "name": "db-passwords", + "namespace": "crossplane-system", + "key": "admin", + }, + { + "name": "db-passwords", + "namespace": "crossplane-system", + "key": "system", + }, + }, + }, + }, + }, + }, + into: map[string]interface{}{ + "some_other_key": "some_other_value", + }, + mapping: map[string]string{ + "db_passwords": "spec.forProvider.passwordsSecretRef", + }, + }, + want: want{ + out: map[string]interface{}{ + "some_other_key": "some_other_value", + "db_passwords": []interface{}{ + "admin_pwd", + "system_pwd", + }, + }, + }, + }, "MultipleNoWildcard": { args: args{ clientFn: func(client *mocks.MockSecretClient) { diff --git a/pkg/types/builder.go b/pkg/types/builder.go index 5c3d0bd4..ea9201d9 100644 --- a/pkg/types/builder.go +++ b/pkg/types/builder.go @@ -155,8 +155,9 @@ func (g *Builder) buildResource(res *schema.Resource, cfg *config.Resource, tfPa sfx := "SecretRef" cfg.Sensitive.AddFieldPath(fieldPathWithWildcard(tfPaths), "spec.forProvider."+fieldPathWithWildcard(xpPaths)+sfx) // todo(turkenh): do we need to support other field types as sensitive? - if fieldType.String() != "string" && fieldType.String() != "*string" { - return nil, nil, fmt.Errorf(`got type %q for field %q, only types "string" and "*string" supported as sensitive`, fieldType.String(), fieldNameCamel) + if fieldType.String() != "string" && fieldType.String() != "*string" && fieldType.String() != "[]string" && + fieldType.String() != "[]*string" { + return nil, nil, fmt.Errorf(`got type %q for field %q, only types "string", "*string", []string and []*string supported as sensitive`, fieldType.String(), fieldNameCamel) } // Replace a parameter field with secretKeyRef if it is sensitive. // If it is an observation field, it will be dropped. @@ -164,10 +165,15 @@ func (g *Builder) buildResource(res *schema.Resource, cfg *config.Resource, tfPa fieldNameCamel += sfx tfTag = "-" - fieldType = typeSecretKeySelector + switch fieldType.String() { + case "string", "*string": + fieldType = typeSecretKeySelector + case "[]string", "[]*string": + fieldType = types.NewSlice(typeSecretKeySelector) + } jsonTag = name.NewFromCamel(fieldNameCamel).LowerCamelComputed if sch.Optional { - fieldType = types.NewPointer(typeSecretKeySelector) + fieldType = types.NewPointer(fieldType) jsonTag += ",omitempty" } }