NIFI-13639 Replaced OkHttp with web-client-api in web-security

This closes #9157

Signed-off-by: Joseph Witt <joewitt@apache.org>
This commit is contained in:
exceptionfactory 2024-08-06 18:25:29 -05:00 committed by Joseph Witt
parent 98f55b21b8
commit 7348740ecc
No known key found for this signature in database
GPG Key ID: 9093BF854F811A1A
6 changed files with 104 additions and 54 deletions

View File

@ -299,8 +299,14 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-web-client-api</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-web-client</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>

View File

@ -73,7 +73,6 @@ import org.springframework.security.saml2.provider.service.web.authentication.lo
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import java.time.Duration;
@ -95,8 +94,6 @@ public class SamlAuthenticationSecurityConfiguration {
private final LogoutRequestManager logoutRequestManager;
private final SSLContext sslContext;
private final X509ExtendedKeyManager keyManager;
private final X509ExtendedTrustManager trustManager;
@ -105,14 +102,12 @@ public class SamlAuthenticationSecurityConfiguration {
@Autowired final NiFiProperties properties,
@Autowired final BearerTokenProvider bearerTokenProvider,
@Autowired final LogoutRequestManager logoutRequestManager,
@Autowired(required = false) final SSLContext sslContext,
@Autowired(required = false) final X509ExtendedKeyManager keyManager,
@Autowired(required = false) final X509ExtendedTrustManager trustManager
) {
this.properties = Objects.requireNonNull(properties, "Properties required");
this.bearerTokenProvider = Objects.requireNonNull(bearerTokenProvider, "Bearer Token Provider required");
this.logoutRequestManager = Objects.requireNonNull(logoutRequestManager, "Logout Request Manager required");
this.sslContext = sslContext;
this.keyManager = keyManager;
this.trustManager = trustManager;
}
@ -320,7 +315,7 @@ public class SamlAuthenticationSecurityConfiguration {
@Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
return properties.isSamlEnabled()
? new StandardRelyingPartyRegistrationRepository(properties, sslContext, keyManager, trustManager)
? new StandardRelyingPartyRegistrationRepository(properties, keyManager, trustManager)
: getDisabledRelyingPartyRegistrationRepository();
}

View File

@ -16,26 +16,27 @@
*/
package org.apache.nifi.web.security.saml2.registration;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.client.StandardWebClientService;
import org.apache.nifi.web.client.api.HttpResponseEntity;
import org.apache.nifi.web.client.api.HttpResponseStatus;
import org.apache.nifi.web.client.api.WebClientService;
import org.apache.nifi.web.client.ssl.TlsContext;
import org.apache.nifi.web.security.saml2.SamlConfigurationException;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.time.Duration;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
@ -46,21 +47,23 @@ class StandardRegistrationBuilderProvider implements RegistrationBuilderProvider
private static final String HTTP_SCHEME_PREFIX = "http";
private static final String TLS_PROTOCOL = "TLS";
private static final ResourceLoader resourceLoader = new DefaultResourceLoader();
private final NiFiProperties properties;
private final SSLContext sslContext;
private final X509KeyManager keyManager;
private final X509TrustManager trustManager;
public StandardRegistrationBuilderProvider(
final NiFiProperties properties,
final SSLContext sslContext,
final X509KeyManager keyManager,
final X509TrustManager trustManager
) {
this.properties = Objects.requireNonNull(properties, "Properties required");
this.sslContext = sslContext;
this.keyManager = keyManager;
this.trustManager = trustManager;
}
@ -91,25 +94,26 @@ class StandardRegistrationBuilderProvider implements RegistrationBuilderProvider
}
private InputStream getRemoteInputStream(final String metadataUrl) {
final OkHttpClient client = getHttpClient();
final WebClientService webClientService = getWebClientService();
final URI uri = URI.create(metadataUrl);
final Request request = new Request.Builder().get().url(metadataUrl).build();
final Call call = client.newCall(request);
try {
final Response response = call.execute();
if (response.isSuccessful()) {
final ResponseBody body = Objects.requireNonNull(response.body(), "SAML Metadata response not found");
return body.byteStream();
final HttpResponseEntity responseEntity = webClientService.get().uri(uri).retrieve();
final int statusCode = responseEntity.statusCode();
if (HttpResponseStatus.OK.getCode() == statusCode) {
return responseEntity.body();
} else {
response.close();
throw new SamlConfigurationException(String.format("SAML Metadata retrieval failed [%s] HTTP %d", metadataUrl, response.code()));
responseEntity.close();
throw new SamlConfigurationException(String.format("SAML Metadata retrieval failed [%s] HTTP %d", metadataUrl, statusCode));
}
} catch (final IOException e) {
throw new SamlConfigurationException(String.format("SAML Metadata retrieval failed [%s]", metadataUrl), e);
}
}
private OkHttpClient getHttpClient() {
private WebClientService getWebClientService() {
final Duration connectTimeout = Duration.ofMillis(
(long) FormatUtils.getPreciseTimeDuration(properties.getSamlHttpClientConnectTimeout(), TimeUnit.MILLISECONDS)
);
@ -117,15 +121,29 @@ class StandardRegistrationBuilderProvider implements RegistrationBuilderProvider
(long) FormatUtils.getPreciseTimeDuration(properties.getSamlHttpClientReadTimeout(), TimeUnit.MILLISECONDS)
);
final OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(connectTimeout)
.readTimeout(readTimeout);
final StandardWebClientService webClientService = new StandardWebClientService();
webClientService.setConnectTimeout(connectTimeout);
webClientService.setReadTimeout(readTimeout);
if (NIFI_TRUST_STORE_STRATEGY.equals(properties.getSamlHttpClientTruststoreStrategy())) {
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
builder.sslSocketFactory(sslSocketFactory, trustManager);
webClientService.setTlsContext(new TlsContext() {
@Override
public String getProtocol() {
return TLS_PROTOCOL;
}
@Override
public X509TrustManager getTrustManager() {
return trustManager;
}
@Override
public Optional<X509KeyManager> getKeyManager() {
return Optional.ofNullable(keyManager);
}
});
}
return builder.build();
return webClientService;
}
}

