Commit b136f6c1 authored by Nils Bandener's avatar Nils Bandener
Browse files

Revert "Backend OIDC impl now allows access of token endpoint via proxy"

This reverts commit 8a3434ed.
parent e49ff309
......@@ -348,13 +348,6 @@
<artifactId>commons-io</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.browserup</groupId>
<artifactId>browserup-proxy-core</artifactId>
<version>2.1.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
/*
* Copyright 2016-2020 by floragunn GmbH - All rights reserved
* Copyright 2016-2018 by floragunn GmbH - All rights reserved
*
*
* Unless required by applicable law or agreed to in writing, software
......@@ -15,110 +15,50 @@
package com.floragunn.dlic.auth.http.jwt.keybyoidc;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.HttpResponse;
import org.apache.http.entity.ContentType;
import org.apache.http.util.EntityUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestStatus;
import com.floragunn.dlic.auth.http.jwt.AbstractHTTPJwtAuthenticator;
import com.floragunn.dlic.auth.http.jwt.oidc.json.OidcProviderConfig;
import com.floragunn.dlic.util.SettingsBasedSSLConfigurator;
import com.floragunn.dlic.util.SettingsBasedSSLConfigurator.SSLConfigException;
import com.floragunn.searchsupport.config.proxy.ProxyConfig;
import com.floragunn.searchsupport.rest.Responses;
public class HTTPJwtKeyByOpenIdConnectAuthenticator extends AbstractHTTPJwtAuthenticator {
private final static Logger log = LogManager.getLogger(HTTPJwtKeyByOpenIdConnectAuthenticator.class);
private ProxyConfig proxyConfig;
private OpenIdProviderClient openIdProviderClient;
public HTTPJwtKeyByOpenIdConnectAuthenticator(Settings settings, Path configPath) {
super(settings, configPath);
}
protected KeyProvider initKeyProvider(Settings settings, Path configPath) throws Exception {
this.proxyConfig = ProxyConfig.parse(settings, "proxy");
try {
this.openIdProviderClient = new OpenIdProviderClient(settings.get("openid_connect_url"), getSSLConfig(settings, configPath), proxyConfig,
settings.getAsBoolean("cache_jwks_endpoint", false));
int idpRequestTimeoutMs = settings.getAsInt("idp_request_timeout_ms", 5000);
openIdProviderClient.setRequestTimeoutMs(idpRequestTimeoutMs);
} catch (SSLConfigException e) {
log.error("Error while initializing openid http authenticator", e);
throw new RuntimeException("Error while initializing openid http authenticator", e);
}
int idpRequestTimeoutMs = settings.getAsInt("idp_request_timeout_ms", 5000);
int idpQueuedThreadTimeoutMs = settings.getAsInt("idp_queued_thread_timeout_ms", 2500);
int refreshRateLimitTimeWindowMs = settings.getAsInt("refresh_rate_limit_time_window_ms", 10000);
int refreshRateLimitCount = settings.getAsInt("refresh_rate_limit_count", 10);
KeySetRetriever keySetRetriever = new KeySetRetriever(openIdProviderClient);
//private final static Logger log = LogManager.getLogger(HTTPJwtKeyByOpenIdConnectAuthenticator.class);
SelfRefreshingKeySet selfRefreshingKeySet = new SelfRefreshingKeySet(keySetRetriever);
public HTTPJwtKeyByOpenIdConnectAuthenticator(Settings settings, Path configPath) {
super(settings, configPath);
}
selfRefreshingKeySet.setRequestTimeoutMs(idpRequestTimeoutMs);
selfRefreshingKeySet.setQueuedThreadTimeoutMs(idpQueuedThreadTimeoutMs);
selfRefreshingKeySet.setRefreshRateLimitTimeWindowMs(refreshRateLimitTimeWindowMs);
selfRefreshingKeySet.setRefreshRateLimitCount(refreshRateLimitCount);
protected KeyProvider initKeyProvider(Settings settings, Path configPath) throws Exception {
int idpRequestTimeoutMs = settings.getAsInt("idp_request_timeout_ms", 5000);
int idpQueuedThreadTimeoutMs = settings.getAsInt("idp_queued_thread_timeout_ms", 2500);
return selfRefreshingKeySet;
}
int refreshRateLimitTimeWindowMs = settings.getAsInt("refresh_rate_limit_time_window_ms", 10000);
int refreshRateLimitCount = settings.getAsInt("refresh_rate_limit_count", 10);
private static SettingsBasedSSLConfigurator.SSLConfig getSSLConfig(Settings settings, Path configPath) throws SSLConfigException {
return new SettingsBasedSSLConfigurator(settings, configPath, "openid_connect_idp").buildSSLConfig();
}
KeySetRetriever keySetRetriever = new KeySetRetriever(settings.get("openid_connect_url"),
getSSLConfig(settings, configPath), settings.getAsBoolean("cache_jwks_endpoint", false));
@Override
public boolean handleMetaRequest(RestRequest restRequest, RestChannel restChannel, String generalRequestPathComponent,
String specificRequestPathComponent, ThreadContext threadContext) {
try {
if ("config".equals(specificRequestPathComponent)) {
OidcProviderConfig oidcProviderConfig = openIdProviderClient.getOidcConfiguration();
Map<String, Object> oidcProviderConfigMap = new HashMap<String, Object>(oidcProviderConfig.getParsedJson());
keySetRetriever.setRequestTimeoutMs(idpRequestTimeoutMs);
oidcProviderConfigMap.put("token_endpoint_proxy", generalRequestPathComponent + "/token");
SelfRefreshingKeySet selfRefreshingKeySet = new SelfRefreshingKeySet(keySetRetriever);
Responses.sendJson(restChannel, oidcProviderConfigMap);
} else if ("token".equals(specificRequestPathComponent)) {
ContentType contentType = ContentType.APPLICATION_FORM_URLENCODED;
selfRefreshingKeySet.setRequestTimeoutMs(idpRequestTimeoutMs);
selfRefreshingKeySet.setQueuedThreadTimeoutMs(idpQueuedThreadTimeoutMs);
selfRefreshingKeySet.setRefreshRateLimitTimeWindowMs(refreshRateLimitTimeWindowMs);
selfRefreshingKeySet.setRefreshRateLimitCount(refreshRateLimitCount);
HttpResponse idpResponse = openIdProviderClient.callTokenEndpoint(BytesReference.toBytes(restRequest.content()), contentType);
return selfRefreshingKeySet;
}
restChannel.sendResponse(new BytesRestResponse(RestStatus.fromCode(idpResponse.getStatusLine().getStatusCode()),
idpResponse.getEntity().getContentType().getValue(), EntityUtils.toByteArray(idpResponse.getEntity())));
} else {
Responses.sendError(restChannel, RestStatus.NOT_FOUND, "Invalid endpoint: " + restRequest.path());
}
return true;
} catch (Exception e) {
log.error("Error while handling request", e);
Responses.sendError(restChannel, RestStatus.INTERNAL_SERVER_ERROR, "Error while handling OpenID request");
return true;
}
}
private static SettingsBasedSSLConfigurator.SSLConfig getSSLConfig(Settings settings, Path configPath)
throws Exception {
return new SettingsBasedSSLConfigurator(settings, configPath, "openid_connect_idp").buildSSLConfig();
}
@Override
public String getType() {
return "openid";
}
@Override
public String getType() {
return "jwt-key-by-oidc";
}
}
/*
* Copyright 2016-2020 by floragunn GmbH - All rights reserved
* Copyright 2016-2018 by floragunn GmbH - All rights reserved
*
*
* Unless required by applicable law or agreed to in writing, software
......@@ -14,17 +14,211 @@
package com.floragunn.dlic.auth.http.jwt.keybyoidc;
import java.io.IOException;
import org.apache.cxf.rs.security.jose.jwk.JsonWebKeys;
import org.apache.cxf.rs.security.jose.jwk.JwkUtils;
import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.cache.HttpCacheContext;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.cache.BasicHttpCacheStorage;
import org.apache.http.impl.client.cache.CacheConfig;
import org.apache.http.impl.client.cache.CachingHttpClients;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.floragunn.dlic.auth.http.jwt.oidc.json.OpenIdProviderConfiguration;
import com.floragunn.dlic.util.SettingsBasedSSLConfigurator.SSLConfig;
import com.floragunn.searchguard.DefaultObjectMapper;
public class KeySetRetriever implements KeySetProvider {
private final static Logger log = LogManager.getLogger(KeySetRetriever.class);
private static final long CACHE_STATUS_LOG_INTERVAL_MS = 60L * 60L * 1000L;
private String openIdConnectEndpoint;
private SSLConfig sslConfig;
private int requestTimeoutMs = 10000;
private CacheConfig cacheConfig;
private HttpCacheStorage oidcHttpCacheStorage;
private int oidcCacheHits = 0;
private int oidcCacheMisses = 0;
private int oidcCacheHitsValidated = 0;
private int oidcCacheModuleResponses = 0;
private long oidcRequests = 0;
private long lastCacheStatusLog = 0;
public class KeySetRetriever implements KeySetProvider {
private final OpenIdProviderClient openIdProviderClient;
KeySetRetriever(String openIdConnectEndpoint, SSLConfig sslConfig, boolean useCacheForOidConnectEndpoint) {
this.openIdConnectEndpoint = openIdConnectEndpoint;
this.sslConfig = sslConfig;
KeySetRetriever(OpenIdProviderClient openIdProviderClient) {
this.openIdProviderClient = openIdProviderClient;
if (useCacheForOidConnectEndpoint) {
cacheConfig = CacheConfig.custom().setMaxCacheEntries(10).setMaxObjectSize(1024L * 1024L).build();
oidcHttpCacheStorage = new BasicHttpCacheStorage(cacheConfig);
}
}
public JsonWebKeys get() throws AuthenticatorUnavailableException {
return openIdProviderClient.getJsonWebKeys();
String uri = getJwksUri();
try (CloseableHttpClient httpClient = createHttpClient(null)) {
HttpGet httpGet = new HttpGet(uri);
RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(getRequestTimeoutMs())
.setConnectTimeout(getRequestTimeoutMs()).setSocketTimeout(getRequestTimeoutMs()).build();
httpGet.setConfig(requestConfig);
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
StatusLine statusLine = response.getStatusLine();
if (statusLine.getStatusCode() < 200 || statusLine.getStatusCode() >= 300) {
throw new AuthenticatorUnavailableException("Error while getting " + uri + ": " + statusLine);
}
HttpEntity httpEntity = response.getEntity();
if (httpEntity == null) {
throw new AuthenticatorUnavailableException(
"Error while getting " + uri + ": Empty response entity");
}
JsonWebKeys keySet = JwkUtils.readJwkSet(httpEntity.getContent());
return keySet;
}
} catch (IOException e) {
throw new AuthenticatorUnavailableException("Error while getting " + uri + ": " + e, e);
}
}
String getJwksUri() throws AuthenticatorUnavailableException {
try (CloseableHttpClient httpClient = createHttpClient(oidcHttpCacheStorage)) {
HttpGet httpGet = new HttpGet(openIdConnectEndpoint);
RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(getRequestTimeoutMs())
.setConnectTimeout(getRequestTimeoutMs()).setSocketTimeout(getRequestTimeoutMs()).build();
httpGet.setConfig(requestConfig);
HttpCacheContext httpContext = null;
if (oidcHttpCacheStorage != null) {
httpContext = new HttpCacheContext();
}
try (CloseableHttpResponse response = httpClient.execute(httpGet, httpContext)) {
if (httpContext != null) {
logCacheResponseStatus(httpContext);
}
StatusLine statusLine = response.getStatusLine();
if (statusLine.getStatusCode() < 200 || statusLine.getStatusCode() >= 300) {
throw new AuthenticatorUnavailableException(
"Error while getting " + openIdConnectEndpoint + ": " + statusLine);
}
HttpEntity httpEntity = response.getEntity();
if (httpEntity == null) {
throw new AuthenticatorUnavailableException(
"Error while getting " + openIdConnectEndpoint + ": Empty response entity");
}
OpenIdProviderConfiguration parsedEntity = DefaultObjectMapper.objectMapper.readValue(httpEntity.getContent(),
OpenIdProviderConfiguration.class);
return parsedEntity.getJwksUri();
}
} catch (IOException e) {
throw new AuthenticatorUnavailableException("Error while getting " + openIdConnectEndpoint + ": " + e, e);
}
}
public int getRequestTimeoutMs() {
return requestTimeoutMs;
}
public void setRequestTimeoutMs(int httpTimeoutMs) {
this.requestTimeoutMs = httpTimeoutMs;
}
private void logCacheResponseStatus(HttpCacheContext httpContext) {
this.oidcRequests++;
switch (httpContext.getCacheResponseStatus()) {
case CACHE_HIT:
this.oidcCacheHits++;
break;
case CACHE_MODULE_RESPONSE:
this.oidcCacheModuleResponses++;
break;
case CACHE_MISS:
this.oidcCacheMisses++;
break;
case VALIDATED:
this.oidcCacheHitsValidated++;
break;
}
long now = System.currentTimeMillis();
if (this.oidcRequests >= 2 && now - lastCacheStatusLog > CACHE_STATUS_LOG_INTERVAL_MS) {
log.info("Cache status for KeySetRetriever:\noidcCacheHits: " + oidcCacheHits + "\noidcCacheHitsValidated: "
+ oidcCacheHitsValidated + "\noidcCacheModuleResponses: " + oidcCacheModuleResponses
+ "\noidcCacheMisses: " + oidcCacheMisses);
lastCacheStatusLog = now;
}
}
private CloseableHttpClient createHttpClient(HttpCacheStorage httpCacheStorage) {
HttpClientBuilder builder;
if (httpCacheStorage != null) {
builder = CachingHttpClients.custom().setCacheConfig(cacheConfig).setHttpCacheStorage(httpCacheStorage);
} else {
builder = HttpClients.custom();
}
builder.useSystemProperties();
if (sslConfig != null) {
builder.setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory());
}
return builder.build();
}
public int getOidcCacheHits() {
return oidcCacheHits;
}
public int getOidcCacheMisses() {
return oidcCacheMisses;
}
public int getOidcCacheHitsValidated() {
return oidcCacheHitsValidated;
}
public int getOidcCacheModuleResponses() {
return oidcCacheModuleResponses;
}
}
/*
* Copyright 2016-2020 by floragunn GmbH - All rights reserved
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed here is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* This software is free of charge for non-commercial and academic use.
* For commercial use in a production environment you have to obtain a license
* from https://floragunn.com
*
*/
package com.floragunn.dlic.auth.http.jwt.keybyoidc;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestRequest.Method.POST;
import java.io.IOException;
import java.util.List;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestStatus;
import com.floragunn.searchsupport.rest.Responses;
import com.google.common.collect.ImmutableList;
public class OidcConfigRestAction extends BaseRestHandler {
@Override
public String getName() {
return "OIDC Configuration";
}
@Override
public List<Route> routes() {
return ImmutableList.of(new Route(GET, "/_searchguard/auth_domain/{authdomain}/openid/{endpoint}"),
new Route(POST, "/_searchguard/auth_domain/{authdomain}/openid/{endpoint}"));
}
@Override
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
request.param("authdomain");
request.param("endpoint");
return channel -> {
Responses.sendError(channel, RestStatus.BAD_REQUEST, "Request could not be handled");
};
}
}
/*
* Copyright 2016-2020 by floragunn GmbH - All rights reserved
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed here is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* This software is free of charge for non-commercial and academic use.
* For commercial use in a production environment you have to obtain a license
* from https://floragunn.com
*
*/
package com.floragunn.dlic.auth.http.jwt.keybyoidc;
import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import org.apache.cxf.rs.security.jose.jwk.JsonWebKeys;
import org.apache.cxf.rs.security.jose.jwk.JwkUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.cache.HttpCacheContext;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.cache.BasicHttpCacheStorage;
import org.apache.http.impl.client.cache.CacheConfig;
import org.apache.http.impl.client.cache.CachingHttpClients;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.util.EntityUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.SpecialPermission;
import com.floragunn.dlic.auth.http.jwt.oidc.json.OidcProviderConfig;
import com.floragunn.dlic.util.SettingsBasedSSLConfigurator.SSLConfig;
import com.floragunn.searchsupport.config.proxy.ProxyConfig;
import com.floragunn.searchsupport.json.BasicJsonReader;
public class OpenIdProviderClient {
private final static Logger log = LogManager.getLogger(KeySetRetriever.class);
private static final long CACHE_STATUS_LOG_INTERVAL_MS = 60L * 60L * 1000L;
private String openIdConnectEndpoint;
private SSLConfig sslConfig;
private ProxyConfig proxyConfig;
private int requestTimeoutMs = 10000;
private CacheConfig cacheConfig;
private HttpCacheStorage oidcHttpCacheStorage;
private int oidcCacheHits = 0;
private int oidcCacheMisses = 0;
private int oidcCacheHitsValidated = 0;
private int oidcCacheModuleResponses = 0;
private long oidcRequests = 0;
private long lastCacheStatusLog = 0;
OpenIdProviderClient(String openIdConnectEndpoint, SSLConfig sslConfig, ProxyConfig proxyConfig, boolean useCacheForOidConnectEndpoint) {
this.openIdConnectEndpoint = openIdConnectEndpoint;
this.sslConfig = sslConfig;
this.proxyConfig = proxyConfig;
if (useCacheForOidConnectEndpoint) {
cacheConfig = CacheConfig.custom().setMaxCacheEntries(10).setMaxObjectSize(1024L * 1024L).build();
oidcHttpCacheStorage = new BasicHttpCacheStorage(cacheConfig);
}
}
public OidcProviderConfig getOidcConfiguration() {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
return AccessController.doPrivileged((PrivilegedAction<OidcProviderConfig>) () -> {
try (CloseableHttpClient httpClient = createHttpClient(oidcHttpCacheStorage)) {
HttpGet httpGet = new HttpGet(openIdConnectEndpoint);
RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(getRequestTimeoutMs())
.setConnectTimeout(getRequestTimeoutMs()).setSocketTimeout(getRequestTimeoutMs()).build();
httpGet.setConfig(requestConfig);
HttpCacheContext httpContext = null;
if (oidcHttpCacheStorage != null) {
httpContext = new HttpCacheContext();
}
try (CloseableHttpResponse response = httpClient.execute(httpGet, httpContext)) {
if (httpContext != null) {
logCacheResponseStatus(httpContext);
}
StatusLine statusLine = response.getStatusLine();
if (statusLine.getStatusCode() < 200 || statusLine.getStatusCode() >= 300) {
throw new AuthenticatorUnavailableException("Error while getting " + openIdConnectEndpoint + ": " + statusLine);
}
HttpEntity httpEntity = response.getEntity();
if (httpEntity == null) {
throw new AuthenticatorUnavailableException("Error while getting " + openIdConnectEndpoint + ": Empty response entity");
}
return new OidcProviderConfig(BasicJsonReader.readObject(httpEntity.getContent()));
}