Skip to content

Commit

Permalink
NIFI-8121 Updated ListenHTTP with inferred Client Authentication Policy
Browse files Browse the repository at this point in the history
- Added default property value for automatic determination of Client Authentication Policy based on SSLContextService Trust Store properties
- Added new ClientAuthentication enum with values specific to ListenHTTP
  • Loading branch information
exceptionfactory committed Jan 7, 2021
1 parent 2e33e6e commit b01291a
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 54 deletions.
Expand Up @@ -24,6 +24,7 @@
import org.apache.nifi.annotation.lifecycle.OnStopped;
import org.apache.nifi.annotation.notification.OnPrimaryNodeStateChange;
import org.apache.nifi.annotation.notification.PrimaryNodeState;
import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.components.ValidationContext;
Expand Down Expand Up @@ -65,6 +66,7 @@
import javax.ws.rs.Path;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
Expand All @@ -77,6 +79,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@InputRequirement(Requirement.INPUT_FORBIDDEN)
@Tags({"ingest", "http", "https", "rest", "listen"})
Expand All @@ -91,8 +94,32 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
private Set<Relationship> relationships;
private List<PropertyDescriptor> properties;

private AtomicBoolean initialized = new AtomicBoolean(false);
private AtomicBoolean runOnPrimary = new AtomicBoolean(false);
private final AtomicBoolean initialized = new AtomicBoolean(false);
private final AtomicBoolean runOnPrimary = new AtomicBoolean(false);

public enum ClientAuthentication {
AUTO("Inferred based on SSL Context Service properties. The presence of Trust Store properties implies REQUIRED, otherwise NONE is configured."),

WANT(ClientAuth.WANT.getDescription()),

REQUIRED(ClientAuth.REQUIRED.getDescription()),

NONE(ClientAuth.NONE.getDescription());

private final String description;

ClientAuthentication(final String description) {
this.description = description;
}

public String getDescription() {
return description;
}

public AllowableValue getAllowableValue() {
return new AllowableValue(name(), name(), description);
}
}

public static final Relationship RELATIONSHIP_SUCCESS = new Relationship.Builder()
.name("success")
Expand Down Expand Up @@ -187,13 +214,16 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
.addValidator(StandardValidators.DATA_SIZE_VALIDATOR)
.defaultValue("512 KB")
.build();
public static final PropertyDescriptor CLIENT_AUTH = new PropertyDescriptor.Builder()
.name("client-auth")
public static final PropertyDescriptor CLIENT_AUTHENTICATION = new PropertyDescriptor.Builder()
.name("client-authentication")
.displayName("Client Authentication")
.description("Client Authentication policy for TLS connections. Required when SSL Context Service configured.")
.required(false)
.allowableValues(ClientAuth.values())
.defaultValue(ClientAuth.REQUIRED.name())
.allowableValues(Arrays.stream(ClientAuthentication.values())
.map(ClientAuthentication::getAllowableValue)
.collect(Collectors.toList())
.toArray(new AllowableValue[]{}))
.defaultValue(ClientAuthentication.AUTO.name())
.dependsOn(SSL_CONTEXT_SERVICE)
.build();

