Polishing.
Refactor User/Pass authentication to be used with LDAP, Okta, and RADIUS. Remove LdapAuthentication to avoid duplications. See: gh-668.
This commit is contained in:
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.vault.authentication;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.VaultException;
|
||||
import org.springframework.vault.client.VaultResponses;
|
||||
import org.springframework.vault.support.VaultResponse;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.springframework.vault.authentication.AuthenticationUtil.getLoginPath;
|
||||
|
||||
/**
|
||||
* LDAP implementation of {@link ClientAuthentication}.
|
||||
*
|
||||
* @author Mikhael Sokolov
|
||||
* @see LdapAuthenticationOptions
|
||||
* @see RestOperations
|
||||
* @see <a href="https://www.vaultproject.io/docs/auth/ldap">LDAP</a>
|
||||
* @since 2.4
|
||||
*/
|
||||
public class LdapAuthentication implements ClientAuthentication, AuthenticationStepsFactory {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(LdapAuthentication.class);
|
||||
|
||||
private final LdapAuthenticationOptions options;
|
||||
|
||||
private final RestOperations restOperations;
|
||||
|
||||
public LdapAuthentication(LdapAuthenticationOptions options, RestOperations restOperations) {
|
||||
Assert.notNull(options, "LdapAuthenticationOptions must not be null");
|
||||
Assert.notNull(restOperations, "RestOperations must not be null");
|
||||
|
||||
this.options = options;
|
||||
this.restOperations = restOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultToken login() throws VaultException {
|
||||
return createTokenUsingLdapAuthentication();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationSteps getAuthenticationSteps() {
|
||||
return AuthenticationSteps
|
||||
.fromSupplier(() -> singletonMap("password", options.getPassword()))
|
||||
.login(String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername()));
|
||||
}
|
||||
|
||||
private VaultToken createTokenUsingLdapAuthentication() {
|
||||
try {
|
||||
VaultResponse response = restOperations.postForObject(String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername()), singletonMap("password", options.getPassword()), VaultResponse.class);
|
||||
|
||||
logger.debug("Login successful using LDAP credentials");
|
||||
|
||||
return LoginTokenUtil.from(response.getAuth());
|
||||
} catch (HttpStatusCodeException e) {
|
||||
throw new VaultException(String.format("Cannot login using LDAP: %s", VaultResponses.getError(e.getResponseBodyAsString())), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.vault.authentication;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @author Mikhael Sokolov
|
||||
*/
|
||||
public class LdapAuthenticationOptions {
|
||||
|
||||
public static final String DEFAULT_LDAP_AUTHENTICATION_PATH = "ldap";
|
||||
|
||||
/**
|
||||
* Path of the ldap authentication backend mount.
|
||||
*/
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* Username of the ldap authentication backend mount.
|
||||
*/
|
||||
private final String username;
|
||||
|
||||
/**
|
||||
* Password of the ldap authentication backend mount.
|
||||
*/
|
||||
private final CharSequence password;
|
||||
|
||||
private LdapAuthenticationOptions(String username, CharSequence password, String path) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public static LdapAuthenticationOptionsBuilder builder() {
|
||||
return new LdapAuthenticationOptionsBuilder();
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public CharSequence getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public static class LdapAuthenticationOptionsBuilder {
|
||||
|
||||
@Nullable
|
||||
private String username;
|
||||
|
||||
@Nullable
|
||||
private CharSequence password;
|
||||
|
||||
private String path = DEFAULT_LDAP_AUTHENTICATION_PATH;
|
||||
|
||||
LdapAuthenticationOptionsBuilder() {
|
||||
}
|
||||
|
||||
public LdapAuthenticationOptionsBuilder username(String username) {
|
||||
this.username = username;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LdapAuthenticationOptionsBuilder password(CharSequence password) {
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LdapAuthenticationOptionsBuilder path(String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LdapAuthenticationOptions build() {
|
||||
Assert.notNull(this.username, "Username must not be null");
|
||||
Assert.notNull(this.password, "Password must not be null");
|
||||
|
||||
return new LdapAuthenticationOptions(username, password, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,12 @@
|
||||
*/
|
||||
package org.springframework.vault.authentication;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.VaultException;
|
||||
import org.springframework.vault.client.VaultResponses;
|
||||
@@ -25,55 +29,94 @@ import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.springframework.vault.authentication.AuthenticationUtil.getLoginPath;
|
||||
import static org.springframework.vault.authentication.AuthenticationUtil.*;
|
||||
|
||||
/**
|
||||
* Username and password implementation of {@link ClientAuthentication}.
|
||||
* Username and password implementation of {@link ClientAuthentication}. Can be used for
|
||||
* {@code userpass}, {@code ldap}, {@code okta}, and {@code radius} authentication
|
||||
* backends.
|
||||
*
|
||||
* @author Mikhael Sokolov
|
||||
* @author Mark Paluch
|
||||
* @see UsernamePasswordAuthenticationOptions
|
||||
* @see RestOperations
|
||||
* @see <a href="https://www.vaultproject.io/docs/auth/userpass">Username & password</a>
|
||||
* @see <a href="https://www.vaultproject.io/docs/auth/userpass">Username and password</a>
|
||||
* @see <a href="https://www.vaultproject.io/docs/auth/ldap">LDAP authentication</a>
|
||||
* @see <a href="https://www.vaultproject.io/docs/auth/okta">Okta authentication</a>
|
||||
* @see <a href="https://www.vaultproject.io/docs/auth/radius">RADIUS authentication</a>
|
||||
* @since 2.4
|
||||
*/
|
||||
public class UsernamePasswordAuthentication implements ClientAuthentication, AuthenticationStepsFactory {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(UsernamePasswordAuthentication.class);
|
||||
private static final Log logger = LogFactory.getLog(UsernamePasswordAuthentication.class);
|
||||
|
||||
private final UsernamePasswordAuthenticationOptions options;
|
||||
private final UsernamePasswordAuthenticationOptions options;
|
||||
|
||||
private final RestOperations restOperations;
|
||||
private final RestOperations restOperations;
|
||||
|
||||
public UsernamePasswordAuthentication(UsernamePasswordAuthenticationOptions options, RestOperations restOperations) {
|
||||
Assert.notNull(options, "UsernamePasswordAuthenticationOptions must not be null");
|
||||
Assert.notNull(restOperations, "RestOperations must not be null");
|
||||
public UsernamePasswordAuthentication(UsernamePasswordAuthenticationOptions options,
|
||||
RestOperations restOperations) {
|
||||
|
||||
this.options = options;
|
||||
this.restOperations = restOperations;
|
||||
}
|
||||
Assert.notNull(options, "UsernamePasswordAuthenticationOptions must not be null");
|
||||
Assert.notNull(restOperations, "RestOperations must not be null");
|
||||
|
||||
@Override
|
||||
public VaultToken login() throws VaultException {
|
||||
return createTokenUsingUsernamePasswordAuthentication();
|
||||
}
|
||||
this.options = options;
|
||||
this.restOperations = restOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationSteps getAuthenticationSteps() {
|
||||
return AuthenticationSteps
|
||||
.fromSupplier(() -> singletonMap("password", options.getPassword()))
|
||||
.login(String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername()));
|
||||
}
|
||||
/**
|
||||
* Creates a {@link AuthenticationSteps} for username/password authentication given
|
||||
* {@link UsernamePasswordAuthenticationOptions}.
|
||||
* @param options must not be {@literal null}.
|
||||
* @return {@link AuthenticationSteps} for username/password authentication.
|
||||
*/
|
||||
public static AuthenticationSteps createAuthenticationSteps(UsernamePasswordAuthenticationOptions options) {
|
||||
|
||||
private VaultToken createTokenUsingUsernamePasswordAuthentication() {
|
||||
try {
|
||||
VaultResponse response = restOperations.postForObject(String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername()), singletonMap("password", options.getPassword()), VaultResponse.class);
|
||||
Assert.notNull(options, "UsernamePasswordAuthenticationOptions must not be null");
|
||||
|
||||
logger.debug("Login successful using username and password credentials");
|
||||
Map<String, Object> body = createLoginBody(options);
|
||||
|
||||
return LoginTokenUtil.from(response.getAuth());
|
||||
} catch (HttpStatusCodeException e) {
|
||||
throw new VaultException(String.format("Cannot login using username and password: %s", VaultResponses.getError(e.getResponseBodyAsString())), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return AuthenticationSteps.fromSupplier(() -> body)
|
||||
.login(String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultToken login() throws VaultException {
|
||||
return createTokenUsingUsernamePasswordAuthentication();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationSteps getAuthenticationSteps() {
|
||||
return createAuthenticationSteps(this.options);
|
||||
}
|
||||
|
||||
private VaultToken createTokenUsingUsernamePasswordAuthentication() {
|
||||
|
||||
try {
|
||||
VaultResponse response = restOperations.postForObject(
|
||||
String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername()),
|
||||
createLoginBody(options), VaultResponse.class);
|
||||
|
||||
logger.debug("Login successful using username and password credentials");
|
||||
|
||||
return LoginTokenUtil.from(response.getAuth());
|
||||
}
|
||||
catch (HttpStatusCodeException e) {
|
||||
throw new VaultException(String.format("Cannot login using username and password: %s",
|
||||
VaultResponses.getError(e.getResponseBodyAsString())), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Object> createLoginBody(UsernamePasswordAuthenticationOptions options) {
|
||||
|
||||
Map<String, Object> body = new LinkedHashMap<>();
|
||||
body.put("password", options.getPassword());
|
||||
|
||||
CharSequence totp = options.getTotp();
|
||||
if (totp != null) {
|
||||
body.put("totp", totp);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,87 +15,171 @@
|
||||
*/
|
||||
package org.springframework.vault.authentication;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Authentication options for {@link UsernamePasswordAuthentication}.
|
||||
*
|
||||
* @author Mikhael Sokolov
|
||||
* @author Mark Paluch
|
||||
* @since 2.4
|
||||
* @see UsernamePasswordAuthentication
|
||||
* @see #builder()
|
||||
*/
|
||||
public class UsernamePasswordAuthenticationOptions {
|
||||
|
||||
public static final String DEFAULT_USERPASS_AUTHENTICATION_PATH = "userpass";
|
||||
public static final String DEFAULT_USERPASS_AUTHENTICATION_PATH = "userpass";
|
||||
|
||||
/**
|
||||
* Path of the userpass authentication backend mount.
|
||||
*/
|
||||
private final String path;
|
||||
/**
|
||||
* Path of the userpass authentication backend mount.
|
||||
*/
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* Username of the userpass authentication backend mount.
|
||||
*/
|
||||
private final String username;
|
||||
/**
|
||||
* Username of the userpass authentication backend mount.
|
||||
*/
|
||||
private final String username;
|
||||
|
||||
/**
|
||||
* Password of the userpass authentication backend mount.
|
||||
*/
|
||||
private final CharSequence password;
|
||||
/**
|
||||
* Password of the userpass authentication backend mount.
|
||||
*/
|
||||
private final CharSequence password;
|
||||
|
||||
private UsernamePasswordAuthenticationOptions(String username, CharSequence password, String path) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.path = path;
|
||||
}
|
||||
/**
|
||||
* TOTP (one-time-token, optional).
|
||||
*/
|
||||
@Nullable
|
||||
private final CharSequence totp;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
private UsernamePasswordAuthenticationOptions(String path, String username, CharSequence password,
|
||||
@Nullable CharSequence totp) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.path = path;
|
||||
this.totp = totp;
|
||||
}
|
||||
|
||||
public CharSequence getPassword() {
|
||||
return password;
|
||||
}
|
||||
/**
|
||||
* @return a new {@link UsernamePasswordAuthenticationBuilder}.
|
||||
*/
|
||||
public static UsernamePasswordAuthenticationBuilder builder() {
|
||||
return new UsernamePasswordAuthenticationBuilder();
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
/**
|
||||
* @return the path of the userpass authentication backend mount.
|
||||
*/
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public static UsernamePasswordAuthenticationBuilder builder() {
|
||||
return new UsernamePasswordAuthenticationBuilder();
|
||||
}
|
||||
/**
|
||||
* @return the username.
|
||||
*/
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public static class UsernamePasswordAuthenticationBuilder {
|
||||
/**
|
||||
* @return the password.
|
||||
*/
|
||||
public CharSequence getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String username;
|
||||
/**
|
||||
* @return the totp (one-time-token). Can be {@code null}.
|
||||
*/
|
||||
@Nullable
|
||||
public CharSequence getTotp() {
|
||||
return this.totp;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private CharSequence password;
|
||||
/**
|
||||
* Builder for {@link UsernamePasswordAuthenticationOptions}.
|
||||
*/
|
||||
public static class UsernamePasswordAuthenticationBuilder {
|
||||
|
||||
private String path = DEFAULT_USERPASS_AUTHENTICATION_PATH;
|
||||
private String path = DEFAULT_USERPASS_AUTHENTICATION_PATH;
|
||||
|
||||
UsernamePasswordAuthenticationBuilder() {
|
||||
}
|
||||
@Nullable
|
||||
private String username;
|
||||
|
||||
public UsernamePasswordAuthenticationBuilder username(String username) {
|
||||
this.username = username;
|
||||
return this;
|
||||
}
|
||||
@Nullable
|
||||
private CharSequence password;
|
||||
|
||||
public UsernamePasswordAuthenticationBuilder password(CharSequence password) {
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
@Nullable
|
||||
private CharSequence totp;
|
||||
|
||||
public UsernamePasswordAuthenticationBuilder path(String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
UsernamePasswordAuthenticationBuilder() {
|
||||
}
|
||||
|
||||
public UsernamePasswordAuthenticationOptions build() {
|
||||
Assert.notNull(this.username, "Username must not be null");
|
||||
Assert.notNull(this.password, "Password must not be null");
|
||||
/**
|
||||
* Configure a {@code username} for userpass authentication.
|
||||
* @param username must not be empty or {@literal null}.
|
||||
* @return {@code this} {@link UsernamePasswordAuthenticationBuilder}.
|
||||
*/
|
||||
public UsernamePasswordAuthenticationBuilder username(String username) {
|
||||
|
||||
Assert.hasText(username, "Username must not be null and not be empty");
|
||||
|
||||
this.username = username;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a {@code password} for userpass authentication.
|
||||
* @param password must not be {@literal null}.
|
||||
* @return {@code this} {@link UsernamePasswordAuthenticationBuilder}.
|
||||
*/
|
||||
public UsernamePasswordAuthenticationBuilder password(CharSequence password) {
|
||||
|
||||
Assert.notNull(password, "Password must not be null");
|
||||
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure an optional {@code totp} (time-based one-time token) for
|
||||
* userpass/Okta authentication.
|
||||
* @param totp must not be {@literal null}.
|
||||
* @return {@code this} {@link UsernamePasswordAuthenticationBuilder}.
|
||||
*/
|
||||
public UsernamePasswordAuthenticationBuilder totp(CharSequence totp) {
|
||||
|
||||
Assert.notNull(password, "One-time token must not be null");
|
||||
|
||||
this.totp = totp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the mount path.
|
||||
* @param path must not be {@literal null} or empty.
|
||||
* @return {@code this} {@link UsernamePasswordAuthenticationBuilder}.
|
||||
*/
|
||||
public UsernamePasswordAuthenticationBuilder path(String path) {
|
||||
|
||||
Assert.hasText(path, "Path must not be empty");
|
||||
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new {@link UsernamePasswordAuthenticationOptions} instance.
|
||||
* @return a new {@link UsernamePasswordAuthenticationOptions}.
|
||||
*/
|
||||
public UsernamePasswordAuthenticationOptions build() {
|
||||
|
||||
Assert.hasText(this.username, "Username must not be null and not be empty");
|
||||
Assert.notNull(this.password, "Password must not be null");
|
||||
|
||||
return new UsernamePasswordAuthenticationOptions(path, username, password, totp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new UsernamePasswordAuthenticationOptions(username, password, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017-2021 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
|
||||
*
|
||||
* https://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.vault.authentication;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.springframework.vault.support.Policy;
|
||||
import org.springframework.vault.util.IntegrationTestSupport;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.springframework.vault.authentication.LdapAuthenticationOptions.DEFAULT_LDAP_AUTHENTICATION_PATH;
|
||||
import static org.springframework.vault.support.Policy.BuiltinCapabilities.*;
|
||||
|
||||
/**
|
||||
* Integration test base class for {@link LdapAuthentication} tests.
|
||||
*
|
||||
* @author Mikhael Sokolov
|
||||
*/
|
||||
public abstract class LdapAuthenticationIntegrationTestBase extends IntegrationTestSupport {
|
||||
|
||||
static final Policy POLICY = Policy.of(Policy.Rule.builder().path("/*").capabilities(READ, CREATE, UPDATE).build());
|
||||
|
||||
protected final String username = "admin";
|
||||
protected final String password = "qwerty";
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
|
||||
if (!prepare().hasAuth(DEFAULT_LDAP_AUTHENTICATION_PATH)) {
|
||||
prepare().mountAuth(DEFAULT_LDAP_AUTHENTICATION_PATH);
|
||||
}
|
||||
|
||||
prepare().getVaultOperations().opsForSys().createOrUpdatePolicy(DEFAULT_LDAP_AUTHENTICATION_PATH, POLICY);
|
||||
prepare().getVaultOperations().doWithSession(restOperations -> restOperations.postForEntity(String.format("auth/%s/users/%s", DEFAULT_LDAP_AUTHENTICATION_PATH, username), singletonMap("password", password), Map.class));
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2021 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
|
||||
*
|
||||
* https://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.vault.authentication;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.vault.client.VaultClients;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link LdapAuthentication}.
|
||||
*
|
||||
* @author Mikhael Sokolov
|
||||
*/
|
||||
class LdapAuthenticationIntegrationTests extends LdapAuthenticationIntegrationTestBase {
|
||||
|
||||
@Test
|
||||
void shouldLoginSuccessfully() {
|
||||
|
||||
RestTemplate restTemplate = VaultClients.createRestTemplate();
|
||||
LdapAuthenticationOptions options = LdapAuthenticationOptions.builder()
|
||||
.username(username)
|
||||
.password(password)
|
||||
.build();
|
||||
|
||||
LdapAuthentication authentication = new LdapAuthentication(options, restTemplate);
|
||||
VaultToken login = authentication.login();
|
||||
|
||||
assertThat(login.getToken()).isNotEmpty();
|
||||
}
|
||||
}
|
||||
@@ -15,14 +15,15 @@
|
||||
*/
|
||||
package org.springframework.vault.authentication;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
import org.springframework.vault.support.Policy;
|
||||
import org.springframework.vault.util.IntegrationTestSupport;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.springframework.vault.authentication.UsernamePasswordAuthenticationOptions.DEFAULT_USERPASS_AUTHENTICATION_PATH;
|
||||
import static java.util.Collections.*;
|
||||
import static org.springframework.vault.authentication.UsernamePasswordAuthenticationOptions.*;
|
||||
import static org.springframework.vault.support.Policy.BuiltinCapabilities.*;
|
||||
|
||||
/**
|
||||
@@ -32,19 +33,24 @@ import static org.springframework.vault.support.Policy.BuiltinCapabilities.*;
|
||||
*/
|
||||
public abstract class UsernamePasswordAuthenticationIntegrationTestBase extends IntegrationTestSupport {
|
||||
|
||||
static final Policy POLICY = Policy.of(Policy.Rule.builder().path("/*").capabilities(READ, CREATE, UPDATE).build());
|
||||
static final Policy POLICY = Policy.of(Policy.Rule.builder().path("/*").capabilities(READ, CREATE, UPDATE).build());
|
||||
|
||||
protected final String username = "admin";
|
||||
protected final String password = "qwerty";
|
||||
protected final String username = "admin";
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
protected final String password = "qwerty";
|
||||
|
||||
if (!prepare().hasAuth(DEFAULT_USERPASS_AUTHENTICATION_PATH)) {
|
||||
prepare().mountAuth(DEFAULT_USERPASS_AUTHENTICATION_PATH);
|
||||
}
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
|
||||
prepare().getVaultOperations().opsForSys().createOrUpdatePolicy(DEFAULT_USERPASS_AUTHENTICATION_PATH, POLICY);
|
||||
prepare().getVaultOperations().doWithSession(restOperations -> restOperations.postForEntity(String.format("auth/%s/users/%s", DEFAULT_USERPASS_AUTHENTICATION_PATH, username), singletonMap("password", password), Map.class));
|
||||
}
|
||||
}
|
||||
if (!prepare().hasAuth(DEFAULT_USERPASS_AUTHENTICATION_PATH)) {
|
||||
prepare().mountAuth(DEFAULT_USERPASS_AUTHENTICATION_PATH);
|
||||
}
|
||||
|
||||
prepare().getVaultOperations().opsForSys().createOrUpdatePolicy(DEFAULT_USERPASS_AUTHENTICATION_PATH, POLICY);
|
||||
prepare().getVaultOperations()
|
||||
.doWithSession(restOperations -> restOperations.postForEntity(
|
||||
String.format("auth/%s/users/%s", DEFAULT_USERPASS_AUTHENTICATION_PATH, username),
|
||||
singletonMap("password", password), Map.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
package org.springframework.vault.authentication;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.vault.client.VaultClients;
|
||||
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.vault.util.Settings;
|
||||
import org.springframework.vault.util.TestRestTemplateFactory;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link UsernamePasswordAuthentication}.
|
||||
@@ -29,18 +31,31 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*/
|
||||
class UsernamePasswordAuthenticationIntegrationTests extends UsernamePasswordAuthenticationIntegrationTestBase {
|
||||
|
||||
@Test
|
||||
void shouldLoginSuccessfully() {
|
||||
RestTemplate restTemplate = TestRestTemplateFactory.create(Settings.createSslConfiguration());
|
||||
|
||||
RestTemplate restTemplate = VaultClients.createRestTemplate();
|
||||
UsernamePasswordAuthenticationOptions options = UsernamePasswordAuthenticationOptions.builder()
|
||||
.username(username)
|
||||
.password(password)
|
||||
.build();
|
||||
@Test
|
||||
void shouldLoginSuccessfully() {
|
||||
|
||||
UsernamePasswordAuthentication authentication = new UsernamePasswordAuthentication(options, restTemplate);
|
||||
VaultToken login = authentication.login();
|
||||
UsernamePasswordAuthenticationOptions options = UsernamePasswordAuthenticationOptions.builder()
|
||||
.username(username).password(password).build();
|
||||
|
||||
assertThat(login.getToken()).isNotEmpty();
|
||||
}
|
||||
}
|
||||
UsernamePasswordAuthentication authentication = new UsernamePasswordAuthentication(options, restTemplate);
|
||||
VaultToken login = authentication.login();
|
||||
|
||||
assertThat(login.getToken()).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLoginUsingAuthenticationSteps() {
|
||||
|
||||
UsernamePasswordAuthenticationOptions options = UsernamePasswordAuthenticationOptions.builder()
|
||||
.username(username).password(password).build();
|
||||
|
||||
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
|
||||
UsernamePasswordAuthentication.createAuthenticationSteps(options), restTemplate);
|
||||
VaultToken login = executor.login();
|
||||
|
||||
assertThat(login.getToken()).isNotEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.vault.authentication;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.client.MockRestServiceServer;
|
||||
import org.springframework.vault.client.VaultClients;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.test.web.client.match.MockRestRequestMatchers.*;
|
||||
import static org.springframework.test.web.client.response.MockRestResponseCreators.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link UsernamePasswordAuthentication}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class UsernamePasswordAuthenticationUnitTests {
|
||||
|
||||
RestTemplate restTemplate;
|
||||
|
||||
MockRestServiceServer mockRest;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
|
||||
RestTemplate restTemplate = VaultClients.createRestTemplate();
|
||||
restTemplate.setUriTemplateHandler(new VaultClients.PrefixAwareUriTemplateHandler());
|
||||
|
||||
this.mockRest = MockRestServiceServer.createServer(restTemplate);
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLoginWithTotp() {
|
||||
|
||||
UsernamePasswordAuthenticationOptions options = UsernamePasswordAuthenticationOptions.builder().path("okta")
|
||||
.username("walter").password("heisenberg").totp("123456").build();
|
||||
|
||||
UsernamePasswordAuthentication sut = new UsernamePasswordAuthentication(options, this.restTemplate);
|
||||
|
||||
this.mockRest.expect(requestTo("/auth/okta/login/walter")).andExpect(method(HttpMethod.POST))
|
||||
.andExpect(jsonPath("$.password").value("heisenberg")).andExpect(jsonPath("$.totp").value("123456"))
|
||||
.andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON).body(
|
||||
"{" + "\"auth\":{\"client_token\":\"my-token\", \"renewable\": true, \"lease_duration\": 10}"
|
||||
+ "}"));
|
||||
|
||||
VaultToken login = sut.login();
|
||||
|
||||
assertThat(login).isInstanceOf(LoginToken.class);
|
||||
assertThat(login.getToken()).isEqualTo("my-token");
|
||||
assertThat(((LoginToken) login).getLeaseDuration()).isEqualTo(Duration.ofSeconds(10));
|
||||
assertThat(((LoginToken) login).isRenewable()).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
[[new-features]]
|
||||
== New & Noteworthy
|
||||
|
||||
[[new-features.2-4-0]]
|
||||
=== What's new in Spring Vault 2.4
|
||||
|
||||
* Support for <<vault.authentication.userpass,Username/Password authentication>> for Username/Password, LDAP, Okta, and RADIUS authentication.
|
||||
|
||||
[[new-features.2-3-0]]
|
||||
=== What's new in Spring Vault 2.3
|
||||
|
||||
|
||||
@@ -784,6 +784,50 @@ See also:
|
||||
* https://www.vaultproject.io/docs/auth/kubernetes.html[Vault Documentation: Using the Kubernetes auth backend]
|
||||
* https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/[Kubernetes Documentation: Configure Service Accounts for Pods]
|
||||
|
||||
[[vault.authentication.userpass]]
|
||||
== Username/Password authentication
|
||||
|
||||
Username/Password is typically a end-user authentication scheme.
|
||||
Using username and password is supported by multiple Vault authentication backends:
|
||||
|
||||
* Username and Password (`userpass`)
|
||||
* LDAP (`ldap`)
|
||||
* Okta (`okta`, supports additionaly time-based one-time tokens)
|
||||
* RADIUS (`radius`)
|
||||
|
||||
`UserPasswordAuthenticationOptions` can be used with all above mentioned authentication backends as the Login API is similar across all mechanisms.
|
||||
Please ensure to use the appropriate auth mount path when configuring `UserPasswordAuthenticationOptions`.
|
||||
|
||||
.Configuring `UserPasswordAuthentication`
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig extends AbstractVaultConfiguration {
|
||||
|
||||
// …
|
||||
|
||||
@Override
|
||||
public ClientAuthentication clientAuthentication() {
|
||||
|
||||
UserPasswordAuthenticationOptions options = UserPasswordAuthenticationOptions.builder()
|
||||
.username(…).password(…).build();
|
||||
|
||||
return new UserPasswordAuthentication(options, restOperations());
|
||||
}
|
||||
|
||||
// …
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
See also:
|
||||
|
||||
* https://www.vaultproject.io/api-docs/auth/userpass[Vault Documentation: Using the Userpass auth backend]
|
||||
* https://www.vaultproject.io/api-docs/auth/ldap[Vault Documentation: Using the LDAP auth backend]
|
||||
* https://www.vaultproject.io/api-docs/auth/radius[Vault Documentation: Using the RADIUS auth backend]
|
||||
* https://www.vaultproject.io/api-docs/auth/okta[Vault Documentation: Using the Okta auth backend]
|
||||
|
||||
[[vault.authentication.steps]]
|
||||
== Authentication Steps
|
||||
|
||||
|
||||
Reference in New Issue
Block a user