Skip to content

Commit d36b152

Browse files
committed
Add security migration for cleaning up ECK role mappings
1 parent bdbd15e commit d36b152

File tree

17 files changed

+394
-44
lines changed

17 files changed

+394
-44
lines changed

server/src/main/java/org/elasticsearch/index/IndexVersions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ private static IndexVersion def(int id, Version luceneVersion) {
117117
public static final IndexVersion ENABLE_IGNORE_MALFORMED_LOGSDB = def(8_514_00_0, Version.LUCENE_9_11_1);
118118
public static final IndexVersion MERGE_ON_RECOVERY_VERSION = def(8_515_00_0, Version.LUCENE_9_11_1);
119119
public static final IndexVersion UPGRADE_TO_LUCENE_9_12 = def(8_516_00_0, Version.LUCENE_9_12_0);
120+
public static final IndexVersion ADD_ROLE_MAPPING_MIGRATION = def(8_517_00_0, Version.LUCENE_9_12_0);
120121

121122
/*
122123
* STOP! READ THIS FIRST! No, really,

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatures.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MIGRATION_FRAMEWORK;
1818
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_PROFILE_ORIGIN_FEATURE;
1919
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_ROLES_METADATA_FLATTENED;
20+
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_ROLE_MAPPING_CLEANUP;
2021
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.VERSION_SECURITY_PROFILE_ORIGIN;
2122

2223
public class SecurityFeatures implements FeatureSpecification {
2324

2425
@Override
2526
public Set<NodeFeature> getFeatures() {
26-
return Set.of(SECURITY_ROLES_METADATA_FLATTENED, SECURITY_MIGRATION_FRAMEWORK);
27+
return Set.of(SECURITY_ROLE_MAPPING_CLEANUP, SECURITY_ROLES_METADATA_FLATTENED, SECURITY_MIGRATION_FRAMEWORK);
2728
}
2829

2930
@Override

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityIndexManager.java

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.elasticsearch.cluster.metadata.IndexMetadata;
3232
import org.elasticsearch.cluster.metadata.MappingMetadata;
3333
import org.elasticsearch.cluster.metadata.Metadata;
34+
import org.elasticsearch.cluster.metadata.ReservedStateMetadata;
3435
import org.elasticsearch.cluster.routing.IndexRoutingTable;
3536
import org.elasticsearch.cluster.service.ClusterService;
3637
import org.elasticsearch.core.TimeValue;
@@ -46,9 +47,12 @@
4647
import org.elasticsearch.rest.RestStatus;
4748
import org.elasticsearch.threadpool.Scheduler;
4849
import org.elasticsearch.xcontent.XContentType;
50+
import org.elasticsearch.xpack.core.security.authz.RoleMappingMetadata;
4951
import org.elasticsearch.xpack.security.SecurityFeatures;
52+
import org.elasticsearch.xpack.security.action.rolemapping.ReservedRoleMappingAction;
5053

5154
import java.time.Instant;
55+
import java.util.Arrays;
5256
import java.util.List;
5357
import java.util.Map;
5458
import java.util.Objects;
@@ -74,7 +78,8 @@
7478
public class SecurityIndexManager implements ClusterStateListener {
7579

7680
public static final String SECURITY_VERSION_STRING = "security-version";
77-
81+
private static final String FILE_SETTINGS_METADATA_NAMESPACE = "file_settings";
82+
private static final String HANDLER_ROLE_MAPPINGS_NAME = "role_mappings";
7883
private static final Logger logger = LogManager.getLogger(SecurityIndexManager.class);
7984

8085
/**
@@ -267,6 +272,11 @@ private static boolean isCreatedOnLatestVersion(IndexMetadata indexMetadata) {
267272
return indexVersionCreated != null && indexVersionCreated.onOrAfter(IndexVersion.current());
268273
}
269274

275+
private static Set<String> getFileSettingsMetadataHandlerRoleMappingKeys(ClusterState clusterState) {
276+
ReservedStateMetadata fileSettingsMetadata = clusterState.metadata().reservedStateMetadata().get(FILE_SETTINGS_METADATA_NAMESPACE);
277+
return fileSettingsMetadata.handlers().get(HANDLER_ROLE_MAPPINGS_NAME).keys();
278+
}
279+
270280
@Override
271281
public void clusterChanged(ClusterChangedEvent event) {
272282
if (event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
@@ -284,6 +294,10 @@ public void clusterChanged(ClusterChangedEvent event) {
284294
Tuple<Boolean, Boolean> available = checkIndexAvailable(event.state());
285295
final boolean indexAvailableForWrite = available.v1();
286296
final boolean indexAvailableForSearch = available.v2();
297+
final Set<String> reservedStateRoleMappingNames = getFileSettingsMetadataHandlerRoleMappingKeys(event.state());
298+
final boolean reservedRoleMappingsSynced = reservedStateRoleMappingNames.size() == RoleMappingMetadata.getFromClusterState(
299+
event.state()
300+
).getRoleMappings().size();
287301
final boolean mappingIsUpToDate = indexMetadata == null || checkIndexMappingUpToDate(event.state());
288302
final int migrationsVersion = getMigrationVersionFromIndexMetadata(indexMetadata);
289303
final SystemIndexDescriptor.MappingsVersion minClusterMappingVersion = getMinSecurityIndexMappingVersion(event.state());
@@ -314,6 +328,7 @@ public void clusterChanged(ClusterChangedEvent event) {
314328
indexAvailableForWrite,
315329
mappingIsUpToDate,
316330
createdOnLatestVersion,
331+
reservedRoleMappingsSynced,
317332
migrationsVersion,
318333
minClusterMappingVersion,
319334
indexMappingVersion,
@@ -323,7 +338,8 @@ public void clusterChanged(ClusterChangedEvent event) {
323338
indexUUID,
324339
allSecurityFeatures.stream()
325340
.filter(feature -> featureService.clusterHasFeature(event.state(), feature))
326-
.collect(Collectors.toSet())
341+
.collect(Collectors.toSet()),
342+
reservedStateRoleMappingNames
327343
);
328344
this.state = newState;
329345

@@ -334,6 +350,10 @@ public void clusterChanged(ClusterChangedEvent event) {
334350
}
335351
}
336352

353+
public Set<String> getReservedStateRoleMappingNames() {
354+
return state.reservedStateRoleMappingNames;
355+
}
356+
337357
public static int getMigrationVersionFromIndexMetadata(IndexMetadata indexMetadata) {
338358
Map<String, String> customMetadata = indexMetadata == null ? null : indexMetadata.getCustomData(MIGRATION_VERSION_CUSTOM_KEY);
339359
if (customMetadata == null) {
@@ -438,7 +458,8 @@ private Tuple<Boolean, Boolean> checkIndexAvailable(ClusterState state) {
438458

439459
public boolean isEligibleSecurityMigration(SecurityMigrations.SecurityMigration securityMigration) {
440460
return state.securityFeatures.containsAll(securityMigration.nodeFeaturesRequired())
441-
&& state.indexMappingVersion >= securityMigration.minMappingVersion();
461+
&& state.indexMappingVersion >= securityMigration.minMappingVersion()
462+
&& securityMigration.checkPreConditions(state);
442463
}
443464

444465
public boolean isReadyForSecurityMigration(SecurityMigrations.SecurityMigration securityMigration) {
@@ -671,13 +692,15 @@ public static class State {
671692
false,
672693
false,
673694
false,
695+
false,
674696
null,
675697
null,
676698
null,
677699
null,
678700
null,
679701
null,
680702
null,
703+
Set.of(),
681704
Set.of()
682705
);
683706
public final Instant creationTime;
@@ -686,6 +709,7 @@ public static class State {
686709
public final boolean indexAvailableForWrite;
687710
public final boolean mappingUpToDate;
688711
public final boolean createdOnLatestVersion;
712+
public final boolean reservedRoleMappingsSynced;
689713
public final Integer migrationsVersion;
690714
// Min mapping version supported by the descriptors in the cluster
691715
public final SystemIndexDescriptor.MappingsVersion minClusterMappingVersion;
@@ -696,6 +720,7 @@ public static class State {
696720
public final IndexMetadata.State indexState;
697721
public final String indexUUID;
698722
public final Set<NodeFeature> securityFeatures;
723+
public final Set<String> reservedStateRoleMappingNames;
699724

700725
public State(
701726
Instant creationTime,
@@ -704,14 +729,16 @@ public State(
704729
boolean indexAvailableForWrite,
705730
boolean mappingUpToDate,
706731
boolean createdOnLatestVersion,
732+
boolean reservedRoleMappingsSynced,
707733
Integer migrationsVersion,
708734
SystemIndexDescriptor.MappingsVersion minClusterMappingVersion,
709735
Integer indexMappingVersion,
710736
String concreteIndexName,
711737
ClusterHealthStatus indexHealth,
712738
IndexMetadata.State indexState,
713739
String indexUUID,
714-
Set<NodeFeature> securityFeatures
740+
Set<NodeFeature> securityFeatures,
741+
Set<String> reservedStateRoleMappingNames
715742
) {
716743
this.creationTime = creationTime;
717744
this.isIndexUpToDate = isIndexUpToDate;
@@ -720,13 +747,15 @@ public State(
720747
this.mappingUpToDate = mappingUpToDate;
721748
this.migrationsVersion = migrationsVersion;
722749
this.createdOnLatestVersion = createdOnLatestVersion;
750+
this.reservedRoleMappingsSynced = reservedRoleMappingsSynced;
723751
this.minClusterMappingVersion = minClusterMappingVersion;
724752
this.indexMappingVersion = indexMappingVersion;
725753
this.concreteIndexName = concreteIndexName;
726754
this.indexHealth = indexHealth;
727755
this.indexState = indexState;
728756
this.indexUUID = indexUUID;
729757
this.securityFeatures = securityFeatures;
758+
this.reservedStateRoleMappingNames = reservedStateRoleMappingNames;
730759
}
731760

732761
@Override
@@ -740,13 +769,15 @@ public boolean equals(Object o) {
740769
&& indexAvailableForWrite == state.indexAvailableForWrite
741770
&& mappingUpToDate == state.mappingUpToDate
742771
&& createdOnLatestVersion == state.createdOnLatestVersion
772+
&& reservedRoleMappingsSynced == state.reservedRoleMappingsSynced
743773
&& Objects.equals(indexMappingVersion, state.indexMappingVersion)
744774
&& Objects.equals(migrationsVersion, state.migrationsVersion)
745775
&& Objects.equals(minClusterMappingVersion, state.minClusterMappingVersion)
746776
&& Objects.equals(concreteIndexName, state.concreteIndexName)
747777
&& indexHealth == state.indexHealth
748778
&& indexState == state.indexState
749-
&& Objects.equals(securityFeatures, state.securityFeatures);
779+
&& Objects.equals(securityFeatures, state.securityFeatures)
780+
&& Objects.equals(reservedStateRoleMappingNames, state.reservedStateRoleMappingNames);
750781
}
751782

752783
public boolean indexExists() {
@@ -762,12 +793,14 @@ public int hashCode() {
762793
indexAvailableForWrite,
763794
mappingUpToDate,
764795
createdOnLatestVersion,
796+
reservedRoleMappingsSynced,
765797
migrationsVersion,
766798
minClusterMappingVersion,
767799
indexMappingVersion,
768800
concreteIndexName,
769801
indexHealth,
770-
securityFeatures
802+
securityFeatures,
803+
reservedStateRoleMappingNames
771804
);
772805
}
773806

@@ -786,6 +819,8 @@ public String toString() {
786819
+ mappingUpToDate
787820
+ ", createdOnLatestVersion="
788821
+ createdOnLatestVersion
822+
+ ", reservedRoleMappingsSynced="
823+
+ reservedRoleMappingsSynced
789824
+ ", migrationsVersion="
790825
+ migrationsVersion
791826
+ ", minClusterMappingVersion="
@@ -804,6 +839,9 @@ public String toString() {
804839
+ '\''
805840
+ ", securityFeatures="
806841
+ securityFeatures
842+
+ ", reservedStateRoleMappingNames=["
843+
+ Arrays.toString(reservedStateRoleMappingNames.toArray(String[]::new))
844+
+ "]"
807845
+ '}';
808846
}
809847
}

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityMigrationExecutor.java

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import org.apache.logging.log4j.LogManager;
1111
import org.apache.logging.log4j.Logger;
1212
import org.elasticsearch.action.ActionListener;
13+
import org.elasticsearch.action.admin.indices.refresh.RefreshAction;
14+
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
1315
import org.elasticsearch.action.support.ThreadedActionListener;
1416
import org.elasticsearch.client.internal.Client;
1517
import org.elasticsearch.core.TimeValue;
@@ -24,6 +26,9 @@
2426
import java.util.TreeMap;
2527
import java.util.concurrent.Executor;
2628

29+
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN;
30+
import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
31+
2732
public class SecurityMigrationExecutor extends PersistentTasksExecutor<SecurityMigrationTaskParams> {
2833

2934
private static final Logger logger = LogManager.getLogger(SecurityMigrationExecutor.class);
@@ -83,13 +88,17 @@ private void applyOutstandingMigrations(AllocatedPersistentTask task, int curren
8388
response -> updateMigrationVersion(
8489
migrationEntry.getKey(),
8590
securityIndexManager.getConcreteIndexName(),
86-
new ThreadedActionListener<>(
87-
this.getExecutor(),
88-
ActionListener.wrap(
89-
updateResponse -> applyOutstandingMigrations(task, migrationEntry.getKey(), listener),
90-
listener::onFailure
91-
)
92-
)
91+
new ThreadedActionListener<>(this.getExecutor(), ActionListener.wrap(updateResponse -> {
92+
refreshSecurityIndex(
93+
new ThreadedActionListener<>(
94+
this.getExecutor(),
95+
ActionListener.wrap(
96+
refreshResponse -> applyOutstandingMigrations(task, migrationEntry.getKey(), listener),
97+
listener::onFailure
98+
)
99+
)
100+
);
101+
}, listener::onFailure))
93102
),
94103
listener::onFailure
95104
)
@@ -100,6 +109,21 @@ private void applyOutstandingMigrations(AllocatedPersistentTask task, int curren
100109
}
101110
}
102111

112+
/**
113+
* Refresh security index to make sure that docs that were migrated are visible to the next migration and to prevent version conflicts
114+
* or unexpected behaviour by APIs relying on migrated docs.
115+
*/
116+
private void refreshSecurityIndex(ActionListener<Void> listener) {
117+
RefreshRequest refreshRequest = new RefreshRequest(securityIndexManager.getConcreteIndexName());
118+
executeAsyncWithOrigin(
119+
client,
120+
SECURITY_ORIGIN,
121+
RefreshAction.INSTANCE,
122+
refreshRequest,
123+
ActionListener.wrap(response -> listener.onResponse(null), listener::onFailure)
124+
);
125+
}
126+
103127
private void updateMigrationVersion(int migrationVersion, String indexName, ActionListener<Void> listener) {
104128
client.execute(
105129
UpdateIndexMigrationVersionAction.INSTANCE,

0 commit comments

Comments
 (0)