Skip to content

Commit 48b7f73

Browse files
committed
Add support for RestClient.
We now accept RestClient instances for our authentication implementations and to construct VaultTemplate. The config part remains as-is without exposing RestClient any further through VaultTemplate. Closes gh-941
1 parent 99b3ee5 commit 48b7f73

37 files changed

+1467
-263
lines changed

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

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.springframework.vault.support.VaultResponse;
4141
import org.springframework.vault.support.VaultToken;
4242
import org.springframework.web.client.HttpStatusCodeException;
43+
import org.springframework.web.client.RestClient;
4344
import org.springframework.web.client.RestClientException;
4445
import org.springframework.web.client.RestOperations;
4546

@@ -67,7 +68,7 @@ public class AppRoleAuthentication implements ClientAuthentication, Authenticati
6768

6869
private final AppRoleAuthenticationOptions options;
6970

70-
private final RestOperations restOperations;
71+
private final ClientAdapter adapter;
7172

7273
/**
7374
* Create a {@link AppRoleAuthentication} using {@link AppRoleAuthenticationOptions}
@@ -81,7 +82,23 @@ public AppRoleAuthentication(AppRoleAuthenticationOptions options, RestOperation
8182
Assert.notNull(restOperations, "RestOperations must not be null");
8283

8384
this.options = options;
84-
this.restOperations = restOperations;
85+
this.adapter = ClientAdapter.from(restOperations);
86+
}
87+
88+
/**
89+
* Create a {@link AppRoleAuthentication} using {@link AppRoleAuthenticationOptions}
90+
* and {@link RestClient}.
91+
* @param options must not be {@literal null}.
92+
* @param client must not be {@literal null}.
93+
* @since 4.0
94+
*/
95+
public AppRoleAuthentication(AppRoleAuthenticationOptions options, RestClient client) {
96+
97+
Assert.notNull(options, "AppRoleAuthenticationOptions must not be null");
98+
Assert.notNull(client, "RestClient must not be null");
99+
100+
this.options = options;
101+
this.adapter = ClientAdapter.from(client);
85102
}
86103

