diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/TransportConfigurationUtil.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/TransportConfigurationUtil.java index fe4cb3de4d..97a4bd2088 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/TransportConfigurationUtil.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/TransportConfigurationUtil.java @@ -27,6 +27,9 @@ import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactor import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; import org.apache.activemq.artemis.utils.ClassloadingUtil; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; + /** * Stores static mappings of class names to ConnectorFactory instances to act as a central repo for ConnectorFactory * objects. @@ -95,4 +98,29 @@ public class TransportConfigurationUtil { } return false; } + + public static Configuration kerb5Config(String principal, boolean initiator) { + final Map krb5LoginModuleOptions = new HashMap<>(); + krb5LoginModuleOptions.put("isInitiator", String.valueOf(initiator)); + krb5LoginModuleOptions.put("principal", principal); + krb5LoginModuleOptions.put("useKeyTab", "true"); + krb5LoginModuleOptions.put("storeKey", "true"); + krb5LoginModuleOptions.put("doNotPrompt", "true"); + krb5LoginModuleOptions.put("renewTGT", "true"); + krb5LoginModuleOptions.put("refreshKrb5Config", "true"); + krb5LoginModuleOptions.put("useTicketCache", "true"); + String ticketCache = System.getenv("KRB5CCNAME"); + if (ticketCache != null) { + krb5LoginModuleOptions.put("ticketCache", ticketCache); + } + return new Configuration() { + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) { + return new AppConfigurationEntry[]{ + new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, + krb5LoginModuleOptions)}; + } + }; + } } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyConnection.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyConnection.java index 0316b1517f..3ce40c97b5 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyConnection.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyConnection.java @@ -28,6 +28,7 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelPromise; import io.netty.channel.EventLoop; import io.netty.handler.ssl.SslHandler; @@ -45,6 +46,8 @@ import org.apache.activemq.artemis.utils.Env; import org.apache.activemq.artemis.utils.IPV6Util; import org.jboss.logging.Logger; +import javax.net.ssl.SSLPeerUnverifiedException; + public class NettyConnection implements Connection { private static final Logger logger = Logger.getLogger(NettyConnection.class); @@ -487,6 +490,17 @@ public class NettyConnection implements Connection { //never allow this @Override public final ActiveMQPrincipal getDefaultActiveMQPrincipal() { + ChannelHandler channelHandler = channel.pipeline().get("ssl"); + if (channelHandler != null && channelHandler instanceof SslHandler) { + SslHandler sslHandler = (SslHandler) channelHandler; + try { + return new ActiveMQPrincipal(sslHandler.engine().getSession().getPeerPrincipal().getName(), ""); + } catch (SSLPeerUnverifiedException ignored) { + if (logger.isTraceEnabled()) { + logger.trace(ignored.getMessage(), ignored); + } + } + } return null; } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyConnector.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyConnector.java index f5b5e5688c..20431f012c 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyConnector.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyConnector.java @@ -28,6 +28,7 @@ import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.PrivilegedExceptionAction; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -43,6 +44,8 @@ import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; @@ -95,6 +98,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.core.client.ActiveMQClientLogger; import org.apache.activemq.artemis.core.client.ActiveMQClientMessageBundle; import org.apache.activemq.artemis.core.protocol.core.impl.ActiveMQClientProtocolManager; +import org.apache.activemq.artemis.core.remoting.impl.TransportConfigurationUtil; import org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport; import org.apache.activemq.artemis.core.server.ActiveMQComponent; import org.apache.activemq.artemis.spi.core.remoting.AbstractConnector; @@ -205,6 +209,10 @@ public class NettyConnector extends AbstractConnector { private boolean verifyHost; + private String sniHost; + + private String kerb5Config; + private boolean useDefaultSslContext; private boolean tcpNoDelay; @@ -328,6 +336,10 @@ public class NettyConnector extends AbstractConnector { verifyHost = ConfigurationHelper.getBooleanProperty(TransportConstants.VERIFY_HOST_PROP_NAME, TransportConstants.DEFAULT_VERIFY_HOST, configuration); + sniHost = ConfigurationHelper.getStringProperty(TransportConstants.SNIHOST_PROP_NAME, TransportConstants.DEFAULT_SNIHOST_CONFIG, configuration); + + kerb5Config = ConfigurationHelper.getStringProperty(TransportConstants.SSL_KRB5_CONFIG_PROP_NAME, TransportConstants.DEFAULT_SSL_KRB5_CONFIG, configuration); + useDefaultSslContext = ConfigurationHelper.getBooleanProperty(TransportConstants.USE_DEFAULT_SSL_CONTEXT_PROP_NAME, TransportConstants.DEFAULT_USE_DEFAULT_SSL_CONTEXT, configuration); } else { keyStoreProvider = TransportConstants.DEFAULT_KEYSTORE_PROVIDER; @@ -509,13 +521,36 @@ public class NettyConnector extends AbstractConnector { public void initChannel(Channel channel) throws Exception { final ChannelPipeline pipeline = channel.pipeline(); if (sslEnabled && !useServlet) { - SSLEngine engine; - if (verifyHost) { - engine = context.createSSLEngine(host, port); - } else { - engine = context.createSSLEngine(); + + Subject subject = null; + if (kerb5Config != null && kerb5Config.length() > 0) { + + LoginContext loginContext = null; + if (Character.isUpperCase(kerb5Config.charAt(0))) { + // use as login.config scope + loginContext = new LoginContext(kerb5Config); + } else { + // inline keytab config using kerb5Config as principal + loginContext = new LoginContext("", null, null, + TransportConfigurationUtil.kerb5Config(kerb5Config, true)); + } + + loginContext.login(); + subject = loginContext.getSubject(); + verifyHost = true; } + SSLEngine engine = Subject.doAs(subject, new PrivilegedExceptionAction() { + @Override + public SSLEngine run() { + if (verifyHost) { + return context.createSSLEngine(sniHost != null ? sniHost : host, port); + } else { + return context.createSSLEngine(); + } + } + }); + engine.setUseClientMode(true); engine.setWantClientAuth(true); diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java index 428a3a0a2a..30342acc6a 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java @@ -27,6 +27,8 @@ public class TransportConstants { public static final String SSL_ENABLED_PROP_NAME = "sslEnabled"; + public static final String SSL_KRB5_CONFIG_PROP_NAME = "sslKrb5Config"; + public static final String HTTP_ENABLED_PROP_NAME = "httpEnabled"; public static final String HTTP_CLIENT_IDLE_PROP_NAME = "httpClientIdleTime"; @@ -99,6 +101,8 @@ public class TransportConstants { public static final String VERIFY_HOST_PROP_NAME = "verifyHost"; + public static final String SNIHOST_PROP_NAME = "sniHost"; + public static final String BACKLOG_PROP_NAME = "backlog"; public static final String USE_DEFAULT_SSL_CONTEXT_PROP_NAME = "useDefaultSslContext"; @@ -145,6 +149,10 @@ public class TransportConstants { public static final boolean DEFAULT_SSL_ENABLED = false; + public static final String DEFAULT_SSL_KRB5_CONFIG = null; + + public static final String DEFAULT_SNIHOST_CONFIG = null; + public static final boolean DEFAULT_USE_GLOBAL_WORKER_POOL = true; public static final boolean DEFAULT_USE_EPOLL = true; diff --git a/artemis-server/pom.xml b/artemis-server/pom.xml index d5f058b043..8e1ecd618a 100644 --- a/artemis-server/pom.xml +++ b/artemis-server/pom.xml @@ -94,7 +94,7 @@ org.apache.directory.server - apacheds-server-integ + apacheds-test-framework ${directory-version} test @@ -105,18 +105,11 @@ - org.apache.directory.server - apacheds-core-integ - ${directory-version} + org.apache.directory.jdbm + apacheds-jdbm2 + ${directory-jdbm2-version} test - - - bouncycastle - bcprov-jdk15 - - - org.apache.activemq artemis-commons diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java index 1fde73f864..bc267765c2 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java @@ -20,10 +20,13 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLParameters; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.security.AccessController; import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -68,6 +71,7 @@ import org.apache.activemq.artemis.api.core.management.CoreNotificationType; import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryImpl; import org.apache.activemq.artemis.core.protocol.ProtocolHandler; import org.apache.activemq.artemis.core.remoting.impl.AbstractAcceptor; +import org.apache.activemq.artemis.core.remoting.impl.TransportConfigurationUtil; import org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport; import org.apache.activemq.artemis.core.security.ActiveMQPrincipal; import org.apache.activemq.artemis.core.server.ActiveMQComponent; @@ -154,6 +158,8 @@ public class NettyAcceptor extends AbstractAcceptor { private final boolean verifyHost; + private final String kerb5Config; + private final boolean tcpNoDelay; private final int backlog; @@ -217,6 +223,8 @@ public class NettyAcceptor extends AbstractAcceptor { sslEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.SSL_ENABLED_PROP_NAME, TransportConstants.DEFAULT_SSL_ENABLED, configuration); + kerb5Config = ConfigurationHelper.getStringProperty(TransportConstants.SSL_KRB5_CONFIG_PROP_NAME, TransportConstants.DEFAULT_SSL_KRB5_CONFIG, configuration); + remotingThreads = ConfigurationHelper.getIntProperty(TransportConstants.NIO_REMOTING_THREADS_PROPNAME, -1, configuration); remotingThreads = ConfigurationHelper.getIntProperty(TransportConstants.REMOTING_THREADS_PROPNAME, remotingThreads, configuration); @@ -423,7 +431,7 @@ public class NettyAcceptor extends AbstractAcceptor { public synchronized SslHandler getSslHandler() throws Exception { final SSLContext context; try { - if (keyStorePath == null && TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER.equals(keyStoreProvider)) + if (kerb5Config == null && keyStorePath == null && TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER.equals(keyStoreProvider)) throw new IllegalArgumentException("If \"" + TransportConstants.SSL_ENABLED_PROP_NAME + "\" is true then \"" + TransportConstants.KEYSTORE_PATH_PROP_NAME + "\" must be non-null " + "unless an alternative \"" + TransportConstants.KEYSTORE_PROVIDER_PROP_NAME + "\" has been specified."); @@ -433,13 +441,32 @@ public class NettyAcceptor extends AbstractAcceptor { ise.initCause(e); throw ise; } - SSLEngine engine; - if (verifyHost) { - engine = context.createSSLEngine(host, port); - } else { - engine = context.createSSLEngine(); + Subject subject = null; + if (kerb5Config != null && kerb5Config.length() > 0) { + LoginContext loginContext = null; + if (Character.isUpperCase(kerb5Config.charAt(0))) { + // use as login.config scope + loginContext = new LoginContext(kerb5Config); + } else { + loginContext = new LoginContext("", null, null, + TransportConfigurationUtil.kerb5Config(kerb5Config, false)); + } + loginContext.login(); + + subject = loginContext.getSubject(); } + SSLEngine engine = Subject.doAs(subject, new PrivilegedExceptionAction() { + @Override + public SSLEngine run() { + if (verifyHost) { + return context.createSSLEngine(host, port); + } else { + return context.createSSLEngine(); + } + } + }); + engine.setUseClientMode(false); if (needClientAuth) diff --git a/pom.xml b/pom.xml index 840dda1697..ae4bc22afb 100644 --- a/pom.xml +++ b/pom.xml @@ -164,7 +164,9 @@ javac-with-errorprone - 1.5.7 + 2.0.0-M15 + 2.0.0-M1 + 1.2 1.0 diff --git a/tests/integration-tests/pom.xml b/tests/integration-tests/pom.xml index 36f1db4fe4..56a4bc8fdb 100644 --- a/tests/integration-tests/pom.xml +++ b/tests/integration-tests/pom.xml @@ -249,13 +249,19 @@ org.apache.directory.server - apacheds-server-integ + apacheds-test-framework ${directory-version} test + + + org.apache.directory.api + api-ldap-schema-data + + org.apache.directory.server - apacheds-core-integ + apacheds-server-annotations ${directory-version} test @@ -328,6 +334,18 @@ + + org.apache.hadoop + hadoop-minikdc + 2.8.1 + test + + + org.apache.directory.jdbm + apacheds-jdbm2 + ${directory-jdbm2-version} + test + diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLKerb5Test.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLKerb5Test.java new file mode 100644 index 0000000000..6c26667299 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLKerb5Test.java @@ -0,0 +1,163 @@ +/* + * 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.activemq.artemis.tests.integration.ssl; + +import org.apache.activemq.artemis.api.core.RoutingType; +import org.apache.activemq.artemis.api.core.SimpleString; +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.api.core.client.ActiveMQClient; +import org.apache.activemq.artemis.api.core.client.ClientConsumer; +import org.apache.activemq.artemis.api.core.client.ClientMessage; +import org.apache.activemq.artemis.api.core.client.ClientProducer; +import org.apache.activemq.artemis.api.core.client.ClientSession; +import org.apache.activemq.artemis.api.core.client.ClientSessionFactory; +import org.apache.activemq.artemis.api.core.client.ServerLocator; +import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl; +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; +import org.apache.activemq.artemis.core.security.Role; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.settings.HierarchicalRepository; +import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager; +import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; +import org.apache.activemq.artemis.utils.RandomUtil; +import org.apache.hadoop.minikdc.MiniKdc; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class CoreClientOverOneWaySSLKerb5Test extends ActiveMQTestBase { + + public static final SimpleString QUEUE = new SimpleString("QueueOverKrb5SSL"); + public static final String CLIENT_PRINCIPAL = "client"; + public static final String SNI_HOST = "sni.host"; + public static final String SERVICE_PRINCIPAL = "host/" + SNI_HOST; + + private MiniKdc kdc; + private ActiveMQServer server; + + private TransportConfiguration tc; + + @Test + public void testOneWaySSLWithGoodClientCipherSuite() throws Exception { + + // hard coded match, default_keytab_name in minikdc-krb5.conf template + File userKeyTab = new File("target/test.krb5.keytab"); + kdc.createPrincipal(userKeyTab, CLIENT_PRINCIPAL, SERVICE_PRINCIPAL); + + createCustomSslServer(); + + tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + tc.getParams().put(TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME, getSuitableCipherSuite()); + tc.getParams().put(TransportConstants.SNIHOST_PROP_NAME, SNI_HOST); // static service name rather than dynamic machine name + tc.getParams().put(TransportConstants.SSL_KRB5_CONFIG_PROP_NAME, "client"); // lower case used as principal with default keytab + final ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)); + + ClientSessionFactory sf = null; + try { + sf = createSessionFactory(locator); + ClientSession session = sf.createSession(false, true, true); + session.createQueue(CoreClientOverOneWaySSLKerb5Test.QUEUE, RoutingType.ANYCAST, CoreClientOverOneWaySSLKerb5Test.QUEUE); + ClientProducer producer = session.createProducer(CoreClientOverOneWaySSLKerb5Test.QUEUE); + + final String text = RandomUtil.randomString(); + ClientMessage message = createTextMessage(session, text); + producer.send(message); + + ClientConsumer consumer = session.createConsumer(CoreClientOverOneWaySSLKerb5Test.QUEUE); + session.start(); + + ClientMessage m = consumer.receive(1000); + Assert.assertNotNull(m); + Assert.assertEquals(text, m.getReadOnlyBodyBuffer().readString()); + System.err.println("m:" + m + ", user:" + m.getValidatedUserID()); + Assert.assertNotNull("got validated user", m.getValidatedUserID()); + Assert.assertTrue("krb id in validated user", m.getValidatedUserID().contains(CLIENT_PRINCIPAL)); + + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(); + } finally { + if (sf != null) { + sf.close(); + } + locator.close(); + } + } + + + public String getSuitableCipherSuite() throws Exception { + return "TLS_KRB5_WITH_3DES_EDE_CBC_SHA"; + } + + + // Package protected --------------------------------------------- + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + kdc = new MiniKdc(MiniKdc.createConf(), temporaryFolder.newFolder("kdc")); + kdc.start(); + } + + @Override + @After + public void tearDown() throws Exception { + try { + kdc.stop(); + } finally { + super.tearDown(); + } + } + + private void createCustomSslServer() throws Exception { + Map params = new HashMap<>(); + + params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + params.put(TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME, getSuitableCipherSuite()); + params.put(TransportConstants.SSL_KRB5_CONFIG_PROP_NAME, SERVICE_PRINCIPAL); + + ConfigurationImpl config = createBasicConfig().addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params, "nettySSL")); + config.setPopulateValidatedUser(true); // so we can verify the kerb5 id is present + config.setSecurityEnabled(true); + + server = createServer(false, config); + server.start(); + waitForServerToStart(server); + + final String roleName = "ALLOW_ALL"; + Role role = new Role(roleName, true, true, true, true, true, true, true, true, true, true); + Set roles = new HashSet<>(); + roles.add(role); + HierarchicalRepository> securityRepository = server.getSecurityRepository(); + securityRepository.addMatch(QUEUE.toString(), roles); + ActiveMQJAASSecurityManager securityManager = (ActiveMQJAASSecurityManager) server.getSecurityManager(); + + final String user = CLIENT_PRINCIPAL + "@" + kdc.getRealm(); + securityManager.getConfiguration().addUser(user, ""); + securityManager.getConfiguration().addRole(user, roleName); + + tc = new TransportConfiguration(NETTY_CONNECTOR_FACTORY); + } +} diff --git a/tests/integration-tests/src/test/resources/minikdc-krb5.conf b/tests/integration-tests/src/test/resources/minikdc-krb5.conf new file mode 100644 index 0000000000..0f068ca44c --- /dev/null +++ b/tests/integration-tests/src/test/resources/minikdc-krb5.conf @@ -0,0 +1,26 @@ +# +# 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. +# +[libdefaults] + default_realm = {0} + udp_preference_limit = 1 + default_keytab_name = FILE:target/test.krb5.keytab + +[realms] + {0} = '{' + kdc = {1}:{2} + '}' \ No newline at end of file