-
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 19 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: [] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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(); | ||
} | ||
|
||
/** | ||
|
@@ -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(); | ||
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. 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). |
||
} | ||
|
||
/** | ||
|
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); | ||
|
||
/** | ||
|
@@ -267,6 +270,21 @@ private static boolean isCreatedOnLatestVersion(IndexMetadata indexMetadata) { | |
return indexVersionCreated != null && indexVersionCreated.onOrAfter(IndexVersion.current()); | ||
} | ||
|
||
/** | ||
* Check to see if there are any file based role mappings and if they have been loaded into cluster state. If the | ||
* {@link ReservedStateMetadata} file_settings namespace contains role mapping names, it means that there should be the same number of | ||
* cluster state role mappings available. If they're not available yet using {@code RoleMappingMetadata.getFromClusterState()}, they | ||
* have not yet been synchronized. | ||
*/ | ||
private static boolean isReadyForRoleMappingCleanupMigration(ClusterState clusterState) { | ||
n1v0lg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ReservedStateMetadata fileSettingsMetadata = clusterState.metadata().reservedStateMetadata().get(FILE_SETTINGS_METADATA_NAMESPACE); | ||
n1v0lg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
assert fileSettingsMetadata != null || clusterState.metadata().reservedStateMetadata().isEmpty(); | ||
return fileSettingsMetadata == null | ||
|| RoleMappingMetadata.getFromClusterState(clusterState).getRoleMappings().size() == fileSettingsMetadata.keys( | ||
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. I think we also need to check that 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. Yes, added that. Good catch! |
||
ReservedRoleMappingAction.NAME | ||
).size(); | ||
} | ||
|
||
@Override | ||
public void clusterChanged(ClusterChangedEvent event) { | ||
if (event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) { | ||
|
@@ -284,6 +302,7 @@ public void clusterChanged(ClusterChangedEvent event) { | |
Tuple<Boolean, Boolean> available = checkIndexAvailable(event.state()); | ||
final boolean indexAvailableForWrite = available.v1(); | ||
final boolean indexAvailableForSearch = available.v2(); | ||
final boolean readyForRoleMappingCleanupMigration = isReadyForRoleMappingCleanupMigration(event.state()); | ||
final boolean mappingIsUpToDate = indexMetadata == null || checkIndexMappingUpToDate(event.state()); | ||
final int migrationsVersion = getMigrationVersionFromIndexMetadata(indexMetadata); | ||
final SystemIndexDescriptor.MappingsVersion minClusterMappingVersion = getMinSecurityIndexMappingVersion(event.state()); | ||
|
@@ -314,6 +333,7 @@ public void clusterChanged(ClusterChangedEvent event) { | |
indexAvailableForWrite, | ||
mappingIsUpToDate, | ||
createdOnLatestVersion, | ||
readyForRoleMappingCleanupMigration, | ||
migrationsVersion, | ||
minClusterMappingVersion, | ||
indexMappingVersion, | ||
|
@@ -438,7 +458,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,6 +692,7 @@ public static class State { | |
false, | ||
false, | ||
false, | ||
false, | ||
null, | ||
null, | ||
null, | ||
|
@@ -686,6 +708,7 @@ public static class State { | |
public final boolean indexAvailableForWrite; | ||
public final boolean mappingUpToDate; | ||
public final boolean createdOnLatestVersion; | ||
public final boolean readyForRoleMappingCleanupMigration; | ||
public final Integer migrationsVersion; | ||
// Min mapping version supported by the descriptors in the cluster | ||
public final SystemIndexDescriptor.MappingsVersion minClusterMappingVersion; | ||
|
@@ -704,6 +727,7 @@ public State( | |
boolean indexAvailableForWrite, | ||
boolean mappingUpToDate, | ||
boolean createdOnLatestVersion, | ||
boolean readyForRoleMappingCleanupMigration, | ||
Integer migrationsVersion, | ||
SystemIndexDescriptor.MappingsVersion minClusterMappingVersion, | ||
Integer indexMappingVersion, | ||
|
@@ -720,6 +744,7 @@ public State( | |
this.mappingUpToDate = mappingUpToDate; | ||
this.migrationsVersion = migrationsVersion; | ||
this.createdOnLatestVersion = createdOnLatestVersion; | ||
this.readyForRoleMappingCleanupMigration = readyForRoleMappingCleanupMigration; | ||
this.minClusterMappingVersion = minClusterMappingVersion; | ||
this.indexMappingVersion = indexMappingVersion; | ||
this.concreteIndexName = concreteIndexName; | ||
|
@@ -740,6 +765,7 @@ public boolean equals(Object o) { | |
&& indexAvailableForWrite == state.indexAvailableForWrite | ||
&& mappingUpToDate == state.mappingUpToDate | ||
&& createdOnLatestVersion == state.createdOnLatestVersion | ||
&& readyForRoleMappingCleanupMigration == state.readyForRoleMappingCleanupMigration | ||
&& Objects.equals(indexMappingVersion, state.indexMappingVersion) | ||
&& Objects.equals(migrationsVersion, state.migrationsVersion) | ||
&& Objects.equals(minClusterMappingVersion, state.minClusterMappingVersion) | ||
|
@@ -762,6 +788,7 @@ public int hashCode() { | |
indexAvailableForWrite, | ||
mappingUpToDate, | ||
createdOnLatestVersion, | ||
readyForRoleMappingCleanupMigration, | ||
migrationsVersion, | ||
minClusterMappingVersion, | ||
indexMappingVersion, | ||
|
@@ -786,6 +813,8 @@ public String toString() { | |
+ mappingUpToDate | ||
+ ", createdOnLatestVersion=" | ||
+ createdOnLatestVersion | ||
+ ", readyForRoleMappingCleanupMigration=" | ||
+ readyForRoleMappingCleanupMigration | ||
+ ", 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.
I've not yet backported anything with a version like this before... how does it work? I'm assuming 8.16.0, 8.17.0 and 9 will all have diverging constants (with 9 having all, 8.17 fewer, etc)
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.
Good point! We have to be careful when creating the backport PRs.
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.
After discussing with core infra, I opted to go for an index setting, see this commit: 9533f6f
Did a small POC in this PR to test this before: #115666
The benefit with this a approach is that we no longer need to manually bump index versions when adding new migrations and that it's not as heavy weight (we no longer need to bump the index version for all indices).