Skip to content

Commit

Permalink
Add IDP Configuration settings and related tests (#51682)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkakavas committed Feb 3, 2020
1 parent aa9e541 commit 207dce8
Show file tree
Hide file tree
Showing 17 changed files with 583 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment;
Expand All @@ -20,9 +21,15 @@
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.core.ssl.X509KeyPairSettings;
import org.elasticsearch.xpack.idp.saml.idp.CloudIdp;
import org.elasticsearch.xpack.idp.saml.support.SamlInit;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
Expand All @@ -31,7 +38,38 @@
*/
public class IdentityProviderPlugin extends Plugin implements ActionPlugin {

private final Setting<Boolean> ENABLED_SETTING = Setting.boolSetting("xpack.idp.enabled", false, Setting.Property.NodeScope);
private static final Setting<Boolean> ENABLED_SETTING = Setting.boolSetting("xpack.idp.enabled", false, Setting.Property.NodeScope);
public static final Setting<String> IDP_ENTITY_ID = Setting.simpleString("xpack.idp.entity_id", Setting.Property.NodeScope);
public static final Setting<String> IDP_SSO_REDIRECT_ENDPOINT = Setting.simpleString("xpack.idp.sso_endpoint.redirect", value -> {
try {
new URI(value);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid value [" + value + "] for [xpack.idp.sso_endpoint.redirect]. Not a valid URI", e);
}
}, Setting.Property.NodeScope);
public static final Setting<String> IDP_SSO_POST_ENDPOINT = Setting.simpleString("xpack.idp.sso_endpoint.post", value -> {
try {
new URI(value);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid value [" + value + "] for [xpack.idp.sso_endpoint.post]. Not a valid URI", e);
}
}, Setting.Property.NodeScope);
public static final Setting<String> IDP_SLO_REDIRECT_ENDPOINT = Setting.simpleString("xpack.idp.slo_endpoint.redirect", value -> {
try {
new URI(value);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid value [" + value + "] for [xpack.idp.slo_endpoint.redirect]. Not a valid URI", e);
}
}, Setting.Property.NodeScope);
public static final Setting<String> IDP_SLO_POST_ENDPOINT = Setting.simpleString("xpack.idp.slo_endpoint.post", value -> {
try {
new URI(value);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid value [" + value + "] for [xpack.idp.slo_endpoint.post]. Not a valid URI", e);
}
}, Setting.Property.NodeScope);
public static final Setting<String> IDP_SIGNING_KEY_ALIAS = Setting.simpleString("xpack.idp.signing.keystore.alias",
Setting.Property.NodeScope);

private final Logger logger = LogManager.getLogger();
private boolean enabled;
Expand All @@ -41,12 +79,27 @@ public Collection<Object> createComponents(Client client, ClusterService cluster
ResourceWatcherService resourceWatcherService, ScriptService scriptService,
NamedXContentRegistry xContentRegistry, Environment environment,
NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry) {
enabled = ENABLED_SETTING.get(environment.settings());
final Settings settings = environment.settings();
enabled = ENABLED_SETTING.get(settings);
if (enabled == false) {
return List.of();
}

SamlInit.initialize();
CloudIdp idp = new CloudIdp(environment, settings);
return List.of();
}

@Override
public List<Setting<?>> getSettings() {
List<Setting<?>> settings = new ArrayList<>();
settings.add(ENABLED_SETTING);
settings.add(IDP_ENTITY_ID);
settings.add(IDP_SLO_REDIRECT_ENDPOINT);
settings.add(IDP_SLO_POST_ENDPOINT);
settings.add(IDP_SSO_REDIRECT_ENDPOINT);
settings.add(IDP_SSO_POST_ENDPOINT);
settings.addAll(X509KeyPairSettings.withPrefix("xpack.idp.signing.", false).getAllSettings());
return Collections.unmodifiableList(settings);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.idp.saml.idp;

import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.common.util.iterable.Iterables;
import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.core.ssl.CertParsingUtils;
import org.elasticsearch.xpack.core.ssl.X509KeyPairSettings;
import org.opensaml.security.x509.X509Credential;
import org.opensaml.security.x509.impl.X509KeyManagerX509CredentialAdapter;

import javax.net.ssl.X509KeyManager;
import java.security.PrivateKey;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import static org.elasticsearch.xpack.idp.IdentityProviderPlugin.IDP_ENTITY_ID;
import static org.elasticsearch.xpack.idp.IdentityProviderPlugin.IDP_SIGNING_KEY_ALIAS;
import static org.elasticsearch.xpack.idp.IdentityProviderPlugin.IDP_SLO_POST_ENDPOINT;
import static org.elasticsearch.xpack.idp.IdentityProviderPlugin.IDP_SLO_REDIRECT_ENDPOINT;
import static org.elasticsearch.xpack.idp.IdentityProviderPlugin.IDP_SSO_POST_ENDPOINT;
import static org.elasticsearch.xpack.idp.IdentityProviderPlugin.IDP_SSO_REDIRECT_ENDPOINT;

public class CloudIdp implements SamlIdentityProvider {

private final String entityId;
private final HashMap<String, String> ssoEndpoints = new HashMap<>();
private final HashMap<String, String> sloEndpoints = new HashMap<>();
private final X509Credential signingCredential;

public CloudIdp(Environment env, Settings settings) {
this.entityId = require(settings, IDP_ENTITY_ID);
this.ssoEndpoints.put("redirect", require(settings, IDP_SSO_REDIRECT_ENDPOINT));
if (settings.hasValue(IDP_SSO_POST_ENDPOINT.getKey())) {
this.ssoEndpoints.put("post", settings.get(IDP_SSO_POST_ENDPOINT.getKey()));
}
if (settings.hasValue(IDP_SLO_POST_ENDPOINT.getKey())) {
this.sloEndpoints.put("post", settings.get(IDP_SLO_POST_ENDPOINT.getKey()));
}
if (settings.hasValue(IDP_SLO_REDIRECT_ENDPOINT.getKey())) {
this.sloEndpoints.put("redirect", settings.get(IDP_SLO_REDIRECT_ENDPOINT.getKey()));
}
this.signingCredential = buildSigningCredential(env, settings);
}

@Override
public String getEntityId() {
return entityId;
}

@Override
public String getSingleSignOnEndpoint(String binding) {
return ssoEndpoints.get(binding);
}

@Override
public String getSingleLogoutEndpoint(String binding) {
return sloEndpoints.get(binding);
}

@Override
public X509Credential getSigningCredential() {
return signingCredential;
}

private static String require(Settings settings, Setting<String> setting) {
if (settings.hasValue(setting.getKey())) {
return setting.get(settings);
} else {
throw new SettingsException("The configuration setting [" + setting.getKey() + "] is required");
}
}

static X509Credential buildSigningCredential(Environment env, Settings settings) {
final X509KeyPairSettings keyPairSettings = X509KeyPairSettings.withPrefix("xpack.idp.signing.", false);
final X509KeyManager keyManager = CertParsingUtils.getKeyManager(keyPairSettings, settings, null, env);
if (keyManager == null) {
return null;
}

final String selectedAlias;
final String configAlias = IDP_SIGNING_KEY_ALIAS.get(settings);
if (Strings.isNullOrEmpty(configAlias)) {
final Set<String> aliases = new HashSet<>();
final String[] rsaAliases = keyManager.getServerAliases("RSA", null);
if (null != rsaAliases) {
aliases.addAll(Arrays.asList(rsaAliases));
}
final String[] ecAliases = keyManager.getServerAliases("EC", null);
if (null != ecAliases) {
aliases.addAll(Arrays.asList(ecAliases));
}
if (aliases.isEmpty()) {
throw new IllegalArgumentException(
"The configured keystore for xpack.idp.signing.keystore does not contain any RSA or EC key pairs");
}
if (aliases.size() > 1) {
throw new IllegalArgumentException("The configured keystore for xpack.idp.signing.keystore contains multiple key pairs" +
" but no alias has been configured with [" + IDP_SIGNING_KEY_ALIAS.getKey() + "]");
}
selectedAlias = Iterables.get(aliases, 0);
} else {
selectedAlias = configAlias;
}
final PrivateKey signingKey = keyManager.getPrivateKey(selectedAlias);
if (signingKey == null) {
throw new IllegalArgumentException("The configured keystore for xpack.idp.signing.keystore does not have a private key" +
" associated with alias [" + selectedAlias + "]");
}

final String keyType = signingKey.getAlgorithm();
if (keyType.equals("RSA") == false && keyType.equals("EC") == false) {
throw new IllegalArgumentException("The key associated with alias [" + selectedAlias + "] " + "that has been configured with ["
+ IDP_SIGNING_KEY_ALIAS.getKey() + "] uses unsupported key algorithm type [" + keyType
+ "], only RSA and EC are supported");
}
return new X509KeyManagerX509CredentialAdapter(keyManager, selectedAlias);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@
package org.elasticsearch.xpack.idp.saml.idp;


import org.opensaml.security.x509.X509Credential;

/**
* SAML 2.0 configuration information about this IdP
*/
public interface SamlIdentityProvider {

String getEntityId();

String getSingleSignOnEndpoint(String binding);

String getSingleLogoutEndpoint(String binding);

X509Credential getSigningCredential();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ grant {
// needed because of SAML (cf. o.e.x.c.s.s.RestorableContextClassLoader)
permission java.lang.RuntimePermission "getClassLoader";
permission java.lang.RuntimePermission "setContextClassLoader";

// needed during initialization of OpenSAML library where xml security algorithms are registered
// see https://github.com/apache/santuario-java/blob/e79f1fe4192de73a975bc7246aee58ed0703343d/src/main/java/org/apache/xml/security/utils/JavaUtils.java#L205-L220
// and https://git.shibboleth.net/view/?p=java-opensaml.git;a=blob;f=opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/signature/impl/SignatureMarshaller.java;hb=db0eaa64210f0e32d359cd6c57bedd57902bf811#l52
Expand Down
Loading

0 comments on commit 207dce8

Please sign in to comment.