-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Fixes #21259: Don't store null secrets in Secrets Manager #27839
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
base: main
Are you sure you want to change the base?
Changes from all commits
23eedf8
087a01c
4eea0c2
a7cd611
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -33,6 +33,16 @@ protected ExternalSecretsManager( | |||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||
| protected String storeValue(String fieldName, String value, String secretId, boolean store) { | ||||||||||||||||||||||||||||||
| String fieldSecretId = buildSecretId(false, secretId, fieldName.toLowerCase(Locale.ROOT)); | ||||||||||||||||||||||||||||||
| // Issue #21259: a null/empty value means "no credential". Handle this case BEFORE | ||||||||||||||||||||||||||||||
| // calling isSecret() — isSecret() invokes startsWith() on the value and would NPE | ||||||||||||||||||||||||||||||
| // on null. Returning null here ensures the entity does not carry a stale secret:/ | ||||||||||||||||||||||||||||||
| // reference to a deleted secret. | ||||||||||||||||||||||||||||||
|
Comment on lines
+36
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Quality: Verbose comments explaining the fix — prefer self-documenting codePer the project's coding guidelines, code should be self-documenting with comments reserved for complex business logic or workarounds. The four-line block comments at lines 36-39, 58-61, and 414-416 essentially re-state what the code already makes clear. Consider trimming these to a single-line issue reference (e.g., Was this helpful? React with 👍 / 👎 | Reply |
||||||||||||||||||||||||||||||
| if (Objects.isNull(value) || value.isEmpty()) { | ||||||||||||||||||||||||||||||
| if (store) { | ||||||||||||||||||||||||||||||
| upsertSecret(fieldSecretId, value); | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mhmh I think this is hard to follow. Why would we say |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| // check if value does not start with 'config:' only String can have password annotation | ||||||||||||||||||||||||||||||
| if (Boolean.FALSE.equals(isSecret(value))) { | ||||||||||||||||||||||||||||||
| if (store) { | ||||||||||||||||||||||||||||||
|
|
@@ -45,12 +55,22 @@ protected String storeValue(String fieldName, String value, String secretId, boo | |||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| public void upsertSecret(String secretName, String secretValue) { | ||||||||||||||||||||||||||||||
| String sanitizedValue = cleanNullOrEmpty(secretValue); | ||||||||||||||||||||||||||||||
| // Issue #21259: when the value is null/empty, the user's intent is "remove this | ||||||||||||||||||||||||||||||
| // credential" — delete the backing secret instead of storing the literal string | ||||||||||||||||||||||||||||||
| // "null". This also satisfies the GCP/Kubernetes empty-string rejection constraint | ||||||||||||||||||||||||||||||
| // (PR #25224) by simply not calling upsert with an empty value. | ||||||||||||||||||||||||||||||
| if (Objects.isNull(secretValue) || secretValue.isEmpty()) { | ||||||||||||||||||||||||||||||
| if (existSecret(secretName)) { | ||||||||||||||||||||||||||||||
| deleteSecretInternal(secretName); | ||||||||||||||||||||||||||||||
| sleep(); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+60
to
+66
|
||||||||||||||||||||||||||||||
| // "null". This also satisfies the GCP/Kubernetes empty-string rejection constraint | |
| // (PR #25224) by simply not calling upsert with an empty value. | |
| if (Objects.isNull(secretValue) || secretValue.isEmpty()) { | |
| if (existSecret(secretName)) { | |
| deleteSecretInternal(secretName); | |
| sleep(); | |
| } | |
| // "null". Avoid a pre-delete existence check here because existSecret() relies on | |
| // getSecret() and suppresses all exceptions, which can incorrectly turn transient | |
| // provider errors into a silent "does not exist" result and leave stale secrets | |
| // orphaned even though the entity field has been cleared. | |
| if (Objects.isNull(secretValue) || secretValue.isEmpty()) { | |
| deleteSecretInternal(secretName); | |
| sleep(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Edge Case: storeValue with store=false and null value still computes fieldSecretId
When
storeValueis called with a nullvalue, the method still callsbuildSecretId(false, secretId, fieldName.toLowerCase(Locale.ROOT))on line 35 before hitting the null guard on line 40. This is harmless (just a wasted string concatenation), but moving the null guard above thebuildSecretIdcall would be marginally cleaner and make the early-return intent clearer.Was this helpful? React with 👍 / 👎 | Reply
gitar fixto apply this suggestion