Skip to content
This repository has been archived by the owner on Dec 15, 2022. It is now read-only.

Commit

Permalink
Support []string and *[]string as sensitive fields
Browse files Browse the repository at this point in the history
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
  • Loading branch information
sergenyalcin committed Jan 11, 2022
1 parent 9152fb5 commit bce950c
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 37 deletions.
100 changes: 71 additions & 29 deletions pkg/resource/sensitive.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
}
}

Expand Down
85 changes: 81 additions & 4 deletions pkg/resource/sensitive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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{},
Expand Down Expand Up @@ -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) {
Expand Down
14 changes: 10 additions & 4 deletions pkg/types/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,19 +155,25 @@ 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.
// Data will be loaded from the referenced secret key.
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"
}
}
Expand Down

0 comments on commit bce950c

Please sign in to comment.