mirror of https://github.com/apache/nifi.git
NIFI-14026 Added proxied-entity module for web-security and toolkit
- Added ProxiedEntityEncoder interface and implementation - Removed nifi-web-security dependency from nifi-toolkit-client - Removed Google Guava dependency from nifi-toolkit-cli Signed-off-by: Pierre Villard <pierre.villard.fr@gmail.com> This closes #9567.
This commit is contained in:
parent
df793ce14e
commit
02947e0eee
|
@ -0,0 +1,25 @@
|
|||
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-commons</artifactId>
|
||||
<version>2.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-security-proxied-entity</artifactId>
|
||||
<description>Shared security components for handling Proxied Entities in HTTP requests and response</description>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.security.proxied.entity;
|
||||
|
||||
/**
|
||||
* Proxied Entity Encoder sanitizes and formats identities for transmission as HTTP request headers
|
||||
*/
|
||||
public interface ProxiedEntityEncoder {
|
||||
/**
|
||||
* Get encoded representation of identity suitable for transmission
|
||||
* @param identity Identity to be encoded
|
||||
* @return Encoded Entity
|
||||
*/
|
||||
String getEncodedEntity(String identity);
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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.security.proxied.entity;
|
||||
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Standard implementation singleton instance of Proxied Entity Encoder
|
||||
*/
|
||||
public class StandardProxiedEntityEncoder implements ProxiedEntityEncoder {
|
||||
private static final String DELIMITED_FORMAT = "<%s>";
|
||||
|
||||
private static final String GT = ">";
|
||||
|
||||
private static final String ESCAPED_GT = "\\\\>";
|
||||
|
||||
private static final String LT = "<";
|
||||
|
||||
private static final String ESCAPED_LT = "\\\\<";
|
||||
|
||||
private static final CharsetEncoder headerValueCharsetEncoder = StandardCharsets.US_ASCII.newEncoder();
|
||||
|
||||
private static final Base64.Encoder headerValueEncoder = Base64.getEncoder();
|
||||
|
||||
private static final StandardProxiedEntityEncoder encoder = new StandardProxiedEntityEncoder();
|
||||
|
||||
private StandardProxiedEntityEncoder() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get singleton instance of standard encoder
|
||||
*
|
||||
* @return Proxied Entity Encoder
|
||||
*/
|
||||
public static ProxiedEntityEncoder getInstance() {
|
||||
return encoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get encoded entity sanitized and formatted for transmission as an HTTP header
|
||||
*
|
||||
* @param identity Identity to be encoded
|
||||
* @return Encoded Entity
|
||||
*/
|
||||
@Override
|
||||
public String getEncodedEntity(final String identity) {
|
||||
final String sanitizedIdentity = getSanitizedIdentity(identity);
|
||||
return DELIMITED_FORMAT.formatted(sanitizedIdentity);
|
||||
}
|
||||
|
||||
private String getSanitizedIdentity(final String identity) {
|
||||
final String sanitized;
|
||||
|
||||
if (identity == null || identity.isEmpty()) {
|
||||
sanitized = identity;
|
||||
} else {
|
||||
final String escaped = identity.replaceAll(LT, ESCAPED_LT).replaceAll(GT, ESCAPED_GT);
|
||||
|
||||
if (headerValueCharsetEncoder.canEncode(escaped)) {
|
||||
// Strings limited to US-ASCII characters can be transmitted as HTTP header values without encoding
|
||||
sanitized = escaped;
|
||||
} else {
|
||||
// Non-ASCII characters require Base64 encoding and additional wrapping for transmission as headers
|
||||
final byte[] escapedBinary = escaped.getBytes(StandardCharsets.UTF_8);
|
||||
final String escapedEncoded = headerValueEncoder.encodeToString(escapedBinary);
|
||||
sanitized = DELIMITED_FORMAT.formatted(escapedEncoded);
|
||||
}
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.security.proxied.entity;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class StandardProxiedEntityEncoderTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource(
|
||||
delimiter = ':',
|
||||
value = {
|
||||
":<null>",
|
||||
"'':<>",
|
||||
"username:<username>",
|
||||
"CN=Common Name, OU=Organizational Unit:<CN=Common Name, OU=Organizational Unit>",
|
||||
"CN=nifi.apache.org, O=Organization:<CN=nifi.apache.org, O=Organization>",
|
||||
"<username>:<\\<username\\>>",
|
||||
"<<username>>:<\\<\\<username\\>\\>>",
|
||||
"CN=\uD83D\uDE00:<<Q0498J+YgA==>>"
|
||||
}
|
||||
)
|
||||
void testGetEncodedEntity(final String identity, final String expected) {
|
||||
final String encodedEntity = StandardProxiedEntityEncoder.getInstance().getEncodedEntity(identity);
|
||||
|
||||
assertEquals(expected, encodedEntity);
|
||||
}
|
||||
}
|
|
@ -51,6 +51,7 @@
|
|||
<module>nifi-security-identity</module>
|
||||
<module>nifi-security-kerberos-api</module>
|
||||
<module>nifi-security-kerberos</module>
|
||||
<module>nifi-security-proxied-entity</module>
|
||||
<module>nifi-security-ssl</module>
|
||||
<module>nifi-security-utils-api</module>
|
||||
<module>nifi-single-user-utils</module>
|
||||
|
|
|
@ -111,6 +111,11 @@
|
|||
<artifactId>nifi-security-cert</artifactId>
|
||||
<version>2.1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-proxied-entity</artifactId>
|
||||
<version>2.1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
|
|
|
@ -29,6 +29,8 @@ import jakarta.servlet.http.HttpServletResponse;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.nifi.authorization.user.NiFiUser;
|
||||
import org.apache.nifi.authorization.user.NiFiUserUtils;
|
||||
import org.apache.nifi.security.proxied.entity.ProxiedEntityEncoder;
|
||||
import org.apache.nifi.security.proxied.entity.StandardProxiedEntityEncoder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
|
@ -51,32 +53,7 @@ public class ProxiedEntitiesUtils {
|
|||
private static final String ANONYMOUS_CHAIN = "<>";
|
||||
private static final String ANONYMOUS_IDENTITY = "";
|
||||
|
||||
/**
|
||||
* Formats a list of DN/usernames to be set as a HTTP header using well known conventions.
|
||||
*
|
||||
* @param proxiedEntities the raw identities (usernames and DNs) to be formatted as a chain
|
||||
* @return the value to use in the X-ProxiedEntitiesChain header
|
||||
*/
|
||||
public static String getProxiedEntitiesChain(final String... proxiedEntities) {
|
||||
return getProxiedEntitiesChain(Arrays.asList(proxiedEntities));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a list of DN/usernames to be set as a HTTP header using well known conventions.
|
||||
*
|
||||
* @param proxiedEntities the raw identities (usernames and DNs) to be formatted as a chain
|
||||
* @return the value to use in the X-ProxiedEntitiesChain header
|
||||
*/
|
||||
public static String getProxiedEntitiesChain(final List<String> proxiedEntities) {
|
||||
if (proxiedEntities == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final List<String> proxiedEntityChain = proxiedEntities.stream()
|
||||
.map(org.apache.nifi.registry.security.util.ProxiedEntitiesUtils::formatProxyDn)
|
||||
.collect(Collectors.toList());
|
||||
return StringUtils.join(proxiedEntityChain, "");
|
||||
}
|
||||
private static final ProxiedEntityEncoder proxiedEntityEncoder = StandardProxiedEntityEncoder.getInstance();
|
||||
|
||||
/**
|
||||
* Tokenizes the specified proxy chain.
|
||||
|
@ -134,15 +111,13 @@ public class ProxiedEntitiesUtils {
|
|||
if (proxyChain.isEmpty()) {
|
||||
return ANONYMOUS_CHAIN;
|
||||
}
|
||||
proxyChain = proxyChain.stream().map(ProxiedEntitiesUtils::formatProxyDn).collect(Collectors.toList());
|
||||
proxyChain = proxyChain.stream().map(proxiedEntityEncoder::getEncodedEntity).collect(Collectors.toList());
|
||||
return StringUtils.join(proxyChain, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the string representation for a set of groups that belong to a proxied entity.
|
||||
*
|
||||
* The resulting string will be formatted similar to a proxied-entity chain.
|
||||
*
|
||||
* Example:
|
||||
* Groups set: ("group1", "group2", "group3")
|
||||
* Returns: {@code "<group1><group2><group3> }
|
||||
|
@ -155,7 +130,7 @@ public class ProxiedEntitiesUtils {
|
|||
return PROXY_ENTITY_GROUPS_EMPTY;
|
||||
}
|
||||
|
||||
final List<String> formattedGroups = groups.stream().map(ProxiedEntitiesUtils::formatProxyDn).collect(Collectors.toList());
|
||||
final List<String> formattedGroups = groups.stream().map(proxiedEntityEncoder::getEncodedEntity).collect(Collectors.toList());
|
||||
return StringUtils.join(formattedGroups, "");
|
||||
}
|
||||
|
||||
|
@ -187,68 +162,6 @@ public class ProxiedEntitiesUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the specified DN to be set as a HTTP header using well known conventions.
|
||||
*
|
||||
* @param dn raw dn
|
||||
* @return the dn formatted as an HTTP header
|
||||
*/
|
||||
public static String formatProxyDn(final String dn) {
|
||||
return LT + sanitizeDn(dn) + GT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes a DN for safe and lossless transmission.
|
||||
*
|
||||
* Sanitization requires:
|
||||
* <ol>
|
||||
* <li>Encoded so that it can be sent losslessly using US-ASCII (the character set of HTTP Header values)</li>
|
||||
* <li>Resilient to a DN with the sequence '><' to attempt to escape the tokenization process and impersonate another user.</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>
|
||||
* Example:
|
||||
* <p>
|
||||
* Provided DN: {@code jdoe><alopresto} -> {@code <jdoe><alopresto><proxy...>} would allow the user to impersonate jdoe
|
||||
* <p>Алйс
|
||||
* Provided DN: {@code Алйс} -> {@code <Алйс>} cannot be encoded/decoded as ASCII
|
||||
*
|
||||
* @param rawDn the unsanitized DN
|
||||
* @return the sanitized DN
|
||||
*/
|
||||
private static String sanitizeDn(final String rawDn) {
|
||||
if (StringUtils.isEmpty(rawDn)) {
|
||||
return rawDn;
|
||||
} else {
|
||||
|
||||
// First, escape any GT [>] or LT [<] characters, which are not safe
|
||||
final String escapedDn = rawDn.replaceAll(GT, ESCAPED_GT).replaceAll(LT, ESCAPED_LT);
|
||||
if (!escapedDn.equals(rawDn)) {
|
||||
logger.warn("The provided DN [{}] contained dangerous characters that were escaped to [{}]", rawDn, escapedDn);
|
||||
}
|
||||
|
||||
// Second, check for characters outside US-ASCII.
|
||||
// This is necessary because X509 Certs can contain international/Unicode characters,
|
||||
// but this value will be passed in an HTTP Header which must be US-ASCII.
|
||||
// If non-ascii characters are present, base64 encode the DN and wrap in <angled-brackets>,
|
||||
// to indicate to the receiving end that the value must be decoded.
|
||||
// Note: We could have decided to always base64 encode these values,
|
||||
// not only to avoid the isPureAscii(...) check, but also as a
|
||||
// method of sanitizing GT [>] or LT [<] chars. However, there
|
||||
// are advantages to encoding only when necessary, namely:
|
||||
// 1. Backwards compatibility
|
||||
// 2. Debugging this X-ProxiedEntitiesChain headers is easier unencoded.
|
||||
// This algorithm can be revisited as part of the next major version change.
|
||||
if (isPureAscii(escapedDn)) {
|
||||
return escapedDn;
|
||||
} else {
|
||||
final String encodedDn = base64Encode(escapedDn);
|
||||
logger.debug("The provided DN [{}] contained non-ASCII characters and was encoded as [{}]", rawDn, encodedDn);
|
||||
return encodedDn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstitutes the original DN from the sanitized version passed in the proxy chain.
|
||||
* <p>
|
||||
|
@ -281,19 +194,7 @@ public class ProxiedEntitiesUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Base64 encodes a DN and wraps it in angled brackets to indicate the value is base64 and not a raw DN.
|
||||
*
|
||||
* @param rawValue The value to encode
|
||||
* @return A string containing a wrapped, encoded value.
|
||||
*/
|
||||
private static String base64Encode(final String rawValue) {
|
||||
final String base64String = Base64.getEncoder().encodeToString(rawValue.getBytes(StandardCharsets.UTF_8));
|
||||
final String wrappedEncodedValue = LT + base64String + GT;
|
||||
return wrappedEncodedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the reverse of ${@link #base64Encode(String)}.
|
||||
* Performs the reverse of Base64 encoding
|
||||
*
|
||||
* @param encodedValue the encoded value to decode.
|
||||
* @return The original, decoded string.
|
||||
|
@ -314,7 +215,7 @@ public class ProxiedEntitiesUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check if a value has been encoded by ${@link #base64Encode(String)}, and therefore needs to be decoded.
|
||||
* Check if a value has been encoded by Base64 encoding and therefore needs to be decoded.
|
||||
*
|
||||
* @param token the value to check
|
||||
* @return true if the value is encoded, false otherwise.
|
||||
|
@ -332,14 +233,4 @@ public class ProxiedEntitiesUtils {
|
|||
private static boolean isWrappedInAngleBrackets(final String string) {
|
||||
return string.startsWith(LT) && string.endsWith(GT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string contains only pure ascii characters.
|
||||
*
|
||||
* @param stringWithUnknownCharacters - the string to check
|
||||
* @return true if string can be encoded as ascii. false otherwise.
|
||||
*/
|
||||
private static boolean isPureAscii(final String stringWithUnknownCharacters) {
|
||||
return StandardCharsets.US_ASCII.newEncoder().canEncode(stringWithUnknownCharacters);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,6 @@ package org.apache.nifi.web.security;
|
|||
import org.apache.nifi.authorization.user.NiFiUser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
|
@ -33,7 +31,6 @@ import java.util.stream.Collectors;
|
|||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
|
@ -45,7 +42,6 @@ public class ProxiedEntitiesUtilsTest {
|
|||
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_JOHN = SAFE_USER_NAME_JOHN + ", OU=Apache NiFi><CN=" + SAFE_USER_NAME_PROXY_1;
|
||||
private static final String MALICIOUS_USER_DN_JOHN = "CN=" + MALICIOUS_USER_NAME_JOHN + ", OU=Apache NiFi";
|
||||
private static final String MALICIOUS_USER_NAME_JOHN_ESCAPED = sanitizeDn(MALICIOUS_USER_NAME_JOHN);
|
||||
private static final String UNICODE_DN_1 = "CN=Алйс, OU=Apache NiFi";
|
||||
private static final String UNICODE_DN_1_ENCODED = "<" + base64Encode(UNICODE_DN_1) + ">";
|
||||
|
@ -62,57 +58,6 @@ public class ProxiedEntitiesUtilsTest {
|
|||
return Base64.getEncoder().encodeToString(dn.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("getMaliciousNames" )
|
||||
public void testSanitizeDnShouldHandleFuzzing(String maliciousName) {
|
||||
assertNotEquals(formatDn(SAFE_USER_NAME_JOHN), ProxiedEntitiesUtils.formatProxyDn(maliciousName));
|
||||
}
|
||||
|
||||
// Contains various attempted >< escapes, trailing NULL, and BACKSPACE + 'n'
|
||||
private static List<String> getMaliciousNames() {
|
||||
return Arrays.asList(MALICIOUS_USER_NAME_JOHN,
|
||||
SAFE_USER_NAME_JOHN + ">",
|
||||
SAFE_USER_NAME_JOHN + "><>",
|
||||
SAFE_USER_NAME_JOHN + "\\>",
|
||||
SAFE_USER_NAME_JOHN + "\u003e",
|
||||
SAFE_USER_NAME_JOHN + "\u005c\u005c\u003e",
|
||||
SAFE_USER_NAME_JOHN + "\u0000",
|
||||
SAFE_USER_NAME_JOHN + "\u0008n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldFormatProxyDn() {
|
||||
assertEquals(formatDn(SAFE_USER_DN_JOHN), ProxiedEntitiesUtils.formatProxyDn(SAFE_USER_DN_JOHN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatProxyDnShouldHandleMaliciousInput() {
|
||||
assertEquals(formatSanitizedDn(MALICIOUS_USER_DN_JOHN), ProxiedEntitiesUtils.formatProxyDn(MALICIOUS_USER_DN_JOHN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetProxiedEntitiesChain() {
|
||||
String[] input = new String[] {SAFE_USER_NAME_JOHN, SAFE_USER_DN_PROXY_1, SAFE_USER_DN_PROXY_2};
|
||||
assertEquals(formatDns(input), ProxiedEntitiesUtils.getProxiedEntitiesChain(input));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetProxiedEntitiesChainShouldHandleMaliciousInput() {
|
||||
final String expectedOutput = formatSanitizedDn(MALICIOUS_USER_DN_JOHN) + formatDns(SAFE_USER_DN_PROXY_1, SAFE_USER_DN_PROXY_2);
|
||||
assertEquals(expectedOutput, ProxiedEntitiesUtils.getProxiedEntitiesChain(MALICIOUS_USER_DN_JOHN, SAFE_USER_DN_PROXY_1, SAFE_USER_DN_PROXY_2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetProxiedEntitiesChainShouldEncodeUnicode() {
|
||||
assertEquals(formatDns(SAFE_USER_NAME_JOHN, UNICODE_DN_1_ENCODED, UNICODE_DN_2_ENCODED),
|
||||
ProxiedEntitiesUtils.getProxiedEntitiesChain(SAFE_USER_NAME_JOHN, UNICODE_DN_1, UNICODE_DN_2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatProxyDnShouldEncodeNonAsciiCharacters() {
|
||||
assertEquals(formatDn(UNICODE_DN_1_ENCODED), ProxiedEntitiesUtils.formatProxyDn(UNICODE_DN_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldBuildProxyChain(@Mock NiFiUser proxy1, @Mock NiFiUser john) {
|
||||
when(proxy1.getIdentity()).thenReturn(SAFE_USER_NAME_PROXY_1);
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
<description>Tooling to make tls configuration easier</description>
|
||||
|
||||
<properties>
|
||||
<guava.version>33.3.1-jre</guava.version>
|
||||
<jline.version>3.27.1</jline.version>
|
||||
</properties>
|
||||
|
||||
|
@ -85,12 +84,6 @@
|
|||
<artifactId>nifi-registry-client</artifactId>
|
||||
<version>2.1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!-- Override Guava 31.1 through nifi-framework-core -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-kerberos</artifactId>
|
||||
|
|
|
@ -21,7 +21,6 @@ import org.apache.commons.cli.MissingOptionException;
|
|||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import org.apache.nifi.flow.VersionedFlowCoordinates;
|
||||
import org.apache.nifi.flow.VersionedProcessGroup;
|
||||
import org.apache.nifi.registry.bucket.Bucket;
|
||||
|
@ -47,6 +46,7 @@ import java.nio.file.Path;
|
|||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -116,11 +116,9 @@ public class ImportAllFlows extends AbstractNiFiRegistryCommand<StringResult> {
|
|||
final List<VersionFileMetaData> files = getFilePathList(properties);
|
||||
|
||||
// As we need to keep the version order the list needs to be sorted
|
||||
files.sort((o1, o2) -> ComparisonChain.start()
|
||||
.compare(o1.getBucketName(), o2.getBucketName())
|
||||
.compare(o1.getFlowName(), o2.getFlowName())
|
||||
.compare(o1.getVersion(), o2.getVersion())
|
||||
.result());
|
||||
files.sort(Comparator.comparing(VersionFileMetaData::getBucketName)
|
||||
.thenComparing(VersionFileMetaData::getFlowName)
|
||||
.thenComparing(VersionFileMetaData::getVersion));
|
||||
|
||||
for (VersionFileMetaData file : files) {
|
||||
final String inputSource = file.getInputSource();
|
||||
|
|
|
@ -23,7 +23,22 @@ language governing permissions and limitations under the License. -->
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-security</artifactId>
|
||||
<artifactId>nifi-security-utils-api</artifactId>
|
||||
<version>2.1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-ssl</artifactId>
|
||||
<version>2.1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi.registry</groupId>
|
||||
<artifactId>nifi-registry-security-utils</artifactId>
|
||||
<version>2.1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-proxied-entity</artifactId>
|
||||
<version>2.1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -36,6 +51,18 @@ language governing permissions and limitations under the License. -->
|
|||
<artifactId>nifi-client-dto</artifactId>
|
||||
<version>2.1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.ws.rs</groupId>
|
||||
<artifactId>jakarta.ws.rs-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.core</groupId>
|
||||
<artifactId>jersey-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.media</groupId>
|
||||
<artifactId>jersey-media-json-jackson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
|
|
|
@ -20,11 +20,11 @@ import jakarta.ws.rs.client.Entity;
|
|||
import jakarta.ws.rs.client.Invocation;
|
||||
import jakarta.ws.rs.client.WebTarget;
|
||||
import jakarta.ws.rs.core.Form;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.nifi.toolkit.client.AccessClient;
|
||||
import org.apache.nifi.toolkit.client.NiFiClientException;
|
||||
import org.apache.nifi.toolkit.client.RequestConfig;
|
||||
import org.apache.nifi.toolkit.client.impl.request.BearerTokenRequestConfig;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
|
|
@ -19,10 +19,10 @@ package org.apache.nifi.toolkit.client.impl;
|
|||
import jakarta.ws.rs.client.Entity;
|
||||
import jakarta.ws.rs.client.WebTarget;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.nifi.toolkit.client.NiFiClientException;
|
||||
import org.apache.nifi.toolkit.client.RequestConfig;
|
||||
import org.apache.nifi.toolkit.client.TenantsClient;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
import org.apache.nifi.web.api.entity.UserEntity;
|
||||
import org.apache.nifi.web.api.entity.UserGroupEntity;
|
||||
import org.apache.nifi.web.api.entity.UserGroupsEntity;
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
package org.apache.nifi.toolkit.client.impl.request;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.nifi.security.proxied.entity.StandardProxiedEntityEncoder;
|
||||
import org.apache.nifi.toolkit.client.RequestConfig;
|
||||
import org.apache.nifi.web.security.ProxiedEntitiesUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
@ -33,6 +33,8 @@ import java.util.stream.Collectors;
|
|||
*/
|
||||
public class ProxiedEntityRequestConfig implements RequestConfig {
|
||||
|
||||
private static final String PROXIED_ENTITIES_CHAIN_HEADER = "X-Proxied-Entities-Chain";
|
||||
|
||||
private final String[] proxiedEntities;
|
||||
|
||||
public ProxiedEntityRequestConfig(final String... proxiedEntities) {
|
||||
|
@ -41,22 +43,22 @@ public class ProxiedEntityRequestConfig implements RequestConfig {
|
|||
|
||||
@Override
|
||||
public Map<String, String> getHeaders() {
|
||||
final String proxiedEntitiesValue = getProxiedEntitesValue(proxiedEntities);
|
||||
final String proxiedEntitiesValue = getProxiedEntitiesValue(proxiedEntities);
|
||||
|
||||
final Map<String, String> headers = new HashMap<>();
|
||||
if (proxiedEntitiesValue != null) {
|
||||
headers.put(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxiedEntitiesValue);
|
||||
headers.put(PROXIED_ENTITIES_CHAIN_HEADER, proxiedEntitiesValue);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
private String getProxiedEntitesValue(final String[] proxiedEntities) {
|
||||
private String getProxiedEntitiesValue(final String[] proxiedEntities) {
|
||||
if (proxiedEntities == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final List<String> proxiedEntityChain = Arrays.stream(proxiedEntities)
|
||||
.map(ProxiedEntitiesUtils::formatProxyDn)
|
||||
.map(StandardProxiedEntityEncoder.getInstance()::getEncodedEntity)
|
||||
.collect(Collectors.toList());
|
||||
return StringUtils.join(proxiedEntityChain, "");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue