Repository: nifi Updated Branches: refs/heads/0.x 5593be400 -> fd6eb669e
- Setting the user details in the web cluster manager and removing unnecessary code. - Deprecating the unnecessary methods. - Code clean up. - Fixing checkstyle issues in PutKafka. Project: http://git-wip-us.apache.org/repos/asf/nifi/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/fd6eb669 Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/fd6eb669 Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/fd6eb669 Branch: refs/heads/0.x Commit: fd6eb669efcf27d3148b624355e2916d3c3e55a5 Parents: 5593be4 Author: Matt Gilman <[email protected]> Authored: Tue Feb 14 16:11:05 2017 -0500 Committer: Matt Gilman <[email protected]> Committed: Tue Feb 14 16:33:56 2017 -0500 ---------------------------------------------------------------------- .../apache/nifi/web/NiFiWebContextConfig.java | 5 + .../apache/nifi/web/NiFiWebRequestContext.java | 5 + .../cluster/manager/impl/WebClusterManager.java | 129 +++++----- .../nifi/web/HttpServletRequestContext.java | 39 +-- .../web/HttpServletRequestContextConfig.java | 40 +--- .../nifi/web/StandardNiFiContentAccess.java | 21 -- .../StandardNiFiWebConfigurationContext.java | 22 -- .../apache/nifi/web/StandardNiFiWebContext.java | 60 ++--- .../nifi/web/api/ApplicationResource.java | 24 -- .../apache/nifi/web/ContentRequestContext.java | 5 + .../nifi/web/ContentViewerController.java | 23 +- .../web/security/NiFiAuthenticationFilter.java | 24 +- .../nifi/web/security/ProxiedEntitiesUtils.java | 134 +++++++---- .../security/ProxiedEntitiesUtilsTest.groovy | 235 +++++++++++++++++++ .../apache/nifi/processors/kafka/PutKafka.java | 35 ++- 15 files changed, 472 insertions(+), 329 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi/blob/fd6eb669/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebContextConfig.java ---------------------------------------------------------------------- diff --git a/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebContextConfig.java b/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebContextConfig.java index 2df94e4..c86599f 100644 --- a/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebContextConfig.java +++ b/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebContextConfig.java @@ -48,8 +48,13 @@ public interface NiFiWebContextConfig { * <CN=original-proxied-entity><CN=first-proxy><CN=second-proxy>... * </code> * + * Update: + * This method has been deprecated since the entire proxy + * chain is able to be rebuilt using the current user if necessary. + * * @return the proxied entities chain or null if no chain */ + @Deprecated String getProxiedEntitiesChain(); } http://git-wip-us.apache.org/repos/asf/nifi/blob/fd6eb669/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebRequestContext.java ---------------------------------------------------------------------- diff --git a/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebRequestContext.java b/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebRequestContext.java index 9dd44ab..bb3bbd3 100644 --- a/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebRequestContext.java +++ b/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebRequestContext.java @@ -49,8 +49,13 @@ public interface NiFiWebRequestContext { * <CN=original-proxied-entity><CN=first-proxy><CN=second-proxy>... * </code> * + * Update: + * This method has been deprecated since the entire proxy + * chain is able to be rebuilt using the current user if necessary. + * * @return the proxied entities chain or null if no chain */ + @Deprecated String getProxiedEntitiesChain(); } http://git-wip-us.apache.org/repos/asf/nifi/blob/fd6eb669/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/impl/WebClusterManager.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/impl/WebClusterManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/impl/WebClusterManager.java index b74ea50..fc0f10f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/impl/WebClusterManager.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/impl/WebClusterManager.java @@ -16,58 +16,7 @@ */ package org.apache.nifi.cluster.manager.impl; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.Serializable; -import java.net.URI; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.NavigableSet; -import java.util.Queue; -import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.UUID; -import java.util.concurrent.CompletionService; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ExecutorCompletionService; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.regex.Pattern; - -import javax.net.ssl.SSLContext; -import javax.ws.rs.HttpMethod; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.StreamingOutput; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; - +import com.sun.jersey.api.client.ClientResponse; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.admin.service.AuditService; import org.apache.nifi.annotation.lifecycle.OnAdded; @@ -246,9 +195,13 @@ import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity; import org.apache.nifi.web.api.entity.RemoteProcessGroupsEntity; import org.apache.nifi.web.api.entity.ReportingTaskEntity; import org.apache.nifi.web.api.entity.ReportingTasksEntity; +import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter; +import org.apache.nifi.web.security.user.NiFiUserDetails; import org.apache.nifi.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -256,7 +209,56 @@ import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; -import com.sun.jersey.api.client.ClientResponse; +import javax.net.ssl.SSLContext; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.StreamingOutput; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Serializable; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Queue; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.UUID; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.regex.Pattern; /** * Provides a cluster manager implementation. The manager federates incoming HTTP client requests to the nodes' external API using the HTTP protocol. The manager also communicates with nodes using the @@ -2327,12 +2329,31 @@ public class WebClusterManager implements HttpClusterManager, ProtocolHandler, C // check if this request can change the flow final boolean mutableRequest = canChangeNodeState(method, uri); + // include the user details + final Map<String, String> headersWithUserDetails = new HashMap<>(headers); + + // add the user's authorities (if any) to the headers + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + final Object userDetailsObj = authentication.getPrincipal(); + if (userDetailsObj instanceof NiFiUserDetails) { + // serialize user details object + final String hexEncodedUserDetails = WebUtils.serializeObjectToHex((Serializable) userDetailsObj); + + // put serialized user details in header + headersWithUserDetails.put("X-ProxiedEntityUserDetails", hexEncodedUserDetails); + + // remove the access token if present, since the user is already authenticated/authorized + headersWithUserDetails.remove(JwtAuthenticationFilter.AUTHORIZATION); + } + } + final ObjectHolder<NodeResponse> holder = new ObjectHolder<>(null); final UpdateRevision federateRequest = new UpdateRevision() { @Override public Revision execute(Revision currentRevision) { // update headers to contain cluster contextual information to send to the node - final Map<String, String> updatedHeaders = new HashMap<>(headers); + final Map<String, String> updatedHeaders = new HashMap<>(headersWithUserDetails); final ClusterContext clusterCtx = new ClusterContextImpl(); clusterCtx.setRequestSentByClusterManager(true); // indicate request is sent from cluster manager clusterCtx.setRevision(currentRevision); http://git-wip-us.apache.org/repos/asf/nifi/blob/fd6eb669/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-custom-ui-utilities/src/main/java/org/apache/nifi/web/HttpServletRequestContext.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-custom-ui-utilities/src/main/java/org/apache/nifi/web/HttpServletRequestContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-custom-ui-utilities/src/main/java/org/apache/nifi/web/HttpServletRequestContext.java index 311fbc7..d8d5860 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-custom-ui-utilities/src/main/java/org/apache/nifi/web/HttpServletRequestContext.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-custom-ui-utilities/src/main/java/org/apache/nifi/web/HttpServletRequestContext.java @@ -16,7 +16,6 @@ */ package org.apache.nifi.web; -import java.security.cert.X509Certificate; import javax.servlet.http.HttpServletRequest; /** @@ -42,19 +41,7 @@ public class HttpServletRequestContext implements NiFiWebRequestContext { @Override public String getProxiedEntitiesChain() { - String xProxiedEntitiesChain = request.getHeader("X-ProxiedEntitiesChain"); - final X509Certificate cert = extractClientCertificate(request); - if (cert != null) { - final String extractedPrincipal = extractPrincipal(cert); - final String formattedPrincipal = formatProxyDn(extractedPrincipal); - if (xProxiedEntitiesChain == null || xProxiedEntitiesChain.trim().isEmpty()) { - xProxiedEntitiesChain = formattedPrincipal; - } else { - xProxiedEntitiesChain += formattedPrincipal; - } - } - - return xProxiedEntitiesChain; + return null; } /** @@ -73,28 +60,4 @@ public class HttpServletRequestContext implements NiFiWebRequestContext { public String getId() { return request.getParameter(ID_PARAM); } - - /** - * Utility methods that have been copied into this class to reduce the - * dependency footprint of this artifact. These utility methods typically - * live in web-utilities but that would pull in spring, jersey, jackson, - * etc. - */ - private X509Certificate extractClientCertificate(HttpServletRequest request) { - X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); - - if (certs != null && certs.length > 0) { - return certs[0]; - } - - return null; - } - - private String extractPrincipal(X509Certificate cert) { - return cert.getSubjectDN().getName().trim(); - } - - private String formatProxyDn(String dn) { - return "<" + dn + ">"; - } } http://git-wip-us.apache.org/repos/asf/nifi/blob/fd6eb669/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-custom-ui-utilities/src/main/java/org/apache/nifi/web/HttpServletRequestContextConfig.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-custom-ui-utilities/src/main/java/org/apache/nifi/web/HttpServletRequestContextConfig.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-custom-ui-utilities/src/main/java/org/apache/nifi/web/HttpServletRequestContextConfig.java index e376ab6..009289d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-custom-ui-utilities/src/main/java/org/apache/nifi/web/HttpServletRequestContextConfig.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-custom-ui-utilities/src/main/java/org/apache/nifi/web/HttpServletRequestContextConfig.java @@ -16,7 +16,6 @@ */ package org.apache.nifi.web; -import java.security.cert.X509Certificate; import javax.servlet.http.HttpServletRequest; /** @@ -40,20 +39,7 @@ public class HttpServletRequestContextConfig implements NiFiWebContextConfig { @Override public String getProxiedEntitiesChain() { - - String xProxiedEntitiesChain = request.getHeader("X-ProxiedEntitiesChain"); - final X509Certificate cert = extractClientCertificate(request); - if (cert != null) { - final String extractedPrincipal = extractPrincipal(cert); - final String formattedPrincipal = formatProxyDn(extractedPrincipal); - if (xProxiedEntitiesChain == null || xProxiedEntitiesChain.trim().isEmpty()) { - xProxiedEntitiesChain = formattedPrincipal; - } else { - xProxiedEntitiesChain += formattedPrincipal; - } - } - - return xProxiedEntitiesChain; + return null; } /** @@ -92,28 +78,4 @@ public class HttpServletRequestContextConfig implements NiFiWebContextConfig { return new Revision(revision, clientId); } - - /** - * Utility methods that have been copied into this class to reduce the - * dependency footprint of this artifact. These utility methods typically - * live in web-utilities but that would pull in spring, jersey, jackson, - * etc. - */ - private X509Certificate extractClientCertificate(HttpServletRequest request) { - X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); - - if (certs != null && certs.length > 0) { - return certs[0]; - } - - return null; - } - - private String extractPrincipal(X509Certificate cert) { - return cert.getSubjectDN().getName().trim(); - } - - private String formatProxyDn(String dn) { - return "<" + dn + ">"; - } } http://git-wip-us.apache.org/repos/asf/nifi/blob/fd6eb669/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java index afaf3ed..5ff82aa 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java @@ -28,18 +28,13 @@ import org.apache.nifi.cluster.node.Node; import org.apache.nifi.cluster.protocol.NodeIdentifier; import org.apache.nifi.controller.repository.claim.ContentDirection; import org.apache.nifi.util.NiFiProperties; -import org.apache.nifi.web.security.user.NiFiUserDetails; import org.apache.nifi.web.security.user.NiFiUserUtils; -import org.apache.nifi.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import javax.ws.rs.HttpMethod; import javax.ws.rs.core.MultivaluedMap; -import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; @@ -85,22 +80,6 @@ public class StandardNiFiContentAccess implements ContentAccess { // set the headers final Map<String, String> headers = new HashMap<>(); - if (StringUtils.isNotBlank(request.getProxiedEntitiesChain())) { - headers.put("X-ProxiedEntitiesChain", request.getProxiedEntitiesChain()); - } - - // add the user's authorities (if any) to the headers - final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null) { - final Object userDetailsObj = authentication.getPrincipal(); - if (userDetailsObj instanceof NiFiUserDetails) { - // serialize user details object - final String hexEncodedUserDetails = WebUtils.serializeObjectToHex((Serializable) userDetailsObj); - - // put serialized user details in header - headers.put("X-ProxiedEntityUserDetails", hexEncodedUserDetails); - } - } // ensure we were able to detect the cluster node id if (request.getClusterNodeId() == null) { http://git-wip-us.apache.org/repos/asf/nifi/blob/fd6eb669/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java index d1d0796..fb59565 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java @@ -44,22 +44,16 @@ import org.apache.nifi.web.api.dto.RevisionDTO; import org.apache.nifi.web.api.entity.ControllerServiceEntity; import org.apache.nifi.web.api.entity.ProcessorEntity; import org.apache.nifi.web.api.entity.ReportingTaskEntity; -import org.apache.nifi.web.security.user.NiFiUserDetails; import org.apache.nifi.web.security.user.NiFiUserUtils; import org.apache.nifi.web.util.ClientResponseUtils; -import org.apache.nifi.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import javax.ws.rs.HttpMethod; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; - import java.io.IOException; -import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; @@ -803,22 +797,6 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration private Map<String, String> getHeaders(final NiFiWebRequestContext config) { final Map<String, String> headers = new HashMap<>(); headers.put("Accept", "application/json,application/xml"); - if (StringUtils.isNotBlank(config.getProxiedEntitiesChain())) { - headers.put("X-ProxiedEntitiesChain", config.getProxiedEntitiesChain()); - } - - // add the user's authorities (if any) to the headers - final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null) { - final Object userDetailsObj = authentication.getPrincipal(); - if (userDetailsObj instanceof NiFiUserDetails) { - // serialize user details object - final String hexEncodedUserDetails = WebUtils.serializeObjectToHex((Serializable) userDetailsObj); - - // put serialized user details in header - headers.put("X-ProxiedEntityUserDetails", hexEncodedUserDetails); - } - } return headers; } http://git-wip-us.apache.org/repos/asf/nifi/blob/fd6eb669/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java index 96ed822..504926f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java @@ -16,22 +16,8 @@ */ package org.apache.nifi.web; -import java.io.Serializable; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; - -import javax.ws.rs.HttpMethod; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; - +import com.sun.jersey.core.util.MultivaluedMapImpl; +import org.apache.commons.lang3.StringUtils; import org.apache.nifi.action.Action; import org.apache.nifi.action.Component; import org.apache.nifi.action.FlowChangeAction; @@ -42,26 +28,32 @@ import org.apache.nifi.admin.service.AuditService; import org.apache.nifi.cluster.manager.NodeResponse; import org.apache.nifi.cluster.manager.impl.WebClusterManager; import org.apache.nifi.controller.ControllerService; -import org.apache.nifi.web.security.user.NiFiUserDetails; -import org.apache.nifi.web.security.user.NiFiUserUtils; +import org.apache.nifi.controller.ControllerServiceLookup; import org.apache.nifi.user.NiFiUser; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.api.dto.ProcessorConfigDTO; import org.apache.nifi.web.api.dto.ProcessorDTO; import org.apache.nifi.web.api.dto.RevisionDTO; import org.apache.nifi.web.api.entity.ProcessorEntity; -import org.apache.nifi.web.util.WebUtils; - -import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.web.security.user.NiFiUserUtils; +import org.apache.nifi.web.util.ClientResponseUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import com.sun.jersey.core.util.MultivaluedMapImpl; -import org.apache.nifi.controller.ControllerServiceLookup; -import org.apache.nifi.web.util.ClientResponseUtils; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; /** * Implements the NiFiWebContext interface to support a context in both standalone and clustered environments. @@ -297,22 +289,6 @@ public class StandardNiFiWebContext implements NiFiWebContext { private Map<String, String> getHeaders(final NiFiWebContextConfig config) { final Map<String, String> headers = new HashMap<>(); headers.put("Accept", "application/json,application/xml"); - if (StringUtils.isNotBlank(config.getProxiedEntitiesChain())) { - headers.put("X-ProxiedEntitiesChain", config.getProxiedEntitiesChain()); - } - - // add the user's authorities (if any) to the headers - final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null) { - final Object userDetailsObj = authentication.getPrincipal(); - if (userDetailsObj instanceof NiFiUserDetails) { - // serialize user details object - final String hexEncodedUserDetails = WebUtils.serializeObjectToHex((Serializable) userDetailsObj); - - // put serialized user details in header - headers.put("X-ProxiedEntityUserDetails", hexEncodedUserDetails); - } - } return headers; } http://git-wip-us.apache.org/repos/asf/nifi/blob/fd6eb669/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java index 6f895b8..77a00be 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java @@ -32,13 +32,9 @@ import org.apache.nifi.cluster.manager.impl.WebClusterManager; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.api.entity.Entity; import org.apache.nifi.web.api.request.ClientIdParameter; -import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter; -import org.apache.nifi.web.security.user.NiFiUserDetails; import org.apache.nifi.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -72,8 +68,6 @@ public abstract class ApplicationResource { public static final String PROXY_HOST_HTTP_HEADER = "X-ProxyHost"; public static final String PROXY_PORT_HTTP_HEADER = "X-ProxyPort"; public static final String PROXY_CONTEXT_PATH_HTTP_HEADER = "X-ProxyContextPath"; - public static final String PROXIED_ENTITIES_CHAIN_HTTP_HEADER = "X-ProxiedEntitiesChain"; - public static final String PROXIED_ENTITY_USER_DETAILS_HTTP_HEADER = "X-ProxiedEntityUserDetails"; private static final int HEADER_BUFFER_SIZE = 16 * 1024; // 16kb private static final int CLUSTER_CONTEXT_HEADER_VALUE_MAX_BYTES = (int) (0.75 * HEADER_BUFFER_SIZE); @@ -372,24 +366,6 @@ public abstract class ApplicationResource { result.put(PROXY_SCHEME_HTTP_HEADER, httpServletRequest.getScheme()); } - if (httpServletRequest.isSecure()) { - - // add the user's authorities (if any) to the headers - final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null) { - final Object userDetailsObj = authentication.getPrincipal(); - if (userDetailsObj instanceof NiFiUserDetails) { - // serialize user details object - final String hexEncodedUserDetails = WebUtils.serializeObjectToHex((Serializable) userDetailsObj); - - // put serialized user details in header - result.put(PROXIED_ENTITY_USER_DETAILS_HTTP_HEADER, hexEncodedUserDetails); - - // remove the access token if present, since the user is already authenticated/authorized - result.remove(JwtAuthenticationFilter.AUTHORIZATION); - } - } - } return result; } http://git-wip-us.apache.org/repos/asf/nifi/blob/fd6eb669/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-access/src/main/java/org/apache/nifi/web/ContentRequestContext.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-access/src/main/java/org/apache/nifi/web/ContentRequestContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-access/src/main/java/org/apache/nifi/web/ContentRequestContext.java index 6154576..a7d83e3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-access/src/main/java/org/apache/nifi/web/ContentRequestContext.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-access/src/main/java/org/apache/nifi/web/ContentRequestContext.java @@ -45,7 +45,12 @@ public interface ContentRequestContext { /** * The proxy chain for the current request, if applicable. * + * Update: + * This method has been deprecated since the entire proxy + * chain is able to be rebuilt using the current user if necessary. + * * @return the proxied entities chain */ + @Deprecated String getProxiedEntitiesChain(); } http://git-wip-us.apache.org/repos/asf/nifi/blob/fd6eb669/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java index 50ca101..eda65e4 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java @@ -18,16 +18,6 @@ package org.apache.nifi.web; import com.ibm.icu.text.CharsetDetector; import com.ibm.icu.text.CharsetMatch; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; - -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -41,6 +31,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.AccessDeniedException; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; + /** * Controller servlet for viewing content. This is responsible for generating * the markup for the header and footer of the page. Included in that is the @@ -299,7 +299,6 @@ public class ContentViewerController extends HttpServlet { private ContentRequestContext getContentRequest(final HttpServletRequest request) { final String ref = request.getParameter("ref"); final String clientId = request.getParameter("clientId"); - final String proxiedEntitiesChain = request.getHeader("X-ProxiedEntitiesChain"); final URI refUri = URI.create(ref); final String query = refUri.getQuery(); @@ -334,7 +333,7 @@ public class ContentViewerController extends HttpServlet { @Override public String getProxiedEntitiesChain() { - return proxiedEntitiesChain; + return null; } }; } http://git-wip-us.apache.org/repos/asf/nifi/blob/fd6eb669/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java index 4b4c66a..d217532 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java @@ -16,14 +16,6 @@ */ package org.apache.nifi.web.security; -import java.io.IOException; -import java.io.PrintWriter; -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 org.apache.commons.lang3.StringUtils; import org.apache.nifi.user.NiFiUser; import org.apache.nifi.util.NiFiProperties; @@ -40,6 +32,16 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UsernameNotFoundException; 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.io.PrintWriter; +import java.util.List; + /** * */ @@ -85,7 +87,7 @@ public abstract class NiFiAuthenticationFilter extends GenericFilterBean { try { final NiFiAuthorizationRequestToken authenticated = attemptAuthentication(request); if (authenticated != null) { - dnChain = ProxiedEntitiesUtils.formatProxyDn(StringUtils.join(authenticated.getChain(), "><")); + dnChain = formatProxyChain(authenticated.getChain()); // log the request attempt - response details will be logged later log.info(String.format("Attempting request for (%s) %s %s (source ip: %s)", dnChain, request.getMethod(), @@ -117,6 +119,10 @@ public abstract class NiFiAuthenticationFilter extends GenericFilterBean { } } + private String formatProxyChain(final List<String> dnChain) { + return "<" + StringUtils.join(dnChain, "><") + ">"; + } + /** * Attempt to authenticate the client making the request. If the request does not contain an authentication attempt, this method should return null. If the request contains an authentication * request, the implementation should convert it to a NiFiAuthorizationRequestToken (which is used when authorizing the client). Implementations should throw InvalidAuthenticationException when http://git-wip-us.apache.org/repos/asf/nifi/blob/fd6eb669/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java index 1b2f28a..3fb883e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java @@ -16,27 +16,34 @@ */ package org.apache.nifi.web.security; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.user.NiFiUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** * */ public class ProxiedEntitiesUtils { + private static final Logger logger = LoggerFactory.getLogger(ProxiedEntitiesUtils.class); + public static final String PROXY_ENTITIES_CHAIN = "X-ProxiedEntitiesChain"; public static final String PROXY_ENTITIES_ACCEPTED = "X-ProxiedEntitiesAccepted"; public static final String PROXY_ENTITIES_DETAILS = "X-ProxiedEntitiesDetails"; - private static final Pattern proxyChainPattern = Pattern.compile("<(.*?)>"); + private static final String GT = ">"; + private static final String ESCAPED_GT = "\\\\>"; + private static final String LT = "<"; + private static final String ESCAPED_LT = "\\\\<"; /** * Formats the specified DN to be set as a HTTP header using well known conventions. @@ -45,7 +52,51 @@ public class ProxiedEntitiesUtils { * @return the dn formatted as an HTTP header */ public static String formatProxyDn(String dn) { - return "<" + dn + ">"; + return LT + sanitizeDn(dn) + GT; + } + + /** + * If a user provides a DN with the sequence '><', they could escape the tokenization process and impersonate another user. + * <p> + * Example: + * <p> + * Provided DN: {@code tom><alopresto} -> {@code <tom><alopresto><proxy...>} would allow the user to impersonate tom + * + * @param rawDn the unsanitized DN + * @return the sanitized DN + */ + private static String sanitizeDn(String rawDn) { + if (StringUtils.isEmpty(rawDn)) { + return rawDn; + } else { + String sanitizedDn = rawDn.replaceAll(GT, ESCAPED_GT).replaceAll(LT, ESCAPED_LT); + if (!sanitizedDn.equals(rawDn)) { + logger.warn("The provided DN [" + rawDn + "] contained dangerous characters that were escaped to [" + sanitizedDn + "]"); + } + return sanitizedDn; + } + } + + /** + * Reconstitutes the original DN from the sanitized version passed in the proxy chain. + * <p> + * Example: + * <p> + * {@code tom\>\<proxy1} -> {@code tom><proxy1} + * + * @param sanitizedDn the sanitized DN + * @return the original DN + */ + private static String unsanitizeDn(String sanitizedDn) { + if (StringUtils.isEmpty(sanitizedDn)) { + return sanitizedDn; + } else { + String unsanitizedDn = sanitizedDn.replaceAll(ESCAPED_GT, GT).replaceAll(ESCAPED_LT, LT); + if (!unsanitizedDn.equals(sanitizedDn)) { + logger.warn("The provided DN [" + sanitizedDn + "] had been escaped, and was reconstituted to the dangerous DN [" + unsanitizedDn + "]"); + } + return unsanitizedDn; + } } /** @@ -56,9 +107,27 @@ public class ProxiedEntitiesUtils { */ public static List<String> tokenizeProxiedEntitiesChain(String rawProxyChain) { final List<String> proxyChain = new ArrayList<>(); - final Matcher rawProxyChainMatcher = proxyChainPattern.matcher(rawProxyChain); - while (rawProxyChainMatcher.find()) { - proxyChain.add(rawProxyChainMatcher.group(1)); + if (!StringUtils.isEmpty(rawProxyChain)) { + // Split the String on the >< token + List<String> elements = Arrays.asList(StringUtils.splitByWholeSeparatorPreserveAllTokens(rawProxyChain, "><")); + + // Unsanitize each DN and collect back + List<String> unsanitizedElements = new ArrayList<>(elements.size()); + for (final String element : elements) { + unsanitizedElements.add(unsanitizeDn(element)); + } + + // Remove the leading < from the first element + unsanitizedElements.set(0, unsanitizedElements.get(0).replaceFirst(LT, "")); + + // Remove the trailing > from the last element + int last = unsanitizedElements.size() - 1; + String lastElement = unsanitizedElements.get(last); + if (lastElement.endsWith(GT)) { + unsanitizedElements.set(last, lastElement.substring(0, lastElement.length() - 1)); + } + + proxyChain.addAll(unsanitizedElements); } return proxyChain; @@ -68,18 +137,6 @@ public class ProxiedEntitiesUtils { * Builds the proxy chain for the specified user. * * @param user The current user - * @return The proxy chain for that user in String form - */ - public static String buildProxiedEntitiesChainString(final NiFiUser user) { - // calculate the dn chain - final List<String> proxyChain = buildProxiedEntitiesChain(user); - return formatProxyDn(StringUtils.join(proxyChain, "><")); - } - - /** - * Builds the proxy chain for the specified user. - * - * @param user The current user * @return The proxy chain for that user in List form */ public static List<String> buildProxiedEntitiesChain(final NiFiUser user) { @@ -88,13 +145,13 @@ public class ProxiedEntitiesUtils { // build the dn chain NiFiUser chainedUser = user; - do { + while (chainedUser != null) { // add the entry for this user proxyChain.add(chainedUser.getIdentity()); // go to the next user in the chain chainedUser = chainedUser.getChain(); - } while (chainedUser != null); + } return proxyChain; } @@ -107,30 +164,9 @@ public class ProxiedEntitiesUtils { * @return the proxy chain in list form */ public static List<String> buildProxiedEntitiesChain(final HttpServletRequest request, final String username) { - final String chain = buildProxiedEntitiesChainString(request, username); - return tokenizeProxiedEntitiesChain(chain); - } - - /** - * Builds the dn chain from the specified request and user. - * - * @param request the request - * @param username the username - * @return the dn chain in string form - */ - public static String buildProxiedEntitiesChainString(final HttpServletRequest request, final String username) { - String principal; - if (username.startsWith("<") && username.endsWith(">")) { - principal = username; - } else { - principal = formatProxyDn(username); - } - - // look for a proxied user - if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { - principal = request.getHeader(PROXY_ENTITIES_CHAIN) + principal; - } - return principal; + final List<String> proxyChain = new ArrayList<>(tokenizeProxiedEntitiesChain(request.getHeader(PROXY_ENTITIES_CHAIN))); + proxyChain.add(username); + return proxyChain; } public static void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) { http://git-wip-us.apache.org/repos/asf/nifi/blob/fd6eb669/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/ProxiedEntitiesUtilsTest.groovy ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/ProxiedEntitiesUtilsTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/ProxiedEntitiesUtilsTest.groovy new file mode 100644 index 0000000..f0511bd --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/ProxiedEntitiesUtilsTest.groovy @@ -0,0 +1,235 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.web.security + +import org.apache.nifi.user.NiFiUser +import org.junit.After +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +@RunWith(JUnit4.class) +class ProxiedEntitiesUtilsTest { + private static final Logger logger = LoggerFactory.getLogger(ProxiedEntitiesUtils.class) + + private static final String SAFE_USER_NAME_ANDY = "alopresto" + private static final String SAFE_USER_DN_ANDY = "CN=${SAFE_USER_NAME_ANDY}, OU=Apache NiFi" + + private static final String SAFE_USER_NAME_TOM = "tom" + private static final String SAFE_USER_DN_TOM = "CN=${SAFE_USER_NAME_TOM}, OU=Apache NiFi" + + private static final String SAFE_USER_NAME_PROXY_1 = "proxy1.nifi.apache.org" + private static final String SAFE_USER_DN_PROXY_1 = "CN=${SAFE_USER_NAME_PROXY_1}, OU=Apache NiFi" + + private static final String SAFE_USER_NAME_PROXY_2 = "proxy2.nifi.apache.org" + private static final String SAFE_USER_DN_PROXY_2 = "CN=${SAFE_USER_NAME_PROXY_2}, OU=Apache NiFi" + + private static + final String MALICIOUS_USER_NAME_TOM = "${SAFE_USER_NAME_TOM}, OU=Apache NiFi><CN=${SAFE_USER_NAME_PROXY_1}" + private static final String MALICIOUS_USER_DN_TOM = "CN=${MALICIOUS_USER_NAME_TOM}, OU=Apache NiFi" + + private static + final String MALICIOUS_USER_NAME_TOM_ESCAPED = sanitizeDn(MALICIOUS_USER_NAME_TOM) + private static final String MALICIOUS_USER_DN_TOM_ESCAPED = sanitizeDn(MALICIOUS_USER_DN_TOM) + + @BeforeClass + static void setUpOnce() throws Exception { + logger.metaClass.methodMissing = { String name, args -> + logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}") + } + } + + @Before + void setUp() { + } + + @After + void tearDown() { + } + + private static String sanitizeDn(String dn = "") { + dn.replaceAll(/>/, '\\\\>').replaceAll('<', '\\\\<') + } + + private static String printUnicodeString(final String raw) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < raw.size(); i++) { + int codePoint = Character.codePointAt(raw, i) + int charCount = Character.charCount(codePoint) + if (charCount > 1) { + i += charCount - 1 // 2. + if (i >= raw.length()) { + throw new IllegalArgumentException("Code point indicated more characters than available") + } + } + sb.append(String.format("\\u%04x ", codePoint)) + } + return sb.toString().trim() + } + + @Test + void testSanitizeDnShouldHandleFuzzing() throws Exception { + // Arrange + final String DESIRED_NAME = SAFE_USER_NAME_TOM + logger.info(" Desired name: ${DESIRED_NAME} | ${printUnicodeString(DESIRED_NAME)}") + + // Contains various attempted >< escapes, trailing NULL, and BACKSPACE + 'n' + final List MALICIOUS_NAMES = [MALICIOUS_USER_NAME_TOM, + SAFE_USER_NAME_TOM + ">", + SAFE_USER_NAME_TOM + "><>", + SAFE_USER_NAME_TOM + "\\>", + SAFE_USER_NAME_TOM + "\u003e", + SAFE_USER_NAME_TOM + "\u005c\u005c\u003e", + SAFE_USER_NAME_TOM + "\u0000", + SAFE_USER_NAME_TOM + "\u0008n"] + + // Act + MALICIOUS_NAMES.each { String name -> + logger.info(" Raw name: ${name} | ${printUnicodeString(name)}") + String sanitizedName = ProxiedEntitiesUtils.sanitizeDn(name) + logger.info("Sanitized name: ${sanitizedName} | ${printUnicodeString(sanitizedName)}") + + // Assert + assert sanitizedName != DESIRED_NAME + } + } + + @Test + void testShouldFormatProxyDn() throws Exception { + // Arrange + final String DN = SAFE_USER_DN_TOM + logger.info(" Provided proxy DN: ${DN}") + + final String EXPECTED_PROXY_DN = "<${DN}>" + logger.info(" Expected proxy DN: ${EXPECTED_PROXY_DN}") + + // Act + String formattedProxyDn = ProxiedEntitiesUtils.formatProxyDn(DN) + logger.info("Formatted proxy DN: ${formattedProxyDn}") + + // Assert + assert formattedProxyDn == EXPECTED_PROXY_DN + } + + @Test + void testFormatProxyDnShouldHandleMaliciousInput() throws Exception { + // Arrange + final String DN = MALICIOUS_USER_DN_TOM + logger.info(" Provided proxy DN: ${DN}") + + final String SANITIZED_DN = sanitizeDn(DN) + final String EXPECTED_PROXY_DN = "<${SANITIZED_DN}>" + logger.info(" Expected proxy DN: ${EXPECTED_PROXY_DN}") + + // Act + String formattedProxyDn = ProxiedEntitiesUtils.formatProxyDn(DN) + logger.info("Formatted proxy DN: ${formattedProxyDn}") + + // Assert + assert formattedProxyDn == EXPECTED_PROXY_DN + } + + @Test + void testShouldTokenizeProxiedEntitiesChainWithUserNames() throws Exception { + // Arrange + final List NAMES = [SAFE_USER_NAME_TOM, SAFE_USER_NAME_PROXY_1, SAFE_USER_NAME_PROXY_2] + final String RAW_PROXY_CHAIN = "<${NAMES.join("><")}>" + logger.info(" Provided proxy chain: ${RAW_PROXY_CHAIN}") + + // Act + def tokenizedNames = ProxiedEntitiesUtils.tokenizeProxiedEntitiesChain(RAW_PROXY_CHAIN) + logger.info("Tokenized proxy chain: ${tokenizedNames}") + + // Assert + assert tokenizedNames == NAMES + } + + @Test + void testShouldTokenizeProxiedEntitiesChainWithDNs() throws Exception { + // Arrange + final List DNS = [SAFE_USER_DN_TOM, SAFE_USER_DN_PROXY_1, SAFE_USER_DN_PROXY_2] + final String RAW_PROXY_CHAIN = "<${DNS.join("><")}>" + logger.info(" Provided proxy chain: ${RAW_PROXY_CHAIN}") + + // Act + def tokenizedDns = ProxiedEntitiesUtils.tokenizeProxiedEntitiesChain(RAW_PROXY_CHAIN) + logger.info("Tokenized proxy chain: ${tokenizedDns.collect { "\"${it}\"" }}") + + // Assert + assert tokenizedDns == DNS + } + + @Test + void testTokenizeProxiedEntitiesChainShouldHandleMaliciousUser() throws Exception { + // Arrange + final List NAMES = [MALICIOUS_USER_NAME_TOM, SAFE_USER_NAME_PROXY_1, SAFE_USER_NAME_PROXY_2] + final String RAW_PROXY_CHAIN = "<${NAMES.collect { sanitizeDn(it) }.join("><")}>" + logger.info(" Provided proxy chain: ${RAW_PROXY_CHAIN}") + + // Act + def tokenizedNames = ProxiedEntitiesUtils.tokenizeProxiedEntitiesChain(RAW_PROXY_CHAIN) + logger.info("Tokenized proxy chain: ${tokenizedNames.collect { "\"${it}\"" }}") + + // Assert + assert tokenizedNames == NAMES + assert tokenizedNames.size() == NAMES.size() + assert !tokenizedNames.contains(SAFE_USER_NAME_TOM) + } + + @Test + void testShouldBuildProxyChain() throws Exception { + // Arrange + def mockProxy1 = [getIdentity: { -> SAFE_USER_NAME_PROXY_1}, getChain: { -> null}] as NiFiUser + def mockTom = [getIdentity: { -> SAFE_USER_NAME_TOM}, getChain: { -> mockProxy1}] as NiFiUser + + // Act + List<String> proxiedEntitiesChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(mockTom) + logger.info("Proxied entities chain: ${proxiedEntitiesChain}") + + // Assert + assert proxiedEntitiesChain == [SAFE_USER_NAME_TOM, SAFE_USER_NAME_PROXY_1] + } + + @Test + void testShouldBuildProxyChainFromSingleUser() throws Exception { + // Arrange + def mockTom = [getIdentity: { -> SAFE_USER_NAME_TOM}, getChain: { -> null}] as NiFiUser + + // Act + List<String> proxiedEntitiesChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(mockTom) + logger.info("Proxied entities chain: ${proxiedEntitiesChain}") + + // Assert + assert proxiedEntitiesChain == [SAFE_USER_NAME_TOM] + } + + @Test + void testBuildProxyChainFromNullUserShouldBeEmpty() throws Exception { + // Arrange + + // Act + List<String> proxiedEntitiesChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(null) + logger.info("Proxied entities chain: ${proxiedEntitiesChain}") + + // Assert + assert proxiedEntitiesChain == [] + } +} http://git-wip-us.apache.org/repos/asf/nifi/blob/fd6eb669/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/main/java/org/apache/nifi/processors/kafka/PutKafka.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/main/java/org/apache/nifi/processors/kafka/PutKafka.java b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/main/java/org/apache/nifi/processors/kafka/PutKafka.java index 38ec20c..c5af063 100644 --- a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/main/java/org/apache/nifi/processors/kafka/PutKafka.java +++ b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/main/java/org/apache/nifi/processors/kafka/PutKafka.java @@ -16,23 +16,6 @@ */ package org.apache.nifi.processors.kafka; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Properties; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.regex.Pattern; - import org.apache.nifi.annotation.behavior.DynamicProperty; import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; @@ -40,8 +23,6 @@ import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; -import org.apache.nifi.components.ValidationContext; -import org.apache.nifi.components.ValidationResult; import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.processor.DataUnit; import org.apache.nifi.processor.ProcessContext; @@ -52,6 +33,22 @@ import org.apache.nifi.processor.io.InputStreamCallback; import org.apache.nifi.processor.util.StandardValidators; import org.apache.nifi.processors.kafka.KafkaPublisher.KafkaPublisherResult; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; + @InputRequirement(Requirement.INPUT_REQUIRED) @Tags({ "Apache", "Kafka", "Put", "Send", "Message", "PubSub", "0.8.x"}) @CapabilityDescription("Sends the contents of a FlowFile as a message to Apache Kafka, specifically for 0.8.x versions. The messages to send may be individual FlowFiles or may be delimited, using a "
