Adapt to AppRole authentication modes in Spring Vault.
We now support a selected subset of push/pull/wrapped RoleId and SecretId combinations using AppRoleAuthentication: * Provided RoleId/SecretId * Provided RoleId without SecretId * Provided RoleId, Pull SecretId * Pull RoleId, provided SecretId * Full pull mode * Wrapped * Wrapped RoleId, provided SecretId * Provided RoleId, wrapped SecretId The particular mode is derived from role-id, secret-id, role (name) and token properties beneath spring.cloud.vault.*. Closes gh-174.
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
:toc: left
|
||||
:nofooter:
|
||||
|
||||
(C) 2016-2017 The original authors.
|
||||
(C) 2016-2018 The original authors.
|
||||
|
||||
NOTE: _Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically._
|
||||
|
||||
@@ -168,8 +168,7 @@ https://www.vaultproject.io/docs/auth/app-id.html[AppRole] is intended for machi
|
||||
authentication, like the deprecated (since Vault 0.6.1) <<vault.config.authentication.appid>>.
|
||||
AppRole authentication consists of two hard to guess (secret) tokens: RoleId and SecretId.
|
||||
|
||||
Spring Vault supports AppRole authentication by providing either RoleId only
|
||||
or together with a provided SecretId (push or pull mode).
|
||||
Spring Vault supports various AppRole scenarios (push/pull mode and wrapped).
|
||||
|
||||
RoleId and optionally SecretId must be provided by configuration,
|
||||
Spring Vault will not look up these or create a custom SecretId.
|
||||
@@ -185,7 +184,39 @@ spring.cloud.vault:
|
||||
----
|
||||
====
|
||||
|
||||
* `role-id` sets the RoleId.
|
||||
The following scenarios are supported along the required configuration details:
|
||||
|
||||
.Configuration
|
||||
|===
|
||||
| *Method* | *RoleId* | *SecretId*| *RoleName* | *Token*
|
||||
| Provided RoleId/SecretId | Provided | Provided | |
|
||||
| Provided RoleId without SecretId | Provided | | |
|
||||
| Provided RoleId, Pull SecretId | Provided | Provided | Provided | Provided
|
||||
| Pull RoleId, provided SecretId | | Provided | Provided | Provided
|
||||
| Full Pull Mode | | | Provided | Provided
|
||||
| Wrapped | | | | Provided
|
||||
| Wrapped RoleId, provided SecretId | Provided | | | Provided
|
||||
| Provided RoleId, wrapped SecretId | | Provided | | Provided
|
||||
|===
|
||||
|
||||
.Pull/Push/Wrapped Matrix
|
||||
|===
|
||||
| *RoleId* | *SecretId* | *Supported*
|
||||
| Provided | Provided | ✅
|
||||
| Provided | Pull | ✅
|
||||
| Provided | Wrapped | ✅
|
||||
| Provided | Absent | ✅
|
||||
| Pull | Provided | ✅
|
||||
| Pull | Pull | ✅
|
||||
| Pull | Wrapped | ❌
|
||||
| Pull | Absent | ❌
|
||||
| Wrapped | Provided | ✅
|
||||
| Wrapped | Pull | ❌
|
||||
| Wrapped | Wrapped | ✅
|
||||
| Wrapped | Absent | ❌
|
||||
|===
|
||||
|
||||
NOTE: You can use still all combinations of push/pull/wrapped modes by providing a configured `AppRoleAuthentication` bean within the boostrap context. Spring Cloud Vault cannot derive all possible AppRole combinations from the configuration properties.
|
||||
|
||||
.bootstrap.yml with all AppRole authentication properties
|
||||
====
|
||||
@@ -196,13 +227,15 @@ spring.cloud.vault:
|
||||
app-role:
|
||||
role-id: bde2076b-cccb-3cf0-d57e-bca7b1e83a52
|
||||
secret-id: 1696536f-1976-73b1-b241-0b4213908d39
|
||||
app-auth-path: approle
|
||||
role: my-role
|
||||
app-role-path: approle
|
||||
----
|
||||
====
|
||||
|
||||
* `role-id` sets the RoleId.
|
||||
* `secret-id` sets the SecretId. SecretId can be omitted if AppRole is configured without requiring SecretId (See `bind_secret_id`)
|
||||
* `approle-path` sets the path of the approle authentication mount to use
|
||||
* `secret-id` sets the SecretId. SecretId can be omitted if AppRole is configured without requiring SecretId (See `bind_secret_id`).
|
||||
* `role`: sets the AppRole name for pull mode.
|
||||
* `app-role-path` sets the path of the approle authentication mount to use.
|
||||
|
||||
See also: https://www.vaultproject.io/docs/auth/approle.html[Vault Documentation: Using the AppRole auth backend]
|
||||
|
||||
|
||||
@@ -24,30 +24,15 @@ import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.cloud.vault.config.VaultProperties.AppRoleProperties;
|
||||
import org.springframework.cloud.vault.config.VaultProperties.AwsIamProperties;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.vault.authentication.AppIdAuthentication;
|
||||
import org.springframework.vault.authentication.AppIdAuthenticationOptions;
|
||||
import org.springframework.vault.authentication.AppIdUserIdMechanism;
|
||||
import org.springframework.vault.authentication.AppRoleAuthentication;
|
||||
import org.springframework.vault.authentication.AppRoleAuthenticationOptions;
|
||||
import org.springframework.vault.authentication.AwsEc2Authentication;
|
||||
import org.springframework.vault.authentication.AwsEc2AuthenticationOptions;
|
||||
import org.springframework.vault.authentication.AwsIamAuthentication;
|
||||
import org.springframework.vault.authentication.AwsIamAuthenticationOptions;
|
||||
import org.springframework.vault.authentication.ClientAuthentication;
|
||||
import org.springframework.vault.authentication.ClientCertificateAuthentication;
|
||||
import org.springframework.vault.authentication.CubbyholeAuthentication;
|
||||
import org.springframework.vault.authentication.CubbyholeAuthenticationOptions;
|
||||
import org.springframework.vault.authentication.IpAddressUserId;
|
||||
import org.springframework.vault.authentication.KubernetesAuthentication;
|
||||
import org.springframework.vault.authentication.KubernetesAuthenticationOptions;
|
||||
import org.springframework.vault.authentication.KubernetesServiceAccountTokenFile;
|
||||
import org.springframework.vault.authentication.MacAddressUserId;
|
||||
import org.springframework.vault.authentication.StaticUserId;
|
||||
import org.springframework.vault.authentication.TokenAuthentication;
|
||||
import org.springframework.vault.authentication.*;
|
||||
import org.springframework.vault.authentication.AppRoleAuthenticationOptions.AppRoleAuthenticationOptionsBuilder;
|
||||
import org.springframework.vault.authentication.AppRoleAuthenticationOptions.RoleId;
|
||||
import org.springframework.vault.authentication.AppRoleAuthenticationOptions.SecretId;
|
||||
import org.springframework.vault.authentication.AwsEc2AuthenticationOptions.Nonce;
|
||||
import org.springframework.vault.authentication.AwsIamAuthenticationOptions.AwsIamAuthenticationOptionsBuilder;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
@@ -156,18 +141,68 @@ class ClientAuthenticationFactory {
|
||||
|
||||
private ClientAuthentication appRoleAuthentication(VaultProperties vaultProperties) {
|
||||
|
||||
VaultProperties.AppRoleProperties appRole = vaultProperties.getAppRole();
|
||||
Assert.hasText(appRole.getRoleId(),
|
||||
"RoleId (spring.cloud.vault.app-role.role-id) must not be empty");
|
||||
AppRoleAuthenticationOptions options = getAppRoleAuthenticationOptions(vaultProperties);
|
||||
|
||||
AppRoleAuthenticationOptions.AppRoleAuthenticationOptionsBuilder builder = AppRoleAuthenticationOptions
|
||||
.builder().path(appRole.getAppRolePath()).roleId(appRole.getRoleId());
|
||||
return new AppRoleAuthentication(options, restOperations);
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(appRole.getSecretId())) {
|
||||
builder = builder.secretId(appRole.getSecretId());
|
||||
static AppRoleAuthenticationOptions getAppRoleAuthenticationOptions(
|
||||
VaultProperties vaultProperties) {
|
||||
|
||||
AppRoleProperties appRole = vaultProperties.getAppRole();
|
||||
|
||||
AppRoleAuthenticationOptionsBuilder builder = AppRoleAuthenticationOptions
|
||||
.builder().path(appRole.getAppRolePath());
|
||||
|
||||
if (StringUtils.hasText(appRole.getRole())) {
|
||||
builder.appRole(appRole.getRole());
|
||||
}
|
||||
|
||||
return new AppRoleAuthentication(builder.build(), restOperations);
|
||||
RoleId roleId = getRoleId(vaultProperties, appRole);
|
||||
SecretId secretId = getSecretId(vaultProperties, appRole);
|
||||
|
||||
builder.roleId(roleId).secretId(secretId);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static RoleId getRoleId(VaultProperties vaultProperties,
|
||||
AppRoleProperties appRole) {
|
||||
|
||||
if (StringUtils.hasText(appRole.getRoleId())) {
|
||||
return RoleId.provided(appRole.getRoleId());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(vaultProperties.getToken())
|
||||
&& StringUtils.hasText(appRole.getRole())) {
|
||||
return RoleId.pull(VaultToken.of(vaultProperties.getToken()));
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(vaultProperties.getToken())) {
|
||||
return RoleId.wrapped(VaultToken.of(vaultProperties.getToken()));
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot configure RoleId. Any of role-id, initial token, or initial toke and role name must be configured.");
|
||||
}
|
||||
|
||||
private static SecretId getSecretId(VaultProperties vaultProperties,
|
||||
AppRoleProperties appRole) {
|
||||
|
||||
if (StringUtils.hasText(appRole.getSecretId())) {
|
||||
return SecretId.provided(appRole.getSecretId());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(vaultProperties.getToken())
|
||||
&& StringUtils.hasText(appRole.getRole())) {
|
||||
return SecretId.pull(VaultToken.of(vaultProperties.getToken()));
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(vaultProperties.getToken())) {
|
||||
return SecretId.wrapped(VaultToken.of(vaultProperties.getToken()));
|
||||
}
|
||||
|
||||
return SecretId.absent();
|
||||
}
|
||||
|
||||
private ClientAuthentication awsEc2Authentication(VaultProperties vaultProperties) {
|
||||
|
||||
@@ -179,10 +179,15 @@ public class VaultProperties implements EnvironmentAware {
|
||||
public static class AppRoleProperties {
|
||||
|
||||
/**
|
||||
* Mount path of the AppId authentication backend.
|
||||
* Mount path of the AppRole authentication backend.
|
||||
*/
|
||||
private String appRolePath = "approle";
|
||||
|
||||
/**
|
||||
* Name of the role, optional, used for pull-mode.
|
||||
*/
|
||||
private String role = "";
|
||||
|
||||
/**
|
||||
* The RoleId.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.vault.config;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.vault.authentication.AppRoleAuthenticationOptions;
|
||||
import org.springframework.vault.authentication.AppRoleAuthenticationOptions.RoleId;
|
||||
import org.springframework.vault.authentication.AppRoleAuthenticationOptions.SecretId;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link }ClientAuthenticationFactory}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ClientAuthenticationFactoryUnitTests {
|
||||
|
||||
@Test
|
||||
public void shouldSupportAppRoleRoleIdProvidedSecretIdProvided() {
|
||||
|
||||
VaultProperties properties = new VaultProperties();
|
||||
properties.getAppRole().setRoleId("foo");
|
||||
properties.getAppRole().setSecretId("bar");
|
||||
|
||||
AppRoleAuthenticationOptions options = ClientAuthenticationFactory
|
||||
.getAppRoleAuthenticationOptions(properties);
|
||||
|
||||
assertThat(options.getRoleId()).isInstanceOf(RoleId.provided("foo").getClass());
|
||||
assertThat(options.getSecretId()).isInstanceOf(
|
||||
SecretId.provided("bar").getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSupportAppRoleRoleIdProvidedSecretIdAbsent() {
|
||||
|
||||
VaultProperties properties = new VaultProperties();
|
||||
properties.getAppRole().setRoleId("foo");
|
||||
|
||||
AppRoleAuthenticationOptions options = ClientAuthenticationFactory
|
||||
.getAppRoleAuthenticationOptions(properties);
|
||||
|
||||
assertThat(options.getRoleId()).isInstanceOf(RoleId.provided("foo").getClass());
|
||||
assertThat(options.getSecretId()).isInstanceOf(SecretId.absent().getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSupportAppRoleRoleIdProvidedSecretIdPull() {
|
||||
|
||||
VaultProperties properties = new VaultProperties();
|
||||
properties.setToken("token");
|
||||
properties.getAppRole().setRoleId("foo");
|
||||
properties.getAppRole().setRole("my-role");
|
||||
|
||||
AppRoleAuthenticationOptions options = ClientAuthenticationFactory
|
||||
.getAppRoleAuthenticationOptions(properties);
|
||||
|
||||
assertThat(options.getAppRole()).isEqualTo("my-role");
|
||||
assertThat(options.getRoleId()).isInstanceOf(RoleId.provided("foo").getClass());
|
||||
assertThat(options.getSecretId()).isInstanceOf(
|
||||
SecretId.pull(VaultToken.of("token")).getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSupportAppRoleFullPull() {
|
||||
|
||||
VaultProperties properties = new VaultProperties();
|
||||
properties.setToken("token");
|
||||
properties.getAppRole().setRole("my-role");
|
||||
|
||||
AppRoleAuthenticationOptions options = ClientAuthenticationFactory
|
||||
.getAppRoleAuthenticationOptions(properties);
|
||||
|
||||
assertThat(options.getAppRole()).isEqualTo("my-role");
|
||||
assertThat(options.getRoleId()).isInstanceOf(
|
||||
RoleId.pull(VaultToken.of("token")).getClass());
|
||||
assertThat(options.getSecretId()).isInstanceOf(
|
||||
SecretId.pull(VaultToken.of("token")).getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSupportAppRoleFullWrapped() {
|
||||
|
||||
VaultProperties properties = new VaultProperties();
|
||||
properties.setToken("token");
|
||||
|
||||
AppRoleAuthenticationOptions options = ClientAuthenticationFactory
|
||||
.getAppRoleAuthenticationOptions(properties);
|
||||
|
||||
assertThat(options.getRoleId()).isInstanceOf(
|
||||
RoleId.wrapped(VaultToken.of("token")).getClass());
|
||||
assertThat(options.getSecretId()).isInstanceOf(
|
||||
SecretId.wrapped(VaultToken.of("token")).getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSupportAppRoleRoleIdWrappedSecretIdProvided() {
|
||||
|
||||
VaultProperties properties = new VaultProperties();
|
||||
properties.setToken("token");
|
||||
properties.getAppRole().setSecretId("bar");
|
||||
|
||||
AppRoleAuthenticationOptions options = ClientAuthenticationFactory
|
||||
.getAppRoleAuthenticationOptions(properties);
|
||||
|
||||
assertThat(options.getRoleId()).isInstanceOf(
|
||||
RoleId.wrapped(VaultToken.of("token")).getClass());
|
||||
assertThat(options.getSecretId()).isInstanceOf(
|
||||
SecretId.provided("bar").getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSupportAppRoleRoleIdProvidedSecretIdWrapped() {
|
||||
|
||||
VaultProperties properties = new VaultProperties();
|
||||
properties.setToken("token");
|
||||
properties.getAppRole().setRoleId("foo");
|
||||
|
||||
AppRoleAuthenticationOptions options = ClientAuthenticationFactory
|
||||
.getAppRoleAuthenticationOptions(properties);
|
||||
|
||||
assertThat(options.getRoleId()).isInstanceOf(RoleId.provided("foo").getClass());
|
||||
assertThat(options.getSecretId()).isInstanceOf(
|
||||
SecretId.wrapped(VaultToken.of("token")).getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRejectUnconfiguredRoleId() {
|
||||
|
||||
VaultProperties properties = new VaultProperties();
|
||||
|
||||
assertThatThrownBy(
|
||||
() -> ClientAuthenticationFactory
|
||||
.getAppRoleAuthenticationOptions(properties)).isInstanceOf(
|
||||
IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRejectUnconfiguredRoleIdIfRoleNameSet() {
|
||||
|
||||
VaultProperties properties = new VaultProperties();
|
||||
properties.getAppRole().setRole("my-role");
|
||||
|
||||
assertThatThrownBy(
|
||||
() -> ClientAuthenticationFactory
|
||||
.getAppRoleAuthenticationOptions(properties)).isInstanceOf(
|
||||
IllegalArgumentException.class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user