Skip to content

Commit

Permalink
Make SAML SP Config Proivder Plugable
Browse files Browse the repository at this point in the history
  • Loading branch information
jfreden committed Nov 21, 2024
1 parent a333957 commit 4f68105
Show file tree
Hide file tree
Showing 24 changed files with 486 additions and 278 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import org.elasticsearch.xpack.core.security.authc.ldap.LdapRealmSettings;
import org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings;
import org.elasticsearch.xpack.core.security.authc.pki.PkiRealmSettings;
import org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings;
import org.elasticsearch.xpack.core.security.authc.saml.SingleSpSamlRealmSettings;

import java.util.Collections;
import java.util.HashSet;
Expand All @@ -34,7 +34,7 @@ public static Set<Setting.AffixSetting<?>> getSettings() {
set.addAll(LdapRealmSettings.getSettings(LdapRealmSettings.AD_TYPE));
set.addAll(LdapRealmSettings.getSettings(LdapRealmSettings.LDAP_TYPE));
set.addAll(PkiRealmSettings.getSettings());
set.addAll(SamlRealmSettings.getSettings());
set.addAll(SingleSpSamlRealmSettings.getSettings());
set.addAll(KerberosRealmSettings.getSettings());
set.addAll(OpenIdConnectRealmSettings.getSettings());
set.addAll(JwtRealmSettings.getSettings());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.elasticsearch.core.Tuple;
import org.elasticsearch.xpack.core.security.authc.esnative.NativeRealmSettings;
import org.elasticsearch.xpack.core.security.authc.file.FileRealmSettings;
import org.elasticsearch.xpack.core.security.authc.saml.SingleSpSamlRealmSettings;

import java.util.Arrays;
import java.util.HashMap;
Expand Down Expand Up @@ -243,6 +244,10 @@ private static void verifyRealmSettings(RealmConfig.RealmIdentifier identifier,
}
}

public static <T> String getFullSettingKey(String realmName, Function<String, Setting.AffixSetting<T>> setting) {
return setting.apply(SingleSpSamlRealmSettings.TYPE).getConcreteSettingForNamespace(realmName).getKey();
}

public static String getFullSettingKey(String realmName, Setting.AffixSetting<?> setting) {
return setting.getConcreteSettingForNamespace(realmName).getKey();
}
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.security.authc.saml;

import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.xpack.core.security.authc.RealmSettings;

import java.util.Set;

