Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrates OpenSAML 4.3 with SAML authenticator #3651

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion plugin-security.policy
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ grant {

//SAML policy
permission java.util.PropertyPermission "*", "read,write";
permission org.opensearch.secure_sm.ThreadPermission "modifyArbitraryThread";
};

grant codeBase "${codebase.netty-common}" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.security.PrivilegedExceptionAction;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -42,9 +43,11 @@
import org.apache.logging.log4j.Logger;
import org.opensaml.core.config.InitializationException;
import org.opensaml.core.config.InitializationService;
import org.opensaml.core.config.Initializer;
import org.opensaml.saml.metadata.resolver.MetadataResolver;
import org.opensaml.saml.metadata.resolver.impl.AbstractMetadataResolver;
import org.opensaml.saml.metadata.resolver.impl.DOMMetadataResolver;
import org.opensaml.xmlsec.config.impl.XMLObjectProviderInitializer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
Expand All @@ -61,10 +64,11 @@
import org.opensearch.rest.RestRequest;
import org.opensearch.security.auth.Destroyable;
import org.opensearch.security.auth.HTTPAuthenticator;
import org.opensearch.security.filter.OpenSearchRequest;
import org.opensearch.security.filter.SecurityRequest;
import org.opensearch.security.filter.SecurityRequestChannelUnsupported;
import org.opensearch.security.filter.SecurityResponse;
import org.opensearch.security.filter.OpenSearchRequest;
import org.opensearch.security.opensaml.integration.SecurityXMLObjectProviderInitializer;
import org.opensearch.security.support.ConfigConstants;
import org.opensearch.security.support.PemKeyReader;
import org.opensearch.security.user.AuthCredentials;
Expand Down Expand Up @@ -112,12 +116,12 @@ public HTTPSamlAuthenticator(final Settings settings, final Path configPath) {
spSignaturePrivateKey = getSpSignaturePrivateKey(settings, configPath);
useForceAuthn = settings.getAsBoolean("sp.forceAuthn", null);

if (rolesKey == null || rolesKey.length() == 0) {
if (rolesKey == null || rolesKey.isEmpty()) {
log.warn("roles_key is not configured, will only extract subject from SAML");
rolesKey = null;
}

if (subjectKey == null || subjectKey.length() == 0) {
if (subjectKey == null || subjectKey.isEmpty()) {
// If subjectKey == null, get subject from the NameID element.
// Thus, this is a valid configuration.
subjectKey = null;
Expand Down Expand Up @@ -288,35 +292,40 @@ static void ensureOpenSamlInitialization() {
}

try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws InitializationException {

Thread thread = Thread.currentThread();
ClassLoader originalClassLoader = thread.getContextClassLoader();

try {

thread.setContextClassLoader(InitializationService.class.getClassLoader());

InitializationService.initialize();

new org.opensaml.saml.config.impl.XMLObjectProviderInitializer().init();
new org.opensaml.saml.config.impl.SAMLConfigurationInitializer().init();
new org.opensaml.xmlsec.config.impl.XMLObjectProviderInitializer().init();
} finally {
thread.setContextClassLoader(originalClassLoader);
}

openSamlInitialized = true;
return null;
AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
Thread thread = Thread.currentThread();
ClassLoader originalClassLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(InitializationService.class.getClassLoader());
initializeOpenSAMLConfiguration();
} catch (InitializationException e) {
throw new RuntimeException(e.getCause());
} finally {
thread.setContextClassLoader(originalClassLoader);
}

openSamlInitialized = true;
return null;
});
} catch (PrivilegedActionException e) {
throw new RuntimeException(e.getCause());
}
}

private static void initializeOpenSAMLConfiguration() throws InitializationException {
log.info("Initializing OpenSAML using the Java Services API");

final ServiceLoader<Initializer> serviceLoader = ServiceLoader.load(Initializer.class);
for (Initializer initializer : serviceLoader) {
if (initializer instanceof XMLObjectProviderInitializer) {
DarshitChanpura marked this conversation as resolved.
Show resolved Hide resolved
// replace initialization of X509 builders which support Cleaner with our own solution
new SecurityXMLObjectProviderInitializer().init();
} else {
initializer.init();
}
}
}

