NIFI-7170:

- Adding a flag to nifi.properties to disable anonymous authentication.

NIFI-7170:
- Fixing checkstyle issues.

NIFI-7170:
- Adding missing license header.

NIFI-7170:
- Initial PR feedback.

NIFI-7170:
- Fixing broken integration tests.
- Creating new integration tests for verifying allowing and preventing anonymous access.

NIFI-7170:
- Ensuring the new anonymous authentication property is considered for proxied requests.

NIFI-7170 - Fixed comment.

Signed-off-by: Nathan Gough <thenatog@gmail.com>

This closes #4099.
This commit is contained in:
Matt Gilman 2020-02-19 15:49:44 -05:00 committed by Nathan Gough
parent 7784178abd
commit e81960f8e8
23 changed files with 952 additions and 145 deletions

View File

@ -153,6 +153,7 @@ public abstract class NiFiProperties {
public static final String SECURITY_TRUSTSTORE_TYPE = "nifi.security.truststoreType";
public static final String SECURITY_TRUSTSTORE_PASSWD = "nifi.security.truststorePasswd";
public static final String SECURITY_USER_AUTHORIZER = "nifi.security.user.authorizer";
public static final String SECURITY_ANONYMOUS_AUTHENTICATION = "nifi.security.allow.anonymous.authentication";
public static final String SECURITY_USER_LOGIN_IDENTITY_PROVIDER = "nifi.security.user.login.identity.provider";
public static final String SECURITY_OCSP_RESPONDER_URL = "nifi.security.ocsp.responder.url";
public static final String SECURITY_OCSP_RESPONDER_CERTIFICATE = "nifi.security.ocsp.responder.certificate";
@ -919,10 +920,19 @@ public abstract class NiFiProperties {
return !StringUtils.isBlank(getProperty(NiFiProperties.SECURITY_USER_LOGIN_IDENTITY_PROVIDER));
}
/**
* @return True if property value is 'true'; False otherwise.
*/
public Boolean isAnonymousAuthenticationAllowed() {
final String anonymousAuthenticationAllowed = getProperty(SECURITY_ANONYMOUS_AUTHENTICATION, "false");
return "true".equalsIgnoreCase(anonymousAuthenticationAllowed);
}
/**
* Returns whether an OpenId Connect (OIDC) URL is set.
*
* @return whether an OpenId Connection URL is set
* @return whether an OpenId Connect URL is set
*/
public boolean isOidcEnabled() {
return !StringUtils.isBlank(getOidcDiscoveryUrl());
@ -1066,12 +1076,13 @@ public abstract class NiFiProperties {
* - Kerberos service support is not enabled
* - openid connect is not enabled
* - knox sso is not enabled
* - anonymous authentication is not enabled
* </p>
*
* @return true if client certificates are required for access to the REST API
*/
public boolean isClientAuthRequiredForRestApi() {
return !isLoginIdentityProviderEnabled() && !isKerberosSpnegoSupportEnabled() && !isOidcEnabled() && !isKnoxSsoEnabled();
return !isLoginIdentityProviderEnabled() && !isKerberosSpnegoSupportEnabled() && !isOidcEnabled() && !isKnoxSsoEnabled() && !isAnonymousAuthenticationAllowed();
}
public InetSocketAddress getNodeApiAddress() {

View File

@ -231,7 +231,16 @@ token during authentication.
NOTE: NiFi can only be configured for username/password, OpenId Connect, or Apache Knox at a given time. It does not support running each of
these concurrently. NiFi will require client certificates for authenticating users over HTTPS if none of these are configured.
A secured instance of NiFi cannot be accessed anonymously unless configured to use an <<ldap_login_identity_provider>> or <<kerberos_login_identity_provider>> Login Identity Provider, which in turn must be configured to explicitly allow anonymous access. Anonymous access is not currently possible by the default FileAuthorizer (see <<authorizer-configuration>>), but is a future effort (link:https://issues.apache.org/jira/browse/NIFI-2730[NIFI-2730^]).
A user cannot anonymously authenticate with a secured instance of NiFi unless `nifi.security.allow.anonymous.authentication` is set to `true`.
If this is the case, NiFi must also be configured with an Authorizer that supports authorizing an anonymous user. Currently, NiFi does not ship
with any Authorizers that support this. There is a feature request here to help support it (link:https://issues.apache.org/jira/browse/NIFI-2730[NIFI-2730^]).
There are three scenarios to consider when setting `nifi.security.allow.anonymous.authentication`. When the user is directly calling an endpoint
with no attempted authentication then `nifi.security.allow.anonymous.authentication` will control whether the request is authenticated or rejected.
The other two scenarios are when the request is proxied. This could either be proxied by a NiFi node (e.g. a node in the NiFi cluster) or by a separate
proxy that is proxying a request for an anonymous user. In these proxy scenarios `nifi.security.allow.anonymous.authentication` will control whether the
request is authenticated or rejected. In all three of these scenarios if the request is authenticated it will subsequently be subjected to normal
authorization based on the requested resource.
NOTE: NiFi does not perform user authentication over HTTP. Using HTTP, all users will be granted all roles.
@ -3289,6 +3298,7 @@ These properties pertain to various security features in NiFi. Many of these pro
|`nifi.security.truststoreType`|The truststore type. It is blank by default.
|`nifi.security.truststorePasswd`|The truststore password. It is blank by default.
|`nifi.security.user.authorizer`|Specifies which of the configured Authorizers in the _authorizers.xml_ file to use. By default, it is set to `file-provider`.
|`nifi.security.allow.anonymous.authentication`|Whether anonymous authentication is allowed when running over HTTPS. If set to true, client certificates are not required to connect via TLS.
|`nifi.security.user.login.identity.provider`|This indicates what type of login identity provider to use. The default value is blank, can be set to the identifier from a provider
in the file specified in `nifi.login.identity.provider.configuration.file`. Setting this property will trigger NiFi to support username/password authentication.
|`nifi.security.ocsp.responder.url`|This is the URL for the Online Certificate Status Protocol (OCSP) responder if one is being used. It is blank by default.

View File

@ -35,6 +35,7 @@ public class NiFiPropertiesDiagnosticTask implements DiagnosticTask {
"nifi.ui.autorefresh.interval",
"nifi.cluster.node.protocol.max.threads",
"nifi.cluster.node.protocol.threads",
"nifi.security.allow.anonymous.authentication",
"nifi.security.user.login.identity.provider",
"nifi.security.user.authorizer",
"nifi.provenance.repository.implementation",

View File

@ -155,6 +155,7 @@
<nifi.security.truststoreType />
<nifi.security.truststorePasswd />
<nifi.security.user.authorizer>managed-authorizer</nifi.security.user.authorizer>
<nifi.security.allow.anonymous.authentication>false</nifi.security.allow.anonymous.authentication>
<nifi.security.user.login.identity.provider />
<nifi.security.x509.principal.extractor />
<nifi.security.ocsp.responder.url />

View File

@ -170,6 +170,7 @@ nifi.security.truststore=${nifi.security.truststore}
nifi.security.truststoreType=${nifi.security.truststoreType}
nifi.security.truststorePasswd=${nifi.security.truststorePasswd}
nifi.security.user.authorizer=${nifi.security.user.authorizer}
nifi.security.allow.anonymous.authentication=${nifi.security.allow.anonymous.authentication}
nifi.security.user.login.identity.provider=${nifi.security.user.login.identity.provider}
nifi.security.ocsp.responder.url=${nifi.security.ocsp.responder.url}
nifi.security.ocsp.responder.certificate=${nifi.security.ocsp.responder.certificate}

View File

@ -17,7 +17,8 @@
package org.apache.nifi.web;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.anonymous.NiFiAnonymousUserFilter;
import org.apache.nifi.web.security.anonymous.NiFiAnonymousAuthenticationFilter;
import org.apache.nifi.web.security.anonymous.NiFiAnonymousAuthenticationProvider;
import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter;
import org.apache.nifi.web.security.jwt.JwtAuthenticationProvider;
import org.apache.nifi.web.security.knox.KnoxAuthenticationFilter;
@ -76,7 +77,8 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
private KnoxAuthenticationFilter knoxAuthenticationFilter;
private KnoxAuthenticationProvider knoxAuthenticationProvider;
private NiFiAnonymousUserFilter anonymousAuthenticationFilter;
private NiFiAnonymousAuthenticationFilter anonymousAuthenticationFilter;
private NiFiAnonymousAuthenticationProvider anonymousAuthenticationProvider;
public NiFiWebApiSecurityConfiguration() {
super(true); // disable defaults
@ -118,7 +120,10 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
http.addFilterBefore(knoxFilterBean(), AnonymousAuthenticationFilter.class);
// anonymous
http.anonymous().authenticationFilter(anonymousFilterBean());
http.addFilterAfter(anonymousFilterBean(), AnonymousAuthenticationFilter.class);
// disable default anonymous handling because it doesn't handle conditional authentication well
http.anonymous().disable();
}
@ -144,7 +149,8 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
.authenticationProvider(x509AuthenticationProvider)
.authenticationProvider(jwtAuthenticationProvider)
.authenticationProvider(otpAuthenticationProvider)
.authenticationProvider(knoxAuthenticationProvider);
.authenticationProvider(knoxAuthenticationProvider)
.authenticationProvider(anonymousAuthenticationProvider);
}
@Bean
@ -190,9 +196,11 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
}
@Bean
public NiFiAnonymousUserFilter anonymousFilterBean() throws Exception {
public NiFiAnonymousAuthenticationFilter anonymousFilterBean() throws Exception {
if (anonymousAuthenticationFilter == null) {
anonymousAuthenticationFilter = new NiFiAnonymousUserFilter();
anonymousAuthenticationFilter = new NiFiAnonymousAuthenticationFilter();
anonymousAuthenticationFilter.setProperties(properties);
anonymousAuthenticationFilter.setAuthenticationManager(authenticationManager());
}
return anonymousAuthenticationFilter;
}
@ -217,6 +225,11 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
this.knoxAuthenticationProvider = knoxAuthenticationProvider;
}
@Autowired
public void setAnonymousAuthenticationProvider(NiFiAnonymousAuthenticationProvider anonymousAuthenticationProvider) {
this.anonymousAuthenticationProvider = anonymousAuthenticationProvider;
}
@Autowired
public void setX509AuthenticationProvider(X509AuthenticationProvider x509AuthenticationProvider) {
this.x509AuthenticationProvider = x509AuthenticationProvider;

View File

@ -28,6 +28,7 @@ import org.apache.nifi.nar.NarUnpacker;
import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
import org.apache.nifi.nar.SystemBundle;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.util.StringUtils;
import javax.ws.rs.core.Response;
import java.io.File;
@ -52,6 +53,7 @@ public class AccessControlHelper {
private NiFiTestUser noneUser;
private NiFiTestUser privilegedUser;
private NiFiTestUser executeCodeUser;
private NiFiTestUser anonymousUser;
private static final String CONTEXT_PATH = "/nifi-api";
@ -67,7 +69,8 @@ public class AccessControlHelper {
// configure the location of the nifi properties
File nifiPropertiesFile = new File(nifiPropertiesPath);
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, nifiPropertiesFile.getAbsolutePath());
NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null, null);
NiFiProperties props = NiFiProperties.createBasicNiFiProperties(nifiPropertiesPath, null);
flowXmlPath = props.getProperty(NiFiProperties.FLOW_CONFIGURATION_FILE);
final File libTargetDir = new File("target/test-classes/access-control/lib");
@ -103,6 +106,7 @@ public class AccessControlHelper {
noneUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.NONE_USER_DN);
privilegedUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.PRIVILEGED_USER_DN);
executeCodeUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.EXECUTED_CODE_USER_DN);
anonymousUser = new NiFiTestUser(server.getClient(), StringUtils.EMPTY);
// populate the initial data flow
NiFiWebApiTest.populateFlow(server.getClient(), baseUrl, readWriteUser, READ_WRITE_CLIENT_ID);
@ -132,6 +136,10 @@ public class AccessControlHelper {
return executeCodeUser;
}
public NiFiTestUser getAnonymousUser() {
return anonymousUser;
}
public void testGenericGetUri(final String uri) throws Exception {
Response response;

View File

@ -16,21 +16,8 @@
*/
package org.apache.nifi.integration.accesscontrol;
import org.apache.nifi.web.security.jwt.JwtServiceTest;
import net.minidev.json.JSONObject;
import org.apache.commons.io.FileUtils;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.integration.util.NiFiTestServer;
import org.apache.nifi.integration.util.NiFiTestUser;
import org.apache.nifi.integration.util.SourceTestProcessor;
import org.apache.nifi.nar.ExtensionDiscoveringManager;
import org.apache.nifi.nar.ExtensionManagerHolder;
import org.apache.nifi.nar.NarClassLoadersHolder;
import org.apache.nifi.nar.NarUnpacker;
import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
import org.apache.nifi.nar.SystemBundle;
import org.apache.nifi.security.util.SslContextFactory;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.api.dto.AccessConfigurationDTO;
import org.apache.nifi.web.api.dto.AccessStatusDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO;
@ -38,18 +25,13 @@ import org.apache.nifi.web.api.dto.RevisionDTO;
import org.apache.nifi.web.api.entity.AccessConfigurationEntity;
import org.apache.nifi.web.api.entity.AccessStatusEntity;
import org.apache.nifi.web.api.entity.ProcessorEntity;
import org.apache.nifi.web.util.WebUtils;
import org.apache.nifi.web.security.jwt.JwtServiceTest;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.net.ssl.SSLContext;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Response;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedHashMap;
@ -61,63 +43,15 @@ import java.util.StringJoiner;
*/
public class ITAccessTokenEndpoint {
private static OneWaySslAccessControlHelper helper;
private final String user = "unregistered-user@nifi";
private final String password = "password";
private static final String CLIENT_ID = "token-endpoint-id";
private static final String CONTEXT_PATH = "/nifi-api";
private static String flowXmlPath;
private static NiFiTestServer SERVER;
private static NiFiTestUser TOKEN_USER;
private static String BASE_URL;
@BeforeClass
public static void setup() throws Exception {
// configure the location of the nifi properties
File nifiPropertiesFile = new File("src/test/resources/access-control/nifi.properties");
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, nifiPropertiesFile.getAbsolutePath());
NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null, null);
flowXmlPath = props.getProperty(NiFiProperties.FLOW_CONFIGURATION_FILE);
// delete the database directory to avoid issues with re-registration in testRequestAccessUsingToken
FileUtils.deleteDirectory(props.getDatabaseRepositoryPath().toFile());
final File libTargetDir = new File("target/test-classes/access-control/lib");
libTargetDir.mkdirs();
final File libSourceDir = new File("src/test/resources/lib");
for (final File libFile : libSourceDir.listFiles()) {
final File libDestFile = new File(libTargetDir, libFile.getName());
Files.copy(libFile.toPath(), libDestFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
final Bundle systemBundle = SystemBundle.create(props);
NarUnpacker.unpackNars(props, systemBundle);
NarClassLoadersHolder.getInstance().init(props.getFrameworkWorkingDirectory(), props.getExtensionsWorkingDirectory());
// load extensions
final ExtensionDiscoveringManager extensionManager = new StandardExtensionDiscoveringManager();
extensionManager.discoverExtensions(systemBundle, NarClassLoadersHolder.getInstance().getBundles());
ExtensionManagerHolder.init(extensionManager);
// start the server
SERVER = new NiFiTestServer("src/main/webapp", CONTEXT_PATH, props);
SERVER.startServer();
SERVER.loadFlow();
// get the base url
BASE_URL = SERVER.getBaseUrl() + CONTEXT_PATH;
// create the user
final Client client = WebUtils.createClient(null, createTrustContext(props));
TOKEN_USER = new NiFiTestUser(client, null);
}
private static SSLContext createTrustContext(final NiFiProperties props) throws Exception {
return SslContextFactory.createTrustSslContext(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE),
props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD).toCharArray(),
props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE), "TLS");
helper = new OneWaySslAccessControlHelper();
}
// -----------
@ -130,9 +64,9 @@ public class ITAccessTokenEndpoint {
*/
@Test
public void testGetAccessConfig() throws Exception {
String url = BASE_URL + "/access/config";
String url = helper.getBaseUrl() + "/access/config";
Response response = TOKEN_USER.testGet(url);
Response response = helper.getUser().testGet(url);
// ensure the request is successful
Assert.assertEquals(200, response.getStatus());
@ -157,9 +91,9 @@ public class ITAccessTokenEndpoint {
*/
@Test
public void testCreateProcessorUsingToken() throws Exception {
String url = BASE_URL + "/access/token";
String url = helper.getBaseUrl() + "/access/token";
Response response = TOKEN_USER.testCreateToken(url, "user@nifi", "whatever");
Response response = helper.getUser().testCreateToken(url, "user@nifi", "whatever");
// ensure the request is successful
Assert.assertEquals(201, response.getStatus());
@ -172,7 +106,7 @@ public class ITAccessTokenEndpoint {
}
private ProcessorDTO createProcessor(final String token) throws Exception {
String url = BASE_URL + "/process-groups/root/processors";
String url = helper.getBaseUrl() + "/process-groups/root/processors";
// authorization header
Map<String, String> headers = new HashMap<>();
@ -194,7 +128,7 @@ public class ITAccessTokenEndpoint {
entity.setComponent(processor);
// perform the request
Response response = TOKEN_USER.testPostWithHeaders(url, entity, headers);
Response response = helper.getUser().testPostWithHeaders(url, entity, headers);
// ensure the request is successful
Assert.assertEquals(201, response.getStatus());
@ -217,9 +151,9 @@ public class ITAccessTokenEndpoint {
*/
@Test
public void testInvalidCredentials() throws Exception {
String url = BASE_URL + "/access/token";
String url = helper.getBaseUrl() + "/access/token";
Response response = TOKEN_USER.testCreateToken(url, "user@nifi", "not a real password");
Response response = helper.getUser().testCreateToken(url, "user@nifi", "not a real password");
// ensure the request is successful
Assert.assertEquals(400, response.getStatus());
@ -232,9 +166,9 @@ public class ITAccessTokenEndpoint {
*/
@Test
public void testUnknownUser() throws Exception {
String url = BASE_URL + "/access/token";
String url = helper.getBaseUrl() + "/access/token";
Response response = TOKEN_USER.testCreateToken(url, "not a real user", "not a real password");
Response response = helper.getUser().testCreateToken(url, "not a real user", "not a real password");
// ensure the request is successful
Assert.assertEquals(400, response.getStatus());
@ -247,10 +181,10 @@ public class ITAccessTokenEndpoint {
*/
@Test
public void testRequestAccessUsingToken() throws Exception {
String accessStatusUrl = BASE_URL + "/access";
String accessTokenUrl = BASE_URL + "/access/token";
String accessStatusUrl = helper.getBaseUrl() + "/access";
String accessTokenUrl = helper.getBaseUrl() + "/access/token";
Response response = TOKEN_USER.testGet(accessStatusUrl);
Response response = helper.getUser().testGet(accessStatusUrl);
// ensure the request is successful
Assert.assertEquals(200, response.getStatus());
@ -261,7 +195,7 @@ public class ITAccessTokenEndpoint {
// verify unknown
Assert.assertEquals("UNKNOWN", accessStatus.getStatus());
response = TOKEN_USER.testCreateToken(accessTokenUrl, user, password);
response = helper.getUser().testCreateToken(accessTokenUrl, user, password);
// ensure the request is successful
Assert.assertEquals(201, response.getStatus());
@ -274,7 +208,7 @@ public class ITAccessTokenEndpoint {
headers.put("Authorization", "Bearer " + token);
// check the status with the token
response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers);
response = helper.getUser().testGetWithHeaders(accessStatusUrl, null, headers);
// ensure the request is successful
Assert.assertEquals(200, response.getStatus());
@ -288,11 +222,11 @@ public class ITAccessTokenEndpoint {
@Test
public void testLogOutSuccess() throws Exception {
String accessStatusUrl = BASE_URL + "/access";
String accessTokenUrl = BASE_URL + "/access/token";
String logoutUrl = BASE_URL + "/access/logout";
String accessStatusUrl = helper.getBaseUrl() + "/access";
String accessTokenUrl = helper.getBaseUrl() + "/access/token";
String logoutUrl = helper.getBaseUrl() + "/access/logout";
Response response = TOKEN_USER.testGet(accessStatusUrl);
Response response = helper.getUser().testGet(accessStatusUrl);
// ensure the request is successful
Assert.assertEquals(200, response.getStatus());
@ -303,7 +237,7 @@ public class ITAccessTokenEndpoint {
// verify unknown
Assert.assertEquals("UNKNOWN", accessStatus.getStatus());
response = TOKEN_USER.testCreateToken(accessTokenUrl, user, password);
response = helper.getUser().testCreateToken(accessTokenUrl, user, password);
// ensure the request is successful
Assert.assertEquals(201, response.getStatus());
@ -316,7 +250,7 @@ public class ITAccessTokenEndpoint {
headers.put("Authorization", "Bearer " + token);
// check the status with the token
response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers);
response = helper.getUser().testGetWithHeaders(accessStatusUrl, null, headers);
// ensure the request is successful
Assert.assertEquals(200, response.getStatus());
@ -329,21 +263,21 @@ public class ITAccessTokenEndpoint {
// log out
response = TOKEN_USER.testGetWithHeaders(logoutUrl, null, headers);
response = helper.getUser().testDeleteWithHeaders(logoutUrl, headers);
Assert.assertEquals(200, response.getStatus());
// ensure we can no longer use our token
response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers);
response = helper.getUser().testGetWithHeaders(accessStatusUrl, null, headers);
Assert.assertEquals(401, response.getStatus());
}
@Test
public void testLogOutNoTokenHeader() throws Exception {
String accessStatusUrl = BASE_URL + "/access";
String accessTokenUrl = BASE_URL + "/access/token";
String logoutUrl = BASE_URL + "/access/logout";
String accessStatusUrl = helper.getBaseUrl() + "/access";
String accessTokenUrl = helper.getBaseUrl() + "/access/token";
String logoutUrl = helper.getBaseUrl() + "/access/logout";
Response response = TOKEN_USER.testGet(accessStatusUrl);
Response response = helper.getUser().testGet(accessStatusUrl);
// ensure the request is successful
Assert.assertEquals(200, response.getStatus());
@ -354,7 +288,7 @@ public class ITAccessTokenEndpoint {
// verify unknown
Assert.assertEquals("UNKNOWN", accessStatus.getStatus());
response = TOKEN_USER.testCreateToken(accessTokenUrl, user, password);
response = helper.getUser().testCreateToken(accessTokenUrl, user, password);
// ensure the request is successful
Assert.assertEquals(201, response.getStatus());
@ -367,7 +301,7 @@ public class ITAccessTokenEndpoint {
headers.put("Authorization", "Bearer " + token);
// check the status with the token
response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers);
response = helper.getUser().testGetWithHeaders(accessStatusUrl, null, headers);
// ensure the request is successful
Assert.assertEquals(200, response.getStatus());
@ -380,8 +314,8 @@ public class ITAccessTokenEndpoint {
// log out should fail as we provided no token for logout to use
response = TOKEN_USER.testGetWithHeaders(logoutUrl, null, null);
Assert.assertEquals(500, response.getStatus());
response = helper.getUser().testDeleteWithHeaders(logoutUrl, null);
Assert.assertEquals(401, response.getStatus());
}
@Test
@ -405,11 +339,11 @@ public class ITAccessTokenEndpoint {
claims.put("iat", TOKEN_ISSUED_AT);
final String EXPECTED_PAYLOAD = new JSONObject(claims).toString();
String accessStatusUrl = BASE_URL + "/access";
String accessTokenUrl = BASE_URL + "/access/token";
String logoutUrl = BASE_URL + "/access/logout";
String accessStatusUrl = helper.getBaseUrl() + "/access";
String accessTokenUrl = helper.getBaseUrl() + "/access/token";
String logoutUrl = helper.getBaseUrl() + "/access/logout";
Response response = TOKEN_USER.testCreateToken(accessTokenUrl, user, password);
Response response = helper.getUser().testCreateToken(accessTokenUrl, user, password);
// ensure the request is successful
Assert.assertEquals(201, response.getStatus());
@ -419,7 +353,7 @@ public class ITAccessTokenEndpoint {
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + token);
// check the status with the token
response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers);
response = helper.getUser().testGetWithHeaders(accessStatusUrl, null, headers);
Assert.assertEquals(200, response.getStatus());
// Generate a token that will not match signatures with the generated token.
@ -428,7 +362,7 @@ public class ITAccessTokenEndpoint {
badHeaders.put("Authorization", "Bearer " + UNKNOWN_USER_TOKEN);
// Log out should fail as we provide a bad token to use, signatures will mismatch
response = TOKEN_USER.testGetWithHeaders(logoutUrl, null, badHeaders);
response = helper.getUser().testGetWithHeaders(logoutUrl, null, badHeaders);
Assert.assertEquals(401, response.getStatus());
}
@ -442,10 +376,10 @@ public class ITAccessTokenEndpoint {
final long TOKEN_ISSUED_AT = currentTime;
final long TOKEN_EXPIRATION_SECONDS = currentTime + EXPIRATION_SECONDS;
String accessTokenUrl = BASE_URL + "/access/token";
String logoutUrl = BASE_URL + "/access/logout";
String accessTokenUrl = helper.getBaseUrl() + "/access/token";
String logoutUrl = helper.getBaseUrl() + "/access/logout";
Response response = TOKEN_USER.testCreateToken(accessTokenUrl, user, password);
Response response = helper.getUser().testCreateToken(accessTokenUrl, user, password);
// ensure the request is successful
Assert.assertEquals(201, response.getStatus());
// replace the user in the token with an unknown user
@ -477,20 +411,12 @@ public class ITAccessTokenEndpoint {
badHeaders.put("Authorization", "Bearer " + splicedUserToken);
// Log out should fail as we provide a bad token to use, signatures will mismatch
response = TOKEN_USER.testGetWithHeaders(logoutUrl, null, badHeaders);
response = helper.getUser().testGetWithHeaders(logoutUrl, null, badHeaders);
Assert.assertEquals(401, response.getStatus());
}
@AfterClass
public static void cleanup() throws Exception {
// shutdown the server
SERVER.shutdownServer();
SERVER = null;
// look for the flow.xml
File flow = new File(flowXmlPath);
if (flow.exists()) {
flow.delete();
}
helper.cleanup();
}
}

View File

@ -0,0 +1,123 @@
/*
* 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.integration.accesscontrol;
import org.apache.commons.io.FileUtils;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.integration.util.NiFiTestServer;
import org.apache.nifi.integration.util.NiFiTestUser;
import org.apache.nifi.nar.ExtensionDiscoveringManager;
import org.apache.nifi.nar.ExtensionManagerHolder;
import org.apache.nifi.nar.NarClassLoadersHolder;
import org.apache.nifi.nar.NarUnpacker;
import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
import org.apache.nifi.nar.SystemBundle;
import org.apache.nifi.security.util.SslContextFactory;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.util.WebUtils;
import javax.net.ssl.SSLContext;
import javax.ws.rs.client.Client;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
/**
* Access control test for the dfm user.
*/
public class OneWaySslAccessControlHelper {
private NiFiTestUser user;
private static final String CONTEXT_PATH = "/nifi-api";
private NiFiTestServer server;
private String baseUrl;
private String flowXmlPath;
public OneWaySslAccessControlHelper() throws Exception {
this("src/test/resources/access-control/nifi.properties");
}
public OneWaySslAccessControlHelper(final String nifiPropertiesPath) throws Exception {
// configure the location of the nifi properties
File nifiPropertiesFile = new File(nifiPropertiesPath);
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, nifiPropertiesFile.getAbsolutePath());
NiFiProperties props = NiFiProperties.createBasicNiFiProperties(nifiPropertiesPath, null);
flowXmlPath = props.getProperty(NiFiProperties.FLOW_CONFIGURATION_FILE);
// delete the database directory to avoid issues with re-registration in testRequestAccessUsingToken
FileUtils.deleteDirectory(props.getDatabaseRepositoryPath().toFile());
final File libTargetDir = new File("target/test-classes/access-control/lib");
libTargetDir.mkdirs();
final File libSourceDir = new File("src/test/resources/lib");
for (final File libFile : libSourceDir.listFiles()) {
final File libDestFile = new File(libTargetDir, libFile.getName());
Files.copy(libFile.toPath(), libDestFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
final Bundle systemBundle = SystemBundle.create(props);
NarUnpacker.unpackNars(props, systemBundle);
NarClassLoadersHolder.getInstance().init(props.getFrameworkWorkingDirectory(), props.getExtensionsWorkingDirectory());
// load extensions
final ExtensionDiscoveringManager extensionManager = new StandardExtensionDiscoveringManager();
extensionManager.discoverExtensions(systemBundle, NarClassLoadersHolder.getInstance().getBundles());
ExtensionManagerHolder.init(extensionManager);
// start the server
server = new NiFiTestServer("src/main/webapp", CONTEXT_PATH, props);
server.startServer();
server.loadFlow();
// get the base url
baseUrl = server.getBaseUrl() + CONTEXT_PATH;
// create the user
final Client client = WebUtils.createClient(null, createTrustContext(props));
user = new NiFiTestUser(client, null);
}
public NiFiTestUser getUser() {
return user;
}
public String getBaseUrl() {
return baseUrl;
}
private static SSLContext createTrustContext(final NiFiProperties props) throws Exception {
return SslContextFactory.createTrustSslContext(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE),
props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD).toCharArray(),
props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE), "TLS");
}
public void cleanup() throws Exception {
// shutdown the server
server.shutdownServer();
server = null;
// look for the flow.xml and toss it
File flow = new File(flowXmlPath);
if (flow.exists()) {
flow.delete();
}
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.integration.accesscontrol.anonymous;
import org.apache.nifi.integration.util.NiFiTestUser;
import org.apache.nifi.integration.util.SourceTestProcessor;
import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.dto.RevisionDTO;
import org.apache.nifi.web.api.entity.ProcessorEntity;
import javax.ws.rs.core.Response;
public abstract class AbstractAnonymousUserTest {
private static final String CLIENT_ID = "anonymous-client-id";
/**
* Attempt to create a processor anonymously.
*
* @throws Exception ex
*/
protected Response testCreateProcessor(final String baseUrl, final NiFiTestUser niFiTestUser) throws Exception {
final String url = baseUrl + "/process-groups/root/processors";
// create the processor
final ProcessorDTO processor = new ProcessorDTO();
processor.setName("Copy");
processor.setType(SourceTestProcessor.class.getName());
// create the revision
final RevisionDTO revision = new RevisionDTO();
revision.setClientId(CLIENT_ID);
revision.setVersion(0l);
// create the entity body
final ProcessorEntity entity = new ProcessorEntity();
entity.setRevision(revision);
entity.setComponent(processor);
// perform the request
return niFiTestUser.testPost(url, entity);
}
}

View File

@ -0,0 +1,66 @@
/*
* 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.integration.accesscontrol.anonymous;
import org.apache.nifi.integration.accesscontrol.OneWaySslAccessControlHelper;
import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.entity.ProcessorEntity;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.ws.rs.core.Response;
/**
* Integration test for allowing direct anonymous access.
*/
public class ITAllowDirectAnonymousAccess extends AbstractAnonymousUserTest {
private static OneWaySslAccessControlHelper helper;
@BeforeClass
public static void setup() throws Exception {
helper = new OneWaySslAccessControlHelper("src/test/resources/access-control/nifi-anonymous-allowed.properties");
}
/**
* Attempt to create a processor anonymously.
*
* @throws Exception ex
*/
@Test
public void testDirectAnonymousAccess() throws Exception {
final Response response = super.testCreateProcessor(helper.getBaseUrl(), helper.getUser());
// ensure the request is successful
Assert.assertEquals(201, response.getStatus());
// get the entity body
final ProcessorEntity entity = response.readEntity(ProcessorEntity.class);
// verify creation
final ProcessorDTO processor = entity.getComponent();
Assert.assertEquals("Copy", processor.getName());
Assert.assertEquals("org.apache.nifi.integration.util.SourceTestProcessor", processor.getType());
}
@AfterClass
public static void cleanup() throws Exception {
helper.cleanup();
}
}

View File

@ -0,0 +1,66 @@
/*
* 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.integration.accesscontrol.anonymous;
import org.apache.nifi.integration.accesscontrol.AccessControlHelper;
import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.entity.ProcessorEntity;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.ws.rs.core.Response;
/**
* Integration test for allowing proxied anonymous access.
*/
public class ITAllowProxiedAnonymousAccess extends AbstractAnonymousUserTest {
private static AccessControlHelper helper;
@BeforeClass
public static void setup() throws Exception {
helper = new AccessControlHelper("src/test/resources/access-control/nifi-anonymous-allowed.properties");
}
/**
* Attempt to create a processor anonymously.
*
* @throws Exception ex
*/
@Test
public void testProxiedAnonymousAccess() throws Exception {
final Response response = super.testCreateProcessor(helper.getBaseUrl(), helper.getAnonymousUser());
// ensure the request is successful
Assert.assertEquals(201, response.getStatus());
// get the entity body
final ProcessorEntity entity = response.readEntity(ProcessorEntity.class);
// verify creation
final ProcessorDTO processor = entity.getComponent();
Assert.assertEquals("Copy", processor.getName());
Assert.assertEquals("org.apache.nifi.integration.util.SourceTestProcessor", processor.getType());
}
@AfterClass
public static void cleanup() throws Exception {
helper.cleanup();
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.integration.accesscontrol.anonymous;
import org.apache.nifi.integration.accesscontrol.OneWaySslAccessControlHelper;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.ws.rs.core.Response;
/**
* Integration test for preventing direct anonymous access.
*/
public class ITPreventDirectAnonymousAccess extends AbstractAnonymousUserTest {
private static OneWaySslAccessControlHelper helper;
@BeforeClass
public static void setup() throws Exception {
helper = new OneWaySslAccessControlHelper();
}
/**
* Attempt to create a processor anonymously.
*
* @throws Exception ex
*/
@Test
public void testDirectAnonymousAccess() throws Exception {
final Response response = super.testCreateProcessor(helper.getBaseUrl(), helper.getUser());
// ensure the request is not successful
Assert.assertEquals(401, response.getStatus());
}
@AfterClass
public static void cleanup() throws Exception {
helper.cleanup();
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.integration.accesscontrol.anonymous;
import org.apache.nifi.integration.accesscontrol.AccessControlHelper;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.ws.rs.core.Response;
/**
* Integration test for preventing proxied anonymous access.
*/
public class ITPreventProxiedAnonymousAccess extends AbstractAnonymousUserTest {
private static AccessControlHelper helper;
@BeforeClass
public static void setup() throws Exception {
System.out.println(ITPreventProxiedAnonymousAccess.class.getName() + " setup()");
helper = new AccessControlHelper();
}
/**
* Attempt to create a processor anonymously.
*
* @throws Exception ex
*/
@Test
public void testProxiedAnonymousAccess() throws Exception {
final Response response = super.testCreateProcessor(helper.getBaseUrl(), helper.getAnonymousUser());
// ensure the request is not successful
Assert.assertEquals(401, response.getStatus());
}
@AfterClass
public static void cleanup() throws Exception {
helper.cleanup();
}
}

View File

@ -77,6 +77,11 @@ public class NiFiTestAuthorizer implements Authorizer {
return AuthorizationResult.resourceNotFound();
}
// allow the anonymous user
if (request.isAnonymous()) {
return AuthorizationResult.approved();
}
// allow the token user
if (TOKEN_USER.equals(request.getIdentity())) {
return AuthorizationResult.approved();

View File

@ -0,0 +1,139 @@
# 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.
# Core Properties #
nifi.flow.configuration.file=target/test-classes/access-control/flow.xml.gz
nifi.flow.configuration.archive.dir=target/archive
nifi.flowcontroller.autoResumeState=true
nifi.flowcontroller.graceful.shutdown.period=10 sec
nifi.flowservice.writedelay.interval=2 sec
nifi.authorizer.configuration.file=target/test-classes/access-control/authorizers.xml
nifi.login.identity.provider.configuration.file=target/test-classes/access-control/login-identity-providers.xml
nifi.templates.directory=target/test-classes/access-control/templates
nifi.ui.banner.text=TEST BANNER
nifi.ui.autorefresh.interval=30 sec
nifi.nar.library.directory=target/test-classes/access-control/lib
nifi.nar.working.directory=target/test-classes/access-control/nar
nifi.state.management.configuration.file=target/test-classes/access-control/state-management.xml
nifi.state.management.embedded.zookeeper.start=false
nifi.state.management.embedded.zookeeper.properties=
nifi.state.management.embedded.zookeeper.max.instances=3
nifi.state.management.provider.local=local-provider
nifi.state.management.provider.cluster=
# H2 Settings
nifi.database.directory=target/test-classes/database_repository
nifi.h2.url.append=;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE
# FlowFile Repository
nifi.provenance.repository.implementation=org.apache.nifi.provenance.VolatileProvenanceRepository
nifi.flowfile.repository.directory=target/test-classes/flowfile_repository
nifi.flowfile.repository.partitions=256
nifi.flowfile.repository.checkpoint.interval=2 mins
nifi.queue.swap.threshold=20000
nifi.swap.storage.directory=target/test-classes/flowfile_repository/swap
nifi.swap.in.period=5 sec
nifi.swap.in.threads=1
nifi.swap.out.period=5 sec
nifi.swap.out.threads=4
# Content Repository
nifi.content.claim.max.appendable.size=10 MB
nifi.content.claim.max.flow.files=100
nifi.content.repository.directory.default=target/test-classes/content_repository
nifi.content.repository.archive.enabled=false
# Provenance Repository Properties
nifi.provenance.repository.directory.default=./target/provenance_repository
nifi.provenance.repository.query.threads=2
nifi.provenance.repository.max.storage.time=24 hours
nifi.provenance.repository.max.storage.size=1 GB
nifi.provenance.repository.rollover.time=30 secs
nifi.provenance.repository.rollover.size=100 MB
# Component Status Repository
nifi.components.status.repository.implementation=org.apache.nifi.controller.status.history.VolatileComponentStatusRepository
nifi.components.status.repository.buffer.size=288
nifi.components.status.snapshot.frequency=10 secs
# Site to Site properties
#For testing purposes. Default value should actually be empty!
nifi.remote.input.host=
nifi.remote.input.socket.port=
nifi.remote.input.secure=false
# web properties #
nifi.web.war.directory=target/test-classes/lib
nifi.web.http.host=
nifi.web.http.port=
nifi.web.https.host=
nifi.web.https.port=8443
nifi.web.jetty.working.directory=target/test-classes/access-control/jetty
# security properties #
nifi.sensitive.props.key=REPLACE_ME
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
nifi.sensitive.props.provider=BC
nifi.security.keystore=target/test-classes/access-control/keystore.jks
nifi.security.keystoreType=JKS
nifi.security.keystorePasswd=passwordpassword
nifi.security.keyPasswd=
nifi.security.truststore=target/test-classes/access-control/truststore.jks
nifi.security.truststoreType=JKS
nifi.security.truststorePasswd=passwordpassword
nifi.security.user.login.identity.provider=test-provider
nifi.security.user.authorizer=test-provider
nifi.security.allow.anonymous.authentication=true
# cluster common properties (cluster manager and nodes must have same values) #
nifi.cluster.protocol.heartbeat.interval=5 sec
nifi.cluster.protocol.is.secure=false
nifi.cluster.protocol.socket.timeout=30 sec
nifi.cluster.protocol.connection.handshake.timeout=45 sec
# if multicast is used, then nifi.cluster.protocol.multicast.xxx properties must be configured #
nifi.cluster.protocol.use.multicast=false
nifi.cluster.protocol.multicast.address=
nifi.cluster.protocol.multicast.port=
nifi.cluster.protocol.multicast.service.broadcast.delay=500 ms
nifi.cluster.protocol.multicast.service.locator.attempts=3
nifi.cluster.protocol.multicast.service.locator.attempts.delay=1 sec
# cluster node properties (only configure for cluster nodes) #
nifi.cluster.is.node=false
nifi.cluster.node.address=
nifi.cluster.node.protocol.port=
nifi.cluster.node.protocol.threads=2
# if multicast is not used, nifi.cluster.node.unicast.xxx must have same values as nifi.cluster.manager.xxx #
nifi.cluster.node.unicast.manager.address=
nifi.cluster.node.unicast.manager.protocol.port=
nifi.cluster.node.unicast.manager.authority.provider.port=
# cluster manager properties (only configure for cluster manager) #
nifi.cluster.is.manager=false
nifi.cluster.manager.address=
nifi.cluster.manager.protocol.port=
nifi.cluster.manager.authority.provider.port=
nifi.cluster.manager.authority.provider.threads=10
nifi.cluster.manager.node.firewall.file=
nifi.cluster.manager.node.event.history.size=10
nifi.cluster.manager.node.api.connection.timeout=30 sec
nifi.cluster.manager.node.api.read.timeout=30 sec
nifi.cluster.manager.node.api.request.threads=10
nifi.cluster.manager.flow.retrieval.delay=5 sec
nifi.cluster.manager.protocol.threads=10
nifi.cluster.manager.safemode.duration=0 sec

View File

@ -16,25 +16,24 @@
*/
package org.apache.nifi.web.security.anonymous;
import org.apache.nifi.web.security.NiFiAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import javax.servlet.http.HttpServletRequest;
import org.apache.nifi.authorization.user.NiFiUserDetails;
import org.apache.nifi.authorization.user.StandardNiFiUser;
import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
/**
* Extracts an anonymous authentication request from a specified servlet request.
*/
public class NiFiAnonymousAuthenticationFilter extends NiFiAuthenticationFilter {
public class NiFiAnonymousUserFilter extends AnonymousAuthenticationFilter {
private static final String ANONYMOUS_KEY = "anonymousNifiKey";
public NiFiAnonymousUserFilter() {
super(ANONYMOUS_KEY);
}
private static final Logger logger = LoggerFactory.getLogger(NiFiAnonymousAuthenticationFilter.class);
@Override
protected Authentication createAuthentication(HttpServletRequest request) {
return new NiFiAuthenticationToken(new NiFiUserDetails(StandardNiFiUser.ANONYMOUS));
public Authentication attemptAuthentication(final HttpServletRequest request) {
// return the anonymous authentication request for this http request
return new NiFiAnonymousAuthenticationRequestToken(request.isSecure(), request.getRemoteAddr());
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.anonymous;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.user.NiFiUserDetails;
import org.apache.nifi.authorization.user.StandardNiFiUser;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.InvalidAuthenticationException;
import org.apache.nifi.web.security.NiFiAuthenticationProvider;
import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
/**
*
*/
public class NiFiAnonymousAuthenticationProvider extends NiFiAuthenticationProvider {
final NiFiProperties properties;
public NiFiAnonymousAuthenticationProvider(NiFiProperties nifiProperties, Authorizer authorizer) {
super(nifiProperties, authorizer);
this.properties = nifiProperties;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
final NiFiAnonymousAuthenticationRequestToken request = (NiFiAnonymousAuthenticationRequestToken) authentication;
if (request.isSecureRequest() && !properties.isAnonymousAuthenticationAllowed()) {
throw new InvalidAuthenticationException("Anonymous authentication has not been configured.");
}
return new NiFiAuthenticationToken(new NiFiUserDetails(StandardNiFiUser.populateAnonymousUser(null, request.getClientAddress())));
}
@Override
public boolean supports(Class<?> authentication) {
return NiFiAnonymousAuthenticationRequestToken.class.isAssignableFrom(authentication);
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.anonymous;
import org.apache.nifi.web.security.NiFiAuthenticationRequestToken;
import static org.apache.nifi.authorization.user.StandardNiFiUser.ANONYMOUS_IDENTITY;
/**
* This is an authentication request for an anonymous user.
*/
public class NiFiAnonymousAuthenticationRequestToken extends NiFiAuthenticationRequestToken {
final boolean secureRequest;
/**
* Creates a representation of the anonymous authentication request for a user.
*
* @param clientAddress the address of the client making the request
*/
public NiFiAnonymousAuthenticationRequestToken(final boolean secureRequest, final String clientAddress) {
super(clientAddress);
setAuthenticated(false);
this.secureRequest = secureRequest;
}
@Override
public Object getCredentials() {
return null;
}
public boolean isSecureRequest() {
return secureRequest;
}
@Override
public Object getPrincipal() {
return ANONYMOUS_IDENTITY;
}
@Override
public String toString() {
return "<anonymous>";
}
}

View File

@ -64,11 +64,13 @@ public class X509AuthenticationProvider extends NiFiAuthenticationProvider {
private X509IdentityProvider certificateIdentityProvider;
private Authorizer authorizer;
final NiFiProperties properties;
public X509AuthenticationProvider(final X509IdentityProvider certificateIdentityProvider, final Authorizer authorizer, final NiFiProperties nifiProperties) {
super(nifiProperties, authorizer);
this.certificateIdentityProvider = certificateIdentityProvider;
this.authorizer = authorizer;
this.properties = nifiProperties;
}
@Override
@ -99,6 +101,11 @@ public class X509AuthenticationProvider extends NiFiAuthenticationProvider {
// determine if the user is anonymous
final boolean isAnonymous = StringUtils.isBlank(identity);
if (isAnonymous) {
// prevent anonymous users unless it's been explicitly configured
if (!properties.isAnonymousAuthenticationAllowed()) {
throw new InvalidAuthenticationException("Anonymous authentication has not been configured.");
}
identity = StandardNiFiUser.ANONYMOUS_IDENTITY;
} else {
identity = mapIdentity(identity);

View File

@ -100,4 +100,10 @@
<constructor-arg ref="oidcProvider"/>
</bean>
<!-- anonymous -->
<bean id="anonymousAuthenticationProvider" class="org.apache.nifi.web.security.anonymous.NiFiAnonymousAuthenticationProvider">
<constructor-arg ref="nifiProperties" index="0"/>
<constructor-arg ref="authorizer" index="1"/>
</bean>
</beans>

View File

@ -0,0 +1,91 @@
/*
* 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.anonymous;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.user.NiFiUserDetails;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.util.StringUtils;
import org.apache.nifi.web.security.InvalidAuthenticationException;
import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
import org.junit.Test;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class NiFiAnonymousAuthenticationProviderTest {
private static final Logger logger = LoggerFactory.getLogger(NiFiAnonymousAuthenticationProviderTest.class);
@Test
public void testAnonymousDisabledNotSecure() throws Exception {
final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class);
when(nifiProperties.isAnonymousAuthenticationAllowed()).thenReturn(false);
final NiFiAnonymousAuthenticationProvider anonymousAuthenticationProvider = new NiFiAnonymousAuthenticationProvider(nifiProperties, mock(Authorizer.class));
final NiFiAnonymousAuthenticationRequestToken authenticationRequest = new NiFiAnonymousAuthenticationRequestToken(false, StringUtils.EMPTY);
final NiFiAuthenticationToken authentication = (NiFiAuthenticationToken) anonymousAuthenticationProvider.authenticate(authenticationRequest);
final NiFiUserDetails userDetails = (NiFiUserDetails) authentication.getDetails();
assertTrue(userDetails.getNiFiUser().isAnonymous());
}
@Test
public void testAnonymousEnabledNotSecure() throws Exception {
final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class);
when(nifiProperties.isAnonymousAuthenticationAllowed()).thenReturn(true);
final NiFiAnonymousAuthenticationProvider anonymousAuthenticationProvider = new NiFiAnonymousAuthenticationProvider(nifiProperties, mock(Authorizer.class));
final NiFiAnonymousAuthenticationRequestToken authenticationRequest = new NiFiAnonymousAuthenticationRequestToken(false, StringUtils.EMPTY);
final NiFiAuthenticationToken authentication = (NiFiAuthenticationToken) anonymousAuthenticationProvider.authenticate(authenticationRequest);
final NiFiUserDetails userDetails = (NiFiUserDetails) authentication.getDetails();
assertTrue(userDetails.getNiFiUser().isAnonymous());
}
@Test(expected = InvalidAuthenticationException.class)
public void testAnonymousDisabledSecure() throws Exception {
final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class);
when(nifiProperties.isAnonymousAuthenticationAllowed()).thenReturn(false);
final NiFiAnonymousAuthenticationProvider anonymousAuthenticationProvider = new NiFiAnonymousAuthenticationProvider(nifiProperties, mock(Authorizer.class));
final NiFiAnonymousAuthenticationRequestToken authenticationRequest = new NiFiAnonymousAuthenticationRequestToken(true, StringUtils.EMPTY);
anonymousAuthenticationProvider.authenticate(authenticationRequest);
}
@Test
public void testAnonymousEnabledSecure() throws Exception {
final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class);
when(nifiProperties.isAnonymousAuthenticationAllowed()).thenReturn(true);
final NiFiAnonymousAuthenticationProvider anonymousAuthenticationProvider = new NiFiAnonymousAuthenticationProvider(nifiProperties, mock(Authorizer.class));
final NiFiAnonymousAuthenticationRequestToken authenticationRequest = new NiFiAnonymousAuthenticationRequestToken(true, StringUtils.EMPTY);
final NiFiAuthenticationToken authentication = (NiFiAuthenticationToken) anonymousAuthenticationProvider.authenticate(authenticationRequest);
final NiFiUserDetails userDetails = (NiFiUserDetails) authentication.getDetails();
assertTrue(userDetails.getNiFiUser().isAnonymous());
}
}

View File

@ -34,7 +34,9 @@ import org.junit.Test;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -132,6 +134,27 @@ public class X509AuthenticationProviderTest {
@Test
public void testAnonymousWithOneProxy() {
// override the setting to enable anonymous authentication
final Map<String, String> additionalProperties = new HashMap<String, String>() {{
put(NiFiProperties.SECURITY_ANONYMOUS_AUTHENTICATION, Boolean.TRUE.toString());
}};
final NiFiProperties properties = NiFiProperties.createBasicNiFiProperties(null, additionalProperties);
x509AuthenticationProvider = new X509AuthenticationProvider(certificateIdentityProvider, authorizer, properties);
final NiFiAuthenticationToken auth = (NiFiAuthenticationToken) x509AuthenticationProvider.authenticate(getX509Request(buildProxyChain(ANONYMOUS), PROXY_1));
final NiFiUser user = ((NiFiUserDetails) auth.getDetails()).getNiFiUser();
assertNotNull(user);
assertEquals(StandardNiFiUser.ANONYMOUS_IDENTITY, user.getIdentity());
assertTrue(user.isAnonymous());
assertNotNull(user.getChain());
assertEquals(PROXY_1, user.getChain().getIdentity());
assertFalse(user.getChain().isAnonymous());
}
@Test(expected = InvalidAuthenticationException.class)
public void testAnonymousWithOneProxyWhileAnonymousAuthenticationPrevented() {
final NiFiAuthenticationToken auth = (NiFiAuthenticationToken) x509AuthenticationProvider.authenticate(getX509Request(buildProxyChain(ANONYMOUS), PROXY_1));
final NiFiUser user = ((NiFiUserDetails) auth.getDetails()).getNiFiUser();
@ -169,6 +192,31 @@ public class X509AuthenticationProviderTest {
@Test
public void testAnonymousProxyInChain() {
// override the setting to enable anonymous authentication
final Map<String, String> additionalProperties = new HashMap<String, String>() {{
put(NiFiProperties.SECURITY_ANONYMOUS_AUTHENTICATION, Boolean.TRUE.toString());
}};
final NiFiProperties properties = NiFiProperties.createBasicNiFiProperties(null, additionalProperties);
x509AuthenticationProvider = new X509AuthenticationProvider(certificateIdentityProvider, authorizer, properties);
final NiFiAuthenticationToken auth = (NiFiAuthenticationToken) x509AuthenticationProvider.authenticate(getX509Request(buildProxyChain(IDENTITY_1, ANONYMOUS), PROXY_1));
final NiFiUser user = ((NiFiUserDetails) auth.getDetails()).getNiFiUser();
assertNotNull(user);
assertEquals(IDENTITY_1, user.getIdentity());
assertFalse(user.isAnonymous());
assertNotNull(user.getChain());
assertEquals(StandardNiFiUser.ANONYMOUS_IDENTITY, user.getChain().getIdentity());
assertTrue(user.getChain().isAnonymous());
assertNotNull(user.getChain().getChain());
assertEquals(PROXY_1, user.getChain().getChain().getIdentity());
assertFalse(user.getChain().getChain().isAnonymous());
}
@Test(expected = InvalidAuthenticationException.class)
public void testAnonymousProxyInChainWhileAnonymousAuthenticationPrevented() {
final NiFiAuthenticationToken auth = (NiFiAuthenticationToken) x509AuthenticationProvider.authenticate(getX509Request(buildProxyChain(IDENTITY_1, ANONYMOUS), PROXY_1));
final NiFiUser user = ((NiFiUserDetails) auth.getDetails()).getNiFiUser();
@ -274,4 +322,4 @@ public class X509AuthenticationProviderTest {
return certificate;
}
}
}