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

Add initial secret manager property source implementation #2168

Merged
merged 9 commits into from
Feb 7, 2020

Conversation

dzou
Copy link
Contributor

@dzou dzou commented Feb 2, 2020

This is the initial scaffolding for setting up Secret Manager as a bootstrap property source.

In progress #2115.

@codecov
Copy link

codecov bot commented Feb 2, 2020

Codecov Report

Merging #2168 into master will decrease coverage by 0.26%.
The diff coverage is 0%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master    #2168      +/-   ##
============================================
- Coverage     72.62%   72.35%   -0.27%     
- Complexity     1911     1954      +43     
============================================
  Files           245      251       +6     
  Lines          7038     7167     +129     
  Branches        725      735      +10     
============================================
+ Hits           5111     5186      +75     
- Misses         1589     1645      +56     
+ Partials        338      336       -2
Flag Coverage Δ Complexity Δ
#integration ? ?
#unittests 72.35% <0%> (-0.27%) 1954 <0> (+43)
Impacted Files Coverage Δ Complexity Δ
...retmanager/SecretManagerPropertySourceLocator.java 0% <0%> (ø) 0 <0> (?)
...ure/secretmanager/SecretManagerPropertySource.java 0% <0%> (ø) 0 <0> (?)
...anager/GcpSecretManagerBootstrapConfiguration.java 0% <0%> (ø) 0 <0> (?)
...gure/secretmanager/GcpSecretManagerProperties.java 0% <0%> (ø) 0 <0> (?)
...nner/core/mapping/SpannerCompositeKeyProperty.java 80.88% <0%> (-1.21%) 37% <0%> (ø)
...nner/core/convert/StructPropertyValueProvider.java 87.87% <0%> (-0.7%) 12% <0%> (-1%)
...oud/gcp/data/datastore/core/DatastoreTemplate.java 87.25% <0%> (-0.63%) 120% <0%> (ø)
...anner/core/admin/SpannerDatabaseAdminTemplate.java 82.81% <0%> (-0.27%) 20% <0%> (ø)
...data/spanner/repository/query/SqlSpannerQuery.java 84.8% <0%> (-0.24%) 29% <0%> (ø)
...t/ConverterAwareMappingSpannerEntityProcessor.java 73.58% <0%> (ø) 22% <0%> (ø) ⬇️
... and 16 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 26e9adf...45d9f2b. Read the comment docs.

.build();

AccessSecretVersionResponse response = client.accessSecretVersion(secretVersionName);
return response.getPayload().getData().toStringUtf8();

Choose a reason for hiding this comment

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

Should probably document it in SpringBoot that only "string" secrets are accepted. Secret Manager accepts a wide character set for payload values.

Copy link
Contributor

Choose a reason for hiding this comment

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

public Object getProperty(String name)
So, I don't think we really need to convert all values to Strings. @dzou WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah good point. I could just leave it as the SecretPayload which would be more flexible. Users would then be injecting/reading SecretPayload from the environment properties (instead of us forcing strings) and can convert the values to string or their preferred payload.

Do you feel this is a reasonable approach?

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, I think there might be problems with mapping to @ConfigurationProperties classes that have Strings and other basic Java types. It's something that we should test.

Copy link
Collaborator

Choose a reason for hiding this comment

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

You wouldn't want SecretPayload leaking into user code.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Spring does the conversion automatically. If there isn't a SecretPayload encode/decoder it would fail.

Copy link
Contributor Author

@dzou dzou Feb 3, 2020

Choose a reason for hiding this comment

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

Ohh ok, this SecretPayload object can really just be converted to two options: String or byte[].

@spencergibb - How do I hook into this encode/decoder concept? (like which interfaces do I implement/register?) Is it possible for me to override getProperty(String propertyName, Class<T> targetType) such that if targetType is String I decode to string, and if the target type is byte[] I decode to byte[]?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

String and byte[] properties are now supported through registering custom SecretPayload converters. See: https://github.com/spring-cloud/spring-cloud-gcp/pull/2168/files#diff-04e47cc423dee80f0eb313f527e9f5b2R74-R90

Copy link
Contributor

@meltsufin meltsufin left a comment

Choose a reason for hiding this comment

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

This is a great start!
The obvious things that are missing, but could be in different PRs: docs, sample app.


@Configuration
@EnableConfigurationProperties(GcpSecretManagerProperties.class)
@ConditionalOnProperty("spring.cloud.gcp.secretmanager.enabled")
Copy link
Contributor

Choose a reason for hiding this comment

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

havingValue = "true"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this should be disabled by default like our config management; otherwise all applications would be making RPCs to secret manager to list the secrets even if none exist.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Are there any required properties in GcpSecretManagerProperties?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are no required properties.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added matchIfMIssing= true to enable by default and then also added @ConditionalOnClass(SecretManagerServiceClient.class) to make conditional on the addition of secret manager class to classpath.


