Repository: nifi-minifi Updated Branches: refs/heads/master 7ecf80e47 -> f89f41504
http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/C2AuthenticationToken.java ---------------------------------------------------------------------- diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/C2AuthenticationToken.java b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/C2AuthenticationToken.java new file mode 100644 index 0000000..d6f85ec --- /dev/null +++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/C2AuthenticationToken.java @@ -0,0 +1,51 @@ +/* + * 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.minifi.c2.security.authentication; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +public class C2AuthenticationToken extends AbstractAuthenticationToken { + private final String principal; + private Object credentials; + + public C2AuthenticationToken(String principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { + super(authorities); + setAuthenticated(true); + this.principal = principal; + this.credentials = credentials; + } + + @Override + public Object getCredentials() { + return credentials; + } + + @Override + public Object getPrincipal() { + return principal; + } + + @Override + public void eraseCredentials() { + super.eraseCredentials(); + credentials = null; + } +} http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationFilter.java ---------------------------------------------------------------------- diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationFilter.java b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationFilter.java new file mode 100644 index 0000000..63c743b --- /dev/null +++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationFilter.java @@ -0,0 +1,70 @@ +/* + * 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.minifi.c2.security.authentication; + +import org.apache.nifi.minifi.c2.util.HttpRequestUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.GenericFilterBean; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import java.io.IOException; +import java.security.cert.X509Certificate; + +public class X509AuthenticationFilter extends GenericFilterBean { + private static final Logger logger = LoggerFactory.getLogger(X509AuthenticationFilter.class); + private AuthenticationManager authenticationManager; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + authenticateIfPossible(request); + chain.doFilter(request, response); + } + + private void authenticateIfPossible(ServletRequest request) { + if (!request.isSecure()) { + return; + } + + X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); + + if (certs == null || certs.length == 0) { + if (logger.isDebugEnabled()) { + logger.debug("Unable to get certificates in request from " + HttpRequestUtil.getClientString(request)); + } + return; + } + + Authentication authentication = authenticationManager.authenticate(new X509AuthenticationToken(certs)); + if (authentication.isAuthenticated()) { + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + + @Autowired + public void setAuthenticationManager(AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } +} http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationProvider.java ---------------------------------------------------------------------- diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationProvider.java b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationProvider.java new file mode 100644 index 0000000..c297947 --- /dev/null +++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationProvider.java @@ -0,0 +1,49 @@ +/* + * 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.minifi.c2.security.authentication; + +import org.apache.nifi.minifi.c2.api.security.authorization.AuthorityGranter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + +public class X509AuthenticationProvider implements AuthenticationProvider { + private static final Logger logger = LoggerFactory.getLogger(X509AuthenticationProvider.class); + private final AuthorityGranter authorityGranter; + + public X509AuthenticationProvider(AuthorityGranter authorityGranter) { + this.authorityGranter = authorityGranter; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + X509AuthenticationToken x509AuthenticationToken = (X509AuthenticationToken) authentication; + if (logger.isDebugEnabled()) { + logger.debug("Authenticating " + X509AuthenticationToken.class.getSimpleName() + " with principal " + x509AuthenticationToken.getPrincipal()); + } + return new C2AuthenticationToken(x509AuthenticationToken.getPrincipal(), x509AuthenticationToken.getCredentials(), + authorityGranter.grantAuthorities(authentication)); + } + + @Override + public boolean supports(Class<?> authentication) { + return X509AuthenticationToken.class.isAssignableFrom(authentication); + } +} http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationToken.java ---------------------------------------------------------------------- diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationToken.java b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationToken.java new file mode 100644 index 0000000..1faa0b3 --- /dev/null +++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationToken.java @@ -0,0 +1,52 @@ +/* + * 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.minifi.c2.security.authentication; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collection; + +public class X509AuthenticationToken extends AbstractAuthenticationToken { + private final X509Certificate[] x509Certificates; + private final String subjectDn; + + public X509AuthenticationToken(X509Certificate[] x509Certificates) { + this(x509Certificates, null); + setAuthenticated(false); + } + + protected X509AuthenticationToken(X509Certificate[] x509Certificates, Collection<GrantedAuthority> grantedAuthorities) { + super(grantedAuthorities); + this.x509Certificates = Arrays.copyOf(x509Certificates, x509Certificates.length, X509Certificate[].class); + X509Certificate x509Certificate = x509Certificates[0]; + this.subjectDn = x509Certificate.getSubjectDN().getName().trim(); + } + + @Override + public X509Certificate[] getCredentials() { + return x509Certificates; + } + + @Override + public String getPrincipal() { + return subjectDn; + } +} http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/GrantedAuthorityAuthorizer.java ---------------------------------------------------------------------- diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/GrantedAuthorityAuthorizer.java b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/GrantedAuthorityAuthorizer.java new file mode 100644 index 0000000..71f3fa4 --- /dev/null +++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/GrantedAuthorityAuthorizer.java @@ -0,0 +1,138 @@ +/* + * 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.minifi.c2.security.authorization; + +import org.apache.nifi.minifi.c2.api.security.authorization.AuthorizationException; +import org.apache.nifi.minifi.c2.api.security.authorization.Authorizer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.yaml.snakeyaml.Yaml; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.UriInfo; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class GrantedAuthorityAuthorizer implements Authorizer { + private static final Logger logger = LoggerFactory.getLogger(GrantedAuthorityAuthorizer.class); + + public static final String DENY = "deny"; + public static final String ALLOW = "allow"; + public static final String DEFAULT_ACTION = "Default Action"; + private final Map<String, Object> grantedAuthorityMap; + + public GrantedAuthorityAuthorizer(Resource configYaml) throws IOException { + try (InputStream inputStream = configYaml.getInputStream()) { + grantedAuthorityMap = as(Map.class, new Yaml().load(inputStream), o -> new IllegalArgumentException("Expected yaml map for root of configuration but was " + o)); + } + } + + @Override + public void authorize(Authentication authentication, UriInfo uriInfo) throws AuthorizationException { + if (authentication == null) { + throw new AuthorizationException("null authentication object provided."); + } + + if (!authentication.isAuthenticated()) { + throw new AuthorizationException(authentication + " not authenticated."); + } + + Set<String> authorities = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()); + + String defaultAction = as(String.class, grantedAuthorityMap.getOrDefault(DEFAULT_ACTION, DENY)); + String path = uriInfo.getAbsolutePath().getPath(); + Map<String, Object> pathAuthorizations = as(Map.class, grantedAuthorityMap.get("Paths")); + if (pathAuthorizations == null && !ALLOW.equalsIgnoreCase(defaultAction)) { + throw new AuthorizationException("Didn't find authorizations for " + path + " and default policy is " + defaultAction + " instead of allow"); + } + + Map<String, Object> pathAuthorization = as(Map.class, pathAuthorizations.get(path)); + if (pathAuthorization == null && !ALLOW.equalsIgnoreCase(defaultAction)) { + throw new AuthorizationException("Didn't find authorizations for " + path + " and default policy is " + defaultAction + " instead of allow"); + } + defaultAction = as(String.class, pathAuthorization.getOrDefault(DEFAULT_ACTION, defaultAction)); + List<Map<String, Object>> actions = as(List.class, pathAuthorization.get("Actions")); + MultivaluedMap<String, String> queryParameters = uriInfo.getQueryParameters(); + for (Map<String, Object> action : actions) { + String ruleAction = as(String.class, action.get("Action")); + if (ruleAction == null || !(ALLOW.equalsIgnoreCase(ruleAction) || DENY.equalsIgnoreCase(ruleAction))) { + throw new AuthorizationException("Expected Action key of allow or deny for " + action); + } + String authorization = as(String.class, action.get("Authorization")); + if (authorization != null && !authorities.contains(authorization)) { + continue; + } + Map<String, Object> parameters = as(Map.class, action.get("Query Parameters")); + if (parameters != null) { + boolean foundParameterMismatch = false; + for (Map.Entry<String, Object> parameter : parameters.entrySet()) { + Object value = parameter.getValue(); + if (value instanceof String) { + value = Arrays.asList((String)value); + } + if (!Objects.equals(queryParameters.get(parameter.getKey()), value)) { + foundParameterMismatch = true; + break; + } + } + if (foundParameterMismatch) { + continue; + } + } + if (ALLOW.equalsIgnoreCase(ruleAction)) { + if (logger.isDebugEnabled()) { + logger.debug("Action " + action + "matched which resulted in " + ruleAction); + } + return; + } else { + throw new AuthorizationException("Action " + action + " matched which resulted in " + ruleAction); + } + } + if (ALLOW.equalsIgnoreCase(defaultAction)) { + if (logger.isDebugEnabled()) { + logger.debug("Found no matching actions so falling back to default action " + defaultAction); + } + } else { + throw new AuthorizationException("Didn't find authorizations for " + path + " and default policy is " + defaultAction + " instead of allow"); + } + } + + private static <T> T as(Class<T> clazz, Object object) throws AuthorizationException { + return as(clazz, object, o -> new AuthorizationException("Expected " + clazz + " but was " + o)); + } + + private static <T, E extends Throwable> T as(Class<T> clazz, Object object, Function<Object, E> exceptionSupplier) throws E { + if (object == null) { + return null; + } + if (!clazz.isInstance(object)) { + throw exceptionSupplier.apply(object); + } + return clazz.cast(object); + } +} http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/PrincipalStringAuthorityGranter.java ---------------------------------------------------------------------- diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/PrincipalStringAuthorityGranter.java b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/PrincipalStringAuthorityGranter.java new file mode 100644 index 0000000..24e1ffa --- /dev/null +++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/PrincipalStringAuthorityGranter.java @@ -0,0 +1,54 @@ +/* + * 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.minifi.c2.security.authorization; + +import org.apache.nifi.minifi.c2.api.security.authorization.AuthorityGranter; +import org.springframework.core.io.Resource; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.yaml.snakeyaml.Yaml; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class PrincipalStringAuthorityGranter implements AuthorityGranter { + private final Map<String, List<String>> grantedAuthorityMap; + + public PrincipalStringAuthorityGranter(Resource configYaml) throws IOException { + try (InputStream inputStream = configYaml.getInputStream()) { + Object yaml = new Yaml().load(inputStream); + if (!(yaml instanceof Map)) { + throw new IllegalArgumentException("Expected authority map of Principal -> Authority list"); + } + grantedAuthorityMap = (Map<String, List<String>>) yaml; + } + } + @Override + public Collection<GrantedAuthority> grantAuthorities(Authentication authentication) { + List<String> authorities = grantedAuthorityMap.get(authentication.getPrincipal().toString()); + if (authorities == null) { + return null; + } + return authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()); + } +} http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/service/ConfigService.java ---------------------------------------------------------------------- diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/service/ConfigService.java b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/service/ConfigService.java new file mode 100644 index 0000000..e57089e --- /dev/null +++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/service/ConfigService.java @@ -0,0 +1,167 @@ +/* + * 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.minifi.c2.service; + +import com.wordnik.swagger.annotations.Api; +import org.apache.nifi.minifi.c2.api.Configuration; +import org.apache.nifi.minifi.c2.api.ConfigurationProvider; +import org.apache.nifi.minifi.c2.api.InvalidParameterException; +import org.apache.nifi.minifi.c2.api.security.authorization.AuthorizationException; +import org.apache.nifi.minifi.c2.api.security.authorization.Authorizer; +import org.apache.nifi.minifi.c2.api.util.Pair; +import org.apache.nifi.minifi.c2.util.HttpRequestUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.context.SecurityContextHolder; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Path("/config") +@Api( + value = "/config", + description = "Provides configuration for MiNiFi instances" +) +public class ConfigService { + private static final Logger logger = LoggerFactory.getLogger(ConfigService.class); + private final List<Pair<MediaType, ConfigurationProvider>> configurationProviders; + private final Authorizer authorizer; + + public ConfigService(List<ConfigurationProvider> configurationProviders, Authorizer authorizer) { + this.authorizer = authorizer; + if (configurationProviders == null || configurationProviders.size() == 0) { + throw new IllegalArgumentException("Expected at least one configuration provider"); + } + this.configurationProviders = configurationProviders.stream().map(c -> new Pair<>(MediaType.valueOf(c.getContentType()), c)).collect(Collectors.toList()); + } + + @GET + public Response getConfig(@Context HttpServletRequest request, @Context HttpHeaders httpHeaders, @Context UriInfo uriInfo) { + try { + authorizer.authorize(SecurityContextHolder.getContext().getAuthentication(), uriInfo); + } catch (AuthorizationException e) { + logger.warn(HttpRequestUtil.getClientString(request) + " not authorized to access " + uriInfo, e); + return Response.status(403).build(); + } + Map<String, List<String>> parameters = new HashMap<>(); + for (Map.Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) { + parameters.put(entry.getKey(), entry.getValue()); + } + List<MediaType> acceptValues = httpHeaders.getAcceptableMediaTypes(); + boolean defaultAccept = false; + if (acceptValues.size() == 0) { + acceptValues = Arrays.asList(MediaType.WILDCARD_TYPE); + defaultAccept = true; + } + if (logger.isDebugEnabled()) { + StringBuilder builder = new StringBuilder("Handling request from ") + .append(HttpRequestUtil.getClientString(request)) + .append(" with parameters ") + .append(parameters) + .append(" and Accept"); + if (defaultAccept) { + builder = builder.append(" default value"); + } + builder = builder.append(": ") + .append(acceptValues.stream().map(Object::toString).collect(Collectors.joining(", "))); + logger.debug(builder.toString()); + } + Pair<MediaType, ConfigurationProvider> providerPair = getProvider(acceptValues); + + try { + Integer version = null; + List<String> versionList = parameters.get("version"); + if (versionList != null && versionList.size() > 0) { + try { + version = Integer.parseInt(versionList.get(0)); + } catch (NumberFormatException e) { + throw new InvalidParameterException("Unable to parse " + version + " as integer.", e); + } + } + Response.ResponseBuilder ok = Response.ok(); + Configuration configuration = providerPair.getSecond().getConfiguration(version, parameters); + ok = ok.header("X-Content-Version", configuration.getVersion()); + ok = ok.type(providerPair.getFirst()); + byte[] buffer = new byte[1024]; + int read; + try (InputStream inputStream = configuration.getInputStream(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + while((read = inputStream.read(buffer)) >= 0) { + outputStream.write(buffer, 0, read); + md5.update(buffer, 0, read); + sha256.update(buffer, 0, read); + } + ok = ok.header("Content-MD5", bytesToHex(md5.digest())); + ok = ok.header("X-Content-SHA-256", bytesToHex(sha256.digest())); + ok = ok.entity(outputStream.toByteArray()); + } catch (IOException|NoSuchAlgorithmException e) { + logger.error("Error reading or checksumming configuration file", e); + throw new WebApplicationException(500); + } + return ok.build(); + } catch (InvalidParameterException e) { + logger.info(HttpRequestUtil.getClientString(request) + " made invalid request with " + HttpRequestUtil.getQueryString(request), e); + return Response.status(400).entity("Invalid request.").build(); + } catch (Throwable t) { + logger.error(HttpRequestUtil.getClientString(request) + " made request with " + HttpRequestUtil.getQueryString(request) + " that caused error in " + providerPair.getSecond(), t); + return Response.status(500).entity("Internal error").build(); + } + } + + // see: http://stackoverflow.com/questions/15429257/how-to-convert-byte-array-to-hexstring-in-java#answer-15429408 + protected static String bytesToHex(byte[] in) { + final StringBuilder builder = new StringBuilder(); + for(byte b : in) { + builder.append(String.format("%02x", b)); + } + return builder.toString(); + } + + private Pair<MediaType, ConfigurationProvider> getProvider(List<MediaType> acceptValues) { + for (MediaType accept : acceptValues) { + for (Pair<MediaType, ConfigurationProvider> configurationProviderPair : configurationProviders) { + if (accept.isCompatible(configurationProviderPair.getFirst())) { + return configurationProviderPair; + } + } + } + + throw new WebApplicationException(Response.status(406).entity("Unable to find configuration provider for " + + "\"Accept: " + acceptValues.stream().map(Object::toString).collect(Collectors.joining(", ")) + "\" supported media types are " + + configurationProviders.stream().map(Pair::getFirst).map(Object::toString).collect(Collectors.joining(", "))).build()); + } +} http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/util/HttpRequestUtil.java ---------------------------------------------------------------------- diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/util/HttpRequestUtil.java b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/util/HttpRequestUtil.java new file mode 100644 index 0000000..395eb73 --- /dev/null +++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/util/HttpRequestUtil.java @@ -0,0 +1,41 @@ +/* + * 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.minifi.c2.util; + +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; + +public class HttpRequestUtil { + public static String getQueryString(HttpServletRequest request) { + String queryString = request.getQueryString(); + if (queryString == null) { + return "no query string"; + } + return "query string \"" + queryString + "\""; + } + + public static String getClientString(ServletRequest request) { + String remoteHost = request.getRemoteHost(); + String remoteAddr = request.getRemoteAddr(); + String result = "Client " + remoteHost; + if (!remoteAddr.equals(remoteHost)) { + result = result + " (" + remoteAddr + ")"; + } + return result; + } +} http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/webapp/WEB-INF/web.xml ---------------------------------------------------------------------- diff --git a/minifi-c2/minifi-c2-service/src/main/webapp/WEB-INF/web.xml b/minifi-c2/minifi-c2-service/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..7be7364 --- /dev/null +++ b/minifi-c2/minifi-c2-service/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> + <display-name>minifi-c2</display-name> + <context-param> + <param-name>contextClass</param-name> + <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> + </context-param> + <context-param> + <param-name>contextConfigLocation</param-name> + <param-value>org.apache.nifi.minifi.c2.configuration</param-value> + </context-param> + <listener> + <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> + </listener> + <servlet> + <servlet-name>jerseySpring</servlet-name> + <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class> + <init-param> + <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name> + <param-value>true</param-value> + </init-param> + </servlet> + <servlet-mapping> + <servlet-name>jerseySpring</servlet-name> + <url-pattern>/*</url-pattern> + </servlet-mapping> + <filter> + <filter-name>springSecurityFilterChain</filter-name> + <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> + </filter> + <filter-mapping> + <filter-name>springSecurityFilterChain</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> +</web-app> http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/pom.xml ---------------------------------------------------------------------- diff --git a/minifi-c2/pom.xml b/minifi-c2/pom.xml new file mode 100644 index 0000000..f4d5add --- /dev/null +++ b/minifi-c2/pom.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>minifi</artifactId> + <groupId>org.apache.nifi.minifi</groupId> + <version>0.2.0-SNAPSHOT</version> + </parent> + <artifactId>minifi-c2</artifactId> + <packaging>pom</packaging> + + <modules> + <module>minifi-c2-api</module> + <module>minifi-c2-cache</module> + <module>minifi-c2-provider</module> + <module>minifi-c2-service</module> + <module>minifi-c2-jetty</module> + <module>minifi-c2-assembly</module> + <module>minifi-c2-docker</module> + <module>minifi-c2-integration-tests</module> + </modules> +</project> http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-resources/src/main/resources/conf/bootstrap.conf ---------------------------------------------------------------------- diff --git a/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-resources/src/main/resources/conf/bootstrap.conf b/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-resources/src/main/resources/conf/bootstrap.conf index 537536e..85204cc 100644 --- a/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-resources/src/main/resources/conf/bootstrap.conf +++ b/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-resources/src/main/resources/conf/bootstrap.conf @@ -54,6 +54,10 @@ nifi.minifi.config=./conf/config.yml #nifi.minifi.notifier.ingestors.pull.http.hostname=localhost # Port on which to pull configurations from #nifi.minifi.notifier.ingestors.pull.http.port=4567 +# Path to pull configurations from +#nifi.minifi.notifier.ingestors.pull.http.path=/c2/config +# Query string to pull configurations with +#nifi.minifi.notifier.ingestors.pull.http.query=class=raspi3 # Period on which to pull configurations from, defaults to 5 minutes if commented out #nifi.minifi.notifier.ingestors.pull.http.period.ms=300000 http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 3727ce4..3fa1f2a 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,7 @@ limitations under the License. <module>minifi-assembly</module> <module>minifi-toolkit</module> <module>minifi-docker</module> + <module>minifi-c2</module> </modules> <url>http://nifi.apache.org/minifi</url> @@ -402,6 +403,11 @@ limitations under the License. </dependency> <dependency> <groupId>org.apache.nifi</groupId> + <artifactId>nifi-toolkit-tls</artifactId> + <version>${org.apache.nifi.version}</version> + </dependency> + <dependency> + <groupId>org.apache.nifi</groupId> <artifactId>nifi-resources</artifactId> <version>${org.apache.nifi.version}</version> <classifier>resources</classifier>
