blob: 1050a1ec667bb3730d0630ca3ebe93d6c92ed0ac [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.accumulo.server.security.delegation;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import javax.crypto.KeyGenerator;
import org.apache.accumulo.core.Constants;
import org.apache.accumulo.fate.zookeeper.ZooReader;
import org.apache.zookeeper.KeeperException.NoNodeException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class ZooAuthenticationKeyWatcherTest {
// From org.apache.hadoop.security.token.SecretManager
private static final String DEFAULT_HMAC_ALGORITHM = "HmacSHA1";
private static final int KEY_LENGTH = 64;
private static KeyGenerator keyGen;
@BeforeClass
public static void setupKeyGenerator() throws Exception {
// From org.apache.hadoop.security.token.SecretManager
keyGen = KeyGenerator.getInstance(DEFAULT_HMAC_ALGORITHM);
keyGen.init(KEY_LENGTH);
}
private ZooReader zk;
private String instanceId;
private String baseNode;
private long tokenLifetime = 7 * 24 * 60 * 60 * 1000; // 7days
private AuthenticationTokenSecretManager secretManager;
private ZooAuthenticationKeyWatcher keyWatcher;
@Before
public void setupMocks() {
zk = createMock(ZooReader.class);
instanceId = UUID.randomUUID().toString();
baseNode = "/accumulo/" + instanceId + Constants.ZDELEGATION_TOKEN_KEYS;
secretManager = new AuthenticationTokenSecretManager(instanceId, tokenLifetime);
keyWatcher = new ZooAuthenticationKeyWatcher(secretManager, zk, baseNode);
}
@Test
public void testBaseNodeCreated() throws Exception {
WatchedEvent event = new WatchedEvent(EventType.NodeCreated, null, baseNode);
expect(zk.getChildren(baseNode, keyWatcher)).andReturn(Collections.emptyList());
replay(zk);
keyWatcher.process(event);
verify(zk);
assertTrue(secretManager.getKeys().isEmpty());
}
@Test
public void testBaseNodeCreatedWithChildren() throws Exception {
WatchedEvent event = new WatchedEvent(EventType.NodeCreated, null, baseNode);
AuthenticationKey key1 = new AuthenticationKey(1, 0L, 10000L, keyGen.generateKey()),
key2 = new AuthenticationKey(2, key1.getExpirationDate(), 20000L, keyGen.generateKey());
byte[] serializedKey1 = serialize(key1), serializedKey2 = serialize(key2);
List<String> children = Arrays.asList("1", "2");
expect(zk.getChildren(baseNode, keyWatcher)).andReturn(children);
expect(zk.getData(baseNode + "/1", keyWatcher)).andReturn(serializedKey1);
expect(zk.getData(baseNode + "/2", keyWatcher)).andReturn(serializedKey2);
replay(zk);
keyWatcher.process(event);
verify(zk);
assertEquals(2, secretManager.getKeys().size());
assertEquals(key1, secretManager.getKeys().get(key1.getKeyId()));
assertEquals(key2, secretManager.getKeys().get(key2.getKeyId()));
}
@Test
public void testBaseNodeChildrenChanged() throws Exception {
WatchedEvent event = new WatchedEvent(EventType.NodeChildrenChanged, null, baseNode);
AuthenticationKey key1 = new AuthenticationKey(1, 0L, 10000L, keyGen.generateKey()),
key2 = new AuthenticationKey(2, key1.getExpirationDate(), 20000L, keyGen.generateKey());
byte[] serializedKey1 = serialize(key1), serializedKey2 = serialize(key2);
List<String> children = Arrays.asList("1", "2");
expect(zk.getChildren(baseNode, keyWatcher)).andReturn(children);
expect(zk.getData(baseNode + "/1", keyWatcher)).andReturn(serializedKey1);
expect(zk.getData(baseNode + "/2", keyWatcher)).andReturn(serializedKey2);
replay(zk);
keyWatcher.process(event);
verify(zk);
assertEquals(2, secretManager.getKeys().size());
assertEquals(key1, secretManager.getKeys().get(key1.getKeyId()));
assertEquals(key2, secretManager.getKeys().get(key2.getKeyId()));
}
@Test
public void testBaseNodeDeleted() {
WatchedEvent event = new WatchedEvent(EventType.NodeDeleted, null, baseNode);
AuthenticationKey key1 = new AuthenticationKey(1, 0L, 10000L, keyGen.generateKey()),
key2 = new AuthenticationKey(2, key1.getExpirationDate(), 20000L, keyGen.generateKey());
secretManager.addKey(key1);
secretManager.addKey(key2);
assertEquals(2, secretManager.getKeys().size());
replay(zk);
keyWatcher.process(event);
verify(zk);
assertEquals(0, secretManager.getKeys().size());
assertFalse(secretManager.isCurrentKeySet());
}
@Test
public void testBaseNodeDataChanged() {
WatchedEvent event = new WatchedEvent(EventType.NodeDataChanged, null, baseNode);
replay(zk);
keyWatcher.process(event);
verify(zk);
assertEquals(0, secretManager.getKeys().size());
assertFalse(secretManager.isCurrentKeySet());
}
@Test
public void testChildChanged() throws Exception {
WatchedEvent event = new WatchedEvent(EventType.NodeCreated, null, baseNode + "/2");
AuthenticationKey key1 = new AuthenticationKey(1, 0L, 10000L, keyGen.generateKey()),
key2 = new AuthenticationKey(2, key1.getExpirationDate(), 20000L, keyGen.generateKey());
secretManager.addKey(key1);
assertEquals(1, secretManager.getKeys().size());
byte[] serializedKey2 = serialize(key2);
expect(zk.getData(event.getPath(), keyWatcher)).andReturn(serializedKey2);
replay(zk);
keyWatcher.process(event);
verify(zk);
assertEquals(2, secretManager.getKeys().size());
assertEquals(key1, secretManager.getKeys().get(key1.getKeyId()));
assertEquals(key2, secretManager.getKeys().get(key2.getKeyId()));
assertEquals(key2, secretManager.getCurrentKey());
}
@Test
public void testChildDeleted() {
WatchedEvent event = new WatchedEvent(EventType.NodeDeleted, null, baseNode + "/1");
AuthenticationKey key1 = new AuthenticationKey(1, 0L, 10000L, keyGen.generateKey()),
key2 = new AuthenticationKey(2, key1.getExpirationDate(), 20000L, keyGen.generateKey());
secretManager.addKey(key1);
secretManager.addKey(key2);
assertEquals(2, secretManager.getKeys().size());
replay(zk);
keyWatcher.process(event);
verify(zk);
assertEquals(1, secretManager.getKeys().size());
assertEquals(key2, secretManager.getKeys().get(key2.getKeyId()));
assertEquals(key2, secretManager.getCurrentKey());
}
@Test
public void testChildChildrenChanged() {
WatchedEvent event = new WatchedEvent(EventType.NodeChildrenChanged, null, baseNode + "/2");
AuthenticationKey key1 = new AuthenticationKey(1, 0L, 10000L, keyGen.generateKey()),
key2 = new AuthenticationKey(2, key1.getExpirationDate(), 20000L, keyGen.generateKey());
secretManager.addKey(key1);
secretManager.addKey(key2);
assertEquals(2, secretManager.getKeys().size());
replay(zk);
// Does nothing
keyWatcher.process(event);
verify(zk);
assertEquals(2, secretManager.getKeys().size());
assertEquals(key1, secretManager.getKeys().get(key1.getKeyId()));
assertEquals(key2, secretManager.getKeys().get(key2.getKeyId()));
assertEquals(key2, secretManager.getCurrentKey());
}
@Test
public void testInitialUpdateNoNode() throws Exception {
expect(zk.exists(baseNode, keyWatcher)).andReturn(false);
replay(zk);
keyWatcher.updateAuthKeys();
verify(zk);
assertEquals(0, secretManager.getKeys().size());
assertNull(secretManager.getCurrentKey());
}
@Test
public void testInitialUpdateWithKeys() throws Exception {
List<String> children = Arrays.asList("1", "5");
AuthenticationKey key1 = new AuthenticationKey(1, 0L, 10000L, keyGen.generateKey()),
key2 = new AuthenticationKey(5, key1.getExpirationDate(), 20000L, keyGen.generateKey());
expect(zk.exists(baseNode, keyWatcher)).andReturn(true);
expect(zk.getChildren(baseNode, keyWatcher)).andReturn(children);
expect(zk.getData(baseNode + "/" + key1.getKeyId(), keyWatcher)).andReturn(serialize(key1));
expect(zk.getData(baseNode + "/" + key2.getKeyId(), keyWatcher)).andReturn(serialize(key2));
replay(zk);
keyWatcher.updateAuthKeys();
verify(zk);
assertEquals(2, secretManager.getKeys().size());
assertEquals(key1, secretManager.getKeys().get(key1.getKeyId()));
assertEquals(key2, secretManager.getKeys().get(key2.getKeyId()));
}
@Test
public void testDisconnectAndReconnect() throws Exception {
lostZooKeeperBase(new WatchedEvent(EventType.None, KeeperState.Disconnected, null),
new WatchedEvent(EventType.None, KeeperState.SyncConnected, null));
}
@Test
public void testExpiredAndReconnect() throws Exception {
lostZooKeeperBase(new WatchedEvent(EventType.None, KeeperState.Expired, null),
new WatchedEvent(EventType.None, KeeperState.SyncConnected, null));
}
private void lostZooKeeperBase(WatchedEvent disconnectEvent, WatchedEvent reconnectEvent)
throws Exception {
List<String> children = Arrays.asList("1", "5");
AuthenticationKey key1 = new AuthenticationKey(1, 0L, 10000L, keyGen.generateKey()),
key2 = new AuthenticationKey(5, key1.getExpirationDate(), 20000L, keyGen.generateKey());
expect(zk.exists(baseNode, keyWatcher)).andReturn(true);
expect(zk.getChildren(baseNode, keyWatcher)).andReturn(children);
expect(zk.getData(baseNode + "/" + key1.getKeyId(), keyWatcher)).andReturn(serialize(key1));
expect(zk.getData(baseNode + "/" + key2.getKeyId(), keyWatcher)).andReturn(serialize(key2));
replay(zk);
// Initialize and then get disconnected
keyWatcher.updateAuthKeys();
keyWatcher.process(disconnectEvent);
verify(zk);
// We should have no auth keys when we're disconnected
assertEquals("Secret manager should be empty after a disconnect", 0,
secretManager.getKeys().size());
assertNull("Current key should be null", secretManager.getCurrentKey());
reset(zk);
expect(zk.exists(baseNode, keyWatcher)).andReturn(true);
expect(zk.getChildren(baseNode, keyWatcher)).andReturn(children);
expect(zk.getData(baseNode + "/" + key1.getKeyId(), keyWatcher)).andReturn(serialize(key1));
expect(zk.getData(baseNode + "/" + key2.getKeyId(), keyWatcher)).andReturn(serialize(key2));
replay(zk);
// Reconnect again, get all the keys
keyWatcher.process(reconnectEvent);
verify(zk);
// Verify we have both keys
assertEquals(2, secretManager.getKeys().size());
assertEquals(key1, secretManager.getKeys().get(key1.getKeyId()));
assertEquals(key2, secretManager.getKeys().get(key2.getKeyId()));
}
@Test
public void missingKeyAfterGetChildren() throws Exception {
List<String> children = Arrays.asList("1");
AuthenticationKey key1 = new AuthenticationKey(1, 0L, 10000L, keyGen.generateKey());
expect(zk.exists(baseNode, keyWatcher)).andReturn(true);
// We saw key1
expect(zk.getChildren(baseNode, keyWatcher)).andReturn(children);
// but it was gone when we tried to access it (manager deleted it)
expect(zk.getData(baseNode + "/" + key1.getKeyId(), keyWatcher))
.andThrow(new NoNodeException());
replay(zk);
// Initialize
keyWatcher.updateAuthKeys();
verify(zk);
// We should have no auth keys after initializing things
assertEquals("Secret manager should be empty after a disconnect", 0,
secretManager.getKeys().size());
assertNull("Current key should be null", secretManager.getCurrentKey());
}
private byte[] serialize(AuthenticationKey key) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
key.write(new DataOutputStream(baos));
return baos.toByteArray();
}
}