http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosIdentityProvider.java ---------------------------------------------------------------------- diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosIdentityProvider.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosIdentityProvider.java new file mode 100644 index 0000000..5e6e7bb --- /dev/null +++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosIdentityProvider.java @@ -0,0 +1,111 @@ +/* + * 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.commons.lang3.StringUtils; +import org.apache.nifi.registry.security.authentication.AuthenticationRequest; +import org.apache.nifi.registry.security.authentication.AuthenticationResponse; +import org.apache.nifi.registry.security.authentication.BasicAuthIdentityProvider; +import org.apache.nifi.registry.security.authentication.IdentityProviderConfigurationContext; +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.util.FormatUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.kerberos.authentication.KerberosAuthenticationProvider; +import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosClient; + +import java.util.concurrent.TimeUnit; + +public class KerberosIdentityProvider extends BasicAuthIdentityProvider { + + private static final Logger logger = LoggerFactory.getLogger(KerberosIdentityProvider.class); + private static final String issuer = KerberosIdentityProvider.class.getSimpleName(); + private static final String default_expiration = "12 hours"; + + private KerberosAuthenticationProvider provider; + + private long expiration; + + @Override + public void onConfigured(IdentityProviderConfigurationContext configurationContext) throws SecurityProviderCreationException { + + String rawDebug = configurationContext.getProperty("Enable Debug"); + boolean enableDebug = (rawDebug != null && rawDebug.equalsIgnoreCase("true")); + + String rawExpiration = configurationContext.getProperty("Authentication Expiration"); + if (StringUtils.isBlank(rawExpiration)) { + rawExpiration = default_expiration; + logger.info("No Authentication Expiration specified, defaulting to " + default_expiration); + } + + try { + expiration = FormatUtils.getTimeDuration(rawExpiration, TimeUnit.MILLISECONDS); + } catch (final IllegalArgumentException iae) { + throw new SecurityProviderCreationException( + String.format("The Expiration Duration '%s' is not a valid time duration", rawExpiration)); + } + + provider = new KerberosAuthenticationProvider(); + SunJaasKerberosClient client = new SunJaasKerberosClient(); + client.setDebug(enableDebug); + provider.setKerberosClient(client); + provider.setUserDetailsService(new KerberosUserDetailsService()); + + } + + @Override + public AuthenticationResponse authenticate(AuthenticationRequest authenticationRequest) throws InvalidCredentialsException, IdentityAccessException { + + if (provider == null) { + throw new IdentityAccessException("The Kerberos authentication provider is not initialized."); + } + + try { + // perform the authentication + final String username = authenticationRequest.getUsername(); + final Object credentials = authenticationRequest.getCredentials(); + final String password = credentials != null && credentials instanceof String ? (String) credentials : null; + + // perform the authentication + final UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, credentials); + logger.debug("Created authentication token " + token.toString()); + + final Authentication authentication = provider.authenticate(token); + logger.debug("Ran provider.authenticate(token) and returned authentication for " + + "principal={} with name={} and isAuthenticated={}", + authentication.getPrincipal(), + authentication.getName(), + authentication.isAuthenticated()); + + return new AuthenticationResponse(authentication.getName(), username, expiration, issuer); + } catch (final AuthenticationException e) { + throw new InvalidCredentialsException(e.getMessage(), e); + } + + } + + @Override + public void preDestruction() throws SecurityProviderDestructionException { + + } +}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosSpnegoFactory.java ---------------------------------------------------------------------- diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosSpnegoFactory.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosSpnegoFactory.java new file mode 100644 index 0000000..16211ed --- /dev/null +++ b/nifi-registry-core/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; + } + + + private 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/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosSpnegoIdentityProvider.java ---------------------------------------------------------------------- diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosSpnegoIdentityProvider.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosSpnegoIdentityProvider.java new file mode 100644 index 0000000..e611b53 --- /dev/null +++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosSpnegoIdentityProvider.java @@ -0,0 +1,184 @@ +/* + * 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.lang.Nullable; +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( + @Nullable 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; + } + + if (kerberosServiceAuthenticationProvider == null) { + throw new IdentityAccessException("The Kerberos authentication provider is not initialized."); + } + + 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/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosTicketValidatorFactory.java ---------------------------------------------------------------------- diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosTicketValidatorFactory.java b/nifi-registry-core/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-core/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/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosUserDetailsService.java ---------------------------------------------------------------------- diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/kerberos/KerberosUserDetailsService.java b/nifi-registry-core/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-core/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/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/SubjectDnX509PrincipalExtractor.java ---------------------------------------------------------------------- diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/SubjectDnX509PrincipalExtractor.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/SubjectDnX509PrincipalExtractor.java new file mode 100644 index 0000000..a9deae1 --- /dev/null +++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/SubjectDnX509PrincipalExtractor.java @@ -0,0 +1,35 @@ +/* + * 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.x509; + +import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; +import org.springframework.stereotype.Component; + +import java.security.cert.X509Certificate; + +/** + * Principal extractor for extracting a DN. + */ +@Component +public class SubjectDnX509PrincipalExtractor implements X509PrincipalExtractor { + + @Override + public Object extractPrincipal(X509Certificate cert) { + return cert.getSubjectDN().getName().trim(); + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509CertificateExtractor.java ---------------------------------------------------------------------- diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509CertificateExtractor.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509CertificateExtractor.java new file mode 100644 index 0000000..34ceada --- /dev/null +++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509CertificateExtractor.java @@ -0,0 +1,55 @@ +/* + * 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.x509; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.security.cert.X509Certificate; + +/** + * Extracts client certificates from Http requests. + */ +@Component +public class X509CertificateExtractor { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * Extract the client certificate from the specified HttpServletRequest or + * null if none is specified. + * + * @param request http request + * @return cert + */ + public X509Certificate[] extractClientCertificate(HttpServletRequest request) { + X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); + + if (certs != null && certs.length > 0) { + return certs; + } + + if (logger.isDebugEnabled()) { + logger.debug("No client certificate found in request."); + } + + return null; + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java ---------------------------------------------------------------------- diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java new file mode 100644 index 0000000..aefdd5b --- /dev/null +++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java @@ -0,0 +1,131 @@ +/* + * 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.x509; + +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.registry.properties.NiFiRegistryProperties; +import org.apache.nifi.registry.security.authentication.AuthenticationRequest; +import org.apache.nifi.registry.web.security.authentication.AuthenticationRequestToken; +import org.apache.nifi.registry.security.authentication.AuthenticationResponse; +import org.apache.nifi.registry.security.authentication.IdentityProvider; +import org.apache.nifi.registry.web.security.authentication.IdentityAuthenticationProvider; +import org.apache.nifi.registry.web.security.authentication.AuthenticationSuccessToken; +import org.apache.nifi.registry.security.authorization.Authorizer; +import org.apache.nifi.registry.security.authorization.RequestAction; +import org.apache.nifi.registry.security.authorization.Resource; +import org.apache.nifi.registry.security.authorization.exception.AccessDeniedException; +import org.apache.nifi.registry.security.authorization.resource.Authorizable; +import org.apache.nifi.registry.security.authorization.resource.ResourceFactory; +import org.apache.nifi.registry.security.authorization.user.NiFiUser; +import org.apache.nifi.registry.security.authorization.user.NiFiUserDetails; +import org.apache.nifi.registry.security.authorization.user.StandardNiFiUser; +import org.apache.nifi.registry.security.util.ProxiedEntitiesUtils; +import org.apache.nifi.registry.web.security.authentication.exception.UntrustedProxyException; + +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +public class X509IdentityAuthenticationProvider extends IdentityAuthenticationProvider { + + private static final Authorizable PROXY_AUTHORIZABLE = new Authorizable() { + @Override + public Authorizable getParentAuthorizable() { + return null; + } + + @Override + public Resource getResource() { + return ResourceFactory.getProxyResource(); + } + }; + + public X509IdentityAuthenticationProvider(NiFiRegistryProperties properties, Authorizer authorizer, IdentityProvider identityProvider) { + super(properties, authorizer, identityProvider); + } + + @Override + protected AuthenticationSuccessToken buildAuthenticatedToken( + AuthenticationRequestToken requestToken, + AuthenticationResponse response) { + + AuthenticationRequest authenticationRequest = requestToken.getAuthenticationRequest(); + + String proxiedEntitiesChain = authenticationRequest.getDetails() != null + ? (String)authenticationRequest.getDetails() + : null; + + if (StringUtils.isBlank(proxiedEntitiesChain)) { + return super.buildAuthenticatedToken(requestToken, response); + } + + // build the entire proxy chain if applicable - <end-user><proxy1><proxy2> + final List<String> proxyChain = ProxiedEntitiesUtils.tokenizeProxiedEntitiesChain(proxiedEntitiesChain); + proxyChain.add(response.getIdentity()); + + // add the chain as appropriate to each proxy + NiFiUser proxy = null; + for (final ListIterator<String> chainIter = proxyChain.listIterator(proxyChain.size()); chainIter.hasPrevious(); ) { + String identity = chainIter.previous(); + + // determine if the user is anonymous + final boolean isAnonymous = StringUtils.isBlank(identity); + if (isAnonymous) { + identity = StandardNiFiUser.ANONYMOUS_IDENTITY; + } else { + identity = mapIdentity(identity); + } + + final Set<String> groups = getUserGroups(identity); + + // Only set the client address for client making the request because we don't know the clientAddress of the proxied entities + String clientAddress = (proxy == null) ? requestToken.getClientAddress() : null; + proxy = createUser(identity, groups, proxy, clientAddress, isAnonymous); + + if (chainIter.hasPrevious()) { + try { + PROXY_AUTHORIZABLE.authorize(authorizer, RequestAction.WRITE, proxy); + } catch (final AccessDeniedException e) { + throw new UntrustedProxyException(String.format("Untrusted proxy [%s].", identity)); + } + } + } + + return new AuthenticationSuccessToken(new NiFiUserDetails(proxy)); + + } + + /** + * Returns a regular user populated with the provided values, or if the user should be anonymous, a well-formed instance of the anonymous user with the provided values. + * + * @param identity the user's identity + * @param chain the proxied entities + * @param clientAddress the requesting IP address + * @param isAnonymous if true, an anonymous user will be returned (identity will be ignored) + * @return the populated user + */ + private static NiFiUser createUser(String identity, Set<String> groups, NiFiUser chain, String clientAddress, boolean isAnonymous) { + if (isAnonymous) { + return StandardNiFiUser.populateAnonymousUser(chain, clientAddress); + } else { + return new StandardNiFiUser.Builder().identity(identity).groups(groups).chain(chain).clientAddress(clientAddress).build(); + } + } + + + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java ---------------------------------------------------------------------- diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java new file mode 100644 index 0000000..2a1856e --- /dev/null +++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java @@ -0,0 +1,174 @@ +/* + * 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.x509; + +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.InvalidCredentialsException; +import org.apache.nifi.registry.security.exception.SecurityProviderCreationException; +import org.apache.nifi.registry.security.exception.SecurityProviderDestructionException; +import org.apache.nifi.registry.security.util.ProxiedEntitiesUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.concurrent.TimeUnit; + +/** + * Identity provider for extract the authenticating a ServletRequest with a X509Certificate. + */ +@Component +public class X509IdentityProvider implements IdentityProvider { + + private static final Logger logger = LoggerFactory.getLogger(X509IdentityProvider.class); + + private static final String issuer = X509IdentityProvider.class.getSimpleName(); + + private static final long expiration = TimeUnit.MILLISECONDS.convert(12, TimeUnit.HOURS); + + private static final IdentityProviderUsage usage = new IdentityProviderUsage() { + @Override + public String getText() { + return "The client must connect over HTTPS and must provide a client certificate during the TLS handshake. " + + "Additionally, the client may declare itself a proxy for another user identity by populating the " + + ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN + " HTTP header field with a value of the format " + + "'<end-user-identity><proxy1-identity><proxy2-identity>...<proxyN-identity>'" + + "for all identities in the chain prior to this client. If the " + ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN + + " header is present in the request, this client's identity will be extracted from the client certificate " + + "used for TLS and added to the end of the chain, and then the entire chain will be authorized. Each proxy " + + "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; + private X509CertificateExtractor certificateExtractor; + + @Autowired + public X509IdentityProvider(X509PrincipalExtractor principalExtractor, X509CertificateExtractor certificateExtractor) { + this.principalExtractor = principalExtractor; + this.certificateExtractor = certificateExtractor; + } + + @Override + public IdentityProviderUsage getUsageInstructions() { + return usage; + } + + /** + * Extracts certificate-based credentials from an {@link HttpServletRequest}. + * + * The resulting {@link AuthenticationRequest} will be populated as: + * - username: principal DN from first client cert + * - credentials: first client certificate (X509Certificate) + * - details: proxied-entities chain (String) + * + * @param servletRequest the {@link HttpServletRequest} request that may contain credentials understood by this IdentityProvider + * @return a populated AuthenticationRequest or null if the credentials could not be found. + */ + @Override + public AuthenticationRequest extractCredentials(HttpServletRequest servletRequest) { + + // only support x509 login when running securely + if (!servletRequest.isSecure()) { + return null; + } + + // look for a client certificate + final X509Certificate[] certificates = certificateExtractor.extractClientCertificate(servletRequest); + if (certificates == null || certificates.length == 0) { + return null; + } + + // extract the principal + final Object certificatePrincipal = principalExtractor.extractPrincipal(certificates[0]); + final String principal = certificatePrincipal.toString(); + + // extract the proxiedEntitiesChain header value from the servletRequest + String proxiedEntitiesChainHeader = servletRequest.getHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN); + + return new AuthenticationRequest(principal, certificates[0], proxiedEntitiesChainHeader); + + } + + /** + * For a given {@link AuthenticationRequest}, this validates the client certificate and creates a populated {@link AuthenticationResponse}. + * + * The {@link AuthenticationRequest} authenticationRequest paramenter is expected to be populated as: + * - username: principal DN from first client cert + * - credentials: first client certificate (X509Certificate) + * - details: proxied-entities chain (String) + * + * @param authenticationRequest the request, containing identity claim credentials for the IdentityProvider to authenticate and determine an identity + */ + @Override + public AuthenticationResponse authenticate(AuthenticationRequest authenticationRequest) throws InvalidCredentialsException { + + if (authenticationRequest == null || authenticationRequest.getUsername() == null) { + return null; + } + + String principal = authenticationRequest.getUsername(); + + try { + X509Certificate clientCertificate = (X509Certificate)authenticationRequest.getCredentials(); + validateClientCertificate(clientCertificate); + } catch (CertificateExpiredException cee) { + final String message = String.format("Client certificate for (%s) is expired.", principal); + logger.warn(message, cee); + throw new InvalidCredentialsException(message, cee); + } catch (CertificateNotYetValidException cnyve) { + final String message = String.format("Client certificate for (%s) is not yet valid.", principal); + logger.warn(message, cnyve); + throw new InvalidCredentialsException(message, cnyve); + } catch (final Exception e) { + logger.warn(e.getMessage(), e); + } + + // build the authentication response + return new AuthenticationResponse(principal, principal, expiration, issuer); + } + + @Override + 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 {} + + + private void validateClientCertificate(X509Certificate certificate) throws CertificateExpiredException, CertificateNotYetValidException { + certificate.checkValidity(); + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/HttpMethodAuthorizationRules.java ---------------------------------------------------------------------- diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/HttpMethodAuthorizationRules.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/HttpMethodAuthorizationRules.java new file mode 100644 index 0000000..c940359 --- /dev/null +++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/HttpMethodAuthorizationRules.java @@ -0,0 +1,48 @@ +/* + * 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.authorization; + +import org.apache.nifi.registry.security.authorization.RequestAction; +import org.springframework.http.HttpMethod; + +public interface HttpMethodAuthorizationRules { + + default boolean requiresAuthorization(HttpMethod httpMethod) { + return true; + } + + default RequestAction mapHttpMethodToAction(HttpMethod httpMethod) { + + switch (httpMethod) { + case TRACE: + case OPTIONS: + case HEAD: + case GET: + return RequestAction.READ; + case POST: + case PUT: + case PATCH: + return RequestAction.WRITE; + case DELETE: + return RequestAction.DELETE; + default: + throw new IllegalArgumentException("Unknown http method: " + httpMethod); + } + + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/ResourceAuthorizationFilter.java ---------------------------------------------------------------------- diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/ResourceAuthorizationFilter.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/ResourceAuthorizationFilter.java new file mode 100644 index 0000000..6e551e1 --- /dev/null +++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/ResourceAuthorizationFilter.java @@ -0,0 +1,218 @@ +/* + * 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.authorization; + +import org.apache.nifi.registry.security.authorization.AuthorizableLookup; +import org.apache.nifi.registry.security.authorization.RequestAction; +import org.apache.nifi.registry.security.authorization.exception.AccessDeniedException; +import org.apache.nifi.registry.security.authorization.resource.Authorizable; +import org.apache.nifi.registry.security.authorization.resource.ResourceType; +import org.apache.nifi.registry.security.authorization.user.NiFiUser; +import org.apache.nifi.registry.security.authorization.user.NiFiUserUtils; +import org.apache.nifi.registry.service.AuthorizationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpMethod; +import org.springframework.web.filter.GenericFilterBean; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * This filter is designed to perform a resource authorization check in the Spring Security filter chain. + * + * It authorizes the current authenticated user for the {@link RequestAction} (based on the HttpMethod) requested + * on the {@link ResourceType} (based on the URI path). + * + * This filter is designed to be place after any authentication and before any application endpoints. + * + * This filter can be used in place of or in addition to authorization checks that occur in the application + * downstream of this filter. + * + * To configure this filter, provide an {@link AuthorizationService} that will be used to perform the authorization + * check, as well as a set of rules that control which resource and HTTP methods are handled by this filter. + * + * Any (ResourceType, HttpMethod) pair that is not configured to require authorization by this filter will be + * allowed to proceed in the filter chain without an authorization check. + * + * Any (ResourceType, HttpMethod) pair that is configured to require authorization by this filter will map + * the HttpMethod to a NiFi Registry RequestAction (configurable when creating this filter), and the + * (Resource Authorizable, RequestAction) pair will be sent to the AuthorizationService, which will use the + * configured Authorizer to authorize the current user for the action on the requested resource. + */ +public class ResourceAuthorizationFilter extends GenericFilterBean { + + private static final Logger logger = LoggerFactory.getLogger(ResourceAuthorizationFilter.class); + + private Map<ResourceType, HttpMethodAuthorizationRules> resourceTypeAuthorizationRules; + private AuthorizationService authorizationService; + private AuthorizableLookup authorizableLookup; + + ResourceAuthorizationFilter(Builder builder) { + if (builder.getAuthorizationService() == null || builder.getResourceTypeAuthorizationRules() == null) { + throw new IllegalArgumentException("Builder is missing one or more required fields [authorizationService, resourceTypeAuthorizationRules]."); + } + this.resourceTypeAuthorizationRules = builder.getResourceTypeAuthorizationRules(); + this.authorizationService = builder.getAuthorizationService(); + this.authorizableLookup = this.authorizationService.getAuthorizableLookup(); + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + + HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; + HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; + + boolean authorizationCheckIsRequired = false; + String resourcePath = null; + RequestAction action = null; + + // Only require authorization if the NiFi Registry is running securely. + if (servletRequest.isSecure()) { + + // Only require authorization for resources for which this filter has been configured + resourcePath = httpServletRequest.getServletPath(); + if (resourcePath != null) { + final ResourceType resourceType = ResourceType.mapFullResourcePathToResourceType(resourcePath); + final HttpMethodAuthorizationRules authorizationRules = resourceTypeAuthorizationRules.get(resourceType); + if (authorizationRules != null) { + final String httpMethodStr = httpServletRequest.getMethod().toUpperCase(); + HttpMethod httpMethod = HttpMethod.resolve(httpMethodStr); + + // Only require authorization for HTTP methods included in this resource type's rule set + if (httpMethod != null && authorizationRules.requiresAuthorization(httpMethod)) { + authorizationCheckIsRequired = true; + action = authorizationRules.mapHttpMethodToAction(httpMethod); + } + } + } + } + + if (!authorizationCheckIsRequired) { + forwardRequestWithoutAuthorizationCheck(httpServletRequest, httpServletResponse, filterChain); + return; + } + + // Perform authorization check + try { + authorizeAccess(resourcePath, action); + successfulAuthorization(httpServletRequest, httpServletResponse, filterChain); + } catch (Exception e) { + logger.debug("Exception occurred while performing authorization check.", e); + failedAuthorization(httpServletRequest, httpServletResponse, filterChain, e); + } + } + + private boolean userIsAuthenticated() { + NiFiUser user = NiFiUserUtils.getNiFiUser(); + return (user != null && !user.isAnonymous()); + } + + private void authorizeAccess(String path, RequestAction action) throws AccessDeniedException { + + if (path == null || action == null) { + throw new IllegalArgumentException("Authorization is required, but a required input [resource, action] is absent."); + } + + Authorizable authorizable = authorizableLookup.getAuthorizableByResource(path); + + if (authorizable == null) { + throw new IllegalStateException("Resource Authorization Filter configured for non-authorizable resource: " + path); + } + + // throws AccessDeniedException if current user is not authorized to perform requested action on resource + authorizationService.authorize(authorizable, action); + } + + private void forwardRequestWithoutAuthorizationCheck(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { + logger.debug("Request filter authorization check is not required for this HTTP Method on this resource. " + + "Allowing request to proceed. An additional authorization check might be performed downstream of this filter."); + chain.doFilter(req, res); + } + + private void successfulAuthorization(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { + logger.debug("Request filter authorization check passed. Allowing request to proceed."); + chain.doFilter(req, res); + } + + private void failedAuthorization(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Exception failure) throws IOException, ServletException { + logger.debug("Request filter authorization check failed. Blocking access."); + + NiFiUser user = NiFiUserUtils.getNiFiUser(); + final String identity = (user != null) ? user.toString() : "<no user found>"; + final int status = !userIsAuthenticated() ? HttpServletResponse.SC_UNAUTHORIZED : HttpServletResponse.SC_FORBIDDEN; + + logger.info("{} does not have permission to perform this action on the requested resource. {} Returning {} response.", identity, failure.getMessage(), status); + logger.debug("", failure); + + if (!response.isCommitted()) { + response.setStatus(status); + response.setContentType("text/plain"); + response.getWriter().println(String.format("Access is denied due to: %s Contact the system administrator.", failure.getLocalizedMessage())); + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private AuthorizationService authorizationService; + final private Map<ResourceType, HttpMethodAuthorizationRules> resourceTypeAuthorizationRules; + + // create via ResourceAuthorizationFilter.builder() + private Builder() { + this.resourceTypeAuthorizationRules = new HashMap<>(); + } + + public AuthorizationService getAuthorizationService() { + return authorizationService; + } + + public Builder setAuthorizationService(AuthorizationService authorizationService) { + this.authorizationService = authorizationService; + return this; + } + + public Map<ResourceType, HttpMethodAuthorizationRules> getResourceTypeAuthorizationRules() { + return resourceTypeAuthorizationRules; + } + + public Builder addResourceType(ResourceType resourceType) { + this.resourceTypeAuthorizationRules.put(resourceType, new HttpMethodAuthorizationRules() {}); + return this; + } + + public Builder addResourceType(ResourceType resourceType, HttpMethodAuthorizationRules authorizationRules) { + this.resourceTypeAuthorizationRules.put(resourceType, authorizationRules); + return this; + } + + public ResourceAuthorizationFilter build() { + return new ResourceAuthorizationFilter(this); + } + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/StandardHttpMethodAuthorizationRules.java ---------------------------------------------------------------------- diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/StandardHttpMethodAuthorizationRules.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/StandardHttpMethodAuthorizationRules.java new file mode 100644 index 0000000..daa5a37 --- /dev/null +++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authorization/StandardHttpMethodAuthorizationRules.java @@ -0,0 +1,40 @@ +/* + * 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.authorization; + +import org.springframework.http.HttpMethod; + +import java.util.EnumSet; +import java.util.Set; + +public class StandardHttpMethodAuthorizationRules implements HttpMethodAuthorizationRules { + + final private Set<HttpMethod> methodsRequiringAuthorization; + + public StandardHttpMethodAuthorizationRules() { + this(EnumSet.allOf(HttpMethod.class)); + } + + public StandardHttpMethodAuthorizationRules(Set<HttpMethod> methodsRequiringAuthorization) { + this.methodsRequiringAuthorization = methodsRequiringAuthorization; + } + + @Override + public boolean requiresAuthorization(HttpMethod httpMethod) { + return methodsRequiringAuthorization.contains(httpMethod); + } +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/resources/META-INF/LICENSE ---------------------------------------------------------------------- diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/resources/META-INF/LICENSE b/nifi-registry-core/nifi-registry-web-api/src/main/resources/META-INF/LICENSE new file mode 100644 index 0000000..cd25b0a --- /dev/null +++ b/nifi-registry-core/nifi-registry-web-api/src/main/resources/META-INF/LICENSE @@ -0,0 +1,352 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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 product bundles 'asm' which is available under a 3-Clause BSD style license. +For details see http://asm.ow2.org/asmdex-license.html + + Copyright (c) 2012 France Télécom + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. + +The binary distribution of this product bundles 'Antlr 3' which is available +under a "3-clause BSD" license. For details see http://www.antlr3.org/license.html + + Copyright (c) 2010 Terence Parr + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. + +The binary distribution of this product bundles 'Bouncy Castle JDK 1.5' +under an MIT style license. + + Copyright (c) 2000 - 2015 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +The binary distribution of this product bundles 'Slf4j' which is available under +an MIT license. + + Copyright (c) 2004-2013 QOS.ch + All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +The binary distribution of this product bundles 'dom4j' which is available under +a "3-Clause BSD" license. For details: https://github.com/dom4j/dom4j/blob/master/LICENSE + + Copyright 2001-2016 (C) MetaStuff, Ltd. and DOM4J contributors. All Rights Reserved. + + Redistribution and use of this software and associated documentation + ("Software"), with or without modification, are permitted provided + that the following conditions are met: + + 1. Redistributions of source code must retain copyright + statements and notices. Redistributions must also contain a + copy of this document. + + 2. Redistributions in binary form must reproduce the + above copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + 3. The name "DOM4J" must not be used to endorse or promote + products derived from this Software without prior written + permission of MetaStuff, Ltd. For written permission, + please contact dom4j-i...@metastuff.com. + + 4. Products derived from this Software may not be called "DOM4J" + nor may "DOM4J" appear in their names without prior written + permission of MetaStuff, Ltd. DOM4J is a registered + trademark of MetaStuff, Ltd. + + 5. Due credit should be given to the DOM4J Project - https://dom4j.github.io/ + + THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT + NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file