NIFI-7799: Relogin with Kerberos on connect exception in DBCPConnectionPool (#4519)

This commit is contained in:
Matthew Burgess 2020-09-17 13:33:54 -04:00 committed by GitHub
parent 9370571131
commit 7e145142e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 208 additions and 61 deletions

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-extension-utils</artifactId>
<version>1.13.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-kerberos-test-utils</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-api</artifactId>
<version>1.13.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>
<version>1.13.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-kerberos-credentials-service-api</artifactId>
<version>1.13.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,85 @@
/*
* 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.kerberos;
import org.apache.nifi.annotation.lifecycle.OnEnabled;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.controller.AbstractControllerService;
import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.processor.util.StandardValidators;
import org.apache.nifi.reporting.InitializationException;
import java.util.ArrayList;
import java.util.List;
public class MockKerberosCredentialsService extends AbstractControllerService implements KerberosCredentialsService {
public static String DEFAULT_KEYTAB = "src/test/resources/fake.keytab";
public static String DEFAULT_PRINCIPAL = "test@REALM.COM";
private volatile String keytab = DEFAULT_KEYTAB;
private volatile String principal = DEFAULT_PRINCIPAL;
public static final PropertyDescriptor PRINCIPAL = new PropertyDescriptor.Builder()
.name("Kerberos Principal")
.description("Kerberos principal to authenticate as. Requires nifi.kerberos.krb5.file to be set in your nifi.properties")
.addValidator(StandardValidators.NON_BLANK_VALIDATOR)
.expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
.required(true)
.build();
public static final PropertyDescriptor KEYTAB = new PropertyDescriptor.Builder()
.name("Kerberos Keytab")
.description("Kerberos keytab associated with the principal. Requires nifi.kerberos.krb5.file to be set in your nifi.properties")
.addValidator(StandardValidators.FILE_EXISTS_VALIDATOR)
.expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
.required(true)
.build();
public MockKerberosCredentialsService() {
}
@OnEnabled
public void onConfigured(final ConfigurationContext context) throws InitializationException {
keytab = context.getProperty(KEYTAB).getValue();
principal = context.getProperty(PRINCIPAL).getValue();
}
@Override
public String getKeytab() {
return keytab;
}
@Override
public String getPrincipal() {
return principal;
}
@Override
protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
final List<PropertyDescriptor> properties = new ArrayList<>(2);
properties.add(KEYTAB);
properties.add(PRINCIPAL);
return properties;
}
@Override
public String getIdentifier() {
return "kcs";
}
}

View File

@ -36,6 +36,7 @@
<module>nifi-database-test-utils</module>
<module>nifi-service-utils</module>
<module>nifi-prometheus-utils</module>
<module>nifi-kerberos-test-utils</module>
</modules>
</project>

View File

@ -147,6 +147,12 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-kerberos-test-utils</artifactId>
<version>1.13.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -48,15 +48,11 @@ import org.apache.hive.streaming.StubSerializationError;
import org.apache.hive.streaming.StubStreamingIOFailure;
import org.apache.hive.streaming.StubTransactionError;
import org.apache.nifi.avro.AvroTypeUtil;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.controller.ControllerServiceInitializationContext;
import org.apache.nifi.hadoop.SecurityUtil;
import org.apache.nifi.json.JsonRecordSetWriter;
import org.apache.nifi.json.JsonTreeReader;
import org.apache.nifi.kerberos.KerberosCredentialsService;
import org.apache.nifi.kerberos.MockKerberosCredentialsService;
import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.reporting.InitializationException;
@ -96,7 +92,6 @@ import java.sql.Timestamp;
import java.time.Instant;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
@ -293,6 +288,8 @@ public class TestPutHive3Streaming {
KerberosCredentialsService kcs = new MockKerberosCredentialsService();
runner.addControllerService("kcs", kcs);
runner.setProperty(KERBEROS_CREDENTIALS_SERVICE, "kcs");
runner.setProperty(kcs, MockKerberosCredentialsService.PRINCIPAL, "test");
runner.setProperty(kcs, MockKerberosCredentialsService.KEYTAB, "src/test/resources/core-site-security.xml");
runner.enableControllerService(kcs);
ugi = mock(UserGroupInformation.class);
when(hiveConfigurator.authenticate(eq(hiveConf), any(KerberosUser.class))).thenReturn(ugi);
@ -313,8 +310,10 @@ public class TestPutHive3Streaming {
runner.setProperty(PutHive3Streaming.HIVE_CONFIGURATION_RESOURCES, "src/test/resources/core-site-security.xml, src/test/resources/hive-site-security.xml");
hiveConf.set(SecurityUtil.HADOOP_SECURITY_AUTHENTICATION, SecurityUtil.KERBEROS);
KerberosCredentialsService kcs = new MockKerberosCredentialsService(null, null);
KerberosCredentialsService kcs = new MockKerberosCredentialsService();
runner.addControllerService("kcs", kcs);
runner.setProperty(kcs, MockKerberosCredentialsService.PRINCIPAL, "test");
runner.setProperty(kcs, MockKerberosCredentialsService.KEYTAB, "src/test/resources/core-site-security.xml");
runner.setProperty(KERBEROS_CREDENTIALS_SERVICE, "kcs");
runner.enableControllerService(kcs);
runner.assertNotValid();
@ -1321,58 +1320,4 @@ public class TestPutHive3Streaming {
return null;
}
}
private static class MockKerberosCredentialsService implements KerberosCredentialsService, ControllerService {
private String keytab = "src/test/resources/fake.keytab";
private String principal = "test@REALM.COM";
public MockKerberosCredentialsService() {
}
public MockKerberosCredentialsService(String keytab, String principal) {
this.keytab = keytab;
this.principal = principal;
}
@Override
public String getKeytab() {
return keytab;
}
@Override
public String getPrincipal() {
return principal;
}
@Override
public void initialize(ControllerServiceInitializationContext context) throws InitializationException {
}
@Override
public Collection<ValidationResult> validate(ValidationContext context) {
return Collections.EMPTY_LIST;
}
@Override
public PropertyDescriptor getPropertyDescriptor(String name) {
return null;
}
@Override
public void onPropertyModified(PropertyDescriptor descriptor, String oldValue, String newValue) {
}
@Override
public List<PropertyDescriptor> getPropertyDescriptors() {
return null;
}
@Override
public String getIdentifier() {
return "kcs";
}
}
}

View File

@ -114,9 +114,28 @@
<version>1.4.192</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-kerberos-test-utils</artifactId>
<version>1.13.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.rat</groupId>
<artifactId>apache-rat-plugin</artifactId>
<configuration>
<excludes combine.children="append">
<exclude>src/test/resources/fake.keytab</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -513,6 +513,15 @@ public class DBCPConnectionPool extends AbstractControllerService implements DBC
}
return con;
} catch (final SQLException e) {
// If using Kerberos, attempt to re-login
if (kerberosUser != null) {
try {
getLogger().info("Error getting connection, performing Kerberos re-login");
kerberosUser.login();
} catch (LoginException le) {
throw new ProcessException("Unable to authenticate Kerberos principal", le);
}
}
throw new ProcessException(e);
}
}

View File

@ -17,6 +17,8 @@
package org.apache.nifi.dbcp;
import org.apache.derby.drda.NetworkServerControl;
import org.apache.nifi.kerberos.KerberosCredentialsService;
import org.apache.nifi.kerberos.MockKerberosCredentialsService;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.reporting.InitializationException;
import org.apache.nifi.util.TestRunner;
@ -304,6 +306,42 @@ public class DBCPServiceTest {
connection.close(); // return to pool
}
/**
* Test that we relogin to Kerberos if a ConnectException occurs during getConnection().
*/
@Test(expected = ProcessException.class)
public void testConnectExceptionCausesKerberosRelogin() throws InitializationException, SQLException {
final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class);
final DBCPConnectionPool service = new DBCPConnectionPool();
runner.addControllerService("test-good1", service);
final KerberosCredentialsService kerberosCredentialsService = new MockKerberosCredentialsService();
runner.addControllerService("kcs", kerberosCredentialsService);
runner.setProperty(kerberosCredentialsService, MockKerberosCredentialsService.PRINCIPAL, "bad@PRINCIPAL.COM");
runner.setProperty(kerberosCredentialsService, MockKerberosCredentialsService.KEYTAB, "src/test/resources/fake.keytab");
runner.enableControllerService(kerberosCredentialsService);
// set fake Derby database connection url
runner.setProperty(service, DBCPConnectionPool.DATABASE_URL, "jdbc:derby://localhost:1527/NoDB");
runner.setProperty(service, DBCPConnectionPool.DB_USER, "tester");
runner.setProperty(service, DBCPConnectionPool.DB_PASSWORD, "testerp");
// Use the client driver here rather than the embedded one, as it will generate a ConnectException for the test
runner.setProperty(service, DBCPConnectionPool.DB_DRIVERNAME, "org.apache.derby.jdbc.ClientDriver");
runner.setProperty(service, DBCPConnectionPool.KERBEROS_CREDENTIALS_SERVICE, "kcs");
try {
runner.enableControllerService(service);
} catch (AssertionError ae) {
// Ignore, this happens because it tries to do the initial Kerberos login
}
runner.assertValid(service);
final DBCPService dbcpService = (DBCPService) runner.getProcessContext().getControllerServiceLookup().getControllerService("test-good1");
Assert.assertNotNull(dbcpService);
dbcpService.getConnection();
}
@Rule
public ExpectedException exception = ExpectedException.none();