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

NIFI-12061: add SECRET_NAME parameter to avoid listing all secrets #9386

Open
wants to merge 11 commits into
base: support/nifi-1.x
Choose a base branch
from

Conversation

grishick
Copy link
Contributor

@grishick grishick commented Oct 14, 2024

Summary

NIFI-12061
Add Secret Name parameter to AwsSecretsManagerParameterProvider to allow the provider to work without ListSecrets permission. Original behavior where the provider lists all secrets and matches them to Secret Name Pattern is preserved by leaving Secret Name parameter undefined. The new behavior will skip listing secrets and ignore Secret Name Pattern only if Secret Name is provided. I made this change in Anetac's Nifi fork months ago and we are using it in production.

Related PR to main branch: #9483

Tracking

Please complete the following tracking steps prior to pull request creation.

Issue Tracking

Pull Request Tracking

  • Pull Request title starts with Apache NiFi Jira issue number, such as NIFI-12061
  • Pull Request commit message starts with Apache NiFi Jira issue number, as such NIFI-12061

Pull Request Formatting

  • Pull Request based on current revision of the support/nifi-1.x branch
  • Pull Request refers to a feature branch with one commit containing changes

Verification

Please indicate the verification steps performed prior to pull request creation.

Build

  • Build completed using mvn clean install -P contrib-check
    • JDK 1.8
    • JDK 11
    • JDK 17

Licensing

  • New dependencies are compatible with the Apache License 2.0 according to the License Policy
  • New dependencies are documented in applicable LICENSE and NOTICE files

Documentation

  • Documentation formatting appears as expected in rendered files

@EndzeitBegins
Copy link
Contributor

EndzeitBegins commented Oct 14, 2024

Thanks for your contribution @grishick.

Instead of one property overruling the other and SECRET_NAME_PATTERN remaining required even though it may not have any effect, what are your thoughts on introducing a "fetch mode" (or whatever name you see fit)?
With the current rather implicit approach, users might provide a SECRET_NAME_PATTERN of e.g. FOO_* and a SECRET_NAME of BAR simultaneously.
On first look it might not be obvious what the behaviour is; which property overrules which or are the results from both combined?

I'm not familiar with this component, but to me it reads like introducing a different "mode" of fetching secrets.
Personally, I'd think first having to select a "fetch mode" with allowed values "Secret by Name" and "All Secrets by Pattern" (or something along those lines) and only then have the corresponding properties available would be more intuitive.
Additionally, this keeps the door open for other "modes" in the future.

What are your thoughts on this?

Does it make sense to extend the existing documentation of SECRET_NAME_PATTERN to clarify that it's use required the permission to list secrets?

Copy link
Contributor

@exceptionfactory exceptionfactory left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution @grishick, and thanks for the thoughtful feedback @EndzeitBegins.

I concur with the recommendation from @EndzeitBegins. Having an alternative retrieval strategy that avoids having to list all secret names seems useful, but it is important to make the configuration understandable.

With that in mind, a new property named something like Secret Listing Strategy with values of Pattern and Enumerated would be a good way forward. The default value would be Pattern, and the Secret Name Pattern property would depend on having set that strategy.

For the new property, listing only one Secret Name is rather limiting, so instead Secret Names, supporting one or more comma-separated values could be dependent on the Enumerated strategy.

@grishick
Copy link
Contributor Author

Thanks for the contribution @grishick, and thanks for the thoughtful feedback @EndzeitBegins.

I concur with the recommendation from @EndzeitBegins. Having an alternative retrieval strategy that avoids having to list all secret names seems useful, but it is important to make the configuration understandable.

With that in mind, a new property named something like Secret Listing Strategy with values of Pattern and Enumerated would be a good way forward. The default value would be Pattern, and the Secret Name Pattern property would depend on having set that strategy.

For the new property, listing only one Secret Name is rather limiting, so instead Secret Names, supporting one or more comma-separated values could be dependent on the Enumerated strategy.

I got to agree, this is a better design. Will try to find time to rewrite it.

@grishick
Copy link
Contributor Author

Thanks for the contribution @grishick, and thanks for the thoughtful feedback @EndzeitBegins.
I concur with the recommendation from @EndzeitBegins. Having an alternative retrieval strategy that avoids having to list all secret names seems useful, but it is important to make the configuration understandable.
With that in mind, a new property named something like Secret Listing Strategy with values of Pattern and Enumerated would be a good way forward. The default value would be Pattern, and the Secret Name Pattern property would depend on having set that strategy.
For the new property, listing only one Secret Name is rather limiting, so instead Secret Names, supporting one or more comma-separated values could be dependent on the Enumerated strategy.

I got to agree, this is a better design. Will try to find time to rewrite it.

