Skip to content

Commit 5cbdb7d

Browse files
committed
Added VaultClientTokenSupplier
1 parent eade98b commit 5cbdb7d

File tree

3 files changed

+198
-43
lines changed

3 files changed

+198
-43
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package io.scalecube.security.vault;
2+
3+
import static io.scalecube.utils.MaskUtil.mask;
4+
5+
import com.bettercloud.vault.VaultConfig;
6+
import com.bettercloud.vault.VaultException;
7+
import io.scalecube.config.utils.ThrowableUtil;
8+
import io.scalecube.config.vault.EnvironmentVaultTokenSupplier;
9+
import io.scalecube.config.vault.KubernetesVaultTokenSupplier;
10+
import io.scalecube.config.vault.VaultTokenSupplier;
11+
import java.util.Objects;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
import reactor.core.publisher.Mono;
15+
import reactor.core.scheduler.Schedulers;
16+
17+
public final class VaultClientTokenSupplier {
18+
19+
private static final Logger LOGGER = LoggerFactory.getLogger(VaultClientTokenSupplier.class);
20+
21+
private String vaultAddress;
22+
private String vaultToken;
23+
private String vaultRole;
24+
25+
public VaultClientTokenSupplier() {}
26+
27+
private VaultClientTokenSupplier(VaultClientTokenSupplier other) {
28+
this.vaultAddress = other.vaultAddress;
29+
this.vaultToken = other.vaultToken;
30+
this.vaultRole = other.vaultRole;
31+
}
32+
33+
private VaultClientTokenSupplier copy() {
34+
return new VaultClientTokenSupplier(this);
35+
}
36+
37+
private void validate() {
38+
if (isNullOrNoneOrEmpty(vaultAddress)) {
39+
throw new IllegalArgumentException("Vault address is required");
40+
}
41+
if (isNullOrNoneOrEmpty(vaultToken) && isNullOrNoneOrEmpty(vaultRole)) {
42+
throw new IllegalArgumentException(
43+
"Vault auth scheme is required (specify either VAULT_ROLE or VAULT_TOKEN)");
44+
}
45+
}
46+
47+
/**
48+
* Setter for vaultAddress.
49+
*
50+
* @param vaultAddress vaultAddress
51+
* @return new instance with applied setting
52+
*/
53+
public VaultClientTokenSupplier vaultAddress(String vaultAddress) {
54+
final VaultClientTokenSupplier c = copy();
55+
c.vaultAddress = vaultAddress;
56+
return c;
57+
}
58+
59+
/**
60+
* Setter for vaultToken.
61+
*
62+
* @param vaultToken vaultToken
63+
* @return new instance with applied setting
64+
*/
65+
public VaultClientTokenSupplier vaultToken(String vaultToken) {
66+
final VaultClientTokenSupplier c = copy();
67+
c.vaultToken = vaultToken;
68+
return c;
69+
}
70+
71+
/**
72+
* Setter for vaultRole.
73+
*
74+
* @param vaultRole vaultRole
75+
* @return new instance with applied setting
76+
*/
77+
public VaultClientTokenSupplier vaultRole(String vaultRole) {
78+
final VaultClientTokenSupplier c = copy();
79+
c.vaultRole = vaultRole;
80+
return c;
81+
}
82+
83+
/**
84+
* Obtains vault client token.
85+
*
86+
* @return vault client token
87+
*/
88+
public Mono<String> getToken() {
89+
return Mono.fromRunnable(this::validate)
90+
.then(Mono.fromCallable(this::getToken0))
91+
.subscribeOn(Schedulers.boundedElastic())
92+
.doOnSubscribe(s -> LOGGER.debug("[getToken] Getting vault client token"))
93+
.doOnSuccess(s -> LOGGER.debug("[getToken][success] result: {}", mask(s)))
94+
.doOnError(th -> LOGGER.error("[getToken][error] cause: {}", th.toString()));
95+
}
96+
97+
private String getToken0() {
98+
try {
99+
VaultTokenSupplier vaultTokenSupplier;
100+
VaultConfig vaultConfig;
101+
102+
if (!isNullOrNoneOrEmpty(vaultRole)) {
103+
if (!isNullOrNoneOrEmpty(vaultToken)) {
104+
LOGGER.warn(
105+
"Taking KubernetesVaultTokenSupplier by precedence rule, "
106+
+ "ignoring EnvironmentVaultTokenSupplier "
107+
+ "(specify either VAULT_ROLE or VAULT_TOKEN, not both)");
108+
}
109+
vaultTokenSupplier = new KubernetesVaultTokenSupplier().vaultRole(vaultRole);
110+
vaultConfig = new VaultConfig().address(vaultAddress).build();
111+
} else {
112+
vaultTokenSupplier = new EnvironmentVaultTokenSupplier();
113+
vaultConfig = new VaultConfig().address(vaultAddress).token(vaultToken).build();
114+
}
115+
116+
return vaultTokenSupplier.getToken(vaultConfig);
117+
} catch (VaultException e) {
118+
throw ThrowableUtil.propagate(e);
119+
}
120+
}
121+
122+
private static boolean isNullOrNoneOrEmpty(String value) {
123+
return Objects.isNull(value)
124+
|| "none".equalsIgnoreCase(value)
125+
|| "null".equals(value)
126+
|| value.isEmpty();
127+
}
128+
}

