blob: 1895aa70ea313d4b5bf9a36aeeead9940a8fa043 [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.hadoop.ozone.security;
import org.apache.hadoop.hdds.HddsConfigKeys;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.BlockTokenSecretProto.AccessModeProto;
import org.apache.hadoop.hdds.security.token.BlockTokenException;
import org.apache.hadoop.hdds.security.token.BlockTokenVerifier;
import org.apache.hadoop.hdds.security.token.OzoneBlockTokenIdentifier;
import org.apache.hadoop.hdds.security.x509.SecurityConfig;
import org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient;
import org.apache.hadoop.hdds.security.x509.certificate.client.OMCertificateClient;
import org.apache.hadoop.hdds.security.x509.exceptions.CertificateException;
import org.apache.hadoop.security.ssl.KeyStoreTestUtil;
import org.apache.hadoop.security.token.Token;
import org.apache.hadoop.test.GenericTestUtils;
import org.apache.hadoop.test.LambdaTestUtils;
import org.apache.hadoop.util.Time;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.EnumSet;
/**
* Test class for {@link OzoneBlockTokenSecretManager}.
*/
public class TestOzoneBlockTokenSecretManager {
private OzoneBlockTokenSecretManager secretManager;
private KeyPair keyPair;
private X509Certificate x509Certificate;
private long expiryTime;
private String omCertSerialId;
private CertificateClient client;
private static final String BASEDIR = GenericTestUtils
.getTempPath(TestOzoneBlockTokenSecretManager.class.getSimpleName());
private BlockTokenVerifier tokenVerifier;
@Rule
public ExpectedException exception = ExpectedException.none();
@Before
public void setUp() throws Exception {
OzoneConfiguration conf = new OzoneConfiguration();
conf.set(HddsConfigKeys.OZONE_METADATA_DIRS, BASEDIR);
conf.setBoolean(HddsConfigKeys.HDDS_BLOCK_TOKEN_ENABLED, true);
// Create Ozone Master key pair.
keyPair = KeyStoreTestUtil.generateKeyPair("RSA");
expiryTime = Time.monotonicNow() + 60 * 60 * 24;
// Create Ozone Master certificate (SCM CA issued cert) and key store.
SecurityConfig securityConfig = new SecurityConfig(conf);
x509Certificate = KeyStoreTestUtil
.generateCertificate("CN=OzoneMaster", keyPair, 30, "SHA256withRSA");
omCertSerialId = x509Certificate.getSerialNumber().toString();
secretManager = new OzoneBlockTokenSecretManager(securityConfig,
expiryTime, omCertSerialId);
client = getCertificateClient(securityConfig);
client.init();
secretManager.start(client);
tokenVerifier = new BlockTokenVerifier(securityConfig, client);
}
private CertificateClient getCertificateClient(SecurityConfig secConf)
throws Exception {
return new OMCertificateClient(secConf){
@Override
public X509Certificate getCertificate() {
return x509Certificate;
}
@Override
public X509Certificate getCertificate(String certSerialId)
throws CertificateException {
return x509Certificate;
}
@Override
public PrivateKey getPrivateKey() {
return keyPair.getPrivate();
}
@Override
public PublicKey getPublicKey() {
return keyPair.getPublic();
}
};
}
@After
public void tearDown() throws Exception {
secretManager = null;
}
@Test
public void testGenerateToken() throws Exception {
Token<OzoneBlockTokenIdentifier> token = secretManager.generateToken(
"101", EnumSet.allOf(AccessModeProto.class), 100);
OzoneBlockTokenIdentifier identifier =
OzoneBlockTokenIdentifier.readFieldsProtobuf(new DataInputStream(
new ByteArrayInputStream(token.getIdentifier())));
// Check basic details.
Assert.assertTrue(identifier.getBlockId().equals("101"));
Assert.assertTrue(identifier.getAccessModes().equals(EnumSet
.allOf(AccessModeProto.class)));
Assert.assertTrue(identifier.getOmCertSerialId().equals(omCertSerialId));
validateHash(token.getPassword(), token.getIdentifier());
}
@Test
public void testCreateIdentifierSuccess() throws Exception {
OzoneBlockTokenIdentifier btIdentifier = secretManager.createIdentifier(
"testUser", "101", EnumSet.allOf(AccessModeProto.class), 100);
// Check basic details.
Assert.assertTrue(btIdentifier.getOwnerId().equals("testUser"));
Assert.assertTrue(btIdentifier.getBlockId().equals("101"));
Assert.assertTrue(btIdentifier.getAccessModes().equals(EnumSet
.allOf(AccessModeProto.class)));
Assert.assertTrue(btIdentifier.getOmCertSerialId().equals(omCertSerialId));
byte[] hash = secretManager.createPassword(btIdentifier);
validateHash(hash, btIdentifier.getBytes());
}
/**
* Validate hash using public key of KeyPair.
* */
private void validateHash(byte[] hash, byte[] identifier) throws Exception {
Signature rsaSignature =
Signature.getInstance(secretManager.getDefaultSignatureAlgorithm());
rsaSignature.initVerify(client.getPublicKey());
rsaSignature.update(identifier);
Assert.assertTrue(rsaSignature.verify(hash));
}
@Test
public void testCreateIdentifierFailure() throws Exception {
LambdaTestUtils.intercept(SecurityException.class,
"Ozone block token can't be created without owner and access mode "
+ "information.", () -> {
secretManager.createIdentifier();
});
}
@Test
public void testRenewToken() throws Exception {
LambdaTestUtils.intercept(UnsupportedOperationException.class,
"Renew token operation is not supported for ozone block" +
" tokens.", () -> {
secretManager.renewToken(null, null);
});
}
@Test
public void testCancelToken() throws Exception {
LambdaTestUtils.intercept(UnsupportedOperationException.class,
"Cancel token operation is not supported for ozone block" +
" tokens.", () -> {
secretManager.cancelToken(null, null);
});
}
@Test
public void testVerifySignatureFailure() throws Exception {
OzoneBlockTokenIdentifier id = new OzoneBlockTokenIdentifier(
"testUser", "4234", EnumSet.allOf(AccessModeProto.class),
Time.now() + 60 * 60 * 24, "123444", 1024);
LambdaTestUtils.intercept(UnsupportedOperationException.class, "operation" +
" is not supported for block tokens",
() -> secretManager.verifySignature(id,
client.signData(id.getBytes())));
}
@Test
public void testBlockTokenVerifier() throws Exception {
String tokenBlockID = "101";
Token<OzoneBlockTokenIdentifier> token =
secretManager.generateToken("testUser", tokenBlockID,
EnumSet.allOf(AccessModeProto.class), 100);
OzoneBlockTokenIdentifier btIdentifier =
OzoneBlockTokenIdentifier.readFieldsProtobuf(new DataInputStream(
new ByteArrayInputStream(token.getIdentifier())));
// Check basic details.
Assert.assertTrue(btIdentifier.getOwnerId().equals("testUser"));
Assert.assertTrue(btIdentifier.getBlockId().equals("101"));
Assert.assertTrue(btIdentifier.getAccessModes().equals(EnumSet
.allOf(AccessModeProto.class)));
Assert.assertTrue(btIdentifier.getOmCertSerialId().equals(omCertSerialId));
validateHash(token.getPassword(), btIdentifier.getBytes());
tokenVerifier.verify("testUser", token.encodeToUrlString(),
ContainerProtos.Type.PutBlock, "101");
String notAllledBlockID = "NotAllowedBlockID";
LambdaTestUtils.intercept(BlockTokenException.class,
"Token for block ID: " + tokenBlockID +
" can't be used to access block: " + notAllledBlockID,
() -> tokenVerifier.verify("testUser", token.encodeToUrlString(),
ContainerProtos.Type.PutBlock, notAllledBlockID));
// Non block operations are not checked by block token verifier
tokenVerifier.verify(null, null,
ContainerProtos.Type.CloseContainer, null);
}
@Test
public void testBlockTokenReadAccessMode() throws Exception {
final String testUser1 = "testUser1";
final String testBlockId1 = "101";
Token<OzoneBlockTokenIdentifier> readToken =
secretManager.generateToken(testUser1, testBlockId1,
EnumSet.of(AccessModeProto.READ), 100);
exception.expect(BlockTokenException.class);
exception.expectMessage("doesn't have WRITE permission");
tokenVerifier.verify(testUser1, readToken.encodeToUrlString(),
ContainerProtos.Type.PutBlock, testBlockId1);
tokenVerifier.verify(testUser1, readToken.encodeToUrlString(),
ContainerProtos.Type.GetBlock, testBlockId1);
}
@Test
public void testBlockTokenWriteAccessMode() throws Exception {
final String testUser2 = "testUser2";
final String testBlockId2 = "102";
Token<OzoneBlockTokenIdentifier> writeToken =
secretManager.generateToken("testUser2", testBlockId2,
EnumSet.of(AccessModeProto.WRITE), 100);
tokenVerifier.verify(testUser2, writeToken.encodeToUrlString(),
ContainerProtos.Type.WriteChunk, testBlockId2);
exception.expect(BlockTokenException.class);
exception.expectMessage("doesn't have READ permission");
tokenVerifier.verify(testUser2, writeToken.encodeToUrlString(),
ContainerProtos.Type.ReadChunk, testBlockId2);
}
}