-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Add Role Mapping Cleanup Security Migration #114830
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 32 commits
c23b783
157be76
c4ad317
dda3c77
c6e54e6
e018eaf
3a7e61f
8b50d79
d3259d5
7299d19
11f775a
97abe3c
4d4f412
3974dce
52146ed
e16279b
9c8efe3
1db4315
07db034
d23974f
08c7d95
22ef3b7
8762d9b
698b554
3430c81
5a96adf
2563993
1709df1
1e01e08
90eb45f
b86c0a8
ac9e6da
a91f37b
ce405e2
304e105
1e65bf7
9533f6f
d6e466b
9112aba
a568a31
2ed011b
e1bb9d0
3160bd4
9eb0dbe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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: [] |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ | |
| import org.elasticsearch.cluster.metadata.IndexMetadata; | ||
| import org.elasticsearch.cluster.metadata.MappingMetadata; | ||
| import org.elasticsearch.cluster.metadata.Metadata; | ||
| import org.elasticsearch.cluster.metadata.ReservedStateMetadata; | ||
| import org.elasticsearch.cluster.routing.IndexRoutingTable; | ||
| import org.elasticsearch.cluster.service.ClusterService; | ||
| import org.elasticsearch.core.TimeValue; | ||
|
|
@@ -46,7 +47,9 @@ | |
| import org.elasticsearch.rest.RestStatus; | ||
| import org.elasticsearch.threadpool.Scheduler; | ||
| import org.elasticsearch.xcontent.XContentType; | ||
| import org.elasticsearch.xpack.core.security.authz.RoleMappingMetadata; | ||
| import org.elasticsearch.xpack.security.SecurityFeatures; | ||
| import org.elasticsearch.xpack.security.action.rolemapping.ReservedRoleMappingAction; | ||
|
|
||
| import java.time.Instant; | ||
| import java.util.List; | ||
|
|
@@ -74,7 +77,7 @@ | |
| public class SecurityIndexManager implements ClusterStateListener { | ||
|
|
||
| public static final String SECURITY_VERSION_STRING = "security-version"; | ||
|
|
||
| private static final String FILE_SETTINGS_METADATA_NAMESPACE = "file_settings"; | ||
| private static final Logger logger = LogManager.getLogger(SecurityIndexManager.class); | ||
|
|
||
| /** | ||
|
|
@@ -85,6 +88,13 @@ public enum Availability { | |
| PRIMARY_SHARDS | ||
| } | ||
|
|
||
| public enum RoleMappingsCleanupMigrationStatus { | ||
| READY, | ||
| NOT_READY, | ||
| SKIP, | ||
| DONE | ||
| } | ||
|
|
||
| private final Client client; | ||
| private final SystemIndexDescriptor systemIndexDescriptor; | ||
|
|
||
|
|
@@ -267,6 +277,49 @@ private static boolean isCreatedOnLatestVersion(IndexMetadata indexMetadata) { | |
| return indexVersionCreated != null && indexVersionCreated.onOrAfter(IndexVersion.current()); | ||
| } | ||
|
|
||
| /** | ||
| * Check if a role mappings cleanup migration is needed or has already been performed and if the cluster is ready for a cleanup | ||
| * migration | ||
| * | ||
| * @param clusterState current cluster state | ||
| * @param migrationsVersion current migration version | ||
| * | ||
| * @return RoleMappingsCleanupMigrationStatus | ||
| */ | ||
| static RoleMappingsCleanupMigrationStatus getRoleMappingsCleanupMigrationStatus(ClusterState clusterState, int migrationsVersion) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It be great to cover this method with some units tests -- the ITs are great but unit tests we can much better cover some of the NOT_READY cases, e.g., size mismatch & some (but not all) mappings with fallback names. |
||
| // Migration already finished | ||
| if (migrationsVersion >= SecurityMigrations.CLEANUP_ROLE_MAPPING_DUPLICATES_MIGRATION_VERSION) { | ||
n1v0lg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return RoleMappingsCleanupMigrationStatus.DONE; | ||
| } | ||
|
|
||
| ReservedStateMetadata fileSettingsMetadata = clusterState.metadata().reservedStateMetadata().get(FILE_SETTINGS_METADATA_NAMESPACE); | ||
n1v0lg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| boolean hasFileSettingsMetadata = fileSettingsMetadata != null; | ||
| // If there is no fileSettingsMetadata, there should be no reserved state (this is to catch bugs related to | ||
| // name changes to FILE_SETTINGS_METADATA_NAMESPACE) | ||
| assert hasFileSettingsMetadata || clusterState.metadata().reservedStateMetadata().isEmpty(); | ||
|
||
|
|
||
| // If no file based role mappings available -> migration not needed | ||
| if (hasFileSettingsMetadata == false || fileSettingsMetadata.keys(ReservedRoleMappingAction.NAME).isEmpty()) { | ||
| return RoleMappingsCleanupMigrationStatus.SKIP; | ||
| } | ||
|
|
||
| RoleMappingMetadata roleMappingMetadata = RoleMappingMetadata.getFromClusterState(clusterState); | ||
|
|
||
| // If there are file based role mappings, make sure they have the latest format (name available) and that they have all been | ||
| // synced to cluster state (same size as the reserved state keys) | ||
| if (roleMappingMetadata.getRoleMappings().size() == fileSettingsMetadata.keys(ReservedRoleMappingAction.NAME).size() | ||
| && roleMappingMetadata.hasAnyMappingWithFallbackName() == false) { | ||
| return RoleMappingsCleanupMigrationStatus.READY; | ||
| } | ||
|
|
||
| // If none of the above conditions are met, wait for a state change to re-evaluate if the cluster is ready for migration | ||
| return RoleMappingsCleanupMigrationStatus.NOT_READY; | ||
| } | ||
|
|
||
| public RoleMappingsCleanupMigrationStatus getRoleMappingsCleanupMigrationStatus() { | ||
| return state.roleMappingsCleanupMigrationStatus; | ||
| } | ||
|
|
||
| @Override | ||
| public void clusterChanged(ClusterChangedEvent event) { | ||
| if (event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) { | ||
|
|
@@ -284,8 +337,12 @@ public void clusterChanged(ClusterChangedEvent event) { | |
| Tuple<Boolean, Boolean> available = checkIndexAvailable(event.state()); | ||
| final boolean indexAvailableForWrite = available.v1(); | ||
| final boolean indexAvailableForSearch = available.v2(); | ||
| final boolean mappingIsUpToDate = indexMetadata == null || checkIndexMappingUpToDate(event.state()); | ||
| final int migrationsVersion = getMigrationVersionFromIndexMetadata(indexMetadata); | ||
| final RoleMappingsCleanupMigrationStatus roleMappingsCleanupMigrationStatus = getRoleMappingsCleanupMigrationStatus( | ||
| event.state(), | ||
| migrationsVersion | ||
| ); | ||
| final boolean mappingIsUpToDate = indexMetadata == null || checkIndexMappingUpToDate(event.state()); | ||
| final SystemIndexDescriptor.MappingsVersion minClusterMappingVersion = getMinSecurityIndexMappingVersion(event.state()); | ||
| final int indexMappingVersion = loadIndexMappingVersion(systemIndexDescriptor.getAliasName(), event.state()); | ||
| final String concreteIndexName = indexMetadata == null | ||
|
|
@@ -314,6 +371,7 @@ public void clusterChanged(ClusterChangedEvent event) { | |
| indexAvailableForWrite, | ||
| mappingIsUpToDate, | ||
| createdOnLatestVersion, | ||
| roleMappingsCleanupMigrationStatus, | ||
| migrationsVersion, | ||
| minClusterMappingVersion, | ||
| indexMappingVersion, | ||
|
|
@@ -438,7 +496,8 @@ private Tuple<Boolean, Boolean> checkIndexAvailable(ClusterState state) { | |
|
|
||
| public boolean isEligibleSecurityMigration(SecurityMigrations.SecurityMigration securityMigration) { | ||
| return state.securityFeatures.containsAll(securityMigration.nodeFeaturesRequired()) | ||
| && state.indexMappingVersion >= securityMigration.minMappingVersion(); | ||
| && state.indexMappingVersion >= securityMigration.minMappingVersion() | ||
| && securityMigration.checkPreConditions(state); | ||
| } | ||
|
|
||
| public boolean isReadyForSecurityMigration(SecurityMigrations.SecurityMigration securityMigration) { | ||
|
|
@@ -678,6 +737,7 @@ public static class State { | |
| null, | ||
| null, | ||
| null, | ||
| null, | ||
| Set.of() | ||
| ); | ||
| public final Instant creationTime; | ||
|
|
@@ -686,6 +746,7 @@ public static class State { | |
| public final boolean indexAvailableForWrite; | ||
| public final boolean mappingUpToDate; | ||
| public final boolean createdOnLatestVersion; | ||
| public final RoleMappingsCleanupMigrationStatus roleMappingsCleanupMigrationStatus; | ||
| public final Integer migrationsVersion; | ||
| // Min mapping version supported by the descriptors in the cluster | ||
| public final SystemIndexDescriptor.MappingsVersion minClusterMappingVersion; | ||
|
|
@@ -704,6 +765,7 @@ public State( | |
| boolean indexAvailableForWrite, | ||
| boolean mappingUpToDate, | ||
| boolean createdOnLatestVersion, | ||
| RoleMappingsCleanupMigrationStatus roleMappingsCleanupMigrationStatus, | ||
| Integer migrationsVersion, | ||
| SystemIndexDescriptor.MappingsVersion minClusterMappingVersion, | ||
| Integer indexMappingVersion, | ||
|
|
@@ -720,6 +782,7 @@ public State( | |
| this.mappingUpToDate = mappingUpToDate; | ||
| this.migrationsVersion = migrationsVersion; | ||
| this.createdOnLatestVersion = createdOnLatestVersion; | ||
| this.roleMappingsCleanupMigrationStatus = roleMappingsCleanupMigrationStatus; | ||
| this.minClusterMappingVersion = minClusterMappingVersion; | ||
| this.indexMappingVersion = indexMappingVersion; | ||
| this.concreteIndexName = concreteIndexName; | ||
|
|
@@ -740,6 +803,7 @@ public boolean equals(Object o) { | |
| && indexAvailableForWrite == state.indexAvailableForWrite | ||
| && mappingUpToDate == state.mappingUpToDate | ||
| && createdOnLatestVersion == state.createdOnLatestVersion | ||
| && roleMappingsCleanupMigrationStatus == state.roleMappingsCleanupMigrationStatus | ||
| && Objects.equals(indexMappingVersion, state.indexMappingVersion) | ||
| && Objects.equals(migrationsVersion, state.migrationsVersion) | ||
| && Objects.equals(minClusterMappingVersion, state.minClusterMappingVersion) | ||
|
|
@@ -762,6 +826,7 @@ public int hashCode() { | |
| indexAvailableForWrite, | ||
| mappingUpToDate, | ||
| createdOnLatestVersion, | ||
| roleMappingsCleanupMigrationStatus, | ||
| migrationsVersion, | ||
| minClusterMappingVersion, | ||
| indexMappingVersion, | ||
|
|
@@ -786,6 +851,8 @@ public String toString() { | |
| + mappingUpToDate | ||
| + ", createdOnLatestVersion=" | ||
| + createdOnLatestVersion | ||
| + ", roleMappingsCleanupMigrationStatus=" | ||
| + roleMappingsCleanupMigrationStatus | ||
| + ", migrationsVersion=" | ||
| + migrationsVersion | ||
| + ", minClusterMappingVersion=" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are not set to null anywhere in production code, but could be in the future (since constructor is public) and can in tests (as I found out).