87104
/**
@@ -186,7 +203,7 @@ private VaultToken createTokenUsingAppRole() {
186203
Map<String, String> login = getAppRoleLoginBody(this.options.getRoleId(), this.options.getSecretId());
187204

188205
try {
189-
VaultResponse response = this.restOperations.postForObject(getLoginPath(this.options.getPath()), login,
206+
VaultResponse response = this.adapter.postForObject(getLoginPath(this.options.getPath()), login,
190207
VaultResponse.class);
191208

192209
Assert.state(response != null && response.getAuth() != null, "Auth field must not be null");
@@ -212,7 +229,7 @@ private String getRoleId(RoleId roleId) throws VaultLoginException {
212229

213230
try {
214231

215-
ResponseEntity<VaultResponse> entity = this.restOperations.exchange(getRoleIdIdPath(this.options),
232+
ResponseEntity<VaultResponse> entity = this.adapter.exchange(getRoleIdIdPath(this.options),
216233
HttpMethod.GET, createHttpEntity(token), VaultResponse.class);
217234
return (String) ResponseUtil.getRequiredValue(entity, "role_id");
218235
}
@@ -228,7 +245,7 @@ private String getRoleId(RoleId roleId) throws VaultLoginException {
228245

229246
try {
230247
UnwrappingEndpoints unwrappingEndpoints = this.options.getUnwrappingEndpoints();
231-
ResponseEntity<VaultResponse> entity = this.restOperations.exchange(unwrappingEndpoints.getPath(),
248+
ResponseEntity<VaultResponse> entity = this.adapter.exchange(unwrappingEndpoints.getPath(),
232249
unwrappingEndpoints.getUnwrapRequestMethod(), createHttpEntity(token), VaultResponse.class);
233250

234251
VaultResponse response = unwrappingEndpoints.unwrap(ResponseUtil.getRequiredBody(entity));
@@ -256,7 +273,7 @@ private String getSecretId(SecretId secretId) throws VaultLoginException {
256273
VaultToken token = ((Pull) secretId).getInitialToken();
257274

258275
try {
259-
VaultResponse response = this.restOperations.postForObject(getSecretIdPath(this.options),
276+
VaultResponse response = this.adapter.postForObject(getSecretIdPath(this.options),
260277
createHttpEntity(token), VaultResponse.class);
261278
return (String) ResponseUtil.getRequiredData(response).get("secret_id");
262279
}
@@ -273,7 +290,7 @@ private String getSecretId(SecretId secretId) throws VaultLoginException {
273290
try {
274291

275292
UnwrappingEndpoints unwrappingEndpoints = this.options.getUnwrappingEndpoints();
276-
ResponseEntity<VaultResponse> entity = this.restOperations.exchange(unwrappingEndpoints.getPath(),
293+
ResponseEntity<VaultResponse> entity = this.adapter.exchange(unwrappingEndpoints.getPath(),
277294
unwrappingEndpoints.getUnwrapRequestMethod(), createHttpEntity(token), VaultResponse.class);
278295

279296
VaultResponse response = unwrappingEndpoints.unwrap(ResponseUtil.getRequiredBody(entity));

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

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.vault.support.VaultResponse;
3838
import org.springframework.vault.support.VaultToken;
3939
import org.springframework.web.client.HttpStatusCodeException;
40+
import org.springframework.web.client.RestClient;
4041
import org.springframework.web.client.RestOperations;
4142

4243
/**
@@ -53,7 +54,7 @@ public class AuthenticationStepsExecutor implements ClientAuthentication {
5354

5455
private final AuthenticationSteps chain;
5556

56-
private final RestOperations restOperations;
57+
private final ClientAdapter adapter;
5758

5859
/**
5960
* Create a new {@link AuthenticationStepsExecutor} given {@link AuthenticationSteps}
@@ -67,7 +68,22 @@ public AuthenticationStepsExecutor(AuthenticationSteps steps, RestOperations res
6768
Assert.notNull(restOperations, "RestOperations must not be null");
6869

6970
this.chain = steps;
70-
this.restOperations = restOperations;
71+
this.adapter = ClientAdapter.from(restOperations);
72+
}
73+
74+
/**
75+
* Create a new {@link AuthenticationStepsExecutor} given {@link AuthenticationSteps}
76+
* and {@link RestOperations}.
77+
* @param steps must not be {@literal null}.
78+
* @param client must not be {@literal null}.
79+
*/
80+
public AuthenticationStepsExecutor(AuthenticationSteps steps, RestClient client) {
81+
82+
Assert.notNull(steps, "AuthenticationSteps must not be null");
83+
Assert.notNull(client, "RestClient must not be null");
84+
85+
this.chain = steps;
86+
this.adapter = ClientAdapter.from(client);
7187
}
7288

