NIFI-7333 Added OIDC trust store strategy property

This closes #5753

Signed-off-by: David Handermann <exceptionfactory@apache.org>
This commit is contained in:
Nathan Gough 2022-02-07 21:46:46 -05:00 committed by exceptionfactory
parent a8fd5993eb
commit 7ef2fd2986
No known key found for this signature in database
GPG Key ID: 29B6A52D2AAE8DBA
7 changed files with 122 additions and 28 deletions

View File

@ -188,6 +188,7 @@ public class NiFiProperties extends ApplicationProperties {
public static final String SECURITY_USER_OIDC_READ_TIMEOUT = "nifi.security.user.oidc.read.timeout";
public static final String SECURITY_USER_OIDC_CLIENT_ID = "nifi.security.user.oidc.client.id";
public static final String SECURITY_USER_OIDC_CLIENT_SECRET = "nifi.security.user.oidc.client.secret";
public static final String SECURITY_USER_OIDC_TRUSTSTORE_STRATEGY = "nifi.security.user.oidc.truststore.strategy";
public static final String SECURITY_USER_OIDC_PREFERRED_JWSALGORITHM = "nifi.security.user.oidc.preferred.jwsalgorithm";
public static final String SECURITY_USER_OIDC_ADDITIONAL_SCOPES = "nifi.security.user.oidc.additional.scopes";
public static final String SECURITY_USER_OIDC_CLAIM_IDENTIFYING_USER = "nifi.security.user.oidc.claim.identifying.user";
@ -369,6 +370,7 @@ public class NiFiProperties extends ApplicationProperties {
public static final String DEFAULT_FLOW_CONFIGURATION_ARCHIVE_MAX_STORAGE = "500 MB";
public static final String DEFAULT_SECURITY_USER_OIDC_CONNECT_TIMEOUT = "5 secs";
public static final String DEFAULT_SECURITY_USER_OIDC_READ_TIMEOUT = "5 secs";
public static final String DEFAULT_SECURITY_USER_OIDC_TRUSTSTORE_STRATEGY = "JDK";
public static final String DEFAULT_SECURITY_USER_SAML_METADATA_SIGNING_ENABLED = "false";
public static final String DEFAULT_SECURITY_USER_SAML_REQUEST_SIGNING_ENABLED = "false";
public static final String DEFAULT_SECURITY_USER_SAML_WANT_ASSERTIONS_SIGNED = "true";
@ -1121,6 +1123,10 @@ public class NiFiProperties extends ApplicationProperties {
}
}
public String getOidcClientTruststoreStrategy() {
return getProperty(SECURITY_USER_OIDC_TRUSTSTORE_STRATEGY, DEFAULT_SECURITY_USER_OIDC_TRUSTSTORE_STRATEGY);
}
public boolean shouldSendServerVersion() {
return Boolean.parseBoolean(getProperty(WEB_SHOULD_SEND_SERVER_VERSION, DEFAULT_WEB_SHOULD_SEND_SERVER_VERSION));
}

View File

@ -448,6 +448,7 @@ JSON Web Key (JWK) provided through the jwks_uri in the metadata found at the di
|`nifi.security.user.oidc.additional.scopes` | Comma separated scopes that are sent to OpenId Connect Provider in addition to `openid` and `email`.
|`nifi.security.user.oidc.claim.identifying.user` | Claim that identifies the user to be logged in; default is `email`. May need to be requested via the `nifi.security.user.oidc.additional.scopes` before usage.
|`nifi.security.user.oidc.fallback.claims.identifying.user` | Comma separated possible fallback claims used to identify the user in case `nifi.security.user.oidc.claim.identifying.user` claim is not present for the login user.
|`nifi.security.user.oidc.truststore.strategy` | If value is `NIFI`, use the NiFi truststore when connecting to the OIDC service, otherwise if value is `JDK` use Java's default `cacerts` truststore. The default value is `JDK`.
|==================================================================================================================================================
[[saml]]

View File

@ -169,6 +169,7 @@
<nifi.security.user.oidc.additional.scopes />
<nifi.security.user.oidc.claim.identifying.user />
<nifi.security.user.oidc.fallback.claims.identifying.user />
<nifi.security.user.oidc.truststore.strategy>JDK</nifi.security.user.oidc.truststore.strategy>
<!-- nifi.properties: apache knox -->
<nifi.security.user.knox.url />

View File

@ -200,6 +200,7 @@ nifi.security.user.oidc.preferred.jwsalgorithm=${nifi.security.user.oidc.preferr
nifi.security.user.oidc.additional.scopes=${nifi.security.user.oidc.additional.scopes}
nifi.security.user.oidc.claim.identifying.user=${nifi.security.user.oidc.claim.identifying.user}
nifi.security.user.oidc.fallback.claims.identifying.user=${nifi.security.user.oidc.fallback.claims.identifying.user}
nifi.security.user.oidc.truststore.strategy=${nifi.security.user.oidc.truststore.strategy}
# Apache Knox SSO Properties #
nifi.security.user.knox.url=${nifi.security.user.knox.url}

View File

@ -38,16 +38,21 @@ import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.nifi.authentication.exception.AuthenticationNotSupportedException;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.security.util.SslContextFactory;
import org.apache.nifi.security.util.StandardTlsConfiguration;
import org.apache.nifi.security.util.TlsConfiguration;
import org.apache.nifi.security.util.TlsException;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.api.cookie.ApplicationCookieName;
import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
import org.apache.nifi.web.security.oidc.OIDCEndpoints;
import org.apache.nifi.web.security.oidc.OidcService;
import org.apache.nifi.web.security.oidc.TruststoreStrategy;
import org.apache.nifi.web.security.token.LoginAuthenticationToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PreDestroy;
import javax.net.ssl.SSLContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
@ -89,19 +94,9 @@ public class OIDCAccessResource extends ApplicationResource {
private OidcService oidcService;
private BearerTokenProvider bearerTokenProvider;
private final CloseableHttpClient httpClient;
public OIDCAccessResource() {
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(msTimeout)
.setConnectionRequestTimeout(msTimeout)
.setSocketTimeout(msTimeout)
.build();
httpClient = HttpClientBuilder
.create()
.setDefaultRequestConfig(config)
.build();
}
@GET
@ -437,7 +432,7 @@ public class OIDCAccessResource extends ApplicationResource {
* @throws IOException exceptional case for communication error with the OpenId Connect Provider
*/
private void revokeEndpointRequest(@Context HttpServletResponse httpServletResponse, String accessToken, URI revokeEndpoint) throws IOException {
final CloseableHttpClient httpClient = getHttpClient();
HttpPost httpPost = new HttpPost(revokeEndpoint);
List<NameValuePair> params = new ArrayList<>();
@ -455,12 +450,27 @@ public class OIDCAccessResource extends ApplicationResource {
logger.error("There was an error logging out of the OpenId Connect Provider. " +
"Response status: " + response.getStatusLine().getStatusCode());
}
} finally {
httpClient.close();
}
}
@PreDestroy
public void closeClient() throws IOException {
httpClient.close();
private CloseableHttpClient getHttpClient() {
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(msTimeout)
.setConnectionRequestTimeout(msTimeout)
.setSocketTimeout(msTimeout)
.build();
HttpClientBuilder builder = HttpClientBuilder
.create()
.setDefaultRequestConfig(config);
if (TruststoreStrategy.NIFI.name().equals(properties.getOidcClientTruststoreStrategy())) {
builder.setSSLContext(getSslContext());
}
return builder.build();
}
private AuthenticationResponse parseOidcResponse(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, boolean isLogin) throws Exception {
@ -515,6 +525,15 @@ public class OIDCAccessResource extends ApplicationResource {
}
}
private SSLContext getSslContext() {
TlsConfiguration tlsConfiguration = StandardTlsConfiguration.fromNiFiProperties(properties);
try {
return SslContextFactory.createSslContext(tlsConfiguration);
} catch (TlsException e) {
throw new RuntimeException("Unable to establish an SSL context for OIDC access resource from nifi.properties", e);
}
}
private String getForwardPageTitle(boolean isLogin) {
return isLogin ? ApplicationResource.LOGIN_ERROR_TITLE : ApplicationResource.LOGOUT_ERROR_TITLE;
}

View File

@ -53,6 +53,20 @@ import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
import com.nimbusds.openid.connect.sdk.validators.AccessTokenValidator;
import com.nimbusds.openid.connect.sdk.validators.IDTokenValidator;
import com.nimbusds.openid.connect.sdk.validators.InvalidHashException;
import net.minidev.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authentication.exception.IdentityAccessException;
import org.apache.nifi.security.util.SslContextFactory;
import org.apache.nifi.security.util.StandardTlsConfiguration;
import org.apache.nifi.security.util.TlsConfiguration;
import org.apache.nifi.security.util.TlsException;
import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.token.LoginAuthenticationToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
@ -63,14 +77,6 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import net.minidev.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authentication.exception.IdentityAccessException;
import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.token.LoginAuthenticationToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
@ -88,6 +94,7 @@ public class StandardOidcIdentityProvider implements OidcIdentityProvider {
private IDTokenValidator tokenValidator;
private ClientID clientId;
private Secret clientSecret;
private SSLContext sslContext;
/**
* Creates a new StandardOidcIdentityProvider.
@ -110,6 +117,11 @@ public class StandardOidcIdentityProvider implements OidcIdentityProvider {
return;
}
// Set up trust store SSLContext
if (TruststoreStrategy.NIFI.name().equals(properties.getOidcClientTruststoreStrategy())) {
setSslContext();
}
validateOIDCConfiguration();
try {
@ -122,6 +134,15 @@ public class StandardOidcIdentityProvider implements OidcIdentityProvider {
validateOIDCProviderMetadata();
}
private void setSslContext() {
TlsConfiguration tlsConfiguration = StandardTlsConfiguration.fromNiFiProperties(properties);
try {
this.sslContext = SslContextFactory.createSslContext(tlsConfiguration);
} catch (TlsException e) {
throw new RuntimeException("Unable to establish an SSL context for OIDC identity provider from nifi.properties", e);
}
}
/**
* Validates the retrieved OIDC provider metadata.
*/
@ -166,7 +187,7 @@ public class StandardOidcIdentityProvider implements OidcIdentityProvider {
} else if (JWSAlgorithm.HS256.equals(preferredJwsAlgorithm) || JWSAlgorithm.HS384.equals(preferredJwsAlgorithm) || JWSAlgorithm.HS512.equals(preferredJwsAlgorithm)) {
tokenValidator = new IDTokenValidator(oidcProviderMetadata.getIssuer(), clientId, preferredJwsAlgorithm, clientSecret);
} else {
final ResourceRetriever retriever = new DefaultResourceRetriever(oidcConnectTimeout, oidcReadTimeout);
final ResourceRetriever retriever = getResourceRetriever();
tokenValidator = new IDTokenValidator(oidcProviderMetadata.getIssuer(), clientId, preferredJwsAlgorithm, oidcProviderMetadata.getJWKSetURI().toURL(), retriever);
}
} catch (final Exception e) {
@ -245,9 +266,7 @@ public class StandardOidcIdentityProvider implements OidcIdentityProvider {
private OIDCProviderMetadata retrieveOidcProviderMetadata(final String discoveryUri) throws IOException, ParseException {
final URL url = new URL(discoveryUri);
final HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.GET, url);
httpRequest.setConnectTimeout(oidcConnectTimeout);
httpRequest.setReadTimeout(oidcReadTimeout);
setHttpRequestProperties(httpRequest);
final HTTPResponse httpResponse = httpRequest.send();
if (httpResponse.getStatusCode() != 200) {
@ -485,12 +504,26 @@ public class StandardOidcIdentityProvider implements OidcIdentityProvider {
}
private HTTPRequest formHTTPRequest(Request request) {
final HTTPRequest httpRequest = request.toHTTPRequest();
return setHttpRequestProperties(request.toHTTPRequest());
}
private HTTPRequest setHttpRequestProperties(final HTTPRequest httpRequest) {
httpRequest.setConnectTimeout(oidcConnectTimeout);
httpRequest.setReadTimeout(oidcReadTimeout);
if (TruststoreStrategy.NIFI.name().equals(properties.getOidcClientTruststoreStrategy())) {
httpRequest.setSSLSocketFactory(sslContext.getSocketFactory());
}
return httpRequest;
}
private ResourceRetriever getResourceRetriever() {
if (TruststoreStrategy.NIFI.name().equals(properties.getOidcClientTruststoreStrategy())) {
return new DefaultResourceRetriever(oidcConnectTimeout, oidcReadTimeout, 0, true, sslContext.getSocketFactory());
} else {
return new DefaultResourceRetriever(oidcConnectTimeout, oidcReadTimeout);
}
}
private ClientAuthentication createClientAuthentication() {
final ClientAuthentication clientAuthentication;
List<ClientAuthenticationMethod> authMethods = oidcProviderMetadata.getTokenEndpointAuthMethods();

View File

@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.nifi.web.security.oidc;
/**
* Indicates which truststore should be used when creating an HttpClient for an https URL.
*/
public enum TruststoreStrategy {
/**
* Use the JDK truststore.
*/
JDK,
/**
* Use NiFi's truststore specified in nifi.properties.
*/
NIFI;
}