public class SingleSpSamlRealmSettings {
public static final String TYPE = "saml";

public static final Setting.AffixSetting<String> SP_ENTITY_ID = RealmSettings.simpleString(
TYPE,
"sp.entity_id",
Setting.Property.NodeScope
);

public static final Setting.AffixSetting<String> SP_ACS = RealmSettings.simpleString(TYPE, "sp.acs", Setting.Property.NodeScope);
public static final Setting.AffixSetting<String> SP_LOGOUT = RealmSettings.simpleString(TYPE, "sp.logout", Setting.Property.NodeScope);

public static Set<Setting.AffixSetting<?>> getSettings() {
Set<Setting.AffixSetting<?>> samlSettings = SamlRealmSettings.getSettings(TYPE);
samlSettings.add(SP_ENTITY_ID);
samlSettings.add(SP_ACS);
samlSettings.add(SP_LOGOUT);

return samlSettings;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import org.elasticsearch.xpack.core.security.authc.ldap.LdapRealmSettings;
import org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings;
import org.elasticsearch.xpack.core.security.authc.pki.PkiRealmSettings;
import org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings;
import org.elasticsearch.xpack.core.security.authc.saml.SingleSpSamlRealmSettings;
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountSettings;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptorsIntersection;
Expand Down Expand Up @@ -193,7 +193,7 @@ private static Supplier<String> randomRealmTypeSupplier(boolean includeInternal)
LdapRealmSettings.LDAP_TYPE,
JwtRealmSettings.TYPE,
OpenIdConnectRealmSettings.TYPE,
SamlRealmSettings.TYPE,
SingleSpSamlRealmSettings.TYPE,
KerberosRealmSettings.TYPE,
PkiRealmSettings.TYPE,
ESTestCase.randomAlphaOfLengthBetween(3, 8)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import org.elasticsearch.xpack.core.security.authc.ldap.LdapRealmSettings;
import org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings;
import org.elasticsearch.xpack.core.security.authc.pki.PkiRealmSettings;
import org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings;
import org.elasticsearch.xpack.core.security.authc.saml.SingleSpSamlRealmSettings;
import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper;
import org.elasticsearch.xpack.core.ssl.SSLService;
import org.elasticsearch.xpack.security.Security;
Expand All @@ -39,6 +39,7 @@
import org.elasticsearch.xpack.security.authc.oidc.OpenIdConnectRealm;
import org.elasticsearch.xpack.security.authc.pki.PkiRealm;
import org.elasticsearch.xpack.security.authc.saml.SamlRealm;
import org.elasticsearch.xpack.security.authc.saml.SingleSamlSpConfiguration;
import org.elasticsearch.xpack.security.authc.support.RoleMappingFileBootstrapCheck;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;

Expand All @@ -63,7 +64,7 @@ public final class InternalRealms {
static final String LDAP_TYPE = LdapRealmSettings.LDAP_TYPE;
static final String AD_TYPE = LdapRealmSettings.AD_TYPE;
static final String PKI_TYPE = PkiRealmSettings.TYPE;
static final String SAML_TYPE = SamlRealmSettings.TYPE;
static final String SAML_TYPE = SingleSpSamlRealmSettings.TYPE;
static final String OIDC_TYPE = OpenIdConnectRealmSettings.TYPE;
static final String JWT_TYPE = JwtRealmSettings.TYPE;
static final String KERBEROS_TYPE = KerberosRealmSettings.TYPE;
Expand Down Expand Up @@ -154,8 +155,14 @@ public static Map<String, Realm.Factory> getFactories(
PkiRealmSettings.TYPE,
config -> new PkiRealm(config, resourceWatcherService, userRoleMapper),
// SAML realm
SamlRealmSettings.TYPE,
config -> SamlRealm.create(config, sslService, resourceWatcherService, userRoleMapper),
SingleSpSamlRealmSettings.TYPE,
config -> SamlRealm.create(
config,
sslService,
resourceWatcherService,
userRoleMapper,
SingleSamlSpConfiguration.create(config)
),
// Kerberos realm
KerberosRealmSettings.TYPE,
config -> new KerberosRealm(config, userRoleMapper, threadPool),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings;
import org.elasticsearch.xpack.core.security.authc.saml.SingleSpSamlRealmSettings;
import org.elasticsearch.xpack.core.ssl.CertParsingUtils;
import org.elasticsearch.xpack.security.authc.saml.SamlSpMetadataBuilder.ContactInfo;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
Expand Down Expand Up @@ -166,7 +167,7 @@ EntityDescriptor buildEntityDescriptor(Terminal terminal, OptionSet options, Env
final Locale locale = findLocale(options);
terminal.println(Terminal.Verbosity.VERBOSE, "Using locale: " + locale.toLanguageTag());

final SpConfiguration spConfig = SamlRealm.getSpConfiguration(realm);
final SpConfiguration spConfig = SingleSamlSpConfiguration.create(realm);
final SamlSpMetadataBuilder builder = new SamlSpMetadataBuilder(locale, spConfig.getEntityId()).assertionConsumerServiceUrl(
spConfig.getAscUrl()
)
Expand Down Expand Up @@ -455,7 +456,7 @@ private RealmConfig findRealm(Terminal terminal, OptionSet options, Environment
final Map<RealmConfig.RealmIdentifier, Settings> realms = RealmSettings.getRealmSettings(settings);
if (options.has(realmSpec)) {
final String name = realmSpec.value(options);
final RealmConfig.RealmIdentifier identifier = new RealmConfig.RealmIdentifier(SamlRealmSettings.TYPE, name);
final RealmConfig.RealmIdentifier identifier = new RealmConfig.RealmIdentifier(SingleSpSamlRealmSettings.TYPE, name);
final Settings realmSettings = realms.get(identifier);
if (realmSettings == null) {
throw new UserException(ExitCodes.CONFIG, "No such realm '" + name + "' defined in " + env.configFile());
Expand Down Expand Up @@ -500,7 +501,7 @@ private static RealmConfig buildRealm(RealmConfig.RealmIdentifier identifier, En
}

private static boolean isSamlRealm(RealmConfig.RealmIdentifier realmIdentifier) {
return SamlRealmSettings.TYPE.equals(realmIdentifier.getType());
return SingleSpSamlRealmSettings.TYPE.equals(realmIdentifier.getType());
}

private Locale findLocale(OptionSet options) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,9 @@
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.NAME_ATTRIBUTE;
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.POPULATE_USER_METADATA;
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.PRINCIPAL_ATTRIBUTE;
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.REQUESTED_AUTHN_CONTEXT_CLASS_REF;
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.SIGNING_KEY_ALIAS;
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.SIGNING_MESSAGE_TYPES;
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.SIGNING_SETTING_KEY;
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.SP_ACS;
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.SP_ENTITY_ID;
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.SP_LOGOUT;

/**
* This class is {@link Releasable} because it uses a library that thinks timers and timer tasks
Expand All @@ -165,7 +161,6 @@ public final class SamlRealm extends Realm implements Releasable {
// to assume that there's any relationship between SAML realms. However, because all the metadata loading code is in static methods, we
// live with this limitation for now.
private static final AtomicBoolean REFRESHING_METADATA = new AtomicBoolean(false);

private final List<Releasable> releasables;

private final SamlAuthenticator authenticator;
Expand Down Expand Up @@ -198,7 +193,8 @@ public static SamlRealm create(
RealmConfig config,
SSLService sslService,
ResourceWatcherService watcherService,
UserRoleMapper roleMapper
UserRoleMapper roleMapper,
SpConfiguration serviceProvider
) throws Exception {
SamlUtils.initialize(logger);

Expand All @@ -217,8 +213,6 @@ public static SamlRealm create(
final AbstractReloadingMetadataResolver metadataResolver = tuple.v1();
final Supplier<EntityDescriptor> idpDescriptor = tuple.v2();

final SpConfiguration serviceProvider = getSpConfiguration(config);

final Clock clock = Clock.systemUTC();
final IdpConfiguration idpConfiguration = getIdpConfiguration(config, metadataResolver, idpDescriptor);
final TimeValue maxSkew = config.getSetting(CLOCK_SKEW);
Expand Down Expand Up @@ -301,6 +295,10 @@ public void initialize(Iterable<Realm> realms, XPackLicenseState licenseState) {
delegatedRealms = new DelegatedAuthorizationSupport(realms, config, licenseState);
}

static String require(RealmConfig config, Function<String, Setting.AffixSetting<String>> settingFactory) {
return require(config, settingFactory.apply(config.type()));
}

static String require(RealmConfig config, Setting.AffixSetting<String> setting) {
final String value = config.getSetting(setting);
if (value.isEmpty()) {
Expand Down Expand Up @@ -387,27 +385,12 @@ private static void logDiff(final List<Credential> newCredentials, final AtomicR
}
}

static SpConfiguration getSpConfiguration(RealmConfig config) throws IOException, GeneralSecurityException {
final String serviceProviderId = require(config, SP_ENTITY_ID);
final String assertionConsumerServiceURL = require(config, SP_ACS);
final String logoutUrl = config.getSetting(SP_LOGOUT);
final List<String> reqAuthnCtxClassRef = config.getSetting(REQUESTED_AUTHN_CONTEXT_CLASS_REF);
return new SpConfiguration(
serviceProviderId,
assertionConsumerServiceURL,
logoutUrl,
buildSigningConfiguration(config),
buildEncryptionCredential(config),
reqAuthnCtxClassRef
);
}

// Package-private for testing
static List<X509Credential> buildEncryptionCredential(RealmConfig config) throws IOException, GeneralSecurityException {
return buildCredential(
config,
RealmSettings.realmSettingPrefix(config.identifier()) + ENCRYPTION_SETTING_KEY,
ENCRYPTION_KEY_ALIAS,
ENCRYPTION_KEY_ALIAS.apply(config.type()),
true
);
}
Expand All @@ -416,7 +399,7 @@ static SigningConfiguration buildSigningConfiguration(RealmConfig config) throws
final List<X509Credential> credentials = buildCredential(
config,
RealmSettings.realmSettingPrefix(config.identifier()) + SIGNING_SETTING_KEY,
SIGNING_KEY_ALIAS,
SIGNING_KEY_ALIAS.apply(config.type()),
false
);
if (credentials == null || credentials.isEmpty()) {
Expand Down Expand Up @@ -573,7 +556,11 @@ public void authenticate(AuthenticationToken authenticationToken, ActionListener
}

private void buildUser(SamlAttributes attributes, ActionListener<AuthenticationResult<User>> baseListener) {
final String principal = resolveSingleValueAttribute(attributes, principalAttribute, PRINCIPAL_ATTRIBUTE.name(config));
final String principal = resolveSingleValueAttribute(
attributes,
principalAttribute,
PRINCIPAL_ATTRIBUTE.apply(config.type()).name(config)
);
if (Strings.isNullOrEmpty(principal)) {
final String msg = principalAttribute
+ " not found in saml attributes"
Expand Down Expand Up @@ -619,9 +606,9 @@ private void buildUser(SamlAttributes attributes, ActionListener<AuthenticationR
final Map<String, Object> userMeta = Map.copyOf(userMetaBuilder);

final List<String> groups = groupsAttribute.getAttribute(attributes);
final String dn = resolveSingleValueAttribute(attributes, dnAttribute, DN_ATTRIBUTE.name(config));
final String name = resolveSingleValueAttribute(attributes, nameAttribute, NAME_ATTRIBUTE.name(config));
final String mail = resolveSingleValueAttribute(attributes, mailAttribute, MAIL_ATTRIBUTE.name(config));
final String dn = resolveSingleValueAttribute(attributes, dnAttribute, DN_ATTRIBUTE.apply(config.type()).name(config));
final String name = resolveSingleValueAttribute(attributes, nameAttribute, NAME_ATTRIBUTE.apply(config.type()).name(config));
final String mail = resolveSingleValueAttribute(attributes, mailAttribute, MAIL_ATTRIBUTE.apply(config.type()).name(config));
UserRoleMapper.UserData userData = new UserRoleMapper.UserData(principal, dn, groups, userMeta, config);
logger.debug("SAML attribute mapping = [{}]", userData);
roleMapper.resolveRoles(userData, wrappedListener.delegateFailureAndWrap((l, roles) -> {
Expand Down Expand Up @@ -796,10 +783,10 @@ private static Tuple<AbstractReloadingMetadataResolver, Supplier<EntityDescripto
final FilesystemMetadataResolver resolver = new SamlFilesystemMetadataResolver(path.toFile());

for (var httpSetting : List.of(IDP_METADATA_HTTP_REFRESH, IDP_METADATA_HTTP_MIN_REFRESH, IDP_METADATA_HTTP_FAIL_ON_ERROR)) {
if (config.hasSetting(httpSetting)) {
if (config.hasSetting(httpSetting.apply(config.type()))) {
logger.info(
"Ignoring setting [{}] because the IdP metadata is being loaded from a file",
RealmSettings.getFullSettingKey(config, httpSetting)
RealmSettings.getFullSettingKey(config, httpSetting.apply(config.type()))
);
}
}
Expand Down Expand Up @@ -1030,6 +1017,14 @@ public String toString() {
return name;
}

static AttributeParser forSetting(
Logger logger,
Function<String, SamlRealmSettings.AttributeSettingWithDelimiter> settingFactory,
RealmConfig realmConfig
) {
return forSetting(logger, settingFactory.apply(realmConfig.type()), realmConfig);
}

static AttributeParser forSetting(Logger logger, SamlRealmSettings.AttributeSettingWithDelimiter setting, RealmConfig realmConfig) {
SamlRealmSettings.AttributeSetting attributeSetting = setting.getAttributeSetting();
if (realmConfig.hasSetting(setting.getDelimiter())) {
Expand Down Expand Up @@ -1090,6 +1085,15 @@ static AttributeParser forSetting(Logger logger, SamlRealmSettings.AttributeSett
return AttributeParser.forSetting(logger, attributeSetting, realmConfig, false);
}

static AttributeParser forSetting(
Logger logger,
Function<String, SamlRealmSettings.AttributeSetting> settingFactory,
RealmConfig realmConfig,
boolean required
) {
return forSetting(logger, settingFactory.apply(realmConfig.type()), realmConfig, required);
}

static AttributeParser forSetting(
Logger logger,
SamlRealmSettings.AttributeSetting setting,
Expand Down
Loading

0 comments on commit 4f68105

Please sign in to comment.