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

SecretResolver support file and base64 variable expansion #1408

Merged
merged 38 commits into from
Jul 19, 2020
Merged

SecretResolver support file and base64 variable expansion #1408

merged 38 commits into from
Jul 19, 2020

Conversation

jetersen
Copy link
Member

@jetersen jetersen commented May 30, 2020

This enables nested variable expansion with included file and base64 encoding support.

If someone could argue for why we should add more String lookups I'd like to understand the use case.

Syntax is as follows:

${base64:${file:path to file}}
${readFile:path to file}
${readFileBase64:path to file}

fixes #1219
fixes #909

Your checklist for this pull request

🚨 Please review the guidelines for contributing to this repository.

  • Make sure you are requesting to pull a topic/feature/bugfix branch (right side) and not your master branch!
  • Ensure that the pull request title represents the desired changelog entry
  • Please describe what you did
  • Link to relevant issues in GitHub or in Jenkins JIRA
  • Link to relevant pull requests, esp. upstream and downstream changes
  • Did you provide a test-case? That demonstrates feature works or fixes the issue.

@jetersen jetersen requested a review from a team as a code owner May 30, 2020 19:57
@codecov
Copy link

codecov bot commented May 30, 2020

Codecov Report

Merging #1408 into master will increase coverage by 0.35%.
The diff coverage is 100.00%.

@@             Coverage Diff              @@
##             master    #1408      +/-   ##
============================================
+ Coverage     80.66%   81.02%   +0.35%     
- Complexity      811      812       +1     
============================================
  Files            66       66              
  Lines          2333     2361      +28     
  Branches        329      329              
============================================
+ Hits           1882     1913      +31     
+ Misses          351      349       -2     
+ Partials        100       99       -1     
Impacted Files Coverage Δ Complexity Δ
.../io/jenkins/plugins/casc/ConfigurationContext.java 85.71% <100.00%> (+0.52%) 19.00 <1.00> (+1.00)
.../io/jenkins/plugins/casc/SecretSourceResolver.java 100.00% <100.00%> (+11.53%) 8.00 <5.00> (ø)
...casc/impl/configurators/PrimitiveConfigurator.java 83.33% <100.00%> (ø) 9.00 <1.00> (ø)

Comment on lines 110 to 111
public String lookup(final String key) {
if (key == null) {
return null;
}
public String lookup(@NonNull final String key) {
Copy link
Member Author

Choose a reason for hiding this comment

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

@jetersen
Copy link
Member Author

jetersen commented May 30, 2020

This might interest the kubernetes Jenkins helm chart and Jenkins operator maintainers:
cc @lachie83 @viglesiasce @maorfr @torstenwalter @mogaal @wmcdona89 @tomaszsek

@jetersen jetersen added the feature A PR that adds a feature - used by Release Drafter label May 30, 2020
@@ -21,7 +21,7 @@
private Deprecation deprecation = Deprecation.reject;
private Restriction restriction = Restriction.reject;
private Unknown unknown = Unknown.reject;
private final int yamlMaxAliasesForCollections;
private transient final int yamlMaxAliasesForCollections;
Copy link
Member Author

Choose a reason for hiding this comment

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

Should have been transient since it should not be serialized 😓

private final StringSubstitutor nullSubstitutor;
private final StringSubstitutor substitutor;

public SecretSourceResolver(ConfigurationContext configurationContext) {
Copy link
Member Author

@jetersen jetersen May 30, 2020

Choose a reason for hiding this comment

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

Made the class none static to reduce memory footprint of the StringSubstitutor when JCasC is not actively using them.

Comment on lines 64 to 78
/**
* Resolve string with potential secrets
*
* @param context Configuration context
* @param toInterpolate potential variables that need to revealed
* @return original string with any secrets that could be resolved if secrets could not be
* resolved they will be defaulted to default value defined by ':-', otherwise default to empty
* String secrets are defined as anything enclosed by '${}'
* @since 1.42
* @deprecated use ${link {@link #resolve(String)}} instead.
*/
@Deprecated
public static String resolve(ConfigurationContext context, String toInterpolate) {
return substitutor(context).replace(toInterpolate);
return context.getSecretSourceResolver().resolve(toInterpolate);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Could potentially remove this as usually this is only called directly from test packages in other plugins ie. HashiCorp Vault plugin.

Depeneds if we are okay with yet another breakage 😆

Copy link
Member

Choose a reason for hiding this comment

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

You could restrict it so when they upgrade they are forced to migrate to the new method?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point

@olivergondza
Copy link
Member

@jetersen, Cool, thanks for looking into this!

Since the "lookup token" is inside the curly brackets, can file and base64 be used as a regular variable names? (Thinking of backward compatibility with existing clients that happen to use those)

@jetersen
Copy link
Member Author

@olivergondza not sure I entirely understand the question.

Most people I seen have been using variable names such as ${BASE64_GOOGLE_PLAY_TOKEN}

that case is very unlikely as it would have to be ${base64:word} before the nested interpolation takes place, also we did not support nested cases before.

Yes you can have a case of ${base64:${GITHUB_PASSWORD}} or even further nested ${base64:${file:/run/secrets/${TEAM_A_GOOGLE_PLAY_TOKEN}.json}}

@jetersen
Copy link
Member Author

jetersen commented May 31, 2020

I added benchmark which revealed some nice performance increases.

FYI this speed increase is definitely noticeable when loading configuration. 👏
Basically all YAML values goes though this method.

image

I will not be adding the benchmark to the CI runs just yet and the current benchmark only provides value for SecretSourceResolver.

jetersen and others added 2 commits June 5, 2020 02:21
Co-authored-by: Victor Martinez <victormartinezrubio@gmail.com>
@jetersen
Copy link
Member Author

jetersen commented Jun 5, 2020

Hmm I discovered that secretBytes and plain credentials actually works fine with a binary base64 for the file content.

So perhaps we could settle for two base64 and file. Though you could no longer combine the two as file would also be.

I am tempted to combine it to one to reduce confusion even more.

Than inside the base64 lookup we would just check if a path exist.

Yeah to avoid any confusion I think a singular base64 would solve the problem nicely and we can always add more lookups if we find a good use-case.

Right now we are just trying to make the credential experience better!

@jetersen
Copy link
Member Author

jetersen commented Jun 5, 2020

Now that I think about it and started making the code change... I thought about the user experience 😆

Users would get unexpected secret results when files were not found since they would suddenly have secrets with file path in base64.

@jetersen
Copy link
Member Author

jetersen commented Jun 5, 2020

As our DockerSecretSource already reads file content into a string you can combine that with ${base64:${DOCKER_SECRET}} where docker secret is read from /run/secrets/DOCKER_SECRET.
You can see the code here:

public class DockerSecretSource extends SecretSource {
public static final String DOCKER_SECRETS = "/run/secrets/";
private final File secrets;
@SuppressFBWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME")
public DockerSecretSource() {
String s = System.getenv("SECRETS");
secrets = s != null ? new File(s) : new File(DOCKER_SECRETS);
}
@Override
public Optional<String> reveal(String secret) throws IOException {
if (StringUtils.isBlank(secret)) return Optional.empty();
final File file = new File(secrets, secret);
if (file.exists()) {
return Optional.of(FileUtils.readFileToString(file, StandardCharsets.UTF_8).trim());
}
return Optional.empty();
}
}

@jetersen jetersen mentioned this pull request Jun 11, 2020
1 task
@timja
Copy link
Member

timja commented Jul 1, 2020

@jetersen is there anything left to do on this?

@prom3theu5
Copy link

Any idea how long is left on this one?
In a position where it's integral to us

@jetersen
Copy link
Member Author

jetersen commented Jul 18, 2020

@timja after rethinking the issue. I think I want to reduce the complexity by having more complex logic that detects whether ${base64:VALUE} is a file or not to avoid introducing multiple implementations.

@jetersen
Copy link
Member Author

@prom3theu5 which part? the base64 encoding?

@jetersen
Copy link
Member Author

jetersen commented Jul 19, 2020

@timja after rethinking the issue. I think I want to reduce the complexity by having more complex logic that detects whether ${base64:VALUE} is a file or not to avoid introducing multiple implementations.

after making the code changes... nothing but errors would occur on so many level... What is a valid path? is it a readable file? Do we log it?.... ARGH
I feel like I already tried 😓 https://github.com/jenkinsci/configuration-as-code-plugin/pull/1408/files#issuecomment-639213839

@jetersen
Copy link
Member Author

@timja I think this is ready we can always improve it.

@prom3theu5
Copy link

@prom3theu5 which part? the base64 encoding?

Yes - base64 encoding, file support not so much right now

@jetersen jetersen merged commit 410c3ae into jenkinsci:master Jul 19, 2020
@jetersen jetersen deleted the fix/1219 branch July 19, 2020 02:37
@sarabesh
Copy link

@jetersen , when can i get this commit as a release, since i am using jenkins operator, where the base plugins can't be added manually,.and casc is a base plugin. We can only specify the plugin versions.

@jetersen
Copy link
Member Author

You should be able to point plugins to a url as far as I know.

@jetersen
Copy link
Member Author

@sarabesh in any case it is now released with v1.42 :)

@olivergondza
Copy link
Member

@jetersen, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature A PR that adds a feature - used by Release Drafter
Projects
None yet
Development

Successfully merging this pull request may close these issues.

JENKINS-60373: Read secret from file and encode it with base64 PKCS12 cert doesn't work anymore
10 participants