Skip to content

Commit 05b52de

Browse files
authored
Use a licensed feature per realm-type (+custom) (#79121)
This commit changes the licensed feature usage tracking for realms to record each realm type as its own separate feature. Custom realms continue to fall under a single catch-all feature. Backport of: #78810
1 parent 6045345 commit 05b52de

File tree

7 files changed

+257
-116
lines changed

7 files changed

+257
-116
lines changed

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -351,13 +351,25 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin,
351351
public static final LicensedFeature.Momentary AUDITING_FEATURE =
352352
LicensedFeature.momentaryLenient(null, "security_auditing", License.OperationMode.GOLD);
353353

354+
private static final String REALMS_FEATURE_FAMILY = "security-realms";
354355
// Builtin realms (file/native) realms are Basic licensed, so don't need to be checked or tracked
355-
// Standard realms (LDAP, AD, PKI, etc) are Gold+
356+
// Some realms (LDAP, AD, PKI) are Gold+
357+
public static final LicensedFeature.Persistent LDAP_REALM_FEATURE =
358+
LicensedFeature.persistentLenient(REALMS_FEATURE_FAMILY, "ldap", License.OperationMode.GOLD);
359+
public static final LicensedFeature.Persistent AD_REALM_FEATURE =
360+
LicensedFeature.persistentLenient(REALMS_FEATURE_FAMILY, "active-directory", License.OperationMode.GOLD);
361+
public static final LicensedFeature.Persistent PKI_REALM_FEATURE =
362+
LicensedFeature.persistentLenient(REALMS_FEATURE_FAMILY, "pki", License.OperationMode.GOLD);
356363
// SSO realms are Platinum+
357-
public static final LicensedFeature.Persistent STANDARD_REALMS_FEATURE =
358-
LicensedFeature.persistentLenient(null, "security_standard_realms", License.OperationMode.GOLD);
359-
public static final LicensedFeature.Persistent ALL_REALMS_FEATURE =
360-
LicensedFeature.persistentLenient(null, "security_all_realms", License.OperationMode.PLATINUM);
364+
public static final LicensedFeature.Persistent SAML_REALM_FEATURE =
365+
LicensedFeature.persistentLenient(REALMS_FEATURE_FAMILY, "saml", License.OperationMode.PLATINUM);
366+
public static final LicensedFeature.Persistent OIDC_REALM_FEATURE =
367+
LicensedFeature.persistentLenient(REALMS_FEATURE_FAMILY, "oidc", License.OperationMode.PLATINUM);
368+
public static final LicensedFeature.Persistent KERBEROS_REALM_FEATURE =
369+
LicensedFeature.persistentLenient(REALMS_FEATURE_FAMILY, "kerberos", License.OperationMode.PLATINUM);
370+
// Custom realms are Platinum+
371+
public static final LicensedFeature.Persistent CUSTOM_REALMS_FEATURE =
372+
LicensedFeature.persistentLenient(REALMS_FEATURE_FAMILY, "custom", License.OperationMode.PLATINUM);
361373

362374
private static final Logger logger = LogManager.getLogger(Security.class);
363375

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

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
package org.elasticsearch.xpack.security.authc;
88

99
import org.elasticsearch.bootstrap.BootstrapCheck;
10+
import org.elasticsearch.common.Strings;
1011
import org.elasticsearch.common.settings.Settings;
1112
import org.elasticsearch.common.util.set.Sets;
13+
import org.elasticsearch.core.Nullable;
1214
import org.elasticsearch.env.Environment;
15+
import org.elasticsearch.license.LicensedFeature;
1316
import org.elasticsearch.threadpool.ThreadPool;
1417
import org.elasticsearch.watcher.ResourceWatcherService;
1518
import org.elasticsearch.xpack.core.security.authc.Realm;
@@ -23,6 +26,7 @@
2326
import org.elasticsearch.xpack.core.security.authc.pki.PkiRealmSettings;
2427
import org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings;
2528
import org.elasticsearch.xpack.core.ssl.SSLService;
29+
import org.elasticsearch.xpack.security.Security;
2630
import org.elasticsearch.xpack.security.authc.esnative.NativeRealm;
2731
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
2832
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
@@ -53,36 +57,64 @@
5357
*/
5458
public final class InternalRealms {
5559

60+
static final String RESERVED_TYPE = ReservedRealm.TYPE;
61+
static final String NATIVE_TYPE = NativeRealmSettings.TYPE;
62+
static final String FILE_TYPE = FileRealmSettings.TYPE;
63+
static final String LDAP_TYPE = LdapRealmSettings.LDAP_TYPE;
64+
static final String AD_TYPE = LdapRealmSettings.AD_TYPE;
65+
static final String PKI_TYPE = PkiRealmSettings.TYPE;
66+
static final String SAML_TYPE = SamlRealmSettings.TYPE;
67+
static final String OIDC_TYPE = OpenIdConnectRealmSettings.TYPE;
68+
static final String KERBEROS_TYPE = KerberosRealmSettings.TYPE;
69+
70+
private static final Set<String> BUILTIN_TYPES = Sets.newHashSet(NATIVE_TYPE, FILE_TYPE);
71+
5672
/**
57-
* The list of all <em>internal</em> realm types, excluding {@link ReservedRealm#TYPE}.
73+
* The map of all <em>licensed</em> internal realm types to their licensed feature
5874
*/
59-
private static final Set<String> XPACK_TYPES = Collections
60-
.unmodifiableSet(Sets.newHashSet(NativeRealmSettings.TYPE, FileRealmSettings.TYPE, LdapRealmSettings.AD_TYPE,
61-
LdapRealmSettings.LDAP_TYPE, PkiRealmSettings.TYPE, SamlRealmSettings.TYPE, KerberosRealmSettings.TYPE,
62-
OpenIdConnectRealmSettings.TYPE));
75+
private static final Map<String, LicensedFeature.Persistent> LICENSED_REALMS = org.elasticsearch.core.Map.ofEntries(
76+
org.elasticsearch.core.Map.entry(AD_TYPE, Security.AD_REALM_FEATURE),
77+
org.elasticsearch.core.Map.entry(LDAP_TYPE, Security.LDAP_REALM_FEATURE),
78+
org.elasticsearch.core.Map.entry(PKI_TYPE, Security.PKI_REALM_FEATURE),
79+
org.elasticsearch.core.Map.entry(SAML_TYPE, Security.SAML_REALM_FEATURE),
80+
org.elasticsearch.core.Map.entry(KERBEROS_TYPE, Security.KERBEROS_REALM_FEATURE),
81+
org.elasticsearch.core.Map.entry(OIDC_TYPE, Security.OIDC_REALM_FEATURE)
82+
);
6383

6484
/**
65-
* The list of all standard realm types, which are those provided by x-pack and do not have extensive
66-
* interaction with third party sources
85+
* The set of all <em>internal</em> realm types, excluding {@link ReservedRealm#TYPE}
86+
* @deprecated Use of this method (other than in tests) is discouraged.
6787
*/
68-
private static final Set<String> STANDARD_TYPES = Collections.unmodifiableSet(Sets.newHashSet(NativeRealmSettings.TYPE,
69-
FileRealmSettings.TYPE, LdapRealmSettings.AD_TYPE, LdapRealmSettings.LDAP_TYPE, PkiRealmSettings.TYPE));
70-
88+
@Deprecated
7189
public static Collection<String> getConfigurableRealmsTypes() {
72-
return Collections.unmodifiableSet(XPACK_TYPES);
90+
return org.elasticsearch.core.Set.copyOf(Sets.union(BUILTIN_TYPES, LICENSED_REALMS.keySet()));
7391
}
7492

75-
/**
76-
* Determines whether <code>type</code> is an internal realm-type that is provided by x-pack,
77-
* excluding the {@link ReservedRealm} and realms that have extensive interaction with
78-
* third party sources
79-
*/
80-
static boolean isStandardRealm(String type) {
81-
return STANDARD_TYPES.contains(type);
93+
static boolean isInternalRealm(String type) {
94+
return RESERVED_TYPE.equals(type) || BUILTIN_TYPES.contains(type) || LICENSED_REALMS.containsKey(type);
8295
}
8396

8497
static boolean isBuiltinRealm(String type) {
85-
return FileRealmSettings.TYPE.equals(type) || NativeRealmSettings.TYPE.equals(type);
98+
return BUILTIN_TYPES.contains(type);
99+
}
100+
101+
/**
102+
* @return The licensed feature for the given realm type, or {@code null} if the realm does not require a specific license type
103+
* @throws IllegalArgumentException if the provided type is not an {@link #isInternalRealm(String) internal realm}
104+
*/
105+
@Nullable
106+
static LicensedFeature.Persistent getLicensedFeature(String type) {
107+
if (Strings.isNullOrEmpty(type)) {
108+
throw new IllegalArgumentException("Empty realm type [" + type + "]");
109+
}
110+
if (type.equals(RESERVED_TYPE) || isBuiltinRealm(type)) {
111+
return null;
112+
}
113+
final LicensedFeature.Persistent feature = LICENSED_REALMS.get(type);
114+
if (feature == null) {
115+
throw new IllegalArgumentException("Unsupported realm type [" + type + "]");
116+
}
117+
return feature;
86118
}
87119

88120
/**

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

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
import org.elasticsearch.common.util.concurrent.CountDown;
1818
import org.elasticsearch.common.util.concurrent.ThreadContext;
1919
import org.elasticsearch.common.util.set.Sets;
20+
import org.elasticsearch.core.Nullable;
2021
import org.elasticsearch.env.Environment;
22+
import org.elasticsearch.license.LicensedFeature;
2123
import org.elasticsearch.license.XPackLicenseState;
2224
import org.elasticsearch.xpack.core.XPackSettings;
2325
import org.elasticsearch.xpack.core.security.authc.Realm;
@@ -127,18 +129,25 @@ protected void recomputeActiveRealms() {
127129
Strings.collectionToCommaDelimitedString(licensedRealms)
128130
);
129131

132+
stopTrackingInactiveRealms(licenseStateSnapshot, licensedRealms);
133+
134+
activeRealms = licensedRealms;
135+
}
136+
137+
// Can be overridden in testing
138+
protected void stopTrackingInactiveRealms(XPackLicenseState licenseStateSnapshot, List<Realm> licensedRealms) {
130139
// Stop license-tracking for any previously-active realms that are no longer allowed
131140
if (activeRealms != null) {
132141
activeRealms.stream().filter(r -> licensedRealms.contains(r) == false).forEach(realm -> {
133-
if (InternalRealms.isStandardRealm(realm.type())) {
134-
Security.STANDARD_REALMS_FEATURE.stopTracking(licenseStateSnapshot, realm.name());
135-
} else {
136-
Security.ALL_REALMS_FEATURE.stopTracking(licenseStateSnapshot, realm.name());
137-
}
142+
final LicensedFeature.Persistent feature = getLicensedFeatureForRealm(realm.type());
143+
assert feature != null : "Realm ["
144+
+ realm
145+
+ "] with no licensed feature became inactive due to change to license mode ["
146+
+ licenseStateSnapshot.getOperationMode()
147+
+ "]";
148+
feature.stopTracking(licenseStateSnapshot, realm.name());
138149
});
139150
}
140-
141-
activeRealms = licensedRealms;
142151
}
143152

144153
@Override
@@ -194,27 +203,29 @@ private boolean hasUserRealm(List<Realm> licensedRealms) {
194203
}
195204

196205
private static boolean checkLicense(Realm realm, XPackLicenseState licenseState) {
197-
if (isBasicLicensedRealm(realm.type())) {
206+
final LicensedFeature.Persistent feature = getLicensedFeatureForRealm(realm.type());
207+
if (feature == null) {
198208
return true;
199209
}
200-
if (InternalRealms.isStandardRealm(realm.type())) {
201-
return Security.STANDARD_REALMS_FEATURE.checkAndStartTracking(licenseState, realm.name());
202-
}
203-
return Security.ALL_REALMS_FEATURE.checkAndStartTracking(licenseState, realm.name());
210+
return feature.checkAndStartTracking(licenseState, realm.name());
204211
}
205212

206213
public static boolean isRealmTypeAvailable(XPackLicenseState licenseState, String type) {
207-
if (Security.ALL_REALMS_FEATURE.checkWithoutTracking(licenseState)) {
214+
final LicensedFeature.Persistent feature = getLicensedFeatureForRealm(type);
215+
if (feature == null) {
208216
return true;
209-
} else if (Security.STANDARD_REALMS_FEATURE.checkWithoutTracking(licenseState)) {
210-
return InternalRealms.isStandardRealm(type) || ReservedRealm.TYPE.equals(type);
211-
} else {
212-
return isBasicLicensedRealm(type);
213217
}
218+
return feature.checkWithoutTracking(licenseState);
214219
}
215220

216-
private static boolean isBasicLicensedRealm(String type) {
217-
return ReservedRealm.TYPE.equals(type) || InternalRealms.isBuiltinRealm(type);
221+
@Nullable
222+
private static LicensedFeature.Persistent getLicensedFeatureForRealm(String realmType) {
223+
assert Strings.hasText(realmType) : "Realm type must be provided (received [" + realmType + "])";
224+
if (InternalRealms.isInternalRealm(realmType)) {
225+
return InternalRealms.getLicensedFeature(realmType);
226+
} else {
227+
return Security.CUSTOM_REALMS_FEATURE;
228+
}
218229
}
219230

220231
public Realm realm(String name) {

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/RestDelegatePkiAuthenticationAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ protected Exception checkFeatureAvailable(RestRequest request) {
5656
Exception failedFeature = super.checkFeatureAvailable(request);
5757
if (failedFeature != null) {
5858
return failedFeature;
59-
} else if (Security.STANDARD_REALMS_FEATURE.checkWithoutTracking(licenseState)) {
59+
} else if (Security.PKI_REALM_FEATURE.checkWithoutTracking(licenseState)) {
6060
return null;
6161
} else {
6262
logger.info("The '{}' realm is not available under the current license", PkiRealmSettings.TYPE);

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.elasticsearch.index.seqno.SequenceNumbers;
5353
import org.elasticsearch.index.shard.ShardId;
5454
import org.elasticsearch.license.License;
55+
import org.elasticsearch.license.LicensedFeature;
5556
import org.elasticsearch.license.MockLicenseState;
5657
import org.elasticsearch.license.XPackLicenseState;
5758
import org.elasticsearch.license.XPackLicenseState.Feature;
@@ -218,9 +219,14 @@ public void init() throws Exception {
218219
.put(XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.getKey(), true)
219220
.build();
220221
MockLicenseState licenseState = mock(MockLicenseState.class);
221-
when(licenseState.isAllowed(Security.ALL_REALMS_FEATURE)).thenReturn(true);
222222
when(licenseState.isSecurityEnabled()).thenReturn(true);
223-
when(licenseState.isAllowed(Security.STANDARD_REALMS_FEATURE)).thenReturn(true);
223+
for (String realmType : InternalRealms.getConfigurableRealmsTypes()) {
224+
final LicensedFeature.Persistent feature = InternalRealms.getLicensedFeature(realmType);
225+
if (feature != null) {
226+
when(licenseState.isAllowed(feature)).thenReturn(true);
227+
}
228+
}
229+
when(licenseState.isAllowed(Security.CUSTOM_REALMS_FEATURE)).thenReturn(true);
224230
when(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE)).thenReturn(true);
225231
when(licenseState.copyCurrentLicenseState()).thenReturn(licenseState);
226232
when(licenseState.checkFeature(Feature.SECURITY_AUDITING)).thenReturn(true);
@@ -229,9 +235,10 @@ public void init() throws Exception {
229235
ReservedRealm reservedRealm = mock(ReservedRealm.class);
230236
when(reservedRealm.type()).thenReturn("reserved");
231237
when(reservedRealm.name()).thenReturn("reserved_realm");
232-
realms = spy(new TestRealms(Settings.EMPTY, TestEnvironment.newEnvironment(settings), Collections.<String, Realm.Factory>emptyMap(),
233-
licenseState, threadContext, reservedRealm, Arrays.asList(firstRealm, secondRealm),
234-
Collections.singletonList(firstRealm)));
238+
realms = spy(new TestRealms(Settings.EMPTY, TestEnvironment.newEnvironment(settings),
239+
Collections.<String, Realm.Factory>emptyMap(),
240+
licenseState, threadContext, reservedRealm, Arrays.asList(firstRealm, secondRealm),
241+
Arrays.asList(firstRealm)));
235242

236243
// Needed because this is calculated in the constructor, which means the override doesn't get called correctly
237244
realms.recomputeActiveRealms();
@@ -428,6 +435,7 @@ public void testAuthenticateBothSupportSecondSucceeds() throws Exception {
428435
verify(realms, atLeastOnce()).recomputeActiveRealms();
429436
verify(realms, atLeastOnce()).calculateLicensedRealms(any(XPackLicenseState.class));
430437
verify(realms, atLeastOnce()).getActiveRealms();
438+
verify(realms, atLeastOnce()).stopTrackingInactiveRealms(any(XPackLicenseState.class), any());
431439
// ^^ We don't care how many times these methods are called, we just check it here so that we can verify no more interactions below.
432440
verifyNoMoreInteractions(realms);
433441
}
@@ -2171,7 +2179,9 @@ protected List<Realm> calculateLicensedRealms(XPackLicenseState licenseState) {
21712179
// This can happen because the realms are recalculated during construction
21722180
return super.calculateLicensedRealms(licenseState);
21732181
}
2174-
if (Security.STANDARD_REALMS_FEATURE.checkWithoutTracking(licenseState)) {
2182+
2183+
// Use custom as a placeholder for all non-internal realm
2184+
if (Security.CUSTOM_REALMS_FEATURE.checkWithoutTracking(licenseState)) {
21752185
return allRealms;
21762186
} else {
21772187
return internalRealms;
@@ -2182,6 +2192,11 @@ protected List<Realm> calculateLicensedRealms(XPackLicenseState licenseState) {
21822192
public void recomputeActiveRealms() {
21832193
super.recomputeActiveRealms();
21842194
}
2195+
2196+
@Override
2197+
protected void stopTrackingInactiveRealms(XPackLicenseState licenseStateSnapshot, List<Realm> licensedRealms) {
2198+
// Ignore
2199+
}
21852200
}
21862201

21872202
private void logAndFail(Exception e) {

0 commit comments

Comments
 (0)