Expand Down Expand Up @@ -252,7 +282,7 @@ protected void init(final ProcessorInitializationContext context) {
descriptors.add(HEALTH_CHECK_PORT);
descriptors.add(MAX_DATA_RATE);
descriptors.add(SSL_CONTEXT_SERVICE);
descriptors.add(CLIENT_AUTH);
descriptors.add(CLIENT_AUTHENTICATION);
descriptors.add(AUTHORIZED_DN_PATTERN);
descriptors.add(MAX_UNCONFIRMED_TIME);
descriptors.add(HEADERS_AS_ATTRIBUTES_REGEX);
Expand Down Expand Up @@ -317,11 +347,8 @@ synchronized private void createHttpServerFromService(final ProcessContext conte
throttlerRef.set(streamThrottler);

final boolean sslRequired = sslContextService != null;
ClientAuth clientAuth = ClientAuth.NONE;
final PropertyValue clientAuthProperty = context.getProperty(CLIENT_AUTH);
if (clientAuthProperty.isSet()) {
clientAuth = ClientAuth.valueOf(clientAuthProperty.getValue());
}
final PropertyValue clientAuthenticationProperty = context.getProperty(CLIENT_AUTHENTICATION);
final ClientAuthentication clientAuthentication = getClientAuthentication(sslContextService, clientAuthenticationProperty);

// thread pool for the jetty instance
final QueuedThreadPool threadPool = new QueuedThreadPool();
Expand All @@ -333,13 +360,21 @@ synchronized private void createHttpServerFromService(final ProcessContext conte
// get the configured port
final int port = context.getProperty(PORT).evaluateAttributeExpressions().asInteger();

final ServerConnector connector = createServerConnector(server, port, sslContextService, sslRequired, clientAuth);
final ServerConnector connector = createServerConnector(server,
port,
sslContextService,
sslRequired,
clientAuthentication);
server.addConnector(connector);

// Add a separate connector for the health check port (if specified)
final Integer healthCheckPort = context.getProperty(HEALTH_CHECK_PORT).evaluateAttributeExpressions().asInteger();
if (healthCheckPort != null) {
final ServerConnector healthCheckConnector = createServerConnector(server, healthCheckPort, sslContextService, sslRequired, ClientAuth.NONE);
final ServerConnector healthCheckConnector = createServerConnector(server,
healthCheckPort,
sslContextService,
sslRequired,
ClientAuthentication.NONE);
server.addConnector(healthCheckConnector);
}

Expand Down Expand Up @@ -383,15 +418,34 @@ synchronized private void createHttpServerFromService(final ProcessContext conte
initialized.set(true);
}

private ServerConnector createServerConnector(Server server, int port, SSLContextService sslContextService, boolean sslRequired, final ClientAuth clientAuth) {
private ClientAuthentication getClientAuthentication(final SSLContextService sslContextService,
final PropertyValue clientAuthenticationProperty) {
ClientAuthentication clientAuthentication = ClientAuthentication.NONE;
if (clientAuthenticationProperty.isSet()) {
clientAuthentication = ClientAuthentication.valueOf(clientAuthenticationProperty.getValue());
final boolean trustStoreConfigured = sslContextService != null && sslContextService.isTrustStoreConfigured();

if (ClientAuthentication.AUTO.equals(clientAuthentication) && trustStoreConfigured) {
clientAuthentication = ClientAuthentication.REQUIRED;
getLogger().debug("Client Authentication REQUIRED from SSLContextService Trust Store configuration");
}
}
return clientAuthentication;
}

private ServerConnector createServerConnector(final Server server,
final int port,
final SSLContextService sslContextService,
final boolean sslRequired,
final ClientAuthentication clientAuthentication) {
final ServerConnector connector;
final HttpConfiguration httpConfiguration = new HttpConfiguration();
if (sslRequired) {
httpConfiguration.setSecureScheme("https");
httpConfiguration.setSecurePort(port);
httpConfiguration.addCustomizer(new SecureRequestCustomizer());

final SslContextFactory contextFactory = createSslContextFactory(sslContextService, clientAuth);
final SslContextFactory contextFactory = createSslContextFactory(sslContextService, clientAuthentication);

connector = new ServerConnector(server, new SslConnectionFactory(contextFactory, "http/1.1"), new HttpConnectionFactory(httpConfiguration));
} else {
Expand All @@ -402,17 +456,17 @@ private ServerConnector createServerConnector(Server server, int port, SSLContex
return connector;
}

private SslContextFactory createSslContextFactory(SSLContextService sslContextService, final ClientAuth clientAuth) {
private SslContextFactory createSslContextFactory(final SSLContextService sslContextService, final ClientAuthentication clientAuthentication) {
final SslContextFactory.Server contextFactory = new SslContextFactory.Server();
final SSLContext sslContext = sslContextService.createContext();
contextFactory.setSslContext(sslContext);

final TlsConfiguration tlsConfiguration = sslContextService.createTlsConfiguration();
contextFactory.setIncludeProtocols(tlsConfiguration.getEnabledProtocols());

if (ClientAuth.REQUIRED.equals(clientAuth)) {
if (ClientAuthentication.REQUIRED.equals(clientAuthentication)) {
contextFactory.setNeedClientAuth(true);
} else if (ClientAuth.WANT.equals(clientAuth)) {
} else if (ClientAuthentication.WANT.equals(clientAuthentication)) {
contextFactory.setWantClientAuth(true);
}

Expand Down

0 comments on commit b01291a

Please sign in to comment.