View File

@ -24,7 +24,6 @@ import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import java.security.Principal;
@ -34,6 +33,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* Standard implementation of Relying Party Registration Repository based on NiFi Properties
*/
@ -52,8 +52,6 @@ public class StandardRelyingPartyRegistrationRepository implements RelyingPartyR
private final NiFiProperties properties;
private final SSLContext sslContext;
private final X509ExtendedTrustManager trustManager;
private final X509ExtendedKeyManager keyManager;
@ -63,19 +61,16 @@ public class StandardRelyingPartyRegistrationRepository implements RelyingPartyR
/**
* Standard implementation builds a Registration based on NiFi Properties and returns the same instance for all queries
*
* @param sslContext SSL Context loaded from properties
* @param keyManager Key Manager loaded from properties
* @param trustManager Trust manager loaded from properties
* @param properties NiFi Application Properties
*/
public StandardRelyingPartyRegistrationRepository(
final NiFiProperties properties,
final SSLContext sslContext,
final X509ExtendedKeyManager keyManager,
final X509ExtendedTrustManager trustManager
) {
this.properties = properties;
this.sslContext = sslContext;
this.keyManager = keyManager;
this.trustManager = trustManager;
this.relyingPartyRegistration = getRelyingPartyRegistration();
@ -87,7 +82,7 @@ public class StandardRelyingPartyRegistrationRepository implements RelyingPartyR
}
private RelyingPartyRegistration getRelyingPartyRegistration() {
final RegistrationBuilderProvider registrationBuilderProvider = new StandardRegistrationBuilderProvider(properties, sslContext, trustManager);
final RegistrationBuilderProvider registrationBuilderProvider = new StandardRegistrationBuilderProvider(properties, keyManager, trustManager);
final RelyingPartyRegistration.Builder builder = registrationBuilderProvider.getRegistrationBuilder();
builder.registrationId(Saml2RegistrationProperty.REGISTRATION_ID.getProperty());

View File

@ -20,10 +20,12 @@ import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.apache.commons.io.IOUtils;
import org.apache.nifi.security.util.SslContextFactory;
import org.apache.nifi.security.ssl.StandardKeyManagerBuilder;
import org.apache.nifi.security.ssl.StandardKeyStoreBuilder;
import org.apache.nifi.security.ssl.StandardSslContextBuilder;
import org.apache.nifi.security.ssl.StandardTrustManagerBuilder;
import org.apache.nifi.security.util.TemporaryKeyStoreBuilder;
import org.apache.nifi.security.util.TlsConfiguration;
import org.apache.nifi.security.util.TlsException;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.saml2.SamlConfigurationException;
import org.junit.jupiter.api.AfterEach;
@ -34,11 +36,16 @@ import org.springframework.security.saml2.provider.service.registration.Saml2Mes
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.util.Objects;
import java.util.Properties;
@ -102,10 +109,15 @@ class StandardRegistrationBuilderProviderTest {
}
@Test
void testGetRegistrationBuilderHttpsUrl() throws IOException, TlsException {
void testGetRegistrationBuilderHttpsUrl() throws IOException {
final TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build();
final SSLContext sslContext = Objects.requireNonNull(SslContextFactory.createSslContext(tlsConfiguration));
final X509TrustManager trustManager = SslContextFactory.getX509TrustManager(tlsConfiguration);
final X509KeyManager keyManager = getKeyManager(tlsConfiguration);
final X509TrustManager trustManager = getTrustManager(tlsConfiguration);
final SSLContext sslContext = new StandardSslContextBuilder()
.keyManager(keyManager)
.keyPassword(tlsConfiguration.getKeyPassword().toCharArray())
.trustManager(trustManager)
.build();
final SSLSocketFactory sslSocketFactory = Objects.requireNonNull(sslContext.getSocketFactory());
mockWebServer.useHttps(sslSocketFactory, PROXY_DISABLED);
@ -117,7 +129,7 @@ class StandardRegistrationBuilderProviderTest {
final NiFiProperties properties = getProperties(metadataUrl, tlsConfiguration);
assertRegistrationFound(properties, sslContext, trustManager);
assertRegistrationFound(properties, keyManager, trustManager);
}
private String getMetadataUrl() {
@ -125,14 +137,41 @@ class StandardRegistrationBuilderProviderTest {
return url.toString();
}
private void assertRegistrationFound(final NiFiProperties properties, final SSLContext sslContext, final X509TrustManager trustManager) {
final StandardRegistrationBuilderProvider provider = new StandardRegistrationBuilderProvider(properties, sslContext, trustManager);
private void assertRegistrationFound(final NiFiProperties properties, final X509KeyManager keyManager, final X509TrustManager trustManager) {
final StandardRegistrationBuilderProvider provider = new StandardRegistrationBuilderProvider(properties, keyManager, trustManager);
final RelyingPartyRegistration.Builder builder = provider.getRegistrationBuilder();
final RelyingPartyRegistration registration = builder.build();
assertEquals(Saml2MessageBinding.POST, registration.getAssertionConsumerServiceBinding());
}
private X509ExtendedKeyManager getKeyManager(final TlsConfiguration tlsConfiguration) throws IOException {
try (InputStream inputStream = new FileInputStream(tlsConfiguration.getKeystorePath())) {
final KeyStore keyStore = new StandardKeyStoreBuilder()
.inputStream(inputStream)
.password(tlsConfiguration.getKeystorePassword().toCharArray())
.type(tlsConfiguration.getKeystoreType().getType())
.build();
return new StandardKeyManagerBuilder()
.keyStore(keyStore)
.keyPassword(tlsConfiguration.getFunctionalKeyPassword().toCharArray())
.build();
}
}
private X509ExtendedTrustManager getTrustManager(final TlsConfiguration tlsConfiguration) throws IOException {
try (InputStream inputStream = new FileInputStream(tlsConfiguration.getTruststorePath())) {
final KeyStore trustStore = new StandardKeyStoreBuilder()
.inputStream(inputStream)
.password(tlsConfiguration.getTruststorePassword().toCharArray())
.type(tlsConfiguration.getTruststoreType().getType())
.build();
return new StandardTrustManagerBuilder().trustStore(trustStore).build();
}
}
private NiFiProperties getProperties(final String metadataUrl) {
final Properties properties = new Properties();
properties.setProperty(NiFiProperties.SECURITY_USER_SAML_IDP_METADATA_URL, metadataUrl);

View File

@ -18,7 +18,6 @@ package org.apache.nifi.web.security.saml2.registration;
import org.apache.nifi.security.ssl.StandardKeyManagerBuilder;
import org.apache.nifi.security.ssl.StandardKeyStoreBuilder;
import org.apache.nifi.security.ssl.StandardSslContextBuilder;
import org.apache.nifi.security.ssl.StandardTrustManagerBuilder;
import org.apache.nifi.security.util.TemporaryKeyStoreBuilder;
import org.apache.nifi.security.util.TlsConfiguration;
@ -28,7 +27,6 @@ import org.opensaml.xmlsec.signature.support.SignatureConstants;
import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.security.auth.x500.X500Principal;
@ -59,7 +57,7 @@ class StandardRelyingPartyRegistrationRepositoryTest {
@Test
void testFindByRegistrationId() {
final NiFiProperties properties = getProperties();
final StandardRelyingPartyRegistrationRepository repository = new StandardRelyingPartyRegistrationRepository(properties, null, null, null);
final StandardRelyingPartyRegistrationRepository repository = new StandardRelyingPartyRegistrationRepository(properties, null, null);
final RelyingPartyRegistration registration = repository.findByRegistrationId(Saml2RegistrationProperty.REGISTRATION_ID.getProperty());
@ -81,10 +79,9 @@ class StandardRelyingPartyRegistrationRepositoryTest {
final TlsConfiguration tlsConfiguration = new TemporaryKeyStoreBuilder().build();
final X509ExtendedKeyManager keyManager = getKeyManager(tlsConfiguration);
final X509ExtendedTrustManager trustManager = getTrustManager(tlsConfiguration);
final SSLContext sslContext = new StandardSslContextBuilder().keyManager(keyManager).trustManager(trustManager).build();
final NiFiProperties properties = getSingleLogoutProperties(tlsConfiguration);
final StandardRelyingPartyRegistrationRepository repository = new StandardRelyingPartyRegistrationRepository(properties, sslContext, keyManager, trustManager);
final StandardRelyingPartyRegistrationRepository repository = new StandardRelyingPartyRegistrationRepository(properties, keyManager, trustManager);
final RelyingPartyRegistration registration = repository.findByRegistrationId(Saml2RegistrationProperty.REGISTRATION_ID.getProperty());