KC-4335: working on adding a reverse proxy support to allow X.509 client certificate authentication when running keycloak behind a reverse proxy

KC-4335: reverse proxy => a swtich to change a type of reverse proxy when running the X509 integration tests; changes to the names of the reverse proxy providers

KC-4335: updated the migration scripts to add x509 spi to standalone and domain configurations; removed the HAproxy and apache x509 spi configuration
This commit is contained in:
Peter Nalyvayko 2017-04-21 13:01:18 -04:00 committed by Marek Posolda
parent b72b01bb9d
commit b8e5fd2b99
21 changed files with 766 additions and 12 deletions

View file

@ -432,4 +432,11 @@ if (outcome == failed) of /profile=$clusteredProfile/subsystem=infinispan/cache-
echo echo
end-if end-if
if (outcome == failed) of /profile=$clusteredProfile/subsystem=keycloak-server/spi=x509cert-lookup/:read-resource
echo Adding spi=x509cert-lookup...
/profile=$clusteredProfile/subsystem=keycloak-server/spi=x509cert-lookup/:add(default-provider=${keycloak.x509cert.lookup.provider:default})
/profile=$clusteredProfile/subsystem=keycloak-server/spi=x509cert-lookup/provider=default/:add(enabled=true)
echo
end-if
echo *** End Migration of /profile=$clusteredProfile *** echo *** End Migration of /profile=$clusteredProfile ***

View file

@ -389,4 +389,11 @@ if (outcome == failed) of /profile=$standaloneProfile/subsystem=infinispan/cache
echo echo
end-if end-if
if (outcome == failed) of /profile=$standaloneProfile/subsystem=keycloak-server/spi=x509cert-lookup/:read-resource
echo Adding spi=x509cert-lookup...
/profile=$standaloneProfile/subsystem=keycloak-server/spi=x509cert-lookup/:add(default-provider=${keycloak.x509cert.lookup.provider:default})
/profile=$standaloneProfile/subsystem=keycloak-server/spi=x509cert-lookup/provider=default/:add(enabled=true)
echo
end-if
echo *** End Migration of /profile=$standaloneProfile *** echo *** End Migration of /profile=$standaloneProfile ***

View file

@ -416,4 +416,11 @@ if (outcome == failed) of /subsystem=infinispan/cache-container=keycloak/distrib
echo echo
end-if end-if
if (outcome == failed) of /subsystem=keycloak-server/spi=x509cert-lookup/:read-resource
echo Adding spi=x509cert-lookup...
/subsystem=keycloak-server/spi=x509cert-lookup/:add(default-provider=${keycloak.x509cert.lookup.provider:default})
/subsystem=keycloak-server/spi=x509cert-lookup/provider=default/:add(enabled=true)
echo
end-if
echo *** End Migration *** echo *** End Migration ***

View file

@ -360,4 +360,11 @@ if (outcome == failed) of /subsystem=infinispan/cache-container=keycloak/local-c
echo echo
end-if end-if
if (outcome == failed) of /subsystem=keycloak-server/spi=x509cert-lookup/:read-resource
echo Adding spi=x509cert-lookup...
/subsystem=keycloak-server/spi=x509cert-lookup/:add(default-provider=${keycloak.x509cert.lookup.provider:default})
/subsystem=keycloak-server/spi=x509cert-lookup/provider=default/:add(enabled=true)
echo
end-if
echo *** End Migration *** echo *** End Migration ***

View file

