blob: a7a3cb1032743c6c971637e85cab841f21d1722c [file] [log] [blame]
/*
* 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.nifi.logging.ComponentLog;
import org.apache.nifi.processor.ProcessContext;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mockito;
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosPrincipal;
import javax.security.auth.kerberos.KerberosTicket;
import javax.security.auth.login.LoginException;
import java.io.File;
import java.nio.file.Path;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class KerberosUserIT {
private static KDCServer kdc;
private static KerberosPrincipal principal1;
private static File principal1KeytabFile;
private static KerberosPrincipal principal2;
private static File principal2KeytabFile;
private static KerberosPrincipal principal3;
private static final String principal3Password = "changeme";
@BeforeAll
public static void setupClass(@TempDir Path tmpDir) throws Exception {
File kdcFolder = tmpDir.resolve("mini-kdc_").toFile();
kdcFolder.mkdirs();
kdc = new KDCServer(kdcFolder);
kdc.setMaxTicketLifetime("15"); // set ticket lifetime to 15 seconds so we can test relogin
kdc.start();
principal1 = new KerberosPrincipal("user1@" + kdc.getRealm());
principal1KeytabFile = tmpDir.resolve("user1.keytab").toFile();
kdc.createKeytabPrincipal(principal1KeytabFile, "user1");
principal2 = new KerberosPrincipal("user2@" + kdc.getRealm());
principal2KeytabFile = tmpDir.resolve("user2.keytab").toFile();
kdc.createKeytabPrincipal(principal2KeytabFile, "user2");
principal3 = new KerberosPrincipal("user3@" + kdc.getRealm());
kdc.createPasswordPrincipal("user3", principal3Password);
}
@Test
public void testKeytabUserSuccessfulLoginAndLogout() throws LoginException {
// perform login for user1
final KerberosUser user1 = new KerberosKeytabUser(principal1.getName(), principal1KeytabFile.getAbsolutePath());
user1.login();
// perform login for user2
final KerberosUser user2 = new KerberosKeytabUser(principal2.getName(), principal2KeytabFile.getAbsolutePath());
user2.login();
// verify user1 Subject only has user1 principal
final Subject user1Subject = ((KerberosKeytabUser) user1).getSubject();
final Set<Principal> user1SubjectPrincipals = user1Subject.getPrincipals();
assertEquals(1, user1SubjectPrincipals.size());
assertEquals(principal1.getName(), user1SubjectPrincipals.iterator().next().getName());
// verify user2 Subject only has user2 principal
final Subject user2Subject = ((KerberosKeytabUser) user2).getSubject();
final Set<Principal> user2SubjectPrincipals = user2Subject.getPrincipals();
assertEquals(1, user2SubjectPrincipals.size());
assertEquals(principal2.getName(), user2SubjectPrincipals.iterator().next().getName());
// call check/relogin and verify neither user performed a relogin
assertFalse(user1.checkTGTAndRelogin());
assertFalse(user2.checkTGTAndRelogin());
// perform logout for both users
user1.logout();
user2.logout();
// verify subjects have no more principals
assertEquals(0, user1Subject.getPrincipals().size());
assertEquals(0, user2Subject.getPrincipals().size());
}
@Test
public void testKeytabLoginWithUnknownPrincipal() {
final String unknownPrincipal = "doesnotexist@" + kdc.getRealm();
final KerberosUser user1 = new KerberosKeytabUser(unknownPrincipal, principal1KeytabFile.getAbsolutePath());
assertThrows(Exception.class, () -> user1.login());
}
@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
public void testPasswordUserLoginWithInvalidPassword() {
// perform login for user
final KerberosUser user = new KerberosPasswordUser("user3", "NOT THE PASSWORD");
assertThrows(LoginException.class, () -> user.login());
}
@Test
public void testCheckTGTAndRelogin() throws LoginException, InterruptedException {
final KerberosUser user1 = new KerberosKeytabUser(principal1.getName(), principal1KeytabFile.getAbsolutePath());
user1.login();
// Since we set the lifetime to 15 seconds we should hit a relogin before 15 attempts
boolean performedRelogin = false;
for (int i=0; i < 30; i++) {
Thread.sleep(1000);
System.out.println("checkTGTAndRelogin #" + i);
performedRelogin = user1.checkTGTAndRelogin();
if (performedRelogin) {
System.out.println("Performed relogin!");
break;
}
}
assertEquals(true, performedRelogin);
Subject subject = user1.doAs((PrivilegedAction<Subject>) () -> {
AccessControlContext context = AccessController.getContext();
return Subject.getSubject(context);
});
// verify only a single KerberosTicket exists in the Subject after relogin
Set<KerberosTicket> kerberosTickets = subject.getPrivateCredentials(KerberosTicket.class);
assertEquals(1, kerberosTickets.size());
// verify the new ticket lifetime is valid for the current time
KerberosTicket kerberosTicket = kerberosTickets.iterator().next();
long currentTimeMillis = System.currentTimeMillis();
long startMilli = kerberosTicket.getStartTime().toInstant().toEpochMilli();
long endMilli = kerberosTicket.getEndTime().toInstant().toEpochMilli();
System.out.println("New ticket is valid for " + TimeUnit.MILLISECONDS.toSeconds(endMilli - startMilli) + " seconds");
assertTrue(startMilli < currentTimeMillis);
assertTrue(endMilli > currentTimeMillis);
}
@Test
public void testKeytabAction() {
final KerberosUser user1 = new KerberosKeytabUser(principal1.getName(), principal1KeytabFile.getAbsolutePath());
final AtomicReference<String> resultHolder = new AtomicReference<>(null);
final PrivilegedExceptionAction<Void> privilegedAction = () -> {
resultHolder.set("SUCCESS");
return null;
};
final ProcessContext context = Mockito.mock(ProcessContext.class);
final ComponentLog logger = Mockito.mock(ComponentLog.class);
// create the action to test and execute it
final KerberosAction kerberosAction = new KerberosAction<>(user1, privilegedAction, logger);
kerberosAction.execute();
// if the result holder has the string success then we know the action executed
assertEquals("SUCCESS", resultHolder.get());
}
}