Я написал код для проверки клиентского билета Kerberos на моем сервере. Я также написал модульные тесты для своих классов. Модульные тесты написаны путем насмешек над вызовами классов библиотеки GSS. Это не вселяет в меня достаточной уверенности, поскольку фактические вызовы GSS являются поддельными.
Из моих исследований я понял, что для проверки токена клиента мне нужно будет расшифровать его с помощью общего ключа, который у меня есть с KDC, который я могу получить из файла keytab. Итак, для того, чтобы выполнить проверку, мне нужно две вещи (Подлежит исправлению):
- токен клиента
- Файл keytab на сервере
Теперь, если у меня есть эти файлы в моем classpath, могу ли я выполнить фактическую проверку токена, без каких-либо ложных вызовов? Есть ли какие-либо технические проблемы при этом? Если да, то каковы они?
Обновление 1:
Кажется, мне нужно было также установить некоторые системные свойства, чтобы библиотеки GSS выбирали правильную область, kdc и т. Д. Поэтому, по сути, нам нужно 3 вещи:
- Билет Kerberos
- Файл keytab
- Системные свойства, соответствующие файлу keytab и тикету.
С этим я, похоже, смогу завершить тестовую работу, с проверкой, но только в течение 5 минут. :)
Ситуация такова: если я беру токен kerberos, недавно сгенерированный KDC, и помещаю его в свой тест, тест выполняется успешно, но через 5 минут начинает проваливаться, за исключением «Слишком большой перекос часов». Я изменил политику kerberos на KDC, чтобы сгенерировать никогда не истекающий билет, но ошибка сохраняется. Серебряная подкладка здесь в том, что теперь у меня есть подтверждение концепции, что подход работает.
Проблема сводится к тому, чтобы обойти ошибку «Слишком большой перекос часов».
Обновление 2:
Значение перекоса часов можно изменить, указав его в файле krb.conf. Это еще одно системное свойство, которое мне нужно было установить. С этим тест теперь работает от начала до конца. Пишу ответ сейчас.
Трассировка стека для ошибки перекоса часов:
Caused by: java.security.PrivilegedActionException: GSSException: Failure unspecified at GSS-API level (Mechanism level: Clock skew too great (37))
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.Subject.doAs(Subject.java:422)
at com.example.vidm.eks.request.KerberosTokenValidator.getPrincipalUserName(KerberosTokenValidator.java:91)
at com.example.vidm.eks.request.KerberosTokenValidator.lambda$validateToken$0(KerberosTokenValidator.java:80)
... 7 more
Caused by: GSSException: Failure unspecified at GSS-API level (Mechanism level: Clock skew too great (37))
at sun.security.jgss.krb5.Krb5Context.acceptSecContext(Krb5Context.java:856)
at sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:342)
at sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:285)
at sun.security.jgss.spnego.SpNegoContext.GSS_acceptSecContext(SpNegoContext.java:906)
at sun.security.jgss.spnego.SpNegoContext.acceptSecContext(SpNegoContext.java:556)
at sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:342)
at sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:285)
at com.example.vidm.eks.krb.KerberosValidateAction.run(KerberosValidateAction.java:47)
at com.example.vidm.eks.krb.KerberosValidateAction.run(KerberosValidateAction.java:22)
... 11 more
Caused by: KrbException: Clock skew too great (37)
at sun.security.krb5.KrbApReq.authenticate(KrbApReq.java:302)
at sun.security.krb5.KrbApReq.<init>(KrbApReq.java:149)
at sun.security.jgss.krb5.InitSecContextToken.<init>(InitSecContextToken.java:108)
at sun.security.jgss.krb5.Krb5Context.acceptSecContext(Krb5Context.java:829)
... 19 more
Код моего функционального теста:
public class KerberosTokenValidatorTest extends AbstractUnitTestBase {
public static final String NO_PRINCIPAL = null;
private String kerberosTicket;
public static final String USERNAME = "username";
private static final String REALM = "EXAMPLE.COM";
private static final String PRINCIPAL = USERNAME + "@" + REALM;
@BeforeClass
public void beforeClass(){
System.setProperty("java.security.krb5.kdc", "host/hw-99402.example.com");
System.setProperty("java.security.krb5.realm", "EXAMPLE.COM");
System.setProperty("javax.security.auth.useSubjectCredsOnly","false");
String confFile = String.format("/tmp/%s", RandomStringUtils.random(10));
try (InputStream is = this.getClass().getClassLoader().getResourceAsStream("testkrb.conf")) {
Files.copy(is, Paths.get(confFile));
} catch (IOException e) {
// An error occurred copying the resource
}
System.setProperty("java.security.krb5.conf", confFile);
}
@Test
public void myTest() throws IOException, GSSException, ExecutionException, InterruptedException {
KerberosTokenValidator kerberosTokenValidator = new KerberosTokenValidator();
String kticket = FileSystemUtils.loadClasspathResourceAsString("kerberosticket");
kerberosTokenValidator.validateToken(kticket, "hw-99402.example.com", "userPrincipalName").get();
}
}
Мой проверочный код:
private String getPrincipalUserName(String token1, String serverName) throws LoginException, PrivilegedActionException {
javax.security.auth.Subject serviceSubject = getServiceSubject(serverName);
byte[] token = base64Decoder.decode(token1);
KerberosTicketValidation ticketValidation = javax.security.auth.Subject.doAs(serviceSubject, new KerberosValidateAction(token));
String kdcPrincipal = ticketValidation.getUsername();
if (StringUtils.isBlank(kdcPrincipal)) {
throw new LoginException("KDC principal is blank after ticket validation");
}
return kdcPrincipal;
}
private javax.security.auth.Subject getServiceSubject(String serverName) throws LoginException {
String servicePrincipal = SERVICE_PRINCIPAL_SERVICE + "/" + serverName;
final Set<Principal> princ = new HashSet<>(1);
princ.add(new KerberosPrincipal(servicePrincipal));
javax.security.auth.Subject sub = new javax.security.auth.Subject(false, princ, Collections.emptySet(), Collections.emptySet());
KerberosConfig kerberosConfig = new KerberosConfig(KEYTAB_PATH, servicePrincipal);
LoginContext lc = new LoginContext("", sub, null, kerberosConfig);
lc.login();
return lc.getSubject();
}
Мой юнит-тест:
@BeforeMethod
public void setup() throws Exception {
reset(mockGSSContext, mockGSSManager, mockGSSName);
mockGSSManager();
}
@InjectMocks
private KerberosTokenValidator kerberosTokenValidator;
@Mock protected GSSManager mockGSSManager;
@Mock protected GSSContext mockGSSContext;
@Mock protected GSSName mockGSSName;
@Test
public void canValidateKerberosToken() throws Throwable {
when(mockGSSName.toString()).thenReturn(PRINCIPAL);
Subject subject = blockAndThrow(kerberosTokenValidator.validateToken(kerberosTicket, "hw-99402.vidmlabs.com", "sAMAccountName"));
Assert.assertEquals(subject.getNameId(), USERNAME);
}
private void mockGSSManager() throws Exception {
when(mockGSSManager.createContext((GSSCredential) null)).thenReturn(mockGSSContext);
when(mockGSSContext.isEstablished()).thenReturn(true);
when(mockGSSContext.acceptSecContext(any(byte[].class), anyInt(), anyInt())).thenReturn(null);
when(mockGSSContext.getSrcName()).thenReturn(mockGSSName);
KerberosValidateAction.setGssManager(mockGSSManager);
}
KerberosValidateAction:
public class KerberosValidateAction implements PrivilegedExceptionAction<KerberosTicketValidation> {
private static GSSManager gssManager = GSSManager.getInstance();
private byte[] kerberosTicket;
private GSSCredential serviceCredentials;
public KerberosValidateAction(byte[] kerberosTicket) {
this(kerberosTicket, null);
}
public KerberosValidateAction(byte[] kerberosTicket, GSSCredential serviceCredentials) {
this.kerberosTicket = kerberosTicket;
this.serviceCredentials = serviceCredentials;
}
@VisibleForTesting
public static void setGssManager(GSSManager manager) {
gssManager = manager;
}
@Override
public KerberosTicketValidation run() throws Exception {
GSSName gssName = null;
GSSContext context = gssManager.createContext(serviceCredentials);
byte[] token = context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
if (!context.isEstablished()) {
throw new ContinueNeededException(token);
}
gssName = context.getSrcName();
if (gssName == null) {
throw new AuthenticationException("GSSContext name of the context initiator is null");
}
context.dispose();
return new KerberosTicketValidation(gssName.toString());
}
}
Файл krb5.conf:
[libdefaults]
clockskew = 999999999