mirror of https://github.com/apache/nifi.git
NIFI-5945 Add support for password login to kerberos code in nifi-security-utils
Fixing solr test Signed-off-by: Matthew Burgess <mattyb149@apache.org> This closes #3256
This commit is contained in:
parent
cf7ab0ce18
commit
2bbfb3217b
|
@ -23,7 +23,6 @@ import org.slf4j.LoggerFactory;
|
||||||
import javax.security.auth.Subject;
|
import javax.security.auth.Subject;
|
||||||
import javax.security.auth.kerberos.KerberosPrincipal;
|
import javax.security.auth.kerberos.KerberosPrincipal;
|
||||||
import javax.security.auth.kerberos.KerberosTicket;
|
import javax.security.auth.kerberos.KerberosTicket;
|
||||||
import javax.security.auth.login.Configuration;
|
|
||||||
import javax.security.auth.login.LoginContext;
|
import javax.security.auth.login.LoginContext;
|
||||||
import javax.security.auth.login.LoginException;
|
import javax.security.auth.login.LoginException;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
|
@ -34,14 +33,9 @@ import java.util.Date;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
/**
|
public abstract class AbstractKerberosUser implements KerberosUser {
|
||||||
* Used to authenticate and execute actions when Kerberos is enabled and a keytab is being used.
|
|
||||||
*
|
|
||||||
* Some of the functionality in this class is adapted from Hadoop's UserGroupInformation.
|
|
||||||
*/
|
|
||||||
public class StandardKeytabUser implements KeytabUser {
|
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(StandardKeytabUser.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractKerberosUser.class);
|
||||||
|
|
||||||
static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
||||||
|
|
||||||
|
@ -50,18 +44,15 @@ public class StandardKeytabUser implements KeytabUser {
|
||||||
*/
|
*/
|
||||||
static final float TICKET_RENEW_WINDOW = 0.80f;
|
static final float TICKET_RENEW_WINDOW = 0.80f;
|
||||||
|
|
||||||
private final String principal;
|
protected final String principal;
|
||||||
private final String keytabFile;
|
protected final AtomicBoolean loggedIn = new AtomicBoolean(false);
|
||||||
private final AtomicBoolean loggedIn = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
private Subject subject;
|
protected Subject subject;
|
||||||
private LoginContext loginContext;
|
protected LoginContext loginContext;
|
||||||
|
|
||||||
public StandardKeytabUser(final String principal, final String keytabFile) {
|
public AbstractKerberosUser(final String principal) {
|
||||||
this.principal = principal;
|
this.principal = principal;
|
||||||
this.keytabFile = keytabFile;
|
Validate.notBlank(this.principal);
|
||||||
Validate.notBlank(principal);
|
|
||||||
Validate.notBlank(keytabFile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,19 +71,19 @@ public class StandardKeytabUser implements KeytabUser {
|
||||||
if (loginContext == null) {
|
if (loginContext == null) {
|
||||||
LOGGER.debug("Initializing new login context...");
|
LOGGER.debug("Initializing new login context...");
|
||||||
this.subject = new Subject();
|
this.subject = new Subject();
|
||||||
|
this.loginContext = createLoginContext(subject);
|
||||||
final Configuration config = new KeytabConfiguration(principal, keytabFile);
|
|
||||||
this.loginContext = new LoginContext("KeytabConf", subject, null, config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loginContext.login();
|
loginContext.login();
|
||||||
loggedIn.set(true);
|
loggedIn.set(true);
|
||||||
LOGGER.debug("Successful login for {}", new Object[]{principal});
|
LOGGER.debug("Successful login for {}", new Object[]{principal});
|
||||||
} catch (LoginException le) {
|
} catch (LoginException le) {
|
||||||
throw new LoginException("Unable to login with " + principal + " and " + keytabFile + " due to: " + le.getMessage());
|
throw new LoginException("Unable to login with " + principal + " due to: " + le.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract LoginContext createLoginContext(final Subject subject) throws LoginException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a logout of the current user.
|
* Performs a logout of the current user.
|
||||||
*
|
*
|
||||||
|
@ -244,14 +235,6 @@ public class StandardKeytabUser implements KeytabUser {
|
||||||
return principal;
|
return principal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the keytab file for this user
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getKeytabFile() {
|
|
||||||
return keytabFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Visible for testing
|
// Visible for testing
|
||||||
Subject getSubject() {
|
Subject getSubject() {
|
||||||
return this.subject;
|
return this.subject;
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* 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.krb;
|
||||||
|
|
||||||
|
public interface ConfigurationUtil {
|
||||||
|
|
||||||
|
boolean IS_IBM = System.getProperty("java.vendor", "").contains("IBM");
|
||||||
|
String IBM_KRB5_LOGIN_MODULE = "com.ibm.security.auth.module.Krb5LoginModule";
|
||||||
|
String SUN_KRB5_LOGIN_MODULE = "com.sun.security.auth.module.Krb5LoginModule";
|
||||||
|
|
||||||
|
}
|
|
@ -25,24 +25,24 @@ import javax.security.auth.login.LoginException;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class for processors to perform an action as a KeytabUser.
|
* Helper class for processors to perform an action as a KerberosUser.
|
||||||
*/
|
*/
|
||||||
public class KeytabAction {
|
public class KerberosAction {
|
||||||
|
|
||||||
private final KeytabUser keytabUser;
|
private final KerberosUser kerberosUser;
|
||||||
private final PrivilegedAction action;
|
private final PrivilegedAction action;
|
||||||
private final ProcessContext context;
|
private final ProcessContext context;
|
||||||
private final ComponentLog logger;
|
private final ComponentLog logger;
|
||||||
|
|
||||||
public KeytabAction(final KeytabUser keytabUser,
|
public KerberosAction(final KerberosUser kerberosUser,
|
||||||
final PrivilegedAction action,
|
final PrivilegedAction action,
|
||||||
final ProcessContext context,
|
final ProcessContext context,
|
||||||
final ComponentLog logger) {
|
final ComponentLog logger) {
|
||||||
this.keytabUser = keytabUser;
|
this.kerberosUser = kerberosUser;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
Validate.notNull(this.keytabUser);
|
Validate.notNull(this.kerberosUser);
|
||||||
Validate.notNull(this.action);
|
Validate.notNull(this.action);
|
||||||
Validate.notNull(this.context);
|
Validate.notNull(this.context);
|
||||||
Validate.notNull(this.logger);
|
Validate.notNull(this.logger);
|
||||||
|
@ -50,10 +50,10 @@ public class KeytabAction {
|
||||||
|
|
||||||
public void execute() {
|
public void execute() {
|
||||||
// lazily login the first time the processor executes
|
// lazily login the first time the processor executes
|
||||||
if (!keytabUser.isLoggedIn()) {
|
if (!kerberosUser.isLoggedIn()) {
|
||||||
try {
|
try {
|
||||||
keytabUser.login();
|
kerberosUser.login();
|
||||||
logger.info("Successful login for {}", new Object[]{keytabUser.getPrincipal()});
|
logger.info("Successful login for {}", new Object[]{kerberosUser.getPrincipal()});
|
||||||
} catch (LoginException e) {
|
} catch (LoginException e) {
|
||||||
// make sure to yield so the processor doesn't keep retrying the rolled back flow files immediately
|
// make sure to yield so the processor doesn't keep retrying the rolled back flow files immediately
|
||||||
context.yield();
|
context.yield();
|
||||||
|
@ -63,7 +63,7 @@ public class KeytabAction {
|
||||||
|
|
||||||
// check if we need to re-login, will only happen if re-login window is reached (80% of TGT life)
|
// check if we need to re-login, will only happen if re-login window is reached (80% of TGT life)
|
||||||
try {
|
try {
|
||||||
keytabUser.checkTGTAndRelogin();
|
kerberosUser.checkTGTAndRelogin();
|
||||||
} catch (LoginException e) {
|
} catch (LoginException e) {
|
||||||
// make sure to yield so the processor doesn't keep retrying the rolled back flow files immediately
|
// make sure to yield so the processor doesn't keep retrying the rolled back flow files immediately
|
||||||
context.yield();
|
context.yield();
|
||||||
|
@ -72,15 +72,15 @@ public class KeytabAction {
|
||||||
|
|
||||||
// attempt to execute the action, if an exception is caught attempt to logout/login and retry
|
// attempt to execute the action, if an exception is caught attempt to logout/login and retry
|
||||||
try {
|
try {
|
||||||
keytabUser.doAs(action);
|
kerberosUser.doAs(action);
|
||||||
} catch (SecurityException se) {
|
} catch (SecurityException se) {
|
||||||
logger.info("Privileged action failed, attempting relogin and retrying...");
|
logger.info("Privileged action failed, attempting relogin and retrying...");
|
||||||
logger.debug("", se);
|
logger.debug("", se);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
keytabUser.logout();
|
kerberosUser.logout();
|
||||||
keytabUser.login();
|
kerberosUser.login();
|
||||||
keytabUser.doAs(action);
|
kerberosUser.doAs(action);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// make sure to yield so the processor doesn't keep retrying the rolled back flow files immediately
|
// make sure to yield so the processor doesn't keep retrying the rolled back flow files immediately
|
||||||
context.yield();
|
context.yield();
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* 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.krb;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
|
import javax.security.auth.Subject;
|
||||||
|
import javax.security.auth.login.Configuration;
|
||||||
|
import javax.security.auth.login.LoginContext;
|
||||||
|
import javax.security.auth.login.LoginException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to authenticate and execute actions when Kerberos is enabled and a keytab is being used.
|
||||||
|
*
|
||||||
|
* Some of the functionality in this class is adapted from Hadoop's UserGroupInformation.
|
||||||
|
*/
|
||||||
|
public class KerberosKeytabUser extends AbstractKerberosUser {
|
||||||
|
|
||||||
|
private final String keytabFile;
|
||||||
|
|
||||||
|
public KerberosKeytabUser(final String principal, final String keytabFile) {
|
||||||
|
super(principal);
|
||||||
|
this.keytabFile = keytabFile;
|
||||||
|
Validate.notBlank(keytabFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected LoginContext createLoginContext(Subject subject) throws LoginException {
|
||||||
|
final Configuration config = new KeytabConfiguration(principal, keytabFile);
|
||||||
|
return new LoginContext("KeytabConf", subject, null, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the keytab file for this user
|
||||||
|
*/
|
||||||
|
public String getKeytabFile() {
|
||||||
|
return keytabFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visible for testing
|
||||||
|
Subject getSubject() {
|
||||||
|
return this.subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* 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.krb;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
|
import javax.security.auth.Subject;
|
||||||
|
import javax.security.auth.callback.Callback;
|
||||||
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
|
import javax.security.auth.callback.NameCallback;
|
||||||
|
import javax.security.auth.callback.PasswordCallback;
|
||||||
|
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||||
|
import javax.security.auth.login.AppConfigurationEntry;
|
||||||
|
import javax.security.auth.login.Configuration;
|
||||||
|
import javax.security.auth.login.LoginContext;
|
||||||
|
import javax.security.auth.login.LoginException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KerberosUser that authenticates via username and password instead of keytab.
|
||||||
|
*/
|
||||||
|
public class KerberosPasswordUser extends AbstractKerberosUser {
|
||||||
|
|
||||||
|
private final String password;
|
||||||
|
|
||||||
|
public KerberosPasswordUser(final String principal, final String password) {
|
||||||
|
super(principal);
|
||||||
|
this.password = password;
|
||||||
|
Validate.notBlank(this.password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected LoginContext createLoginContext(final Subject subject) throws LoginException {
|
||||||
|
final Configuration configuration = new PasswordConfig();
|
||||||
|
final CallbackHandler callbackHandler = new UsernamePasswordCallbackHandler(principal, password);
|
||||||
|
return new LoginContext("PasswordConf", subject, callbackHandler, configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JAAS Configuration to use when logging in with username/password.
|
||||||
|
*/
|
||||||
|
private static class PasswordConfig extends Configuration {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
|
||||||
|
HashMap<String, String> options = new HashMap<String, String>();
|
||||||
|
options.put("storeKey", "true");
|
||||||
|
options.put("refreshKrb5Config", "true");
|
||||||
|
|
||||||
|
final String krbLoginModuleName = ConfigurationUtil.IS_IBM
|
||||||
|
? ConfigurationUtil.IBM_KRB5_LOGIN_MODULE : ConfigurationUtil.SUN_KRB5_LOGIN_MODULE;
|
||||||
|
|
||||||
|
return new AppConfigurationEntry[] {
|
||||||
|
new AppConfigurationEntry(
|
||||||
|
krbLoginModuleName,
|
||||||
|
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CallbackHandler that provides the given username and password.
|
||||||
|
*/
|
||||||
|
private static class UsernamePasswordCallbackHandler implements CallbackHandler {
|
||||||
|
|
||||||
|
private final String username;
|
||||||
|
private final String password;
|
||||||
|
|
||||||
|
public UsernamePasswordCallbackHandler(final String username, final String password) {
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
Validate.notBlank(this.username);
|
||||||
|
Validate.notBlank(this.password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||||
|
for (final Callback callback : callbacks) {
|
||||||
|
if (callback instanceof NameCallback) {
|
||||||
|
final NameCallback nameCallback = (NameCallback) callback;
|
||||||
|
nameCallback.setName(username);
|
||||||
|
} else if (callback instanceof PasswordCallback) {
|
||||||
|
final PasswordCallback passwordCallback = (PasswordCallback) callback;
|
||||||
|
passwordCallback.setPassword(password.toCharArray());
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unexpected callback type: " + callback.getClass().getCanonicalName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ import java.security.PrivilegedExceptionAction;
|
||||||
/**
|
/**
|
||||||
* A keytab-based user that can login/logout and perform actions as the given user.
|
* A keytab-based user that can login/logout and perform actions as the given user.
|
||||||
*/
|
*/
|
||||||
public interface KeytabUser {
|
public interface KerberosUser {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a login for the given user.
|
* Performs a login for the given user.
|
||||||
|
@ -80,9 +80,4 @@ public interface KeytabUser {
|
||||||
*/
|
*/
|
||||||
String getPrincipal();
|
String getPrincipal();
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the keytab file for this user
|
|
||||||
*/
|
|
||||||
String getKeytabFile();
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -28,10 +28,6 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public class KeytabConfiguration extends Configuration {
|
public class KeytabConfiguration extends Configuration {
|
||||||
|
|
||||||
static final boolean IS_IBM = System.getProperty("java.vendor", "").contains("IBM");
|
|
||||||
static final String IBM_KRB5_LOGIN_MODULE = "com.ibm.security.auth.module.Krb5LoginModule";
|
|
||||||
static final String SUN_KRB5_LOGIN_MODULE = "com.sun.security.auth.module.Krb5LoginModule";
|
|
||||||
|
|
||||||
private final String principal;
|
private final String principal;
|
||||||
private final String keytabFile;
|
private final String keytabFile;
|
||||||
|
|
||||||
|
@ -53,7 +49,7 @@ public class KeytabConfiguration extends Configuration {
|
||||||
options.put("principal", principal);
|
options.put("principal", principal);
|
||||||
options.put("refreshKrb5Config", "true");
|
options.put("refreshKrb5Config", "true");
|
||||||
|
|
||||||
if (IS_IBM) {
|
if (ConfigurationUtil.IS_IBM) {
|
||||||
options.put("useKeytab", keytabFile);
|
options.put("useKeytab", keytabFile);
|
||||||
options.put("credsType", "both");
|
options.put("credsType", "both");
|
||||||
} else {
|
} else {
|
||||||
|
@ -64,7 +60,8 @@ public class KeytabConfiguration extends Configuration {
|
||||||
options.put("storeKey", "true");
|
options.put("storeKey", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
final String krbLoginModuleName = IS_IBM ? IBM_KRB5_LOGIN_MODULE : SUN_KRB5_LOGIN_MODULE;
|
final String krbLoginModuleName = ConfigurationUtil.IS_IBM
|
||||||
|
? ConfigurationUtil.IBM_KRB5_LOGIN_MODULE : ConfigurationUtil.SUN_KRB5_LOGIN_MODULE;
|
||||||
|
|
||||||
this.kerberosKeytabConfigEntry = new AppConfigurationEntry(
|
this.kerberosKeytabConfigEntry = new AppConfigurationEntry(
|
||||||
krbLoginModuleName, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
|
krbLoginModuleName, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
|
||||||
|
|
|
@ -67,8 +67,11 @@ public class KDCServer {
|
||||||
return kdc.getRealm();
|
return kdc.getRealm();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createKeytabFile(final File keytabFile, final String... names) throws Exception {
|
public void createKeytabPrincipal(final File keytabFile, final String... names) throws Exception {
|
||||||
kdc.createPrincipal(keytabFile, names);
|
kdc.createPrincipal(keytabFile, names);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void createPasswordPrincipal(final String principal, final String password) throws Exception {
|
||||||
|
kdc.createPrincipal(principal, password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
public class KeytabUserIT {
|
public class KerberosUserIT {
|
||||||
|
|
||||||
@ClassRule
|
@ClassRule
|
||||||
public static TemporaryFolder tmpDir = new TemporaryFolder();
|
public static TemporaryFolder tmpDir = new TemporaryFolder();
|
||||||
|
@ -50,6 +50,9 @@ public class KeytabUserIT {
|
||||||
private static KerberosPrincipal principal2;
|
private static KerberosPrincipal principal2;
|
||||||
private static File principal2KeytabFile;
|
private static File principal2KeytabFile;
|
||||||
|
|
||||||
|
private static KerberosPrincipal principal3;
|
||||||
|
private static final String principal3Password = "changeme";
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void setupClass() throws Exception {
|
public static void setupClass() throws Exception {
|
||||||
kdc = new KDCServer(tmpDir.newFolder("mini-kdc_"));
|
kdc = new KDCServer(tmpDir.newFolder("mini-kdc_"));
|
||||||
|
@ -58,31 +61,34 @@ public class KeytabUserIT {
|
||||||
|
|
||||||
principal1 = new KerberosPrincipal("user1@" + kdc.getRealm());
|
principal1 = new KerberosPrincipal("user1@" + kdc.getRealm());
|
||||||
principal1KeytabFile = tmpDir.newFile("user1.keytab");
|
principal1KeytabFile = tmpDir.newFile("user1.keytab");
|
||||||
kdc.createKeytabFile(principal1KeytabFile, "user1");
|
kdc.createKeytabPrincipal(principal1KeytabFile, "user1");
|
||||||
|
|
||||||
principal2 = new KerberosPrincipal("user2@" + kdc.getRealm());
|
principal2 = new KerberosPrincipal("user2@" + kdc.getRealm());
|
||||||
principal2KeytabFile = tmpDir.newFile("user2.keytab");
|
principal2KeytabFile = tmpDir.newFile("user2.keytab");
|
||||||
kdc.createKeytabFile(principal2KeytabFile, "user2");
|
kdc.createKeytabPrincipal(principal2KeytabFile, "user2");
|
||||||
|
|
||||||
|
principal3 = new KerberosPrincipal("user3@" + kdc.getRealm());
|
||||||
|
kdc.createPasswordPrincipal("user3", principal3Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSuccessfulLoginAndLogout() throws LoginException {
|
public void testKeytabUserSuccessfulLoginAndLogout() throws LoginException {
|
||||||
// perform login for user1
|
// perform login for user1
|
||||||
final KeytabUser user1 = new StandardKeytabUser(principal1.getName(), principal1KeytabFile.getAbsolutePath());
|
final KerberosUser user1 = new KerberosKeytabUser(principal1.getName(), principal1KeytabFile.getAbsolutePath());
|
||||||
user1.login();
|
user1.login();
|
||||||
|
|
||||||
// perform login for user2
|
// perform login for user2
|
||||||
final KeytabUser user2 = new StandardKeytabUser(principal2.getName(), principal2KeytabFile.getAbsolutePath());
|
final KerberosUser user2 = new KerberosKeytabUser(principal2.getName(), principal2KeytabFile.getAbsolutePath());
|
||||||
user2.login();
|
user2.login();
|
||||||
|
|
||||||
// verify user1 Subject only has user1 principal
|
// verify user1 Subject only has user1 principal
|
||||||
final Subject user1Subject = ((StandardKeytabUser) user1).getSubject();
|
final Subject user1Subject = ((KerberosKeytabUser) user1).getSubject();
|
||||||
final Set<Principal> user1SubjectPrincipals = user1Subject.getPrincipals();
|
final Set<Principal> user1SubjectPrincipals = user1Subject.getPrincipals();
|
||||||
assertEquals(1, user1SubjectPrincipals.size());
|
assertEquals(1, user1SubjectPrincipals.size());
|
||||||
assertEquals(principal1.getName(), user1SubjectPrincipals.iterator().next().getName());
|
assertEquals(principal1.getName(), user1SubjectPrincipals.iterator().next().getName());
|
||||||
|
|
||||||
// verify user2 Subject only has user2 principal
|
// verify user2 Subject only has user2 principal
|
||||||
final Subject user2Subject = ((StandardKeytabUser) user2).getSubject();
|
final Subject user2Subject = ((KerberosKeytabUser) user2).getSubject();
|
||||||
final Set<Principal> user2SubjectPrincipals = user2Subject.getPrincipals();
|
final Set<Principal> user2SubjectPrincipals = user2Subject.getPrincipals();
|
||||||
assertEquals(1, user2SubjectPrincipals.size());
|
assertEquals(1, user2SubjectPrincipals.size());
|
||||||
assertEquals(principal2.getName(), user2SubjectPrincipals.iterator().next().getName());
|
assertEquals(principal2.getName(), user2SubjectPrincipals.iterator().next().getName());
|
||||||
|
@ -101,9 +107,9 @@ public class KeytabUserIT {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLoginWithUnknownPrincipal() throws LoginException {
|
public void testKeytabLoginWithUnknownPrincipal() throws LoginException {
|
||||||
final String unknownPrincipal = "doesnotexist@" + kdc.getRealm();
|
final String unknownPrincipal = "doesnotexist@" + kdc.getRealm();
|
||||||
final KeytabUser user1 = new StandardKeytabUser(unknownPrincipal, principal1KeytabFile.getAbsolutePath());
|
final KerberosUser user1 = new KerberosKeytabUser(unknownPrincipal, principal1KeytabFile.getAbsolutePath());
|
||||||
try {
|
try {
|
||||||
user1.login();
|
user1.login();
|
||||||
fail("Login should have failed");
|
fail("Login should have failed");
|
||||||
|
@ -113,9 +119,38 @@ public class KeytabUserIT {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPasswordUserSuccessfulLoginAndLogout() throws LoginException {
|
||||||
|
// perform login for user
|
||||||
|
final KerberosUser user = new KerberosPasswordUser(principal3.getName(), principal3Password);
|
||||||
|
user.login();
|
||||||
|
|
||||||
|
// verify user Subject only has user principal
|
||||||
|
final Subject userSubject = ((KerberosPasswordUser) user).getSubject();
|
||||||
|
final Set<Principal> userSubjectPrincipals = userSubject.getPrincipals();
|
||||||
|
assertEquals(1, userSubjectPrincipals.size());
|
||||||
|
assertEquals(principal3.getName(), userSubjectPrincipals.iterator().next().getName());
|
||||||
|
|
||||||
|
// call check/relogin and verify neither user performed a relogin
|
||||||
|
assertFalse(user.checkTGTAndRelogin());
|
||||||
|
|
||||||
|
// perform logout for both users
|
||||||
|
user.logout();
|
||||||
|
|
||||||
|
// verify subjects have no more principals
|
||||||
|
assertEquals(0, userSubject.getPrincipals().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = LoginException.class)
|
||||||
|
public void testPasswordUserLoginWithInvalidPassword() throws LoginException {
|
||||||
|
// perform login for user
|
||||||
|
final KerberosUser user = new KerberosPasswordUser("user3", "NOT THE PASSWORD");
|
||||||
|
user.login();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCheckTGTAndRelogin() throws LoginException, InterruptedException {
|
public void testCheckTGTAndRelogin() throws LoginException, InterruptedException {
|
||||||
final KeytabUser user1 = new StandardKeytabUser(principal1.getName(), principal1KeytabFile.getAbsolutePath());
|
final KerberosUser user1 = new KerberosKeytabUser(principal1.getName(), principal1KeytabFile.getAbsolutePath());
|
||||||
user1.login();
|
user1.login();
|
||||||
|
|
||||||
// Since we set the lifetime to 15 seconds we should hit a relogin before 15 attempts
|
// Since we set the lifetime to 15 seconds we should hit a relogin before 15 attempts
|
||||||
|
@ -136,7 +171,7 @@ public class KeytabUserIT {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeytabAction() {
|
public void testKeytabAction() {
|
||||||
final KeytabUser user1 = new StandardKeytabUser(principal1.getName(), principal1KeytabFile.getAbsolutePath());
|
final KerberosUser user1 = new KerberosKeytabUser(principal1.getName(), principal1KeytabFile.getAbsolutePath());
|
||||||
|
|
||||||
final AtomicReference<String> resultHolder = new AtomicReference<>(null);
|
final AtomicReference<String> resultHolder = new AtomicReference<>(null);
|
||||||
final PrivilegedAction privilegedAction = () -> {
|
final PrivilegedAction privilegedAction = () -> {
|
||||||
|
@ -148,8 +183,8 @@ public class KeytabUserIT {
|
||||||
final ComponentLog logger = Mockito.mock(ComponentLog.class);
|
final ComponentLog logger = Mockito.mock(ComponentLog.class);
|
||||||
|
|
||||||
// create the action to test and execute it
|
// create the action to test and execute it
|
||||||
final KeytabAction keytabAction = new KeytabAction(user1, privilegedAction, context, logger);
|
final KerberosAction kerberosAction = new KerberosAction(user1, privilegedAction, context, logger);
|
||||||
keytabAction.execute();
|
kerberosAction.execute();
|
||||||
|
|
||||||
// if the result holder has the string success then we know the action executed
|
// if the result holder has the string success then we know the action executed
|
||||||
assertEquals("SUCCESS", resultHolder.get());
|
assertEquals("SUCCESS", resultHolder.get());
|
|
@ -39,7 +39,7 @@ public class TestKeytabConfiguration {
|
||||||
assertEquals(1, entries.length);
|
assertEquals(1, entries.length);
|
||||||
|
|
||||||
final AppConfigurationEntry entry = entries[0];
|
final AppConfigurationEntry entry = entries[0];
|
||||||
assertEquals(KeytabConfiguration.SUN_KRB5_LOGIN_MODULE, entry.getLoginModuleName());
|
assertEquals(ConfigurationUtil.SUN_KRB5_LOGIN_MODULE, entry.getLoginModuleName());
|
||||||
assertEquals(principal, entry.getOptions().get("principal"));
|
assertEquals(principal, entry.getOptions().get("principal"));
|
||||||
assertEquals(keytab, entry.getOptions().get("keyTab"));
|
assertEquals(keytab, entry.getOptions().get("keyTab"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,9 +28,9 @@ import org.apache.nifi.processor.AbstractProcessor;
|
||||||
import org.apache.nifi.processor.ProcessContext;
|
import org.apache.nifi.processor.ProcessContext;
|
||||||
import org.apache.nifi.processor.ProcessSession;
|
import org.apache.nifi.processor.ProcessSession;
|
||||||
import org.apache.nifi.processor.exception.ProcessException;
|
import org.apache.nifi.processor.exception.ProcessException;
|
||||||
import org.apache.nifi.security.krb.KeytabAction;
|
import org.apache.nifi.security.krb.KerberosAction;
|
||||||
import org.apache.nifi.security.krb.KeytabUser;
|
import org.apache.nifi.security.krb.KerberosUser;
|
||||||
import org.apache.nifi.security.krb.StandardKeytabUser;
|
import org.apache.nifi.security.krb.KerberosKeytabUser;
|
||||||
import org.apache.nifi.ssl.SSLContextService;
|
import org.apache.nifi.ssl.SSLContextService;
|
||||||
import org.apache.solr.client.solrj.SolrClient;
|
import org.apache.solr.client.solrj.SolrClient;
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ public abstract class SolrProcessor extends AbstractProcessor {
|
||||||
private volatile String basicPassword;
|
private volatile String basicPassword;
|
||||||
private volatile boolean basicAuthEnabled = false;
|
private volatile boolean basicAuthEnabled = false;
|
||||||
|
|
||||||
private volatile KeytabUser keytabUser;
|
private volatile KerberosUser kerberosUser;
|
||||||
|
|
||||||
@OnScheduled
|
@OnScheduled
|
||||||
public final void onScheduled(final ProcessContext context) throws IOException {
|
public final void onScheduled(final ProcessContext context) throws IOException {
|
||||||
|
@ -78,12 +78,12 @@ public abstract class SolrProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
final KerberosCredentialsService kerberosCredentialsService = context.getProperty(KERBEROS_CREDENTIALS_SERVICE).asControllerService(KerberosCredentialsService.class);
|
final KerberosCredentialsService kerberosCredentialsService = context.getProperty(KERBEROS_CREDENTIALS_SERVICE).asControllerService(KerberosCredentialsService.class);
|
||||||
if (kerberosCredentialsService != null) {
|
if (kerberosCredentialsService != null) {
|
||||||
this.keytabUser = createKeytabUser(kerberosCredentialsService);
|
this.kerberosUser = createKeytabUser(kerberosCredentialsService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected KeytabUser createKeytabUser(final KerberosCredentialsService kerberosCredentialsService) {
|
protected KerberosUser createKeytabUser(final KerberosCredentialsService kerberosCredentialsService) {
|
||||||
return new StandardKeytabUser(kerberosCredentialsService.getPrincipal(), kerberosCredentialsService.getKeytab());
|
return new KerberosKeytabUser(kerberosCredentialsService.getPrincipal(), kerberosCredentialsService.getKeytab());
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnStopped
|
@OnStopped
|
||||||
|
@ -96,10 +96,10 @@ public abstract class SolrProcessor extends AbstractProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keytabUser != null) {
|
if (kerberosUser != null) {
|
||||||
try {
|
try {
|
||||||
keytabUser.logout();
|
kerberosUser.logout();
|
||||||
keytabUser = null;
|
kerberosUser = null;
|
||||||
} catch (LoginException e) {
|
} catch (LoginException e) {
|
||||||
getLogger().debug("Error logging out keytab user", e);
|
getLogger().debug("Error logging out keytab user", e);
|
||||||
}
|
}
|
||||||
|
@ -108,8 +108,8 @@ public abstract class SolrProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException {
|
public final void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException {
|
||||||
final KeytabUser keytabUser = getKerberosKeytabUser();
|
final KerberosUser kerberosUser = getKerberosKeytabUser();
|
||||||
if (keytabUser == null) {
|
if (kerberosUser == null) {
|
||||||
doOnTrigger(context, session);
|
doOnTrigger(context, session);
|
||||||
} else {
|
} else {
|
||||||
// wrap doOnTrigger in a privileged action
|
// wrap doOnTrigger in a privileged action
|
||||||
|
@ -119,8 +119,8 @@ public abstract class SolrProcessor extends AbstractProcessor {
|
||||||
};
|
};
|
||||||
|
|
||||||
// execute the privileged action as the given keytab user
|
// execute the privileged action as the given keytab user
|
||||||
final KeytabAction keytabAction = new KeytabAction(keytabUser, action, context, getLogger());
|
final KerberosAction kerberosAction = new KerberosAction(kerberosUser, action, context, getLogger());
|
||||||
keytabAction.execute();
|
kerberosAction.execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,8 +168,8 @@ public abstract class SolrProcessor extends AbstractProcessor {
|
||||||
return basicAuthEnabled;
|
return basicAuthEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final KeytabUser getKerberosKeytabUser() {
|
protected final KerberosUser getKerberosKeytabUser() {
|
||||||
return keytabUser;
|
return kerberosUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -21,7 +21,8 @@ import org.apache.nifi.kerberos.KerberosCredentialsService;
|
||||||
import org.apache.nifi.processor.ProcessContext;
|
import org.apache.nifi.processor.ProcessContext;
|
||||||
import org.apache.nifi.processor.exception.ProcessException;
|
import org.apache.nifi.processor.exception.ProcessException;
|
||||||
import org.apache.nifi.reporting.InitializationException;
|
import org.apache.nifi.reporting.InitializationException;
|
||||||
import org.apache.nifi.security.krb.KeytabUser;
|
import org.apache.nifi.security.krb.KerberosKeytabUser;
|
||||||
|
import org.apache.nifi.security.krb.KerberosUser;
|
||||||
import org.apache.nifi.ssl.SSLContextService;
|
import org.apache.nifi.ssl.SSLContextService;
|
||||||
import org.apache.nifi.util.TestRunner;
|
import org.apache.nifi.util.TestRunner;
|
||||||
import org.apache.nifi.util.TestRunners;
|
import org.apache.nifi.util.TestRunners;
|
||||||
|
@ -451,10 +452,10 @@ public class TestPutSolrContentStream {
|
||||||
runner.assertValid();
|
runner.assertValid();
|
||||||
|
|
||||||
proc.onScheduled(runner.getProcessContext());
|
proc.onScheduled(runner.getProcessContext());
|
||||||
final KeytabUser keytabUser = proc.getMockKerberosKeytabUser();
|
final KerberosUser kerberosUser = proc.getMockKerberosKeytabUser();;
|
||||||
Assert.assertNotNull(keytabUser);
|
Assert.assertNotNull(kerberosUser);
|
||||||
Assert.assertEquals(principal, keytabUser.getPrincipal());
|
Assert.assertEquals(principal, kerberosUser.getPrincipal());
|
||||||
Assert.assertEquals(keytab, keytabUser.getKeytabFile());
|
Assert.assertEquals(keytab, ((KerberosKeytabUser)kerberosUser).getKeytabFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -462,20 +463,20 @@ public class TestPutSolrContentStream {
|
||||||
final String principal = "nifi@FOO.COM";
|
final String principal = "nifi@FOO.COM";
|
||||||
final String keytab = "src/test/resources/foo.keytab";
|
final String keytab = "src/test/resources/foo.keytab";
|
||||||
|
|
||||||
// Setup a mock KeytabUser that will still execute the privileged action
|
// Setup a mock KerberosUser that will still execute the privileged action
|
||||||
final KeytabUser keytabUser = Mockito.mock(KeytabUser.class);
|
final KerberosKeytabUser kerberosUser = Mockito.mock(KerberosKeytabUser.class);
|
||||||
when(keytabUser.getPrincipal()).thenReturn(principal);
|
when(kerberosUser.getPrincipal()).thenReturn(principal);
|
||||||
when(keytabUser.getKeytabFile()).thenReturn(keytab);
|
when(kerberosUser.getKeytabFile()).thenReturn(keytab);
|
||||||
when(keytabUser.doAs(any(PrivilegedAction.class))).thenAnswer((invocation -> {
|
when(kerberosUser.doAs(any(PrivilegedAction.class))).thenAnswer((invocation -> {
|
||||||
final PrivilegedAction action = (PrivilegedAction) invocation.getArguments()[0];
|
final PrivilegedAction action = (PrivilegedAction) invocation.getArguments()[0];
|
||||||
action.run();
|
action.run();
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Configure the processor with the mock KeytabUser and with a credentials service
|
// Configure the processor with the mock KerberosUser and with a credentials service
|
||||||
final SolrClient solrClient = createEmbeddedSolrClient(DEFAULT_SOLR_CORE);
|
final SolrClient solrClient = createEmbeddedSolrClient(DEFAULT_SOLR_CORE);
|
||||||
final TestableProcessor proc = new TestableProcessor(solrClient, keytabUser);
|
final TestableProcessor proc = new TestableProcessor(solrClient, kerberosUser);
|
||||||
final TestRunner runner = createDefaultTestRunner(proc);
|
final TestRunner runner = createDefaultTestRunner(proc);
|
||||||
|
|
||||||
final KerberosCredentialsService kerberosCredentialsService = new MockKerberosCredentialsService(principal, keytab);
|
final KerberosCredentialsService kerberosCredentialsService = new MockKerberosCredentialsService(principal, keytab);
|
||||||
|
@ -499,9 +500,9 @@ public class TestPutSolrContentStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that during the update the user was logged in, TGT was checked, and the action was executed
|
// Verify that during the update the user was logged in, TGT was checked, and the action was executed
|
||||||
verify(keytabUser, times(1)).login();
|
verify(kerberosUser, times(1)).login();
|
||||||
verify(keytabUser, times(1)).checkTGTAndRelogin();
|
verify(kerberosUser, times(1)).checkTGTAndRelogin();
|
||||||
verify(keytabUser, times(1)).doAs(any(PrivilegedAction.class));
|
verify(kerberosUser, times(1)).doAs(any(PrivilegedAction.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -647,15 +648,15 @@ public class TestPutSolrContentStream {
|
||||||
// Override createSolrClient and return the passed in SolrClient
|
// Override createSolrClient and return the passed in SolrClient
|
||||||
private class TestableProcessor extends PutSolrContentStream {
|
private class TestableProcessor extends PutSolrContentStream {
|
||||||
private SolrClient solrClient;
|
private SolrClient solrClient;
|
||||||
private KeytabUser keytabUser;
|
private KerberosUser kerberosUser;
|
||||||
|
|
||||||
public TestableProcessor(SolrClient solrClient) {
|
public TestableProcessor(SolrClient solrClient) {
|
||||||
this.solrClient = solrClient;
|
this.solrClient = solrClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestableProcessor(SolrClient solrClient, KeytabUser keytabUser) {
|
public TestableProcessor(SolrClient solrClient, KerberosUser kerberosUser) {
|
||||||
this.solrClient = solrClient;
|
this.solrClient = solrClient;
|
||||||
this.keytabUser = keytabUser;
|
this.kerberosUser = kerberosUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -664,15 +665,15 @@ public class TestPutSolrContentStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected KeytabUser createKeytabUser(KerberosCredentialsService kerberosCredentialsService) {
|
protected KerberosUser createKeytabUser(KerberosCredentialsService kerberosCredentialsService) {
|
||||||
if (keytabUser != null) {
|
if (kerberosUser != null) {
|
||||||
return keytabUser;
|
return kerberosUser;
|
||||||
} else {
|
} else {
|
||||||
return super.createKeytabUser(kerberosCredentialsService);
|
return super.createKeytabUser(kerberosCredentialsService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeytabUser getMockKerberosKeytabUser() {
|
public KerberosUser getMockKerberosKeytabUser() {
|
||||||
return super.getKerberosKeytabUser();
|
return super.getKerberosKeytabUser();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue