blob: c0f84d3329f0752310faa4818d83733800c4d99b [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.bookkeeper.sasl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicLong;
import javax.security.auth.login.Configuration;
import org.apache.bookkeeper.bookie.BookieImpl;
import org.apache.bookkeeper.client.BKException;
import org.apache.bookkeeper.client.BKException.BKUnauthorizedAccessException;
import org.apache.bookkeeper.client.BookKeeper;
import org.apache.bookkeeper.client.BookKeeper.DigestType;
import org.apache.bookkeeper.client.LedgerEntry;
import org.apache.bookkeeper.client.LedgerHandle;
import org.apache.bookkeeper.conf.ClientConfiguration;
import org.apache.bookkeeper.conf.ServerConfiguration;
import org.apache.bookkeeper.conf.TestBKConfiguration;
import org.apache.bookkeeper.proto.BookieServer;
import org.apache.bookkeeper.test.BookKeeperClusterTestCase;
import org.apache.zookeeper.KeeperException;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* GSSAPI tests.
*/
public class GSSAPIBookKeeperTest extends BookKeeperClusterTestCase {
static final Logger LOG = LoggerFactory.getLogger(GSSAPIBookKeeperTest.class);
private static final byte[] PASSWD = "testPasswd".getBytes();
private static final byte[] ENTRY = "TestEntry".getBytes();
private static MiniKdc kdc;
private static Properties conf;
private static final String non_default_sasl_service_name = "non_default_servicename";
@ClassRule
public static TemporaryFolder kdcDir = new TemporaryFolder();
@ClassRule
public static TemporaryFolder kerberosWorkDir = new TemporaryFolder();
@BeforeClass
public static void startMiniKdc() throws Exception {
conf = MiniKdc.createConf();
kdc = new MiniKdc(conf, kdcDir.getRoot());
kdc.start();
// this is just to calculate "localhostName" the same way the bookie does
ServerConfiguration bookieConf = TestBKConfiguration.newServerConfiguration();
bookieConf.setUseHostNameAsBookieID(true);
String localhostName = BookieImpl.getBookieAddress(bookieConf).getHostName();
String principalServerNoRealm = non_default_sasl_service_name + "/" + localhostName;
String principalServer = non_default_sasl_service_name + "/" + localhostName + "@" + kdc.getRealm();
LOG.info("principalServer: " + principalServer);
String principalClientNoRealm = "bookkeeperclient/" + localhostName;
String principalClient = principalClientNoRealm + "@" + kdc.getRealm();
LOG.info("principalClient: " + principalClient);
File keytabClient = new File(kerberosWorkDir.getRoot(), "bookkeeperclient.keytab");
kdc.createPrincipal(keytabClient, principalClientNoRealm);
File keytabServer = new File(kerberosWorkDir.getRoot(), "bookkeeperserver.keytab");
kdc.createPrincipal(keytabServer, principalServerNoRealm);
File jaasFile = new File(kerberosWorkDir.getRoot(), "jaas.conf");
try (FileWriter writer = new FileWriter(jaasFile)) {
writer.write("\n"
+ "Bookie {\n"
+ " com.sun.security.auth.module.Krb5LoginModule required debug=true\n"
+ " useKeyTab=true\n"
+ " keyTab=\"" + keytabServer.getAbsolutePath() + "\n"
+ " storeKey=true\n"
+ " useTicketCache=false\n" // won't test useTicketCache=true on JUnit tests
+ " principal=\"" + principalServer + "\";\n"
+ "};\n"
+ "\n"
+ "\n"
+ "\n"
+ "BookKeeper {\n"
+ " com.sun.security.auth.module.Krb5LoginModule required debug=true\n"
+ " useKeyTab=true\n"
+ " keyTab=\"" + keytabClient.getAbsolutePath() + "\n"
+ " storeKey=true\n"
+ " useTicketCache=false\n"
+ " principal=\"" + principalClient + "\";\n"
+ "};\n"
);
}
File krb5file = new File(kerberosWorkDir.getRoot(), "krb5.conf");
try (FileWriter writer = new FileWriter(krb5file)) {
String conf = "[libdefaults]\n"
+ " default_realm = " + kdc.getRealm() + "\n"
+ " udp_preference_limit = 1\n" // force use TCP
+ "\n"
+ "\n"
+ "[realms]\n"
+ " " + kdc.getRealm() + " = {\n"
+ " kdc = " + kdc.getHost() + ":" + kdc.getPort() + "\n"
+ " }";
writer.write(conf);
LOG.info("krb5.conf:\n" + conf);
}
System.setProperty("java.security.auth.login.config", jaasFile.getAbsolutePath());
System.setProperty("java.security.krb5.conf", krb5file.getAbsolutePath());
javax.security.auth.login.Configuration.getConfiguration().refresh();
}
@AfterClass
public static void stopMiniKdc() {
System.clearProperty("java.security.auth.login.config");
System.clearProperty("java.security.krb5.conf");
if (kdc != null) {
kdc.stop();
}
}
public GSSAPIBookKeeperTest() {
super(0); // start them later when auth providers are configured
}
// we pass in ledgerId because the method may throw exceptions
private void connectAndWriteToBookie(ClientConfiguration conf, AtomicLong ledgerWritten)
throws BKException, InterruptedException, IOException, KeeperException {
LOG.info("Connecting to bookie");
try (BookKeeper bkc = new BookKeeper(conf, zkc)) {
LedgerHandle l = bkc.createLedger(1, 1, DigestType.CRC32,
PASSWD);
ledgerWritten.set(l.getId());
l.addEntry(ENTRY);
l.close();
}
}
/**
* check if the entry exists. Restart the bookie to allow access
*/
private int entryCount(long ledgerId, ClientConfiguration clientConf)
throws Exception {
LOG.info("Counting entries in {}", ledgerId);
for (ServerConfiguration conf : bsConfs) {
conf.setUseHostNameAsBookieID(true);
conf.setBookieAuthProviderFactoryClass(
SASLBookieAuthProviderFactory.class.getName());
}
clientConf.setClientAuthProviderFactoryClass(
SASLClientProviderFactory.class.getName());
restartBookies();
try (BookKeeper bkc = new BookKeeper(clientConf, zkc);
LedgerHandle lh = bkc.openLedger(ledgerId, DigestType.CRC32,
PASSWD)) {
if (lh.getLastAddConfirmed() < 0) {
return 0;
}
Enumeration<LedgerEntry> e = lh.readEntries(0, lh.getLastAddConfirmed());
int count = 0;
while (e.hasMoreElements()) {
count++;
assertTrue("Should match what we wrote",
Arrays.equals(e.nextElement().getEntry(), ENTRY));
}
return count;
}
}
/**
* Test an connection will authorize with a single message to the server and a single response.
*/
@Test
public void testSingleMessageAuth() throws Exception {
ServerConfiguration bookieConf = newServerConfiguration();
bookieConf.setUseHostNameAsBookieID(true);
bookieConf.setBookieAuthProviderFactoryClass(
SASLBookieAuthProviderFactory.class.getName());
ClientConfiguration clientConf = newClientConfiguration();
clientConf.setClientAuthProviderFactoryClass(
SASLClientProviderFactory.class.getName());
startAndStoreBookie(bookieConf);
AtomicLong ledgerId = new AtomicLong(-1);
connectAndWriteToBookie(clientConf, ledgerId); // should succeed
assertFalse(ledgerId.get() == -1);
assertEquals("Should have entry", 1, entryCount(ledgerId.get(), clientConf));
}
@Test
public void testNotAllowedClientId() throws Exception {
ServerConfiguration bookieConf = newServerConfiguration();
bookieConf.setUseHostNameAsBookieID(true);
bookieConf.setBookieAuthProviderFactoryClass(
SASLBookieAuthProviderFactory.class.getName());
bookieConf.setProperty(SaslConstants.JAAS_CLIENT_ALLOWED_IDS, "nobody");
ClientConfiguration clientConf = newClientConfiguration();
clientConf.setClientAuthProviderFactoryClass(
SASLClientProviderFactory.class.getName());
startAndStoreBookie(bookieConf);
AtomicLong ledgerId = new AtomicLong(-1);
try {
connectAndWriteToBookie(clientConf, ledgerId);
fail("should not be able to access the bookie");
} catch (BKUnauthorizedAccessException err) {
}
}
BookieServer startAndStoreBookie(ServerConfiguration conf) throws Exception {
System.setProperty(SaslConstants.SASL_SERVICE_NAME, non_default_sasl_service_name);
bsConfs.add(conf);
BookieServer s = startBookie(conf);
bs.add(s);
return s;
}
@AfterClass
public static void resetJAAS() {
System.clearProperty("java.security.auth.login.config");
Configuration.getConfiguration().refresh();
}
}