@ -18,6 +18,7 @@
package org.keycloak.authentication.authenticators.x509; package org.keycloak.authentication.authenticators.x509;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.function.Function; import java.util.function.Function;
@ -34,6 +35,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
import org.keycloak.services.x509.X509ClientCertificateLookup;
/** /**
* @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a> * @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
@ -46,8 +48,6 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
public static final String DEFAULT_ATTRIBUTE_NAME = "usercertificate"; public static final String DEFAULT_ATTRIBUTE_NAME = "usercertificate";
protected static ServicesLogger logger = ServicesLogger.LOGGER; protected static ServicesLogger logger = ServicesLogger.LOGGER;
public static final String JAVAX_SERVLET_REQUEST_X509_CERTIFICATE = "javax.servlet.request.X509Certificate";
public static final String REGULAR_EXPRESSION = "x509-cert-auth.regular-expression"; public static final String REGULAR_EXPRESSION = "x509-cert-auth.regular-expression";
public static final String ENABLE_CRL = "x509-cert-auth.crl-checking-enabled"; public static final String ENABLE_CRL = "x509-cert-auth.crl-checking-enabled";
public static final String ENABLE_OCSP = "x509-cert-auth.ocsp-checking-enabled"; public static final String ENABLE_OCSP = "x509-cert-auth.ocsp-checking-enabled";
@ -190,16 +190,29 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
} }
protected X509Certificate[] getCertificateChain(AuthenticationFlowContext context) { protected X509Certificate[] getCertificateChain(AuthenticationFlowContext context) {
// Get a x509 client certificate try {
X509Certificate[] certs = (X509Certificate[]) context.getHttpRequest().getAttribute(JAVAX_SERVLET_REQUEST_X509_CERTIFICATE); // Get a x509 client certificate
X509ClientCertificateLookup provider = context.getSession().getProvider(X509ClientCertificateLookup.class);
if (certs != null) { if (provider == null) {
for (X509Certificate cert : certs) { logger.errorv("\"{0}\" Spi is not available, did you forget to update the configuration?",
logger.tracef("[X509ClientCertificateAuthenticator:getCertificateChain] \"%s\"", cert.getSubjectDN().getName()); X509ClientCertificateLookup.class);
return null;
} }
}
return certs; X509Certificate[] certs = provider.getCertificateChain(context.getHttpRequest());
if (certs != null) {
for (X509Certificate cert : certs) {
logger.tracev("\"{0}\"", cert.getSubjectDN().getName());
}
}
return certs;
}
catch (GeneralSecurityException e) {
logger.error(e.getMessage(), e);
}
return null;
} }
// Purely for unit testing // Purely for unit testing
public UserIdentityExtractor getUserIdentityExtractor(X509AuthenticatorConfigModel config) { public UserIdentityExtractor getUserIdentityExtractor(X509AuthenticatorConfigModel config) {

View file

@ -0,0 +1,140 @@
/*
* Copyright 2017 Analytical Graphics, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.services.x509;
import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.common.util.PemException;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @since 3/29/2017
*/
public abstract class AbstractClientCertificateFromHttpHeadersLookup implements X509ClientCertificateLookup {
protected static final Logger logger = Logger.getLogger(AbstractClientCertificateFromHttpHeadersLookup.class);
protected final String sslClientCertHttpHeader;
protected final String sslCertChainHttpHeaderPrefix;
private final int certificateChainLength;
public AbstractClientCertificateFromHttpHeadersLookup(String sslCientCertHttpHeader,
String sslCertChainHttpHeaderPrefix,
int certificateChainLength) {
if (sslCientCertHttpHeader == null) {
throw new IllegalArgumentException("sslClientCertHttpHeader");
}
if (certificateChainLength < 0) {
throw new IllegalArgumentException("certificateChainLength must be greater or equal to zero");
}
this.sslClientCertHttpHeader = sslCientCertHttpHeader;
this.sslCertChainHttpHeaderPrefix = sslCertChainHttpHeaderPrefix;
this.certificateChainLength = certificateChainLength;
}
@Override
public void close() {
}
static String getHeaderValue(HttpRequest httpRequest, String headerName) {
return httpRequest.getHttpHeaders().getRequestHeaders().getFirst(headerName);
}
private static String trimDoubleQuotes(String quotedString) {
if (quotedString == null) return null;
int len = quotedString.length();
if (len > 1 && quotedString.charAt(0) == '"' &&
quotedString.charAt(len - 1) == '"') {
logger.trace("Detected a certificate enclosed in double quotes");
return quotedString.substring(1, len - 1);
}
return quotedString;
}
protected abstract X509Certificate decodeCertificateFromPem(String pem) throws PemException;
protected X509Certificate getCertificateFromHttpHeader(HttpRequest request, String httpHeader) throws GeneralSecurityException {
String encodedCertificate = getHeaderValue(request, httpHeader);
// Remove double quotes
encodedCertificate = trimDoubleQuotes(encodedCertificate);
if (encodedCertificate == null ||
encodedCertificate.trim().length() == 0) {
logger.warnf("HTTP header \"%s\" is empty", httpHeader);
return null;
}
try {
X509Certificate cert = decodeCertificateFromPem(encodedCertificate);
if (cert == null) {
logger.warnf("HTTP header \"%s\" does not contain a valid x.509 certificate\n%s",
httpHeader, encodedCertificate);
} else {
logger.debugf("Found a valid x.509 certificate in \"%s\" HTTP header",
httpHeader);
}
return cert;
}
catch(PemException e) {
logger.error(e.getMessage(), e);
throw new GeneralSecurityException(e);
}
}
@Override
public X509Certificate[] getCertificateChain(HttpRequest httpRequest) throws GeneralSecurityException {
List<X509Certificate> chain = new ArrayList<>();
// Get the client certificate
X509Certificate cert = getCertificateFromHttpHeader(httpRequest, sslClientCertHttpHeader);
if (cert != null) {
chain.add(cert);
// Get the certificate of the client certificate chain
for (int i = 0; i < certificateChainLength; i++) {
try {
String s = String.format("{0}_{1}", sslCertChainHttpHeaderPrefix, i);
cert = getCertificateFromHttpHeader(httpRequest, s);
if (cert != null) {
chain.add(cert);
}
}
catch(GeneralSecurityException e) {
logger.warn(e.getMessage(), e);
}
}
}
return chain.toArray(new X509Certificate[0]);
}
}

