Skip to content

Commit 4a60c1c

Browse files
alintnersfdcmp911de
authored andcommitted
Role name can now be used with cert auth.
When using TLS authentication, the Vault API allows the caller to specify a role to use for authorization. Previously, we did not allow this to be specified, which caused Vault to select one of the roles associated with the certificate. See gh-780
1 parent 3734309 commit 4a60c1c

File tree

6 files changed

+175
-40
lines changed

6 files changed

+175
-40
lines changed

spring-vault-core/src/main/java/org/springframework/vault/authentication/ClientCertificateAuthentication.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@
1515
*/
1616
package org.springframework.vault.authentication;
1717

18+
import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.post;
19+
1820
import java.util.Collections;
21+
import java.util.Map;
1922

2023
import org.apache.commons.logging.Log;
2124
import org.apache.commons.logging.LogFactory;
22-
2325
import org.springframework.util.Assert;
2426
import org.springframework.vault.support.VaultResponse;
2527
import org.springframework.vault.support.VaultToken;
2628
import org.springframework.web.client.RestClientException;
2729
import org.springframework.web.client.RestOperations;
2830

29-
import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.post;
30-
3131
/**
3232
* TLS Client Certificate {@link ClientAuthentication}.
3333
*
@@ -81,11 +81,13 @@ public static AuthenticationSteps createAuthenticationSteps() {
8181
* @since 2.3
8282
*/
8383
public static AuthenticationSteps createAuthenticationSteps(ClientCertificateAuthenticationOptions options) {
84-
8584
Assert.notNull(options, "ClientCertificateAuthenticationOptions must not be null");
8685

87-
return AuthenticationSteps
88-
.just(post(AuthenticationUtil.getLoginPath(options.getPath())).as(VaultResponse.class));
86+
String name = options.getName();
87+
Map<String, String> body = name != null ? Collections.singletonMap("name", name) : Collections.emptyMap();
88+
89+
return AuthenticationSteps.fromSupplier(() -> body)
90+
.login(post(AuthenticationUtil.getLoginPath(options.getPath())).as(VaultResponse.class));
8991
}
9092