super(propertySourceName, client);

Map<String, Object> propertiesMap = initializePropertiesMap(client, projectIdProvider.getProjectId());
Copy link
Contributor

Choose a reason for hiding this comment

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

You're loading properties in the constructor. Would it make sense to allow users to reload the properties by providing a public "refresh" method to do it?

Copy link
Contributor

Choose a reason for hiding this comment

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

More generally, test behavior of a@RefreshScope property in the sample. Ideally, triggering a refresh event should get those properties to expire.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Regarding the refresh functionality, can I verify this in a separate PR? This would be something to verify through the addition of the sample app and a Properties class.

.build();

AccessSecretVersionResponse response = client.accessSecretVersion(secretVersionName);
return response.getPayload().getData().toStringUtf8();
Copy link
Contributor

Choose a reason for hiding this comment

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

public Object getProperty(String name)
So, I don't think we really need to convert all values to Strings. @dzou WDYT?

/**
* Creates the secret with the specified payload if the secret does not already exist.
*/
private void createSecret(String secretId, String payload) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This reminds me, do we want to create a SecretManagerTemplate? You would also put the secret reading in there.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1, and this would also go into the new module.

Copy link
Contributor

@elefeint elefeint left a comment

Choose a reason for hiding this comment

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

Can you make sure this property source supports injecting and refreshing @RefreshScope annotated properties?
https://cloud.spring.io/spring-cloud-commons/multi/multi__spring_cloud_context_application_context_services.html#refresh-scope

import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;

public class SecretManagerPropertySourceLocator implements PropertySourceLocator {
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it makes sense to put this class into a new spring-cloud-gcp-secretmanager module. Then we could give people a single-dependency starter instead of having them bring in the autoconfiguration module combined with explicitly setting a property.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't it just be conditional on class of the secret manager api rather than setting a property?

Copy link
Contributor

Choose a reason for hiding this comment

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

+1

Copy link
Contributor Author

@dzou dzou Feb 5, 2020

Choose a reason for hiding this comment

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

Sounds good, we can discuss moving this in the next PR with the addition of secret template which will necessitate creating a the new secretmanager module. For now I am keeping the door open if we decide not to add a template class then this will mirror how our spring config support is organized (in which all the relevant classes are kept in autoconfigure/config).

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add a tracking issue to add a template?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this is tracked in #2176


super(propertySourceName, client);

Map<String, Object> propertiesMap = initializePropertiesMap(client, projectIdProvider.getProjectId());
Copy link
Contributor

Choose a reason for hiding this comment

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

More generally, test behavior of a@RefreshScope property in the sample. Ideally, triggering a refresh event should get those properties to expire.

/**
* Creates the secret with the specified payload if the secret does not already exist.
*/
private void createSecret(String secretId, String payload) {
Copy link
Contributor

Choose a reason for hiding this comment

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

+1, and this would also go into the new module.

HashMap<String, Object> secretsMap = new HashMap<>();
for (Secret secret : response.iterateAll()) {
String secretId = extractSecretId(secret);
ByteString secretPayload = getSecretPayload(client, projectId, secretId);

Choose a reason for hiding this comment

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

Non-blocking, but a potential future optimization would be to parallelize this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted, added to #2176 to track.

SecretVersionName secretVersionName = SecretVersionName.newBuilder()
.setProject(projectId)
.setSecret(secretId)
.setSecretVersion(LATEST_VERSION_STRING)

Choose a reason for hiding this comment

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

I think people will want to access specific secret versions. Defaulting to "latest" is not a production best practice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah good point, we'll definitely need an answer for this. I think we'll have to discuss a bit more in-depth with our team what is the best way to let users specify which secret versions they want to load into the environment; I guess we wouldn't want to load all versions of all secrets by default.

I added this point to #2176 to track; we will definitely implement this before releasing to users. I just want this PR to setup the initial scaffolding.

@dzou dzou mentioned this pull request Feb 5, 2020
8 tasks
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;

public class SecretManagerPropertySourceLocator implements PropertySourceLocator {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add a tracking issue to add a template?

@sonarcloud
Copy link

sonarcloud bot commented Feb 5, 2020

SonarCloud Quality Gate failed.

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities (and Security Hotspot 0 Security Hotspots to review)
Code Smell A 3 Code Smells

0.0% 0.0% Coverage
0.0% 0.0% Duplication

@dzou dzou requested a review from meltsufin February 6, 2020 16:25
@dzou
Copy link
Contributor Author

dzou commented Feb 6, 2020

@elefeint @meltsufin - PTAL when you guys get a chance :)

@dzou dzou merged commit 9c6cf54 into master Feb 7, 2020
@dzou dzou deleted the secret-manager branch February 7, 2020 15:52
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants