Skip to content

Commit

Permalink
Support creating child tokens for token credential binding
Browse files Browse the repository at this point in the history
  • Loading branch information
saville committed Sep 19, 2024
1 parent 9324022 commit 7555d3c
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 28 deletions.
4 changes: 2 additions & 2 deletions src/main/java/com/datapipe/jenkins/vault/VaultAccessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public VaultAccessor init() {
if (credential == null) {
vault = new Vault(config);
} else {
vault = credential.authorizeWithVault(config, policies);
vault = credential.authorizeWithVault(config, policies).getVault();
}

vault.withRetries(maxRetries, retryIntervalMilliseconds);
Expand Down Expand Up @@ -161,7 +161,7 @@ private static StringSubstitutor getPolicyTokenSubstitutor(EnvVars envVars) {
return new StringSubstitutor(valueMap);
}

protected static List<String> generatePolicies(String policies, EnvVars envVars) {
public static List<String> generatePolicies(String policies, EnvVars envVars) {
if (StringUtils.isBlank(policies)) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ protected AbstractVaultTokenCredential(CredentialsScope scope, String id, String
protected abstract String getToken(Vault vault);

@Override
public Vault authorizeWithVault(VaultConfig config, List<String> policies) {
public VaultAuthorizationResult authorizeWithVault(VaultConfig config, List<String> policies) {
Vault vault = new Vault(config);
return new Vault(config.token(getToken(vault)));
String token = getToken(vault);
return new VaultAuthorizationResult(new Vault(config.token(token)), token);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ private String getCacheKey(List<String> policies) {
}

@Override
public Vault authorizeWithVault(VaultConfig config, List<String> policies) {
public VaultAuthorizationResult authorizeWithVault(VaultConfig config, List<String> policies) {
// Upgraded instances can have these not initialized in the constructor (serialized jobs possibly)
if (tokenCache == null) {
tokenCache = new HashMap<>();
Expand All @@ -129,7 +129,7 @@ public Vault authorizeWithVault(VaultConfig config, List<String> policies) {
} else {
config.token(tokenCache.get(cacheKey));
}
return vault;
return new VaultAuthorizationResult(vault, config.getToken());
}

protected Vault getVault(VaultConfig config) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@NameWith(VaultCredential.NameProvider.class)
public interface VaultCredential extends StandardCredentials, Serializable {

Vault authorizeWithVault(VaultConfig config, List<String> policies);
VaultAuthorizationResult authorizeWithVault(VaultConfig config, List<String> policies);

class NameProvider extends CredentialsNameProvider<VaultCredential> {

Expand All @@ -21,4 +21,22 @@ public String getName(@NonNull VaultCredential credentials) {
return credentials.getDescription();
}
}

final class VaultAuthorizationResult {
private final Vault vault;
private final String token;

public VaultAuthorizationResult(Vault vault, String token) {
this.vault = vault;
this.token = token;
}

public Vault getVault() {
return vault;
}

public String getToken() {
return token;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package com.datapipe.jenkins.vault.credentials;

import com.bettercloud.vault.Vault;
import com.bettercloud.vault.VaultConfig;
import com.bettercloud.vault.VaultException;
import com.datapipe.jenkins.vault.exception.VaultPluginException;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.IdCredentials;
import com.datapipe.jenkins.vault.VaultAccessor;
import com.datapipe.jenkins.vault.configuration.VaultConfiguration;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.Descriptor;
import hudson.model.Run;
import hudson.model.TaskListener;
import java.io.IOException;
Expand All @@ -17,9 +19,12 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.credentialsbinding.BindingDescriptor;
import org.jenkinsci.plugins.credentialsbinding.MultiBinding;
import org.jenkinsci.plugins.credentialsbinding.impl.CredentialNotFoundException;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;

Expand All @@ -29,7 +34,7 @@ public class VaultTokenCredentialBinding extends MultiBinding<AbstractVaultToken
private final static String DEFAULT_VAULT_TOKEN_VARIABLE_NAME = "VAULT_TOKEN";
private final static String DEFAULT_VAULT_NAMESPACE_VARIABLE_NAME = "VAULT_NAMESPACE";

@NonNull
private final String credentialsId;
private final String addrVariable;
private final String tokenVariable;
private final String vaultAddr;
Expand All @@ -47,6 +52,8 @@ public class VaultTokenCredentialBinding extends MultiBinding<AbstractVaultToken
public VaultTokenCredentialBinding(@Nullable String addrVariable,
@Nullable String tokenVariable, String credentialsId, String vaultAddr) {
super(credentialsId);
// The superclass field is private, so we need to store our own version
this.credentialsId = credentialsId;
this.vaultAddr = vaultAddr;
this.addrVariable = StringUtils
.defaultIfBlank(addrVariable, DEFAULT_VAULT_ADDR_VARIABLE_NAME);
Expand Down Expand Up @@ -94,32 +101,60 @@ protected Class<AbstractVaultTokenCredential> type() {
return AbstractVaultTokenCredential.class;
}

private @Nonnull AbstractVaultTokenCredential getCredentials(@Nonnull Run<?,?> build,
VaultConfiguration config) throws CredentialNotFoundException {
// Copied and modified to pull the credentials ID from the Vault configuration
IdCredentials cred = CredentialsProvider.findCredentialById(config.getVaultCredentialId(),
IdCredentials.class, build);
if (cred==null)
throw new CredentialNotFoundException("Could not find credentials entry with ID '" +
config.getVaultCredentialId() + "'");

if (type().isInstance(cred)) {
CredentialsProvider.track(build, cred);
return type().cast(cred);
}

Descriptor expected = Jenkins.getActiveInstance().getDescriptor(type());
throw new CredentialNotFoundException("Credentials '"+config.getVaultCredentialId()+"' is of type '"+
cred.getDescriptor().getDisplayName()+"' where '"+

Check warning on line 120 in src/main/java/com/datapipe/jenkins/vault/credentials/VaultTokenCredentialBinding.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 120 is only partially covered, one branch is missing
(expected!=null ? expected.getDisplayName() : type().getName())+
"' was expected");
}

@Override
public MultiEnvironment bind(@NonNull Run<?, ?> build, FilePath workspace, Launcher launcher,
@NonNull TaskListener listener) throws IOException, InterruptedException {
AbstractVaultTokenCredential credentials = getCredentials(build);
@NonNull TaskListener listener) throws IOException {
VaultConfiguration config = getVaultConfiguration(build);
AbstractVaultTokenCredential credentials = getCredentials(build, config);
Map<String, String> m = new HashMap<>();
m.put(addrVariable, vaultAddr);
m.put(namespaceVariable, vaultNamespace);
String token = getToken(credentials);
m.put(addrVariable, config.getVaultUrl());
m.put(namespaceVariable, StringUtils.defaultString(config.getVaultNamespace()));
String token = getToken(build, credentials, config);
// don't add null token variable, can cause NPE in places where credential bindings impls
// are not expecting null env var values.
m.put(tokenVariable, StringUtils.defaultString(token));
return new MultiEnvironment(m);
}

private String getToken(AbstractVaultTokenCredential credentials) {
try {
VaultConfig config = new VaultConfig().address(vaultAddr);
if (StringUtils.isNotEmpty(vaultNamespace)) {
config.nameSpace(vaultNamespace);
}
config.build();

return credentials.getToken(new Vault(config));
} catch (VaultException e) {
throw new VaultPluginException("could not log in into vault", e);
private VaultConfiguration getVaultConfiguration(Run<?, ?> build) {
VaultConfiguration initialConfig = new VaultConfiguration();
initialConfig.setVaultCredentialId(credentialsId);
initialConfig.setVaultUrl(vaultAddr);
initialConfig.setVaultNamespace(vaultNamespace);
return VaultAccessor.pullAndMergeConfiguration(build, initialConfig);
}

private String getToken(Run<?, ?> build, AbstractVaultTokenCredential credentials,
VaultConfiguration config) {
if (StringUtils.isBlank(config.getPolicies())) {
// Use simpler method to get token if no policies are set
return credentials.getToken(new Vault(config.getVaultConfig()));
}
return credentials.authorizeWithVault(
config.getVaultConfig(),
VaultAccessor.generatePolicies(config.getPolicies(), build.getCharacteristicEnvVars())
).getToken();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.datapipe.jenkins.vault.configuration.VaultConfiguration;
import com.datapipe.jenkins.vault.credentials.VaultAppRoleCredential;
import com.datapipe.jenkins.vault.credentials.VaultCredential;
import com.datapipe.jenkins.vault.credentials.VaultCredential.VaultAuthorizationResult;
import com.datapipe.jenkins.vault.credentials.VaultTokenCredential;
import com.datapipe.jenkins.vault.model.VaultSecret;
import com.datapipe.jenkins.vault.model.VaultSecretValue;
Expand Down Expand Up @@ -483,7 +484,8 @@ public static VaultAppRoleCredential createTokenCredential(final String credenti
when(cred.getDescription()).thenReturn("description");
when(cred.getRoleId()).thenReturn("role-id-" + credentialId);
when(cred.getSecretId()).thenReturn(Secret.fromString("secret-id-" + credentialId));
when(cred.authorizeWithVault(any(), eq(null))).thenReturn(vault);
when(cred.authorizeWithVault(any(), eq(null))).thenReturn(
new VaultAuthorizationResult(vault, "token-" + credentialId));
return cred;

}
Expand Down
Loading

0 comments on commit 7555d3c

Please sign in to comment.