Skip to content

Commit 684f7a9

Browse files
committed
Added scalecube-services-security-vault
1 parent ce936c4 commit 684f7a9

File tree

3 files changed

+459
-0
lines changed

3 files changed

+459
-0
lines changed

services-security-parent/services-security-vault/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
<artifactId>scalecube-services</artifactId>
1919
<version>${project.version}</version>
2020
</dependency>
21+
<dependency>
22+
<groupId>io.scalecube</groupId>
23+
<artifactId>scalecube-services-security</artifactId>
24+
<version>${project.version}</version>
25+
</dependency>
2126
<!-- Other -->
2227
<dependency>
2328
<groupId>io.scalecube</groupId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
package io.scalecube.services.security.vault;
2+
3+
import com.bettercloud.vault.json.Json;
4+
import com.bettercloud.vault.rest.Rest;
5+
import com.bettercloud.vault.rest.RestException;
6+
import io.scalecube.services.security.vault.VaultServiceRolesInstaller.ServiceRoles.Role;
7+
import java.io.InputStream;
8+
import java.util.Base64;
9+
import java.util.List;
10+
import java.util.Objects;
11+
import java.util.StringJoiner;
12+
import java.util.function.Function;
13+
import java.util.function.Supplier;
14+
import org.slf4j.Logger;
15+
import org.slf4j.LoggerFactory;
16+
import org.yaml.snakeyaml.Yaml;
17+
import org.yaml.snakeyaml.constructor.Constructor;
18+
import reactor.core.Exceptions;
19+
20+
public final class VaultServiceRolesInstaller {
21+
22+
private static final Logger LOGGER = LoggerFactory.getLogger(VaultServiceRolesInstaller.class);
23+
24+
private static final String VAULT_TOKEN_HEADER = "X-Vault-Token";
25+
26+
private String vaultAddress;
27+
private Supplier<String> vaultTokenSupplier;
28+
private Supplier<String> keyNameSupplier;
29+
private Function<String, String> roleNameBuilder;
30+
private String inputFileName = "service-roles.yaml";
31+
private String keyAlgorithm = "RS256";
32+
private String keyRotationPeriod = "1h";
33+
private String keyVerificationTtl = "1h";
34+
private String roleTtl = "1m";
35+
36+
public VaultServiceRolesInstaller() {}
37+
38+
private VaultServiceRolesInstaller(VaultServiceRolesInstaller other) {
39+
this.vaultAddress = other.vaultAddress;
40+
this.vaultTokenSupplier = other.vaultTokenSupplier;
41+
this.keyNameSupplier = other.keyNameSupplier;
42+
this.roleNameBuilder = other.roleNameBuilder;
43+
this.inputFileName = other.inputFileName;
44+
this.keyAlgorithm = other.keyAlgorithm;
45+
this.keyRotationPeriod = other.keyRotationPeriod;
46+
this.keyVerificationTtl = other.keyVerificationTtl;
47+
this.roleTtl = other.roleTtl;
48+
}
49+
50+
/**
51+
* Setter for vaultAddress.
52+
*
53+
* @param vaultAddress vaultAddress
54+
* @return new instance with applied setting
55+
*/
56+
public VaultServiceRolesInstaller vaultAddress(String vaultAddress) {
57+
final VaultServiceRolesInstaller c = copy();
58+
c.vaultAddress = vaultAddress;
59+
return c;
60+
}
61+
62+
/**
63+
* Setter for vaultTokenSupplier.
64+
*
65+
* @param vaultTokenSupplier vaultTokenSupplier
66+
* @return new instance with applied setting
67+
*/
68+
public VaultServiceRolesInstaller vaultTokenSupplier(Supplier<String> vaultTokenSupplier) {
69+
final VaultServiceRolesInstaller c = copy();
70+
c.vaultTokenSupplier = vaultTokenSupplier;
71+
return c;
72+
}
73+
74+
/**
75+
* Setter for keyNameSupplier.
76+
*
77+
* @param keyNameSupplier keyNameSupplier
78+
* @return new instance with applied setting
79+
*/
80+
public VaultServiceRolesInstaller keyNameSupplier(Supplier<String> keyNameSupplier) {
81+
final VaultServiceRolesInstaller c = copy();
82+
c.keyNameSupplier = keyNameSupplier;
83+
return c;
84+
}
85+
86+
/**
87+
* Setter for roleNameBuilder.
88+
*
89+
* @param roleNameBuilder roleNameBuilder
90+
* @return new instance with applied setting
91+
*/
92+
public VaultServiceRolesInstaller roleNameBuilder(Function<String, String> roleNameBuilder) {
93+
final VaultServiceRolesInstaller c = copy();
94+
c.roleNameBuilder = roleNameBuilder;
95+
return c;
96+
}
97+
98+
/**
99+
* Setter for inputFileName.
100+
*
101+
* @param inputFileName inputFileName
102+
* @return new instance with applied setting
103+
*/
104+
public VaultServiceRolesInstaller inputFileName(String inputFileName) {
105+
final VaultServiceRolesInstaller c = copy();
106+
c.inputFileName = inputFileName;
107+
return c;
108+
}
109+
110+
/**
111+
* Setter for keyAlgorithm.
112+
*
113+
* @param keyAlgorithm keyAlgorithm
114+
* @return new instance with applied setting
115+
*/
116+
public VaultServiceRolesInstaller keyAlgorithm(String keyAlgorithm) {
117+
final VaultServiceRolesInstaller c = copy();
118+
c.keyAlgorithm = keyAlgorithm;
119+
return c;
120+
}
121+
122+
/**
123+
* Setter for keyRotationPeriod.
124+
*
125+
* @param keyRotationPeriod keyRotationPeriod
126+
* @return new instance with applied setting
127+
*/
128+
public VaultServiceRolesInstaller keyRotationPeriod(String keyRotationPeriod) {
129+
final VaultServiceRolesInstaller c = copy();
130+
c.keyRotationPeriod = keyRotationPeriod;
131+
return c;
132+
}
133+
134+
/**
135+
* Setter for keyVerificationTtl.
136+
*
137+
* @param keyVerificationTtl keyVerificationTtl
138+
* @return new instance with applied setting
139+
*/
140+
public VaultServiceRolesInstaller keyVerificationTtl(String keyVerificationTtl) {
141+
final VaultServiceRolesInstaller c = copy();
142+
c.keyVerificationTtl = keyVerificationTtl;
143+
return c;
144+
}
145+
146+
/**
147+
* Setter for roleTtl.
148+
*
149+
* @param roleTtl roleTtl
150+
* @return new instance with applied setting
151+
*/
152+
public VaultServiceRolesInstaller roleTtl(String roleTtl) {
153+
final VaultServiceRolesInstaller c = copy();
154+
c.roleTtl = roleTtl;
155+
return c;
156+
}
157+
158+
/**
159+
* Reads {@code serviceRolesFileName (access-file.yaml)} and builds micro-infrastructure for
160+
* machine-to-machine authentication in the vault.
161+
*/
162+
public void install() {
163+
if (isNullOrNoneOrEmpty(vaultAddress)) {
164+
return;
165+
}
166+
167+
final ServiceRoles serviceRoles = loadServiceRoles();
168+
if (serviceRoles == null) {
169+
return;
170+
}
171+
172+
final Rest rest = new Rest().header(VAULT_TOKEN_HEADER, vaultTokenSupplier.get());
173+
174+
if (!serviceRoles.roles.isEmpty()) {
175+
String keyName = keyNameSupplier.get();
176+
createVaultIdentityKey(keyName, () -> rest.url(buildVaultIdentityKeyUri(keyName)));
177+
178+
for (Role role : serviceRoles.roles) {
179+
String roleName = roleNameBuilder.apply(role.role);
180+
createVaultIdentityRole(
181+
keyName,
182+
roleName,
183+
role.permissions,
184+
() -> rest.url(buildVaultIdentityRoleUri(roleName)));
185+
}
186+
}
187+
}
188+
189+
private ServiceRoles loadServiceRoles() {
190+
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
191+
InputStream inputStream = classLoader.getResourceAsStream(inputFileName);
192+
return inputStream != null
193+
? new Yaml(new Constructor(ServiceRoles.class)).load(inputStream)
194+
: null;
195+
}
196+
197+
private static void verifyOk(int status, String operation) {
198+
if (status != 200 && status != 204) {
199+
LOGGER.error("Not expected status ({}) returned on [{}]", status, operation);
200+
throw new IllegalStateException("Not expected status returned, status=" + status);
201+
}
202+
}
203+
204+
private void createVaultIdentityKey(String keyName, Supplier<Rest> restSupplier) {
205+
LOGGER.debug("[createVaultIdentityKey] {}", keyName);
206+
207+
byte[] body =
208+
Json.object()
209+
.add("rotation_period", keyRotationPeriod)
210+
.add("verification_ttl", keyVerificationTtl)
211+
.add("allowed_client_ids", "*")
212+
.add("algorithm", keyAlgorithm)
213+
.toString()
214+
.getBytes();
215+
216+
try {
217+
verifyOk(restSupplier.get().body(body).post().getStatus(), "createVaultIdentityKey");
218+
} catch (RestException e) {
219+
throw Exceptions.propagate(e);
220+
}
221+
}
222+
223+
private void createVaultIdentityRole(
224+
String keyName, String roleName, List<String> permissions, Supplier<Rest> restSupplier) {
225+
LOGGER.debug("[createVaultIdentityRole] {}", roleName);
226+
227+
byte[] body =
228+
Json.object()
229+
.add("key", keyName)
230+
.add("template", createTemplate(permissions))
231+
.add("ttl", roleTtl)
232+
.toString()
233+
.getBytes();
234+
235+
try {
236+
verifyOk(restSupplier.get().body(body).post().getStatus(), "createVaultIdentityRole");
237+
} catch (RestException e) {
238+
throw Exceptions.propagate(e);
239+
}
240+
}
241+
242+
private static String createTemplate(List<String> permissions) {
243+
return Base64.getUrlEncoder()
244+
.encodeToString(
245+
Json.object().add("permissions", String.join(",", permissions)).toString().getBytes());
246+
}
247+
248+
private String buildVaultIdentityKeyUri(String keyName) {
249+
return new StringJoiner("/", vaultAddress, "")
250+
.add("v1/identity/oidc/key")
251+
.add(keyName)
252+
.toString();
253+
}
254+
255+
private String buildVaultIdentityRoleUri(String roleName) {
256+
return new StringJoiner("/", vaultAddress, "")
257+
.add("v1/identity/oidc/role")
258+
.add(roleName)
259+
.toString();
260+
}
261+
262+
private VaultServiceRolesInstaller copy() {
263+
return new VaultServiceRolesInstaller(this);
264+
}
265+
266+
private static boolean isNullOrNoneOrEmpty(String value) {
267+
return Objects.isNull(value)
268+
|| "none".equalsIgnoreCase(value)
269+
|| "null".equals(value)
270+
|| value.isEmpty();
271+
}
272+
273+
public static class ServiceRoles {
274+
275+
private List<Role> roles;
276+
277+
public List<Role> getRoles() {
278+
return roles;
279+
}
280+
281+
public void setRoles(List<Role> roles) {
282+
this.roles = roles;
283+
}
284+
285+
public static class Role {
286+
287+
private String role;
288+
private List<String> permissions;
289+
290+
public String getRole() {
291+
return role;
292+
}
293+
294+
public void setRole(String role) {
295+
this.role = role;
296+
}
297+
298+
public List<String> getPermissions() {
299+
return permissions;
300+
}
301+
302+
public void setPermissions(List<String> permissions) {
303+
this.permissions = permissions;
304+
}
305+
}
306+
}
307+
}

0 commit comments

Comments
 (0)