I think with the addition of Secret Listing Strategy there is no need for additional Secret Names parameter. Instead, the original Secret Name Patter parameter will be used either as a coma separated list or as a regular expression depending on the listing strategy.

@exceptionfactory
Copy link
Contributor

Thanks for the contribution @grishick, and thanks for the thoughtful feedback @EndzeitBegins.
I concur with the recommendation from @EndzeitBegins. Having an alternative retrieval strategy that avoids having to list all secret names seems useful, but it is important to make the configuration understandable.
With that in mind, a new property named something like Secret Listing Strategy with values of Pattern and Enumerated would be a good way forward. The default value would be Pattern, and the Secret Name Pattern property would depend on having set that strategy.
For the new property, listing only one Secret Name is rather limiting, so instead Secret Names, supporting one or more comma-separated values could be dependent on the Enumerated strategy.

I got to agree, this is a better design. Will try to find time to rewrite it.

I think with the addition of Secret Listing Strategy there is no need for additional Secret Names parameter. Instead, the original Secret Name Patter parameter will be used either as a coma separated list or as a regular expression depending on the listing strategy.

That could probably work, although it may involve changing the name and description of the property. With the dependsOn() builder method, the properties are shown or hidden based on the selected value, so in this case, it may be cleaner to have different properties to avoid any confusion.

@grishick
Copy link
Contributor Author

Thanks for the contribution @grishick, and thanks for the thoughtful feedback @EndzeitBegins.
I concur with the recommendation from @EndzeitBegins. Having an alternative retrieval strategy that avoids having to list all secret names seems useful, but it is important to make the configuration understandable.
With that in mind, a new property named something like Secret Listing Strategy with values of Pattern and Enumerated would be a good way forward. The default value would be Pattern, and the Secret Name Pattern property would depend on having set that strategy.
For the new property, listing only one Secret Name is rather limiting, so instead Secret Names, supporting one or more comma-separated values could be dependent on the Enumerated strategy.

I got to agree, this is a better design. Will try to find time to rewrite it.

I think with the addition of Secret Listing Strategy there is no need for additional Secret Names parameter. Instead, the original Secret Name Patter parameter will be used either as a coma separated list or as a regular expression depending on the listing strategy.

That could probably work, although it may involve changing the name and description of the property. With the dependsOn() builder method, the properties are shown or hidden based on the selected value, so in this case, it may be cleaner to have different properties to avoid any confusion.

If I add Secret Names in addition to Secret Listing Strategy - would Secret Names and Secret Pattern have required set to true or to false?

@exceptionfactory
Copy link
Contributor

If I add Secret Names in addition to Secret Listing Strategy - would Secret Names and Secret Pattern have required set to true or to false?

They can both be set to required(true), but the framework ensures that only the one associated with the related strategy value will be shown and required.

Copy link
Contributor

@exceptionfactory exceptionfactory left a comment

Choose a reason for hiding this comment

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

Thanks for the initial updates @grishick. Based on the new strategy property, adding a new Secret Names property looks like it would be cleaner and easier to understand than overloading the existing Secret Name Pattern property.

As with other pull requests, please also submit the pull request to the main branch first, and then it can be considered for backporting in a follow-on PR.

Comment on lines 79 to 82
.description("Strategy to use when listing secrets. 'Pattern' strategy treats Secret Name Pattern as a regular expression and fetches " +
"all secrets whose names match the pattern. 'Pattern' strategy requires ListSecrets and GetSecretValue permissions. "+
"'Enumerated' strategy treats Secret Name Pattern as a coma-separated list and fetches all secrets whose names are in the list. " +
"'Enumerated' strategy requires only GetSecretValue permission.")
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of describing the option in the property description, each value should be an instance of AllowableValue and include a description property.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with David to move the description of the options to each option itself respectively.

An alternative approach to using AllowableValue is the declaration of an enum that implements the DescribedValue interface. An example can be found here.
Due to improved type safety at the use site, this approach allows for exhaustive switch expressions, as can be seen here and here for example.

The advantage I see in this approach is that the compiler enforces to handle all options on all use sites, especially as new options are added.

Regardless of the approach you take, moving the descriptions to the options is sensible.

Comment on lines 92 to 93
"Any secrets whose names do not match this pattern will not be fetched. Using this parameter requires the ListSecrets permission." +
"This parameter is ignored if the Secret Name parameter is set.")
Copy link
Contributor

Choose a reason for hiding this comment

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

To avoid confusion, I think it is clearer to keep this property description unchanged, and add a new property with the a corresponding description.

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 added dependsOn to SECRET_NAME_PATTERN and added a new property called SECRET_NAMES also with dependsOn option. The new property SECRET_NAMES is expected to be a coma-separated list of AWS secret names. I've also added testFetchTwoSecrets unit test to verify the behavior when SECRET_NAMES is a list.