@SuppressWarnings("removal")
private MetadataResolver createMetadataResolver(final Settings settings, final Path configPath) throws Exception {
final AbstractMetadataResolver metadataResolver;
Expand Down Expand Up @@ -350,12 +359,9 @@ private MetadataResolver createMetadataResolver(final Settings settings, final P
}

try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws ComponentInitializationException {
metadataResolver.initialize();
return null;
}
AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
metadataResolver.initialize();
return null;
});
} catch (PrivilegedActionException e) {
if (e.getCause() instanceof ComponentInitializationException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,7 @@ public class SamlHTTPMetadataResolver extends HTTPMetadataResolver {
@SuppressWarnings("removal")
protected byte[] fetchMetadata() throws ResolverException {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<byte[]>() {
@Override
public byte[] run() throws ResolverException {
return SamlHTTPMetadataResolver.super.fetchMetadata();
}
});
return AccessController.doPrivileged((PrivilegedExceptionAction<byte[]>) () -> SamlHTTPMetadataResolver.super.fetchMetadata());
} catch (PrivilegedActionException e) {

if (e.getCause() instanceof ResolverException) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.security.opensaml.integration;

import org.opensearch.common.util.concurrent.OpenSearchExecutors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.ref.Cleaner;
import java.util.concurrent.ThreadFactory;

/**
* The class was adapted from {@link net.shibboleth.utilities.java.support.primitive.CleanerSupport}.
* The main reason is that it is only one way to set Cleaner.create()
* together with cleaners daemon thread factory which is required for OpenSearch
*/
public class CleanerFactory {

private static final Logger LOG = LoggerFactory.getLogger(CleanerFactory.class);

private static final ThreadFactory cleanersThreadFactory = OpenSearchExecutors.daemonThreadFactory("cleaners");

/** Constructor. */
private CleanerFactory() {}

public static Cleaner create(final Class<?> requester) {
// Current approach here is to create a new Cleaner on each call. A given class requester/owner
// is assumed to call only once and store in static storage.
LOG.debug("Creating new java.lang.ref.Cleaner instance requested by class: {}", requester.getName());
return Cleaner.create(cleanersThreadFactory);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.security.opensaml.integration;

import org.opensaml.xmlsec.signature.X509CRL;
import org.opensaml.xmlsec.signature.impl.X509CRLBuilder;

public class SecurityX509CRLBuilder extends X509CRLBuilder {

public X509CRL buildObject(final String namespaceURI, final String localName, final String namespacePrefix) {
return new SecurityX509CRLImpl(namespaceURI, localName, namespacePrefix);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.security.opensaml.integration;

import net.shibboleth.utilities.java.support.collection.IndexingObjectStore;
import org.opensaml.core.xml.AbstractXMLObject;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.xmlsec.signature.X509CRL;

import javax.annotation.Nonnull;
import java.lang.ref.Cleaner;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
* The class was adapted from {@link org.opensaml.xmlsec.signature.impl.X509CRLImpl}.
* The main reason is that it is only one way to set up {@link CleanerFactory}
* together with cleaners daemon thread factory which is required for OpenSearch
*/
public class SecurityX509CRLImpl extends AbstractXMLObject implements X509CRL {
DarshitChanpura marked this conversation as resolved.
Show resolved Hide resolved

private static final IndexingObjectStore<String> B64_CRL_STORE = new IndexingObjectStore<>();

private static final Cleaner CLEANER = CleanerFactory.create(SecurityX509CRLImpl.class);

private Cleaner.Cleanable cleanable;

private String b64CRLIndex;

protected SecurityX509CRLImpl(final String namespaceURI, final String elementLocalName, final String namespacePrefix) {
super(namespaceURI, elementLocalName, namespacePrefix);
}

public String getValue() {
return B64_CRL_STORE.get(b64CRLIndex);
}

public void setValue(final String newValue) {
// Dump our cached DOM if the new value really is new
final String currentCRL = B64_CRL_STORE.get(b64CRLIndex);
final String newCRL = prepareForAssignment(currentCRL, newValue);

// This is a new value, remove the old one, add the new one
if (!Objects.equals(currentCRL, newCRL)) {
if (cleanable != null) {
cleanable.clean();
cleanable = null;
}
b64CRLIndex = B64_CRL_STORE.put(newCRL);
if (b64CRLIndex != null) {
cleanable = CLEANER.register(this, new SecurityX509CRLImpl.CleanerState(b64CRLIndex));
}
}
}

@Override
public List<XMLObject> getOrderedChildren() {
return Collections.emptyList();
}

static class CleanerState implements Runnable {

/** The index to remove from the store. */
private String index;

public CleanerState(@Nonnull final String idx) {
index = idx;
}

/** {@inheritDoc} */
public void run() {
SecurityX509CRLImpl.B64_CRL_STORE.remove(index);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.security.opensaml.integration;

import org.opensaml.xmlsec.signature.X509Certificate;
import org.opensaml.xmlsec.signature.impl.X509CertificateBuilder;

public class SecurityX509CertificateBuilder extends X509CertificateBuilder {

@Override
public X509Certificate buildObject(final String namespaceURI, final String localName, final String namespacePrefix) {
return new SecurityX509CertificateImpl(namespaceURI, localName, namespacePrefix);
}

}
Loading