Skip to content
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
ee15ed9
Add security migration for cleaning up ECK role mappings
jfreden Oct 4, 2024
8656e76
Update docs/changelog/114830.yaml
jfreden Oct 16, 2024
505b530
fixup! Use new API for role mapping cleanup
jfreden Oct 17, 2024
007147d
fixup! Test
jfreden Oct 17, 2024
3be7fcc
fixup! Test
jfreden Oct 17, 2024
992f8fa
fixup! Refactor and add tests
jfreden Oct 17, 2024
4e979e0
fixup! Fix cleanup test
jfreden Oct 17, 2024
77cd0b1
fixup! Add comments
jfreden Oct 17, 2024
002872d
fixup! merge build.gradle
jfreden Oct 17, 2024
1d5c288
fixup! another build.gradle
jfreden Oct 17, 2024
3e7397b
fixup! Formatting
jfreden Oct 17, 2024
ab1fe28
fixup! comments
jfreden Oct 17, 2024
5fb0208
fixup! Code review comments
jfreden Oct 23, 2024
a26c856
fixup! Code review comment
jfreden Oct 23, 2024
7c80d4f
fixup! Spotless :(
jfreden Oct 23, 2024
f0d6bcc
fixup! Keep deleting even if something fails
jfreden Oct 23, 2024
0f5b515
fixup! Use new constants
jfreden Oct 23, 2024
3a579a8
fixup! Code review comments and bug fixes
jfreden Oct 23, 2024
8c0c3ac
fixup! typo
jfreden Oct 23, 2024
e493aec
fixup! test
jfreden Oct 23, 2024
d714e1e
fixup! lint
jfreden Oct 23, 2024
fdaa78a
fixup! Tests
jfreden Oct 24, 2024
abf985f
Use an enum to track status
jfreden Oct 24, 2024
1791ea8
fixup! checkstyle issue
jfreden Oct 24, 2024
308df3c
Always include operator file
jfreden Oct 24, 2024
3a1b91d
Add another migration test suite
jfreden Oct 24, 2024
0d58198
fixup! Spotless
jfreden Oct 24, 2024
be6666c
fixup! Add assertion on migration status
jfreden Oct 24, 2024
11a04fc
fixup! Skip calc + test
jfreden Oct 24, 2024
98f578c
fixup! Review comments and test code
jfreden Oct 25, 2024
b62a680
fixup! Test
jfreden Oct 25, 2024
f289004
fixup! Bug
jfreden Oct 25, 2024
be5eab1
Use index setting to track migration version index created on
jfreden Oct 25, 2024
6d3bc66
fixup! line
jfreden Oct 28, 2024
75ac9ad
fixup! Update name
jfreden Oct 28, 2024
9a62db9
fixup! Add tests and review comments
jfreden Oct 28, 2024
e5402df
Revert "Use index setting to track migration version index created on"
jfreden Oct 28, 2024
560923c
fixup! name
jfreden Oct 28, 2024
ea7eed2
fixup! Too much revert
jfreden Oct 28, 2024
3fae71b
fixup! Failing tests
jfreden Oct 29, 2024
f485ebb
Update docs/changelog/115823.yaml
jfreden Oct 29, 2024
bda46c3
fixup! bwc
jfreden Oct 29, 2024
30bce02
fixup! Delete duplicate changelog
jfreden Oct 29, 2024
97fd8cb
Delete docs/changelog/115823.yaml
jfreden Oct 29, 2024
65229f9
Update docs/changelog/115823.yaml
jfreden Oct 29, 2024
a0864a8
fixup! Review comments
jfreden Oct 29, 2024
c18abd7
fixup! Allow warning since can be passed fix
jfreden Oct 29, 2024
06b94a6
fixup! Cannot verify mappings pre migration anymore
jfreden Oct 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog/114830.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 114830
summary: Add Role Mapping Cleanup Security Migration
area: Security
type: bug
issues: []
5 changes: 5 additions & 0 deletions docs/changelog/115823.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 115823
summary: Add ECK Role Mapping Cleanup
area: Security
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,20 @@
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestRule;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;

public class FileSettingsRoleMappingUpgradeIT extends ParameterizedRollingUpgradeTestCase {

private static final String settingsJSON = """
private static final int ROLE_MAPPINGS_CLEANUP_MIGRATION_VERSION = 2;
private static final String SETTING_JSON = """
{
"metadata": {
"version": "1",
Expand All @@ -53,7 +54,6 @@ public class FileSettingsRoleMappingUpgradeIT extends ParameterizedRollingUpgrad
}""";

private static final TemporaryFolder repoDirectory = new TemporaryFolder();

private static final ElasticsearchCluster cluster = ElasticsearchCluster.local()
.distribution(DistributionType.DEFAULT)
.version(getOldClusterTestVersion())
Expand All @@ -68,7 +68,7 @@ public String get() {
.setting("xpack.security.enabled", "true")
// workaround to avoid having to set up clients and authorization headers
.setting("xpack.security.authc.anonymous.roles", "superuser")
.configFile("operator/settings.json", Resource.fromString(settingsJSON))
.configFile("operator/settings.json", Resource.fromString(SETTING_JSON))
.build();

@ClassRule
Expand All @@ -91,7 +91,30 @@ public void checkVersions() {
);
}

public void testRoleMappingsAppliedOnUpgrade() throws IOException {
private static void waitForSecurityMigrationCompletionIfIndexExists() throws Exception {
final Request request = new Request("GET", "_cluster/state/metadata/.security-7");
assertBusy(() -> {
Map<String, Object> indices = new XContentTestUtils.JsonMapView(entityAsMap(client().performRequest(request))).get(
"metadata.indices"
);
assertNotNull(indices);
// If the security index exists, migration needs to happen. There is a bug in pre cluster state role mappings code that tries
// to write file based role mappings before security index manager state is recovered, this makes it look like the security
// index is outdated (isIndexUpToDate == false). Because we can't rely on the index being there for old versions, this check
// is needed.
if (indices.containsKey(".security-7")) {
// JsonMapView doesn't support . prefixed indices (splits on .)
@SuppressWarnings("unchecked")
String responseVersion = new XContentTestUtils.JsonMapView((Map<String, Object>) indices.get(".security-7")).get(
"migration_version.version"
);
assertNotNull(responseVersion);
assertTrue(Integer.parseInt(responseVersion) >= ROLE_MAPPINGS_CLEANUP_MIGRATION_VERSION);
}
});
}

public void testRoleMappingsAppliedOnUpgrade() throws Exception {
if (isOldCluster()) {
Request clusterStateRequest = new Request("GET", "/_cluster/state/metadata");
List<Object> roleMappings = new XContentTestUtils.JsonMapView(entityAsMap(client().performRequest(clusterStateRequest))).get(
Expand All @@ -107,11 +130,10 @@ public void testRoleMappingsAppliedOnUpgrade() throws IOException {
).get("metadata.role_mappings.role_mappings");
assertThat(clusterStateRoleMappings, is(not(nullValue())));
assertThat(clusterStateRoleMappings.size(), equalTo(1));

waitForSecurityMigrationCompletionIfIndexExists();
assertThat(
entityAsMap(client().performRequest(new Request("GET", "/_security/role_mapping"))).keySet(),
// TODO change this to `contains` once the clean-up migration work is merged
hasItem("everyone_kibana-read-only-operator-mapping")
contains("everyone_kibana-read-only-operator-mapping")
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,21 @@ public Set<String> conflicts(String handlerName, Set<String> modified) {
return Collections.unmodifiableSet(intersect);
}

/**
* Get the reserved keys for the handler name
*
* @param handlerName handler name to get keys for
* @return set of keys for that handler
*/
public Set<String> keys(String handlerName) {
ReservedStateHandlerMetadata handlerMetadata = handlers.get(handlerName);
if (handlerMetadata == null || handlerMetadata.keys().isEmpty()) {
return Collections.emptySet();
}

return Collections.unmodifiableSet(handlerMetadata.keys());
}

/**
* Reads an {@link ReservedStateMetadata} from a {@link StreamInput}
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ private static Version parseUnchecked(String version) {
public static final IndexVersion MERGE_ON_RECOVERY_VERSION = def(8_515_00_0, Version.LUCENE_9_11_1);
public static final IndexVersion UPGRADE_TO_LUCENE_9_12 = def(8_516_00_0, Version.LUCENE_9_12_0);
public static final IndexVersion ENABLE_IGNORE_ABOVE_LOGSDB = def(8_517_00_0, Version.LUCENE_9_12_0);

public static final IndexVersion ADD_ROLE_MAPPING_CLEANUP_MIGRATION = def(8_518_00_0, Version.LUCENE_9_12_0);
public static final IndexVersion UPGRADE_TO_LUCENE_10_0_0 = def(9_000_00_0, Version.LUCENE_10_0_0);

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,15 +206,15 @@ public RoleMapperExpression getExpression() {
* that match the {@link #getExpression() expression} in this mapping.
*/
public List<String> getRoles() {
return Collections.unmodifiableList(roles);
return roles != null ? Collections.unmodifiableList(roles) : Collections.emptyList();
}

/**
* The list of {@link RoleDescriptor roles} (specified by a {@link TemplateRoleName template} that evaluates to one or more names)
* that should be assigned to users that match the {@link #getExpression() expression} in this mapping.
*/
public List<TemplateRoleName> getRoleTemplates() {
return Collections.unmodifiableList(roleTemplates);
return roleTemplates != null ? Collections.unmodifiableList(roleTemplates) : Collections.emptyList();
}

/**
Expand All @@ -223,7 +223,7 @@ public List<TemplateRoleName> getRoleTemplates() {
* This is not used within the mapping process, and does not affect whether the expression matches, nor which roles are assigned.
*/
public Map<String, Object> getMetadata() {
return Collections.unmodifiableMap(metadata);
return metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
}

/**
Expand All @@ -233,6 +233,15 @@ public boolean isEnabled() {
return enabled;
}

/**
* Whether this mapping is an operator defined/read only role mapping
*/
public boolean isReadOnly() {
return metadata != null && metadata.get(ExpressionRoleMapping.READ_ONLY_ROLE_MAPPING_METADATA_FLAG) instanceof Boolean readOnly
? readOnly
: false;
}

@Override
public String toString() {
return getClass().getSimpleName() + "<" + name + " ; " + roles + "/" + roleTemplates + " = " + Strings.toString(expression) + ">";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,14 @@ public static boolean hasFallbackName(ExpressionRoleMapping expressionRoleMappin
return expressionRoleMapping.getName().equals(FALLBACK_NAME);
}

/**
* Check if any of the role mappings have a fallback name
* @return true if any role mappings have the fallback name
*/
public boolean hasAnyMappingWithFallbackName() {
return roleMappings.stream().anyMatch(RoleMappingMetadata::hasFallbackName);
}

/**
* Parse a role mapping from XContent, restoring the name from a reserved metadata field.
* Used to parse a role mapping annotated with its name in metadata via @see {@link #copyWithNameInMetadata(ExpressionRoleMapping)}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,29 @@ public void clusterChanged(ClusterChangedEvent event) {
return new Tuple<>(savedClusterState, metadataVersion);
}

// Wait for any file metadata
public static Tuple<CountDownLatch, AtomicLong> setupClusterStateListener(String node) {
ClusterService clusterService = internalCluster().clusterService(node);
CountDownLatch savedClusterState = new CountDownLatch(1);
AtomicLong metadataVersion = new AtomicLong(-1);
clusterService.addListener(new ClusterStateListener() {
@Override
public void clusterChanged(ClusterChangedEvent event) {
ReservedStateMetadata reservedState = event.state().metadata().reservedStateMetadata().get(FileSettingsService.NAMESPACE);
if (reservedState != null) {
ReservedStateHandlerMetadata handlerMetadata = reservedState.handlers().get(ReservedRoleMappingAction.NAME);
if (handlerMetadata != null) {
clusterService.removeListener(this);
metadataVersion.set(event.state().metadata().version());
savedClusterState.countDown();
}
}
}
});

return new Tuple<>(savedClusterState, metadataVersion);
}

public static Tuple<CountDownLatch, AtomicLong> setupClusterStateListenerForCleanup(String node) {
ClusterService clusterService = internalCluster().clusterService(node);
CountDownLatch savedClusterState = new CountDownLatch(1);
Expand Down
Loading