Skip to content

Commit

Permalink
Tests
Browse files Browse the repository at this point in the history
  • Loading branch information
n1v0lg committed Nov 3, 2023
1 parent dff2616 commit 0a184f3
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package org.elasticsearch.xpack.security.authc.jwt;

import com.carrotsearch.randomizedtesting.annotations.TestCaseOrdering;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.jwk.JWK;
Expand All @@ -23,8 +24,10 @@
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.core.PathUtils;
import org.elasticsearch.core.Strings;
import org.elasticsearch.test.AnnotationTestOrdering;
import org.elasticsearch.test.TestSecurityClient;
import org.elasticsearch.test.cluster.ElasticsearchCluster;
import org.elasticsearch.test.cluster.MutableSettingsProvider;
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
import org.elasticsearch.test.cluster.util.resource.Resource;
import org.elasticsearch.test.rest.ESRestTestCase;
Expand All @@ -48,8 +51,41 @@
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.instanceOf;

@TestCaseOrdering(AnnotationTestOrdering.class)
public class JwtUnavailableSecurityIndexRestIT extends ESRestTestCase {

private static final MutableSettingsProvider mutableSettings = new MutableSettingsProvider() {
{
put("xpack.ml.enabled", "false");
put("xpack.license.self_generated.type", "trial");
put("xpack.security.enabled", "true");
put("xpack.security.http.ssl.enabled", "true");
put("xpack.security.transport.ssl.enabled", "false");
put("xpack.security.authc.token.enabled", "true");
put("xpack.security.authc.api_key.enabled", "true");
put("xpack.security.http.ssl.enabled", "true");
put("xpack.security.http.ssl.certificate", "http.crt");
put("xpack.security.http.ssl.key", "http.key");
put("xpack.security.http.ssl.key_passphrase", "http-password");
put("xpack.security.http.ssl.certificate_authorities", "ca.crt");
put("xpack.security.http.ssl.client_authentication", "optional");
put("xpack.security.authc.realms.jwt.jwt1.order", "1");
put("xpack.security.authc.realms.jwt.jwt1.allowed_issuer", "https://issuer.example.com/");
put("xpack.security.authc.realms.jwt.jwt1.allowed_audiences", "https://audience.example.com/");
put("xpack.security.authc.realms.jwt.jwt1.claims.principal", "sub");
put("xpack.security.authc.realms.jwt.jwt1.claims.dn", "dn");
put("xpack.security.authc.realms.jwt.jwt1.required_claims.token_use", "id");
put("xpack.security.authc.realms.jwt.jwt1.required_claims.version", "2.0");
put("xpack.security.authc.realms.jwt.jwt1.client_authentication.type", "NONE");
put("xpack.security.authc.realms.jwt.jwt1.pkc_jwkset_path", "rsa.jwkset");
}
};

@Override
protected boolean preserveClusterUponCompletion() {
return true;
}

@ClassRule
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
.nodes(1)
Expand All @@ -58,34 +94,7 @@ public class JwtUnavailableSecurityIndexRestIT extends ESRestTestCase {
.configFile("http.crt", Resource.fromClasspath("ssl/http.crt"))
.configFile("ca.crt", Resource.fromClasspath("ssl/ca.crt"))
.configFile("rsa.jwkset", Resource.fromClasspath("jwk/rsa-public-jwkset.json"))
.setting("xpack.ml.enabled", "false")
.setting("xpack.license.self_generated.type", "trial")
.setting("xpack.security.enabled", "true")
.setting("xpack.security.http.ssl.enabled", "true")
.setting("xpack.security.transport.ssl.enabled", "false")
.setting("xpack.security.authc.token.enabled", "true")
.setting("xpack.security.authc.api_key.enabled", "true")

.setting("xpack.security.http.ssl.enabled", "true")
.setting("xpack.security.http.ssl.certificate", "http.crt")
.setting("xpack.security.http.ssl.key", "http.key")
.setting("xpack.security.http.ssl.key_passphrase", "http-password")
.setting("xpack.security.http.ssl.certificate_authorities", "ca.crt")
.setting("xpack.security.http.ssl.client_authentication", "optional")

.setting("xpack.security.authc.realms.file.admin_file.order", "0")

.setting("xpack.security.authc.realms.jwt.jwt1.order", "1")
.setting("xpack.security.authc.realms.jwt.jwt1.allowed_issuer", "https://issuer.example.com/")
.setting("xpack.security.authc.realms.jwt.jwt1.allowed_audiences", "https://audience.example.com/")
.setting("xpack.security.authc.realms.jwt.jwt1.claims.principal", "sub")
.setting("xpack.security.authc.realms.jwt.jwt1.claims.dn", "dn")
.setting("xpack.security.authc.realms.jwt.jwt1.required_claims.token_use", "id")
.setting("xpack.security.authc.realms.jwt.jwt1.required_claims.version", "2.0")
.setting("xpack.security.authc.realms.jwt.jwt1.client_authentication.type", "NONE")
.setting("xpack.security.authc.realms.jwt.jwt1.pkc_jwkset_path", "rsa.jwkset")

.setting("xpack.security.authc.role_mapping.fallback_cache.enabled", "true")
.settings(mutableSettings)
.user("admin_user", "admin-password")
.build();

Expand Down Expand Up @@ -137,7 +146,8 @@ protected TestSecurityClient getAdminSecurityClient() {
return adminSecurityClient;
}

public void testAuthenticateWithCachedRoleMappingSucceedsWithoutAccessToSecurityIndex() throws Exception {
@AnnotationTestOrdering.Order(10)
public void testRoleMappingWithoutCacheFailsWithoutAccessToSecurityIndex() throws Exception {
final String dn = randomDn();

final String rules = Strings.format("""
Expand All @@ -149,22 +159,64 @@ public void testAuthenticateWithCachedRoleMappingSucceedsWithoutAccessToSecurity

final List<String> roles = randomRoles();
final String roleMappingName = createRoleMapping(roles, rules);
final String principal = randomPrincipal();

try {
{
final String principal = randomPrincipal();
final SignedJWT jwt = buildAndSignJwt(principal, dn, Instant.now());
final TestSecurityClient client = getSecurityClient(jwt);

final Map<String, Object> response = client.authenticate();
final Map<String, Object> response = getSecurityClient(jwt).authenticate();

assertAuthenticationHasUsernameAndRoles(response, principal, roles);
}

makeSecurityIndexUnavailable();

{
final SignedJWT jwt = buildAndSignJwt(principal, dn, Instant.now());

final Map<String, Object> response = getSecurityClient(jwt).authenticate();

assertAuthenticationHasUsernameAndRoles(response, principal, List.of());
}
} finally {
makeSecurityIndexAvailable();
deleteRoleMapping(roleMappingName);

mutableSettings.put("xpack.security.authc.role_mapping.successful_load_cache.enabled", "true");
cluster.restart(false);
closeClients();
adminSecurityClient = null;
}
}

@AnnotationTestOrdering.Order(20)
public void testRoleMappingWithCacheSucceedsWithoutAccessToSecurityIndex() throws Exception {
final String dn = randomDn();

final String rules = Strings.format("""
{ "all": [
{ "field": { "realm.name": "jwt1" } },
{ "field": { "dn": "%s" } }
] }
""", dn);

final List<String> roles = randomRoles();
final String roleMappingName = createRoleMapping(roles, rules);
final String principal = randomPrincipal();

try {
{
final SignedJWT jwt = buildAndSignJwt(principal, dn, Instant.now());

final Map<String, Object> response = getSecurityClient(jwt).authenticate();

assertAuthenticationHasUsernameAndRoles(response, principal, roles);
}

makeSecurityIndexUnavailable();

{
final String principal = randomPrincipal();
final SignedJWT jwt = buildAndSignJwt(principal, dn, Instant.now());

final Map<String, Object> response = getSecurityClient(jwt).authenticate();
Expand All @@ -173,7 +225,6 @@ public void testAuthenticateWithCachedRoleMappingSucceedsWithoutAccessToSecurity
}

{
final String principal = randomPrincipal();
final SignedJWT jwt = buildAndSignJwt(principal, randomValueOtherThan(dn, this::randomDn), Instant.now());

final Map<String, Object> response = getSecurityClient(jwt).authenticate();
Expand All @@ -182,7 +233,7 @@ public void testAuthenticateWithCachedRoleMappingSucceedsWithoutAccessToSecurity
assertAuthenticationHasUsernameAndRoles(response, principal, List.of());
}
} finally {
restoreSecurityIndexAvailability();
makeSecurityIndexAvailable();
deleteRoleMapping(roleMappingName);
}
}
Expand All @@ -207,7 +258,7 @@ private void makeSecurityIndexUnavailable() throws IOException {
assertOK(adminClient().performRequest(closeRequest));
}

private void restoreSecurityIndexAvailability() throws IOException {
private void makeSecurityIndexAvailable() throws IOException {
Request openRequest = new Request("POST", "/.security/_open");
openRequest.setOptions(systemIndexWarningHandlerOptions(".security-7"));
assertOK(adminClient().performRequest(openRequest));
Expand Down Expand Up @@ -285,5 +336,4 @@ private String createRoleMapping(List<String> roles, String rules) throws IOExce
private void deleteRoleMapping(String name) throws IOException {
getAdminSecurityClient().deleteRoleMapping(name);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1198,7 +1198,7 @@ public static List<Setting<?>> getSettings(List<SecurityExtension> securityExten
settingsList.add(CachingServiceAccountTokenStore.CACHE_HASH_ALGO_SETTING);
settingsList.add(CachingServiceAccountTokenStore.CACHE_MAX_TOKENS_SETTING);
settingsList.add(SimpleRole.CACHE_SIZE_SETTING);
settingsList.add(NativeRoleMappingStore.FALLBACK_CACHE_ENABLED_SETTING);
settingsList.add(NativeRoleMappingStore.SUCCESSFUL_LOAD_CACHE_ENABLED_SETTING);

// hide settings
settingsList.add(Setting.stringListSetting(SecurityField.setting("hide_settings"), Property.NodeScope, Property.Filtered));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ public class NativeRoleMappingStore implements UserRoleMapper {

private static final String ID_PREFIX = DOC_TYPE_ROLE_MAPPING + "_";

public static final Setting<Boolean> FALLBACK_CACHE_ENABLED_SETTING = Setting.boolSetting(
"xpack.security.authc.role_mapping.fallback_cache.enabled",
public static final Setting<Boolean> SUCCESSFUL_LOAD_CACHE_ENABLED_SETTING = Setting.boolSetting(
"xpack.security.authc.role_mapping.successful_load_cache.enabled",
false,
Setting.Property.NodeScope,
Setting.Property.Filtered
Expand All @@ -102,15 +102,15 @@ public class NativeRoleMappingStore implements UserRoleMapper {
private final SecurityIndexManager securityIndex;
private final ScriptService scriptService;
private final List<String> realmsToRefresh = new CopyOnWriteArrayList<>();
private final boolean fallbackCacheEnabled;
private final boolean successfulLoadCacheEnabled;
private final AtomicReference<List<ExpressionRoleMapping>> lastSuccessfulLoadRef = new AtomicReference<>(null);

public NativeRoleMappingStore(Settings settings, Client client, SecurityIndexManager securityIndex, ScriptService scriptService) {
this.settings = settings;
this.client = client;
this.securityIndex = securityIndex;
this.scriptService = scriptService;
this.fallbackCacheEnabled = FALLBACK_CACHE_ENABLED_SETTING.get(settings);
this.successfulLoadCacheEnabled = SUCCESSFUL_LOAD_CACHE_ENABLED_SETTING.get(settings);
}

private static String getNameFromId(String id) {
Expand Down Expand Up @@ -152,7 +152,7 @@ protected void loadMappings(ActionListener<List<ExpressionRoleMapping>> listener
new ContextPreservingActionListener<>(supplier, ActionListener.wrap((Collection<ExpressionRoleMapping> mappings) -> {
final List<ExpressionRoleMapping> mappingList = mappings.stream().filter(Objects::nonNull).toList();
logger.debug("successfully loaded [{}] role-mapping(s) from [{}]", mappingList.size(), securityIndex.aliasName());
if (fallbackCacheEnabled) {
if (successfulLoadCacheEnabled) {
logger.debug("caching loaded role-mapping(s)");
lastSuccessfulLoadRef.set(mappingList);
}
Expand Down Expand Up @@ -318,7 +318,7 @@ private void getMappings(ActionListener<List<ExpressionRoleMapping>> listener) {
final List<ExpressionRoleMapping> lastSuccessfulLoad = lastSuccessfulLoadRef.get();
if (frozenSecurityIndex.indexIsClosed()) {
if (lastSuccessfulLoad != null) {
assert fallbackCacheEnabled;
assert successfulLoadCacheEnabled;
logger.debug("The security index exists but is closed - returning previously cached role mappings");
listener.onResponse(lastSuccessfulLoad);
} else {
Expand All @@ -328,7 +328,7 @@ private void getMappings(ActionListener<List<ExpressionRoleMapping>> listener) {
} else if (frozenSecurityIndex.isAvailable(SEARCH_SHARDS) == false) {
final ElasticsearchException unavailableReason = frozenSecurityIndex.getUnavailableReason(SEARCH_SHARDS);
if (lastSuccessfulLoad != null) {
assert fallbackCacheEnabled;
assert successfulLoadCacheEnabled;
logger.debug(
"The security index exists but is not available - returning previously cached role mappings",
unavailableReason
Expand Down

0 comments on commit 0a184f3

Please sign in to comment.