Skip to content
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

feat: Support plaintext secrets #1102

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

MartinAltmayerTMH
Copy link
Contributor

Fixes #1096.

This PR adds a new option plaintext. If set, we will expect the SOPS file to contain just a single key data on toplevel and will sync only the value below this key to the secret. Note that SOPS files always contain a toplevel object, so it was previously impossible to sync just a string to the secret.

plaintext

Open points:

  • Should the key data be configurable? Or should we just require that there is exactly one key, no matter its name?
  • How does this play with the existing convertToJson and convertToYaml options? Currently I ignore those, if plaintext is true.

@markussiebert Are you ok with the general direction of this PR? If so, I would add some tests for the new functionality.

@MartinAltmayerTMH MartinAltmayerTMH changed the title Support plaintext secrets feat: Support plaintext secrets Jan 27, 2025
Copy link

codecov bot commented Jan 27, 2025

Codecov Report

Attention: Patch coverage is 31.57895% with 26 lines in your changes missing coverage. Please review.

Project coverage is 68.71%. Comparing base (cd7673a) to head (3e36ee6).

Files with missing lines Patch % Lines
lambda/main.go 31.57% 23 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1102      +/-   ##
==========================================
- Coverage   71.80%   68.71%   -3.09%     
==========================================
  Files           3        3              
  Lines         461      489      +28     
==========================================
+ Hits          331      336       +5     
- Misses         91      112      +21     
- Partials       39       41       +2     
Flag Coverage Δ
go-lambda 68.71% <31.57%> (-3.09%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@markussiebert
Copy link
Contributor

Right now the integ tests can be a bit painful, they should self mutate in pipeline. Don't take asset hashes to serious. I can fix them If we merge this :-)

@@ -269,22 +270,36 @@ func (a AWS) syncSopsToSecretsmanager(ctx context.Context, event cfn.Event) (phy
}
}

if resourceProperties.ConvertToJSON == "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe the diff is wrong, but for me it looks like you are replacing ConvertToJSON? I think we should keep this option :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is indeed an artifact from the diff. I did not change the ConvertToJson logic, except for adding one level of indentation.

Here is the new code:

if resourceProperties.Plaintext == "" {
	resourceProperties.Plaintext = "false"
}
resourcePropertiesPlaintext, err := strconv.ParseBool(resourceProperties.Plaintext)
if err != nil {
	return tempArn, nil, fmt.Errorf("failed to parse bool:\n%v", err)
}
if resourcePropertiesPlaintext {
	decryptedContent, err = convertToPlainText(finalInterface)
	if err != nil {
		return tempArn, nil, fmt.Errorf("failed to convert to plaintext: \n%v", err)
	}
} else {
 ... ConvertToJson and ConvertToYaml logic goes here ...
} 

Comment on lines +483 to +501
func convertToPlainText(input interface{}) ([]byte, error) {
v := reflect.ValueOf(input)
key := reflect.ValueOf("data")
value := v.MapIndex(key)

if !value.IsValid() {
return nil, fmt.Errorf("if plaintext is set to true, secret must contain exactly one key '%s'", key.Interface())
}

switch v := value.Interface().(type) {
case string:
return []byte(v), nil
case int:
return []byte(strconv.Itoa(v)), nil
default:
return nil, fmt.Errorf("failed to convert value to bytes: %v", v)
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, your implementation checks for the existence of a "data" key. If it exists, we use its value; otherwise, we throw an error. This approach seems fine to me.

However, I'm wondering if we need to distinguish the type of the interface. Can't we just write the bytes we receive without converting them? I am referring to this issue. Perhaps it is possible to handle any "type" in the interface as a byte array?

I'll need to investigate what SOPS produces for "binary files."

Copy link
Contributor

@markussiebert markussiebert Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, we need to handle binaries when passing them to the AWS Secrets Manager API. In such cases, we should set the content to the SecretBinary field instead of the SecretString field. However, I believe we should consider this here as well. I just checked with random binary data; it will also be encoded into the YAML file with the data key.

So what do you think, can we just handle the interface as byte[], without the switch statement? Than it would be more of a removeDataKey method.

Copy link
Contributor Author

@MartinAltmayerTMH MartinAltmayerTMH Jan 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds like a good idea to use the same approach for binary secrets (though I'd like to keep the actual support for binary, i.e. using SecretBinary, out of this PR).

I don't see how a generic conversion to byte[] could work, though: Currently, when my implementation starts, it already has an interface (read from the JSON/Yaml file), so the value below data might be anything and there is no obvious way to convert this to byte[]. For example, I could use json.Marshal. But this would encode the string 12341234 as "12341234" and thus change the secret content.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will look into this tomorrow. Maybe you can give me access to your fork?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Plaintext secrets
2 participants