Skip to content

Commit 5a1eeda

Browse files
authored
Namespace and prefix path support on credentials (#129)
* Namespace and prefix path support on credentials Add "namespace" and "prefixPath" configuration options to all vault credential types (string/username password/ssh). Add "namespace" configuration option to vault auth credentials to handle case where auth provider is mounted in a different namespace (i.e. root) than the credential. Add missing getters on vault credentials so they are displayed in the ui forms. * fix: Remove redundant null check * chore: refactor auth token namespace management Introduce new AbstractAuthenticatingVaultTokenCredential. This specialization of the AbstractVaultTokenCredential is specific to vault credential types that retrieve the auth client token by using one of the vault auth login methods. This new abstraction takes care of two issues from the previous implementation: 1. the auth namespace property only applies to auth methods that use it (i.e. VaultTokenCredential should not inherit that property). 2. the auth namespace can be safely scoped to the auth client and not effect the underlying VaultConfig namespace setting. * fix: invalid javadoc syntax * chore: remove unused vault argument The vault argument for the abstract getToken method is not necessary, all the implementations use the provided auth client. * fix: remove unused imports
1 parent 3d36af9 commit 5a1eeda

39 files changed

+466
-132
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ work/
99
*.sublime*
1010
tmp/
1111
src/test/resources/com/datapipe/jenkins/vault/util/ssl/
12+
.DS_Store
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.datapipe.jenkins.vault.credentials;
2+
3+
import com.bettercloud.vault.Vault;
4+
import com.bettercloud.vault.api.Auth;
5+
import com.cloudbees.plugins.credentials.CredentialsScope;
6+
import com.datapipe.jenkins.vault.exception.VaultPluginException;
7+
import edu.umd.cs.findbugs.annotations.CheckForNull;
8+
import edu.umd.cs.findbugs.annotations.NonNull;
9+
import hudson.Util;
10+
import org.kohsuke.stapler.DataBoundSetter;
11+
12+
/**
13+
* Abstract Vault token credential that authenticates with the vault server to retrieve the
14+
* authentication token. This credential type can explicitly configure the namespace which
15+
* the authentication method is mounted.
16+
*/
17+
public abstract class AbstractAuthenticatingVaultTokenCredential extends AbstractVaultTokenCredential {
18+
19+
@CheckForNull
20+
private String namespace;
21+
22+
protected AbstractAuthenticatingVaultTokenCredential(CredentialsScope scope, String id,
23+
String description) {
24+
super(scope, id, description);
25+
}
26+
27+
/**
28+
* Get Vault namespace.
29+
* @return vault namespace or null
30+
*/
31+
@CheckForNull
32+
public String getNamespace() {
33+
return namespace;
34+
}
35+
36+
/**
37+
* Set namespace where auth method is mounted. If set to "/"
38+
* the root namespace is explicitly forced, otherwise the
39+
* namespace from the secret credential vault config is used.
40+
* @param namespace vault namespace
41+
*/
42+
@DataBoundSetter
43+
public void setNamespace(String namespace) {
44+
this.namespace = Util.fixEmptyAndTrim(namespace);
45+
}
46+
47+
@Override
48+
protected final String getToken(Vault vault) {
49+
// set authentication namespace if configured for this credential.
50+
// importantly, this will not effect the underlying VaultConfig namespace.
51+
Auth auth = vault.auth();
52+
if (namespace != null) {
53+
if (!namespace.trim().equals("/")) {
54+
auth.withNameSpace(namespace);
55+
} else {
56+
auth.withNameSpace(null);
57+
}
58+
}
59+
return getToken(auth);
60+
}
61+
62+
/**
63+
* Authenticate with vault using this credential and return the token. The {@code auth} client
64+
* will be configured with this credentials namespace.
65+
* @param auth vault auth client
66+
* @return authentication token
67+
* @throws VaultPluginException if failed to authenticate with vault
68+
*/
69+
protected abstract String getToken(@NonNull Auth auth);
70+
}

Diff for: src/main/java/com/datapipe/jenkins/vault/credentials/VaultAppRoleCredential.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.datapipe.jenkins.vault.credentials;
22

3-
import com.bettercloud.vault.Vault;
43
import com.bettercloud.vault.VaultException;
4+
import com.bettercloud.vault.api.Auth;
55
import com.cloudbees.plugins.credentials.CredentialsScope;
66
import com.datapipe.jenkins.vault.exception.VaultPluginException;
77
import edu.umd.cs.findbugs.annotations.CheckForNull;
@@ -12,7 +12,7 @@
1212
import org.apache.commons.lang.StringUtils;
1313
import org.kohsuke.stapler.DataBoundConstructor;
1414

15-
public class VaultAppRoleCredential extends AbstractVaultTokenCredential {
15+
public class VaultAppRoleCredential extends AbstractAuthenticatingVaultTokenCredential {
1616

1717
private final @NonNull
1818
Secret secretId;
@@ -48,9 +48,9 @@ public String getPath() {
4848
}
4949

5050
@Override
51-
public String getToken(Vault vault) {
51+
public String getToken(Auth auth) {
5252
try {
53-
return vault.auth().loginByAppRole(path, roleId, Secret.toString(secretId))
53+
return auth.loginByAppRole(path, roleId, Secret.toString(secretId))
5454
.getAuthClientToken();
5555
} catch (VaultException e) {
5656
throw new VaultPluginException("could not log in into vault", e);

Diff for: src/main/java/com/datapipe/jenkins/vault/credentials/VaultGCPCredential.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.datapipe.jenkins.vault.credentials;
22

3-
import com.bettercloud.vault.Vault;
43
import com.bettercloud.vault.VaultException;
4+
import com.bettercloud.vault.api.Auth;
55
import com.cloudbees.plugins.credentials.CredentialsScope;
66
import com.datapipe.jenkins.vault.exception.VaultPluginException;
77
import edu.umd.cs.findbugs.annotations.CheckForNull;
@@ -18,7 +18,7 @@
1818
import java.nio.charset.StandardCharsets;
1919
import org.kohsuke.stapler.DataBoundConstructor;
2020

21-
public class VaultGCPCredential extends AbstractVaultTokenCredential {
21+
public class VaultGCPCredential extends AbstractAuthenticatingVaultTokenCredential {
2222

2323
@NonNull
2424
private final String role;
@@ -38,8 +38,13 @@ public String getRole() {
3838
return role;
3939
}
4040

41+
@NonNull
42+
public String getAudience() {
43+
return audience;
44+
}
45+
4146
@Override
42-
public String getToken(Vault vault) {
47+
public String getToken(Auth auth) {
4348
String jwt;
4449
try {
4550
jwt = retrieveGoogleJWT();
@@ -48,7 +53,7 @@ public String getToken(Vault vault) {
4853
}
4954

5055
try {
51-
return vault.withRetries(5, 500).auth().loginByGCP(role, jwt).getAuthClientToken();
56+
return auth.loginByGCP(role, jwt).getAuthClientToken();
5257
} catch (VaultException e) {
5358
throw new VaultPluginException("could not log in into vault", e);
5459
}

Diff for: src/main/java/com/datapipe/jenkins/vault/credentials/VaultGithubTokenCredential.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.datapipe.jenkins.vault.credentials;
22

3-
import com.bettercloud.vault.Vault;
43
import com.bettercloud.vault.VaultException;
4+
import com.bettercloud.vault.api.Auth;
55
import com.cloudbees.plugins.credentials.CredentialsScope;
66
import com.datapipe.jenkins.vault.exception.VaultPluginException;
77
import edu.umd.cs.findbugs.annotations.CheckForNull;
@@ -10,7 +10,7 @@
1010
import hudson.util.Secret;
1111
import org.kohsuke.stapler.DataBoundConstructor;
1212

13-
public class VaultGithubTokenCredential extends AbstractVaultTokenCredential {
13+
public class VaultGithubTokenCredential extends AbstractAuthenticatingVaultTokenCredential {
1414

1515
// https://www.vaultproject.io/docs/auth/github.html#generate-a-github-personal-access-token
1616
private final @NonNull
@@ -31,9 +31,9 @@ public Secret getAccessToken() {
3131
}
3232

3333
@Override
34-
public String getToken(Vault vault) {
34+
public String getToken(Auth auth) {
3535
try {
36-
return vault.auth().loginByGithub(Secret.toString(accessToken)).getAuthClientToken();
36+
return auth.loginByGithub(Secret.toString(accessToken)).getAuthClientToken();
3737
} catch (VaultException e) {
3838
throw new VaultPluginException("could not log in into vault", e);
3939
}

Diff for: src/main/java/com/datapipe/jenkins/vault/credentials/VaultKubernetesCredential.java

+10-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.datapipe.jenkins.vault.credentials;
22

3-
import com.bettercloud.vault.Vault;
43
import com.bettercloud.vault.VaultException;
4+
import com.bettercloud.vault.api.Auth;
55
import com.cloudbees.plugins.credentials.CredentialsScope;
66
import com.datapipe.jenkins.vault.exception.VaultPluginException;
77
import edu.umd.cs.findbugs.annotations.CheckForNull;
@@ -16,7 +16,7 @@
1616
import org.kohsuke.stapler.DataBoundConstructor;
1717
import org.kohsuke.stapler.DataBoundSetter;
1818

19-
public class VaultKubernetesCredential extends AbstractVaultTokenCredential {
19+
public class VaultKubernetesCredential extends AbstractAuthenticatingVaultTokenCredential {
2020

2121
private static final String SERVICE_ACCOUNT_TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token";
2222

@@ -43,9 +43,14 @@ public void setMountPath(@NonNull String mountPath) {
4343
this.mountPath = mountPath;
4444
}
4545

46+
@NonNull
47+
public String getRole() {
48+
return this.role;
49+
}
50+
4651
@Override
4752
@SuppressFBWarnings(value = "DMI_HARDCODED_ABSOLUTE_FILENAME")
48-
public String getToken(Vault vault) {
53+
protected String getToken(Auth auth) {
4954
String jwt;
5055
try (Stream<String> input = Files.lines(Paths.get(SERVICE_ACCOUNT_TOKEN_PATH)) ) {
5156
jwt = input.collect(Collectors.joining());
@@ -54,13 +59,10 @@ public String getToken(Vault vault) {
5459
}
5560

5661
try {
57-
return vault
58-
.withRetries(5, 500)
59-
.auth()
60-
.loginByJwt(mountPath, role, jwt)
62+
return auth.loginByJwt(mountPath, role, jwt)
6163
.getAuthClientToken();
6264
} catch (VaultException e) {
63-
throw new VaultPluginException("could not log in into vault", e);
65+
throw new VaultPluginException("could not log in into vault: " + e.getMessage(), e);
6466
}
6567
}
6668

Diff for: src/main/java/com/datapipe/jenkins/vault/credentials/VaultTokenCredential.java

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ public VaultTokenCredential(@CheckForNull CredentialsScope scope, @CheckForNull
1919
this.token = token;
2020
}
2121

22+
@NonNull
23+
public Secret getToken() {
24+
return token;
25+
}
2226

2327
@Override
2428
public String getToken(Vault vault) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.datapipe.jenkins.vault.credentials.common;
2+
3+
import com.cloudbees.plugins.credentials.CredentialsScope;
4+
import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials;
5+
import com.datapipe.jenkins.vault.exception.VaultPluginException;
6+
import edu.umd.cs.findbugs.annotations.CheckForNull;
7+
import edu.umd.cs.findbugs.annotations.NonNull;
8+
import hudson.Util;
9+
import org.kohsuke.stapler.DataBoundSetter;
10+
11+
import static com.datapipe.jenkins.vault.credentials.common.VaultHelper.getVaultSecret;
12+
13+
/**
14+
* Base Vault credentials that contain a {@code path}, {@code prefixPath}, {@code namespace},
15+
* and {@code engineVersion}.
16+
*/
17+
public abstract class AbstractVaultBaseStandardCredentials extends BaseStandardCredentials {
18+
19+
private String path;
20+
private String prefixPath;
21+
private String namespace;
22+
private Integer engineVersion;
23+
24+
AbstractVaultBaseStandardCredentials(CredentialsScope scope, String id, String description) {
25+
super(scope, id, description);
26+
}
27+
28+
@NonNull
29+
public String getPrefixPath() {
30+
return prefixPath;
31+
}
32+
33+
@DataBoundSetter
34+
public void setPrefixPath(String prefixPath) {
35+
this.prefixPath = Util.fixEmptyAndTrim(prefixPath);
36+
}
37+
38+
@NonNull
39+
public String getPath() {
40+
return path;
41+
}
42+
43+
@DataBoundSetter
44+
public void setPath(String path) {
45+
this.path = path;
46+
}
47+
48+
@CheckForNull
49+
public String getNamespace() {
50+
return namespace;
51+
}
52+
53+
@DataBoundSetter
54+
public void setNamespace(String namespace) {
55+
this.namespace = Util.fixEmptyAndTrim(namespace);
56+
}
57+
58+
@CheckForNull
59+
public Integer getEngineVersion() {
60+
return engineVersion;
61+
}
62+
63+
@DataBoundSetter
64+
public void setEngineVersion(Integer engineVersion) {
65+
this.engineVersion = engineVersion;
66+
}
67+
68+
/**
69+
* Look up secret key value.
70+
* @param key secret key name
71+
* @return vault secret value
72+
*/
73+
@NonNull
74+
protected String getVaultSecretKeyValue(String key) {
75+
String s = getVaultSecret(this.path, key, this.prefixPath, this.namespace, this.engineVersion);
76+
if (s == null) {
77+
throw new VaultPluginException("Fetching from Vault failed for key '" + key + "'");
78+
}
79+
return s;
80+
}
81+
82+
/**
83+
* Get credential display name. Defaults to secret path.
84+
* @return display name
85+
*/
86+
public String getDisplayName() {
87+
return this.path;
88+
}
89+
}

Diff for: src/main/java/com/datapipe/jenkins/vault/credentials/common/VaultHelper.java

+20-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.datapipe.jenkins.vault.exception.VaultPluginException;
1414
import edu.umd.cs.findbugs.annotations.CheckForNull;
1515
import edu.umd.cs.findbugs.annotations.NonNull;
16+
import hudson.Util;
1617
import hudson.remoting.Channel;
1718
import hudson.security.ACL;
1819
import java.io.IOException;
@@ -29,10 +30,10 @@ public class VaultHelper {
2930

3031
private static final Logger LOGGER = Logger.getLogger(VaultHelper.class.getName());
3132

32-
static String getVaultSecret(@NonNull String secretPath, @NonNull String secretKey, @CheckForNull Integer engineVersion) {
33+
static String getVaultSecret(@NonNull String secretPath, @NonNull String secretKey, @CheckForNull String prefixPath, @CheckForNull String namespace, @CheckForNull Integer engineVersion) {
3334
try {
3435
String secret;
35-
SecretRetrieve retrieve = new SecretRetrieve(secretPath, secretKey, engineVersion);
36+
SecretRetrieve retrieve = new SecretRetrieve(secretPath, secretKey, prefixPath, namespace, engineVersion);
3637

3738
Channel channel = Channel.current();
3839
if (channel == null) {
@@ -54,11 +55,17 @@ private static class SecretRetrieve extends SlaveToMasterCallable<String, IOExce
5455
private final String secretPath;
5556
private final String secretKey;
5657
@CheckForNull
58+
private final String prefixPath;
59+
@CheckForNull
60+
private final String namespace;
61+
@CheckForNull
5762
private Integer engineVersion;
5863

59-
SecretRetrieve(String secretPath, String secretKey, Integer engineVersion) {
64+
SecretRetrieve(String secretPath, String secretKey, String prefixPath, String namespace, Integer engineVersion) {
6065
this.secretPath = secretPath;
6166
this.secretKey = secretKey;
67+
this.prefixPath = Util.fixEmptyAndTrim(prefixPath);
68+
this.namespace = Util.fixEmptyAndTrim(namespace);
6269
this.engineVersion = engineVersion;
6370
}
6471

@@ -85,6 +92,14 @@ public String call() throws IOException {
8592
try {
8693
VaultConfig vaultConfig = configuration.getVaultConfig();
8794

95+
if (prefixPath != null) {
96+
vaultConfig.prefixPath(prefixPath);
97+
}
98+
99+
if (namespace != null) {
100+
vaultConfig.nameSpace(namespace);
101+
}
102+
88103
VaultCredential vaultCredential = configuration.getVaultCredential();
89104
if (vaultCredential == null) vaultCredential = retrieveVaultCredentials(configuration.getVaultCredentialId());
90105

@@ -103,6 +118,8 @@ public String call() throws IOException {
103118
}
104119

105120
return values.get(secretKey);
121+
} catch (VaultPluginException vpe) {
122+
throw vpe;
106123
} catch (Exception e) {
107124
throw new RuntimeException(e);
108125
}

0 commit comments

Comments
 (0)