View file

@ -0,0 +1,76 @@
/*
* Copyright 2017 Analytical Graphics, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.services.x509;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @since 4/4/2017
*/
public abstract class AbstractClientCertificateFromHttpHeadersLookupFactory implements X509ClientCertificateLookupFactory {
private final static Logger logger = Logger.getLogger(AbstractClientCertificateFromHttpHeadersLookupFactory.class);
protected final static String CERTIFICATE_CHAIN_LENGTH = "certificateChainLength";
protected final static String HTTP_HEADER_CLIENT_CERT = "sslClientCert";
protected final static String HTTP_HEADER_CERT_CHAIN_PREFIX = "sslCertChainPrefix";
protected String sslClientCertHttpHeader;
protected String sslChainHttpHeaderPrefix;
protected int certificateChainLength = 1;
@Override
public void init(Config.Scope config) {
if (config != null) {
certificateChainLength = config.getInt(CERTIFICATE_CHAIN_LENGTH, 1);
logger.tracev("{0}: '{1}'", CERTIFICATE_CHAIN_LENGTH, certificateChainLength);
sslClientCertHttpHeader = config.get(HTTP_HEADER_CLIENT_CERT, "");
logger.tracev("{0}: '{1}'", HTTP_HEADER_CLIENT_CERT, sslClientCertHttpHeader);
sslChainHttpHeaderPrefix = config.get(HTTP_HEADER_CERT_CHAIN_PREFIX, null);
if (sslChainHttpHeaderPrefix != null) {
logger.tracev("{0}: '{1}'", HTTP_HEADER_CERT_CHAIN_PREFIX, sslChainHttpHeaderPrefix);
} else {
logger.tracev("{0} was not configured", HTTP_HEADER_CERT_CHAIN_PREFIX);
}
}
else {
logger.tracev("No configuration for '{0}' reverse proxy was found", this.getId());
sslClientCertHttpHeader = "";
sslChainHttpHeaderPrefix = "";
certificateChainLength = 0;
}
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright 2017 Analytical Graphics, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.services.x509;
import org.keycloak.common.util.PemException;
import org.keycloak.common.util.PemUtils;
import java.security.cert.X509Certificate;
/**
* The provider allows to extract X.509 client certificate forwarded
* to keycloak configured behind the Apache reverse proxy.
*
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @since 3/29/2017
*/
public class ApacheProxySslClientCertificateLookup extends AbstractClientCertificateFromHttpHeadersLookup {
public ApacheProxySslClientCertificateLookup(String sslCientCertHttpHeader,
String sslCertChainHttpHeaderPrefix,
int certificateChainLength) {
super(sslCientCertHttpHeader, sslCertChainHttpHeaderPrefix, certificateChainLength);
}
private static String removeBeginEnd(String pem) {
pem = pem.replace("-----BEGIN CERTIFICATE-----", "");
pem = pem.replace("-----END CERTIFICATE-----", "");
pem = pem.replace("\r\n", "");
pem = pem.replace("\n", "");
return pem.trim();
}
@Override
protected X509Certificate decodeCertificateFromPem(String pem) throws PemException {
if (pem == null) {
return null;
}
if (pem.startsWith("-----BEGIN CERTIFICATE-----")) {
pem = removeBeginEnd(pem);
}
return PemUtils.decodeCertificate(pem);
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright 2017 Analytical Graphics, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.services.x509;
import org.keycloak.models.KeycloakSession;
/**
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @since 4/4/2017
*/
public class ApacheProxySslClientCertificateLookupFactory extends AbstractClientCertificateFromHttpHeadersLookupFactory {
private final static String PROVIDER = "apache";
@Override
public X509ClientCertificateLookup create(KeycloakSession session) {
return new ApacheProxySslClientCertificateLookup(sslClientCertHttpHeader,
sslChainHttpHeaderPrefix, certificateChainLength);
}
@Override
public String getId() {
return PROVIDER;
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright 2017 Analytical Graphics, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.services.x509;
import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import java.security.cert.X509Certificate;
/**
* The provider retrieves a client certificate and the certificate chain
* (if any) from the incoming TLS connection.
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @since 3/26/2017
*/
public class DefaultClientCertificateLookup implements X509ClientCertificateLookup {
private static final Logger logger = Logger.getLogger(DefaultClientCertificateLookup.class);
public static final String JAVAX_SERVLET_REQUEST_X509_CERTIFICATE = "javax.servlet.request.X509Certificate";
public DefaultClientCertificateLookup() {
}
@Override
public void close() {
}
@Override
public X509Certificate[] getCertificateChain(HttpRequest httpRequest) {
X509Certificate[] certs = (X509Certificate[]) httpRequest.getAttribute(JAVAX_SERVLET_REQUEST_X509_CERTIFICATE);
if (certs != null) {
for (X509Certificate cert : certs) {
logger.tracef("Certificate's SubjectDN => \"%s\"", cert.getSubjectDN().getName());
}
}
return certs;
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright 2017 Analytical Graphics, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.services.x509;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* The factory and the corresponding providers extract a client certificate
* and the certificate chain (if any) from the incoming TLS connection.
*
* This factory is used by default
*
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @since 4/4/2017
*/
public class DefaultClientCertificateLookupFactory implements X509ClientCertificateLookupFactory {
private final static String PROVIDER = "default";
private final static X509ClientCertificateLookup SINGLETON =
new DefaultClientCertificateLookup();
@Override
public X509ClientCertificateLookup create(KeycloakSession session) {
return SINGLETON;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER;
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright 2017 Analytical Graphics, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.services.x509;
import org.jboss.logging.Logger;
import org.keycloak.common.util.PemException;
import org.keycloak.common.util.PemUtils;
import java.security.cert.X509Certificate;
/**
* The provider allows to extract X.509 client certificate forwarded
* to the keycloak middleware configured behind the haproxy reverse proxy.
*
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @since 3/27/2017
*/
public class HaProxySslClientCertificateLookup extends AbstractClientCertificateFromHttpHeadersLookup {
private static final Logger logger = Logger.getLogger(HaProxySslClientCertificateLookup.class);
public HaProxySslClientCertificateLookup(String sslCientCertHttpHeader,
String sslCertChainHttpHeaderPrefix,
int certificateChainLength) {
super(sslCientCertHttpHeader, sslCertChainHttpHeaderPrefix, certificateChainLength);
}
@Override
protected X509Certificate decodeCertificateFromPem(String pem) throws PemException {
if (pem == null) {
return null;
}
return PemUtils.decodeCertificate(pem);
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright 2017 Analytical Graphics, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.services.x509;
import org.keycloak.models.KeycloakSession;
/**
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @since 4/4/2017
*/
public class HaProxySslClientCertificateLookupFactory extends AbstractClientCertificateFromHttpHeadersLookupFactory {
private final static String PROVIDER = "haproxy";
@Override
public X509ClientCertificateLookup create(KeycloakSession session) {
return new HaProxySslClientCertificateLookup(sslClientCertHttpHeader,
sslChainHttpHeaderPrefix, certificateChainLength);
}
@Override
public String getId() {
return PROVIDER;
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright 2017 Analytical Graphics, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.services.x509;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.provider.Provider;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
/**
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @since 3/26/2017
*/
public interface X509ClientCertificateLookup extends Provider {
/**
* Returns a client certificate, and optionally any certificates
* in the certificate chain.
* @return
*/
X509Certificate[] getCertificateChain(HttpRequest httpRequest) throws GeneralSecurityException;
}

View file

@ -0,0 +1,31 @@
/*
* Copyright 2017 Analytical Graphics, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.services.x509;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @since 3/26/2017
*/
public interface X509ClientCertificateLookupFactory extends ProviderFactory<X509ClientCertificateLookup> {
}

View file

@ -0,0 +1,52 @@
/*
* Copyright 2017 Analytical Graphics, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.services.x509;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @since 3/26/2017
*/
public class X509ClientCertificateLookupSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "x509cert-lookup";
}
@Override
public Class<? extends Provider> getProviderClass() {
return X509ClientCertificateLookup.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return X509ClientCertificateLookupFactory.class;
}
}

View file

@ -20,3 +20,4 @@ org.keycloak.wellknown.WellKnownSpi
org.keycloak.services.clientregistration.ClientRegistrationSpi org.keycloak.services.clientregistration.ClientRegistrationSpi
org.keycloak.services.clientregistration.policy.ClientRegistrationPolicySpi org.keycloak.services.clientregistration.policy.ClientRegistrationPolicySpi
org.keycloak.authentication.actiontoken.ActionTokenHandlerSpi org.keycloak.authentication.actiontoken.ActionTokenHandlerSpi
org.keycloak.services.x509.X509ClientCertificateLookupSpi

View file

@ -0,0 +1,21 @@
#
# Copyright 2017 Analytical Graphics, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
org.keycloak.services.x509.DefaultClientCertificateLookupFactory
org.keycloak.services.x509.HaProxySslClientCertificateLookupFactory
org.keycloak.services.x509.ApacheProxySslClientCertificateLookupFactory

View file

@ -143,6 +143,25 @@
"http=${auth.server.http.port}", "http=${auth.server.http.port}",
"https=${auth.server.https.port}" "https=${auth.server.https.port}"
] ]
}
},
"x509cert-lookup": {
"provider": "${keycloak.x509cert.lookup.provider:default}",
"default": {
"enabled": true
},
"haproxy": {
"enabled": true,
"sslClientCert": "x-ssl-client-cert",
"sslCertChainPrefix": "x-ssl-client-cert-chain",
"certificateChainLength": 1
},
"apache": {
"enabled": true,
"sslClientCert": "x-ssl-client-cert",
"sslCertChainPrefix": "x-ssl-client-cert-chain",
"certificateChainLength": 1
} }
} }
} }

View file

@ -90,7 +90,9 @@
<keycloak.connectionsJpa.url.crossdc>jdbc:h2:mem:test-dc-shared</keycloak.connectionsJpa.url.crossdc> <keycloak.connectionsJpa.url.crossdc>jdbc:h2:mem:test-dc-shared</keycloak.connectionsJpa.url.crossdc>
<keycloak.testsuite.logging.pattern>%d{HH:mm:ss,SSS} %-5p [%c] %m%n</keycloak.testsuite.logging.pattern> <keycloak.testsuite.logging.pattern>%d{HH:mm:ss,SSS} %-5p [%c] %m%n</keycloak.testsuite.logging.pattern>
<adapter.test.props/> <adapter.test.props>
-Dkeycloak.x509cert.lookup.provider=${keycloak.x509cert.lookup.provider}
</adapter.test.props>
<migration.import.properties/> <migration.import.properties/>
<kie.maven.settings/> <kie.maven.settings/>
@ -125,6 +127,7 @@
<client.key.passphrase>secret</client.key.passphrase> <client.key.passphrase>secret</client.key.passphrase>
<auth.server.ocsp.responder.enabled>false</auth.server.ocsp.responder.enabled> <auth.server.ocsp.responder.enabled>false</auth.server.ocsp.responder.enabled>
<keycloak.x509cert.lookup.provider>default</keycloak.x509cert.lookup.provider>
</properties> </properties>
<build> <build>

View file

@ -73,4 +73,8 @@ keycloak.server.subsys.default.config=\
</properties>\ </properties>\
</provider>\ </provider>\
</spi>\ </spi>\
<spi name="x509cert-lookup">\
<default-provider>${keycloak.x509cert.lookup.provider:default}</default-provider>\
<provider name="default" enabled="true"/>\
</spi>\
</subsystem>\ </subsystem>\