-
Notifications
You must be signed in to change notification settings - Fork 25.1k
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
Add Role Mapping Cleanup Security Migration #114830
Changes from 1 commit
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 |
---|---|---|
|
@@ -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,6 +47,8 @@ | |
import org.elasticsearch.rest.RestStatus; | ||
import org.elasticsearch.threadpool.Scheduler; | ||
import org.elasticsearch.xcontent.XContentType; | ||
import org.elasticsearch.xpack.core.security.authc.support.mapper.ExpressionRoleMapping; | ||
import org.elasticsearch.xpack.core.security.authz.RoleMappingMetadata; | ||
import org.elasticsearch.xpack.security.SecurityFeatures; | ||
|
||
import java.time.Instant; | ||
|
@@ -74,7 +77,8 @@ | |
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 String HANDLER_ROLE_MAPPINGS_NAME = "role_mappings"; | ||
private static final Logger logger = LogManager.getLogger(SecurityIndexManager.class); | ||
|
||
/** | ||
|
@@ -267,6 +271,22 @@ private static boolean isCreatedOnLatestVersion(IndexMetadata indexMetadata) { | |
return indexVersionCreated != null && indexVersionCreated.onOrAfter(IndexVersion.current()); | ||
} | ||
|
||
private static Set<String> getFileSettingsMetadataHandlerRoleMappingKeys(ClusterState clusterState) { | ||
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. This isn't exactly part of the security index, so this doesn't belong here, but I added it here anyway for simplicity. Ideally the security migration state should be tracked in a separate cluster state handler. I think that's an improvement/refactor that should be done in a separate PR if we keep adding migrations on state outside the security index. The new state handler would still need to handle the main security index manager state, since we rely heavily on state from it in the framework (is the index available for example). |
||
ReservedStateMetadata fileSettingsMetadata = clusterState.metadata().reservedStateMetadata().get(FILE_SETTINGS_METADATA_NAMESPACE); | ||
n1v0lg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (fileSettingsMetadata != null && fileSettingsMetadata.handlers().containsKey(HANDLER_ROLE_MAPPINGS_NAME)) { | ||
return fileSettingsMetadata.handlers().get(HANDLER_ROLE_MAPPINGS_NAME).keys(); | ||
} | ||
return Set.of(); | ||
} | ||
|
||
private static Set<ExpressionRoleMapping> getRoleMappingMetadataMappings(ClusterState clusterState) { | ||
RoleMappingMetadata roleMappingMetadata = RoleMappingMetadata.getFromClusterState(clusterState); | ||
if (roleMappingMetadata != null) { | ||
return roleMappingMetadata.getRoleMappings(); | ||
} | ||
return Set.of(); | ||
} | ||
|
||
@Override | ||
public void clusterChanged(ClusterChangedEvent event) { | ||
if (event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) { | ||
|
@@ -284,6 +304,9 @@ public void clusterChanged(ClusterChangedEvent event) { | |
Tuple<Boolean, Boolean> available = checkIndexAvailable(event.state()); | ||
final boolean indexAvailableForWrite = available.v1(); | ||
final boolean indexAvailableForSearch = available.v2(); | ||
final Set<String> reservedStateRoleMappingNames = getFileSettingsMetadataHandlerRoleMappingKeys(event.state()); | ||
final boolean reservedRoleMappingsSynced = reservedStateRoleMappingNames.size() == getRoleMappingMetadataMappings(event.state()) | ||
.size(); | ||
final boolean mappingIsUpToDate = indexMetadata == null || checkIndexMappingUpToDate(event.state()); | ||
final int migrationsVersion = getMigrationVersionFromIndexMetadata(indexMetadata); | ||
final SystemIndexDescriptor.MappingsVersion minClusterMappingVersion = getMinSecurityIndexMappingVersion(event.state()); | ||
|
@@ -314,6 +337,7 @@ public void clusterChanged(ClusterChangedEvent event) { | |
indexAvailableForWrite, | ||
mappingIsUpToDate, | ||
createdOnLatestVersion, | ||
reservedRoleMappingsSynced, | ||
migrationsVersion, | ||
minClusterMappingVersion, | ||
indexMappingVersion, | ||
|
@@ -323,7 +347,8 @@ public void clusterChanged(ClusterChangedEvent event) { | |
indexUUID, | ||
allSecurityFeatures.stream() | ||
.filter(feature -> featureService.clusterHasFeature(event.state(), feature)) | ||
.collect(Collectors.toSet()) | ||
.collect(Collectors.toSet()), | ||
reservedStateRoleMappingNames | ||
); | ||
this.state = newState; | ||
|
||
|
@@ -334,6 +359,10 @@ public void clusterChanged(ClusterChangedEvent event) { | |
} | ||
} | ||
|
||
public Set<String> getReservedStateRoleMappingNames() { | ||
return state.reservedStateRoleMappingNames; | ||
} | ||
|
||
public static int getMigrationVersionFromIndexMetadata(IndexMetadata indexMetadata) { | ||
Map<String, String> customMetadata = indexMetadata == null ? null : indexMetadata.getCustomData(MIGRATION_VERSION_CUSTOM_KEY); | ||
if (customMetadata == null) { | ||
|
@@ -438,7 +467,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) { | ||
|
@@ -671,13 +701,15 @@ public static class State { | |
false, | ||
false, | ||
false, | ||
false, | ||
null, | ||
null, | ||
null, | ||
null, | ||
null, | ||
null, | ||
null, | ||
Set.of(), | ||
Set.of() | ||
); | ||
public final Instant creationTime; | ||
|
@@ -686,6 +718,7 @@ public static class State { | |
public final boolean indexAvailableForWrite; | ||
public final boolean mappingUpToDate; | ||
public final boolean createdOnLatestVersion; | ||
public final boolean reservedRoleMappingsSynced; | ||
public final Integer migrationsVersion; | ||
// Min mapping version supported by the descriptors in the cluster | ||
public final SystemIndexDescriptor.MappingsVersion minClusterMappingVersion; | ||
|
@@ -696,6 +729,7 @@ public static class State { | |
public final IndexMetadata.State indexState; | ||
public final String indexUUID; | ||
public final Set<NodeFeature> securityFeatures; | ||
public final Set<String> reservedStateRoleMappingNames; | ||
|
||
public State( | ||
Instant creationTime, | ||
|
@@ -704,14 +738,16 @@ public State( | |
boolean indexAvailableForWrite, | ||
boolean mappingUpToDate, | ||
boolean createdOnLatestVersion, | ||
boolean reservedRoleMappingsSynced, | ||
Integer migrationsVersion, | ||
SystemIndexDescriptor.MappingsVersion minClusterMappingVersion, | ||
Integer indexMappingVersion, | ||
String concreteIndexName, | ||
ClusterHealthStatus indexHealth, | ||
IndexMetadata.State indexState, | ||
String indexUUID, | ||
Set<NodeFeature> securityFeatures | ||
Set<NodeFeature> securityFeatures, | ||
Set<String> reservedStateRoleMappingNames | ||
) { | ||
this.creationTime = creationTime; | ||
this.isIndexUpToDate = isIndexUpToDate; | ||
|
@@ -720,13 +756,15 @@ public State( | |
this.mappingUpToDate = mappingUpToDate; | ||
this.migrationsVersion = migrationsVersion; | ||
this.createdOnLatestVersion = createdOnLatestVersion; | ||
this.reservedRoleMappingsSynced = reservedRoleMappingsSynced; | ||
this.minClusterMappingVersion = minClusterMappingVersion; | ||
this.indexMappingVersion = indexMappingVersion; | ||
this.concreteIndexName = concreteIndexName; | ||
this.indexHealth = indexHealth; | ||
this.indexState = indexState; | ||
this.indexUUID = indexUUID; | ||
this.securityFeatures = securityFeatures; | ||
this.reservedStateRoleMappingNames = reservedStateRoleMappingNames; | ||
} | ||
|
||
@Override | ||
|
@@ -740,13 +778,15 @@ public boolean equals(Object o) { | |
&& indexAvailableForWrite == state.indexAvailableForWrite | ||
&& mappingUpToDate == state.mappingUpToDate | ||
&& createdOnLatestVersion == state.createdOnLatestVersion | ||
&& reservedRoleMappingsSynced == state.reservedRoleMappingsSynced | ||
&& Objects.equals(indexMappingVersion, state.indexMappingVersion) | ||
&& Objects.equals(migrationsVersion, state.migrationsVersion) | ||
&& Objects.equals(minClusterMappingVersion, state.minClusterMappingVersion) | ||
&& Objects.equals(concreteIndexName, state.concreteIndexName) | ||
&& indexHealth == state.indexHealth | ||
&& indexState == state.indexState | ||
&& Objects.equals(securityFeatures, state.securityFeatures); | ||
&& Objects.equals(securityFeatures, state.securityFeatures) | ||
&& Objects.equals(reservedStateRoleMappingNames, state.reservedStateRoleMappingNames); | ||
} | ||
|
||
public boolean indexExists() { | ||
|
@@ -762,12 +802,14 @@ public int hashCode() { | |
indexAvailableForWrite, | ||
mappingUpToDate, | ||
createdOnLatestVersion, | ||
reservedRoleMappingsSynced, | ||
migrationsVersion, | ||
minClusterMappingVersion, | ||
indexMappingVersion, | ||
concreteIndexName, | ||
indexHealth, | ||
securityFeatures | ||
securityFeatures, | ||
reservedStateRoleMappingNames | ||
); | ||
} | ||
|
||
|
@@ -786,6 +828,8 @@ public String toString() { | |
+ mappingUpToDate | ||
+ ", createdOnLatestVersion=" | ||
+ createdOnLatestVersion | ||
+ ", reservedRoleMappingsSynced=" | ||
+ reservedRoleMappingsSynced | ||
+ ", migrationsVersion=" | ||
+ migrationsVersion | ||
+ ", minClusterMappingVersion=" | ||
|
@@ -804,6 +848,8 @@ public String toString() { | |
+ '\'' | ||
+ ", securityFeatures=" | ||
+ securityFeatures | ||
+ ", reservedStateRoleMappingNames=" | ||
+ reservedStateRoleMappingNames | ||
+ '}'; | ||
} | ||
} | ||
|
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.
This is needed to allow us to check if an index was created on the same version as the migration. This means that the index was created with all the settings/mappings/docs needed, so we don't need to run the migration (it can be short-circuit and skipped since there is nothing to migrate).