7389
@Override
@@ -152,16 +168,16 @@ public VaultToken login() throws VaultException {
152168

153169
if (definition.getUriTemplate() != null) {
154170

155-
ResponseEntity<?> exchange = this.restOperations.exchange(definition.getUriTemplate(),
156-
definition.getMethod(), getEntity(definition.getEntity(), state), definition.getResponseType(),
171+
ResponseEntity<?> exchange = this.adapter.exchange(definition.getUriTemplate(), definition.getMethod(),
172+
getEntity(definition.getEntity(), state), definition.getResponseType(),
157173
(Object[]) definition.getUrlVariables());
158174

159175
return exchange.getBody();
160176
}
161177

162178
if (definition.getUri() != null) {
163179

164-
ResponseEntity<?> exchange = this.restOperations.exchange(definition.getUri(), definition.getMethod(),
180+
ResponseEntity<?> exchange = this.adapter.exchange(definition.getUri(), definition.getMethod(),
165181
getEntity(definition.getEntity(), state), definition.getResponseType());
166182

167183
return exchange.getBody();

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

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.vault.support.VaultResponse;
3737
import org.springframework.vault.support.VaultToken;
3838
import org.springframework.web.client.HttpClientErrorException;
39+
import org.springframework.web.client.RestClient;
3940
import org.springframework.web.client.RestClientException;
4041
import org.springframework.web.client.RestOperations;
4142

@@ -64,9 +65,9 @@ public class AwsEc2Authentication implements ClientAuthentication, Authenticatio
6465

6566
private final AwsEc2AuthenticationOptions options;
6667

67-
private final RestOperations vaultRestOperations;
68+
private final ClientAdapter vaultAdapter;
6869

69-
private final RestOperations awsMetadataRestOperations;
70+
private final ClientAdapter awsMetadataAdapter;
7071

7172
private final AtomicReference<char[]> nonce = new AtomicReference<>(EMPTY);
7273

@@ -94,8 +95,38 @@ public AwsEc2Authentication(AwsEc2AuthenticationOptions options, RestOperations
9495
Assert.notNull(awsMetadataRestOperations, "AWS Metadata RestOperations must not be null");
9596

9697
this.options = options;
97-
this.vaultRestOperations = vaultRestOperations;
98-
this.awsMetadataRestOperations = awsMetadataRestOperations;
98+
this.vaultAdapter = ClientAdapter.from(vaultRestOperations);
99+
this.awsMetadataAdapter = ClientAdapter.from(awsMetadataRestOperations);
100+
}
101+
102+
/**
103+
* Create a new {@link AwsEc2Authentication}.
104+
* @param vaultClient must not be {@literal null}.
105+
* @since 4.0
106+
*/
107+
public AwsEc2Authentication(RestClient vaultClient) {
108+
this(AwsEc2AuthenticationOptions.DEFAULT, vaultClient, vaultClient);
109+
}
110+
111+
/**
112+
* Create a new {@link AwsEc2Authentication} specifying
113+
* {@link AwsEc2AuthenticationOptions}, a Vault and an AWS-Metadata-specific
114+
* {@link RestClient}.
115+
* @param options must not be {@literal null}.
116+
* @param vaultClient must not be {@literal null}.
117+
* @param awsMetadataClient must not be {@literal null}.
118+
* @since 4.0
119+
*/
120+
public AwsEc2Authentication(AwsEc2AuthenticationOptions options, RestClient vaultClient,
121+
RestClient awsMetadataClient) {
122+
123+
Assert.notNull(options, "AwsEc2AuthenticationOptions must not be null");
124+
Assert.notNull(vaultClient, "Vault RestClient must not be null");
125+
Assert.notNull(awsMetadataClient, "AWS Metadata RestClient must not be null");
126+
127+
this.options = options;
128+
this.vaultAdapter = ClientAdapter.from(vaultClient);
129+
this.awsMetadataAdapter = ClientAdapter.from(awsMetadataClient);
99130
}
100131

101132
/**
@@ -179,7 +210,7 @@ private VaultToken createTokenUsingAwsEc2() {
179210

180211
try {
181212

182-
VaultResponse response = this.vaultRestOperations
213+
VaultResponse response = this.vaultAdapter
183214
.postForObject(AuthenticationUtil.getLoginPath(this.options.getPath()), login, VaultResponse.class);
184215

185216
Assert.state(response != null && response.getAuth() != null, "Auth field must not be null");
@@ -225,8 +256,8 @@ protected Map<String, String> getEc2Login() {
225256
try {
226257

227258
HttpEntity<Object> entity = new HttpEntity<>(headers);
228-
ResponseEntity<String> exchange = this.awsMetadataRestOperations
229-
.exchange(this.options.getIdentityDocumentUri(), HttpMethod.GET, entity, String.class);
259+
ResponseEntity<String> exchange = this.awsMetadataAdapter.exchange(this.options.getIdentityDocumentUri(),
260+
HttpMethod.GET, entity, String.class);
230261
if (!exchange.getStatusCode().is2xxSuccessful()) {
231262
throw new HttpClientErrorException(exchange.getStatusCode());
232263
}
@@ -250,7 +281,7 @@ private String createIMDSv2Token() {
250281

251282
HttpEntity<Object> entity = new HttpEntity<>(createTokenRequestHeaders(this.options));
252283

253-
ResponseEntity<String> exchange = this.awsMetadataRestOperations
284+
ResponseEntity<String> exchange = this.awsMetadataAdapter
254285
.exchange(this.options.getMetadataTokenRequestUri(), HttpMethod.PUT, entity, String.class);
255286
if (!exchange.getStatusCode().is2xxSuccessful()) {
256287
throw new HttpClientErrorException(exchange.getStatusCode());

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

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@
2626
import org.apache.commons.logging.Log;
2727
import org.apache.commons.logging.LogFactory;
2828
import software.amazon.awssdk.auth.credentials.AwsCredentials;
29-
import software.amazon.awssdk.auth.signer.Aws4Signer;
30-
import software.amazon.awssdk.auth.signer.params.Aws4SignerParams;
3129
import software.amazon.awssdk.http.ContentStreamProvider;
3230
import software.amazon.awssdk.http.SdkHttpFullRequest;
3331
import software.amazon.awssdk.http.SdkHttpMethod;
@@ -44,6 +42,7 @@
4442
import org.springframework.vault.support.JacksonCompat;
4543
import org.springframework.vault.support.VaultResponse;
4644
import org.springframework.vault.support.VaultToken;
45+
import org.springframework.web.client.RestClient;
4746
import org.springframework.web.client.RestClientException;
4847
import org.springframework.web.client.RestOperations;
4948

@@ -84,12 +83,11 @@ public class AwsIamAuthentication implements ClientAuthentication, Authenticatio
8483

8584
private final AwsIamAuthenticationOptions options;
8685

87-
private final RestOperations vaultRestOperations;
86+
private final ClientAdapter adapter;
8887

8988
/**
9089
* Create a new {@link AwsIamAuthentication} specifying
91-
* {@link AwsIamAuthenticationOptions}, a Vault and an AWS-Metadata-specific
92-
* {@link RestOperations}.
90+
* {@link AwsIamAuthenticationOptions} and a Vault {@link RestOperations}.
9391
* @param options must not be {@literal null}.
9492
* @param vaultRestOperations must not be {@literal null}.
9593
*/
@@ -99,7 +97,22 @@ public AwsIamAuthentication(AwsIamAuthenticationOptions options, RestOperations
9997
Assert.notNull(vaultRestOperations, "Vault RestOperations must not be null");
10098

10199
this.options = options;
102-
this.vaultRestOperations = vaultRestOperations;
100+
this.adapter = ClientAdapter.from(vaultRestOperations);
101+
}
102+
103+
/**
104+
* Create a new {@link AwsIamAuthentication} specifying
105+
* {@link AwsIamAuthenticationOptions} and a Vault {@link RestClient}.
106+
* @param options must not be {@literal null}.
107+
* @param vaultClient must not be {@literal null}.
108+
*/
109+
public AwsIamAuthentication(AwsIamAuthenticationOptions options, RestClient vaultClient) {
110+
111+
Assert.notNull(options, "AwsIamAuthenticationOptions must not be null");
112+
Assert.notNull(vaultClient, "Vault RestOperations must not be null");
113+
114+
this.options = options;
115+
this.adapter = ClientAdapter.from(vaultClient);
103116
}
104117

105118
/**
@@ -146,8 +159,8 @@ private VaultToken createTokenUsingAwsIam() {
146159

147160
try {
148161

149-
VaultResponse response = this.vaultRestOperations
150-
.postForObject(AuthenticationUtil.getLoginPath(this.options.getPath()), login, VaultResponse.class);
162+
VaultResponse response = this.adapter.postForObject(AuthenticationUtil.getLoginPath(this.options.getPath()),
163+
login, VaultResponse.class);
151164

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

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

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.vault.authentication.AuthenticationSteps.Node;
3333
import org.springframework.vault.support.VaultResponse;
3434
import org.springframework.vault.support.VaultToken;
35+
import org.springframework.web.client.RestClient;
3536
import org.springframework.web.client.RestClientException;
3637
import org.springframework.web.client.RestOperations;
3738

@@ -67,9 +68,9 @@ public class AzureMsiAuthentication implements ClientAuthentication, Authenticat
6768

6869
private final AzureMsiAuthenticationOptions options;
6970

70-
private final RestOperations vaultRestOperations;
71+
private final ClientAdapter vaultAdapter;
7172

72-
private final RestOperations azureMetadataRestOperations;
73+
private final ClientAdapter azureMetadataAdapter;
7374

7475
/**
7576
* Create a new {@link AzureMsiAuthentication}.
@@ -96,8 +97,38 @@ public AzureMsiAuthentication(AzureMsiAuthenticationOptions options, RestOperati
9697
Assert.notNull(azureMetadataRestOperations, "Azure Instance Metadata RestOperations must not be null");
9798

9899
this.options = options;
99-
this.vaultRestOperations = vaultRestOperations;
100-
this.azureMetadataRestOperations = azureMetadataRestOperations;
100+
this.vaultAdapter = ClientAdapter.from(vaultRestOperations);
101+
this.azureMetadataAdapter = ClientAdapter.from(azureMetadataRestOperations);
102+
}
103+
104+
/**
105+
* Create a new {@link AzureMsiAuthentication}.
106+
* @param options must not be {@literal null}.
107+
* @param client must not be {@literal null}.
108+
* @since 4.0
109+
*/
110+
public AzureMsiAuthentication(AzureMsiAuthenticationOptions options, RestClient client) {
111+
this(options, client, client);
112+
}
113+
114+
/**
115+
* Create a new {@link AzureMsiAuthentication} specifying
116+
* {@link AzureMsiAuthenticationOptions}, a Vault and an Azure-Metadata-specific
117+
* {@link RestClient}.
118+
* @param options must not be {@literal null}.
119+
* @param vaultClient must not be {@literal null}.
120+
* @param azureMetadataClient must not be {@literal null}.
121+
*/
122+
public AzureMsiAuthentication(AzureMsiAuthenticationOptions options, RestClient vaultClient,
123+
RestClient azureMetadataClient) {
124+
125+
Assert.notNull(options, "AzureAuthenticationOptions must not be null");
126+
Assert.notNull(vaultClient, "Vault RestOperations must not be null");
127+
Assert.notNull(azureMetadataClient, "Azure Instance Metadata RestOperations must not be null");
128+
129+
this.options = options;
130+
this.vaultAdapter = ClientAdapter.from(vaultClient);
131+
this.azureMetadataAdapter = ClientAdapter.from(azureMetadataClient);
101132
}
102133

103134
/**
@@ -156,7 +187,7 @@ private VaultToken createTokenUsingAzureMsiCompute() {
156187

157188
try {
158189

159-
VaultResponse response = this.vaultRestOperations
190+
VaultResponse response = this.vaultAdapter
160191
.postForObject(AuthenticationUtil.getLoginPath(this.options.getPath()), login, VaultResponse.class);
161192

162193
Assert.state(response != null, "Auth field must not be null");
@@ -188,8 +219,8 @@ private static Map<String, String> getAzureLogin(String role, AzureVmEnvironment
188219
@SuppressWarnings({ "NullAway", "rawtypes" })
189220
private String getAccessToken() {
190221

191-
ResponseEntity<Map> response = this.azureMetadataRestOperations
192-
.exchange(this.options.getIdentityTokenServiceUri(), HttpMethod.GET, METADATA_HEADERS, Map.class);
222+
ResponseEntity<Map> response = this.azureMetadataAdapter.exchange(this.options.getIdentityTokenServiceUri(),
223+
HttpMethod.GET, METADATA_HEADERS, Map.class);
193224

194225
return (String) ResponseUtil.getRequiredBody(response).get("access_token");
195226
}
@@ -204,8 +235,8 @@ private AzureVmEnvironment getVmEnvironment() {
204235
@SuppressWarnings({ "unchecked", "rawtypes" })
205236
private AzureVmEnvironment fetchAzureVmEnvironment() {
206237

207-
ResponseEntity<Map> response = this.azureMetadataRestOperations
208-
.exchange(this.options.getInstanceMetadataServiceUri(), HttpMethod.GET, METADATA_HEADERS, Map.class);
238+
ResponseEntity<Map> response = this.azureMetadataAdapter.exchange(this.options.getInstanceMetadataServiceUri(),
239+
HttpMethod.GET, METADATA_HEADERS, Map.class);
209240

210241
return toAzureVmEnvironment(ResponseUtil.getRequiredBody(response));
211242
}

0 commit comments

Comments
 (0)