You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ex...@apache.org on 2022/02/18 20:22:39 UTC

[nifi] branch main updated: NIFI-7333 Added OIDC trust store strategy property

This is an automated email from the ASF dual-hosted git repository.

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 7ef2fd2  NIFI-7333 Added OIDC trust store strategy property
7ef2fd2 is described below

commit 7ef2fd2986461b632e398b7ddff47db153c3c0e5
Author: Nathan Gough <th...@gmail.com>
AuthorDate: Mon Feb 7 21:46:46 2022 -0500

    NIFI-7333 Added OIDC trust store strategy property
    
    This closes #5753
    
    Signed-off-by: David Handermann <ex...@apache.org>
---
 .../java/org/apache/nifi/util/NiFiProperties.java  |  6 +++
 .../src/main/asciidoc/administration-guide.adoc    |  1 +
 .../nifi-framework/nifi-resources/pom.xml          |  1 +
 .../src/main/resources/conf/nifi.properties        |  1 +
 .../apache/nifi/web/api/OIDCAccessResource.java    | 49 ++++++++++++------
 .../oidc/StandardOidcIdentityProvider.java         | 59 +++++++++++++++++-----
 .../nifi/web/security/oidc/TruststoreStrategy.java | 33 ++++++++++++
 7 files changed, 122 insertions(+), 28 deletions(-)

diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
index 3ed2801..6533dec 100644
--- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
+++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -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));
     }
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index b13961f..6501c27 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -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]]
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
index 7e9747c..df08858 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
@@ -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 />
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
index 0b90803..f260928 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
@@ -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}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java
index cc952a7..89abed0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java
@@ -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;
     }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/StandardOidcIdentityProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/StandardOidcIdentityProvider.java
index eaadd55..09fa79e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/StandardOidcIdentityProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/StandardOidcIdentityProvider.java
@@ -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();
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/TruststoreStrategy.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/TruststoreStrategy.java
new file mode 100644
index 0000000..43e1606
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/TruststoreStrategy.java
@@ -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;
+}