Skip to content

Commit

Permalink
Integrates OpenSAML 4.3 with SAML authenticator (opensearch-project#3651
Browse files Browse the repository at this point in the history
)

In order to exclude `permission
org.opensearch.secure_sm.ThreadPermission "modifyArbitraryThread";` we
must use the dedicated `cleaners` thread factory in `OpenSearch`. Since
`OpenSAML` packages are sealed it it impossible to replace
`CleanerSupport` class with our own solution. Instead we have to change
configuration of how such objects should be parsed. There are only 2
classes in the library which use `CleanerSupport`:
 - `X509CertificateImpl`
 - `X509CRLImpl` 

This fix uses the same solution as in `OpenSAML` and replaces
`CleanerSupport` with our custom implementation `CleanerFactory`.


Signed-off-by: Andrey Pleskach <[email protected]>
  • Loading branch information
willyborankin authored Nov 7, 2023
1 parent f6f561e commit 44a03c5
Show file tree
Hide file tree
Showing 9 changed files with 386 additions and 38 deletions.
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) {
// 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 {

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

0 comments on commit 44a03c5

Please sign in to comment.