mirror of
https://github.com/apache/nifi.git
synced 2025-02-06 18:18:27 +00:00
NIFI-11195 Refactored Identity Mapping to nifi-security-identity
- Moved StringUtils from nifi-properties to nifi-property-utils - Moved Peer Identity methods from CertificateUtils to specific Site-to-Site classes Signed-off-by: Joe Gresock <jgresock@gmail.com> This closes #6977.
This commit is contained in:
parent
49bc62c928
commit
fec34036a5
@ -15,6 +15,12 @@
|
||||
-->
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-commons</artifactId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-properties</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
@ -23,10 +29,4 @@
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-commons</artifactId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-properties</artifactId>
|
||||
</project>
|
||||
|
35
nifi-commons/nifi-security-identity/pom.xml
Normal file
35
nifi-commons/nifi-security-identity/pom.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<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.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-security-identity</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-properties</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -18,12 +18,10 @@ package org.apache.nifi.security.util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.net.Socket;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Security;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
@ -42,8 +40,6 @@ import javax.naming.InvalidNameException;
|
||||
import javax.naming.ldap.LdapName;
|
||||
import javax.naming.ldap.Rdn;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bouncycastle.asn1.ASN1Encodable;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
@ -81,14 +77,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class CertificateUtils {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CertificateUtils.class);
|
||||
private static final String PEER_NOT_AUTHENTICATED_MSG = "peer not authenticated";
|
||||
private static final Map<ASN1ObjectIdentifier, Integer> dnOrderMap = createDnOrderMap();
|
||||
|
||||
public static final String JAVA_8_MAX_SUPPORTED_TLS_PROTOCOL_VERSION = "TLSv1.2";
|
||||
public static final String JAVA_11_MAX_SUPPORTED_TLS_PROTOCOL_VERSION = "TLSv1.3";
|
||||
public static final String[] JAVA_8_SUPPORTED_TLS_PROTOCOL_VERSIONS = new String[]{JAVA_8_MAX_SUPPORTED_TLS_PROTOCOL_VERSION};
|
||||
public static final String[] JAVA_11_SUPPORTED_TLS_PROTOCOL_VERSIONS = new String[]{JAVA_11_MAX_SUPPORTED_TLS_PROTOCOL_VERSION, JAVA_8_MAX_SUPPORTED_TLS_PROTOCOL_VERSION};
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
@ -206,136 +196,6 @@ public final class CertificateUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DN extracted from the peer certificate (the server DN if run on the client; the client DN (if available) if run on the server).
|
||||
* <p>
|
||||
* If the client auth setting is WANT or NONE and a client certificate is not present, this method will return {@code null}.
|
||||
* If the client auth is NEED, it will throw a {@link CertificateException}.
|
||||
*
|
||||
* @param socket the SSL Socket
|
||||
* @return the extracted DN
|
||||
* @throws CertificateException if there is a problem parsing the certificate
|
||||
*/
|
||||
public static String extractPeerDNFromSSLSocket(Socket socket) throws CertificateException {
|
||||
String dn = null;
|
||||
if (socket instanceof SSLSocket) {
|
||||
final SSLSocket sslSocket = (SSLSocket) socket;
|
||||
|
||||
boolean clientMode = sslSocket.getUseClientMode();
|
||||
logger.debug("SSL Socket in {} mode", clientMode ? "client" : "server");
|
||||
ClientAuth clientAuth = getClientAuthStatus(sslSocket);
|
||||
logger.debug("SSL Socket client auth status: {}", clientAuth);
|
||||
|
||||
if (clientMode) {
|
||||
logger.debug("This socket is in client mode, so attempting to extract certificate from remote 'server' socket");
|
||||
dn = extractPeerDNFromServerSSLSocket(sslSocket);
|
||||
} else {
|
||||
logger.debug("This socket is in server mode, so attempting to extract certificate from remote 'client' socket");
|
||||
dn = extractPeerDNFromClientSSLSocket(sslSocket);
|
||||
}
|
||||
}
|
||||
|
||||
return dn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DN extracted from the client certificate.
|
||||
* <p>
|
||||
* If the client auth setting is WANT or NONE and a certificate is not present (and {@code respectClientAuth} is {@code true}), this method will return {@code null}.
|
||||
* If the client auth is NEED, it will throw a {@link CertificateException}.
|
||||
*
|
||||
* @param sslSocket the SSL Socket
|
||||
* @return the extracted DN
|
||||
* @throws CertificateException if there is a problem parsing the certificate
|
||||
*/
|
||||
private static String extractPeerDNFromClientSSLSocket(SSLSocket sslSocket) throws CertificateException {
|
||||
String dn = null;
|
||||
|
||||
/** The clientAuth value can be "need", "want", or "none"
|
||||
* A client must send client certificates for need, should for want, and will not for none.
|
||||
* This method should throw an exception if none are provided for need, return null if none are provided for want, and return null (without checking) for none.
|
||||
*/
|
||||
|
||||
ClientAuth clientAuth = getClientAuthStatus(sslSocket);
|
||||
logger.debug("SSL Socket client auth status: {}", clientAuth);
|
||||
|
||||
if (clientAuth != ClientAuth.NONE) {
|
||||
try {
|
||||
final Certificate[] certChains = sslSocket.getSession().getPeerCertificates();
|
||||
if (certChains != null && certChains.length > 0) {
|
||||
X509Certificate x509Certificate = convertAbstractX509Certificate(certChains[0]);
|
||||
dn = x509Certificate.getSubjectDN().getName().trim();
|
||||
logger.debug("Extracted DN={} from client certificate", dn);
|
||||
}
|
||||
} catch (SSLPeerUnverifiedException e) {
|
||||
if (e.getMessage().equals(PEER_NOT_AUTHENTICATED_MSG)) {
|
||||
logger.error("The incoming request did not contain client certificates and thus the DN cannot" +
|
||||
" be extracted. Check that the other endpoint is providing a complete client certificate chain");
|
||||
}
|
||||
if (clientAuth == ClientAuth.WANT) {
|
||||
logger.warn("Suppressing missing client certificate exception because client auth is set to 'want'");
|
||||
return null;
|
||||
}
|
||||
throw new CertificateException(e);
|
||||
}
|
||||
}
|
||||
return dn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DN extracted from the server certificate.
|
||||
*
|
||||
* @param socket the SSL Socket
|
||||
* @return the extracted DN
|
||||
* @throws CertificateException if there is a problem parsing the certificate
|
||||
*/
|
||||
private static String extractPeerDNFromServerSSLSocket(Socket socket) throws CertificateException {
|
||||
String dn = null;
|
||||
if (socket instanceof SSLSocket) {
|
||||
final SSLSocket sslSocket = (SSLSocket) socket;
|
||||
try {
|
||||
final Certificate[] certChains = sslSocket.getSession().getPeerCertificates();
|
||||
if (certChains != null && certChains.length > 0) {
|
||||
X509Certificate x509Certificate = convertAbstractX509Certificate(certChains[0]);
|
||||
dn = x509Certificate.getSubjectDN().getName().trim();
|
||||
logger.debug("Extracted DN={} from server certificate", dn);
|
||||
}
|
||||
} catch (SSLPeerUnverifiedException e) {
|
||||
if (e.getMessage().equals(PEER_NOT_AUTHENTICATED_MSG)) {
|
||||
logger.error("The server did not present a certificate and thus the DN cannot" +
|
||||
" be extracted. Check that the other endpoint is providing a complete certificate chain");
|
||||
}
|
||||
throw new CertificateException(e);
|
||||
}
|
||||
}
|
||||
return dn;
|
||||
}
|
||||
|
||||
private static ClientAuth getClientAuthStatus(SSLSocket sslSocket) {
|
||||
return sslSocket.getNeedClientAuth() ? ClientAuth.REQUIRED : sslSocket.getWantClientAuth() ? ClientAuth.WANT : ClientAuth.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts a legacy {@link javax.security.cert.X509Certificate} and returns an {@link X509Certificate}. The {@code javax.*} package certificate classes are for legacy compatibility and should
|
||||
* not be used for new development.
|
||||
*
|
||||
* @param legacyCertificate the {@code javax.security.cert.X509Certificate}
|
||||
* @return a new {@code java.security.cert.X509Certificate}
|
||||
* @throws CertificateException if there is an error generating the new certificate
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static X509Certificate convertLegacyX509Certificate(javax.security.cert.X509Certificate legacyCertificate) throws CertificateException {
|
||||
if (legacyCertificate == null) {
|
||||
throw new IllegalArgumentException("The X.509 certificate cannot be null");
|
||||
}
|
||||
|
||||
try {
|
||||
return formX509Certificate(legacyCertificate.getEncoded());
|
||||
} catch (javax.security.cert.CertificateEncodingException e) {
|
||||
throw new CertificateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts an abstract {@link java.security.cert.Certificate} and returns an {@link X509Certificate}. Because {@code sslSocket.getSession().getPeerCertificates()} returns an array of the
|
||||
* abstract certificates, they must be translated to X.509 to replace the functionality of {@code sslSocket.getSession().getPeerCertificateChain()}.
|
||||
|
@ -46,16 +46,6 @@
|
||||
<artifactId>nifi-utils</artifactId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-utils</artifactId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-utils-api</artifactId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-ssl</artifactId>
|
||||
@ -136,6 +126,14 @@
|
||||
<artifactId>validation-api</artifactId>
|
||||
<version>2.0.1.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<profiles>
|
||||
<profile>
|
||||
|
@ -26,12 +26,12 @@ import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@ -64,7 +64,6 @@ import org.apache.nifi.remote.io.socket.SocketCommunicationsSession;
|
||||
import org.apache.nifi.remote.protocol.CommunicationsSession;
|
||||
import org.apache.nifi.remote.protocol.SiteToSiteTransportProtocol;
|
||||
import org.apache.nifi.remote.protocol.socket.SocketClientProtocol;
|
||||
import org.apache.nifi.security.util.CertificateUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -72,6 +71,8 @@ public class EndpointConnectionPool implements PeerStatusProvider {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(EndpointConnectionPool.class);
|
||||
|
||||
private static final SocketPeerIdentityProvider socketPeerIdentityProvider = new StandardSocketPeerIdentityProvider();
|
||||
|
||||
private final ConcurrentMap<PeerDescription, BlockingQueue<EndpointConnection>> connectionQueueMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final Set<EndpointConnection> activeConnections = Collections.synchronizedSet(new HashSet<>());
|
||||
@ -449,11 +450,12 @@ public class EndpointConnectionPool implements PeerStatusProvider {
|
||||
socket.setSoTimeout(commsTimeout);
|
||||
commsSession = new SocketCommunicationsSession(socket);
|
||||
|
||||
try {
|
||||
final String dn = CertificateUtils.extractPeerDNFromSSLSocket(socket);
|
||||
commsSession.setUserDn(dn);
|
||||
} catch (final CertificateException ex) {
|
||||
throw new IOException(ex);
|
||||
final Optional<String> peerIdentity = socketPeerIdentityProvider.getPeerIdentity(socket);
|
||||
if (peerIdentity.isPresent()) {
|
||||
final String userDn = peerIdentity.get();
|
||||
commsSession.setUserDn(userDn);
|
||||
} else {
|
||||
throw new IOException(String.format("Site-to-Site Peer [%s] Identity not found", socket.getRemoteSocketAddress()));
|
||||
}
|
||||
} else {
|
||||
|
||||
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.remote.client.socket;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Abstraction for reading identity information from socket connections
|
||||
*/
|
||||
public interface SocketPeerIdentityProvider {
|
||||
/**
|
||||
* Get Peer Identity from Socket
|
||||
*
|
||||
* @param socket Socket
|
||||
* @return Peer Identity or empty when not found
|
||||
*/
|
||||
Optional<String> getPeerIdentity(Socket socket);
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.remote.client.socket;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import java.net.Socket;
|
||||
import java.security.Principal;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Standard implementation attempts to read X.509 certificates from an SSLSocket
|
||||
*/
|
||||
public class StandardSocketPeerIdentityProvider implements SocketPeerIdentityProvider {
|
||||
private static final Logger logger = LoggerFactory.getLogger(StandardSocketPeerIdentityProvider.class);
|
||||
|
||||
@Override
|
||||
public Optional<String> getPeerIdentity(final Socket socket) {
|
||||
final Optional<String> peerIdentity;
|
||||
|
||||
if (socket instanceof SSLSocket) {
|
||||
final SSLSocket sslSocket = (SSLSocket) socket;
|
||||
final SSLSession sslSession = sslSocket.getSession();
|
||||
peerIdentity = getPeerIdentity(sslSession);
|
||||
} else {
|
||||
peerIdentity = Optional.empty();
|
||||
}
|
||||
|
||||
return peerIdentity;
|
||||
}
|
||||
|
||||
private Optional<String> getPeerIdentity(final SSLSession sslSession) {
|
||||
String peerIdentity = null;
|
||||
|
||||
final String peerHost = sslSession.getPeerHost();
|
||||
final int peerPort = sslSession.getPeerPort();
|
||||
|
||||
try {
|
||||
final Certificate[] peerCertificates = sslSession.getPeerCertificates();
|
||||
if (peerCertificates == null || peerCertificates.length == 0) {
|
||||
logger.warn("Peer Identity not found: Peer Certificates not provided [{}:{}]", peerHost, peerPort);
|
||||
} else {
|
||||
final X509Certificate peerCertificate = (X509Certificate) peerCertificates[0];
|
||||
final Principal subjectDistinguishedName = peerCertificate.getSubjectDN();
|
||||
peerIdentity = subjectDistinguishedName.getName();
|
||||
}
|
||||
} catch (final SSLPeerUnverifiedException e) {
|
||||
logger.warn("Peer Identity not found: Peer Unverified [{}:{}]", peerHost, peerPort);
|
||||
logger.debug("TLS Protocol [{}] Peer Unverified [{}:{}]", sslSession.getProtocol(), peerHost, peerPort, e);
|
||||
}
|
||||
|
||||
return Optional.ofNullable(peerIdentity);
|
||||
}
|
||||
}
|
@ -47,7 +47,6 @@ import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@ -128,7 +127,6 @@ import org.apache.nifi.remote.protocol.ResponseCode;
|
||||
import org.apache.nifi.remote.protocol.http.HttpHeaders;
|
||||
import org.apache.nifi.remote.protocol.http.HttpProxy;
|
||||
import org.apache.nifi.reporting.Severity;
|
||||
import org.apache.nifi.security.util.CertificateUtils;
|
||||
import org.apache.nifi.stream.io.StreamUtils;
|
||||
import org.apache.nifi.web.api.dto.ControllerDTO;
|
||||
import org.apache.nifi.web.api.dto.remote.PeerDTO;
|
||||
@ -319,9 +317,9 @@ public class SiteToSiteRestApiClient implements Closeable {
|
||||
}
|
||||
|
||||
try {
|
||||
final X509Certificate cert = CertificateUtils.convertAbstractX509Certificate(certChain[0]);
|
||||
final X509Certificate cert = (X509Certificate) certChain[0];
|
||||
trustedPeerDn = cert.getSubjectDN().getName().trim();
|
||||
} catch (final CertificateException e) {
|
||||
} catch (final RuntimeException e) {
|
||||
final String msg = "Could not extract subject DN from SSL session peer certificate";
|
||||
logger.warn(msg);
|
||||
eventReporter.reportEvent(Severity.WARNING, EVENT_CATEGORY, msg);
|
||||
|
@ -16,21 +16,16 @@
|
||||
*/
|
||||
package org.apache.nifi.remote.client
|
||||
|
||||
|
||||
import org.apache.nifi.remote.PeerDescription
|
||||
import org.apache.nifi.remote.PeerStatus
|
||||
import org.apache.nifi.remote.TransferDirection
|
||||
import org.apache.nifi.remote.protocol.SiteToSiteTransportProtocol
|
||||
import org.apache.nifi.remote.util.PeerStatusCache
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import java.security.Security
|
||||
import java.util.concurrent.ArrayBlockingQueue
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
@ -52,16 +47,6 @@ class PeerSelectorTest {
|
||||
private static mockPSP
|
||||
private static mockPP
|
||||
|
||||
|
||||
@BeforeAll
|
||||
static void setUpOnce() throws Exception {
|
||||
Security.addProvider(new BouncyCastleProvider())
|
||||
|
||||
logger.metaClass.methodMissing = { String name, args ->
|
||||
logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// Mock collaborators
|
||||
@ -69,11 +54,6 @@ class PeerSelectorTest {
|
||||
mockPP = mockPeerPersistence()
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
|
||||
}
|
||||
|
||||
private static String buildRemoteInstanceUris(List<String> nodes = DEFAULT_NODES) {
|
||||
String remoteInstanceUris = "http://" + nodes.join(":8443/nifi-api,http://") + ":8443/nifi-api";
|
||||
remoteInstanceUris
|
||||
@ -206,7 +186,6 @@ class PeerSelectorTest {
|
||||
new PeerStatusCache(peerStatuses, System.currentTimeMillis(), remoteInstanceUris, SiteToSiteTransportProtocol.HTTP)
|
||||
},
|
||||
save : { PeerStatusCache psc ->
|
||||
logger.mock("Persisting PeerStatusCache: ${psc}")
|
||||
}] as PeerPersistence
|
||||
}
|
||||
|
||||
@ -985,8 +964,6 @@ class PeerSelectorTest {
|
||||
bootstrapDescription
|
||||
},
|
||||
fetchRemotePeerStatuses : { PeerDescription pd ->
|
||||
// Depending on the scenario, return given peer statuses
|
||||
logger.mock("Scenario ${currentAttempt} fetchRemotePeerStatus for ${pd}")
|
||||
switch (currentAttempt) {
|
||||
case 1:
|
||||
return [bootstrapStatus, node2Status] as Set<PeerStatus>
|
||||
|
@ -22,10 +22,8 @@ import org.apache.nifi.events.EventReporter;
|
||||
import org.apache.nifi.remote.Peer;
|
||||
import org.apache.nifi.remote.Transaction;
|
||||
import org.apache.nifi.remote.TransferDirection;
|
||||
import org.apache.nifi.remote.client.KeystoreType;
|
||||
import org.apache.nifi.remote.client.SiteToSiteClient;
|
||||
import org.apache.nifi.remote.codec.StandardFlowFileCodec;
|
||||
import org.apache.nifi.remote.exception.HandshakeException;
|
||||
import org.apache.nifi.remote.io.CompressionInputStream;
|
||||
import org.apache.nifi.remote.io.CompressionOutputStream;
|
||||
import org.apache.nifi.remote.protocol.DataPacket;
|
||||
@ -34,8 +32,6 @@ import org.apache.nifi.remote.protocol.SiteToSiteTransportProtocol;
|
||||
import org.apache.nifi.remote.protocol.http.HttpHeaders;
|
||||
import org.apache.nifi.remote.protocol.http.HttpProxy;
|
||||
import org.apache.nifi.remote.util.StandardDataPacket;
|
||||
import org.apache.nifi.security.util.TemporaryKeyStoreBuilder;
|
||||
import org.apache.nifi.security.util.TlsConfiguration;
|
||||
import org.apache.nifi.stream.io.StreamUtils;
|
||||
import org.apache.nifi.web.api.dto.ControllerDTO;
|
||||
import org.apache.nifi.web.api.dto.PortDTO;
|
||||
@ -45,16 +41,11 @@ import org.apache.nifi.web.api.entity.PeersEntity;
|
||||
import org.apache.nifi.web.api.entity.TransactionResultEntity;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHandler;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@ -117,7 +108,6 @@ public class TestHttpClient {
|
||||
|
||||
private static Server server;
|
||||
private static ServerConnector httpConnector;
|
||||
private static ServerConnector sslConnector;
|
||||
private static CountDownLatch testCaseFinished;
|
||||
|
||||
private static HttpProxyServer proxyServer;
|
||||
@ -126,11 +116,8 @@ public class TestHttpClient {
|
||||
private static Set<PortDTO> inputPorts;
|
||||
private static Set<PortDTO> outputPorts;
|
||||
private static Set<PeerDTO> peers;
|
||||
private static Set<PeerDTO> peersSecure;
|
||||
private static String serverChecksum;
|
||||
|
||||
private static TlsConfiguration tlsConfiguration;
|
||||
|
||||
private static final int INITIAL_TRANSACTIONS = 0;
|
||||
|
||||
private static final AtomicInteger outputExtendTransactions = new AtomicInteger(INITIAL_TRANSACTIONS);
|
||||
@ -141,16 +128,13 @@ public class TestHttpClient {
|
||||
public static class SiteInfoServlet extends HttpServlet {
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
|
||||
final ControllerDTO controller = new ControllerDTO();
|
||||
|
||||
if (req.getLocalPort() == httpConnector.getLocalPort()) {
|
||||
controller.setRemoteSiteHttpListeningPort(httpConnector.getLocalPort());
|
||||
controller.setSiteToSiteSecure(false);
|
||||
} else {
|
||||
controller.setRemoteSiteHttpListeningPort(sslConnector.getLocalPort());
|
||||
controller.setSiteToSiteSecure(true);
|
||||
}
|
||||
|
||||
controller.setId("remote-controller-id");
|
||||
@ -175,7 +159,7 @@ public class TestHttpClient {
|
||||
public static class WrongSiteInfoServlet extends HttpServlet {
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
// This response simulates when a Site-to-Site is given a URL which has wrong path.
|
||||
respondWithText(resp, "<p class=\"message-pane-content\">You may have mistyped...</p>", 200);
|
||||
}
|
||||
@ -184,16 +168,13 @@ public class TestHttpClient {
|
||||
public static class PeersServlet extends HttpServlet {
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
|
||||
final PeersEntity peersEntity = new PeersEntity();
|
||||
|
||||
if (req.getLocalPort() == httpConnector.getLocalPort()) {
|
||||
assertNotNull(peers, "Test case should set <peers> depending on the test scenario.");
|
||||
peersEntity.setPeers(peers);
|
||||
} else {
|
||||
assertNotNull(peersSecure, "Test case should set <peersSecure> depending on the test scenario.");
|
||||
peersEntity.setPeers(peersSecure);
|
||||
}
|
||||
|
||||
respondWithJson(resp, peersEntity);
|
||||
@ -368,7 +349,7 @@ public class TestHttpClient {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
|
||||
final int reqProtocolVersion = getReqProtocolVersion(req);
|
||||
|
||||
@ -477,24 +458,10 @@ public class TestHttpClient {
|
||||
final ServletHandler wrongPathServletHandler = new ServletHandler();
|
||||
wrongPathContextHandler.insertHandler(wrongPathServletHandler);
|
||||
|
||||
final SslContextFactory sslContextFactory = new SslContextFactory.Server();
|
||||
|
||||
setTlsConfiguration();
|
||||
sslContextFactory.setKeyStorePath(tlsConfiguration.getKeystorePath());
|
||||
sslContextFactory.setKeyStorePassword(tlsConfiguration.getKeystorePassword());
|
||||
sslContextFactory.setKeyStoreType(tlsConfiguration.getKeystoreType().getType());
|
||||
sslContextFactory.setProtocol(TlsConfiguration.getHighestCurrentSupportedTlsProtocolVersion());
|
||||
|
||||
httpConnector = new ServerConnector(server);
|
||||
|
||||
final HttpConfiguration https = new HttpConfiguration();
|
||||
https.addCustomizer(new SecureRequestCustomizer());
|
||||
sslConnector = new ServerConnector(server,
|
||||
new SslConnectionFactory(sslContextFactory, "http/1.1"),
|
||||
new HttpConnectionFactory(https));
|
||||
logger.info("SSL Connector: " + sslConnector.dump());
|
||||
|
||||
server.setConnectors(new Connector[] { httpConnector, sslConnector });
|
||||
server.setConnectors(new Connector[] { httpConnector });
|
||||
|
||||
wrongPathServletHandler.addServletWithMapping(WrongSiteInfoServlet.class, "/site-to-site");
|
||||
|
||||
@ -528,8 +495,6 @@ public class TestHttpClient {
|
||||
|
||||
server.start();
|
||||
|
||||
logger.info("Starting server on port {} for HTTP, and {} for HTTPS", httpConnector.getLocalPort(), sslConnector.getLocalPort());
|
||||
|
||||
startProxyServer();
|
||||
startProxyServerWithAuth();
|
||||
}
|
||||
@ -634,15 +599,6 @@ public class TestHttpClient {
|
||||
peers = new HashSet<>();
|
||||
peers.add(peer);
|
||||
|
||||
final PeerDTO peerSecure = new PeerDTO();
|
||||
peerSecure.setHostname("localhost");
|
||||
peerSecure.setPort(sslConnector.getLocalPort());
|
||||
peerSecure.setFlowFileCount(10);
|
||||
peerSecure.setSecure(true);
|
||||
|
||||
peersSecure = new HashSet<>();
|
||||
peersSecure.add(peerSecure);
|
||||
|
||||
inputPorts = new HashSet<>();
|
||||
|
||||
final PortDTO runningInputPort = new PortDTO();
|
||||
@ -711,18 +667,6 @@ public class TestHttpClient {
|
||||
;
|
||||
}
|
||||
|
||||
private SiteToSiteClient.Builder getDefaultBuilderHTTPS() {
|
||||
return new SiteToSiteClient.Builder().transportProtocol(SiteToSiteTransportProtocol.HTTP)
|
||||
.url("https://localhost:" + sslConnector.getLocalPort() + "/nifi")
|
||||
.timeout(3, TimeUnit.MINUTES)
|
||||
.keystoreFilename(tlsConfiguration.getKeystorePath())
|
||||
.keystorePass(tlsConfiguration.getKeystorePassword())
|
||||
.keystoreType(KeystoreType.valueOf(tlsConfiguration.getKeystoreType().getType()))
|
||||
.truststoreFilename(tlsConfiguration.getTruststorePath())
|
||||
.truststorePass(tlsConfiguration.getTruststorePassword())
|
||||
.truststoreType(KeystoreType.valueOf(tlsConfiguration.getTruststoreType().getType()));
|
||||
}
|
||||
|
||||
private static void consumeDataPacket(DataPacket packet) throws IOException {
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
StreamUtils.copy(packet.getData(), bos);
|
||||
@ -893,31 +837,6 @@ public class TestHttpClient {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAccessDeniedHTTPS() throws Exception {
|
||||
|
||||
try (
|
||||
final SiteToSiteClient client = getDefaultBuilderHTTPS()
|
||||
.portName("input-access-denied")
|
||||
.build()
|
||||
) {
|
||||
assertThrows(HandshakeException.class, () -> client.createTransaction(TransferDirection.SEND));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendSuccessHTTPS() throws Exception {
|
||||
|
||||
try (
|
||||
final SiteToSiteClient client = getDefaultBuilderHTTPS()
|
||||
.portName("input-running")
|
||||
.build()
|
||||
) {
|
||||
testSend(client);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private interface SendData {
|
||||
void apply(final Transaction transaction) throws IOException;
|
||||
}
|
||||
@ -1013,47 +932,6 @@ public class TestHttpClient {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendLargeFileHTTPS() throws Exception {
|
||||
|
||||
try (
|
||||
SiteToSiteClient client = getDefaultBuilderHTTPS()
|
||||
.portName("input-running")
|
||||
.build()
|
||||
) {
|
||||
testSendLargeFile(client);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendLargeFileHTTPSWithProxy() throws Exception {
|
||||
|
||||
try (
|
||||
SiteToSiteClient client = getDefaultBuilderHTTPS()
|
||||
.portName("input-running")
|
||||
.httpProxy(new HttpProxy("localhost", proxyServer.getListenAddress().getPort(), null, null))
|
||||
.build()
|
||||
) {
|
||||
testSendLargeFile(client);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendLargeFileHTTPSWithProxyAuth() throws Exception {
|
||||
|
||||
try (
|
||||
SiteToSiteClient client = getDefaultBuilderHTTPS()
|
||||
.portName("input-running")
|
||||
.httpProxy(new HttpProxy("localhost", proxyServerWithAuth.getListenAddress().getPort(), PROXY_USER, PROXY_PASSWORD))
|
||||
.build()
|
||||
) {
|
||||
testSendLargeFile(client);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendSuccessCompressed() throws Exception {
|
||||
|
||||
@ -1264,44 +1142,6 @@ public class TestHttpClient {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveSuccessHTTPS() throws Exception {
|
||||
|
||||
try (
|
||||
SiteToSiteClient client = getDefaultBuilderHTTPS()
|
||||
.portName("output-running")
|
||||
.build()
|
||||
) {
|
||||
testReceive(client);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveSuccessHTTPSWithProxy() throws Exception {
|
||||
|
||||
try (
|
||||
SiteToSiteClient client = getDefaultBuilderHTTPS()
|
||||
.portName("output-running")
|
||||
.httpProxy(new HttpProxy("localhost", proxyServer.getListenAddress().getPort(), null, null))
|
||||
.build()
|
||||
) {
|
||||
testReceive(client);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveSuccessHTTPSWithProxyAuth() throws Exception {
|
||||
|
||||
try (
|
||||
SiteToSiteClient client = getDefaultBuilderHTTPS()
|
||||
.portName("output-running")
|
||||
.httpProxy(new HttpProxy("localhost", proxyServerWithAuth.getListenAddress().getPort(), PROXY_USER, PROXY_PASSWORD))
|
||||
.build()
|
||||
) {
|
||||
testReceive(client);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveSuccessCompressed() throws Exception {
|
||||
|
||||
@ -1375,8 +1215,4 @@ public class TestHttpClient {
|
||||
assertNotSame(INITIAL_TRANSACTIONS, outputExtendTransactions.get());
|
||||
}
|
||||
}
|
||||
|
||||
private static void setTlsConfiguration() {
|
||||
tlsConfiguration = new TemporaryKeyStoreBuilder().trustStoreType(KeystoreType.JKS.name()).build();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.remote.client.socket;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class StandardSocketPeerIdentityProviderTest {
|
||||
private static final String DISTINGUISHED_NAME = "CN=Common Name,OU=Organizational Unit,O=Organization";
|
||||
|
||||
@Mock
|
||||
SSLSocket sslSocket;
|
||||
|
||||
@Mock
|
||||
SSLSession sslSession;
|
||||
|
||||
@Mock
|
||||
X509Certificate peerCertificate;
|
||||
|
||||
StandardSocketPeerIdentityProvider provider;
|
||||
|
||||
@BeforeEach
|
||||
void setProvider() {
|
||||
provider = new StandardSocketPeerIdentityProvider();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetPeerIdentityStandardSocket() throws IOException {
|
||||
try (Socket socket = new Socket()) {
|
||||
final Optional<String> peerIdentity = provider.getPeerIdentity(socket);
|
||||
|
||||
assertFalse(peerIdentity.isPresent());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetPeerIdentitySSLSocketPeerUnverifiedException() throws SSLPeerUnverifiedException {
|
||||
when(sslSocket.getSession()).thenReturn(sslSession);
|
||||
when(sslSession.getPeerCertificates()).thenThrow(new SSLPeerUnverifiedException(SSLPeerUnverifiedException.class.getSimpleName()));
|
||||
|
||||
final Optional<String> peerIdentity = provider.getPeerIdentity(sslSocket);
|
||||
|
||||
assertFalse(peerIdentity.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetPeerIdentitySSLSocketPeerCertificatesNotFound() throws SSLPeerUnverifiedException {
|
||||
when(sslSocket.getSession()).thenReturn(sslSession);
|
||||
when(sslSession.getPeerCertificates()).thenReturn(new Certificate[]{});
|
||||
|
||||
final Optional<String> peerIdentity = provider.getPeerIdentity(sslSocket);
|
||||
|
||||
assertFalse(peerIdentity.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetPeerIdentityFound() throws SSLPeerUnverifiedException {
|
||||
when(sslSocket.getSession()).thenReturn(sslSession);
|
||||
when(sslSession.getPeerCertificates()).thenReturn(new X509Certificate[]{peerCertificate});
|
||||
|
||||
final X500Principal subjectDistinguishedName = new X500Principal(DISTINGUISHED_NAME);
|
||||
when(peerCertificate.getSubjectDN()).thenReturn(subjectDistinguishedName);
|
||||
|
||||
final Optional<String> peerIdentity = provider.getPeerIdentity(sslSocket);
|
||||
|
||||
assertTrue(peerIdentity.isPresent());
|
||||
final String identity = peerIdentity.get();
|
||||
assertEquals(DISTINGUISHED_NAME, identity);
|
||||
}
|
||||
}
|
@ -39,6 +39,12 @@
|
||||
<artifactId>nifi-api</artifactId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!-- Included for StringUtils -->
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-utils</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
|
@ -55,6 +55,7 @@
|
||||
<module>nifi-repository-encryption</module>
|
||||
<module>nifi-schema-utils</module>
|
||||
<module>nifi-security-crypto-key</module>
|
||||
<module>nifi-security-identity</module>
|
||||
<module>nifi-security-kerberos-api</module>
|
||||
<module>nifi-security-kerberos</module>
|
||||
<module>nifi-security-kms</module>
|
||||
|
@ -136,6 +136,12 @@
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-utils</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.icegreen</groupId>
|
||||
<artifactId>greenmail</artifactId>
|
||||
|
@ -32,6 +32,16 @@
|
||||
<artifactId>nifi-api</artifactId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-utils-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-utils</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-mock</artifactId>
|
||||
|
@ -38,6 +38,10 @@
|
||||
<artifactId>nifi-security-socket-ssl</artifactId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-utils-api</artifactId>
|
||||
</dependency>
|
||||
<!-- Other modules using nifi-standard-record-utils are expected to have these APIs available, typically through a NAR dependency -->
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
|
@ -34,6 +34,10 @@
|
||||
<artifactId>nifi-utils</artifactId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-utils-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>javax.json</artifactId>
|
||||
|
@ -45,7 +45,6 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-utils</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
|
@ -202,7 +202,8 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-utils</artifactId>
|
||||
<artifactId>nifi-security-identity</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
|
@ -77,6 +77,11 @@
|
||||
<artifactId>nifi-security-kms</artifactId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-identity</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-repository-encryption</artifactId>
|
||||
|
@ -38,6 +38,15 @@
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-framework-core-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-utils-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-identity</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-mock</artifactId>
|
||||
|
@ -26,6 +26,10 @@ import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Principal;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@ -33,7 +37,12 @@ import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLServerSocket;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
import org.apache.nifi.groups.ProcessGroup;
|
||||
import org.apache.nifi.remote.cluster.ClusterNodeInformation;
|
||||
import org.apache.nifi.remote.cluster.NodeInformant;
|
||||
@ -46,7 +55,6 @@ import org.apache.nifi.remote.io.socket.SocketCommunicationsSession;
|
||||
import org.apache.nifi.remote.protocol.CommunicationsSession;
|
||||
import org.apache.nifi.remote.protocol.RequestType;
|
||||
import org.apache.nifi.remote.protocol.ServerProtocol;
|
||||
import org.apache.nifi.security.util.CertificateUtils;
|
||||
import org.apache.nifi.security.util.TlsConfiguration;
|
||||
import org.apache.nifi.util.NiFiProperties;
|
||||
import org.slf4j.Logger;
|
||||
@ -160,7 +168,8 @@ public class SocketRemoteSiteListener implements RemoteSiteListener {
|
||||
try {
|
||||
if (secure) {
|
||||
LOG.trace("{} Connection is secure", this);
|
||||
dn = CertificateUtils.extractPeerDNFromSSLSocket(socket);
|
||||
final SSLSocket sslSocket = (SSLSocket) socket;
|
||||
dn = getPeerIdentity(sslSocket);
|
||||
|
||||
commsSession = new SocketCommunicationsSession(socket);
|
||||
commsSession.setUserDn(dn);
|
||||
@ -174,7 +183,7 @@ public class SocketRemoteSiteListener implements RemoteSiteListener {
|
||||
// TODO: Add SocketProtocolListener#handleTlsError logic here
|
||||
String msg = String.format("RemoteSiteListener Unable to accept connection from %s due to %s", socket, e.getLocalizedMessage());
|
||||
// Suppress repeated TLS errors
|
||||
if (CertificateUtils.isTlsError(e)) {
|
||||
if (isTlsError(e)) {
|
||||
boolean printedAsWarning = handleTlsError(msg);
|
||||
|
||||
// TODO: Move into handleTlsError and refactor shared behavior
|
||||
@ -320,6 +329,32 @@ public class SocketRemoteSiteListener implements RemoteSiteListener {
|
||||
listenerThread.start();
|
||||
}
|
||||
|
||||
private boolean isTlsError(final Throwable e) {
|
||||
final boolean tlsError;
|
||||
|
||||
if (e instanceof SSLException || e instanceof GeneralSecurityException) {
|
||||
tlsError = true;
|
||||
} else if (e.getCause() == null) {
|
||||
tlsError = false;
|
||||
} else {
|
||||
tlsError = isTlsError(e.getCause());
|
||||
}
|
||||
|
||||
return tlsError;
|
||||
}
|
||||
|
||||
private String getPeerIdentity(final SSLSocket sslSocket) throws SSLPeerUnverifiedException {
|
||||
final SSLSession sslSession = sslSocket.getSession();
|
||||
final Certificate[] peerCertificates = sslSession.getPeerCertificates();
|
||||
if (peerCertificates == null || peerCertificates.length == 0) {
|
||||
throw new SSLPeerUnverifiedException(String.format("Peer [%s] certificates not found", sslSocket.getRemoteSocketAddress()));
|
||||
}
|
||||
|
||||
final X509Certificate peerCertificate = (X509Certificate) peerCertificates[0];
|
||||
final Principal subjectDistinguishedName = peerCertificate.getSubjectDN();
|
||||
return subjectDistinguishedName.getName();
|
||||
}
|
||||
|
||||
private boolean handleTlsError(String msg) {
|
||||
if (tlsErrorRecentlySeen()) {
|
||||
LOG.debug(msg);
|
||||
@ -331,7 +366,7 @@ public class SocketRemoteSiteListener implements RemoteSiteListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if any related exception (determined by {@link CertificateUtils#isTlsError(Throwable)}) has occurred within the last
|
||||
* Returns {@code true} if any related exception has occurred within the last
|
||||
* {@link #EXCEPTION_THRESHOLD_MILLIS} milliseconds. Does not evaluate the error locally,
|
||||
* simply checks the last time the timestamp was updated.
|
||||
*
|
||||
|
Binary file not shown.
Binary file not shown.
@ -198,6 +198,12 @@
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-xml-processing</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-identity</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-security</artifactId>
|
||||
|
@ -152,6 +152,11 @@
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-utils</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-identity</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-framework-core</artifactId>
|
||||
|
@ -32,7 +32,6 @@
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-utils</artifactId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
|
@ -41,6 +41,11 @@
|
||||
<artifactId>nifi-security-utils</artifactId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-identity</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-properties</artifactId>
|
||||
|
@ -59,6 +59,11 @@
|
||||
<artifactId>nifi-event-transport</artifactId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-utils-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-mock</artifactId>
|
||||
|
@ -57,6 +57,12 @@
|
||||
<artifactId>nifi-utils</artifactId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-utils</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-mock</artifactId>
|
||||
|
Loading…
x
Reference in New Issue
Block a user