[Backport 7.x] Add example settings to sample security realm (#63301)
This change adds configurable settings to the `CustomRealm` in the QA project as the correct declaration and use of settings can be a source of confusion in custom realms. The "username" "password" and "roles" are now all configurable, which demonstrates the use of a simple string setting ("username") a secure setting ("password") and a more complex list setting ("roles"). Backport of: #62287
This commit is contained in:
parent
8a61b95a0f
commit
eeb45b4a74
|
@ -29,8 +29,11 @@ testClusters.all {
|
|||
// processors are being used that are in ingest-common module.
|
||||
testDistribution = 'DEFAULT'
|
||||
|
||||
setting 'xpack.security.authc.realms.custom.custom.order', '0'
|
||||
setting 'xpack.security.authc.realms.custom.custom.filtered_setting', 'should be filtered'
|
||||
setting 'xpack.security.authc.realms.custom.my_realm.order', '0'
|
||||
setting 'xpack.security.authc.realms.custom.my_realm.filtered_setting', 'should be filtered'
|
||||
setting 'xpack.security.authc.realms.custom.my_realm.username', 'test_user'
|
||||
keystore 'xpack.security.authc.realms.custom.my_realm.password', 'secret_password'
|
||||
|
||||
setting 'xpack.security.authc.realms.file.esusers.order', '1'
|
||||
setting 'xpack.security.authc.realms.native.native.order', '2'
|
||||
setting 'xpack.security.authc.realms.custom_role_mapping.role_map.order', '3'
|
||||
|
|
|
@ -37,13 +37,17 @@ import static org.hamcrest.Matchers.is;
|
|||
*/
|
||||
public class CustomRealmIT extends ESIntegTestCase {
|
||||
|
||||
// These are configured in build.gradle
|
||||
public static final String USERNAME= "test_user";
|
||||
public static final String PASSWORD = "secret_password";
|
||||
|
||||
@Override
|
||||
protected Settings externalClusterClientSettings() {
|
||||
return Settings.builder()
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.USER_HEADER, CustomRealm.KNOWN_USER)
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.PW_HEADER, CustomRealm.KNOWN_PW.toString())
|
||||
.put(NetworkModule.TRANSPORT_TYPE_KEY, "security4")
|
||||
.build();
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.USER_HEADER, USERNAME)
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.PW_HEADER, PASSWORD)
|
||||
.put(NetworkModule.TRANSPORT_TYPE_KEY, "security4")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -66,8 +70,8 @@ public class CustomRealmIT extends ESIntegTestCase {
|
|||
public void testHttpAuthentication() throws Exception {
|
||||
Request request = new Request("GET", "/");
|
||||
RequestOptions.Builder options = request.getOptions().toBuilder();
|
||||
options.addHeader(CustomRealm.USER_HEADER, CustomRealm.KNOWN_USER);
|
||||
options.addHeader(CustomRealm.PW_HEADER, CustomRealm.KNOWN_PW.toString());
|
||||
options.addHeader(CustomRealm.USER_HEADER, USERNAME);
|
||||
options.addHeader(CustomRealm.PW_HEADER, PASSWORD);
|
||||
request.setOptions(options);
|
||||
Response response = getRestClient().performRequest(request);
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(200));
|
||||
|
@ -83,8 +87,8 @@ public class CustomRealmIT extends ESIntegTestCase {
|
|||
Settings settings = Settings.builder()
|
||||
.put("cluster.name", clusterName)
|
||||
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toAbsolutePath().toString())
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.USER_HEADER, CustomRealm.KNOWN_USER)
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.PW_HEADER, CustomRealm.KNOWN_PW.toString())
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.USER_HEADER, USERNAME)
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.PW_HEADER, PASSWORD)
|
||||
.build();
|
||||
try (TransportClient client = new PreBuiltXPackTransportClient(settings)) {
|
||||
client.addTransportAddress(publishAddress);
|
||||
|
@ -103,8 +107,8 @@ public class CustomRealmIT extends ESIntegTestCase {
|
|||
Settings settings = Settings.builder()
|
||||
.put("cluster.name", clusterName)
|
||||
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toAbsolutePath().toString())
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.USER_HEADER, CustomRealm.KNOWN_USER + randomAlphaOfLength(1))
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.PW_HEADER, CustomRealm.KNOWN_PW.toString())
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.USER_HEADER, USERNAME + randomAlphaOfLength(1))
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.PW_HEADER, PASSWORD)
|
||||
.build();
|
||||
try (TransportClient client = new PreBuiltXPackTransportClient(settings)) {
|
||||
client.addTransportAddress(publishAddress);
|
||||
|
@ -116,12 +120,13 @@ public class CustomRealmIT extends ESIntegTestCase {
|
|||
}
|
||||
|
||||
public void testSettingsFiltering() throws Exception {
|
||||
NodesInfoResponse nodeInfos = client().admin().cluster().prepareNodesInfo().clear().addMetric(Metric.SETTINGS.metricName()).get();
|
||||
NodesInfoResponse nodeInfos = client().admin()
|
||||
.cluster().prepareNodesInfo().clear().addMetric(Metric.SETTINGS.metricName()).get();
|
||||
for(NodeInfo info : nodeInfos.getNodes()) {
|
||||
Settings settings = info.getSettings();
|
||||
assertNotNull(settings);
|
||||
assertNull(settings.get("xpack.security.authc.realms.custom.custom.filtered_setting"));
|
||||
assertEquals("0", settings.get("xpack.security.authc.realms.custom.custom.order"));
|
||||
assertNull(settings.get("xpack.security.authc.realms.custom.my_realm.filtered_setting"));
|
||||
assertEquals("0", settings.get("xpack.security.authc.realms.custom.my_realm.order"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,8 +31,8 @@ public class CustomRoleMappingRealmIT extends ESRestTestCase {
|
|||
@Override
|
||||
protected Settings restClientSettings() {
|
||||
return Settings.builder()
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.USER_HEADER, CustomRealm.KNOWN_USER)
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.PW_HEADER, CustomRealm.KNOWN_PW.toString())
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.USER_HEADER, CustomRealmIT.USERNAME)
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.PW_HEADER, CustomRealmIT.PASSWORD)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -54,8 +54,8 @@ public class CustomRoleMappingRealmIT extends ESRestTestCase {
|
|||
Request request = new Request("GET", "/_security/_authenticate");
|
||||
RequestOptions.Builder options = request.getOptions().toBuilder();
|
||||
// Authenticate as the custom realm superuser
|
||||
options.addHeader(CustomRealm.USER_HEADER, CustomRealm.KNOWN_USER);
|
||||
options.addHeader(CustomRealm.PW_HEADER, CustomRealm.KNOWN_PW.toString());
|
||||
options.addHeader(CustomRealm.USER_HEADER, CustomRealmIT.USERNAME);
|
||||
options.addHeader(CustomRealm.PW_HEADER, CustomRealmIT.PASSWORD);
|
||||
// But "run-as" the role mapped user
|
||||
options.addHeader("es-security-runas-user", CustomRoleMappingRealm.USERNAME);
|
||||
request.setOptions(options);
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.elasticsearch.plugins.Plugin;
|
|||
import org.elasticsearch.test.ESIntegTestCase;
|
||||
import org.elasticsearch.xpack.core.XPackClientPlugin;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
|
||||
import org.elasticsearch.example.realm.CustomRealmIT;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
|
||||
import org.elasticsearch.xpack.core.security.client.SecurityClient;
|
||||
|
||||
|
@ -47,10 +48,10 @@ public class CustomRolesProviderIT extends ESIntegTestCase {
|
|||
@Override
|
||||
protected Settings externalClusterClientSettings() {
|
||||
return Settings.builder()
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.USER_HEADER, CustomRealm.KNOWN_USER)
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.PW_HEADER, CustomRealm.KNOWN_PW.toString())
|
||||
.put(NetworkModule.TRANSPORT_TYPE_KEY, "security4")
|
||||
.build();
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.USER_HEADER, CustomRealmIT.USERNAME)
|
||||
.put(ThreadContext.PREFIX + "." + CustomRealm.PW_HEADER, CustomRealmIT.PASSWORD)
|
||||
.put(NetworkModule.TRANSPORT_TYPE_KEY, "security4")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -35,6 +35,9 @@ public class SpiExtensionPlugin extends Plugin implements ActionPlugin {
|
|||
List<Setting<?>> list = new ArrayList<>(RealmSettings.getStandardSettings(CustomRealm.TYPE));
|
||||
list.add(RealmSettings.simpleString(CustomRealm.TYPE, "filtered_setting", Setting.Property.NodeScope, Setting.Property.Filtered));
|
||||
list.addAll(RealmSettings.getStandardSettings(CustomRoleMappingRealm.TYPE));
|
||||
list.add(CustomRealm.USERNAME_SETTING);
|
||||
list.add(CustomRealm.PASSWORD_SETTING);
|
||||
list.add(CustomRealm.ROLES_SETTING);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,16 +6,23 @@
|
|||
package org.elasticsearch.example.realm;
|
||||
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.common.CharArrays;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.example.SpiExtensionPlugin;
|
||||
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
|
||||
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.Realm;
|
||||
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
|
||||
import org.elasticsearch.common.CharArrays;
|
||||
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
|
||||
import org.elasticsearch.xpack.core.security.user.User;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class CustomRealm extends Realm {
|
||||
|
||||
public static final String TYPE = "custom";
|
||||
|
@ -23,12 +30,37 @@ public class CustomRealm extends Realm {
|
|||
public static final String USER_HEADER = "User";
|
||||
public static final String PW_HEADER = "Password";
|
||||
|
||||
public static final String KNOWN_USER = "custom_user";
|
||||
public static final SecureString KNOWN_PW = new SecureString("x-pack-test-password".toCharArray());
|
||||
static final String[] ROLES = new String[] { "superuser" };
|
||||
public static final String DEFAULT_KNOWN_USER = "custom_user";
|
||||
public static final SecureString DEFAULT_KNOWN_PW = new SecureString("x-pack-test-password".toCharArray());
|
||||
static final List<String> DEFAULT_ROLES = Collections.singletonList("superuser");
|
||||
|
||||
// Because simple string settings in realms are common, this is a shorthand method, but it does the same thing as the ROLES_SETTING
|
||||
// that is declared below (with the minor difference that "username" is a single string, and "roles" is a list)
|
||||
public static final Setting.AffixSetting<String> USERNAME_SETTING = RealmSettings.simpleString(TYPE, "username",
|
||||
Setting.Property.NodeScope, Setting.Property.Filtered);
|
||||
|
||||
public static final Setting.AffixSetting<SecureString> PASSWORD_SETTING = RealmSettings.secureString(TYPE, "password");
|
||||
|
||||
/**
|
||||
* The setting is declared as an AffixSetting, because part of the setting name is variable (the name of the realm).
|
||||
* An AffixSetting uses a factory method to construct a "concrete setting", which in this case is a list.
|
||||
* It will be entered in elasticsearch.yml as "xpack.security.authc.realms.{TYPE}.{NAME}.roles"
|
||||
* For example: {@code xpack.security.authc.realms.custom.your_realm_name.roles: [ "role1" , "role2" ]}
|
||||
* @see SpiExtensionPlugin#getSettings()
|
||||
*/
|
||||
public static final Setting.AffixSetting<List<String>> ROLES_SETTING = Setting.affixKeySetting(
|
||||
RealmSettings.realmSettingPrefix(TYPE),
|
||||
"roles", key -> Setting.listSetting(key, DEFAULT_ROLES, Function.identity(), Setting.Property.NodeScope));
|
||||
|
||||
private final String username;
|
||||
private final SecureString password;
|
||||
private final String[] roles;
|
||||
|
||||
public CustomRealm(RealmConfig config) {
|
||||
super(config);
|
||||
this.username = config.getSetting(USERNAME_SETTING, () -> DEFAULT_KNOWN_USER);
|
||||
this.password = config.getSetting(PASSWORD_SETTING, () -> DEFAULT_KNOWN_PW);
|
||||
this.roles = config.getSetting(ROLES_SETTING).toArray(new String[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -52,9 +84,9 @@ public class CustomRealm extends Realm {
|
|||
public void authenticate(AuthenticationToken authToken, ActionListener<AuthenticationResult> listener) {
|
||||
UsernamePasswordToken token = (UsernamePasswordToken)authToken;
|
||||
final String actualUser = token.principal();
|
||||
if (KNOWN_USER.equals(actualUser)) {
|
||||
if (CharArrays.constantTimeEquals(token.credentials().getChars(), KNOWN_PW.getChars())) {
|
||||
listener.onResponse(AuthenticationResult.success(new User(actualUser, ROLES)));
|
||||
if (username.equals(actualUser)) {
|
||||
if (CharArrays.constantTimeEquals(token.credentials().getChars(), password.getChars())) {
|
||||
listener.onResponse(AuthenticationResult.success(new User(actualUser, roles)));
|
||||
} else {
|
||||
listener.onResponse(AuthenticationResult.unsuccessful("Invalid password for user " + actualUser, null));
|
||||
}
|
||||
|
@ -65,6 +97,7 @@ public class CustomRealm extends Realm {
|
|||
|
||||
@Override
|
||||
public void lookupUser(String username, ActionListener<User> listener) {
|
||||
// Lookup (run-as) is not supported in this realm
|
||||
listener.onResponse(null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
package org.elasticsearch.example.realm;
|
||||
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.common.settings.MockSecureSettings;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
|
@ -13,33 +14,64 @@ import org.elasticsearch.env.TestEnvironment;
|
|||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
|
||||
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
|
||||
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
|
||||
import org.elasticsearch.xpack.core.security.user.User;
|
||||
|
||||
import static org.elasticsearch.xpack.core.security.authc.RealmSettings.getFullSettingKey;
|
||||
import static org.hamcrest.Matchers.arrayContaining;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
|
||||
public class CustomRealmTests extends ESTestCase {
|
||||
public void testAuthenticate() {
|
||||
|
||||
public void testAuthenticateDefaultConfig() {
|
||||
Settings globalSettings = Settings.builder().put("path.home", createTempDir()).build();
|
||||
CustomRealm realm = new CustomRealm(new RealmConfig(new RealmConfig.RealmIdentifier(CustomRealm.TYPE, "test"),
|
||||
globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)));
|
||||
SecureString password = CustomRealm.KNOWN_PW.clone();
|
||||
UsernamePasswordToken token = new UsernamePasswordToken(CustomRealm.KNOWN_USER, password);
|
||||
SecureString password = CustomRealm.DEFAULT_KNOWN_PW.clone();
|
||||
UsernamePasswordToken token = new UsernamePasswordToken(CustomRealm.DEFAULT_KNOWN_USER, password);
|
||||
PlainActionFuture<AuthenticationResult> plainActionFuture = new PlainActionFuture<>();
|
||||
realm.authenticate(token, plainActionFuture);
|
||||
User user = plainActionFuture.actionGet().getUser();
|
||||
assertThat(user, notNullValue());
|
||||
assertThat(user.roles(), equalTo(CustomRealm.ROLES));
|
||||
assertThat(user.principal(), equalTo(CustomRealm.KNOWN_USER));
|
||||
assertThat(user.roles(), arrayContaining(CustomRealm.DEFAULT_ROLES.toArray()));
|
||||
assertThat(user.principal(), equalTo(CustomRealm.DEFAULT_KNOWN_USER));
|
||||
}
|
||||
|
||||
public void testAuthenticateCustomConfig() {
|
||||
final RealmConfig.RealmIdentifier realmIdentifier = new RealmConfig.RealmIdentifier(CustomRealm.TYPE, "test");
|
||||
MockSecureSettings secureSettings = new MockSecureSettings();
|
||||
final String password = randomAlphaOfLengthBetween(8, 24);
|
||||
secureSettings.setString(getFullSettingKey(realmIdentifier.getName(), CustomRealm.PASSWORD_SETTING), password);
|
||||
Settings settings = Settings.builder()
|
||||
.put("path.home", createTempDir())
|
||||
.put(getFullSettingKey(realmIdentifier, RealmSettings.ORDER_SETTING), 0)
|
||||
.put(getFullSettingKey(realmIdentifier.getName(), CustomRealm.USERNAME_SETTING), "skroob")
|
||||
.setSecureSettings(secureSettings)
|
||||
.putList(getFullSettingKey(realmIdentifier.getName(), CustomRealm.ROLES_SETTING), "president", "villain")
|
||||
.build();
|
||||
CustomRealm realm = new CustomRealm(new RealmConfig(
|
||||
realmIdentifier,
|
||||
settings,
|
||||
TestEnvironment.newEnvironment(settings),
|
||||
new ThreadContext(settings)
|
||||
));
|
||||
UsernamePasswordToken token = new UsernamePasswordToken("skroob", new SecureString(password.toCharArray()));
|
||||
PlainActionFuture<AuthenticationResult> plainActionFuture = new PlainActionFuture<>();
|
||||
realm.authenticate(token, plainActionFuture);
|
||||
User user = plainActionFuture.actionGet().getUser();
|
||||
assertThat(user, notNullValue());
|
||||
assertThat(user.roles(), arrayContaining("president", "villain"));
|
||||
assertThat(user.principal(), equalTo("skroob"));
|
||||
}
|
||||
|
||||
public void testAuthenticateBadUser() {
|
||||
Settings globalSettings = Settings.builder().put("path.home", createTempDir()).build();
|
||||
CustomRealm realm = new CustomRealm(new RealmConfig(new RealmConfig.RealmIdentifier(CustomRealm.TYPE, "test"),
|
||||
globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings)));
|
||||
SecureString password = CustomRealm.KNOWN_PW.clone();
|
||||
UsernamePasswordToken token = new UsernamePasswordToken(CustomRealm.KNOWN_USER + "1", password);
|
||||
SecureString password = CustomRealm.DEFAULT_KNOWN_PW.clone();
|
||||
UsernamePasswordToken token = new UsernamePasswordToken(CustomRealm.DEFAULT_KNOWN_USER + "1", password);
|
||||
PlainActionFuture<AuthenticationResult> plainActionFuture = new PlainActionFuture<>();
|
||||
realm.authenticate(token, plainActionFuture);
|
||||
final AuthenticationResult result = plainActionFuture.actionGet();
|
||||
|
|
Loading…
Reference in New Issue