mirror of https://github.com/apache/nifi.git
NIFI-10625 Added support for HTTP/2 in Registry
- Added nifi-security-ssl for generalized SSLContext creation - Removed static keystore and truststore test files from nifi-registry-jetty Signed-off-by: Nathan Gough <thenatog@gmail.com> This closes #6514.
This commit is contained in:
parent
dfbc7dfc21
commit
b753c1c72d
|
@ -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>1.19.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>nifi-security-ssl</artifactId>
|
||||||
|
<description>Shared TLS security components without additional dependencies</description>
|
||||||
|
</project>
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.nifi.security.ssl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception indicating runtime failure to create configured objects
|
||||||
|
*/
|
||||||
|
public class BuilderConfigurationException extends RuntimeException {
|
||||||
|
/**
|
||||||
|
* Builder Configuration Exception Constructor with standard properties
|
||||||
|
*
|
||||||
|
* @param message Exception Message
|
||||||
|
* @param cause Exception Cause
|
||||||
|
*/
|
||||||
|
public BuilderConfigurationException(final String message, final Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder Configuration Exception Constructor without Throwable cause
|
||||||
|
*
|
||||||
|
* @param message Exception Message
|
||||||
|
*/
|
||||||
|
public BuilderConfigurationException(final String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.ssl;
|
||||||
|
|
||||||
|
import java.security.KeyStore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder interface for instances of java.security.KeyStore
|
||||||
|
*/
|
||||||
|
public interface KeyStoreBuilder {
|
||||||
|
/**
|
||||||
|
* Build Key Store based on configured properties
|
||||||
|
*
|
||||||
|
* @return Key Store
|
||||||
|
*/
|
||||||
|
KeyStore build();
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.ssl;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder interface for instances of javax.net.ssl.SSLContext
|
||||||
|
*/
|
||||||
|
public interface SslContextBuilder {
|
||||||
|
/**
|
||||||
|
* Build SSLContext using configured properties
|
||||||
|
*
|
||||||
|
* @return SSLContext
|
||||||
|
*/
|
||||||
|
SSLContext build();
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* 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.ssl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard implementation of Key Store Builder
|
||||||
|
*/
|
||||||
|
public class StandardKeyStoreBuilder implements KeyStoreBuilder {
|
||||||
|
private String provider;
|
||||||
|
|
||||||
|
private String type = KeyStore.getDefaultType();
|
||||||
|
|
||||||
|
private InputStream inputStream;
|
||||||
|
|
||||||
|
private char[] password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build Key Store using configured properties
|
||||||
|
*
|
||||||
|
* @return Key Store
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public KeyStore build() {
|
||||||
|
final KeyStore keyStore = getKeyStore();
|
||||||
|
|
||||||
|
if (inputStream == null) {
|
||||||
|
throw new BuilderConfigurationException("Key Store InputStream not configured");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
keyStore.load(inputStream, password);
|
||||||
|
} catch (final IOException|NoSuchAlgorithmException|CertificateException e) {
|
||||||
|
throw new BuilderConfigurationException("Key Store loading failed", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Key Store Provider for Key Store implementation
|
||||||
|
*
|
||||||
|
* @param provider Key Store Provider
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public StandardKeyStoreBuilder provider(final String provider) {
|
||||||
|
this.provider = Objects.requireNonNull(provider, "Key Store Provider required");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Key Store Type defaults to platform configuration derived from KeyStore.getDefaultType()
|
||||||
|
*
|
||||||
|
* @param type Key Store Type
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public StandardKeyStoreBuilder type(final String type) {
|
||||||
|
this.type = Objects.requireNonNull(type, "Key Store Type required");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Key Store Password
|
||||||
|
*
|
||||||
|
* @param password Key Store Password
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public StandardKeyStoreBuilder password(final char[] password) {
|
||||||
|
this.password = Objects.requireNonNull(password, "Key Store Password required");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Key Store InputStream to be loaded
|
||||||
|
*
|
||||||
|
* @param inputStream Key Store InputStream
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public StandardKeyStoreBuilder inputStream(final InputStream inputStream) {
|
||||||
|
this.inputStream = Objects.requireNonNull(inputStream, "Key Store InputStream required");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyStore getKeyStore() {
|
||||||
|
try {
|
||||||
|
return provider == null ? KeyStore.getInstance(type) : KeyStore.getInstance(type, provider);
|
||||||
|
} catch (final KeyStoreException e) {
|
||||||
|
final String message = String.format("Key Store Type [%s] creation failed", type);
|
||||||
|
throw new BuilderConfigurationException(message, e);
|
||||||
|
} catch (final NoSuchProviderException e) {
|
||||||
|
final String message = String.format("Key Store Type [%s] Provider [%s] creation failed", type, provider);
|
||||||
|
throw new BuilderConfigurationException(message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
/*
|
||||||
|
* 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.ssl;
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard implementation of SSL Context Builder
|
||||||
|
*/
|
||||||
|
public class StandardSslContextBuilder implements SslContextBuilder {
|
||||||
|
private static final String DEFAULT_PROTOCOL = "TLS";
|
||||||
|
|
||||||
|
private String protocol = DEFAULT_PROTOCOL;
|
||||||
|
|
||||||
|
private KeyStore keyStore;
|
||||||
|
|
||||||
|
private char[] keyPassword;
|
||||||
|
|
||||||
|
private KeyStore trustStore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build and initialize an SSL Context using configured Key Manager and Trust Manager sources
|
||||||
|
*
|
||||||
|
* @return SSL Context
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SSLContext build() {
|
||||||
|
final SSLContext sslContext = getSslContext();
|
||||||
|
final SecureRandom secureRandom = new SecureRandom();
|
||||||
|
final KeyManager[] keyManagers = getKeyManagers();
|
||||||
|
final TrustManager[] trustManagers = getTrustManagers();
|
||||||
|
|
||||||
|
try {
|
||||||
|
sslContext.init(keyManagers, trustManagers, secureRandom);
|
||||||
|
} catch (final KeyManagementException e) {
|
||||||
|
throw new BuilderConfigurationException("SSLContext initialization failed", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sslContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set TLS Protocol defaults to TLS without a specific version number
|
||||||
|
*
|
||||||
|
* @param protocol TLS Protocol
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public StandardSslContextBuilder protocol(final String protocol) {
|
||||||
|
this.protocol = Objects.requireNonNull(protocol, "Protocol required");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Key Store with Private Key and Certificate Entry
|
||||||
|
*
|
||||||
|
* @param keyStore Key Store
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public StandardSslContextBuilder keyStore(final KeyStore keyStore) {
|
||||||
|
this.keyStore = Objects.requireNonNull(keyStore, "Key Store required");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Key Password for reading Private Key entries from Key Store
|
||||||
|
*
|
||||||
|
* @param keyPassword Key Password
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public StandardSslContextBuilder keyPassword(final char[] keyPassword) {
|
||||||
|
this.keyPassword = Objects.requireNonNull(keyPassword, "Key Password required");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Trust Store with Certificate Entries
|
||||||
|
*
|
||||||
|
* @param trustStore Trust Store
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public StandardSslContextBuilder trustStore(final KeyStore trustStore) {
|
||||||
|
this.trustStore = Objects.requireNonNull(trustStore, "Trust Store required");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyManager[] getKeyManagers() {
|
||||||
|
final KeyManager[] keyManagers;
|
||||||
|
if (keyStore == null) {
|
||||||
|
keyManagers = null;
|
||||||
|
} else {
|
||||||
|
final KeyManagerFactory keyManagerFactory = getKeyManagerFactory();
|
||||||
|
try {
|
||||||
|
keyManagerFactory.init(keyStore, keyPassword);
|
||||||
|
} catch (final KeyStoreException|NoSuchAlgorithmException|UnrecoverableKeyException e) {
|
||||||
|
throw new BuilderConfigurationException("Key Manager initialization failed", e);
|
||||||
|
}
|
||||||
|
keyManagers = keyManagerFactory.getKeyManagers();
|
||||||
|
}
|
||||||
|
return keyManagers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TrustManager[] getTrustManagers() {
|
||||||
|
final TrustManager[] trustManagers;
|
||||||
|
if (trustStore == null) {
|
||||||
|
trustManagers = null;
|
||||||
|
} else {
|
||||||
|
final TrustManagerFactory trustManagerFactory = getTrustManagerFactory();
|
||||||
|
try {
|
||||||
|
trustManagerFactory.init(trustStore);
|
||||||
|
} catch (final KeyStoreException e) {
|
||||||
|
throw new BuilderConfigurationException("Trust Manager initialization failed", e);
|
||||||
|
}
|
||||||
|
trustManagers = trustManagerFactory.getTrustManagers();
|
||||||
|
}
|
||||||
|
return trustManagers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyManagerFactory getKeyManagerFactory() {
|
||||||
|
final String algorithm = KeyManagerFactory.getDefaultAlgorithm();
|
||||||
|
try {
|
||||||
|
return KeyManagerFactory.getInstance(algorithm);
|
||||||
|
} catch (final NoSuchAlgorithmException e) {
|
||||||
|
final String message = String.format("KeyManagerFactory creation failed with algorithm [%s]", algorithm);
|
||||||
|
throw new BuilderConfigurationException(message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TrustManagerFactory getTrustManagerFactory() {
|
||||||
|
final String algorithm = TrustManagerFactory.getDefaultAlgorithm();
|
||||||
|
try {
|
||||||
|
return TrustManagerFactory.getInstance(algorithm);
|
||||||
|
} catch (final NoSuchAlgorithmException e) {
|
||||||
|
final String message = String.format("TrustManagerFactory creation failed with algorithm [%s]", algorithm);
|
||||||
|
throw new BuilderConfigurationException(message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SSLContext getSslContext() {
|
||||||
|
try {
|
||||||
|
return SSLContext.getInstance(protocol);
|
||||||
|
} catch (final NoSuchAlgorithmException e) {
|
||||||
|
final String message = String.format("SSLContext creation failed with protocol [%s]", protocol);
|
||||||
|
throw new BuilderConfigurationException(message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* 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.ssl;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static java.nio.file.Files.createTempFile;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
class StandardKeyStoreBuilderTest {
|
||||||
|
|
||||||
|
private static final String TYPE = KeyStore.getDefaultType();
|
||||||
|
|
||||||
|
private static final char[] PASSWORD = UUID.randomUUID().toString().toCharArray();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBuild() throws Exception {
|
||||||
|
final Path path = createTempFile(StandardKeyStoreBuilderTest.class.getSimpleName(), TYPE);
|
||||||
|
path.toFile().deleteOnExit();
|
||||||
|
|
||||||
|
final KeyStore sourceKeyStore = KeyStore.getInstance(TYPE);
|
||||||
|
sourceKeyStore.load(null);
|
||||||
|
try (final OutputStream outputStream = Files.newOutputStream(path)) {
|
||||||
|
sourceKeyStore.store(outputStream, PASSWORD);
|
||||||
|
}
|
||||||
|
|
||||||
|
final StandardKeyStoreBuilder builder = new StandardKeyStoreBuilder();
|
||||||
|
builder.type(TYPE);
|
||||||
|
builder.password(PASSWORD);
|
||||||
|
try (final InputStream inputStream = Files.newInputStream(path)) {
|
||||||
|
builder.inputStream(inputStream);
|
||||||
|
final KeyStore keyStore = builder.build();
|
||||||
|
assertNotNull(keyStore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* 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.ssl;
|
||||||
|
|
||||||
|
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.SSLContext;
|
||||||
|
|
||||||
|
import java.security.KeyStore;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class StandardSslContextBuilderTest {
|
||||||
|
private static final String TLS_PROTOCOL = "TLS";
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
KeyStore trustStore;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBuild() {
|
||||||
|
final StandardSslContextBuilder builder = new StandardSslContextBuilder();
|
||||||
|
|
||||||
|
final SSLContext sslContext = builder.build();
|
||||||
|
|
||||||
|
assertNotNull(sslContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBuildProtocol() {
|
||||||
|
final StandardSslContextBuilder builder = new StandardSslContextBuilder();
|
||||||
|
builder.protocol(TLS_PROTOCOL);
|
||||||
|
|
||||||
|
final SSLContext sslContext = builder.build();
|
||||||
|
|
||||||
|
assertNotNull(sslContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBuildKeyStore() throws Exception {
|
||||||
|
final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
keyStore.load(null);
|
||||||
|
|
||||||
|
final StandardSslContextBuilder builder = new StandardSslContextBuilder();
|
||||||
|
builder.keyStore(keyStore);
|
||||||
|
|
||||||
|
final SSLContext sslContext = builder.build();
|
||||||
|
|
||||||
|
assertNotNull(sslContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBuildTrustStore() {
|
||||||
|
final StandardSslContextBuilder builder = new StandardSslContextBuilder();
|
||||||
|
builder.trustStore(trustStore);
|
||||||
|
|
||||||
|
final SSLContext sslContext = builder.build();
|
||||||
|
|
||||||
|
assertNotNull(sslContext);
|
||||||
|
}
|
||||||
|
}
|
|
@ -57,6 +57,7 @@
|
||||||
<module>nifi-security-kerberos</module>
|
<module>nifi-security-kerberos</module>
|
||||||
<module>nifi-security-kms</module>
|
<module>nifi-security-kms</module>
|
||||||
<module>nifi-security-socket-ssl</module>
|
<module>nifi-security-socket-ssl</module>
|
||||||
|
<module>nifi-security-ssl</module>
|
||||||
<module>nifi-security-utils-api</module>
|
<module>nifi-security-utils-api</module>
|
||||||
<module>nifi-security-utils</module>
|
<module>nifi-security-utils</module>
|
||||||
<module>nifi-single-user-utils</module>
|
<module>nifi-single-user-utils</module>
|
||||||
|
|
|
@ -165,6 +165,7 @@
|
||||||
<nifi.registry.web.http.port>18080</nifi.registry.web.http.port>
|
<nifi.registry.web.http.port>18080</nifi.registry.web.http.port>
|
||||||
<nifi.registry.web.https.host />
|
<nifi.registry.web.https.host />
|
||||||
<nifi.registry.web.https.port />
|
<nifi.registry.web.https.port />
|
||||||
|
<nifi.registry.web.https.application.protocols>http/1.1</nifi.registry.web.https.application.protocols>
|
||||||
<nifi.registry.jetty.work.dir>./work/jetty</nifi.registry.jetty.work.dir>
|
<nifi.registry.jetty.work.dir>./work/jetty</nifi.registry.jetty.work.dir>
|
||||||
<nifi.registry.web.jetty.threads>200</nifi.registry.web.jetty.threads>
|
<nifi.registry.web.jetty.threads>200</nifi.registry.web.jetty.threads>
|
||||||
<nifi.registry.web.should.send.server.version>true</nifi.registry.web.should.send.server.version>
|
<nifi.registry.web.should.send.server.version>true</nifi.registry.web.should.send.server.version>
|
||||||
|
|
|
@ -1013,6 +1013,13 @@ These properties pertain to the web-based User Interface.
|
||||||
|`nifi.registry.web.http.port`|The HTTP port. The default value is `18080`.
|
|`nifi.registry.web.http.port`|The HTTP port. The default value is `18080`.
|
||||||
|`nifi.registry.web.https.host`|The HTTPS host. It is blank by default.
|
|`nifi.registry.web.https.host`|The HTTPS host. It is blank by default.
|
||||||
|`nifi.registry.web.https.port`|The HTTPS port. It is blank by default. When configuring NiFi Registry to run securely, this port should be configured.
|
|`nifi.registry.web.https.port`|The HTTPS port. It is blank by default. When configuring NiFi Registry to run securely, this port should be configured.
|
||||||
|
|`nifi.registry.web.https.application.protocols`|The space-separated list of application protocols supported when running with HTTPS enabled.
|
||||||
|
|
||||||
|
The default value is `http/1.1`.
|
||||||
|
|
||||||
|
The value can be set to `h2 http/1.1` to support Application Layer Protocol Negotiation (ALPN) for HTTP/2 or HTTP/1.1 based on client capabilities.
|
||||||
|
|
||||||
|
The value can be set to `h2` to require HTTP/2 and disable HTTP/1.1.
|
||||||
|`nifi.registry.web.jetty.working.directory`|The location of the Jetty working directory. The default value is `./work/jetty`.
|
|`nifi.registry.web.jetty.working.directory`|The location of the Jetty working directory. The default value is `./work/jetty`.
|
||||||
|`nifi.registry.web.jetty.threads`|The number of Jetty threads. The default value is `200`.
|
|`nifi.registry.web.jetty.threads`|The number of Jetty threads. The default value is `200`.
|
||||||
|====
|
|====
|
||||||
|
|
|
@ -28,6 +28,29 @@
|
||||||
<artifactId>nifi-registry-properties</artifactId>
|
<artifactId>nifi-registry-properties</artifactId>
|
||||||
<version>1.19.0-SNAPSHOT</version>
|
<version>1.19.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.nifi</groupId>
|
||||||
|
<artifactId>nifi-jetty-configuration</artifactId>
|
||||||
|
<version>1.19.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.nifi</groupId>
|
||||||
|
<artifactId>nifi-security-ssl</artifactId>
|
||||||
|
<version>1.19.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.nifi</groupId>
|
||||||
|
<artifactId>nifi-security-utils-api</artifactId>
|
||||||
|
<version>1.19.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.http2</groupId>
|
||||||
|
<artifactId>http2-server</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-alpn-server</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-server</artifactId>
|
<artifactId>jetty-server</artifactId>
|
||||||
|
@ -62,5 +85,11 @@
|
||||||
<artifactId>apache-jstl</artifactId>
|
<artifactId>apache-jstl</artifactId>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.nifi</groupId>
|
||||||
|
<artifactId>nifi-security-utils</artifactId>
|
||||||
|
<version>1.19.0-SNAPSHOT</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
package org.apache.nifi.registry.jetty;
|
package org.apache.nifi.registry.jetty;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.nifi.jetty.configuration.connector.ServerConnectorFactory;
|
||||||
|
import org.apache.nifi.registry.jetty.connector.ApplicationServerConnectorFactory;
|
||||||
import org.apache.nifi.registry.jetty.headers.ContentSecurityPolicyFilter;
|
import org.apache.nifi.registry.jetty.headers.ContentSecurityPolicyFilter;
|
||||||
import org.apache.nifi.registry.jetty.headers.StrictTransportSecurityFilter;
|
import org.apache.nifi.registry.jetty.headers.StrictTransportSecurityFilter;
|
||||||
import org.apache.nifi.registry.jetty.headers.XFrameOptionsFilter;
|
import org.apache.nifi.registry.jetty.headers.XFrameOptionsFilter;
|
||||||
|
@ -26,17 +28,12 @@ import org.apache.nifi.registry.security.crypto.CryptoKeyProvider;
|
||||||
import org.eclipse.jetty.annotations.AnnotationConfiguration;
|
import org.eclipse.jetty.annotations.AnnotationConfiguration;
|
||||||
import org.eclipse.jetty.server.Connector;
|
import org.eclipse.jetty.server.Connector;
|
||||||
import org.eclipse.jetty.server.Handler;
|
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.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
import org.eclipse.jetty.server.SslConnectionFactory;
|
|
||||||
import org.eclipse.jetty.server.handler.HandlerCollection;
|
import org.eclipse.jetty.server.handler.HandlerCollection;
|
||||||
import org.eclipse.jetty.servlet.DefaultServlet;
|
import org.eclipse.jetty.servlet.DefaultServlet;
|
||||||
import org.eclipse.jetty.servlet.FilterHolder;
|
import org.eclipse.jetty.servlet.FilterHolder;
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
import org.eclipse.jetty.webapp.Configuration;
|
import org.eclipse.jetty.webapp.Configuration;
|
||||||
import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
|
import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
|
||||||
|
@ -72,16 +69,12 @@ public class JettyServer {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(JettyServer.class);
|
private static final Logger logger = LoggerFactory.getLogger(JettyServer.class);
|
||||||
private static final String WEB_DEFAULTS_XML = "org/apache/nifi-registry/web/webdefault.xml";
|
private static final String WEB_DEFAULTS_XML = "org/apache/nifi-registry/web/webdefault.xml";
|
||||||
private static final int HEADER_BUFFER_SIZE = 16 * 1024; // 16kb
|
|
||||||
private static final String CIPHER_SUITE_SEPARATOR_PATTERN = ",\\s*";
|
|
||||||
|
|
||||||
|
private static final String ALL_PATHS = "/*";
|
||||||
|
|
||||||
private static final FileFilter WAR_FILTER = new FileFilter() {
|
private static final FileFilter WAR_FILTER = pathname -> {
|
||||||
@Override
|
final String nameToTest = pathname.getName().toLowerCase();
|
||||||
public boolean accept(File pathname) {
|
return nameToTest.endsWith(".war") && pathname.isFile();
|
||||||
final String nameToTest = pathname.getName().toLowerCase();
|
|
||||||
return nameToTest.endsWith(".war") && pathname.isFile();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private final NiFiRegistryProperties properties;
|
private final NiFiRegistryProperties properties;
|
||||||
|
@ -89,9 +82,7 @@ public class JettyServer {
|
||||||
private final String docsLocation;
|
private final String docsLocation;
|
||||||
private final Server server;
|
private final Server server;
|
||||||
|
|
||||||
private WebAppContext webUiContext;
|
|
||||||
private WebAppContext webApiContext;
|
private WebAppContext webApiContext;
|
||||||
private WebAppContext webDocsContext;
|
|
||||||
|
|
||||||
public JettyServer(final NiFiRegistryProperties properties, final CryptoKeyProvider cryptoKeyProvider, final String docsLocation) {
|
public JettyServer(final NiFiRegistryProperties properties, final CryptoKeyProvider cryptoKeyProvider, final String docsLocation) {
|
||||||
final QueuedThreadPool threadPool = new QueuedThreadPool(properties.getWebThreads());
|
final QueuedThreadPool threadPool = new QueuedThreadPool(properties.getWebThreads());
|
||||||
|
@ -156,128 +147,9 @@ public class JettyServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureConnectors() {
|
private void configureConnectors() {
|
||||||
// create the http configuration
|
final ServerConnectorFactory serverConnectorFactory = new ApplicationServerConnectorFactory(server, properties);
|
||||||
final HttpConfiguration httpConfiguration = new HttpConfiguration();
|
final ServerConnector serverConnector = serverConnectorFactory.getServerConnector();
|
||||||
httpConfiguration.setRequestHeaderSize(HEADER_BUFFER_SIZE);
|
server.addConnector(serverConnector);
|
||||||
httpConfiguration.setResponseHeaderSize(HEADER_BUFFER_SIZE);
|
|
||||||
httpConfiguration.setSendServerVersion(properties.shouldSendServerVersion());
|
|
||||||
|
|
||||||
if (properties.getPort() != null) {
|
|
||||||
final Integer port = properties.getPort();
|
|
||||||
if (port < 0 || (int) Math.pow(2, 16) <= port) {
|
|
||||||
throw new IllegalStateException("Invalid HTTP port: " + port);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Configuring Jetty for HTTP on port: " + port);
|
|
||||||
|
|
||||||
// create the connector
|
|
||||||
final ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
|
|
||||||
|
|
||||||
// set host and port
|
|
||||||
if (StringUtils.isNotBlank(properties.getHttpHost())) {
|
|
||||||
http.setHost(properties.getHttpHost());
|
|
||||||
}
|
|
||||||
http.setPort(port);
|
|
||||||
|
|
||||||
// add this connector
|
|
||||||
server.addConnector(http);
|
|
||||||
} else if (properties.getSslPort() != null) {
|
|
||||||
final Integer port = properties.getSslPort();
|
|
||||||
if (port < 0 || (int) Math.pow(2, 16) <= port) {
|
|
||||||
throw new IllegalStateException("Invalid HTTPs port: " + port);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(properties.getKeyStorePath())) {
|
|
||||||
throw new IllegalStateException(NiFiRegistryProperties.SECURITY_KEYSTORE
|
|
||||||
+ " must be provided to configure Jetty for HTTPs");
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Configuring Jetty for HTTPs on port: " + port);
|
|
||||||
|
|
||||||
// add some secure config
|
|
||||||
final HttpConfiguration httpsConfiguration = new HttpConfiguration(httpConfiguration);
|
|
||||||
httpsConfiguration.setSecureScheme("https");
|
|
||||||
httpsConfiguration.setSecurePort(properties.getSslPort());
|
|
||||||
httpsConfiguration.addCustomizer(new SecureRequestCustomizer());
|
|
||||||
|
|
||||||
// build the connector
|
|
||||||
final ServerConnector https = new ServerConnector(server,
|
|
||||||
new SslConnectionFactory(createSslContextFactory(), "http/1.1"),
|
|
||||||
new HttpConnectionFactory(httpsConfiguration));
|
|
||||||
|
|
||||||
// set host and port
|
|
||||||
if (StringUtils.isNotBlank(properties.getHttpsHost())) {
|
|
||||||
https.setHost(properties.getHttpsHost());
|
|
||||||
}
|
|
||||||
https.setPort(port);
|
|
||||||
|
|
||||||
// add this connector
|
|
||||||
server.addConnector(https);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String[] getCipherSuites(final String cipherSuitesProperty) {
|
|
||||||
return cipherSuitesProperty.split(CIPHER_SUITE_SEPARATOR_PATTERN);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SslContextFactory createSslContextFactory() {
|
|
||||||
final SslContextFactory.Server contextFactory = new SslContextFactory.Server();
|
|
||||||
|
|
||||||
// if needClientAuth is false then set want to true so we can optionally use certs
|
|
||||||
if (properties.getNeedClientAuth()) {
|
|
||||||
logger.info("Setting Jetty's SSLContextFactory needClientAuth to true");
|
|
||||||
contextFactory.setNeedClientAuth(true);
|
|
||||||
} else {
|
|
||||||
logger.info("Setting Jetty's SSLContextFactory wantClientAuth to true");
|
|
||||||
contextFactory.setWantClientAuth(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* below code sets JSSE system properties when values are provided */
|
|
||||||
// keystore properties
|
|
||||||
if (StringUtils.isNotBlank(properties.getKeyStorePath())) {
|
|
||||||
contextFactory.setKeyStorePath(properties.getKeyStorePath());
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotBlank(properties.getKeyStoreType())) {
|
|
||||||
contextFactory.setKeyStoreType(properties.getKeyStoreType());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
final String keystorePassword = properties.getKeyStorePassword();
|
|
||||||
final String keyPassword = properties.getKeyPassword();
|
|
||||||
|
|
||||||
if (StringUtils.isEmpty(keystorePassword)) {
|
|
||||||
throw new IllegalArgumentException("The keystore password cannot be null or empty");
|
|
||||||
} else {
|
|
||||||
// if no key password was provided, then assume the key password is the same as the keystore password.
|
|
||||||
final String defaultKeyPassword = (StringUtils.isBlank(keyPassword)) ? keystorePassword : keyPassword;
|
|
||||||
contextFactory.setKeyStorePassword(keystorePassword);
|
|
||||||
contextFactory.setKeyManagerPassword(defaultKeyPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
// truststore properties
|
|
||||||
if (StringUtils.isNotBlank(properties.getTrustStorePath())) {
|
|
||||||
contextFactory.setTrustStorePath(properties.getTrustStorePath());
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotBlank(properties.getTrustStoreType())) {
|
|
||||||
contextFactory.setTrustStoreType(properties.getTrustStoreType());
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotBlank(properties.getTrustStorePassword())) {
|
|
||||||
contextFactory.setTrustStorePassword(properties.getTrustStorePassword());
|
|
||||||
}
|
|
||||||
|
|
||||||
final String includeCipherSuites = properties.getHttpsCipherSuitesInclude();
|
|
||||||
if (StringUtils.isNotBlank(includeCipherSuites)) {
|
|
||||||
final String[] cipherSuites = getCipherSuites(includeCipherSuites);
|
|
||||||
contextFactory.setIncludeCipherSuites(cipherSuites);
|
|
||||||
}
|
|
||||||
|
|
||||||
final String excludeCipherSuites = properties.getHttpsCipherSuitesExclude();
|
|
||||||
if (StringUtils.isNotBlank(excludeCipherSuites)) {
|
|
||||||
final String[] cipherSuites = getCipherSuites(excludeCipherSuites);
|
|
||||||
contextFactory.setExcludeCipherSuites(cipherSuites);
|
|
||||||
}
|
|
||||||
|
|
||||||
return contextFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadWars() throws IOException {
|
private void loadWars() throws IOException {
|
||||||
|
@ -309,7 +181,7 @@ public class JettyServer {
|
||||||
throw new IllegalStateException("Unable to locate NiFi Registry Web Docs");
|
throw new IllegalStateException("Unable to locate NiFi Registry Web Docs");
|
||||||
}
|
}
|
||||||
|
|
||||||
webUiContext = loadWar(webUiWar, "/nifi-registry");
|
WebAppContext webUiContext = loadWar(webUiWar, "/nifi-registry");
|
||||||
webUiContext.getInitParams().put("oidc-supported", String.valueOf(properties.isOidcEnabled()));
|
webUiContext.getInitParams().put("oidc-supported", String.valueOf(properties.isOidcEnabled()));
|
||||||
|
|
||||||
webApiContext = loadWar(webApiWar, "/nifi-registry-api", getWebApiAdditionalClasspath());
|
webApiContext = loadWar(webApiWar, "/nifi-registry-api", getWebApiAdditionalClasspath());
|
||||||
|
@ -322,7 +194,7 @@ public class JettyServer {
|
||||||
webApiContext.setAttribute("org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern", ".*/spring-[^/]*\\.jar$");
|
webApiContext.setAttribute("org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern", ".*/spring-[^/]*\\.jar$");
|
||||||
|
|
||||||
final String docsContextPath = "/nifi-registry-docs";
|
final String docsContextPath = "/nifi-registry-docs";
|
||||||
webDocsContext = loadWar(webDocsWar, docsContextPath);
|
WebAppContext webDocsContext = loadWar(webDocsWar, docsContextPath);
|
||||||
addDocsServlets(webDocsContext);
|
addDocsServlets(webDocsContext);
|
||||||
|
|
||||||
final HandlerCollection handlers = new HandlerCollection();
|
final HandlerCollection handlers = new HandlerCollection();
|
||||||
|
@ -371,20 +243,18 @@ public class JettyServer {
|
||||||
webappContext.setMaxFormContentSize(600000);
|
webappContext.setMaxFormContentSize(600000);
|
||||||
|
|
||||||
// add HTTP security headers to all responses
|
// add HTTP security headers to all responses
|
||||||
final String ALL_PATHS = "/*";
|
|
||||||
ArrayList<Class<? extends Filter>> filters = new ArrayList<>(Arrays.asList(XFrameOptionsFilter.class, ContentSecurityPolicyFilter.class, XSSProtectionFilter.class));
|
ArrayList<Class<? extends Filter>> filters = new ArrayList<>(Arrays.asList(XFrameOptionsFilter.class, ContentSecurityPolicyFilter.class, XSSProtectionFilter.class));
|
||||||
if(properties.isHTTPSConfigured()) {
|
if (properties.isHTTPSConfigured()) {
|
||||||
filters.add(StrictTransportSecurityFilter.class);
|
filters.add(StrictTransportSecurityFilter.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
filters.forEach( (filter) -> addFilters(filter, ALL_PATHS, webappContext));
|
filters.forEach( (filter) -> addFilters(filter, webappContext));
|
||||||
|
|
||||||
// start out assuming the system ClassLoader will be the parent, but if additional resources were specified then
|
// start out assuming the system ClassLoader will be the parent, but if additional resources were specified then
|
||||||
// inject a new ClassLoader in between the system and webapp ClassLoaders that contains the additional resources
|
// inject a new ClassLoader in between the system and webapp ClassLoaders that contains the additional resources
|
||||||
ClassLoader parentClassLoader = ClassLoader.getSystemClassLoader();
|
ClassLoader parentClassLoader = ClassLoader.getSystemClassLoader();
|
||||||
if (additionalResources != null && additionalResources.length > 0) {
|
if (additionalResources != null && additionalResources.length > 0) {
|
||||||
URLClassLoader additionalClassLoader = new URLClassLoader(additionalResources, ClassLoader.getSystemClassLoader());
|
parentClassLoader = new URLClassLoader(additionalResources, ClassLoader.getSystemClassLoader());
|
||||||
parentClassLoader = additionalClassLoader;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
webappContext.setClassLoader(new WebAppClassLoader(parentClassLoader, webappContext));
|
webappContext.setClassLoader(new WebAppClassLoader(parentClassLoader, webappContext));
|
||||||
|
@ -393,10 +263,10 @@ public class JettyServer {
|
||||||
return webappContext;
|
return webappContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addFilters(Class<? extends Filter> clazz, String path, WebAppContext webappContext) {
|
private void addFilters(Class<? extends Filter> clazz, final WebAppContext webappContext) {
|
||||||
FilterHolder holder = new FilterHolder(clazz);
|
FilterHolder holder = new FilterHolder(clazz);
|
||||||
holder.setName(clazz.getSimpleName());
|
holder.setName(clazz.getSimpleName());
|
||||||
webappContext.addFilter(holder, path, EnumSet.allOf(DispatcherType.class));
|
webappContext.addFilter(holder, ALL_PATHS, EnumSet.allOf(DispatcherType.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
private URL[] getWebApiAdditionalClasspath() {
|
private URL[] getWebApiAdditionalClasspath() {
|
||||||
|
@ -451,7 +321,7 @@ public class JettyServer {
|
||||||
logger.info("]");
|
logger.info("]");
|
||||||
}
|
}
|
||||||
|
|
||||||
return resources.toArray(new URL[resources.size()]);
|
return resources.toArray(new URL[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addDocsServlets(WebAppContext docsContext) {
|
private void addDocsServlets(WebAppContext docsContext) {
|
||||||
|
|
|
@ -0,0 +1,231 @@
|
||||||
|
/*
|
||||||
|
* 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.registry.jetty.connector;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.nifi.jetty.configuration.connector.ApplicationLayerProtocol;
|
||||||
|
import org.apache.nifi.jetty.configuration.connector.StandardServerConnectorFactory;
|
||||||
|
import org.apache.nifi.registry.properties.NiFiRegistryProperties;
|
||||||
|
import org.apache.nifi.security.ssl.StandardKeyStoreBuilder;
|
||||||
|
import org.apache.nifi.security.ssl.StandardSslContextBuilder;
|
||||||
|
import org.apache.nifi.security.util.TlsPlatform;
|
||||||
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry Application extension of Standard Jetty Server Connector Factory
|
||||||
|
*/
|
||||||
|
public class ApplicationServerConnectorFactory extends StandardServerConnectorFactory {
|
||||||
|
|
||||||
|
private static final int HEADER_SIZE = 16384;
|
||||||
|
|
||||||
|
private static final String CIPHER_SUITE_SEPARATOR_PATTERN = ",\\s*";
|
||||||
|
|
||||||
|
private final String includeCipherSuites;
|
||||||
|
|
||||||
|
private final String excludeCipherSuites;
|
||||||
|
|
||||||
|
private final String host;
|
||||||
|
|
||||||
|
private SslContextFactory.Server sslContextFactory;
|
||||||
|
|
||||||
|
public ApplicationServerConnectorFactory(final Server server, final NiFiRegistryProperties properties) {
|
||||||
|
super(server, getPort(properties));
|
||||||
|
|
||||||
|
host = getHost(properties);
|
||||||
|
includeCipherSuites = properties.getHttpsCipherSuitesInclude();
|
||||||
|
excludeCipherSuites = properties.getHttpsCipherSuitesExclude();
|
||||||
|
|
||||||
|
if (properties.isHTTPSConfigured()) {
|
||||||
|
if (properties.getNeedClientAuth()) {
|
||||||
|
setNeedClientAuth(true);
|
||||||
|
} else {
|
||||||
|
setWantClientAuth(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
final SSLContext sslContext = buildSslContext(properties);
|
||||||
|
setSslContext(sslContext);
|
||||||
|
|
||||||
|
setApplicationLayerProtocols(properties);
|
||||||
|
|
||||||
|
// Set Transport Layer Security Protocols based on platform configuration
|
||||||
|
setIncludeSecurityProtocols(TlsPlatform.getPreferredProtocols().toArray(new String[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Server Connector using configured properties
|
||||||
|
*
|
||||||
|
* @return Server Connector
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ServerConnector getServerConnector() {
|
||||||
|
final ServerConnector serverConnector = super.getServerConnector();
|
||||||
|
serverConnector.setHost(host);
|
||||||
|
return serverConnector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Jetty Server SSL Context Factory and reuse the same instance for multiple invocations
|
||||||
|
*
|
||||||
|
* @return Jetty Server SSL Context Factory
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected SslContextFactory.Server getSslContextFactory() {
|
||||||
|
if (sslContextFactory == null) {
|
||||||
|
sslContextFactory = super.getSslContextFactory();
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(includeCipherSuites)) {
|
||||||
|
final String[] cipherSuites = getCipherSuites(includeCipherSuites);
|
||||||
|
sslContextFactory.setIncludeCipherSuites(cipherSuites);
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(excludeCipherSuites)) {
|
||||||
|
final String[] cipherSuites = getCipherSuites(excludeCipherSuites);
|
||||||
|
sslContextFactory.setExcludeCipherSuites(cipherSuites);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sslContextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get HTTP Configuration with additional settings
|
||||||
|
*
|
||||||
|
* @return HTTP Configuration
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected HttpConfiguration getHttpConfiguration() {
|
||||||
|
final HttpConfiguration httpConfiguration = super.getHttpConfiguration();
|
||||||
|
|
||||||
|
httpConfiguration.setRequestHeaderSize(HEADER_SIZE);
|
||||||
|
httpConfiguration.setResponseHeaderSize(HEADER_SIZE);
|
||||||
|
|
||||||
|
return httpConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] getCipherSuites(final String cipherSuitesProperty) {
|
||||||
|
return cipherSuitesProperty.split(CIPHER_SUITE_SEPARATOR_PATTERN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SSLContext buildSslContext(final NiFiRegistryProperties properties) {
|
||||||
|
final KeyStore keyStore = buildKeyStore(properties);
|
||||||
|
final char[] keyPassword = getKeyPassword(properties);
|
||||||
|
final KeyStore trustStore = buildTrustStore(properties);
|
||||||
|
|
||||||
|
return new StandardSslContextBuilder()
|
||||||
|
.keyStore(keyStore)
|
||||||
|
.keyPassword(keyPassword)
|
||||||
|
.trustStore(trustStore)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private char[] getKeyPassword(final NiFiRegistryProperties properties) {
|
||||||
|
final String keyStorePassword = getRequiredProperty(properties, NiFiRegistryProperties.SECURITY_KEYSTORE_PASSWD);
|
||||||
|
final String keyPassword = properties.getProperty(NiFiRegistryProperties.SECURITY_KEY_PASSWD, keyStorePassword);
|
||||||
|
return keyPassword.toCharArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyStore buildKeyStore(final NiFiRegistryProperties properties) {
|
||||||
|
final String keyStore = getRequiredProperty(properties, NiFiRegistryProperties.SECURITY_KEYSTORE);
|
||||||
|
final String keyStoreType = getRequiredProperty(properties, NiFiRegistryProperties.SECURITY_KEYSTORE_TYPE);
|
||||||
|
final String keyStorePassword = getRequiredProperty(properties, NiFiRegistryProperties.SECURITY_KEYSTORE_PASSWD);
|
||||||
|
return buildStore(keyStore, keyStoreType, keyStorePassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyStore buildTrustStore(final NiFiRegistryProperties properties) {
|
||||||
|
final String trustStore = getRequiredProperty(properties, NiFiRegistryProperties.SECURITY_TRUSTSTORE);
|
||||||
|
final String trustStoreType = getRequiredProperty(properties, NiFiRegistryProperties.SECURITY_KEYSTORE_TYPE);
|
||||||
|
final String trustStorePassword = getRequiredProperty(properties, NiFiRegistryProperties.SECURITY_TRUSTSTORE_PASSWD);
|
||||||
|
return buildStore(trustStore, trustStoreType, trustStorePassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyStore buildStore(
|
||||||
|
final String store,
|
||||||
|
final String storeType,
|
||||||
|
final String storePassword
|
||||||
|
) {
|
||||||
|
final Path trustStorePath = Paths.get(store);
|
||||||
|
try (final InputStream inputStream = Files.newInputStream(trustStorePath)) {
|
||||||
|
return new StandardKeyStoreBuilder()
|
||||||
|
.type(storeType)
|
||||||
|
.password(storePassword.toCharArray())
|
||||||
|
.inputStream(inputStream)
|
||||||
|
.build();
|
||||||
|
} catch (final IOException e) {
|
||||||
|
final String message = String.format("Store Path [%s] read failed", store);
|
||||||
|
throw new IllegalStateException(message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRequiredProperty(final NiFiRegistryProperties properties, final String property) {
|
||||||
|
final String requiredProperty = properties.getProperty(property);
|
||||||
|
if (requiredProperty == null || requiredProperty.isEmpty()) {
|
||||||
|
throw new IllegalStateException(String.format("Required Property [%s] not configured", property));
|
||||||
|
}
|
||||||
|
return requiredProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setApplicationLayerProtocols(final NiFiRegistryProperties properties) {
|
||||||
|
final Set<String> protocols = properties.getWebHttpsApplicationProtocols();
|
||||||
|
|
||||||
|
final Set<ApplicationLayerProtocol> applicationLayerProtocols = Arrays.stream(ApplicationLayerProtocol.values())
|
||||||
|
.filter(
|
||||||
|
applicationLayerProtocol -> protocols.contains(applicationLayerProtocol.getProtocol())
|
||||||
|
)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
setApplicationLayerProtocols(applicationLayerProtocols);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getHost(final NiFiRegistryProperties properties) {
|
||||||
|
final String host;
|
||||||
|
|
||||||
|
if (properties.isHTTPSConfigured()) {
|
||||||
|
host = properties.getHttpsHost();
|
||||||
|
} else {
|
||||||
|
host = properties.getHttpHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getPort(final NiFiRegistryProperties properties) {
|
||||||
|
final Integer httpsPort = properties.getSslPort();
|
||||||
|
final Integer httpPort = properties.getPort();
|
||||||
|
|
||||||
|
if (ObjectUtils.allNull(httpsPort, httpPort)) {
|
||||||
|
throw new IllegalStateException("Invalid port configuration: Neither nifi.registry.web.https.port nor nifi.registry.web.http.port specified");
|
||||||
|
} else if (ObjectUtils.allNotNull(httpsPort, httpPort)) {
|
||||||
|
throw new IllegalStateException("Invalid port configuration: Both nifi.registry.web.https.port and nifi.registry.web.http.port specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ObjectUtils.defaultIfNull(httpsPort, httpPort);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,165 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.registry.jetty
|
|
||||||
|
|
||||||
import org.apache.nifi.registry.properties.NiFiRegistryProperties
|
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.rules.ExpectedException
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.mockito.junit.MockitoJUnitRunner
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import org.eclipse.jetty.server.Server
|
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
|
||||||
class JettyServerGroovyTest extends GroovyTestCase {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(JettyServerGroovyTest.class)
|
|
||||||
|
|
||||||
private static final keyPassword = "keyPassword"
|
|
||||||
private static final keystorePassword = "keystorePassword"
|
|
||||||
private static final truststorePassword = "truststorePassword"
|
|
||||||
private static final matchingPassword = "thePassword"
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCreateSslContextFactoryWithKeystoreAndKeypassword() throws Exception {
|
|
||||||
|
|
||||||
// Arrange
|
|
||||||
NiFiRegistryProperties properties = new NiFiRegistryProperties()
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_TRUSTSTORE, "src/test/resources/truststore.jks")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_TRUSTSTORE_PASSWD, truststorePassword)
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_TRUSTSTORE_TYPE, "JKS")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEYSTORE, "src/test/resources/keystoreDifferentPasswords.jks")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEY_PASSWD, keyPassword)
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEYSTORE_PASSWD, keystorePassword)
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEYSTORE_TYPE, "JKS")
|
|
||||||
|
|
||||||
Server internalServer = new Server()
|
|
||||||
JettyServer testServer = new JettyServer(internalServer, properties)
|
|
||||||
|
|
||||||
// Act
|
|
||||||
SslContextFactory sslContextFactory = testServer.createSslContextFactory()
|
|
||||||
sslContextFactory.start()
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
assertNotNull(sslContextFactory)
|
|
||||||
assertNotNull(sslContextFactory.getSslContext())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCreateSslContextFactoryWithOnlyKeystorePassword() throws Exception {
|
|
||||||
|
|
||||||
// Arrange
|
|
||||||
NiFiRegistryProperties properties = new NiFiRegistryProperties()
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_TRUSTSTORE, "src/test/resources/truststore.jks")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_TRUSTSTORE_PASSWD, truststorePassword)
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_TRUSTSTORE_TYPE, "JKS")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEYSTORE, "src/test/resources/keystoreSamePassword.jks")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEYSTORE_PASSWD, matchingPassword)
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEYSTORE_TYPE, "JKS")
|
|
||||||
|
|
||||||
Server internalServer = new Server()
|
|
||||||
JettyServer testServer = new JettyServer(internalServer, properties)
|
|
||||||
|
|
||||||
// Act
|
|
||||||
SslContextFactory sslContextFactory = testServer.createSslContextFactory()
|
|
||||||
sslContextFactory.start()
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
assertNotNull(sslContextFactory)
|
|
||||||
assertNotNull(sslContextFactory.getSslContext())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCreateSslContextFactoryWithMatchingPasswordsDefined() throws Exception {
|
|
||||||
|
|
||||||
// Arrange
|
|
||||||
NiFiRegistryProperties properties = new NiFiRegistryProperties()
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_TRUSTSTORE, "src/test/resources/truststore.jks")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_TRUSTSTORE_PASSWD, truststorePassword)
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_TRUSTSTORE_TYPE, "JKS")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEYSTORE, "src/test/resources/keystoreSamePassword.jks")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEY_PASSWD, matchingPassword)
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEYSTORE_PASSWD, matchingPassword)
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEYSTORE_TYPE, "JKS")
|
|
||||||
|
|
||||||
Server internalServer = new Server()
|
|
||||||
JettyServer testServer = new JettyServer(internalServer, properties)
|
|
||||||
|
|
||||||
// Act
|
|
||||||
SslContextFactory sslContextFactory = testServer.createSslContextFactory()
|
|
||||||
sslContextFactory.start()
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
assertNotNull(sslContextFactory)
|
|
||||||
assertNotNull(sslContextFactory.getSslContext())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Rule public ExpectedException exception = ExpectedException.none()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCreateSslContextFactoryWithNoKeystorePasswordFails() throws Exception {
|
|
||||||
|
|
||||||
// Arrange
|
|
||||||
exception.expect(IllegalArgumentException.class)
|
|
||||||
exception.expectMessage("The keystore password cannot be null or empty")
|
|
||||||
|
|
||||||
NiFiRegistryProperties properties = new NiFiRegistryProperties()
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_TRUSTSTORE, "src/test/resources/truststore.jks")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_TRUSTSTORE_PASSWD, truststorePassword)
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_TRUSTSTORE_TYPE, "JKS")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEYSTORE, "src/test/resources/keystoreSamePassword.jks")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEYSTORE_TYPE, "JKS")
|
|
||||||
|
|
||||||
Server internalServer = new Server()
|
|
||||||
JettyServer testServer = new JettyServer(internalServer, properties)
|
|
||||||
|
|
||||||
// Act but expect exception
|
|
||||||
SslContextFactory sslContextFactory = testServer.createSslContextFactory()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCreateSslContextFactoryWithCipherSuites() throws Exception {
|
|
||||||
|
|
||||||
// Arrange
|
|
||||||
NiFiRegistryProperties properties = new NiFiRegistryProperties()
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_TRUSTSTORE, "src/test/resources/truststore.jks")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_TRUSTSTORE_PASSWD, truststorePassword)
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_TRUSTSTORE_TYPE, "JKS")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEYSTORE, "src/test/resources/keystoreSamePassword.jks")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEYSTORE_PASSWD, matchingPassword)
|
|
||||||
properties.setProperty(NiFiRegistryProperties.SECURITY_KEYSTORE_TYPE, "JKS")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.WEB_HTTPS_CIPHERSUITES_INCLUDE, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384")
|
|
||||||
properties.setProperty(NiFiRegistryProperties.WEB_HTTPS_CIPHERSUITES_EXCLUDE, "BAD_CIPHER")
|
|
||||||
|
|
||||||
Server internalServer = new Server()
|
|
||||||
JettyServer testServer = new JettyServer(internalServer, properties)
|
|
||||||
|
|
||||||
// Act
|
|
||||||
SslContextFactory sslContextFactory = testServer.createSslContextFactory()
|
|
||||||
sslContextFactory.start()
|
|
||||||
|
|
||||||
// Assert this
|
|
||||||
assertNotNull(sslContextFactory)
|
|
||||||
assertNotNull(sslContextFactory.getSslContext())
|
|
||||||
assertEquals("INCLUDE_CIPHER_SUITES", sslContextFactory.getIncludeCipherSuites(), ["TLS_DHE_RSA_WITH_AES_128_GCM_SHA256","TLS_DHE_RSA_WITH_AES_256_GCM_SHA384"])
|
|
||||||
assertEquals("EXCLUDE_CIPHER_SUITES", sslContextFactory.getExcludeCipherSuites(), ["BAD_CIPHER"])
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
/*
|
||||||
|
* 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.registry.jetty.connector;
|
||||||
|
|
||||||
|
import org.apache.nifi.registry.properties.NiFiRegistryProperties;
|
||||||
|
import org.apache.nifi.remote.io.socket.NetworkUtils;
|
||||||
|
import org.apache.nifi.security.util.TemporaryKeyStoreBuilder;
|
||||||
|
import org.apache.nifi.security.util.TlsConfiguration;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class ApplicationServerConnectorFactoryTest {
|
||||||
|
|
||||||
|
private static final String SSL_PROTOCOL = "ssl";
|
||||||
|
|
||||||
|
private static final String H2_PROTOCOL = "h2";
|
||||||
|
|
||||||
|
private static final String INCLUDE_CIPHERS = ".*GCM.*";
|
||||||
|
|
||||||
|
private static final String EXCLUDE_CIPHERS = "*.CBC.*";
|
||||||
|
|
||||||
|
private static final String LOCALHOST = "127.0.0.1";
|
||||||
|
|
||||||
|
static TlsConfiguration tlsConfiguration;
|
||||||
|
|
||||||
|
Server server;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setTlsConfiguration() {
|
||||||
|
tlsConfiguration = new TemporaryKeyStoreBuilder().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setServer() {
|
||||||
|
server = new Server();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetServerConnectorRequiredProperties() {
|
||||||
|
final int port = NetworkUtils.getAvailableTcpPort();
|
||||||
|
final Properties configuredProperties = new Properties();
|
||||||
|
configuredProperties.put(NiFiRegistryProperties.WEB_HTTP_PORT, Integer.toString(port));
|
||||||
|
|
||||||
|
final NiFiRegistryProperties properties = getProperties(configuredProperties);
|
||||||
|
final ApplicationServerConnectorFactory factory = new ApplicationServerConnectorFactory(server, properties);
|
||||||
|
|
||||||
|
final ServerConnector serverConnector = factory.getServerConnector();
|
||||||
|
|
||||||
|
assertNotNull(serverConnector);
|
||||||
|
assertNull(serverConnector.getHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetServerConnectorHostProperty() {
|
||||||
|
final int port = NetworkUtils.getAvailableTcpPort();
|
||||||
|
final Properties configuredProperties = new Properties();
|
||||||
|
configuredProperties.put(NiFiRegistryProperties.WEB_HTTP_PORT, Integer.toString(port));
|
||||||
|
configuredProperties.put(NiFiRegistryProperties.WEB_HTTP_HOST, LOCALHOST);
|
||||||
|
|
||||||
|
final NiFiRegistryProperties properties = getProperties(configuredProperties);
|
||||||
|
final ApplicationServerConnectorFactory factory = new ApplicationServerConnectorFactory(server, properties);
|
||||||
|
|
||||||
|
final ServerConnector serverConnector = factory.getServerConnector();
|
||||||
|
|
||||||
|
assertNotNull(serverConnector);
|
||||||
|
assertEquals(LOCALHOST, serverConnector.getHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetServerConnectorSslProperties() {
|
||||||
|
final int port = NetworkUtils.getAvailableTcpPort();
|
||||||
|
final Properties configuredProperties = getSecurityProperties();
|
||||||
|
configuredProperties.put(NiFiRegistryProperties.WEB_HTTPS_PORT, Integer.toString(port));
|
||||||
|
|
||||||
|
final NiFiRegistryProperties properties = getProperties(configuredProperties);
|
||||||
|
final ApplicationServerConnectorFactory factory = new ApplicationServerConnectorFactory(server, properties);
|
||||||
|
|
||||||
|
final ServerConnector serverConnector = factory.getServerConnector();
|
||||||
|
|
||||||
|
assertNotNull(serverConnector);
|
||||||
|
assertNull(serverConnector.getHost());
|
||||||
|
assertTrue(serverConnector.getProtocols().contains(SSL_PROTOCOL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetServerConnectorHttp2Properties() {
|
||||||
|
final int port = NetworkUtils.getAvailableTcpPort();
|
||||||
|
final Properties configuredProperties = getSecurityProperties();
|
||||||
|
configuredProperties.put(NiFiRegistryProperties.WEB_HTTPS_PORT, Integer.toString(port));
|
||||||
|
configuredProperties.put(NiFiRegistryProperties.WEB_HTTPS_APPLICATION_PROTOCOLS, H2_PROTOCOL);
|
||||||
|
configuredProperties.put(NiFiRegistryProperties.WEB_HTTPS_CIPHERSUITES_INCLUDE, INCLUDE_CIPHERS);
|
||||||
|
configuredProperties.put(NiFiRegistryProperties.WEB_HTTPS_CIPHERSUITES_EXCLUDE, EXCLUDE_CIPHERS);
|
||||||
|
|
||||||
|
final NiFiRegistryProperties properties = getProperties(configuredProperties);
|
||||||
|
final ApplicationServerConnectorFactory factory = new ApplicationServerConnectorFactory(server, properties);
|
||||||
|
|
||||||
|
final ServerConnector serverConnector = factory.getServerConnector();
|
||||||
|
|
||||||
|
assertNotNull(serverConnector);
|
||||||
|
assertNull(serverConnector.getHost());
|
||||||
|
|
||||||
|
final List<String> protocols = serverConnector.getProtocols();
|
||||||
|
assertTrue(protocols.contains(SSL_PROTOCOL));
|
||||||
|
assertTrue(protocols.contains(H2_PROTOCOL));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Properties getSecurityProperties() {
|
||||||
|
final Properties securityProperties = new Properties();
|
||||||
|
securityProperties.put(NiFiRegistryProperties.SECURITY_KEYSTORE, tlsConfiguration.getKeystorePath());
|
||||||
|
securityProperties.put(NiFiRegistryProperties.SECURITY_KEYSTORE_TYPE, tlsConfiguration.getKeystoreType().getType());
|
||||||
|
securityProperties.put(NiFiRegistryProperties.SECURITY_KEYSTORE_PASSWD, tlsConfiguration.getKeystorePassword());
|
||||||
|
securityProperties.put(NiFiRegistryProperties.SECURITY_TRUSTSTORE, tlsConfiguration.getTruststorePath());
|
||||||
|
securityProperties.put(NiFiRegistryProperties.SECURITY_TRUSTSTORE_TYPE, tlsConfiguration.getTruststoreType().getType());
|
||||||
|
securityProperties.put(NiFiRegistryProperties.SECURITY_TRUSTSTORE_PASSWD, tlsConfiguration.getTruststorePassword());
|
||||||
|
return securityProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NiFiRegistryProperties getProperties(final Properties configuredProperties) {
|
||||||
|
return new NiFiRegistryProperties(configuredProperties);
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -53,6 +53,7 @@ public class NiFiRegistryProperties extends ApplicationProperties {
|
||||||
public static final String WEB_HTTPS_HOST = "nifi.registry.web.https.host";
|
public static final String WEB_HTTPS_HOST = "nifi.registry.web.https.host";
|
||||||
public static final String WEB_HTTPS_CIPHERSUITES_INCLUDE = "nifi.registry.web.https.ciphersuites.include";
|
public static final String WEB_HTTPS_CIPHERSUITES_INCLUDE = "nifi.registry.web.https.ciphersuites.include";
|
||||||
public static final String WEB_HTTPS_CIPHERSUITES_EXCLUDE = "nifi.registry.web.https.ciphersuites.exclude";
|
public static final String WEB_HTTPS_CIPHERSUITES_EXCLUDE = "nifi.registry.web.https.ciphersuites.exclude";
|
||||||
|
public static final String WEB_HTTPS_APPLICATION_PROTOCOLS = "nifi.registry.web.https.application.protocols";
|
||||||
public static final String WEB_WORKING_DIR = "nifi.registry.web.jetty.working.directory";
|
public static final String WEB_WORKING_DIR = "nifi.registry.web.jetty.working.directory";
|
||||||
public static final String WEB_THREADS = "nifi.registry.web.jetty.threads";
|
public static final String WEB_THREADS = "nifi.registry.web.jetty.threads";
|
||||||
public static final String WEB_SHOULD_SEND_SERVER_VERSION = "nifi.registry.web.should.send.server.version";
|
public static final String WEB_SHOULD_SEND_SERVER_VERSION = "nifi.registry.web.should.send.server.version";
|
||||||
|
@ -119,6 +120,7 @@ public class NiFiRegistryProperties extends ApplicationProperties {
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
public static final String DEFAULT_WEB_WORKING_DIR = "./work/jetty";
|
public static final String DEFAULT_WEB_WORKING_DIR = "./work/jetty";
|
||||||
|
public static final String DEFAULT_WEB_HTTPS_APPLICATION_PROTOCOLS = "http/1.1";
|
||||||
public static final String DEFAULT_WAR_DIR = "./lib";
|
public static final String DEFAULT_WAR_DIR = "./lib";
|
||||||
public static final String DEFAULT_PROVIDERS_CONFIGURATION_FILE = "./conf/providers.xml";
|
public static final String DEFAULT_PROVIDERS_CONFIGURATION_FILE = "./conf/providers.xml";
|
||||||
public static final String DEFAULT_REGISTRY_ALIAS_CONFIGURATION_FILE = "./conf/registry-aliases.xml";
|
public static final String DEFAULT_REGISTRY_ALIAS_CONFIGURATION_FILE = "./conf/registry-aliases.xml";
|
||||||
|
@ -229,6 +231,16 @@ public class NiFiRegistryProperties extends ApplicationProperties {
|
||||||
return new File(getProperty(WEB_WORKING_DIR, DEFAULT_WEB_WORKING_DIR));
|
return new File(getProperty(WEB_WORKING_DIR, DEFAULT_WEB_WORKING_DIR));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Web HTTPS Application Protocols defaults to HTTP/1.1
|
||||||
|
*
|
||||||
|
* @return Set of configured HTTPS Application Protocols
|
||||||
|
*/
|
||||||
|
public Set<String> getWebHttpsApplicationProtocols() {
|
||||||
|
final String protocols = getProperty(WEB_HTTPS_APPLICATION_PROTOCOLS, DEFAULT_WEB_HTTPS_APPLICATION_PROTOCOLS);
|
||||||
|
return Arrays.stream(protocols.split("\\s+")).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
public File getExtensionsWorkingDirectory() {
|
public File getExtensionsWorkingDirectory() {
|
||||||
return new File(getProperty(EXTENSIONS_WORKING_DIR, DEFAULT_EXTENSIONS_WORKING_DIR));
|
return new File(getProperty(EXTENSIONS_WORKING_DIR, DEFAULT_EXTENSIONS_WORKING_DIR));
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ nifi.registry.web.http.host=${nifi.registry.web.http.host}
|
||||||
nifi.registry.web.http.port=${nifi.registry.web.http.port}
|
nifi.registry.web.http.port=${nifi.registry.web.http.port}
|
||||||
nifi.registry.web.https.host=${nifi.registry.web.https.host}
|
nifi.registry.web.https.host=${nifi.registry.web.https.host}
|
||||||
nifi.registry.web.https.port=${nifi.registry.web.https.port}
|
nifi.registry.web.https.port=${nifi.registry.web.https.port}
|
||||||
|
nifi.registry.web.https.application.protocols=${nifi.registry.web.https.application.protocols}
|
||||||
nifi.registry.web.jetty.working.directory=${nifi.registry.jetty.work.dir}
|
nifi.registry.web.jetty.working.directory=${nifi.registry.jetty.work.dir}
|
||||||
nifi.registry.web.jetty.threads=${nifi.registry.web.jetty.threads}
|
nifi.registry.web.jetty.threads=${nifi.registry.web.jetty.threads}
|
||||||
nifi.registry.web.should.send.server.version=${nifi.registry.web.should.send.server.version}
|
nifi.registry.web.should.send.server.version=${nifi.registry.web.should.send.server.version}
|
||||||
|
|
|
@ -93,6 +93,18 @@
|
||||||
<artifactId>apache-jstl</artifactId>
|
<artifactId>apache-jstl</artifactId>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.http2</groupId>
|
||||||
|
<artifactId>http2-server</artifactId>
|
||||||
|
<version>${jetty.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-alpn-server</artifactId>
|
||||||
|
<version>${jetty.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
<!-- lib/java11 -->
|
<!-- lib/java11 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>jakarta.xml.bind</groupId>
|
<groupId>jakarta.xml.bind</groupId>
|
||||||
|
|
Loading…
Reference in New Issue