@grishick
Copy link
Contributor Author

OK. I'll try to make the changes asap. It's a small change, but integrating it back to Anetac's fork is the tricky part.

Copy link
Contributor

@EndzeitBegins EndzeitBegins left a comment

Choose a reason for hiding this comment

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

Thank you for your continued work on this @grishick.

There may have been a mistake due to the force-push, but some of the previous review feedback hasn't been addressed in the latest code version.
I've added some remarks and suggestions.

Additionally, as NiFi 2.0 is almost there, pull-requests should be addressing the main branch first before back porting changes to the support/nifi-1.x branch.

Comment on lines 167 to 193
// Fetch either by pattern or by enumerated list. See description of SECRET_LISTING_STRATEGY for more details.
String listingStrategy = context.getProperty(SECRET_LISTING_STRATEGY).getValue();
if (ENUMERATED_STRATEGY.equals(listingStrategy)) {
// if secret-name is set, fetch the secrets
String secretNames = context.getProperty(SECRET_NAMES).getValue();
for (String secretName : secretNames.split(",")) {
groups.addAll(fetchSecret(secretsManager, secretName));
}
final String nextToken = listSecretsResult.getNextToken();
if (nextToken == null) {
break;
} else {
final Pattern secretNamePattern = Pattern.compile(context.getProperty(SECRET_NAME_PATTERN).getValue());
ListSecretsRequest listSecretsRequest = new ListSecretsRequest();
ListSecretsResult listSecretsResult = secretsManager.listSecrets(listSecretsRequest);
while(!listSecretsResult.getSecretList().isEmpty()) {
for (final SecretListEntry entry : listSecretsResult.getSecretList()) {
String secretName = entry.getName();
if (!secretNamePattern.matcher(secretName).matches()) {
getLogger().debug("Secret [{}] does not match the secret name pattern {}", secretName, secretNamePattern);
continue;
}
groups.addAll(fetchSecret(secretsManager, secretName));
}
final String nextToken = listSecretsResult.getNextToken();
if (nextToken == null) {
break;
}
listSecretsRequest.setNextToken(nextToken);
listSecretsResult = secretsManager.listSecrets(listSecretsRequest);
Copy link
Contributor

@EndzeitBegins EndzeitBegins Oct 28, 2024

Choose a reason for hiding this comment

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

You could untangle determining the set of secrets to fetch from the actual fetching in here, turning one intertwined action into two separate steps.

That is,

  1. collecting a Set<String> of secretNames (based on either the provided enumeration or by fetching all available secret names and then filtering for those matching the regex), and then
  2. using that set to fetch the corresponding secrets.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. Done.

grishick and others added 5 commits October 28, 2024 18:14
…src/main/java/org/apache/nifi/parameter/aws/AwsSecretsManagerParameterProvider.java

Co-authored-by: Lucas <[email protected]>
…src/main/java/org/apache/nifi/parameter/aws/AwsSecretsManagerParameterProvider.java

Co-authored-by: Lucas <[email protected]>
…src/main/java/org/apache/nifi/parameter/aws/AwsSecretsManagerParameterProvider.java

Co-authored-by: Lucas <[email protected]>
…src/main/java/org/apache/nifi/parameter/aws/AwsSecretsManagerParameterProvider.java

Co-authored-by: Lucas <[email protected]>
@greg-anetac
Copy link

Thank you for your continued work on this @grishick.

There may have been a mistake due to the force-push, but some of the previous review feedback hasn't been addressed in the latest code version. I've added some remarks and suggestions.

Additionally, as NiFi 2.0 is almost there, pull-requests should be addressing the main branch first before back porting changes to the support/nifi-1.x branch.

Roger that. I'll fix the merge. I was waiting to create a PR for main until discussions on this PR settle on implementation approach.

@grishick
Copy link
Contributor Author

grishick commented Nov 4, 2024

Thank you for your continued work on this @grishick.
There may have been a mistake due to the force-push, but some of the previous review feedback hasn't been addressed in the latest code version. I've added some remarks and suggestions.
Additionally, as NiFi 2.0 is almost there, pull-requests should be addressing the main branch first before back porting changes to the support/nifi-1.x branch.

Roger that. I'll fix the merge. I was waiting to create a PR for main until discussions on this PR settle on implementation approach.

done

@grishick
Copy link
Contributor Author

grishick commented Nov 4, 2024

Here is a PR for main branch with the same change: #9483

Copy link
Contributor

@EndzeitBegins EndzeitBegins left a comment

Choose a reason for hiding this comment

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

Most of David's review comments on #9483 apply here too.

The only things that isn't available for 1.x that comes to my mind is asAllowableValue().
List.of isn't available for Java 8, so the PROPERTIES declaration can stay as is.

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.

4 participants