Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add setting for keep-alive duration for oidc back-channel #87773

Merged
merged 14 commits into from
Jun 20, 2022
5 changes: 5 additions & 0 deletions docs/changelog/87773.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 87773
summary: Automatically close idle connections in OIDC back-channel
area: Security
type: enhancement
issues: []
15 changes: 15 additions & 0 deletions docs/reference/settings/security-settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -1858,6 +1858,21 @@ connections allowed per endpoint.
Defaults to `200`.
// end::oidc-http-max-endpoint-connections-tag[]

// tag::oidc-http-connection-pool-ttl-tag[]
`http.connection_pool_ttl` {ess-icon}::
(<<static-cluster-setting,Static>>)
Controls the behavior of the http client used for back-channel communication to
the OpenID Connect Provider endpoints. Specifies the time-to-live of connections
in the connection pool (default to 3 minutes). A connection is closed if it is
idle for more than the specified timeout.

The server can also set the `Keep-Alive` HTTP response header. The effective
time-to-live value is the smaller value between this setting and the `Keep-Alive`
reponse header. Configure this setting to `-1` to let the server dictate the value.
If the header is not set by the server and the setting has value of `-1`,
the time-to-live is infinite and connections never expire.
// end::oidc-http-connection-pool-ttl-tag[]

[discrete]
[[ref-oidc-ssl-settings]]
===== OpenID Connect realm SSL settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

public class OpenIdConnectRealmSettings {
Expand Down Expand Up @@ -212,6 +213,12 @@ private OpenIdConnectRealmSettings() {}
"http.max_endpoint_connections",
key -> Setting.intSetting(key, 200, Setting.Property.NodeScope)
);

public static final Setting.AffixSetting<TimeValue> HTTP_CONNECTION_POOL_TTL = Setting.affixKeySetting(
RealmSettings.realmSettingPrefix(TYPE),
"http.connection_pool_ttl",
tvernum marked this conversation as resolved.
Show resolved Hide resolved
key -> Setting.timeSetting(key, new TimeValue(3, TimeUnit.MINUTES), Setting.Property.NodeScope)
);
public static final Setting.AffixSetting<String> HTTP_PROXY_HOST = Setting.affixKeySetting(
RealmSettings.realmSettingPrefix(TYPE),
"http.proxy.host",
Expand Down Expand Up @@ -307,6 +314,7 @@ public static Set<Setting.AffixSetting<?>> getSettings() {
HTTP_SOCKET_TIMEOUT,
HTTP_MAX_CONNECTIONS,
HTTP_MAX_ENDPOINT_CONNECTIONS,
HTTP_CONNECTION_POOL_TTL,
HTTP_PROXY_HOST,
HTTP_PROXY_PORT,
HTTP_PROXY_SCHEME,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ testClusters.matching { it.name == 'javaRestTest' }.configureEach {
setting 'xpack.security.authc.realms.oidc.openid7.op.authorization_endpoint', 'https://op.example.com/auth'
setting 'xpack.security.authc.realms.oidc.openid7.op.jwkset_path', 'oidc-jwkset.json'
setting 'xpack.security.authc.realms.oidc.openid7.claims.principal', 'sub'
setting 'xpack.security.authc.realms.oidc.openid7.http.connection_pool_ttl', '1m'
keystore 'xpack.security.authc.realms.oidc.openid7.rp.client_secret', 'this-is-my-secret'
// - JWT (works)
setting 'xpack.security.authc.realms.jwt.jwt8.order', '8'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.client.HttpAsyncClients;
Expand Down Expand Up @@ -114,6 +116,7 @@

import static org.elasticsearch.core.Strings.format;
import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.ALLOWED_CLOCK_SKEW;
import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.HTTP_CONNECTION_POOL_TTL;
import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.HTTP_CONNECTION_READ_TIMEOUT;
import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.HTTP_CONNECT_TIMEOUT;
import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.HTTP_MAX_CONNECTIONS;
Expand Down Expand Up @@ -705,9 +708,11 @@ private CloseableHttpAsyncClient createHttpClient() {
.setConnectionRequestTimeout(Math.toIntExact(realmConfig.getSetting(HTTP_CONNECTION_READ_TIMEOUT).getSeconds()))
.setSocketTimeout(Math.toIntExact(realmConfig.getSetting(HTTP_SOCKET_TIMEOUT).getMillis()))
.build();

HttpAsyncClientBuilder httpAsyncClientBuilder = HttpAsyncClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig);
.setDefaultRequestConfig(requestConfig)
.setKeepAliveStrategy(getKeepAliveStrategy());
if (realmConfig.hasSetting(HTTP_PROXY_HOST)) {
httpAsyncClientBuilder.setProxy(
new HttpHost(
Expand All @@ -726,6 +731,32 @@ private CloseableHttpAsyncClient createHttpClient() {
}
}

// Package private for testing
CloseableHttpAsyncClient getHttpClient() {
return httpClient;
}

// Package private for testing
ConnectionKeepAliveStrategy getKeepAliveStrategy() {
final long userConfiguredKeepAlive = realmConfig.getSetting(HTTP_CONNECTION_POOL_TTL).millis();
return (response, context) -> {
var serverKeepAlive = DefaultConnectionKeepAliveStrategy.INSTANCE.getKeepAliveDuration(response, context);
long actualKeepAlive;
if (serverKeepAlive <= -1) {
actualKeepAlive = userConfiguredKeepAlive;
} else if (userConfiguredKeepAlive <= -1) {
actualKeepAlive = serverKeepAlive;
} else {
actualKeepAlive = Math.min(serverKeepAlive, userConfiguredKeepAlive);
}
if (actualKeepAlive < -1) {
actualKeepAlive = -1;
}
LOGGER.debug("effective HTTP connection keep-alive: [{}]ms", actualKeepAlive);
return actualKeepAlive;
};
}

/*
* Creates an {@link IDTokenValidator} based on the current Relying Party configuration
*/
Expand Down
Loading