vault/src/main/java/io/scalecube/security/vault/VaultServiceRolesInstaller.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ private VaultServiceRolesInstaller(VaultServiceRolesInstaller other) {
4747
this.roleTtl = other.roleTtl;
4848
}
4949

50+
private VaultServiceRolesInstaller copy() {
51+
return new VaultServiceRolesInstaller(this);
52+
}
53+
5054
/**
5155
* Setter for vaultAddress.
5256
*
@@ -156,8 +160,8 @@ public VaultServiceRolesInstaller roleTtl(String roleTtl) {
156160
}
157161

158162
/**
159-
* Reads {@code serviceRolesFileName (access-file.yaml)} and builds micro-infrastructure for
160-
* machine-to-machine authentication in the vault.
163+
* Reads {@code inputFileName} and builds vault oidc micro-infrastructure (identity roles and
164+
* keys) to use it for machine-to-machine authentication.
161165
*/
162166
public void install() {
163167
if (isNullOrNoneOrEmpty(vaultAddress)) {
@@ -259,10 +263,6 @@ private String buildVaultIdentityRoleUri(String roleName) {
259263
.toString();
260264
}
261265

262-
private VaultServiceRolesInstaller copy() {
263-
return new VaultServiceRolesInstaller(this);
264-
}
265-
266266
private static boolean isNullOrNoneOrEmpty(String value) {
267267
return Objects.isNull(value)
268268
|| "none".equalsIgnoreCase(value)

vault/src/main/java/io/scalecube/security/vault/VaultServiceTokenSupplier.java

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
package io.scalecube.security.vault;
22

3+
import static io.scalecube.utils.MaskUtil.mask;
4+
35
import com.bettercloud.vault.json.Json;
46
import com.bettercloud.vault.rest.Rest;
57
import com.bettercloud.vault.rest.RestException;
68
import com.bettercloud.vault.rest.RestResponse;
7-
import io.scalecube.utils.MaskUtil;
89
import java.util.Map;
10+
import java.util.Objects;
911
import java.util.StringJoiner;
1012
import java.util.function.BiFunction;
11-
import java.util.function.Supplier;
1213
import org.slf4j.Logger;
1314
import org.slf4j.LoggerFactory;
1415
import reactor.core.Exceptions;
1516
import reactor.core.publisher.Mono;
17+
import reactor.core.scheduler.Schedulers;
1618

1719
public final class VaultServiceTokenSupplier {
1820

@@ -22,7 +24,7 @@ public final class VaultServiceTokenSupplier {
2224

2325
private String serviceRole;
2426
private String vaultAddress;
25-
private Supplier<String> vaultTokenSupplier;
27+
private Mono<String> vaultTokenSupplier;
2628
private BiFunction<String, Map<String, String>, String> serviceTokenNameBuilder;
2729

2830
public VaultServiceTokenSupplier() {}
@@ -34,6 +36,18 @@ private VaultServiceTokenSupplier(VaultServiceTokenSupplier other) {
3436
this.serviceTokenNameBuilder = other.serviceTokenNameBuilder;
3537
}
3638

39+
private VaultServiceTokenSupplier copy() {
40+
return new VaultServiceTokenSupplier(this);
41+
}
42+
43+
private void validate() {
44+
Objects.requireNonNull(serviceRole, "VaultServiceTokenSupplier.serviceRole");
45+
Objects.requireNonNull(vaultAddress, "VaultServiceTokenSupplier.vaultAddress");
46+
Objects.requireNonNull(vaultTokenSupplier, "VaultServiceTokenSupplier.vaultTokenSupplier");
47+
Objects.requireNonNull(
48+
serviceTokenNameBuilder, "VaultServiceTokenSupplier.serviceTokenNameBuilder");
49+
}
50+
3751
/**
3852
* Setter for serviceRole.
3953
*
@@ -64,7 +78,7 @@ public VaultServiceTokenSupplier vaultAddress(String vaultAddress) {
6478
* @param vaultTokenSupplier vaultTokenSupplier
6579
* @return new instance with applied setting
6680
*/
67-
public VaultServiceTokenSupplier vaultTokenSupplier(Supplier<String> vaultTokenSupplier) {
81+
public VaultServiceTokenSupplier vaultTokenSupplier(Mono<String> vaultTokenSupplier) {
6882
final VaultServiceTokenSupplier c = copy();
6983
c.vaultTokenSupplier = vaultTokenSupplier;
7084
return c;
@@ -85,59 +99,72 @@ public VaultServiceTokenSupplier serviceTokenNameBuilder(
8599
}
86100

87101
/**
88-
* Returns credentials as {@code Map<String, String>} for the given args.
102+
* Obtains vault service token (aka identity token or oidc token).
89103
*
90-
* @param tags tags attributes
104+
* @param tags tags attributes; along with {@code serviceRole} will be applied on {@code
105+
* serviceTokenNameBuilder}
91106
* @return vault service token
92107
*/
93-
public Mono<String> getServiceToken(Map<String, String> tags) {
94-
return Mono.fromCallable(vaultTokenSupplier::get)
95-
.map(vaultToken -> rpcGetServiceToken(tags, vaultToken))
96-
.doOnNext(response -> verifyOk(response.getStatus()))
97-
.map(
98-
response ->
99-
Json.parse(new String(response.getBody()))
100-
.asObject()
101-
.get("data")
102-
.asObject()
103-
.get("token")
104-
.asString())
105-
.doOnSuccess(
106-
creds ->
107-
LOGGER.info(
108-
"[rpcGetServiceToken] Successfully obtained vault service token: {}",
109-
MaskUtil.mask(creds)));
108+
public Mono<String> getToken(Map<String, String> tags) {
109+
return Mono.fromRunnable(this::validate)
110+
.then(Mono.defer(() -> vaultTokenSupplier))
111+
.flatMap(
112+
vaultToken -> {
113+
final String uri = buildServiceTokenUri(tags);
114+
return Mono.fromCallable(() -> rpcGetToken(uri, vaultToken))
115+
.subscribeOn(Schedulers.boundedElastic())
116+
.doOnSubscribe(
117+
s ->
118+
LOGGER.debug(
119+
"[getToken] Getting vault service token, uri='{}', tags={}",
120+
uri,
121+
tags))
122+
.doOnSuccess(
123+
s ->
124+
LOGGER.debug(
125+
"[getToken][success] uri='{}', tags={}, result: {}",
126+
uri,
127+
tags,
128+
mask(s)))
129+
.doOnError(
130+
th ->
131+
LOGGER.error(
132+
"[getToken][error] uri='{}', tags={}, cause: {}",
133+
uri,
134+
tags,
135+
th.toString()));
136+
});
110137
}
111138

112-
private RestResponse rpcGetServiceToken(Map<String, String> tags, String vaultToken) {
113-
String uri = buildVaultServiceTokenUri(tags);
114-
LOGGER.info("[rpcGetServiceToken] Getting vault service token (uri='{}')", uri);
139+
private String rpcGetToken(String uri, String vaultToken) {
115140
try {
116-
return new Rest().header(VAULT_TOKEN_HEADER, vaultToken).url(uri).get();
141+
final RestResponse response =
142+
new Rest().header(VAULT_TOKEN_HEADER, vaultToken).url(uri).get();
143+
144+
verifyOk(response.getStatus());
145+
146+
return Json.parse(new String(response.getBody()))
147+
.asObject()
148+
.get("data")
149+
.asObject()
150+
.get("token")
151+
.asString();
117152
} catch (RestException e) {
118-
LOGGER.error(
119-
"[rpcGetServiceToken] Failed to get vault service token (uri='{}'), cause: {}",
120-
uri,
121-
e.toString());
122153
throw Exceptions.propagate(e);
123154
}
124155
}
125156

126157
private static void verifyOk(int status) {
127158
if (status != 200) {
128-
LOGGER.error("[rpcGetServiceToken] Not expected status ({}) returned", status);
159+
LOGGER.error("[rpcGetToken] Not expected status ({}) returned", status);
129160
throw new IllegalStateException("Not expected status returned, status=" + status);
130161
}
131162
}
132163

133-
private String buildVaultServiceTokenUri(Map<String, String> tags) {
164+
private String buildServiceTokenUri(Map<String, String> tags) {
134165
return new StringJoiner("/", vaultAddress, "")
135166
.add("v1/identity/oidc/token")
136167
.add(serviceTokenNameBuilder.apply(serviceRole, tags))
137168
.toString();
138169
}
139-
140-
private VaultServiceTokenSupplier copy() {
141-
return new VaultServiceTokenSupplier(this);
142-
}
143170
}

0 commit comments

Comments
 (0)