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
|
||||
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 ***
|
|
@ -389,4 +389,11 @@ if (outcome == failed) of /profile=$standaloneProfile/subsystem=infinispan/cache
|
|||
echo
|
||||
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 ***
|
|
@ -416,4 +416,11 @@ if (outcome == failed) of /subsystem=infinispan/cache-container=keycloak/distrib
|
|||
echo
|
||||
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 ***
|
|
@ -360,4 +360,11 @@ if (outcome == failed) of /subsystem=infinispan/cache-container=keycloak/local-c
|
|||
echo
|
||||
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 ***
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package org.keycloak.authentication.authenticators.x509;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.function.Function;
|
||||
|
@ -34,6 +35,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.services.x509.X509ClientCertificateLookup;
|
||||
|
||||
/**
|
||||
* @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";
|
||||
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 ENABLE_CRL = "x509-cert-auth.crl-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) {
|
||||
// Get a x509 client certificate
|
||||
X509Certificate[] certs = (X509Certificate[]) context.getHttpRequest().getAttribute(JAVAX_SERVLET_REQUEST_X509_CERTIFICATE);
|
||||
|
||||
if (certs != null) {
|
||||
for (X509Certificate cert : certs) {
|
||||
logger.tracef("[X509ClientCertificateAuthenticator:getCertificateChain] \"%s\"", cert.getSubjectDN().getName());
|
||||
try {
|
||||
// Get a x509 client certificate
|
||||
X509ClientCertificateLookup provider = context.getSession().getProvider(X509ClientCertificateLookup.class);
|
||||
if (provider == null) {
|
||||
logger.errorv("\"{0}\" Spi is not available, did you forget to update the configuration?",
|
||||
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
|
||||
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.policy.ClientRegistrationPolicySpi
|
||||
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}",
|
||||
"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.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/>
|
||||
<kie.maven.settings/>
|
||||
|
||||
|
@ -125,6 +127,7 @@
|
|||
<client.key.passphrase>secret</client.key.passphrase>
|
||||
|
||||
<auth.server.ocsp.responder.enabled>false</auth.server.ocsp.responder.enabled>
|
||||
<keycloak.x509cert.lookup.provider>default</keycloak.x509cert.lookup.provider>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -73,4 +73,8 @@ keycloak.server.subsys.default.config=\
|
|||
</properties>\
|
||||
</provider>\
|
||||
</spi>\
|
||||
</subsystem>\
|
||||
<spi name="x509cert-lookup">\
|
||||
<default-provider>${keycloak.x509cert.lookup.provider:default}</default-provider>\
|
||||
<provider name="default" enabled="true"/>\
|
||||
</spi>\
|
||||
</subsystem>\
|
||||
|
|
Loading…
Reference in a new issue