Repository: nifi-registry Updated Branches: refs/heads/master ef8ba127c -> 589253778
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/58925377/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosSpnegoFactory.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosSpnegoFactory.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosSpnegoFactory.java new file mode 100644 index 0000000..66d369d --- /dev/null +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosSpnegoFactory.java @@ -0,0 +1,67 @@ +/* + * 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.registry.web.security.authentication.kerberos; + +import org.apache.nifi.registry.properties.NiFiRegistryProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider; +import org.springframework.security.kerberos.authentication.KerberosTicketValidator; + +@Configuration +public class KerberosSpnegoFactory { + + @Autowired + private NiFiRegistryProperties properties; + + @Autowired(required = false) + private KerberosTicketValidator kerberosTicketValidator; + + private KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider; + private KerberosSpnegoIdentityProvider kerberosSpnegoIdentityProvider; + + @Bean + public KerberosSpnegoIdentityProvider kerberosSpnegoIdentityProvider() throws Exception { + + if (kerberosSpnegoIdentityProvider == null && properties.isKerberosSpnegoSupportEnabled()) { + kerberosSpnegoIdentityProvider = new KerberosSpnegoIdentityProvider( + kerberosServiceAuthenticationProvider(), + properties); + } + + return kerberosSpnegoIdentityProvider; + } + + @Bean + public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider() throws Exception { + + if (kerberosServiceAuthenticationProvider == null && properties.isKerberosSpnegoSupportEnabled()) { + + KerberosServiceAuthenticationProvider ksap = new KerberosServiceAuthenticationProvider(); + ksap.setTicketValidator(kerberosTicketValidator); + ksap.setUserDetailsService(new KerberosUserDetailsService()); + ksap.afterPropertiesSet(); + + kerberosServiceAuthenticationProvider = ksap; + + } + + return kerberosServiceAuthenticationProvider; + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/58925377/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosSpnegoIdentityProvider.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosSpnegoIdentityProvider.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosSpnegoIdentityProvider.java new file mode 100644 index 0000000..7002792 --- /dev/null +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosSpnegoIdentityProvider.java @@ -0,0 +1,177 @@ +/* + * 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.registry.web.security.authentication.kerberos; + +import org.apache.nifi.registry.properties.NiFiRegistryProperties; +import org.apache.nifi.registry.security.authentication.AuthenticationRequest; +import org.apache.nifi.registry.security.authentication.AuthenticationResponse; +import org.apache.nifi.registry.security.authentication.IdentityProvider; +import org.apache.nifi.registry.security.authentication.IdentityProviderConfigurationContext; +import org.apache.nifi.registry.security.authentication.IdentityProviderUsage; +import org.apache.nifi.registry.security.authentication.exception.IdentityAccessException; +import org.apache.nifi.registry.security.authentication.exception.InvalidCredentialsException; +import org.apache.nifi.registry.security.exception.SecurityProviderCreationException; +import org.apache.nifi.registry.security.exception.SecurityProviderDestructionException; +import org.apache.nifi.registry.security.util.CryptoUtils; +import org.apache.nifi.registry.util.FormatUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationDetailsSource; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider; +import org.springframework.security.kerberos.authentication.KerberosServiceRequestToken; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; + +@Component +public class KerberosSpnegoIdentityProvider implements IdentityProvider { + + private static final Logger logger = LoggerFactory.getLogger(KerberosSpnegoIdentityProvider.class); + + private static final String issuer = KerberosSpnegoIdentityProvider.class.getSimpleName(); + + private static final IdentityProviderUsage usage = new IdentityProviderUsage() { + @Override + public String getText() { + return "The Kerberos user credentials must be passed in the HTTP Authorization header as specified by SPNEGO-based Kerberos. " + + "That is: 'Authorization: Negotiate <kerberosTicket>', " + + "where <kerberosTicket> is a value that will be validated by this identity provider against a Kerberos cluster."; + } + + @Override + public AuthType getAuthType() { + return AuthType.NEGOTIATE; + } + }; + + private static final String AUTHORIZATION = "Authorization"; + private static final String AUTHORIZATION_NEGOTIATE = "Negotiate"; + + private long expiration = TimeUnit.MILLISECONDS.convert(12, TimeUnit.HOURS);; + private KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider; + private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource; + + @Autowired + public KerberosSpnegoIdentityProvider(KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider, NiFiRegistryProperties properties) { + this.kerberosServiceAuthenticationProvider = kerberosServiceAuthenticationProvider; + authenticationDetailsSource = new WebAuthenticationDetailsSource(); + + final String expirationFromProperties = properties.getKerberosSpnegoAuthenticationExpiration(); + if (expirationFromProperties != null) { + long expiration = FormatUtils.getTimeDuration(expirationFromProperties, TimeUnit.MILLISECONDS); + } + } + + @Override + public IdentityProviderUsage getUsageInstructions() { + return usage; + } + + @Override + public AuthenticationRequest extractCredentials(HttpServletRequest request) { + + // Only support Kerberos authentication when running securely + if (!request.isSecure()) { + return null; + } + + String headerValue = request.getHeader(AUTHORIZATION); + + if (!isValidKerberosHeader(headerValue)) { + return null; + } + + logger.debug("Detected 'Authorization: Negotiate header in request {}", request.getRequestURL()); + byte[] base64Token = headerValue.substring(headerValue.indexOf(" ") + 1).getBytes(StandardCharsets.UTF_8); + byte[] kerberosTicket = Base64.decode(base64Token); + if (kerberosTicket != null) { + logger.debug("Successfully decoded SPNEGO/Kerberos ticket passed in Authorization: Negotiate <ticket> header.", request.getRequestURL()); + } + + return new AuthenticationRequest(null, kerberosTicket, authenticationDetailsSource.buildDetails(request)); + + } + + @Override + public AuthenticationResponse authenticate(AuthenticationRequest authenticationRequest) throws InvalidCredentialsException, IdentityAccessException { + + if (authenticationRequest == null) { + logger.info("Cannot authenticate null authenticationRequest, returning null."); + return null; + } + + final Object credentials = authenticationRequest.getCredentials(); + byte[] kerberosTicket = credentials != null && credentials instanceof byte[] ? (byte[]) authenticationRequest.getCredentials() : null; + + if (credentials == null) { + logger.info("Kerberos Ticket not found in authenticationRequest credentials, returning null."); + return null; + } + + try { + KerberosServiceRequestToken kerberosServiceRequestToken = new KerberosServiceRequestToken(kerberosTicket); + kerberosServiceRequestToken.setDetails(authenticationRequest.getDetails()); + Authentication authentication = kerberosServiceAuthenticationProvider.authenticate(kerberosServiceRequestToken); + if (authentication == null) { + throw new InvalidCredentialsException("Kerberos credentials could not be authenticated."); + } + + final String kerberosPrincipal = authentication.getName(); + + return new AuthenticationResponse(kerberosPrincipal, kerberosPrincipal, expiration, issuer); + + } catch (AuthenticationException e) { + String authFailedMessage = "Kerberos credentials could not be authenticated."; + + /* Kerberos uses encryption with up to AES-256, specifically AES256-CTS-HMAC-SHA1-96. + * That is not available in every JRE, particularly if Unlimited Strength Encryption + * policies are not installed in the Java home lib dir. The Kerberos lib does not + * differentiate between failures due to decryption and those due to bad credentials + * without walking the causes of the exception, so this check puts something + * potentially useful in the logs for those troubleshooting Kerberos authentication. */ + if (!Boolean.FALSE.equals(CryptoUtils.isCryptoRestricted())) { + authFailedMessage += " This Java Runtime does not support unlimited strength encryption. " + + "This could cause Kerberos authentication to fail as it can require AES-256."; + } + + logger.info(authFailedMessage); + throw new InvalidCredentialsException(authFailedMessage, e); + } + + } + + @Override + public void onConfigured(IdentityProviderConfigurationContext configurationContext) throws SecurityProviderCreationException { + throw new SecurityProviderCreationException(KerberosSpnegoIdentityProvider.class.getSimpleName() + + " does not currently support being loaded via IdentityProviderFactory"); + } + + @Override + public void preDestruction() throws SecurityProviderDestructionException { + } + + public boolean isValidKerberosHeader(String headerValue) { + return headerValue != null && (headerValue.startsWith(AUTHORIZATION_NEGOTIATE + " ") || headerValue.startsWith("Kerberos ")); + } +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/58925377/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosTicketValidatorFactory.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosTicketValidatorFactory.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosTicketValidatorFactory.java new file mode 100644 index 0000000..ed3e6eb --- /dev/null +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosTicketValidatorFactory.java @@ -0,0 +1,69 @@ +/* + * 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.registry.web.security.authentication.kerberos; + +import org.apache.nifi.registry.properties.NiFiRegistryProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.FileSystemResource; +import org.springframework.security.kerberos.authentication.KerberosTicketValidator; +import org.springframework.security.kerberos.authentication.sun.GlobalSunJaasKerberosConfig; +import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator; + +import java.io.File; + +@Configuration +public class KerberosTicketValidatorFactory { + + private NiFiRegistryProperties properties; + + private KerberosTicketValidator kerberosTicketValidator; + + @Autowired + public KerberosTicketValidatorFactory(NiFiRegistryProperties properties) { + this.properties = properties; + } + + @Bean + public KerberosTicketValidator kerberosTicketValidator() throws Exception { + + if (kerberosTicketValidator == null && properties.isKerberosSpnegoSupportEnabled()) { + + // Configure SunJaasKerberos (global) + final File krb5ConfigFile = properties.getKerberosConfigurationFile(); + if (krb5ConfigFile != null) { + final GlobalSunJaasKerberosConfig krb5Config = new GlobalSunJaasKerberosConfig(); + krb5Config.setKrbConfLocation(krb5ConfigFile.getAbsolutePath()); + krb5Config.afterPropertiesSet(); + } + + // Create ticket validator to inject into KerberosServiceAuthenticationProvider + SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator(); + ticketValidator.setServicePrincipal(properties.getKerberosSpnegoPrincipal()); + ticketValidator.setKeyTabLocation(new FileSystemResource(properties.getKerberosSpnegoKeytabLocation())); + ticketValidator.afterPropertiesSet(); + + kerberosTicketValidator = ticketValidator; + + } + + return kerberosTicketValidator; + + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/58925377/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosUserDetailsService.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosUserDetailsService.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosUserDetailsService.java new file mode 100644 index 0000000..5471906 --- /dev/null +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosUserDetailsService.java @@ -0,0 +1,38 @@ +/* + * 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.registry.web.security.authentication.kerberos; + +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +public class KerberosUserDetailsService implements UserDetailsService { + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return new User( + username, + "notUsed", + true, + true, + true, + true, + AuthorityUtils.createAuthorityList("ROLE_USER")); + } +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/58925377/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java index 9631efc..2a1856e 100644 --- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java +++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java @@ -62,6 +62,11 @@ public class X509IdentityProvider implements IdentityProvider { "will be authorized to have 'write' access to '/proxy', and the originating user identity will be " + "authorized for access to the resource being accessed in the request."; } + + @Override + public AuthType getAuthType() { + return AuthType.OTHER.httpAuthScheme("TLS-client-cert"); + } }; private X509PrincipalExtractor principalExtractor; @@ -153,7 +158,10 @@ public class X509IdentityProvider implements IdentityProvider { } @Override - public void onConfigured(IdentityProviderConfigurationContext configurationContext) throws SecurityProviderCreationException {} + public void onConfigured(IdentityProviderConfigurationContext configurationContext) throws SecurityProviderCreationException { + throw new SecurityProviderCreationException(X509IdentityProvider.class.getSimpleName() + + " does not currently support being loaded via IdentityProviderFactory"); + } @Override public void preDestruction() throws SecurityProviderDestructionException {} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/58925377/nifi-registry-web-api/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authentication.IdentityProvider ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authentication.IdentityProvider b/nifi-registry-web-api/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authentication.IdentityProvider new file mode 100644 index 0000000..ea80a03 --- /dev/null +++ b/nifi-registry-web-api/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authentication.IdentityProvider @@ -0,0 +1,15 @@ +# 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. +org.apache.nifi.registry.web.security.authentication.kerberos.KerberosIdentityProvider \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/58925377/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureKerberosIT.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureKerberosIT.java b/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureKerberosIT.java new file mode 100644 index 0000000..550c729 --- /dev/null +++ b/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureKerberosIT.java @@ -0,0 +1,173 @@ +/* + * 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.registry.web.api; + + +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.registry.NiFiRegistryTestApiApplication; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.kerberos.authentication.KerberosTicketValidation; +import org.springframework.security.kerberos.authentication.KerberosTicketValidator; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.ws.rs.core.Response; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Base64; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Deploy the Web API Application using an embedded Jetty Server for local integration testing, with the follow characteristics: + * + * - A NiFiRegistryProperties has to be explicitly provided to the ApplicationContext using a profile unique to this test suite. + * - A NiFiRegistryClientConfig has been configured to create a client capable of completing one-way TLS + * - The database is embed H2 using volatile (in-memory) persistence + * - Custom SQL is clearing the DB before each test method by default, unless method overrides this behavior + */ +@RunWith(SpringRunner.class) +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "spring.profiles.include=ITSecureKerberos") +@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = "classpath:db/clearDB.sql") +public class SecureKerberosIT extends IntegrationTestBase { + + private static final String validKerberosTicket = "authenticate_me"; + private static final String invalidKerberosTicket = "do_not_authenticate_me"; + + public static class MockKerberosTicketValidator implements KerberosTicketValidator { + + @Override + public KerberosTicketValidation validateTicket(byte[] token) throws BadCredentialsException { + + boolean validTicket; + try { + validTicket = Arrays.equals(validKerberosTicket.getBytes("UTF-8"), token); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + + if (!validTicket) { + throw new BadCredentialsException(MockKerberosTicketValidator.class.getSimpleName() + " does not validate token"); + } + + return new KerberosTicketValidation( + "kerberosUser@LOCALHOST", + "HTTP/localhsot@LOCALHOST", + null, + null); + } + } + + @Configuration + @Profile("ITSecureKerberos") + @Import({NiFiRegistryTestApiApplication.class, SecureITClientConfiguration.class}) + public static class KerberosSpnegoTestConfiguration { + + @Primary + @Bean + public static KerberosTicketValidator kerberosTicketValidator() { + return new MockKerberosTicketValidator(); + } + + } + + @Test + public void testTokenGenerationAndAccessStatus() throws Exception { + + // Note: this test intentionally does not use the token generated + // for nifiadmin by the @Before method + + // Given: the client and server have been configured correctly for Kerberos SPNEGO authentication + String expectedJwtPayloadJson = "{" + + "\"sub\":\"kerberosUser@LOCALHOST\"," + + "\"preferred_username\":\"kerberosUser@LOCALHOST\"," + + "\"iss\":\"KerberosSpnegoIdentityProvider\"" + + "}"; + String expectedAccessStatusJson = "{" + + "\"identity\":\"kerberosUser@LOCALHOST\"," + + "\"status\":\"ACTIVE\"}"; + + // When: the /access/token/kerberos endpoint is accessed with no credentials + final Response tokenResponse1 = client + .target(createURL("/access/token/kerberos")) + .request() + .post(null, Response.class); + + // Then: the server returns 401 Unauthorized with an authenticate challenge header + assertEquals(401, tokenResponse1.getStatus()); + assertNotNull(tokenResponse1.getHeaders().get("www-authenticate")); + assertEquals(1, tokenResponse1.getHeaders().get("www-authenticate").size()); + assertEquals("Negotiate", tokenResponse1.getHeaders().get("www-authenticate").get(0)); + + // When: the /access/token/kerberos endpoint is accessed again with an invalid ticket + String invalidTicket = new String(java.util.Base64.getEncoder().encode(invalidKerberosTicket.getBytes(Charset.forName("UTF-8")))); + final Response tokenResponse2 = client + .target(createURL("/access/token/kerberos")) + .request() + .header("Authorization", "Negotiate " + invalidTicket) + .post(null, Response.class); + + // Then: the server returns 401 Unauthorized + assertEquals(401, tokenResponse2.getStatus()); + + // When: the /access/token/kerberos endpoint is accessed with a valid ticket + String validTicket = new String(Base64.getEncoder().encode(validKerberosTicket.getBytes(Charset.forName("UTF-8")))); + final Response tokenResponse3 = client + .target(createURL("/access/token/kerberos")) + .request() + .header("Authorization", "Negotiate " + validTicket) + .post(null, Response.class); + + // Then: the server returns 200 OK with a JWT in the body + assertEquals(201, tokenResponse3.getStatus()); + String token = tokenResponse3.readEntity(String.class); + assertTrue(StringUtils.isNotEmpty(token)); + String[] jwtParts = token.split("\\."); + assertEquals(3, jwtParts.length); + String jwtPayload = new String(Base64.getDecoder().decode(jwtParts[1]), "UTF-8"); + JSONAssert.assertEquals(expectedJwtPayloadJson, jwtPayload, false); + + // When: the token is returned in the Authorization header + final Response accessResponse = client + .target(createURL("access")) + .request() + .header("Authorization", "Bearer " + token) + .get(Response.class); + + // Then: the server acknowledges the client has access + assertEquals(200, accessResponse.getStatus()); + String accessStatus = accessResponse.readEntity(String.class); + JSONAssert.assertEquals(expectedAccessStatusJson, accessStatus, false); + + } + + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/58925377/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java b/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java index bdd8e11..e84ee2b 100644 --- a/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java +++ b/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java @@ -25,7 +25,6 @@ import org.apache.nifi.registry.model.authorization.Tenant; import org.apache.nifi.registry.properties.NiFiRegistryProperties; import org.apache.nifi.registry.security.authorization.Authorizer; import org.apache.nifi.registry.security.authorization.AuthorizerFactory; -import org.apache.tomcat.util.codec.binary.Base64; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,6 +46,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.nio.charset.Charset; import java.util.Arrays; +import java.util.Base64; import java.util.HashSet; import java.util.Set; import java.util.stream.Stream; @@ -59,7 +59,7 @@ import static org.junit.Assert.assertTrue; * Deploy the Web API Application using an embedded Jetty Server for local integration testing, with the follow characteristics: * * - A NiFiRegistryProperties has to be explicitly provided to the ApplicationContext using a profile unique to this test suite. - * - A NiFiRegistryClientConfig has been configured to create a client capable of completing two-way TLS + * - A NiFiRegistryClientConfig has been configured to create a client capable of completing one-way TLS * - The database is embed H2 using volatile (in-memory) persistence * - Custom SQL is clearing the DB before each test method by default, unless method overrides this behavior */ @@ -115,8 +115,7 @@ public class SecureLdapIT extends IntegrationTestBase { String expectedJwtPayloadJson = "{" + "\"sub\":\"nobel\"," + "\"preferred_username\":\"nobel\"," + - "\"iss\":\"LdapIdentityProvider\"," + - "\"aud\":\"LdapIdentityProvider\"" + + "\"iss\":\"LdapIdentityProvider\"" + "}"; String expectedAccessStatusJson = "{" + "\"identity\":\"nobel\"," + @@ -136,7 +135,7 @@ public class SecureLdapIT extends IntegrationTestBase { assertTrue(StringUtils.isNotEmpty(token)); String[] jwtParts = token.split("\\."); assertEquals(3, jwtParts.length); - String jwtPayload = new String(Base64.decodeBase64(jwtParts[1]), "UTF-8"); + String jwtPayload = new String(Base64.getDecoder().decode(jwtParts[1]), "UTF-8"); JSONAssert.assertEquals(expectedJwtPayloadJson, jwtPayload, false); // When: the token is returned in the Authorization header @@ -182,7 +181,7 @@ public class SecureLdapIT extends IntegrationTestBase { assertTrue(StringUtils.isNotEmpty(token)); String[] jwtParts = token.split("\\."); assertEquals(3, jwtParts.length); - String jwtPayload = new String(Base64.decodeBase64(jwtParts[1]), "UTF-8"); + String jwtPayload = new String(Base64.getDecoder().decode(jwtParts[1]), "UTF-8"); JSONAssert.assertEquals(expectedJwtPayloadJson, jwtPayload, false); // When: the token is returned in the Authorization header @@ -434,7 +433,7 @@ public class SecureLdapIT extends IntegrationTestBase { private static String encodeCredentialsForBasicAuth(String username, String password) { final String credentials = username + ":" + password; - final String base64credentials = new String(java.util.Base64.getEncoder().encode(credentials.getBytes(Charset.forName("UTF-8")))); + final String base64credentials = new String(Base64.getEncoder().encode(credentials.getBytes(Charset.forName("UTF-8")))); return base64credentials; } } http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/58925377/nifi-registry-web-api/src/test/resources/application-ITSecureKerberos.properties ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/test/resources/application-ITSecureKerberos.properties b/nifi-registry-web-api/src/test/resources/application-ITSecureKerberos.properties new file mode 100644 index 0000000..6ce3665 --- /dev/null +++ b/nifi-registry-web-api/src/test/resources/application-ITSecureKerberos.properties @@ -0,0 +1,36 @@ +# +# 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. +# + + +# Properties for Spring Boot integration tests +# Documentation for common Spring Boot application properties can be found at: +# https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html + + +# Custom (non-standard to Spring Boot) properties +nifi.registry.properties.file: src/test/resources/conf/secure-kerberos/nifi-registry.properties +nifi.registry.client.properties.file: src/test/resources/conf/secure-kerberos/nifi-registry-client.properties + + +# Embedded Server SSL Context Config +#server.ssl.client-auth: need # LDAP-configured server does not require two-way TLS +server.ssl.key-store: ./target/test-classes/keys/localhost-ks.jks +server.ssl.key-store-password: localhostKeystorePassword +server.ssl.key-password: localhostKeystorePassword +server.ssl.protocol: TLS +server.ssl.trust-store: ./target/test-classes/keys/localhost-ts.jks +server.ssl.trust-store-password: localhostTruststorePassword http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/58925377/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/authorizers.xml ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/authorizers.xml b/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/authorizers.xml new file mode 100644 index 0000000..6b42fa2 --- /dev/null +++ b/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/authorizers.xml @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- + ~ 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. + --> +<!-- + This file lists the userGroupProviders, accessPolicyProviders, and authorizers to use when running securely. In order + to use a specific authorizer it must be configured here and its identifier must be specified in the nifi.properties file. + If the authorizer is a managedAuthorizer, it may need to be configured with an accessPolicyProvider and an userGroupProvider. + This file allows for configuration of them, but they must be configured in order: + + ... + all userGroupProviders + all accessPolicyProviders + all Authorizers + ... +--> +<authorizers> + + <!-- + The FileUserGroupProvider will provide support for managing users and groups which is backed by a file + on the local file system. + + - Users File - The file where the FileUserGroupProvider will store users and groups. + + - Initial User Identity [unique key] - The identity of a users and systems to seed the Users File. The name of + each property must be unique, for example: "Initial User Identity A", "Initial User Identity B", + "Initial User Identity C" or "Initial User Identity 1", "Initial User Identity 2", "Initial User Identity 3" + + NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the user identities, + so the values should be the unmapped identities (i.e. full DN from a certificate). + --> + <userGroupProvider> + <identifier>file-user-group-provider</identifier> + <class>org.apache.nifi.registry.security.authorization.file.FileUserGroupProvider</class> + <property name="Users File">./target/test-classes/conf/secure-kerberos/users.xml</property> + <property name="Initial User Identity 1">kerberosUser@LOCALHOST</property> + </userGroupProvider> + + <!-- + The FileAccessPolicyProvider will provide support for managing access policies which is backed by a file + on the local file system. + + - User Group Provider - The identifier for an User Group Provider defined above that will be used to access + users and groups for use in the managed access policies. + + - Authorizations File - The file where the FileAccessPolicyProvider will store policies. + + - Initial Admin Identity - The identity of an initial admin user that will be granted access to the UI and + given the ability to create additional users, groups, and policies. The value of this property could be + a DN when using certificates or LDAP. This property will only be used when there + are no other policies defined. + + NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the initial admin identity, + so the value should be the unmapped identity. This identity must be found in the configured User Group Provider. + + - Node Identity [unique key] - The identity of a NiFi cluster node. When clustered, a property for each node + should be defined, so that every node knows about every other node. If not clustered these properties can be ignored. + The name of each property must be unique, for example for a three node cluster: + "Node Identity A", "Node Identity B", "Node Identity C" or "Node Identity 1", "Node Identity 2", "Node Identity 3" + + NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the node identities, + so the values should be the unmapped identities (i.e. full DN from a certificate). This identity must be found + in the configured User Group Provider. + --> + <accessPolicyProvider> + <identifier>file-access-policy-provider</identifier> + <class>org.apache.nifi.registry.security.authorization.file.FileAccessPolicyProvider</class> + <property name="User Group Provider">file-user-group-provider</property> + <property name="Authorizations File">./target/test-classes/conf/secure-kerberos/authorizations.xml</property> + <property name="Initial Admin Identity">kerberosUser@LOCALHOST</property> + + <!--<property name="Node Identity 1"></property>--> + </accessPolicyProvider> + + <!-- + The StandardManagedAuthorizer. This authorizer implementation must be configured with the + Access Policy Provider which it will use to access and manage users, groups, and policies. + These users, groups, and policies will be used to make all access decisions during authorization + requests. + + - Access Policy Provider - The identifier for an Access Policy Provider defined above. + --> + <authorizer> + <identifier>managed-authorizer</identifier> + <class>org.apache.nifi.registry.security.authorization.StandardManagedAuthorizer</class> + <property name="Access Policy Provider">file-access-policy-provider</property> + </authorizer> + +</authorizers> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/58925377/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/identity-providers.xml ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/identity-providers.xml b/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/identity-providers.xml new file mode 100644 index 0000000..85f1957 --- /dev/null +++ b/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/identity-providers.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- + ~ 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. + --> +<!-- + This file lists the login identity providers to use when running securely. In order + to use a specific provider it must be configured here and it's identifier + must be specified in the nifi.properties file. +--> +<identityProviders> + + <!-- This test conf is for KerberosSpnegoIdentityProvider, + which is configured in nifi-registry.properties and loaded as an auto-scanned Spring Bean. + + This is not intended for KerberosIdentityProvider, + which would be loaded from here using IdentityProviderFactory --> + +</identityProviders> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/58925377/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/nifi-registry-client.properties ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/nifi-registry-client.properties b/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/nifi-registry-client.properties new file mode 100644 index 0000000..f431ccc --- /dev/null +++ b/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/nifi-registry-client.properties @@ -0,0 +1,22 @@ +# +# 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. +# + +# client security properties # +# Don't use a client cert for one-way TLS. Client identity will be provided via Kerberos SPNEGO to get JWT +nifi.registry.security.truststore=./target/test-classes/keys/localhost-ts.jks +nifi.registry.security.truststoreType=JKS +nifi.registry.security.truststorePasswd=localhostTruststorePassword http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/58925377/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/nifi-registry.properties ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/nifi-registry.properties b/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/nifi-registry.properties new file mode 100644 index 0000000..3d5c122 --- /dev/null +++ b/nifi-registry-web-api/src/test/resources/conf/secure-kerberos/nifi-registry.properties @@ -0,0 +1,36 @@ +# +# 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. +# + +# web properties # +nifi.registry.web.https.host=localhost +nifi.registry.web.https.port=0 + +# security properties # +# +# ** Server KeyStore and TrustStore configuration set in Spring profile properties for embedded Jetty ** +# +nifi.registry.security.authorizers.configuration.file=./target/test-classes/conf/secure-kerberos/authorizers.xml +nifi.registry.security.authorizer=managed-authorizer + +# providers properties # +nifi.registry.providers.configuration.file=./target/test-classes/conf/providers.xml + +# kerberos properties # (aside from expiration, these don't actually matter as the KerberosServiceAuthenticationProvider will be mocked) +nifi.registry.kerberos.krb5.file=/path/to/krb5.conf +nifi.registry.kerberos.spnego.authentication.expiration=12 hours +nifi.registry.kerberos.spnego.principal=HTTP/localhost@LOCALHOST +nifi.registry.kerberos.spnego.keytab.location=/path/to/keytab http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/58925377/nifi-registry-web-api/src/test/resources/conf/secure-ldap/nifi-registry-client.properties ---------------------------------------------------------------------- diff --git a/nifi-registry-web-api/src/test/resources/conf/secure-ldap/nifi-registry-client.properties b/nifi-registry-web-api/src/test/resources/conf/secure-ldap/nifi-registry-client.properties index 929e1a7..68cb0f9 100644 --- a/nifi-registry-web-api/src/test/resources/conf/secure-ldap/nifi-registry-client.properties +++ b/nifi-registry-web-api/src/test/resources/conf/secure-ldap/nifi-registry-client.properties @@ -16,10 +16,7 @@ # # client security properties # -#nifi.registry.security.keystore=./target/test-classes/keys/client-ks.jks -#nifi.registry.security.keystoreType=JKS -#nifi.registry.security.keystorePasswd=clientKeystorePassword -#nifi.registry.security.keyPasswd=u1Pass +# Don't use a client cert for one-way TLS. Client identity will be provided via LDAP user/pass to get JWT nifi.registry.security.truststore=./target/test-classes/keys/localhost-ts.jks nifi.registry.security.truststoreType=JKS nifi.registry.security.truststorePasswd=localhostTruststorePassword
