Additional settings for SAML NameID policy (elastic/x-pack-elasticsearch#3969)
* Additional settings for SAML NameID policy We should not be populating SPNameQualifier by default as it is intended to be used to specify an alternate SP EntityID rather than our own. Some IdPs (ADFS) fail when presented with this value. This commit - makes the SPNameQualifier a setting that defaults to blank - adds a setting for "AllowCreate" - documents the above Original commit: elastic/x-pack-elasticsearch@093557e88f
This commit is contained in:
parent
64653e525a
commit
c4582cdcd0
|
@ -93,6 +93,12 @@ for SAML realms.
|
|||
to authenticate the current user.
|
||||
Defaults to requesting _transient_ names
|
||||
(`urn:oasis:names:tc:SAML:2.0:nameid-format:transient`)
|
||||
| `nameid.allow_create` | no | The value of the `AllowCreate` attribute of the `NameIdPolicy`
|
||||
element in an authentication request.
|
||||
Defaults to `false`
|
||||
| `nameid.sp_qualifier` | no | The value of the `SPNameQualifier` attribute of the `NameIdPolicy`
|
||||
element in an authentication request.
|
||||
The default is to not include the `SPNameQualifier` attribute.
|
||||
| `force_authn` | no | Whether to set the `ForceAuthn` attribute when requesting that the
|
||||
IdP authenticate the current user. If this is set to `true`, the
|
||||
IdP will be required to freshly establish the user's identity,
|
||||
|
|
|
@ -39,6 +39,10 @@ public class SamlRealmSettings {
|
|||
|
||||
public static final Setting<String> NAMEID_FORMAT = new Setting<>("nameid_format", s -> TRANSIENT_NAMEID_FORMAT, Function.identity(),
|
||||
Setting.Property.NodeScope);
|
||||
public static final Setting<Boolean> NAMEID_ALLOW_CREATE = Setting.boolSetting("nameid.allow_create", false,
|
||||
Setting.Property.NodeScope);
|
||||
public static final Setting<String> NAMEID_SP_QUALIFIER = Setting.simpleString("nameid.sp_qualifier", Setting.Property.NodeScope);
|
||||
|
||||
public static final Setting<Boolean> FORCE_AUTHN = Setting.boolSetting("force_authn", false, Setting.Property.NodeScope);
|
||||
public static final Setting<Boolean> POPULATE_USER_METADATA = Setting.boolSetting("populate_user_metadata", true,
|
||||
Setting.Property.NodeScope);
|
||||
|
@ -73,7 +77,7 @@ public class SamlRealmSettings {
|
|||
public static Set<Setting<?>> getSettings() {
|
||||
final Set<Setting<?>> set = Sets.newHashSet(IDP_ENTITY_ID, IDP_METADATA_PATH,
|
||||
SP_ENTITY_ID, SP_ACS, SP_LOGOUT,
|
||||
NAMEID_FORMAT, FORCE_AUTHN,
|
||||
NAMEID_FORMAT, NAMEID_ALLOW_CREATE, NAMEID_SP_QUALIFIER, FORCE_AUTHN,
|
||||
CLOCK_SKEW,
|
||||
ENCRYPTION_KEY_ALIAS, SIGNING_KEY_ALIAS, SIGNING_MESSAGE_TYPES);
|
||||
set.addAll(ENCRYPTION_SETTINGS.getAllSettings());
|
||||
|
|
|
@ -5,17 +5,16 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.authc.saml;
|
||||
|
||||
import java.time.Clock;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||
import org.opensaml.saml.saml2.core.Issuer;
|
||||
import org.opensaml.saml.saml2.core.NameID;
|
||||
import org.opensaml.saml.saml2.core.NameIDPolicy;
|
||||
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
|
||||
|
||||
import java.time.Clock;
|
||||
|
||||
/**
|
||||
* Generates a SAML {@link AuthnRequest} from a simplified set of parameters.
|
||||
*/
|
||||
|
@ -23,18 +22,14 @@ class SamlAuthnRequestBuilder extends SamlMessageBuilder {
|
|||
|
||||
private final String spBinding;
|
||||
private final String idpBinding;
|
||||
private String nameIdFormat;
|
||||
private Boolean forceAuthn;
|
||||
private NameIDPolicySettings nameIdSettings;
|
||||
|
||||
SamlAuthnRequestBuilder(SpConfiguration spConfig, String spBinding, EntityDescriptor idpDescriptor, String idBinding, Clock clock) {
|
||||
super(idpDescriptor, spConfig, clock);
|
||||
this.spBinding = spBinding;
|
||||
this.idpBinding = idBinding;
|
||||
}
|
||||
|
||||
SamlAuthnRequestBuilder nameIdFormat(String nameIdFormat) {
|
||||
this.nameIdFormat = nameIdFormat;
|
||||
return this;
|
||||
this.nameIdSettings = new NameIDPolicySettings(NameID.TRANSIENT, false, null);
|
||||
}
|
||||
|
||||
SamlAuthnRequestBuilder forceAuthn(Boolean forceAuthn) {
|
||||
|
@ -42,6 +37,11 @@ class SamlAuthnRequestBuilder extends SamlMessageBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
SamlAuthnRequestBuilder nameIDPolicy(NameIDPolicySettings settings) {
|
||||
this.nameIdSettings = settings;
|
||||
return this;
|
||||
}
|
||||
|
||||
AuthnRequest build() {
|
||||
final String destination = getIdpLocation();
|
||||
|
||||
|
@ -52,16 +52,18 @@ class SamlAuthnRequestBuilder extends SamlMessageBuilder {
|
|||
request.setProtocolBinding(spBinding);
|
||||
request.setAssertionConsumerServiceURL(serviceProvider.getAscUrl());
|
||||
request.setIssuer(buildIssuer());
|
||||
if (nameIdSettings != null) {
|
||||
request.setNameIDPolicy(buildNameIDPolicy());
|
||||
}
|
||||
request.setForceAuthn(forceAuthn);
|
||||
return request;
|
||||
}
|
||||
|
||||
private NameIDPolicy buildNameIDPolicy() {
|
||||
NameIDPolicy nameIDPolicy = SamlUtils.buildObject(NameIDPolicy.class, NameIDPolicy.DEFAULT_ELEMENT_NAME);
|
||||
nameIDPolicy.setFormat(nameIdFormat);
|
||||
nameIDPolicy.setSPNameQualifier(serviceProvider.getEntityId());
|
||||
nameIDPolicy.setAllowCreate(false);
|
||||
nameIDPolicy.setFormat(nameIdSettings.format);
|
||||
nameIDPolicy.setAllowCreate(nameIdSettings.allowCreate);
|
||||
nameIDPolicy.setSPNameQualifier(Strings.isNullOrEmpty(nameIdSettings.spNameQualifier) ? null : nameIdSettings.spNameQualifier);
|
||||
return nameIDPolicy;
|
||||
}
|
||||
|
||||
|
@ -74,4 +76,16 @@ class SamlAuthnRequestBuilder extends SamlMessageBuilder {
|
|||
return location;
|
||||
}
|
||||
|
||||
static class NameIDPolicySettings {
|
||||
private final String format;
|
||||
private final boolean allowCreate;
|
||||
private final String spNameQualifier;
|
||||
|
||||
NameIDPolicySettings(String format, boolean allowCreate, String spNameQualifier) {
|
||||
this.format = format;
|
||||
this.allowCreate = allowCreate;
|
||||
this.spNameQualifier = spNameQualifier;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ public class SamlMetadataCommand extends EnvironmentAwareCommand {
|
|||
.encryptionCredential(spConfig.getEncryptionCredential())
|
||||
.signingCredential(spConfig.getSigningConfiguration().getCredential())
|
||||
.authnRequestsSigned(spConfig.getSigningConfiguration().shouldSign(AuthnRequest.DEFAULT_ELEMENT_LOCAL_NAME))
|
||||
.nameIdFormat(require(realm, SamlRealmSettings.NAMEID_FORMAT))
|
||||
.nameIdFormat(SamlRealmSettings.NAMEID_FORMAT.get(realm.settings()))
|
||||
.serviceName(option(serviceNameSpec, options, env.settings().get("cluster.name")));
|
||||
|
||||
Map<String, String> attributes = getAttributeNames(options, realm);
|
||||
|
|
|
@ -76,7 +76,6 @@ import org.opensaml.xmlsec.keyinfo.impl.provider.InlineX509DataProvider;
|
|||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.X509KeyManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.AccessController;
|
||||
|
@ -108,7 +107,9 @@ import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings
|
|||
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.IDP_METADATA_PATH;
|
||||
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.IDP_SINGLE_LOGOUT;
|
||||
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.MAIL_ATTRIBUTE;
|
||||
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.NAMEID_ALLOW_CREATE;
|
||||
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.NAMEID_FORMAT;
|
||||
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.NAMEID_SP_QUALIFIER;
|
||||
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;
|
||||
|
@ -149,7 +150,7 @@ public final class SamlRealm extends Realm implements Releasable {
|
|||
private final Supplier<EntityDescriptor> idpDescriptor;
|
||||
|
||||
private final SpConfiguration serviceProvider;
|
||||
private final String nameIdFormat;
|
||||
private final SamlAuthnRequestBuilder.NameIDPolicySettings nameIdPolicy;
|
||||
private final Boolean forceAuthn;
|
||||
private final boolean useSingleLogout;
|
||||
private final Boolean populateUserMetadata;
|
||||
|
@ -210,7 +211,8 @@ public final class SamlRealm extends Realm implements Releasable {
|
|||
this.idpDescriptor = idpDescriptor;
|
||||
this.serviceProvider = spConfiguration;
|
||||
|
||||
this.nameIdFormat = require(config, NAMEID_FORMAT);
|
||||
this.nameIdPolicy = new SamlAuthnRequestBuilder.NameIDPolicySettings(require(config, NAMEID_FORMAT),
|
||||
NAMEID_ALLOW_CREATE.get(config.settings()), NAMEID_SP_QUALIFIER.get(config.settings()));
|
||||
this.forceAuthn = FORCE_AUTHN.exists(config.settings()) ? FORCE_AUTHN.get(config.settings()) : null;
|
||||
this.useSingleLogout = IDP_SINGLE_LOGOUT.get(config.settings());
|
||||
this.populateUserMetadata = POPULATE_USER_METADATA.get(config.settings());
|
||||
|
@ -570,8 +572,8 @@ public final class SamlRealm extends Realm implements Releasable {
|
|||
idpDescriptor.get(),
|
||||
SAMLConstants.SAML2_REDIRECT_BINDING_URI,
|
||||
Clock.systemUTC())
|
||||
.nameIdFormat(nameIdFormat)
|
||||
.forceAuthn(this.forceAuthn)
|
||||
.nameIDPolicy(nameIdPolicy)
|
||||
.forceAuthn(forceAuthn)
|
||||
.build();
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Constructed SAML Authentication Request: {}", SamlUtils.samlObjectToString(authnRequest));
|
||||
|
|
|
@ -5,16 +5,6 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.authc.saml;
|
||||
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.collect.MapBuilder;
|
||||
import org.opensaml.saml.common.xml.SAMLConstants;
|
||||
|
@ -60,6 +50,16 @@ import org.opensaml.xmlsec.keyinfo.KeyInfoSupport;
|
|||
import org.opensaml.xmlsec.signature.KeyInfo;
|
||||
import org.opensaml.xmlsec.signature.impl.KeyInfoBuilder;
|
||||
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Constructs SAML Metadata to describe a <em>Service Provider</em>.
|
||||
* This metadata is used to configure Identity Providers that will interact with the Service Provider.
|
||||
|
@ -217,7 +217,9 @@ public class SamlSpMetadataBuilder {
|
|||
spRoleDescriptor.setWantAssertionsSigned(true);
|
||||
spRoleDescriptor.setAuthnRequestsSigned(this.authnRequestsSigned);
|
||||
|
||||
if (Strings.hasLength(nameIdFormat)) {
|
||||
spRoleDescriptor.getNameIDFormats().add(buildNameIdFormat());
|
||||
}
|
||||
spRoleDescriptor.getAssertionConsumerServices().add(buildAssertionConsumerService());
|
||||
if (attributeNames.size() > 0) {
|
||||
spRoleDescriptor.getAttributeConsumingServices().add(buildAttributeConsumerService());
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.opensaml.saml.saml2.metadata.SingleSignOnService;
|
|||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
||||
public class SamlAuthnRequestBuilderTests extends SamlTestCase {
|
||||
|
||||
|
@ -51,7 +52,7 @@ public class SamlAuthnRequestBuilderTests extends SamlTestCase {
|
|||
sp, SAMLConstants.SAML2_POST_BINDING_URI,
|
||||
idpDescriptor, SAMLConstants.SAML2_REDIRECT_BINDING_URI,
|
||||
Clock.systemUTC());
|
||||
builder.nameIdFormat(NameID.PERSISTENT);
|
||||
builder.nameIDPolicy(new SamlAuthnRequestBuilder.NameIDPolicySettings(NameID.PERSISTENT, false, SP_ENTITY_ID));
|
||||
builder.forceAuthn(null);
|
||||
|
||||
final AuthnRequest request = buildAndValidateAuthnRequest(builder);
|
||||
|
@ -75,7 +76,9 @@ public class SamlAuthnRequestBuilderTests extends SamlTestCase {
|
|||
sp, SAMLConstants.SAML2_POST_BINDING_URI,
|
||||
idpDescriptor, SAMLConstants.SAML2_REDIRECT_BINDING_URI,
|
||||
Clock.systemUTC());
|
||||
builder.nameIdFormat(NameID.TRANSIENT);
|
||||
|
||||
final String noSpNameQualifier = randomBoolean() ? "" : null;
|
||||
builder.nameIDPolicy(new SamlAuthnRequestBuilder.NameIDPolicySettings(NameID.TRANSIENT, true, noSpNameQualifier));
|
||||
builder.forceAuthn(Boolean.TRUE);
|
||||
|
||||
final AuthnRequest request = buildAndValidateAuthnRequest(builder);
|
||||
|
@ -87,8 +90,8 @@ public class SamlAuthnRequestBuilderTests extends SamlTestCase {
|
|||
|
||||
assertThat(request.getNameIDPolicy(), notNullValue());
|
||||
assertThat(request.getNameIDPolicy().getFormat(), equalTo(NameID.TRANSIENT));
|
||||
assertThat(request.getNameIDPolicy().getSPNameQualifier(), equalTo(SP_ENTITY_ID));
|
||||
assertThat(request.getNameIDPolicy().getAllowCreate(), equalTo(Boolean.FALSE));
|
||||
assertThat(request.getNameIDPolicy().getSPNameQualifier(), nullValue());
|
||||
assertThat(request.getNameIDPolicy().getAllowCreate(), equalTo(Boolean.TRUE));
|
||||
|
||||
assertThat(request.isForceAuthn(), equalTo(Boolean.TRUE));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue