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:
parent
b72b01bb9d
commit
b8e5fd2b99
21 changed files with 766 additions and 12 deletions
|
@ -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 ***
|
|
@ -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 ***
|
|
@ -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 ***
|
|
@ -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 ***
|
|
@ -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) {
|
||||||
|
|
|
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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> {
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>\
|
Loading…
Reference in a new issue