Skip to content

Commit

Permalink
OpenSAML 4.3 integration
Browse files Browse the repository at this point in the history
Signed-off-by: Andrey Pleskach <[email protected]>
  • Loading branch information
willyborankin committed Nov 4, 2023
1 parent 3fbeec6 commit 5016037
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 27 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 @@ -23,6 +23,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;

import com.google.common.annotations.VisibleForTesting;
Expand All @@ -36,15 +37,21 @@
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.DestructableComponent;
import net.shibboleth.utilities.java.support.xml.BasicParserPool;
import net.shibboleth.utilities.java.support.xml.QNameSupport;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.http.HttpStatus;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opensaml.core.config.InitializationException;
import org.opensaml.core.config.ConfigurationService;
import org.opensaml.core.config.InitializationService;
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
import org.opensaml.saml.config.impl.SAMLConfigurationInitializer;
import org.opensaml.saml.config.impl.XMLObjectProviderInitializer;
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.opensearch.security.opensaml.integration.SecurityX509CRLBuilder;
import org.opensearch.security.opensaml.integration.SecurityX509CertificateBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
Expand Down Expand Up @@ -112,12 +119,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 +295,51 @@ static void ensureOpenSamlInitialization() {
}

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

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

try {
try {

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

InitializationService.initialize();
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;
new XMLObjectProviderInitializer().init();
new SAMLConfigurationInitializer().init();
registerObjectProviders();
} finally {
thread.setContextClassLoader(originalClassLoader);
}

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

private static void registerObjectProviders() {
final QName x509CertificateQname = QNameSupport.constructQName("http://www.w3.org/2000/09/xmldsig#", "X509Certificate", "ds");
final QName x509CrlQname = QNameSupport.constructQName("http://www.w3.org/2000/09/xmldsig#", "X509CRL", "ds");
final XMLObjectProviderRegistry xmlObjectProviderRegistry = ConfigurationService.get(XMLObjectProviderRegistry.class);
// re-register object providers here
xmlObjectProviderRegistry.registerObjectProvider(
x509CertificateQname,
new SecurityX509CertificateBuilder(),
xmlObjectProviderRegistry.getMarshallerFactory().getMarshaller(x509CertificateQname),
xmlObjectProviderRegistry.getUnmarshallerFactory().getUnmarshaller(x509CertificateQname)
);
xmlObjectProviderRegistry.registerObjectProvider(
x509CrlQname,
new SecurityX509CRLBuilder(),
xmlObjectProviderRegistry.getMarshallerFactory().getMarshaller(x509CrlQname),
xmlObjectProviderRegistry.getUnmarshallerFactory().getUnmarshaller(x509CrlQname)
);
}

@SuppressWarnings("removal")
private MetadataResolver createMetadataResolver(final Settings settings, final Path configPath) throws Exception {
final AbstractMetadataResolver metadataResolver;
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,34 @@
/*
* 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;

public class CleanerCreator {

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

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

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(OpenSearchExecutors.daemonThreadFactory("cleaners"));
}

}
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,89 @@
/*
* 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 org.opensaml.xmlsec.signature.impl.X509CRLImpl;

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

public class SecurityX509CRLImpl extends AbstractXMLObject implements X509CRL {

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

private static final Cleaner CLEANER = CleanerCreator.create(X509CRLImpl.class);

private Cleaner.Cleanable cleanable;

/** Index to a stored Base64 encoded CRL. */
private String b64CRLIndex;

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

/** {@inheritDoc} */
public String getValue() {
return B64_CRL_STORE.get(b64CRLIndex);
}

/** {@inheritDoc} */
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));
}
}
}

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

/**
* The action to be taken when the current state must be cleaned.
*/
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);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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.X509Certificate;
import org.opensaml.xmlsec.signature.impl.X509CertificateImpl;

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

public class SecurityX509CertificateImpl extends AbstractXMLObject implements X509Certificate {

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

private static final Cleaner CLEANER = CleanerCreator.create(X509CertificateImpl.class);

private Cleaner.Cleanable cleanable;

private String b64CertIndex;

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

@Override
public String getValue() {
return B64_CERT_STORE.get(b64CertIndex);
}

@Override
public void setValue(final String newValue) {
// Dump our cached DOM if the new value really is new
final String currentCert = B64_CERT_STORE.get(b64CertIndex);
final String newCert = prepareForAssignment(currentCert, newValue);

// This is a new value, remove the old one, add the new one
if (!Objects.equals(currentCert, newCert)) {
if (cleanable != null) {
cleanable.clean();
cleanable = null;
}
b64CertIndex = B64_CERT_STORE.put(newCert);
if (b64CertIndex != null) {
cleanable = CLEANER.register(this, new SecurityX509CertificateImpl.CleanerState(b64CertIndex));
}
}
}

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

/**
* The action to be taken when the current state must be cleaned.
*/
static class CleanerState implements Runnable {

private String index;

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

public void run() {
SecurityX509CertificateImpl.B64_CERT_STORE.remove(index);
}

}
}

0 comments on commit 5016037

Please sign in to comment.