| /** |
| * 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; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.InetAddress; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.security.KeyPair; |
| import java.security.PrivilegedExceptionAction; |
| import java.util.Properties; |
| import java.util.UUID; |
| import java.util.concurrent.Callable; |
| |
| import org.apache.commons.lang3.RandomStringUtils; |
| import org.apache.hadoop.classification.InterfaceAudience; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.fs.CommonConfigurationKeysPublic; |
| import org.apache.hadoop.hdds.HddsConfigKeys; |
| import org.apache.hadoop.hdds.conf.OzoneConfiguration; |
| import org.apache.hadoop.hdds.protocol.SCMSecurityProtocol; |
| import org.apache.hadoop.hdds.scm.HddsTestUtils; |
| import org.apache.hadoop.hdds.scm.ScmConfigKeys; |
| import org.apache.hadoop.hdds.scm.ScmInfo; |
| import org.apache.hadoop.hdds.scm.client.HddsClientUtils; |
| import org.apache.hadoop.hdds.scm.server.SCMStorageConfig; |
| import org.apache.hadoop.hdds.scm.server.StorageContainerManager; |
| import org.apache.hadoop.hdds.security.x509.SecurityConfig; |
| import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec; |
| import org.apache.hadoop.hdds.security.x509.keys.HDDSKeyGenerator; |
| import org.apache.hadoop.hdds.security.x509.keys.KeyCodec; |
| import org.apache.hadoop.io.Text; |
| import org.apache.hadoop.ipc.Client; |
| import org.apache.hadoop.ipc.RPC; |
| import org.apache.hadoop.ipc.RemoteException; |
| import org.apache.hadoop.ipc.Server; |
| import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem; |
| import org.apache.hadoop.minikdc.MiniKdc; |
| import org.apache.hadoop.net.NetUtils; |
| import org.apache.hadoop.net.ServerSocketUtil; |
| import org.apache.hadoop.ozone.client.CertificateClientTestImpl; |
| import org.apache.hadoop.ozone.common.Storage; |
| import org.apache.hadoop.ozone.om.OMConfigKeys; |
| import org.apache.hadoop.ozone.om.OMStorage; |
| import org.apache.hadoop.ozone.om.OzoneManager; |
| import org.apache.hadoop.ozone.om.exceptions.OMException; |
| import org.apache.hadoop.ozone.om.helpers.S3SecretValue; |
| import org.apache.hadoop.ozone.om.protocolPB.OzoneManagerProtocolClientSideTranslatorPB; |
| import org.apache.hadoop.ozone.om.protocolPB.OzoneManagerProtocolPB; |
| import org.apache.hadoop.ozone.security.OzoneTokenIdentifier; |
| import org.apache.hadoop.security.KerberosAuthException; |
| import org.apache.hadoop.security.SaslRpcServer.AuthMethod; |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.apache.hadoop.security.authentication.client.AuthenticationException; |
| import org.apache.hadoop.security.token.Token; |
| import org.apache.hadoop.test.GenericTestUtils; |
| import org.apache.hadoop.test.GenericTestUtils.LogCapturer; |
| import org.apache.hadoop.test.LambdaTestUtils; |
| import org.bouncycastle.asn1.x500.RDN; |
| import org.bouncycastle.asn1.x500.X500Name; |
| import org.bouncycastle.asn1.x500.style.BCStyle; |
| import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TemporaryFolder; |
| import org.junit.rules.Timeout; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.security.cert.X509Certificate; |
| import java.time.LocalDate; |
| import java.time.LocalDateTime; |
| import java.time.temporal.ChronoUnit; |
| import java.util.Date; |
| |
| import static junit.framework.TestCase.assertNotNull; |
| import static org.apache.hadoop.hdds.HddsConfigKeys.OZONE_METADATA_DIRS; |
| import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ADMINISTRATORS; |
| import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ENABLED; |
| import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_SECURITY_ENABLED_KEY; |
| import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INVALID_AUTH_METHOD; |
| import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.TOKEN_ERROR_OTHER; |
| import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.TOKEN_EXPIRED; |
| import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.VOLUME_NOT_FOUND; |
| import static org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod.KERBEROS; |
| 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 static org.junit.Assert.fail; |
| import static org.slf4j.event.Level.INFO; |
| |
| /** |
| * Test class to for security enabled Ozone cluster. |
| */ |
| @InterfaceAudience.Private |
| public final class TestSecureOzoneCluster { |
| |
| private static final String TEST_USER = "testUgiUser@EXAMPLE.COM"; |
| private static final String COMPONENT = "test"; |
| private static final int CLIENT_TIMEOUT = 2 * 1000; |
| private Logger logger = LoggerFactory |
| .getLogger(TestSecureOzoneCluster.class); |
| |
| @Rule |
| public Timeout timeout = new Timeout(80000); |
| |
| private MiniKdc miniKdc; |
| private OzoneConfiguration conf; |
| private File workDir; |
| private static Properties securityProperties; |
| private File scmKeytab; |
| private File spnegoKeytab; |
| private File omKeyTab; |
| private File testUserKeytab; |
| private String curUser; |
| private String testUserPrincipal; |
| private UserGroupInformation testKerberosUgi; |
| private StorageContainerManager scm; |
| private OzoneManager om; |
| private String host; |
| |
| private static String clusterId; |
| private static String scmId; |
| private static String omId; |
| private OzoneManagerProtocolClientSideTranslatorPB omClient; |
| private KeyPair keyPair; |
| private Path metaDirPath; |
| @Rule |
| public TemporaryFolder folder= new TemporaryFolder(); |
| private String omCertSerialId = "9879877970576"; |
| |
| @Before |
| public void init() { |
| try { |
| conf = new OzoneConfiguration(); |
| conf.set(ScmConfigKeys.OZONE_SCM_CLIENT_ADDRESS_KEY, "localhost"); |
| |
| conf.setInt(ScmConfigKeys.OZONE_SCM_CLIENT_PORT_KEY, ServerSocketUtil |
| .getPort(ScmConfigKeys.OZONE_SCM_CLIENT_PORT_DEFAULT, 100)); |
| conf.setInt(ScmConfigKeys.OZONE_SCM_DATANODE_PORT_KEY, ServerSocketUtil |
| .getPort(ScmConfigKeys.OZONE_SCM_DATANODE_PORT_DEFAULT, 100)); |
| conf.setInt(ScmConfigKeys.OZONE_SCM_BLOCK_CLIENT_PORT_KEY, |
| ServerSocketUtil.getPort(ScmConfigKeys |
| .OZONE_SCM_BLOCK_CLIENT_PORT_DEFAULT, 100)); |
| conf.setInt(ScmConfigKeys.OZONE_SCM_SECURITY_SERVICE_PORT_KEY, |
| ServerSocketUtil.getPort(ScmConfigKeys |
| .OZONE_SCM_SECURITY_SERVICE_PORT_DEFAULT, 100)); |
| |
| DefaultMetricsSystem.setMiniClusterMode(true); |
| final String path = folder.newFolder().toString(); |
| metaDirPath = Paths.get(path, "om-meta"); |
| conf.set(OZONE_METADATA_DIRS, metaDirPath.toString()); |
| conf.setBoolean(OZONE_SECURITY_ENABLED_KEY, true); |
| conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, |
| KERBEROS.toString()); |
| |
| startMiniKdc(); |
| setSecureConfig(conf); |
| createCredentialsInKDC(conf, miniKdc); |
| generateKeyPair(conf); |
| // OzoneManager.setTestSecureOmFlag(true); |
| } catch (IOException e) { |
| logger.error("Failed to initialize TestSecureOzoneCluster", e); |
| } catch (Exception e) { |
| logger.error("Failed to initialize TestSecureOzoneCluster", e); |
| } |
| } |
| |
| @After |
| public void stop() { |
| try { |
| stopMiniKdc(); |
| if (scm != null) { |
| scm.stop(); |
| } |
| if (om != null) { |
| om.stop(); |
| } |
| if (omClient != null) { |
| omClient.close(); |
| } |
| } catch (Exception e) { |
| logger.error("Failed to stop TestSecureOzoneCluster", e); |
| } |
| } |
| |
| private void createCredentialsInKDC(Configuration configuration, |
| MiniKdc kdc) throws Exception { |
| createPrincipal(scmKeytab, |
| configuration.get(ScmConfigKeys.HDDS_SCM_KERBEROS_PRINCIPAL_KEY)); |
| createPrincipal(spnegoKeytab, |
| configuration.get(ScmConfigKeys |
| .HDDS_SCM_HTTP_KERBEROS_PRINCIPAL_KEY)); |
| createPrincipal(testUserKeytab, testUserPrincipal); |
| createPrincipal(omKeyTab, |
| configuration.get(OMConfigKeys.OZONE_OM_KERBEROS_PRINCIPAL_KEY)); |
| } |
| |
| private void createPrincipal(File keytab, String... principal) |
| throws Exception { |
| miniKdc.createPrincipal(keytab, principal); |
| } |
| |
| private void startMiniKdc() throws Exception { |
| workDir = GenericTestUtils |
| .getTestDir(TestSecureOzoneCluster.class.getSimpleName()); |
| securityProperties = MiniKdc.createConf(); |
| miniKdc = new MiniKdc(securityProperties, workDir); |
| miniKdc.start(); |
| } |
| |
| private void stopMiniKdc() { |
| miniKdc.stop(); |
| } |
| |
| private void setSecureConfig(Configuration configuration) throws IOException { |
| configuration.setBoolean(OZONE_SECURITY_ENABLED_KEY, true); |
| configuration.setBoolean(OZONE_ENABLED, true); |
| host = InetAddress.getLocalHost().getCanonicalHostName() |
| .toLowerCase(); |
| String realm = miniKdc.getRealm(); |
| curUser = UserGroupInformation.getCurrentUser() |
| .getUserName(); |
| configuration.set( |
| CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, |
| "kerberos"); |
| configuration.set(OZONE_ADMINISTRATORS, curUser); |
| |
| configuration.set(ScmConfigKeys.HDDS_SCM_KERBEROS_PRINCIPAL_KEY, |
| "scm/" + host + "@" + realm); |
| configuration.set(ScmConfigKeys.HDDS_SCM_HTTP_KERBEROS_PRINCIPAL_KEY, |
| "HTTP_SCM/" + host + "@" + realm); |
| |
| configuration.set(OMConfigKeys.OZONE_OM_KERBEROS_PRINCIPAL_KEY, |
| "om/" + host + "@" + realm); |
| configuration.set(OMConfigKeys.OZONE_OM_HTTP_KERBEROS_PRINCIPAL_KEY, |
| "HTTP_OM/" + host + "@" + realm); |
| |
| scmKeytab = new File(workDir, "scm.keytab"); |
| spnegoKeytab = new File(workDir, "http.keytab"); |
| omKeyTab = new File(workDir, "om.keytab"); |
| testUserKeytab = new File(workDir, "testuser.keytab"); |
| testUserPrincipal = "test@" + realm; |
| |
| configuration.set(ScmConfigKeys.HDDS_SCM_KERBEROS_KEYTAB_FILE_KEY, |
| scmKeytab.getAbsolutePath()); |
| configuration.set( |
| ScmConfigKeys.HDDS_SCM_HTTP_KERBEROS_KEYTAB_FILE_KEY, |
| spnegoKeytab.getAbsolutePath()); |
| configuration.set(OMConfigKeys.OZONE_OM_KERBEROS_KEYTAB_FILE_KEY, |
| omKeyTab.getAbsolutePath()); |
| conf.set(OMConfigKeys.OZONE_OM_HTTP_KERBEROS_KEYTAB_FILE, |
| spnegoKeytab.getAbsolutePath()); |
| } |
| |
| @Test |
| public void testSecureScmStartupSuccess() throws Exception { |
| |
| initSCM(); |
| scm = StorageContainerManager.createSCM(conf); |
| //Reads the SCM Info from SCM instance |
| ScmInfo scmInfo = scm.getClientProtocolServer().getScmInfo(); |
| Assert.assertEquals(clusterId, scmInfo.getClusterId()); |
| Assert.assertEquals(scmId, scmInfo.getScmId()); |
| } |
| |
| @Test |
| public void testSCMSecurityProtocol() throws Exception { |
| |
| initSCM(); |
| scm = HddsTestUtils.getScm(conf); |
| //Reads the SCM Info from SCM instance |
| try { |
| scm.start(); |
| |
| // Case 1: User with Kerberos credentials should succeed. |
| UserGroupInformation ugi = |
| UserGroupInformation.loginUserFromKeytabAndReturnUGI( |
| testUserPrincipal, testUserKeytab.getCanonicalPath()); |
| ugi.setAuthenticationMethod(KERBEROS); |
| SCMSecurityProtocol scmSecurityProtocolClient = |
| HddsClientUtils.getScmSecurityClient(conf, ugi); |
| assertNotNull(scmSecurityProtocolClient); |
| String caCert = scmSecurityProtocolClient.getCACertificate(); |
| LambdaTestUtils.intercept(RemoteException.class, "Certificate not found", |
| () -> scmSecurityProtocolClient.getCertificate("1")); |
| assertNotNull(caCert); |
| |
| // Case 2: User without Kerberos credentials should fail. |
| ugi = UserGroupInformation.createRemoteUser("test"); |
| ugi.setAuthenticationMethod(AuthMethod.TOKEN); |
| SCMSecurityProtocol finalScmSecurityProtocolClient = |
| HddsClientUtils.getScmSecurityClient(conf, ugi); |
| |
| LambdaTestUtils.intercept(IOException.class, "Client cannot" + |
| " authenticate via:[KERBEROS]", |
| () -> finalScmSecurityProtocolClient.getCACertificate()); |
| LambdaTestUtils.intercept(IOException.class, "Client cannot" + |
| " authenticate via:[KERBEROS]", |
| () -> finalScmSecurityProtocolClient.getCertificate("1")); |
| } finally { |
| if (scm != null) { |
| scm.stop(); |
| } |
| } |
| } |
| |
| private void initSCM() |
| throws IOException, AuthenticationException { |
| |
| clusterId = UUID.randomUUID().toString(); |
| scmId = UUID.randomUUID().toString(); |
| omId = UUID.randomUUID().toString(); |
| |
| final String path = folder.newFolder().toString(); |
| Path scmPath = Paths.get(path, "scm-meta"); |
| File temp = scmPath.toFile(); |
| if(!temp.exists()) { |
| temp.mkdirs(); |
| } |
| conf.set(HddsConfigKeys.OZONE_METADATA_DIRS, scmPath.toString()); |
| conf.setBoolean(OzoneConfigKeys.OZONE_ENABLED, true); |
| SCMStorageConfig scmStore = new SCMStorageConfig(conf); |
| scmStore.setClusterId(clusterId); |
| scmStore.setScmId(scmId); |
| // writes the version file properties |
| scmStore.initialize(); |
| } |
| |
| @Test |
| public void testSecureScmStartupFailure() throws Exception { |
| initSCM(); |
| conf.set(ScmConfigKeys.HDDS_SCM_KERBEROS_KEYTAB_FILE_KEY, ""); |
| conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, |
| "kerberos"); |
| |
| LambdaTestUtils.intercept(IOException.class, |
| "Running in secure mode, but config doesn't have a keytab", |
| () -> { |
| StorageContainerManager.createSCM(conf); |
| }); |
| |
| conf.set(ScmConfigKeys.HDDS_SCM_KERBEROS_PRINCIPAL_KEY, |
| "scm/_HOST@EXAMPLE.com"); |
| conf.set(ScmConfigKeys.HDDS_SCM_KERBEROS_KEYTAB_FILE_KEY, |
| "/etc/security/keytabs/scm.keytab"); |
| |
| testCommonKerberosFailures( |
| () -> StorageContainerManager.createSCM(conf)); |
| |
| } |
| |
| private void testCommonKerberosFailures(Callable callable) throws Exception { |
| LambdaTestUtils.intercept(KerberosAuthException.class, "failure " |
| + "to login: for principal:", callable); |
| conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, |
| "OAuth2"); |
| |
| LambdaTestUtils.intercept(IllegalArgumentException.class, "Invalid" |
| + " attribute value for hadoop.security.authentication of OAuth2", |
| callable); |
| |
| conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, |
| "KERBEROS_SSL"); |
| LambdaTestUtils.intercept(AuthenticationException.class, |
| "KERBEROS_SSL authentication method not", |
| callable); |
| } |
| |
| /** |
| * Tests the secure om Initialization Failure. |
| * |
| * @throws IOException |
| */ |
| @Test |
| public void testSecureOMInitializationFailure() throws Exception { |
| initSCM(); |
| // Create a secure SCM instance as om client will connect to it |
| scm = StorageContainerManager.createSCM(conf); |
| setupOm(conf); |
| conf.set(OMConfigKeys.OZONE_OM_KERBEROS_PRINCIPAL_KEY, |
| "non-existent-user@EXAMPLE.com"); |
| testCommonKerberosFailures(() -> OzoneManager.createOm(conf)); |
| } |
| |
| /** |
| * Tests the secure om Initialization success. |
| * |
| * @throws IOException |
| */ |
| @Test |
| public void testSecureOmInitializationSuccess() throws Exception { |
| initSCM(); |
| // Create a secure SCM instance as om client will connect to it |
| scm = StorageContainerManager.createSCM(conf); |
| LogCapturer logs = LogCapturer.captureLogs(OzoneManager.LOG); |
| GenericTestUtils.setLogLevel(OzoneManager.LOG, INFO); |
| |
| setupOm(conf); |
| try { |
| om.start(); |
| } catch (Exception ex) { |
| // Expects timeout failure from scmClient in om but om user login via |
| // kerberos should succeed. |
| assertTrue(logs.getOutput().contains("Ozone Manager login" |
| + " successful")); |
| } |
| } |
| |
| /** |
| * Performs following tests for delegation token. |
| * 1. Get valid delegation token |
| * 2. Test successful token renewal. |
| * 3. Client can authenticate using token. |
| * 4. Delegation token renewal without Kerberos auth fails. |
| * 5. Test success of token cancellation. |
| * 5. Test failure of token cancellation. |
| * |
| * @throws Exception |
| */ |
| @Test |
| public void testDelegationToken() throws Exception { |
| |
| // Capture logs for assertions |
| LogCapturer logs = LogCapturer.captureLogs(Server.AUDITLOG); |
| LogCapturer omLogs = LogCapturer.captureLogs(OzoneManager.getLogger()); |
| GenericTestUtils |
| .setLogLevel(LoggerFactory.getLogger(Server.class.getName()), INFO); |
| |
| // Setup secure OM for start |
| setupOm(conf); |
| long omVersion = |
| RPC.getProtocolVersion(OzoneManagerProtocolPB.class); |
| try { |
| // Start OM |
| om.setCertClient(new CertificateClientTestImpl(conf)); |
| om.start(); |
| UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); |
| String username = ugi.getUserName(); |
| |
| // Get first OM client which will authenticate via Kerberos |
| omClient = new OzoneManagerProtocolClientSideTranslatorPB( |
| RPC.getProxy(OzoneManagerProtocolPB.class, omVersion, |
| OmUtils.getOmAddress(conf), ugi, conf, |
| NetUtils.getDefaultSocketFactory(conf), |
| CLIENT_TIMEOUT), RandomStringUtils.randomAscii(5)); |
| |
| // Assert if auth was successful via Kerberos |
| assertFalse(logs.getOutput().contains( |
| "Auth successful for " + username + " (auth:KERBEROS)")); |
| |
| // Case 1: Test successful delegation token. |
| Token<OzoneTokenIdentifier> token = omClient |
| .getDelegationToken(new Text("om")); |
| |
| // Case 2: Test successful token renewal. |
| long renewalTime = omClient.renewDelegationToken(token); |
| assertTrue(renewalTime > 0); |
| |
| // Check if token is of right kind and renewer is running om instance |
| Assert.assertEquals(token.getKind().toString(), "OzoneToken"); |
| Assert.assertEquals(token.getService().toString(), |
| OmUtils.getOmRpcAddress(conf)); |
| omClient.close(); |
| |
| // Create a remote ugi and set its authentication method to Token |
| UserGroupInformation testUser = UserGroupInformation |
| .createRemoteUser(TEST_USER); |
| testUser.addToken(token); |
| testUser.setAuthenticationMethod(AuthMethod.TOKEN); |
| UserGroupInformation.setLoginUser(testUser); |
| |
| // Get Om client, this time authentication should happen via Token |
| testUser.doAs(new PrivilegedExceptionAction<Void>() { |
| @Override |
| public Void run() throws Exception { |
| omClient = new OzoneManagerProtocolClientSideTranslatorPB( |
| RPC.getProxy(OzoneManagerProtocolPB.class, omVersion, |
| OmUtils.getOmAddress(conf), testUser, conf, |
| NetUtils.getDefaultSocketFactory(conf), CLIENT_TIMEOUT), |
| RandomStringUtils.randomAscii(5)); |
| return null; |
| } |
| }); |
| |
| // Case 3: Test Client can authenticate using token. |
| assertFalse(logs.getOutput().contains( |
| "Auth successful for " + username + " (auth:TOKEN)")); |
| OzoneTestUtils.expectOmException(VOLUME_NOT_FOUND, |
| () -> omClient.deleteVolume("vol1")); |
| assertTrue(logs.getOutput().contains("Auth successful for " |
| + username + " (auth:TOKEN)")); |
| |
| // Case 4: Test failure of token renewal. |
| // Call to renewDelegationToken will fail but it will confirm that |
| // initial connection via DT succeeded |
| omLogs.clearOutput(); |
| |
| LambdaTestUtils.intercept(OMException.class, "INVALID_AUTH_METHOD", |
| () -> { |
| try { |
| omClient.renewDelegationToken(token); |
| } catch (OMException ex) { |
| assertTrue(ex.getResult().equals(INVALID_AUTH_METHOD)); |
| throw ex; |
| } |
| }); |
| assertTrue(logs.getOutput().contains( |
| "Auth successful for " + username + " (auth:TOKEN)")); |
| omLogs.clearOutput(); |
| //testUser.setAuthenticationMethod(AuthMethod.KERBEROS); |
| UserGroupInformation.setLoginUser(ugi); |
| omClient = new OzoneManagerProtocolClientSideTranslatorPB( |
| RPC.getProxy(OzoneManagerProtocolPB.class, omVersion, |
| OmUtils.getOmAddress(conf), ugi, conf, |
| NetUtils.getDefaultSocketFactory(conf), |
| Client.getRpcTimeout(conf)), RandomStringUtils.randomAscii(5)); |
| |
| // Case 5: Test success of token cancellation. |
| omClient.cancelDelegationToken(token); |
| omClient.close(); |
| |
| // Wait for client to timeout |
| Thread.sleep(CLIENT_TIMEOUT); |
| |
| assertFalse(logs.getOutput().contains("Auth failed for")); |
| |
| // Case 6: Test failure of token cancellation. |
| // Get Om client, this time authentication using Token will fail as |
| // token is not in cache anymore. |
| omClient = new OzoneManagerProtocolClientSideTranslatorPB( |
| RPC.getProxy(OzoneManagerProtocolPB.class, omVersion, |
| OmUtils.getOmAddress(conf), testUser, conf, |
| NetUtils.getDefaultSocketFactory(conf), |
| Client.getRpcTimeout(conf)), RandomStringUtils.randomAscii(5)); |
| LambdaTestUtils.intercept(OMException.class, "Cancel delegation " + |
| "token failed", |
| () -> { |
| try { |
| omClient.cancelDelegationToken(token); |
| } catch (OMException ex) { |
| assertTrue(ex.getResult().equals(TOKEN_ERROR_OTHER)); |
| throw ex; |
| } |
| }); |
| |
| assertTrue(logs.getOutput().contains("Auth failed for")); |
| } finally { |
| om.stop(); |
| om.join(); |
| } |
| } |
| |
| private void generateKeyPair(OzoneConfiguration config) throws Exception { |
| HDDSKeyGenerator keyGenerator = new HDDSKeyGenerator(conf); |
| keyPair = keyGenerator.generateKey(); |
| KeyCodec pemWriter = new KeyCodec(new SecurityConfig(config), COMPONENT); |
| pemWriter.writeKey(keyPair, true); |
| } |
| |
| /** |
| * Tests delegation token renewal. |
| * |
| * @throws Exception |
| */ |
| @Test |
| public void testDelegationTokenRenewal() throws Exception { |
| GenericTestUtils |
| .setLogLevel(LoggerFactory.getLogger(Server.class.getName()), INFO); |
| LogCapturer omLogs = LogCapturer.captureLogs(OzoneManager.getLogger()); |
| |
| // Setup secure OM for start. |
| OzoneConfiguration newConf = new OzoneConfiguration(conf); |
| newConf.setLong(OMConfigKeys.DELEGATION_TOKEN_MAX_LIFETIME_KEY, 500); |
| setupOm(newConf); |
| long omVersion = |
| RPC.getProtocolVersion(OzoneManagerProtocolPB.class); |
| OzoneManager.setTestSecureOmFlag(true); |
| // Start OM |
| |
| try { |
| om.setCertClient(new CertificateClientTestImpl(conf)); |
| om.start(); |
| |
| UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); |
| |
| // Get first OM client which will authenticate via Kerberos |
| omClient = new OzoneManagerProtocolClientSideTranslatorPB(RPC.getProxy( |
| OzoneManagerProtocolPB.class, omVersion, OmUtils.getOmAddress(conf), |
| ugi, conf, NetUtils.getDefaultSocketFactory(conf), |
| CLIENT_TIMEOUT), RandomStringUtils.randomAscii(5)); |
| |
| // Since client is already connected get a delegation token |
| Token<OzoneTokenIdentifier> token = omClient.getDelegationToken( |
| new Text("om")); |
| |
| // Check if token is of right kind and renewer is running om instance |
| Assert.assertEquals(token.getKind().toString(), "OzoneToken"); |
| Assert.assertEquals(token.getService().toString(), OmUtils |
| .getOmRpcAddress(conf)); |
| |
| // Renew delegation token |
| long expiryTime = omClient.renewDelegationToken(token); |
| assertTrue(expiryTime > 0); |
| omLogs.clearOutput(); |
| |
| // Test failure of delegation renewal |
| // 1. When token maxExpiryTime exceeds |
| Thread.sleep(500); |
| LambdaTestUtils.intercept(OMException.class, |
| "TOKEN_EXPIRED", |
| () -> { |
| try { |
| omClient.renewDelegationToken(token); |
| } catch (OMException ex) { |
| assertTrue(ex.getResult().equals(TOKEN_EXPIRED)); |
| throw ex; |
| } |
| }); |
| |
| omLogs.clearOutput(); |
| |
| // 2. When renewer doesn't match (implicitly covers when renewer is |
| // null or empty ) |
| Token token2 = omClient.getDelegationToken(new Text("randomService")); |
| LambdaTestUtils.intercept(OMException.class, |
| "Delegation token renewal failed", |
| () -> omClient.renewDelegationToken(token2)); |
| assertTrue(omLogs.getOutput().contains(" with non-matching " + |
| "renewer randomService")); |
| omLogs.clearOutput(); |
| |
| // 3. Test tampered token |
| OzoneTokenIdentifier tokenId = OzoneTokenIdentifier.readProtoBuf( |
| token.getIdentifier()); |
| tokenId.setRenewer(new Text("om")); |
| tokenId.setMaxDate(System.currentTimeMillis() * 2); |
| Token<OzoneTokenIdentifier> tamperedToken = new Token<>( |
| tokenId.getBytes(), token2.getPassword(), token2.getKind(), |
| token2.getService()); |
| LambdaTestUtils.intercept(OMException.class, |
| "Delegation token renewal failed", |
| () -> omClient.renewDelegationToken(tamperedToken)); |
| assertTrue(omLogs.getOutput().contains("can't be found in " + |
| "cache")); |
| omLogs.clearOutput(); |
| |
| } finally { |
| om.stop(); |
| om.join(); |
| } |
| } |
| |
| private void setupOm(OzoneConfiguration config) throws Exception { |
| OMStorage omStore = new OMStorage(config); |
| omStore.setClusterId("testClusterId"); |
| omStore.setScmId("testScmId"); |
| omStore.setOmCertSerialId(omCertSerialId); |
| // writes the version file properties |
| omStore.initialize(); |
| OzoneManager.setTestSecureOmFlag(true); |
| om = OzoneManager.createOm(config); |
| } |
| |
| @Test |
| public void testGetS3Secret() throws Exception { |
| |
| // Setup secure OM for start |
| setupOm(conf); |
| long omVersion = |
| RPC.getProtocolVersion(OzoneManagerProtocolPB.class); |
| try { |
| // Start OM |
| om.setCertClient(new CertificateClientTestImpl(conf)); |
| om.start(); |
| UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); |
| String username = ugi.getUserName(); |
| |
| // Get first OM client which will authenticate via Kerberos |
| omClient = new OzoneManagerProtocolClientSideTranslatorPB( |
| RPC.getProxy(OzoneManagerProtocolPB.class, omVersion, |
| OmUtils.getOmAddress(conf), ugi, conf, |
| NetUtils.getDefaultSocketFactory(conf), |
| CLIENT_TIMEOUT), RandomStringUtils.randomAscii(5)); |
| |
| //Creates a secret since it does not exist |
| S3SecretValue firstAttempt = omClient |
| .getS3Secret(UserGroupInformation.getCurrentUser().getUserName()); |
| |
| //Fetches the secret from db since it was created in previous step |
| S3SecretValue secondAttempt = omClient |
| .getS3Secret(UserGroupInformation.getCurrentUser().getUserName()); |
| |
| //secret fetched on both attempts must be same |
| assertTrue(firstAttempt.getAwsSecret() |
| .equals(secondAttempt.getAwsSecret())); |
| |
| //access key fetched on both attempts must be same |
| assertTrue(firstAttempt.getAwsAccessKey() |
| .equals(secondAttempt.getAwsAccessKey())); |
| |
| |
| try { |
| omClient.getS3Secret("HADOOP/JOHNDOE"); |
| fail("testGetS3Secret failed"); |
| } catch (IOException ex) { |
| GenericTestUtils.assertExceptionContains("USER_MISMATCH", ex); |
| } |
| } finally { |
| if(om != null){ |
| om.stop(); |
| } |
| } |
| } |
| |
| /** |
| * Tests functionality to init secure OM when it is already initialized. |
| */ |
| @Test |
| public void testSecureOmReInit() throws Exception { |
| LogCapturer omLogs = |
| LogCapturer.captureLogs(OzoneManager.getLogger()); |
| omLogs.clearOutput(); |
| |
| /** |
| * As all these processes run inside the same JVM, there are issues around |
| * the Hadoop UGI if different processes run with different principals. |
| * In this test, the OM has to contact the SCM to download certs. SCM runs |
| * as scm/host@REALM, but the OM logs in as om/host@REALM, and then the test |
| * fails, and the OM is unable to contact the SCM due to kerberos login |
| * issues. To work around that, have the OM run as the same principal as the |
| * SCM, and then the test passes. |
| * |
| * TODO: Need to look into this further to see if there is a better way to |
| * address this problem. |
| */ |
| String realm = miniKdc.getRealm(); |
| conf.set(OMConfigKeys.OZONE_OM_KERBEROS_PRINCIPAL_KEY, |
| "scm/" + host + "@" + realm); |
| omKeyTab = new File(workDir, "scm.keytab"); |
| conf.set(OMConfigKeys.OZONE_OM_KERBEROS_KEYTAB_FILE_KEY, |
| omKeyTab.getAbsolutePath()); |
| |
| initSCM(); |
| try { |
| scm = HddsTestUtils.getScm(conf); |
| scm.start(); |
| conf.setBoolean(OZONE_SECURITY_ENABLED_KEY, false); |
| OMStorage omStore = new OMStorage(conf); |
| initializeOmStorage(omStore); |
| OzoneManager.setTestSecureOmFlag(true); |
| om = OzoneManager.createOm(conf); |
| |
| assertNull(om.getCertificateClient()); |
| assertFalse(omLogs.getOutput().contains("Init response: GETCERT")); |
| assertFalse(omLogs.getOutput().contains("Successfully stored " + |
| "SCM signed certificate")); |
| |
| conf.setBoolean(OZONE_SECURITY_ENABLED_KEY, true); |
| OzoneManager.omInit(conf); |
| om.stop(); |
| om = OzoneManager.createOm(conf); |
| |
| Assert.assertNotNull(om.getCertificateClient()); |
| Assert.assertNotNull(om.getCertificateClient().getPublicKey()); |
| Assert.assertNotNull(om.getCertificateClient().getPrivateKey()); |
| Assert.assertNotNull(om.getCertificateClient().getCertificate()); |
| assertTrue(omLogs.getOutput().contains("Init response: GETCERT")); |
| assertTrue(omLogs.getOutput().contains("Successfully stored " + |
| "SCM signed certificate")); |
| X509Certificate certificate = om.getCertificateClient().getCertificate(); |
| validateCertificate(certificate); |
| |
| } finally { |
| if (scm != null) { |
| scm.stop(); |
| } |
| } |
| |
| } |
| |
| /** |
| * Test functionality to get SCM signed certificate for OM. |
| */ |
| @Test |
| public void testSecureOmInitSuccess() throws Exception { |
| LogCapturer omLogs = |
| LogCapturer.captureLogs(OzoneManager.getLogger()); |
| omLogs.clearOutput(); |
| initSCM(); |
| try { |
| scm = HddsTestUtils.getScm(conf); |
| scm.start(); |
| |
| OMStorage omStore = new OMStorage(conf); |
| initializeOmStorage(omStore); |
| OzoneManager.setTestSecureOmFlag(true); |
| om = OzoneManager.createOm(conf); |
| |
| Assert.assertNotNull(om.getCertificateClient()); |
| Assert.assertNotNull(om.getCertificateClient().getPublicKey()); |
| Assert.assertNotNull(om.getCertificateClient().getPrivateKey()); |
| Assert.assertNotNull(om.getCertificateClient().getCertificate()); |
| assertTrue(omLogs.getOutput().contains("Init response: GETCERT")); |
| assertTrue(omLogs.getOutput().contains("Successfully stored " + |
| "SCM signed certificate")); |
| X509Certificate certificate = om.getCertificateClient().getCertificate(); |
| validateCertificate(certificate); |
| String pemEncodedCACert = |
| scm.getSecurityProtocolServer().getCACertificate(); |
| X509Certificate caCert = CertificateCodec.getX509Cert(pemEncodedCACert); |
| X509Certificate caCertStored = om.getCertificateClient() |
| .getCertificate(caCert.getSerialNumber().toString()); |
| assertEquals(caCert, caCertStored); |
| } finally { |
| if (scm != null) { |
| scm.stop(); |
| } |
| if (om != null) { |
| om.stop(); |
| } |
| |
| } |
| |
| } |
| |
| public void validateCertificate(X509Certificate cert) throws Exception { |
| |
| // Assert that we indeed have a self signed certificate. |
| X500Name x500Issuer = new JcaX509CertificateHolder(cert).getIssuer(); |
| RDN cn = x500Issuer.getRDNs(BCStyle.CN)[0]; |
| String hostName = InetAddress.getLocalHost().getHostName(); |
| String scmUser = "scm@" + hostName; |
| Assert.assertEquals(scmUser, cn.getFirst().getValue().toString()); |
| |
| // Subject name should be om login user in real world but in this test |
| // UGI has scm user context. |
| Assert.assertEquals(scmUser, cn.getFirst().getValue().toString()); |
| |
| LocalDate today = LocalDateTime.now().toLocalDate(); |
| Date invalidDate; |
| |
| // Make sure the end date is honored. |
| invalidDate = java.sql.Date.valueOf(today.plus(1, ChronoUnit.DAYS)); |
| assertTrue(cert.getNotAfter().after(invalidDate)); |
| |
| invalidDate = java.sql.Date.valueOf(today.plus(400, ChronoUnit.DAYS)); |
| assertTrue(cert.getNotAfter().before(invalidDate)); |
| |
| assertTrue(cert.getSubjectDN().toString().contains(scmId)); |
| assertTrue(cert.getSubjectDN().toString().contains(clusterId)); |
| |
| assertTrue(cert.getIssuerDN().toString().contains(scmUser)); |
| assertTrue(cert.getIssuerDN().toString().contains(scmId)); |
| assertTrue(cert.getIssuerDN().toString().contains(clusterId)); |
| |
| // Verify that certificate matches the public key. |
| String encodedKey1 = cert.getPublicKey().toString(); |
| String encodedKey2 = om.getCertificateClient().getPublicKey().toString(); |
| Assert.assertEquals(encodedKey1, encodedKey2); |
| } |
| |
| private void initializeOmStorage(OMStorage omStorage) throws IOException { |
| if (omStorage.getState() == Storage.StorageState.INITIALIZED) { |
| return; |
| } |
| omStorage.setClusterId(clusterId); |
| omStorage.setScmId(scmId); |
| omStorage.setOmId(omId); |
| // Initialize ozone certificate client if security is enabled. |
| if (OzoneSecurityUtil.isSecurityEnabled(conf)) { |
| OzoneManager.initializeSecurity(conf, omStorage); |
| } |
| omStorage.initialize(); |
| } |
| } |