/** * Licensed 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.aurora.scheduler.http.api.security; import javax.inject.Inject; import javax.security.auth.kerberos.KerberosPrincipal; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.realm.Realm; import org.apache.shiro.subject.SimplePrincipalCollection; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; import static java.util.Objects.requireNonNull; /** * Authentication-only realm for Kerberos V5. */ class Kerberos5Realm implements Realm { private static final Splitter AT_SPLITTER = Splitter.on("@"); private final GSSManager gssManager; private final GSSCredential serverCredential; @Inject Kerberos5Realm(GSSManager gssManager, GSSCredential serverCredential) { this.gssManager = requireNonNull(gssManager); this.serverCredential = requireNonNull(serverCredential); } @Override public String getName() { return getClass().getName(); } @Override public boolean supports(AuthenticationToken token) { return token instanceof AuthorizeHeaderToken; } @Override public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { byte[] tokenFromInitiator = ((AuthorizeHeaderToken) token).getAuthorizeHeaderValue(); GSSContext context; try { context = gssManager.createContext(serverCredential); context.acceptSecContext(tokenFromInitiator, 0, tokenFromInitiator.length); } catch (GSSException e) { throw new AuthenticationException(e); } // Technically the GSS-API requires us to continue sending data back and forth in a loop // until the context is established, but we can short-circuit here since we know we're using // Kerberos V5 directly or Kerberos V5-backed SPNEGO. This is important because it means we // don't need to keep state between requests. // From http://docs.oracle.com/javase/7/docs/technotes/guides/security/jgss/single-signon.html // "In the case of the Kerberos V5 mechanism, there is no more than one round trip of // tokens during context establishment." if (context.isEstablished()) { try { KerberosPrincipal kerberosPrincipal = new KerberosPrincipal(context.getSrcName().toString()); return new SimpleAuthenticationInfo( new SimplePrincipalCollection( ImmutableList.of( // We assume there's a single Kerberos realm in use here. Most Authorizer // implementations care about the "simple" username instead of the full // principal. AT_SPLITTER.splitToList(kerberosPrincipal.getName()).get(0), kerberosPrincipal), getName()), null /* There are no credentials that can be cached. */); } catch (GSSException | IndexOutOfBoundsException e) { throw new AuthenticationException(e); } } else { throw new AuthenticationException("GSSContext was not established with a single message."); } } }