9193
@Override
@@ -101,8 +103,11 @@ public AuthenticationSteps getAuthenticationSteps() {
101103
private VaultToken createTokenUsingTlsCertAuthentication() {
102104

103105
try {
106+
String name = this.options.getName();
107+
104108
VaultResponse response = this.restOperations.postForObject(
105-
AuthenticationUtil.getLoginPath(this.options.getPath()), Collections.emptyMap(),
109+
AuthenticationUtil.getLoginPath(this.options.getPath()),
110+
name != null ? Collections.singletonMap("name", name) : Collections.emptyMap(),
106111
VaultResponse.class);
107112

108113
Assert.state(response.getAuth() != null, "Auth field must not be null");

spring-vault-core/src/main/java/org/springframework/vault/authentication/ClientCertificateAuthenticationOptions.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.vault.authentication;
1717

18+
import org.springframework.lang.Nullable;
1819
import org.springframework.util.Assert;
1920

2021
/**
@@ -38,8 +39,15 @@ public class ClientCertificateAuthenticationOptions {
3839
*/
3940
private final String path;
4041

41-
private ClientCertificateAuthenticationOptions(String path) {
42+
/**
43+
* Optional named certificate role to authenticate against.
44+
*/
45+
@Nullable
46+
private final String name;
47+
48+
private ClientCertificateAuthenticationOptions(String path, String name) {
4249
this.path = path;
50+
this.name = name;
4351
}
4452

4553
/**
@@ -56,13 +64,24 @@ public String getPath() {
5664
return this.path;
5765
}
5866

67+
/**
68+
* @return the optional named certificate role to authenticate against.
69+
*/
70+
@Nullable
71+
public String getName() {
72+
return this.name;
73+
}
74+
5975
/**
6076
* Builder for {@link ClientCertificateAuthenticationOptions}.
6177
*/
6278
public static class ClientCertificateAuthenticationOptionsBuilder {
6379

6480
private String path = DEFAULT_CERT_PATH;
6581

82+
@Nullable
83+
private String name;
84+
6685
ClientCertificateAuthenticationOptionsBuilder() {
6786
}
6887

@@ -79,12 +98,25 @@ public ClientCertificateAuthenticationOptionsBuilder path(String path) {
7998
return this;
8099
}
81100

101+
/**
102+
* Configure the named certificate role to authenticate against.
103+
* @param name must not be empty or {@literal null}.
104+
* @return {@code this} {@link ClientCertificateAuthenticationOptionsBuilder}.
105+
*/
106+
public ClientCertificateAuthenticationOptionsBuilder name(String name) {
107+
108+
Assert.hasText(name, "Name must not be empty");
109+
110+
this.name = name;
111+
return this;
112+
}
113+
82114
/**
83115
* Build a new {@link ClientCertificateAuthenticationOptions} instance.
84116
* @return a new {@link ClientCertificateAuthenticationOptions}.
85117
*/
86118
public ClientCertificateAuthenticationOptions build() {
87-
return new ClientCertificateAuthenticationOptions(this.path);
119+
return new ClientCertificateAuthenticationOptions(this.path, this.name);
88120
}
89121

90122
}

spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTestBase.java

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,55 +15,92 @@
1515
*/
1616
package org.springframework.vault.authentication;
1717

18+
import static org.assertj.core.api.Assertions.as;
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.springframework.vault.util.Settings.createSslConfiguration;
21+
import static org.springframework.vault.util.Settings.findWorkDir;
22+
1823
import java.io.File;
1924
import java.nio.charset.StandardCharsets;
2025
import java.util.LinkedHashMap;
2126
import java.util.Map;
2227

28+
import org.assertj.core.api.InstanceOfAssertFactories;
29+
import org.assertj.core.api.ListAssert;
2330
import org.assertj.core.util.Files;
2431
import org.junit.jupiter.api.BeforeEach;
25-
32+
import org.springframework.core.ParameterizedTypeReference;
2633
import org.springframework.core.io.FileSystemResource;
34+
import org.springframework.http.*;
35+
import org.springframework.vault.client.VaultHttpHeaders;
2736
import org.springframework.vault.core.RestOperationsCallback;
28-
import org.springframework.vault.support.Policy;
29-
import org.springframework.vault.support.SslConfiguration;
37+
import org.springframework.vault.core.VaultOperations;
38+
import org.springframework.vault.support.*;
3039
import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration;
3140
import org.springframework.vault.util.IntegrationTestSupport;
3241

33-
import static org.springframework.vault.util.Settings.createSslConfiguration;
34-
import static org.springframework.vault.util.Settings.findWorkDir;
35-
3642
/**
3743
* Integration test base class for {@link ClientCertificateAuthentication} tests.
3844
*
3945
* @author Mark Paluch
4046
*/
4147
public abstract class ClientCertificateAuthenticationIntegrationTestBase extends IntegrationTestSupport {
4248

43-
static final Policy POLICY = Policy
44-
.of(Policy.Rule.builder().path("/*").capabilities(Policy.BuiltinCapabilities.READ,
49+
static final Policy DEFAULT_POLICY = Policy
50+
.of(Policy.Rule.builder().path("/default/*").capabilities(Policy.BuiltinCapabilities.READ,
4551
Policy.BuiltinCapabilities.CREATE, Policy.BuiltinCapabilities.UPDATE).build());
4652

53+
static final Policy ALTERNATE_POLICY = Policy
54+
.of(Policy.Rule.builder().path("/alternate/*").capabilities(Policy.BuiltinCapabilities.READ,
55+
Policy.BuiltinCapabilities.CREATE, Policy.BuiltinCapabilities.UPDATE).build());
56+
57+
VaultOperations vaultOperations;
58+
4759
@BeforeEach
4860
public void before() {
4961

5062
if (!prepare().hasAuth("cert")) {
5163
prepare().mountAuth("cert");
5264
}
5365

54-
prepare().getVaultOperations().opsForSys().createOrUpdatePolicy("cert-auth", POLICY);
66+
vaultOperations = prepare().getVaultOperations();
5567

56-
prepare().getVaultOperations().doWithSession((RestOperationsCallback<Object>) restOperations -> {
68+
vaultOperations.opsForSys().createOrUpdatePolicy("cert-auth1", DEFAULT_POLICY);
69+
vaultOperations.opsForSys().createOrUpdatePolicy("cert-auth2", ALTERNATE_POLICY);
70+
71+
vaultOperations.doWithSession((RestOperationsCallback<Object>) restOperations -> {
5772
File workDir = findWorkDir();
5873

5974
String certificate = Files.contentOf(new File(workDir, "ca/certs/client.cert.pem"),
6075
StandardCharsets.US_ASCII);
6176

6277
Map<String, Object> role = new LinkedHashMap<>();
63-
role.put("token_policies", "cert-auth");
78+
role.put("token_policies", "cert-auth1");
6479
role.put("certificate", certificate);
6580

66-
return restOperations.postForEntity("auth/cert/certs/my-role", role, Map.class);
81+
restOperations.postForEntity("auth/cert/certs/my-default-role", role, Map.class);
82+
83+
role.put("token_policies", "cert-auth2");
84+
restOperations.postForEntity("auth/cert/certs/my-alternate-role", role, Map.class);
85+
return true;
86+
});
87+
}
88+
89+
ListAssert<String> assertThatPolicies(final VaultToken token) {
90+
return assertThat(lookupSelf(token).getBody()).isNotNull()
91+
.extracting("data", as(InstanceOfAssertFactories.map(String.class, Object.class))).isNotNull()
92+
.extracting("policies", as(InstanceOfAssertFactories.list(String.class))).isNotNull();
93+
}
94+
95+
ResponseEntity<Map<String, Object>> lookupSelf(final VaultToken token) {
96+
97+
return vaultOperations.doWithVault(restOperations -> {
98+
HttpHeaders headers = new HttpHeaders();
99+
headers.add(VaultHttpHeaders.VAULT_TOKEN, token.getToken());
100+
101+
return restOperations.exchange("auth/token/lookup-self", HttpMethod.GET, new HttpEntity<>(headers),
102+
new ParameterizedTypeReference<Map<String, Object>>() {
103+
});
67104
});
68105
}
69106

spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTests.java

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,18 @@
1515
*/
1616
package org.springframework.vault.authentication;
1717

18-
import org.junit.jupiter.api.Test;
18+
import static org.assertj.core.api.Assertions.*;
1919

20+
import org.junit.jupiter.api.Test;
2021
import org.springframework.core.NestedRuntimeException;
2122
import org.springframework.http.client.ClientHttpRequestFactory;
2223
import org.springframework.vault.client.ClientHttpRequestFactoryFactory;
2324
import org.springframework.vault.client.VaultClients;
24-
import org.springframework.vault.support.ClientOptions;
25-
import org.springframework.vault.support.SslConfiguration;
26-
import org.springframework.vault.support.VaultToken;
25+
import org.springframework.vault.support.*;
2726
import org.springframework.vault.util.Settings;
2827
import org.springframework.vault.util.TestRestTemplateFactory;
2928
import org.springframework.web.client.RestTemplate;
3029

31-
import static org.assertj.core.api.Assertions.assertThat;
32-
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
33-
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
34-
3530
/**
3631
* Integration tests for {@link ClientCertificateAuthentication}.
3732
*
@@ -87,6 +82,38 @@ void shouldProvideInvalidKeyPassword() {
8782
prepareCertAuthenticationMethod(SslConfiguration.KeyConfiguration.of("wrong".toCharArray(), "1"))));
8883
}
8984

85+
@Test
86+
void shouldSelectRoleOne() {
87+
ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory.create(new ClientOptions(),
88+
prepareCertAuthenticationMethod());
89+
90+
RestTemplate restTemplate = VaultClients.createRestTemplate(TestRestTemplateFactory.TEST_VAULT_ENDPOINT,
91+
clientHttpRequestFactory);
92+
ClientCertificateAuthentication authentication = new ClientCertificateAuthentication(
93+
ClientCertificateAuthenticationOptions.builder().name("my-default-role").build(), restTemplate);
94+
VaultToken login = authentication.login();
95+
96+
assertThat(login.getToken()).isNotEmpty();
97+
assertThatPolicies(login).contains("cert-auth1") //
98+
.doesNotContain("cert-auth2");
99+
}
100+
101+
@Test
102+
void shouldSelectRoleTwo() {
103+
ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory.create(new ClientOptions(),
104+
prepareCertAuthenticationMethod());
105+
106+
RestTemplate restTemplate = VaultClients.createRestTemplate(TestRestTemplateFactory.TEST_VAULT_ENDPOINT,
107+
clientHttpRequestFactory);
108+
ClientCertificateAuthentication authentication = new ClientCertificateAuthentication(
109+
ClientCertificateAuthenticationOptions.builder().name("my-alternate-role").build(), restTemplate);
110+
VaultToken login = authentication.login();
111+
112+
assertThat(login.getToken()).isNotEmpty();
113+
assertThatPolicies(login).contains("cert-auth2") //
114+
.doesNotContain("cert-auth1");
115+
}
116+
90117
// Compatibility for Vault 0.6.0 and below. Vault 0.6.1 fixed that issue and we
91118
// receive a VaultException here.
92119
@Test

spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationOperatorIntegrationTests.java

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
*/
1616
package org.springframework.vault.authentication;
1717

18-
import org.junit.jupiter.api.Test;
19-
import reactor.test.StepVerifier;
18+
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
2019

20+
import org.junit.jupiter.api.Test;
2121
import org.springframework.vault.support.SslConfiguration;
2222
import org.springframework.vault.util.TestWebClientFactory;
2323
import org.springframework.web.reactive.function.client.WebClient;
2424

25-
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
25+
import reactor.test.StepVerifier;
2626

2727
/**
2828
* Integration tests for {@link ClientCertificateAuthentication} using
@@ -76,6 +76,40 @@ void shouldSelectInvalidKey() {
7676
.verifyError(VaultLoginException.class);
7777
}
7878

79+
@Test
80+
void shouldSelectRoleOne() {
81+
82+
WebClient webClient = TestWebClientFactory.create(prepareCertAuthenticationMethod());
83+
84+
AuthenticationStepsOperator operator = new AuthenticationStepsOperator(
85+
ClientCertificateAuthentication.createAuthenticationSteps(
86+
ClientCertificateAuthenticationOptions.builder().name("my-default-role").build()),
87+
webClient);
88+
89+
operator.getVaultToken() //
90+
.as(StepVerifier::create) //
91+
.assertNext(token -> assertThatPolicies(token).contains("cert-auth1") //
92+
.doesNotContain("cert-auth2")) //
93+
.verifyComplete();
94+
}
95+
96+
@Test
97+
void shouldSelectRoleTwo() {
98+
99+
WebClient webClient = TestWebClientFactory.create(prepareCertAuthenticationMethod());
100+
101+
AuthenticationStepsOperator operator = new AuthenticationStepsOperator(
102+
ClientCertificateAuthentication.createAuthenticationSteps(
103+
ClientCertificateAuthenticationOptions.builder().name("my-alternate-role").build()),
104+
webClient);
105+
106+
operator.getVaultToken() //
107+
.as(StepVerifier::create) //
108+
.assertNext(token -> assertThatPolicies(token).contains("cert-auth2") //
109+
.doesNotContain("cert-auth1")) //
110+
.verifyComplete();
111+
}
112+
79113
@Test
80114
void shouldProvideInvalidKeyPassword() {
81115
assertThatIllegalStateException().isThrownBy(() -> TestWebClientFactory.create(

0 commit comments

Comments
 (0)