blob: 74cb2d2aadfd8788b383d4481f45a900ab1d8681 [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.qpid.server.test;
import static java.lang.Boolean.TRUE;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.security.auth.DestroyFailedException;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.TextOutputCallback;
import javax.security.auth.kerberos.KerberosKey;
import javax.security.auth.kerberos.KerberosPrincipal;
import javax.security.auth.kerberos.KeyTab;
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 org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.qpid.server.util.FileUtils;
public class KerberosUtilities
{
private static final Logger LOGGER = LoggerFactory.getLogger(KerberosUtilities.class);
private static final String IBM_LOGIN_MODULE_CLASS = "com.ibm.security.auth.module.Krb5LoginModule";
private static final String SUN_LOGIN_MODULE_CLASS = "com.sun.security.auth.module.Krb5LoginModule";
public static final String KERBEROS_LOGIN_MODULE_CLASS =
System.getProperty("java.vendor").contains("IBM") ? IBM_LOGIN_MODULE_CLASS : SUN_LOGIN_MODULE_CLASS;
public byte[] buildToken(String clientPrincipalName, String targetServerPrincipalName) throws GSSException
{
debugConfig();
debug("Building token for client principal '{}' and server principal '{}'",
clientPrincipalName,
targetServerPrincipalName);
final GSSManager manager = GSSManager.getInstance();
final GSSName clientName = manager.createName(clientPrincipalName, GSSName.NT_USER_NAME);
final GSSCredential credential = manager.createCredential(clientName,
GSSCredential.DEFAULT_LIFETIME,
new Oid("1.2.840.113554.1.2.2"),
GSSCredential.INITIATE_ONLY);
debug("Client credential '{}'", credential);
final GSSName serverName = manager.createName(targetServerPrincipalName, GSSName.NT_USER_NAME);
final Oid spnegoMechOid = new Oid("1.3.6.1.5.5.2");
final GSSContext clientContext = manager.createContext(serverName.canonicalize(spnegoMechOid),
spnegoMechOid,
credential,
GSSContext.DEFAULT_LIFETIME);
debug("Requesting ticket using initiator's credentials");
try
{
clientContext.requestCredDeleg(true);
debug("Requesting ticket");
return clientContext.initSecContext(new byte[]{}, 0, 0);
}
catch (GSSException e)
{
debug("Failure to request token", e);
throw e;
}
finally
{
clientContext.dispose();
}
}
public LoginContext createKerberosKeyTabLoginContext(final String scopeName,
final String principalName,
final File keyTabFile)
throws LoginException
{
final KerberosPrincipal principal = new KerberosPrincipal(principalName);
final KeyTab keyTab = getKeyTab(principal, keyTabFile);
final Subject subject = new Subject(false,
Collections.singleton(principal),
Collections.emptySet(),
Collections.singleton(keyTab));
return createLoginContext(scopeName,
subject,
createKeyTabConfiguration(scopeName, keyTabFile, principal.getName()));
}
public KerberosKeyTabLoginConfiguration createKeyTabConfiguration(final String scopeName,
final File keyTabFile,
final String name)
{
return new KerberosKeyTabLoginConfiguration(scopeName, name, keyTabFile);
}
private LoginContext createLoginContext(final String serviceName, final Subject subject, final Configuration config)
throws LoginException
{
return new LoginContext(serviceName, subject, callbacks -> {
for (Callback callback : callbacks)
{
if (callback instanceof TextOutputCallback)
{
LOGGER.error(((TextOutputCallback) callback).getMessage());
}
}
}, config);
}
private KeyTab getKeyTab(final KerberosPrincipal principal, final File keyTabFile)
{
if (!keyTabFile.exists() || !keyTabFile.canRead())
{
throw new IllegalArgumentException("Specified file does not exist or is not readable.");
}
final KeyTab keytab = KeyTab.getInstance(principal, keyTabFile);
if (!keytab.exists())
{
throw new IllegalArgumentException("Specified file is not a keyTab file.");
}
final KerberosKey[] keys = keytab.getKeys(principal);
if (keys.length == 0)
{
throw new IllegalArgumentException("Specified file does not contain at least one key for this principal.");
}
for (final KerberosKey key : keys)
{
try
{
key.destroy();
}
catch (DestroyFailedException e)
{
LOGGER.debug("Unable to destroy key", e);
}
}
return keytab;
}
public static class KerberosKeyTabLoginConfiguration extends Configuration
{
private final String _scopeName;
private final AppConfigurationEntry _entry;
KerberosKeyTabLoginConfiguration(final String scopeName,
final String principalName,
final File keyTabFile)
{
final Map<String, String> options = new HashMap<>();
options.put("principal", principalName);
options.put("useKeyTab", TRUE.toString());
options.put("keyTab", keyTabFile.getAbsolutePath());
options.put("refreshKrb5Config", TRUE.toString());
options.put("doNotPrompt", TRUE.toString());
_entry = new AppConfigurationEntry(KERBEROS_LOGIN_MODULE_CLASS,
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
options);
_scopeName = scopeName;
}
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name)
{
if (_scopeName.equals(name))
{
return new AppConfigurationEntry[]{_entry};
}
return new AppConfigurationEntry[0];
}
}
public static void debugConfig()
{
if (LOGGER.isDebugEnabled())
{
final String krb5Conf = System.getProperty("java.security.krb5.conf");
if (krb5Conf != null)
{
final File file = new File(krb5Conf);
if (file.exists())
{
String config = FileUtils.readFileAsString(file);
debug("Kerberos config: {}", config);
}
else
{
LOGGER.warn("Kerberos config file was not found in the expected location at '{}'", krb5Conf);
}
}
else
{
LOGGER.warn("JVM system property 'java.security.krb5.conf' is not set");
}
}
}
public static void debug(String message, Object... args)
{
LOGGER.debug(message, args);
if (Boolean.TRUE.toString().equalsIgnoreCase(System.getProperty("sun.security.krb5.debug")))
{
System.out.println(String.format(message.replace("{}", "%s"), args));
}
}
}