Skip to content

Commit f5d54b3

Browse files
bluesliverxsaville
and
saville
authored
Adds support for using job-specific policies (#223)
* Fixes #214, adds support for separating job policies * Add configuration to credentials to enable using limited policies * Fix handling of TTL in child tokens * Add ability to disable folders or jobs from overriding policies * Use StringSubstitutor for templating policies * Fix flaky test --------- Co-authored-by: saville <[email protected]>
1 parent af8c162 commit f5d54b3

24 files changed

+492
-37
lines changed

README.md

+18
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,24 @@ When registering the approle backend you can set a couple of different parameter
2121
* many more
2222

2323
This is just a short introduction, please refer to [Hashicorp itself](https://www.vaultproject.io/docs/auth/approle.html) to get detailed information.
24+
25+
### Isolating policies for different jobs
26+
It may be desirable to have jobs or folders with separate Vault policies allocated. This may be done
27+
with the optional `policies` configuration option combined with authentication such as the AppRole
28+
credential. The process is the following:
29+
* The Jenkins job attempts to retrieve a secret from Vault
30+
* The AppRole authentication is used to retrieve a new token (if the old one has not expired yet)
31+
* The Vault plugin then uses the `policies` configuration value with job info to come up with a list of policies
32+
* If this list is not empty, the AppRole token is used to retrieve a new token that only has the specified policies applied
33+
* This token is then used for all Vault plugin operations in the job
34+
35+
The policies list may be templatized with values that can come from each job in order to customize
36+
policies per job or folder. See the `policies` configuration help for more information on available
37+
tokens to use in the configuration. The `Limit Token Policies` option must also be enabled on the
38+
auth credential. Please note that the AppRole (or other authentication method) should have all policies
39+
configured as `token_policies` and not `identity_policies`, as job-specific tokens inherit all
40+
`identity_policies` automatically.
41+
2442
### What about other backends?
2543
Hashicorp explicitly recommends the AppRole Backend for machine-to-machine authentication. Token based auth is mainly supported for backward compatibility.
2644
Other backends that might make sense are the AWS EC2 backend, the Azure backend, and the Kubernetes backend. But we do not support these yet. Feel free to contribute!

src/main/java/com/datapipe/jenkins/vault/VaultAccessor.java

+43-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.io.PrintStream;
2828
import java.io.Serializable;
2929
import java.nio.charset.StandardCharsets;
30+
import java.util.Arrays;
3031
import java.util.Collections;
3132
import java.util.HashMap;
3233
import java.util.List;
@@ -35,13 +36,15 @@
3536
import java.util.stream.Collectors;
3637
import jenkins.model.Jenkins;
3738
import org.apache.commons.lang.StringUtils;
39+
import org.apache.commons.text.StringSubstitutor;
3840

3941
public class VaultAccessor implements Serializable {
4042

4143
private static final long serialVersionUID = 1L;
4244

4345
private VaultConfig config;
4446
private VaultCredential credential;
47+
private List<String> policies;
4548
private int maxRetries = 0;
4649
private int retryIntervalMilliseconds = 1000;
4750

@@ -63,7 +66,7 @@ public VaultAccessor init() {
6366
if (credential == null) {
6467
vault = new Vault(config);
6568
} else {
66-
vault = credential.authorizeWithVault(config);
69+
vault = credential.authorizeWithVault(config, policies);
6770
}
6871

6972
vault.withRetries(maxRetries, retryIntervalMilliseconds);
@@ -89,6 +92,14 @@ public void setCredential(VaultCredential credential) {
8992
this.credential = credential;
9093
}
9194

95+
public List<String> getPolicies() {
96+
return policies;
97+
}
98+
99+
public void setPolicies(List<String> policies) {
100+
this.policies = policies;
101+
}
102+
92103
public int getMaxRetries() {
93104
return maxRetries;
94105
}
@@ -130,6 +141,36 @@ public VaultResponse revoke(String leaseId) {
130141
}
131142
}
132143

144+
private static StringSubstitutor getPolicyTokenSubstitutor(EnvVars envVars) {
145+
String jobName = envVars.get("JOB_NAME");
146+
String jobBaseName = envVars.get("JOB_BASE_NAME");
147+
String folder = "";
148+
if (!jobName.equals(jobBaseName) && jobName.contains("/")) {
149+
String[] jobElements = jobName.split("/");
150+
folder = Arrays.stream(jobElements)
151+
.limit(jobElements.length - 1)
152+
.collect(Collectors.joining("/"));
153+
}
154+
Map<String, String> valueMap = new HashMap<>();
155+
valueMap.put("job_base_name", jobBaseName);
156+
valueMap.put("job_name", jobName);
157+
valueMap.put("job_name_us", jobName.replaceAll("/", "_"));
158+
valueMap.put("job_folder", folder);
159+
valueMap.put("job_folder_us", folder.replaceAll("/", "_"));
160+
valueMap.put("node_name", envVars.get("NODE_NAME"));
161+
return new StringSubstitutor(valueMap);
162+
}
163+
164+
protected static List<String> generatePolicies(String policies, EnvVars envVars) {
165+
if (StringUtils.isBlank(policies)) {
166+
return null;
167+
}
168+
return Arrays.stream(getPolicyTokenSubstitutor(envVars).replace(policies).split("\n"))
169+
.filter(StringUtils::isNotBlank)
170+
.map(String::trim)
171+
.collect(Collectors.toList());
172+
}
173+
133174
public static Map<String, String> retrieveVaultSecrets(Run<?,?> run, PrintStream logger, EnvVars envVars, VaultAccessor vaultAccessor, VaultConfiguration initialConfiguration, List<VaultSecret> vaultSecrets) {
134175
Map<String, String> overrides = new HashMap<>();
135176

@@ -156,6 +197,7 @@ public static Map<String, String> retrieveVaultSecrets(Run<?,?> run, PrintStream
156197
}
157198
vaultAccessor.setConfig(vaultConfig);
158199
vaultAccessor.setCredential(credential);
200+
vaultAccessor.setPolicies(generatePolicies(config.getPolicies(), envVars));
159201
vaultAccessor.setMaxRetries(config.getMaxRetries());
160202
vaultAccessor.setRetryIntervalMilliseconds(config.getRetryIntervalMilliseconds());
161203
vaultAccessor.init();

src/main/java/com/datapipe/jenkins/vault/configuration/VaultConfiguration.java

+28
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ public class VaultConfiguration extends AbstractDescribableImpl<VaultConfigurati
5050

5151
private String prefixPath;
5252

53+
private String policies;
54+
55+
private Boolean disableChildPoliciesOverride;
56+
5357
private Integer timeout = DEFAULT_TIMEOUT;
5458

5559
@DataBoundConstructor
@@ -73,6 +77,8 @@ public VaultConfiguration(VaultConfiguration toCopy) {
7377
this.engineVersion = toCopy.engineVersion;
7478
this.vaultNamespace = toCopy.vaultNamespace;
7579
this.prefixPath = toCopy.prefixPath;
80+
this.policies = toCopy.policies;
81+
this.disableChildPoliciesOverride = toCopy.disableChildPoliciesOverride;
7682
this.timeout = toCopy.timeout;
7783
}
7884

@@ -99,6 +105,10 @@ public VaultConfiguration mergeWithParent(VaultConfiguration parent) {
99105
if (StringUtils.isBlank(result.getPrefixPath())) {
100106
result.setPrefixPath(parent.getPrefixPath());
101107
}
108+
if (StringUtils.isBlank(result.getPolicies()) ||
109+
(parent.getDisableChildPoliciesOverride() != null && parent.getDisableChildPoliciesOverride())) {
110+
result.setPolicies(parent.getPolicies());
111+
}
102112
if (result.timeout == null) {
103113
result.setTimeout(parent.getTimeout());
104114
}
@@ -183,6 +193,24 @@ public void setPrefixPath(String prefixPath) {
183193
this.prefixPath = fixEmptyAndTrim(prefixPath);
184194
}
185195

196+
public String getPolicies() {
197+
return policies;
198+
}
199+
200+
@DataBoundSetter
201+
public void setPolicies(String policies) {
202+
this.policies = fixEmptyAndTrim(policies);
203+
}
204+
205+
public Boolean getDisableChildPoliciesOverride() {
206+
return disableChildPoliciesOverride;
207+
}
208+
209+
@DataBoundSetter
210+
public void setDisableChildPoliciesOverride(Boolean disableChildPoliciesOverride) {
211+
this.disableChildPoliciesOverride = disableChildPoliciesOverride;
212+
}
213+
186214
public Integer getTimeout() {
187215
return timeout;
188216
}

src/main/java/com/datapipe/jenkins/vault/credentials/AbstractAuthenticatingVaultTokenCredential.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public void setNamespace(String namespace) {
4646
}
4747

4848
@Override
49-
protected final String getToken(Vault vault) {
49+
protected Auth getVaultAuth(@NonNull Vault vault) {
5050
// set authentication namespace if configured for this credential.
5151
// importantly, this will not effect the underlying VaultConfig namespace.
5252
Auth auth = vault.auth();
@@ -57,7 +57,12 @@ protected final String getToken(Vault vault) {
5757
auth.withNameSpace(null);
5858
}
5959
}
60-
return getToken(auth);
60+
return auth;
61+
}
62+
63+
@Override
64+
protected final String getToken(Vault vault) {
65+
return getToken(getVaultAuth(vault));
6166
}
6267

6368
/**

src/main/java/com/datapipe/jenkins/vault/credentials/AbstractVaultTokenCredential.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.bettercloud.vault.VaultConfig;
55
import com.cloudbees.plugins.credentials.CredentialsScope;
66
import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials;
7+
import java.util.List;
78

89
public abstract class AbstractVaultTokenCredential
910
extends BaseStandardCredentials implements VaultCredential {
@@ -15,7 +16,7 @@ protected AbstractVaultTokenCredential(CredentialsScope scope, String id, String
1516
protected abstract String getToken(Vault vault);
1617

1718
@Override
18-
public Vault authorizeWithVault(VaultConfig config) {
19+
public Vault authorizeWithVault(VaultConfig config, List<String> policies) {
1920
Vault vault = new Vault(config);
2021
return new Vault(config.token(getToken(vault)));
2122
}

0 commit comments

Comments
 (0)