Я написал некоторый код для проверки билета kerberos клиента на моем сервере. Я также написал модульные тесты для своих классов. Модульные тесты пишутся путем имитации вызовов классов библиотеки GSS. Это не дает мне достаточной уверенности, поскольку фактические вызовы GSS высмеиваются.
Из моих исследований на данный момент я пришел к выводу, что для проверки токена клиента мне нужно расшифровать его с помощью общего ключа, который у меня есть с KDC, который я могу получить из файла keytab. Итак, чтобы выполнить проверку, мне нужны две вещи (подождите, чтобы их исправили):
- Токен клиента
- Keytab файл на сервере
Теперь, если у меня есть эти файлы в моем пути к классам, могу ли я выполнить фактическую проверку токена без каких-либо имитационных вызовов? Есть ли какие-либо технические проблемы при этом? Если да, то какие они?
Обновление 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
run()
, вы создаете новый контекст. Это не правильно. Вы должны сохранить его. В лучшем случае используйтеPriviledgedAction
только для получения учетных данных сервера. - person Michael-O   schedule 13.06.2019