[AMBARI-23921] If Kerberos is enabled, then stack upgrade prerequisite check should...
authorRobert Levas <rlevas@hortonworks.com>
Tue, 22 May 2018 02:30:19 +0000 (22:30 -0400)
committerRobert Levas <rlevas@users.noreply.github.com>
Tue, 22 May 2018 12:28:06 +0000 (08:28 -0400)
ambari-server/src/main/java/org/apache/ambari/server/checks/CheckDescription.java
ambari-server/src/main/java/org/apache/ambari/server/checks/KerberosAdminPersistedCredentialCheck.java [new file with mode: 0644]
ambari-server/src/test/java/org/apache/ambari/server/checks/KerberosAdminPersistedCredentialCheckTest.java [new file with mode: 0644]

index 4ef5d50..76b8e23 100644 (file)
@@ -379,6 +379,19 @@ public class CheckDescription {
         "Hence need to migrate existing data to newer formats post upgrade. " +
         "To migrate existing data, Kindly refer and follow Apache Atlas documentation for 1.0 release.").build());
 
+  public static CheckDescription KERBEROS_ADMIN_CREDENTIAL_CHECK = new CheckDescription("KERBEROS_ADMIN_CREDENTIAL_CHECK",
+      PrereqCheckType.CLUSTER,
+      "The KDC administrator credentials need to be stored in Ambari persisted credential store.",
+      new ImmutableMap.Builder<String, String>()
+          .put(KerberosAdminPersistedCredentialCheck.KEY_PERSISTED_STORE_NOT_CONFIGURED,
+              "Ambari's credential store has not been configured.  " +
+                  "This is needed so the KDC administrator credential may be stored long enough to ensure it will be around if needed during the upgrade process.")
+          .put(KerberosAdminPersistedCredentialCheck.KEY_CREDENTIAL_NOT_STORED,
+              "The KDC administrator credential has not been stored in the persisted credential store. " +
+                  "Visit the Kerberos administrator page to set the credential. " +
+                  "This is needed so the KDC administrator credential may be stored long enough to ensure it will be around if needed during the upgrade process.")
+          .build());
+
   private String m_name;
   private PrereqCheckType m_type;
   private String m_description;
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/checks/KerberosAdminPersistedCredentialCheck.java b/ambari-server/src/main/java/org/apache/ambari/server/checks/KerberosAdminPersistedCredentialCheck.java
new file mode 100644 (file)
index 0000000..1e5821d
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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.ambari.server.checks;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.controller.KerberosHelper;
+import org.apache.ambari.server.controller.PrereqCheckRequest;
+import org.apache.ambari.server.security.encryption.CredentialStoreService;
+import org.apache.ambari.server.security.encryption.CredentialStoreType;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.SecurityType;
+import org.apache.ambari.server.state.stack.PrereqCheckStatus;
+import org.apache.ambari.server.state.stack.PrerequisiteCheck;
+import org.apache.ambari.server.state.stack.upgrade.UpgradeType;
+
+import com.google.inject.Inject;
+
+/**
+ * The {@link KerberosAdminPersistedCredentialCheck} class is used to check that the Kerberos
+ * administrator credentials are stored in the persisted credential store when Kerberos is enabled.
+ * <p>
+ * This is needed so that if Kerberos principals and/or keytab files need to be created during a stack
+ * upgrade, the KDC administrator credentials are guaranteed to be available.  If the temporary store
+ * is used, the credential may have expired before needed.
+ */
+@UpgradeCheck(
+    group = UpgradeCheckGroup.KERBEROS,
+    required = {UpgradeType.ROLLING, UpgradeType.NON_ROLLING, UpgradeType.HOST_ORDERED})
+public class KerberosAdminPersistedCredentialCheck extends AbstractCheckDescriptor {
+
+  public static final String KEY_PERSISTED_STORE_NOT_CONFIGURED = "persisted_store_no_configured";
+
+  public static final String KEY_CREDENTIAL_NOT_STORED = "admin_credential_not_stored";
+
+  @Inject
+  private CredentialStoreService credentialStoreService;
+
+  /**
+   * Constructor.
+   */
+  @Inject
+  public KerberosAdminPersistedCredentialCheck() {
+    super(CheckDescription.KERBEROS_ADMIN_CREDENTIAL_CHECK);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Set<String> getApplicableServices() {
+    return Collections.emptySet();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void perform(PrerequisiteCheck prerequisiteCheck, PrereqCheckRequest request) throws AmbariException {
+
+    final String clusterName = request.getClusterName();
+    final Cluster cluster = clustersProvider.get().getCluster(clusterName);
+
+    // Perform the check only if Kerberos is enabled
+    if (cluster.getSecurityType() != SecurityType.KERBEROS) {
+      return;
+    }
+
+    // Perform the check only if Ambari is managing the Kerberos identities
+    if (!"true".equalsIgnoreCase(getProperty(request, "kerberos-env", "manage_identities"))) {
+      return;
+    }
+
+    if (!credentialStoreService.isInitialized(CredentialStoreType.PERSISTED)) {
+      // The persisted store is not available
+      prerequisiteCheck.setFailReason(getFailReason(KEY_PERSISTED_STORE_NOT_CONFIGURED, prerequisiteCheck, request));
+      prerequisiteCheck.setStatus(PrereqCheckStatus.FAIL);
+      prerequisiteCheck.getFailedOn().add(request.getClusterName());
+    } else if (credentialStoreService.getCredential(clusterName, KerberosHelper.KDC_ADMINISTRATOR_CREDENTIAL_ALIAS, CredentialStoreType.PERSISTED) == null) {
+      // The KDC administrator credential has not been stored in the persisted credential store
+      prerequisiteCheck.setFailReason(getFailReason(KEY_CREDENTIAL_NOT_STORED, prerequisiteCheck, request));
+      prerequisiteCheck.setStatus(PrereqCheckStatus.FAIL);
+      prerequisiteCheck.getFailedOn().add(request.getClusterName());
+    }
+
+  }
+}
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/checks/KerberosAdminPersistedCredentialCheckTest.java b/ambari-server/src/test/java/org/apache/ambari/server/checks/KerberosAdminPersistedCredentialCheckTest.java
new file mode 100644 (file)
index 0000000..46184ec
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ * 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.ambari.server.checks;
+
+import static org.easymock.EasyMock.expect;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+
+import org.apache.ambari.server.actionmanager.ActionDBAccessor;
+import org.apache.ambari.server.actionmanager.ActionDBAccessorImpl;
+import org.apache.ambari.server.actionmanager.ActionManager;
+import org.apache.ambari.server.actionmanager.HostRoleCommandFactory;
+import org.apache.ambari.server.actionmanager.HostRoleCommandFactoryImpl;
+import org.apache.ambari.server.actionmanager.StageFactory;
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.audit.AuditLogger;
+import org.apache.ambari.server.controller.AbstractRootServiceResponseFactory;
+import org.apache.ambari.server.controller.AmbariCustomCommandExecutionHelper;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.KerberosHelper;
+import org.apache.ambari.server.controller.PrereqCheckRequest;
+import org.apache.ambari.server.controller.RootServiceResponseFactory;
+import org.apache.ambari.server.hooks.HookService;
+import org.apache.ambari.server.hooks.users.UserHookService;
+import org.apache.ambari.server.metadata.CachedRoleCommandOrderProvider;
+import org.apache.ambari.server.metadata.RoleCommandOrderProvider;
+import org.apache.ambari.server.orm.DBAccessor;
+import org.apache.ambari.server.orm.dao.ArtifactDAO;
+import org.apache.ambari.server.orm.dao.HostRoleCommandDAO;
+import org.apache.ambari.server.scheduler.ExecutionScheduler;
+import org.apache.ambari.server.scheduler.ExecutionSchedulerImpl;
+import org.apache.ambari.server.security.SecurityHelper;
+import org.apache.ambari.server.security.credential.Credential;
+import org.apache.ambari.server.security.encryption.CredentialStoreService;
+import org.apache.ambari.server.security.encryption.CredentialStoreType;
+import org.apache.ambari.server.stack.StackManagerFactory;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.Config;
+import org.apache.ambari.server.state.ConfigHelper;
+import org.apache.ambari.server.state.DesiredConfig;
+import org.apache.ambari.server.state.SecurityType;
+import org.apache.ambari.server.state.ServiceComponentHostFactory;
+import org.apache.ambari.server.state.stack.OsFamily;
+import org.apache.ambari.server.state.stack.PrereqCheckStatus;
+import org.apache.ambari.server.state.stack.PrerequisiteCheck;
+import org.apache.ambari.server.state.stack.UpgradePack;
+import org.apache.ambari.server.testutils.PartialNiceMockBinder;
+import org.apache.ambari.server.topology.PersistedState;
+import org.apache.ambari.server.topology.PersistedStateImpl;
+import org.easymock.EasyMockSupport;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+
+public class KerberosAdminPersistedCredentialCheckTest extends EasyMockSupport {
+
+  @Test
+  public void testMissingCredentialStoreKerberosEnabledManagingIdentities() throws Exception {
+    PrerequisiteCheck result = executeCheck(true, true, false, false);
+    Assert.assertEquals(PrereqCheckStatus.FAIL, result.getStatus());
+    Assert.assertTrue(result.getFailReason().startsWith("Ambari's credential store has not been configured."));
+  }
+
+  @Test
+  public void testMissingCredentialStoreKerberosEnabledNotManagingIdentities() throws Exception {
+    PrerequisiteCheck result = executeCheck(true, false, false, false);
+    Assert.assertEquals(PrereqCheckStatus.PASS, result.getStatus());
+  }
+
+  @Test
+  public void testMissingCredentialStoreKerberosNotEnabled() throws Exception {
+    PrerequisiteCheck result = executeCheck(false, false, false, false);
+    Assert.assertEquals(PrereqCheckStatus.PASS, result.getStatus());
+  }
+
+  @Test
+  public void testMissingCredentialKerberosEnabledManagingIdentities() throws Exception {
+    PrerequisiteCheck result = executeCheck(true, true, true, false);
+    Assert.assertEquals(PrereqCheckStatus.FAIL, result.getStatus());
+    Assert.assertTrue(result.getFailReason().startsWith("The KDC administrator credential has not been stored in the persisted credential store."));
+  }
+
+  @Test
+  public void testMissingCredentialKerberosEnabledNotManagingIdentities() throws Exception {
+    PrerequisiteCheck result = executeCheck(true, false, true, false);
+    Assert.assertEquals(PrereqCheckStatus.PASS, result.getStatus());
+  }
+
+  @Test
+  public void testMissingCredentialKerberosNotEnabled() throws Exception {
+    PrerequisiteCheck result = executeCheck(false, true, true, false);
+    Assert.assertEquals(PrereqCheckStatus.PASS, result.getStatus());
+  }
+
+  @Test
+  public void testCredentialsSetKerberosNotEnabled() throws Exception {
+    PrerequisiteCheck result = executeCheck(false, false, true, true);
+    Assert.assertEquals(PrereqCheckStatus.PASS, result.getStatus());
+  }
+
+  @Test
+  public void testCredentialsSetKerberosEnabledNotManagingIdentities() throws Exception {
+    PrerequisiteCheck result = executeCheck(true, false, true, true);
+    Assert.assertEquals(PrereqCheckStatus.PASS, result.getStatus());
+  }
+
+  @Test
+  public void testCredentialsSetKerberosEnabledManagingIdentities() throws Exception {
+    PrerequisiteCheck result = executeCheck(true, true, true, true);
+    Assert.assertEquals(PrereqCheckStatus.PASS, result.getStatus());
+  }
+
+  private PrerequisiteCheck executeCheck(boolean kerberosEnabled, boolean manageIdentities, boolean credentialStoreInitialized, boolean credentialSet) throws Exception {
+
+    String clusterName = "c1";
+
+    Map<String, String> checkProperties = new HashMap<>();
+
+    UpgradePack.PrerequisiteCheckConfig prerequisiteCheckConfig = createMock(UpgradePack.PrerequisiteCheckConfig.class);
+    expect(prerequisiteCheckConfig.getCheckProperties(KerberosAdminPersistedCredentialCheck.class.getName())).andReturn(checkProperties).anyTimes();
+
+    PrerequisiteCheck prerequisiteCheck = new PrerequisiteCheck(null, null);
+    PrereqCheckRequest request = new PrereqCheckRequest(clusterName);
+    request.setPrerequisiteCheckConfig(prerequisiteCheckConfig);
+
+    DesiredConfig desiredKerberosEnv = createMock(DesiredConfig.class);
+    expect(desiredKerberosEnv.getTag()).andReturn("tag").anyTimes();
+
+    Map<String, DesiredConfig> desiredConfigs = Collections.singletonMap("kerberos-env", desiredKerberosEnv);
+
+    Config kerberosEnv = createMock(Config.class);
+    expect(kerberosEnv.getProperties()).andReturn(Collections.singletonMap("manage_identities", manageIdentities ? "true" : "false")).anyTimes();
+
+    Cluster cluster = createMock(Cluster.class);
+    expect(cluster.getSecurityType()).andReturn(kerberosEnabled ? SecurityType.KERBEROS : SecurityType.NONE).anyTimes();
+    expect(cluster.getDesiredConfigs()).andReturn(desiredConfigs).anyTimes();
+    expect(cluster.getConfig("kerberos-env", "tag")).andReturn(kerberosEnv).anyTimes();
+
+    Clusters clusters = createMock(Clusters.class);
+    expect(clusters.getCluster(clusterName)).andReturn(cluster).anyTimes();
+
+    Credential credential = createMock(Credential.class);
+
+    Injector injector = getInjector();
+    CredentialStoreService credentialStoreProvider = injector.getInstance(CredentialStoreService.class);
+    expect(credentialStoreProvider.isInitialized(CredentialStoreType.PERSISTED)).andReturn(credentialStoreInitialized).anyTimes();
+    expect(credentialStoreProvider.getCredential(clusterName, KerberosHelper.KDC_ADMINISTRATOR_CREDENTIAL_ALIAS, CredentialStoreType.PERSISTED)).andReturn(credentialSet ? credential : null).anyTimes();
+
+    Provider<Clusters> clustersProvider = () -> clusters;
+
+    replayAll();
+
+    injector.getInstance(AmbariMetaInfo.class).init();
+
+    KerberosAdminPersistedCredentialCheck check = new KerberosAdminPersistedCredentialCheck();
+    injector.injectMembers(check);
+
+    check.clustersProvider = clustersProvider;
+    check.perform(prerequisiteCheck, request);
+
+    verifyAll();
+
+    return prerequisiteCheck;
+  }
+
+
+  Injector getInjector() {
+    return Guice.createInjector(new AbstractModule() {
+
+      @Override
+      protected void configure() {
+        PartialNiceMockBinder.newBuilder().addActionDBAccessorConfigsBindings().addFactoriesInstallBinding()
+            .build().configure(binder());
+
+        bind(ExecutionScheduler.class).toInstance(createNiceMock(ExecutionSchedulerImpl.class));
+        bind(EntityManager.class).toInstance(createNiceMock(EntityManager.class));
+        bind(DBAccessor.class).toInstance(createNiceMock(DBAccessor.class));
+        bind(OsFamily.class).toInstance(createNiceMock(OsFamily.class));
+        bind(HostRoleCommandDAO.class).toInstance(createNiceMock(HostRoleCommandDAO.class));
+        bind(HostRoleCommandFactory.class).toInstance(createNiceMock(HostRoleCommandFactoryImpl.class));
+        bind(ActionDBAccessor.class).to(ActionDBAccessorImpl.class);
+        bind(AbstractRootServiceResponseFactory.class).to(RootServiceResponseFactory.class);
+        bind(ServiceComponentHostFactory.class).toInstance(createNiceMock(ServiceComponentHostFactory.class));
+        bind(PasswordEncoder.class).toInstance(createNiceMock(PasswordEncoder.class));
+        bind(HookService.class).to(UserHookService.class);
+        bind(PersistedState.class).to(PersistedStateImpl.class);
+        bind(SecurityHelper.class).toInstance(createNiceMock(SecurityHelper.class));
+        bind(AmbariCustomCommandExecutionHelper.class).toInstance(createNiceMock(AmbariCustomCommandExecutionHelper.class));
+        bind(AmbariManagementController.class).toInstance(createNiceMock(AmbariManagementController.class));
+        bind(AmbariMetaInfo.class).toInstance(createNiceMock(AmbariMetaInfo.class));
+        bind(ActionManager.class).toInstance(createNiceMock(ActionManager.class));
+        bind(StageFactory.class).toInstance(createNiceMock(StageFactory.class));
+        bind(Clusters.class).toInstance(createNiceMock(Clusters.class));
+        bind(ConfigHelper.class).toInstance(createNiceMock(ConfigHelper.class));
+        bind(StackManagerFactory.class).toInstance(createNiceMock(StackManagerFactory.class));
+        bind(AuditLogger.class).toInstance(createNiceMock(AuditLogger.class));
+        bind(ArtifactDAO.class).toInstance(createNiceMock(ArtifactDAO.class));
+        bind(RoleCommandOrderProvider.class).to(CachedRoleCommandOrderProvider.class);
+
+        bind(CredentialStoreService.class).toInstance(createMock(CredentialStoreService.class));
+      }
+    });
+  }
+}