-
Notifications
You must be signed in to change notification settings - Fork 692
Create protocol for specifying secrets' project and versions #2302
Conversation
Codecov Report
@@ Coverage Diff @@
## master #2302 +/- ##
============================================
- Coverage 80.95% 73.25% -7.70%
+ Complexity 2309 2072 -237
============================================
Files 258 258
Lines 7456 7471 +15
Branches 762 775 +13
============================================
- Hits 6036 5473 -563
- Misses 1100 1641 +541
- Partials 320 357 +37
Continue to review full report at Codecov.
|
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.
Looks great, just a couple of typos and a question (which you also had in PR description) about what to do with the old one.
...g/springframework/cloud/gcp/autoconfigure/secretmanager/SecretManagerPropertyIdentifier.java
Outdated
Show resolved
Hide resolved
...g/springframework/cloud/gcp/autoconfigure/secretmanager/SecretManagerPropertyIdentifier.java
Outdated
Show resolved
Hide resolved
this.versions); | ||
CompositePropertySource compositePropertySource = new CompositePropertySource(SECRET_MANAGER_NAME); | ||
|
||
compositePropertySource.addPropertySource( |
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.
Do we want to have both resolvable? The new version is more comprehensive. Or are we doing it for backwards compatibility?
Perhaps we could automatically configure only the newest property source, and provide a "legacy" property for loading the old one?
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.
Yeah I wasn't sure how to handle this. I think would prefer if we only have the new version. Maybe if it's ok, I'd like to just remove the existing functionality and replace it with the new method. @meltsufin
I guess it doesn't make sense for both to co-exist since the legacy one already does the work of downloading all the secrets, and then if the new system exists they might redownload secrets they already loaded into their properties.
my-application-secret=${secrets.application-secret} | ||
# This demonstrates multiple ways of loading the same application secret using template syntax. | ||
my-application-secret-1=${secrets.application-secret} | ||
my-application-secret-2=${gcp-secret/projects/my-kubernetes-codelab-217414/secrets/application-secret} |
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.
Consider different prefix usage
# This demonstrates multiple ways of loading the same application secret using template syntax. | ||
my-application-secret-1=${secrets.application-secret} | ||
my-application-secret-2=${gcp-secret/projects/my-kubernetes-codelab-217414/secrets/application-secret} | ||
my-application-secret-3=${gcp-secret/projects/my-kubernetes-codelab-217414/secrets/application-secret/version/latest} |
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.
Can also be a project number for projects/ value
...g/springframework/cloud/gcp/autoconfigure/secretmanager/SecretManagerPropertyIdentifier.java
Outdated
Show resolved
Hide resolved
Redid the PR to just remove the legacy way of doing things and replace with new. If you feel good about this, I can move forward with doc updates. |
private static final String LATEST_VERSION_STRING = "latest"; | ||
|
||
private final Map<String, Object> properties; | ||
private static final String GCP_SECRET_PREFIX = "gcp-secret/"; |
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.
Did the Secrets Manager team comment on this prefix? Have you looked into using sm://
?
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.
Colon can't be used in a property name because it means something special in SPEL. I wouldn't want to try to escape the colon either; I think it would look worse with the back slashes.
Am open to other secret prefixes though.
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.
I wonder if we can tap into that "special meaning" and configure it for our purpose.
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.
Colon is like the null-safety operator. If the left side of the colon is evaluated to null, then it defaults to the value on the right side.
So if i do gs://blah/blah
- it firsts evaluates gs
which it finds to be null, then returns the string //blah/blah
.
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.
This is only the case if SPEL is used, such as with @Value
, right?
Notice that with @Value
on a Resource
it's interpreted as a protocol instead.
For example, see the "gs://" implementation we have for Storage.
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.
Ran the code through the debugger, the colon is a special character; i don't think this is configurable. I think we should avoid it to avoid unexpected behavior in the future with null values.
I would be open to other choices of prefixes, maybe sm|
or gcp-sm|
these seem to all work. I recall Seth suggested using |
as an alternative.
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.
Should we ever return null
though? Is there something wrong with throwing an exception for a property that doesn't exist?
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.
Yeah I can see the argument where if a user specifies a secret using our syntax, there is the expectation that a secret is there; if it's missing then that's most likely an error and they probably don't want null there.
I would be comfortable with either approach. We can still throw exception using |
too by the way.
To me it seems the tradeoff here is purely cosmetic. I.e. if the cosmetic benefits of using :
outweighs the risk that we need to change behavior to return null
someday.
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.
What's the prefix that the Secret Manager team suggested we use? There's value in being compatible with other libraries for this product. Let's confirm with the product team.
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.
Based on discussion offline, looks like sm://
has precedent of being used, so I just changed it to that for now.
...a/org/springframework/cloud/gcp/autoconfigure/secretmanager/SecretManagerPropertySource.java
Outdated
Show resolved
Hide resolved
...va/org/springframework/cloud/gcp/autoconfigure/secretmanager/GcpSecretManagerProperties.java
Show resolved
Hide resolved
return response.getPayload().getData(); | ||
} | ||
catch (NotFoundException e) { | ||
return null; |
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.
Wouldn't this be considered an error if we got here? Not sure we should swallow the exception.
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.
Sure, let's throw exception, this might be good for supporting the gcp-sm://
prefix per our discussion above.
...a/org/springframework/cloud/gcp/autoconfigure/secretmanager/SecretManagerPropertySource.java
Outdated
Show resolved
Hide resolved
...ava/org/springframework/cloud/gcp/autoconfigure/secretmanager/SecretPropertySourceTests.java
Outdated
Show resolved
Hide resolved
...ava/org/springframework/cloud/gcp/autoconfigure/secretmanager/SecretPropertySourceTests.java
Outdated
Show resolved
Hide resolved
...a/org/springframework/cloud/gcp/autoconfigure/secretmanager/SecretManagerPropertySource.java
Outdated
Show resolved
Hide resolved
assertThat(secretIdentifier.getProject()).isEqualTo("my-project"); | ||
assertThat(secretIdentifier.getSecret()).isEqualTo("the-secret"); | ||
assertThat(secretIdentifier.getSecretVersion()).isEqualTo("3"); | ||
} |
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.
Missing a test for getSecretPayload
.
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.
We'll cover this directly in your configuration tests.
I don't think it's worth making it non-private to write tests for it unlike the parseProperty
method which had a lot more code.
@dzou Please make sure the sample app integration tests work as well. |
Done. |
@@ -48,7 +49,7 @@ | |||
@Configuration | |||
@EnableConfigurationProperties(GcpSecretManagerProperties.class) | |||
@ConditionalOnClass(SecretManagerServiceClient.class) | |||
@ConditionalOnProperty("spring.cloud.gcp.secretmanager.bootstrap.enabled") |
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.
Docs need to be updated following this change.
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.
Done, updated docs.
private static final String LATEST_VERSION_STRING = "latest"; | ||
|
||
private final Map<String, Object> properties; | ||
private static final String GCP_SECRET_PREFIX = "gcp-secret/"; |
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.
I'm not following fully. Let's discuss.
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.
The main feedback I have is that the SecretManagerTemlate
needs to be updated as well to accept fully qualified secret names.
...gframework/cloud/gcp/autoconfigure/secretmanager/GcpSecretManagerBootstrapConfiguration.java
Outdated
Show resolved
Hide resolved
secretsMap.put(secretsPrefix + secretId, secretPayload); | ||
} | ||
// Visible for Testing | ||
static SecretVersionName parseFromProperty(String property, GcpProjectIdProvider projectIdProvider) { |
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.
Should this move to SecretManagerTemplate
? We probably want to reconsider all those additional methods for specifying project and version and use the fully qualified secret identifiers instead.
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.
Done; I added these methods for getSecret, but for createSecret
and secretExists
it doesn't quite fit since those methods can accept project and secretId as valid inputs but are not fixed to a version.
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.
I think we can simplify further by removing the *Uri
methods and re-using the template in the property source.
* @return The secret payload as String | ||
*/ | ||
String getSecretString(String secretId, String versionName); | ||
String getSecretStringByUri(String secretUri); |
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.
Do we really need a separate method for getting by URI?
We can easily detect that something is a URI and do it all in the existing getSecretString(String)
.
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.
Done.
* @return The secret payload as {@link ByteString} | ||
*/ | ||
ByteString getSecretByteString(String secretId, String versionName, String projectId); | ||
byte[] getSecretBytesByUri(String secretUri); |
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.
Same here.
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.
Done.
...r/src/main/java/org/springframework/cloud/gcp/secretmanager/SecretManagerPropertySource.java
Show resolved
Hide resolved
SonarCloud Quality Gate failed.
|
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.
Looks good at this point.
One minor comment which you can ignore.
"Unrecognized format for specifying a GCP Secret Manager secret: " + input); | ||
} | ||
|
||
Assert.isTrue( |
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.
Assert.hasText
instead maybe?
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.
Gotcha, i'll change it.
Let me submit this and i'll send a followup cleanup PR; some other stuff I want to cleanup too.
Apologies in advance for commenting on such an old PR! I'm currently in the process of setting up a spring boot project on GCP app engine, which requires us us to use secrets manager as a replacement for env vars. One of my worries here is that the forced protocol prefix adds to vendor lock-in that we'd like to avoid. I was hoping I could add a custom prefix (e.g. I'm curious to hear why the protocol prefix is necessary. Is it just to denote to the starter that it should be looked up via secrets manager? Can this protocol prefix be configurable? |
@gsdatta -- The Hmm we could make the prefix configurable, but I have an alternative approach which may be simpler than a custom prefix like What about a default value feature? Something like:
So we could check if the secret is present/null; if so, default to the provided value This way you can keep the behavior with relying on env vars to be vendor-generic. Does that work for you? |
Makes sense. I think the default value structure would work for us quite well! Gives us the flexibility of overriding in certain environments as required when secrets manager may not be necessary. |
@gsdatta -- Thanks for the input. We're starting version We're going to start making all of our updates there. I filed a new feature request for this in our new repo: GoogleCloudPlatform/spring-cloud-gcp#213 |
@gsdatta -- Hey I did some research, I think we have a decent way of doing this already in the current implementation. You can try something like below; in the example
In this case, if foobar is not null, it will use the value of This uses the |
This creates a protocol for specifying secrets' projects and versions.
The protocol format uses the same conventions as https://github.com/GoogleCloudPlatform/github-actions/tree/master/get-secretmanager-secrets#inputs
Some further questions:
How to introduce this feature? And how to deprecate old way of specifying secrets? Can they co-exist for some time?
Which of the two approaches for specifying the default project do you like here: Allow to select the source projectId when using secret #2283 (comment) The OP makes a good point about how the
#4
form above might be better replaced by<secret-id>/<version>
with it defaulting to a project instead. However this approach comes with the drawback that it diverges from the github actions convention.Perhaps people need
<secret-id>/<version-id>
more based on discussion offline.