瀏覽代碼

AMBARI-16851. Cluster operator and cluster admin not allowed to install ambari agent (rlevas)

Robert Levas 9 年之前
父節點
當前提交
e7b852607f
共有 22 個文件被更改,包括 366 次插入67 次删除
  1. 29 2
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RequestResourceProvider.java
  2. 15 1
      ambari-server/src/main/java/org/apache/ambari/server/customactions/ActionDefinition.java
  3. 36 1
      ambari-server/src/main/java/org/apache/ambari/server/customactions/ActionDefinitionManager.java
  4. 10 0
      ambari-server/src/main/java/org/apache/ambari/server/customactions/ActionDefinitionSpec.java
  5. 50 37
      ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java
  6. 13 0
      ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java
  7. 1 0
      ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
  8. 1 0
      ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
  9. 1 0
      ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
  10. 1 0
      ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
  11. 1 0
      ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql
  12. 1 0
      ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
  13. 1 0
      ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
  14. 1 0
      ambari-server/src/main/resources/custom_action_definitions/system_action_definitions.xml
  15. 9 9
      ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java
  16. 4 4
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ActionResourceProviderTest.java
  17. 64 10
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/RequestResourceProviderTest.java
  18. 26 3
      ambari-server/src/test/java/org/apache/ambari/server/customactions/ActionDefinitionManagerTest.java
  19. 57 0
      ambari-server/src/test/java/org/apache/ambari/server/security/TestAuthenticationFactory.java
  20. 3 0
      ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog240Test.java
  21. 9 0
      ambari-server/src/test/resources/custom_action_definitions/cust_action_definitions1.xml
  22. 33 0
      ambari-server/src/test/resources/custom_action_definitions_invalid/cust_action_definitions_invalid.xml

+ 29 - 2
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RequestResourceProvider.java

@@ -56,6 +56,7 @@ import org.apache.ambari.server.controller.spi.SystemException;
 import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 import org.apache.ambari.server.controller.utilities.PredicateBuilder;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.customactions.ActionDefinition;
 import org.apache.ambari.server.orm.dao.HostRoleCommandDAO;
 import org.apache.ambari.server.orm.dao.HostRoleCommandStatusSummaryDTO;
 import org.apache.ambari.server.orm.dao.RequestDAO;
@@ -71,6 +72,7 @@ import org.apache.ambari.server.topology.TopologyManager;
 
 import com.google.common.collect.Sets;
 import com.google.inject.Inject;
+import org.apache.commons.lang.StringUtils;
 
 /**
  * Resource provider for request resources.
@@ -185,8 +187,33 @@ public class RequestResourceProvider extends AbstractControllerResourceProvider
         String clusterName = actionRequest.getClusterName();
 
         if(clusterName == null) {
-          // This must be an administrative action?
-          // TODO: Perform authorization check for this?
+          String actionName = actionRequest.getActionName();
+
+          // Ensure that the actionName is not null or empty.  A null actionName will result in
+          // a NPE at when getting the action definition.  The string "_unknown_action_" should not
+          // result in a valid action definition and should be easy to understand in any error message
+          // that gets displayed or logged due to an authorization issue.
+          if(StringUtils.isEmpty(actionName)) {
+            actionName = "_unknown_action_";
+          }
+
+          ActionDefinition actionDefinition = getManagementController().getAmbariMetaInfo().getActionDefinition(actionName);
+          Set<RoleAuthorization> permissions = (actionDefinition == null) ? null : actionDefinition.getPermissions();
+
+          if(permissions == null) {
+            if (!AuthorizationHelper.isAuthorized(ResourceType.AMBARI, null, RoleAuthorization.SERVICE_RUN_CUSTOM_COMMAND)) {
+              throw new AuthorizationException(String.format("The authenticated user is not authorized to execute the '%s'command.", actionName));
+            }
+          }
+          else {
+            // Since we cannot tell whether the action is to be exectued for the system or a
+            // non-disclosed cluster, specify that the resource is a CLUSTER with no resource id.
+            // This should ensure that a user with a role for any cluster with the appropriate
+            // permissions or an Ambari administrator can execute the command.
+            if (!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, null, permissions)) {
+              throw new AuthorizationException(String.format("The authenticated user is not authorized to execute the '%s'command.", actionName));
+            }
+          }
         }
         else if(actionRequest.isCommand()) {
           if (!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER,

+ 15 - 1
ambari-server/src/main/java/org/apache/ambari/server/customactions/ActionDefinition.java

@@ -21,6 +21,9 @@ package org.apache.ambari.server.customactions;
 import org.apache.ambari.server.actionmanager.ActionType;
 import org.apache.ambari.server.actionmanager.TargetHostType;
 import org.apache.ambari.server.controller.ActionResponse;
+import org.apache.ambari.server.security.authorization.RoleAuthorization;
+
+import java.util.Set;
 
 /**
  * The resource describing the definition of an action
@@ -34,6 +37,7 @@ public class ActionDefinition {
   private String description;
   private TargetHostType targetType;
   private Short defaultTimeout;
+  private Set<RoleAuthorization> permissions;
 
   /**
    * Create an instance of ActionDefinition
@@ -46,10 +50,11 @@ public class ActionDefinition {
    * @param description     Short description of the action
    * @param targetType      Selection criteria for target hosts
    * @param defaultTimeout  The timeout value for this action when executed
+   * @param permissions     A set of permissions to use when verifiying authorization to execute this action
    */
   public ActionDefinition(String actionName, ActionType actionType, String inputs,
                           String targetService, String targetComponent, String description,
-                          TargetHostType targetType, Short defaultTimeout) {
+                          TargetHostType targetType, Short defaultTimeout, Set<RoleAuthorization> permissions) {
     setActionName(actionName);
     setActionType(actionType);
     setInputs(inputs);
@@ -58,6 +63,7 @@ public class ActionDefinition {
     setDescription(description);
     setTargetType(targetType);
     setDefaultTimeout(defaultTimeout);
+    setPermissions(permissions);
   }
 
   public String getActionName() {
@@ -124,6 +130,14 @@ public class ActionDefinition {
     this.defaultTimeout = defaultTimeout;
   }
 
+  public void setPermissions(Set<RoleAuthorization> permissions) {
+    this.permissions = permissions;
+  }
+
+  public Set<RoleAuthorization> getPermissions() {
+    return permissions;
+  }
+
   public ActionResponse convertToResponse() {
     return new ActionResponse(getActionName(), getActionType().name(), getInputs(),
         getTargetService(), getTargetComponent(), getDescription(), getTargetType().name(),

+ 36 - 1
ambari-server/src/main/java/org/apache/ambari/server/customactions/ActionDefinitionManager.java

@@ -21,6 +21,8 @@ import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.actionmanager.ActionType;
 import org.apache.ambari.server.actionmanager.TargetHostType;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.security.authorization.RoleAuthorization;
+import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -30,9 +32,12 @@ import javax.xml.bind.UnmarshalException;
 import javax.xml.bind.Unmarshaller;
 import java.io.File;
 import java.util.ArrayList;
+import java.util.EnumSet;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Manages Action definitions read from XML files
@@ -120,7 +125,8 @@ public class ActionDefinitionManager {
             }
 
             actionDefinitionMap.put(ad.getActionName(), new ActionDefinition(ad.getActionName(), actionType,
-                ad.getInputs(), ad.getTargetService(), ad.getTargetComponent(), ad.getDescription(), targetType, defaultTimeout));
+                ad.getInputs(), ad.getTargetService(), ad.getTargetComponent(), ad.getDescription(), targetType, defaultTimeout,
+                translatePermissions(ad.getPermissions())));
             LOG.info("Added custom action definition for " + ad.getActionName());
           } else {
             LOG.warn(errorReason.toString());
@@ -199,4 +205,33 @@ public class ActionDefinitionManager {
     }
     return true;
   }
+
+  /**
+   * Given a comma-delimited list of permission names, translates into a {@link Set} of
+   * {@link RoleAuthorization}s.
+   * <p>
+   * <code>null</code> is returned if the permission string is null or empty, or if none of the
+   * permissions in the string translate to a {@link RoleAuthorization}.  Permissions that do not
+   * translate to a {@link RoleAuthorization} will yield a {@link IllegalArgumentException}.
+   *
+   * @param permissions a comma-delimited string of permission names
+   * @return a set of {@link RoleAuthorization}s; or null if no permissions are specified
+   */
+  private Set<RoleAuthorization> translatePermissions(String permissions) {
+    if (StringUtils.isEmpty(permissions)) {
+      return null;
+    } else {
+      Set<RoleAuthorization> authorizations = new HashSet<RoleAuthorization>();
+      String[] parts = permissions.split(",");
+
+      for (String permission : parts) {
+        RoleAuthorization authorization = RoleAuthorization.translate(permission);
+        if (authorization != null) {
+          authorizations.add(authorization);
+        }
+      }
+
+      return (authorizations.isEmpty()) ? null : EnumSet.copyOf(authorizations);
+    }
+  }
 }

+ 10 - 0
ambari-server/src/main/java/org/apache/ambari/server/customactions/ActionDefinitionSpec.java

@@ -27,6 +27,7 @@ public class ActionDefinitionSpec {
   private String description;
   private String targetType;
   private String defaultTimeout;
+  private String permissions;
 
   public String getTargetComponent() {
     return targetComponent;
@@ -92,6 +93,14 @@ public class ActionDefinitionSpec {
     this.targetService = targetService;
   }
 
+  public String getPermissions() {
+    return permissions;
+  }
+
+  public void setPermissions(String permissions) {
+    this.permissions = permissions;
+  }
+
   @Override
   public int hashCode() {
     final int prime = 31;
@@ -134,6 +143,7 @@ public class ActionDefinitionSpec {
         .append(" targetComponent: ").append(targetComponent)
         .append(" defaultTimeout: ").append(defaultTimeout)
         .append(" targetType: ").append(targetType)
+        .append(" permissions: ").append(permissions)
         .toString();
   }
 }

+ 50 - 37
ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java

@@ -47,6 +47,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.security.Principal;
+import java.util.EnumSet;
 import java.util.regex.Pattern;
 
 public class AmbariAuthorizationFilter implements Filter {
@@ -77,9 +78,12 @@ public class AmbariAuthorizationFilter implements Filter {
   private static final String API_CLUSTER_SERVICES_ALL_PATTERN = API_VERSION_PREFIX + "/clusters/.*?/services.*";
   private static final String API_CLUSTER_ALERT_ALL_PATTERN = API_VERSION_PREFIX + "/clusters/.*?/alert.*";
   private static final String API_CLUSTER_HOSTS_ALL_PATTERN = API_VERSION_PREFIX + "/clusters/.*?/hosts.*";
+  private static final String API_CLUSTER_HOST_COMPONENTS_ALL_PATTERN = API_VERSION_PREFIX + "/clusters/.*?/host_components.*";
   private static final String API_STACK_VERSIONS_PATTERN = API_VERSION_PREFIX + "/stacks/.*?/versions/.*";
   private static final String API_HOSTS_ALL_PATTERN = API_VERSION_PREFIX + "/hosts.*";
   private static final String API_ALERT_TARGETS_ALL_PATTERN = API_VERSION_PREFIX + "/alert_targets.*";
+  private static final String API_BOOTSTRAP_PATTERN_ALL = API_VERSION_PREFIX + "/bootstrap.*";
+  private static final String API_REQUESTS_ALL_PATTERN = API_VERSION_PREFIX + "/requests.*";
 
   protected static final String LOGIN_REDIRECT_BASE = "/#/login?targetURI=";
 
@@ -161,55 +165,62 @@ public class AmbariAuthorizationFilter implements Filter {
     } else if (!authorizationPerformedInternally(requestURI)) {
       boolean authorized = false;
 
-      for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
-        if (grantedAuthority instanceof AmbariGrantedAuthority) {
-
-          AmbariGrantedAuthority ambariGrantedAuthority = (AmbariGrantedAuthority) grantedAuthority;
+      if (requestURI.matches(API_BOOTSTRAP_PATTERN_ALL)) {
+        authorized = AuthorizationHelper.isAuthorized(authentication,
+            ResourceType.CLUSTER,
+            null,
+            EnumSet.of(RoleAuthorization.HOST_ADD_DELETE_HOSTS));
+      }
+      else {
+        for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
+          if (grantedAuthority instanceof AmbariGrantedAuthority) {
 
-          PrivilegeEntity privilegeEntity = ambariGrantedAuthority.getPrivilegeEntity();
-          Integer permissionId = privilegeEntity.getPermission().getId();
+            AmbariGrantedAuthority ambariGrantedAuthority = (AmbariGrantedAuthority) grantedAuthority;
 
-          // admin has full access
-          if (permissionId.equals(PermissionEntity.AMBARI_ADMINISTRATOR_PERMISSION)) {
-            authorized = true;
-            break;
-          }
+            PrivilegeEntity privilegeEntity = ambariGrantedAuthority.getPrivilegeEntity();
+            Integer permissionId = privilegeEntity.getPermission().getId();
 
-          // clusters require permission
-          if (!"GET".equalsIgnoreCase(httpRequest.getMethod()) && requestURI.matches(API_CREDENTIALS_AMBARI_PATTERN)) {
-            // Only the administrator can operate on credentials where the alias starts with "ambari."
+            // admin has full access
             if (permissionId.equals(PermissionEntity.AMBARI_ADMINISTRATOR_PERMISSION)) {
               authorized = true;
               break;
             }
-          } else if (requestURI.matches(API_CLUSTERS_ALL_PATTERN)) {
-            if (permissionId.equals(PermissionEntity.CLUSTER_USER_PERMISSION) ||
-                permissionId.equals(PermissionEntity.CLUSTER_ADMINISTRATOR_PERMISSION)) {
-              authorized = true;
-              break;
-            }
-          } else if (STACK_ADVISOR_REGEX.matcher(requestURI).matches()) {
-            //TODO permissions model doesn't manage stacks api, but we need access to stack advisor to save configs
-            if (permissionId.equals(PermissionEntity.CLUSTER_USER_PERMISSION) ||
-                permissionId.equals(PermissionEntity.CLUSTER_ADMINISTRATOR_PERMISSION)) {
-              authorized = true;
-              break;
-            }
-          } else if (requestURI.matches(API_VIEWS_ALL_PATTERN)) {
-            // views require permission
-            if (permissionId.equals(PermissionEntity.VIEW_USER_PERMISSION)) {
-              authorized = true;
-              break;
+
+            // clusters require permission
+            if (!"GET".equalsIgnoreCase(httpRequest.getMethod()) && requestURI.matches(API_CREDENTIALS_AMBARI_PATTERN)) {
+              // Only the administrator can operate on credentials where the alias starts with "ambari."
+              if (permissionId.equals(PermissionEntity.AMBARI_ADMINISTRATOR_PERMISSION)) {
+                authorized = true;
+                break;
+              }
+            } else if (requestURI.matches(API_CLUSTERS_ALL_PATTERN)) {
+              if (permissionId.equals(PermissionEntity.CLUSTER_USER_PERMISSION) ||
+                  permissionId.equals(PermissionEntity.CLUSTER_ADMINISTRATOR_PERMISSION)) {
+                authorized = true;
+                break;
+              }
+            } else if (STACK_ADVISOR_REGEX.matcher(requestURI).matches()) {
+              //TODO permissions model doesn't manage stacks api, but we need access to stack advisor to save configs
+              if (permissionId.equals(PermissionEntity.CLUSTER_USER_PERMISSION) ||
+                  permissionId.equals(PermissionEntity.CLUSTER_ADMINISTRATOR_PERMISSION)) {
+                authorized = true;
+                break;
+              }
+            } else if (requestURI.matches(API_VIEWS_ALL_PATTERN)) {
+              // views require permission
+              if (permissionId.equals(PermissionEntity.VIEW_USER_PERMISSION)) {
+                authorized = true;
+                break;
+              }
             }
           }
         }
-      }
 
-      // allow GET for everything except /views, /api/v1/users, /api/v1/groups, /api/v1/ldap_sync_events
-      if (!authorized &&
-          (!httpRequest.getMethod().equals("GET")
-              || requestURI.matches(API_LDAP_SYNC_EVENTS_ALL_PATTERN))) {
+        // Allow all GETs that are not LDAP sync events...
+        authorized = authorized || (httpRequest.getMethod().equals("GET") && !requestURI.matches(API_LDAP_SYNC_EVENTS_ALL_PATTERN));
+      }
 
+      if (!authorized) {
         if(auditLogger.isEnabled()) {
           auditEvent = AccessUnauthorizedAuditEvent.builder()
             .withHttpMethodName(httpRequest.getMethod())
@@ -283,6 +294,7 @@ public class AmbariAuthorizationFilter implements Filter {
    */
   private boolean authorizationPerformedInternally(String requestURI) {
     return requestURI.matches(API_USERS_ALL_PATTERN) ||
+        requestURI.matches(API_REQUESTS_ALL_PATTERN) ||
         requestURI.matches(API_GROUPS_ALL_PATTERN) ||
         requestURI.matches(API_CREDENTIALS_ALL_PATTERN) ||
         requestURI.matches(API_PRIVILEGES_ALL_PATTERN) ||
@@ -295,6 +307,7 @@ public class AmbariAuthorizationFilter implements Filter {
         requestURI.matches(VIEWS_CONTEXT_PATH_PATTERN) ||
         requestURI.matches(API_WIDGET_LAYOUTS_PATTERN) ||
         requestURI.matches(API_CLUSTER_HOSTS_ALL_PATTERN) ||
+        requestURI.matches(API_CLUSTER_HOST_COMPONENTS_ALL_PATTERN) ||
         requestURI.matches(API_HOSTS_ALL_PATTERN) ||
         requestURI.matches(API_ALERT_TARGETS_ALL_PATTERN) ||
         requestURI.matches(API_PRIVILEGES_ALL_PATTERN) ||

+ 13 - 0
ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java

@@ -329,6 +329,7 @@ public class UpgradeCatalog240 extends AbstractUpgradeCatalog {
     setRoleSortOrder();
     addSettingPermission();
     addManageUserPersistedDataPermission();
+    allowClusterOperatorToManageCredentials();
     updateHDFSConfigs();
     updateHIVEConfigs();
     updateAMSConfigs();
@@ -513,6 +514,18 @@ public class UpgradeCatalog240 extends AbstractUpgradeCatalog {
 
   }
 
+  /**
+   * Adds <code>CLUSTER.MANAGE_CREDENTIALS</code> to the set of authorizations a <code>CLUSTER.OPERATOR</code> can perform.
+   *
+   * @throws SQLException
+   */
+  protected void allowClusterOperatorToManageCredentials() throws SQLException {
+    String permissionId = permissionDAO.findPermissionByNameAndType("CLUSTER.OPERATOR",
+        resourceTypeDAO.findByName("CLUSTER")).getId().toString();
+    dbAccessor.insertRowIfMissing("permission_roleauthorization", new String[]{"permission_id", "authorization_id" },
+        new String[]{"'" + permissionId + "'", "'CLUSTER.MANAGE_CREDENTIALS'" }, false);
+  }
+
   protected void removeHiveOozieDBConnectionConfigs() throws AmbariException {
     AmbariManagementController ambariManagementController = injector.getInstance(AmbariManagementController.class);
     Map<String, Cluster> clusterMap = getCheckedClusterMap(ambariManagementController.getClusters());

+ 1 - 0
ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql

@@ -1341,6 +1341,7 @@ INSERT INTO permission_roleauthorization(permission_id, authorization_id)
   SELECT permission_id, 'CLUSTER.VIEW_STACK_DETAILS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR'  UNION ALL
   SELECT permission_id, 'CLUSTER.MANAGE_CONFIG_GROUPS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR'  UNION ALL
   SELECT permission_id, 'CLUSTER.VIEW_ALERTS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR'  UNION ALL
+  SELECT permission_id, 'CLUSTER.MANAGE_CREDENTIALS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR'  UNION ALL
   SELECT permission_id, 'CLUSTER.MANAGE_USER_PERSISTED_DATA' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR';
 
 -- Set authorizations for Cluster Administrator role

+ 1 - 0
ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql

@@ -1274,6 +1274,7 @@ INSERT INTO permission_roleauthorization(permission_id, authorization_id)
   SELECT permission_id, 'CLUSTER.VIEW_STACK_DETAILS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
   SELECT permission_id, 'CLUSTER.MANAGE_CONFIG_GROUPS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
   SELECT permission_id, 'CLUSTER.VIEW_ALERTS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
+  SELECT permission_id, 'CLUSTER.MANAGE_CREDENTIALS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
   SELECT permission_id, 'CLUSTER.MANAGE_USER_PERSISTED_DATA' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR';
 
 -- Set authorizations for Cluster Administrator role

+ 1 - 0
ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql

@@ -1293,6 +1293,7 @@ INSERT INTO permission_roleauthorization(permission_id, authorization_id)
   SELECT permission_id, 'CLUSTER.VIEW_STACK_DETAILS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
   SELECT permission_id, 'CLUSTER.MANAGE_CONFIG_GROUPS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
   SELECT permission_id, 'CLUSTER.VIEW_ALERTS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
+  SELECT permission_id, 'CLUSTER.MANAGE_CREDENTIALS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
   SELECT permission_id, 'CLUSTER.MANAGE_USER_PERSISTED_DATA' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR';
 
 -- Set authorizations for Cluster Administrator role

+ 1 - 0
ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql

@@ -1265,6 +1265,7 @@ INSERT INTO permission_roleauthorization(permission_id, authorization_id)
   SELECT permission_id, 'CLUSTER.VIEW_STACK_DETAILS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
   SELECT permission_id, 'CLUSTER.MANAGE_CONFIG_GROUPS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
   SELECT permission_id, 'CLUSTER.VIEW_ALERTS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
+  SELECT permission_id, 'CLUSTER.MANAGE_CREDENTIALS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
   SELECT permission_id, 'CLUSTER.MANAGE_USER_PERSISTED_DATA' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR';
 
 -- Set authorizations for Cluster Administrator role

+ 1 - 0
ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql

@@ -1427,6 +1427,7 @@ INSERT INTO ambari.permission_roleauthorization(permission_id, authorization_id)
   SELECT permission_id, 'CLUSTER.VIEW_STACK_DETAILS' FROM ambari.adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
   SELECT permission_id, 'CLUSTER.MANAGE_CONFIG_GROUPS' FROM ambari.adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
   SELECT permission_id, 'CLUSTER.VIEW_ALERTS' FROM ambari.adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
+  SELECT permission_id, 'CLUSTER.MANAGE_CREDENTIALS' FROM ambari.adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
   SELECT permission_id, 'CLUSTER.MANAGE_USER_PERSISTED_DATA' FROM ambari.adminpermission WHERE permission_name='CLUSTER.OPERATOR';
 
 -- Set authorizations for Cluster Administrator role

+ 1 - 0
ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql

@@ -1290,6 +1290,7 @@ insert into adminpermission(permission_id, permission_name, resource_type_id, pe
     SELECT permission_id, 'CLUSTER.VIEW_STACK_DETAILS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
     SELECT permission_id, 'CLUSTER.MANAGE_CONFIG_GROUPS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
     SELECT permission_id, 'CLUSTER.VIEW_ALERTS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
+    SELECT permission_id, 'CLUSTER.MANAGE_CREDENTIALS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
     SELECT permission_id, 'CLUSTER.MANAGE_USER_PERSISTED_DATA' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR';
 
   -- Set authorizations for Cluster Administrator role

+ 1 - 0
ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql

@@ -1293,6 +1293,7 @@ BEGIN TRANSACTION
     SELECT permission_id, 'CLUSTER.VIEW_STACK_DETAILS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
     SELECT permission_id, 'CLUSTER.MANAGE_CONFIG_GROUPS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
     SELECT permission_id, 'CLUSTER.VIEW_ALERTS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
+    SELECT permission_id, 'CLUSTER.MANAGE_CREDENTIALS' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR' UNION ALL
     SELECT permission_id, 'CLUSTER.MANAGE_USER_PERSISTED_DATA' FROM adminpermission WHERE permission_name='CLUSTER.OPERATOR';
 
   -- Set authorizations for Cluster Administrator role

+ 1 - 0
ambari-server/src/main/resources/custom_action_definitions/system_action_definitions.xml

@@ -28,6 +28,7 @@
     <defaultTimeout>60</defaultTimeout>
     <description>General check for host</description>
     <targetType>ANY</targetType>
+    <permissions>HOST.ADD_DELETE_HOSTS</permissions>
   </actionDefinition>
   <actionDefinition>
     <actionName>update_repo</actionName>

+ 9 - 9
ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java

@@ -4314,11 +4314,11 @@ public class AmbariManagementControllerTest {
 
 
     ActionDefinition a1 = new ActionDefinition(actionDef1, ActionType.SYSTEM,
-        "test,[optional1]", "", "", "Does file exist", TargetHostType.SPECIFIC, Short.valueOf("100"));
+        "test,[optional1]", "", "", "Does file exist", TargetHostType.SPECIFIC, Short.valueOf("100"), null);
     controller.getAmbariMetaInfo().addActionDefinition(a1);
     controller.getAmbariMetaInfo().addActionDefinition(new ActionDefinition(
         actionDef2, ActionType.SYSTEM, "", "HDFS", "DATANODE", "Does file exist",
-        TargetHostType.ALL, Short.valueOf("1000")));
+        TargetHostType.ALL, Short.valueOf("1000"), null));
 
     Map<String, String> params = new HashMap<String, String>() {{
       put("test", "test");
@@ -4700,23 +4700,23 @@ public class AmbariManagementControllerTest {
 
     controller.getAmbariMetaInfo().addActionDefinition(new ActionDefinition(
         actionDef1, ActionType.SYSTEM, "test,dirName", "", "", "Does file exist",
-        TargetHostType.SPECIFIC, Short.valueOf("100")));
+        TargetHostType.SPECIFIC, Short.valueOf("100"), null));
 
     controller.getAmbariMetaInfo().addActionDefinition(new ActionDefinition(
         actionDef2, ActionType.SYSTEM, "", "HDFS", "DATANODE", "Does file exist",
-        TargetHostType.ANY, Short.valueOf("100")));
+        TargetHostType.ANY, Short.valueOf("100"), null));
 
     controller.getAmbariMetaInfo().addActionDefinition(new ActionDefinition(
             "update_repo", ActionType.SYSTEM, "", "HDFS", "DATANODE", "Does file exist",
-            TargetHostType.ANY, Short.valueOf("100")));
+            TargetHostType.ANY, Short.valueOf("100"), null));
 
     controller.getAmbariMetaInfo().addActionDefinition(new ActionDefinition(
         actionDef3, ActionType.SYSTEM, "", "MAPREDUCE", "MAPREDUCE_CLIENT", "Does file exist",
-        TargetHostType.ANY, Short.valueOf("100")));
+        TargetHostType.ANY, Short.valueOf("100"), null));
 
     controller.getAmbariMetaInfo().addActionDefinition(new ActionDefinition(
         actionDef4, ActionType.SYSTEM, "", "HIVE", "", "Does file exist",
-        TargetHostType.ANY, Short.valueOf("100")));
+        TargetHostType.ANY, Short.valueOf("100"), null));
 
     actionRequest = new ExecuteActionRequest(cluster1, null, actionDef1, null, null, null, false);
     expectActionCreationErrorWithMessage(actionRequest, requestProperties,
@@ -6560,7 +6560,7 @@ public class AmbariManagementControllerTest {
 
     controller.getAmbariMetaInfo().addActionDefinition(new ActionDefinition(
       action1, ActionType.SYSTEM, "", "HDFS", "", "Some custom action.",
-      TargetHostType.ALL, Short.valueOf("10010")));
+      TargetHostType.ALL, Short.valueOf("10010"), null));
 
     Map<String, String> params = new HashMap<String, String>() {{
       put("test", "test");
@@ -10601,7 +10601,7 @@ public class AmbariManagementControllerTest {
 
     ambariMetaInfo.addActionDefinition(new ActionDefinition(action1, ActionType.SYSTEM,
         "", "", "", "action def description", TargetHostType.ANY,
-        Short.valueOf("60")));
+        Short.valueOf("60"), null));
 
     Map<String, String> requestProperties = new HashMap<String, String>();
     requestProperties.put(REQUEST_CONTEXT_PROPERTY, "Called from a test");

+ 4 - 4
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ActionResourceProviderTest.java

@@ -97,13 +97,13 @@ public class ActionResourceProviderTest {
     List<ActionDefinition> allDefinition = new ArrayList<ActionDefinition>();
     allDefinition.add(new ActionDefinition(
         "a1", ActionType.SYSTEM, "fileName", "HDFS", "DATANODE", "Does file exist", TargetHostType.ANY,
-        Short.valueOf("100")));
+        Short.valueOf("100"), null));
     allDefinition.add(new ActionDefinition(
         "a2", ActionType.SYSTEM, "fileName", "HDFS", "DATANODE", "Does file exist", TargetHostType.ANY,
-        Short.valueOf("100")));
+        Short.valueOf("100"), null));
     allDefinition.add(new ActionDefinition(
         "a3", ActionType.SYSTEM, "fileName", "HDFS", "DATANODE", "Does file exist", TargetHostType.ANY,
-        Short.valueOf("100")));
+        Short.valueOf("100"), null));
 
     Set<ActionResponse> allResponse = new HashSet<ActionResponse>();
     for (ActionDefinition definition : allDefinition) {
@@ -112,7 +112,7 @@ public class ActionResourceProviderTest {
 
     ActionDefinition namedDefinition = new ActionDefinition(
         "a1", ActionType.SYSTEM, "fileName", "HDFS", "DATANODE", "Does file exist", TargetHostType.ANY,
-        Short.valueOf("100"));
+        Short.valueOf("100"), null);
 
     Set<ActionResponse> nameResponse = new HashSet<ActionResponse>();
     nameResponse.add(namedDefinition.convertToResponse());

+ 64 - 10
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/RequestResourceProviderTest.java

@@ -22,19 +22,20 @@ package org.apache.ambari.server.controller.internal;
 import static org.apache.ambari.server.controller.internal.HostComponentResourceProvider.HOST_COMPONENT_STALE_CONFIGS_PROPERTY_ID;
 import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.capture;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.eq;
 import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.eq;
 import static org.easymock.EasyMock.newCapture;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
-import static org.easymock.EasyMock.verify;
+import static org.powermock.api.easymock.PowerMock.createMock;
+import static org.powermock.api.easymock.PowerMock.createNiceMock;
+import static org.powermock.api.easymock.PowerMock.replay;
+import static org.powermock.api.easymock.PowerMock.reset;
+import static org.powermock.api.easymock.PowerMock.verify;
 
 import java.lang.reflect.Field;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -49,6 +50,7 @@ import org.apache.ambari.server.actionmanager.ActionManager;
 import org.apache.ambari.server.actionmanager.HostRoleCommand;
 import org.apache.ambari.server.actionmanager.HostRoleStatus;
 import org.apache.ambari.server.actionmanager.Stage;
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.api.services.BaseRequest;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.AmbariServer;
@@ -63,6 +65,7 @@ import org.apache.ambari.server.controller.utilities.ClusterControllerHelper;
 import org.apache.ambari.server.controller.utilities.PredicateBuilder;
 import org.apache.ambari.server.controller.utilities.PredicateHelper;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.customactions.ActionDefinition;
 import org.apache.ambari.server.orm.dao.HostRoleCommandDAO;
 import org.apache.ambari.server.orm.dao.HostRoleCommandStatusSummaryDTO;
 import org.apache.ambari.server.orm.dao.RequestDAO;
@@ -70,6 +73,7 @@ import org.apache.ambari.server.orm.entities.RequestEntity;
 import org.apache.ambari.server.security.TestAuthenticationFactory;
 import org.apache.ambari.server.security.authorization.AuthorizationException;
 import org.apache.ambari.server.security.authorization.AuthorizationHelperInitializer;
+import org.apache.ambari.server.security.authorization.RoleAuthorization;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.topology.ClusterTopology;
@@ -1307,21 +1311,69 @@ public class RequestResourceProviderTest {
   }
 
   @Test
-  public void testCreateResourcesForNonCluster() throws Exception {
+  public void testCreateResourcesCheckHostForNonClusterAsAdministrator() throws Exception {
+    testCreateResourcesForNonCluster(TestAuthenticationFactory.createAdministrator(), "check_host",
+        EnumSet.of(RoleAuthorization.HOST_ADD_DELETE_HOSTS));
+  }
+
+  @Test
+  public void testCreateResourcesCheckHostForNonClusterAsClusterAdministrator() throws Exception {
+    testCreateResourcesForNonCluster(TestAuthenticationFactory.createClusterAdministrator(), "check_host",
+        EnumSet.of(RoleAuthorization.HOST_ADD_DELETE_HOSTS));
+  }
+
+  @Test
+  public void testCreateResourcesCheckHostForNonClusterAsClusterOperator() throws Exception {
+    testCreateResourcesForNonCluster(TestAuthenticationFactory.createClusterOperator(), "check_host",
+        EnumSet.of(RoleAuthorization.HOST_ADD_DELETE_HOSTS));
+  }
+
+  @Test(expected = AuthorizationException.class)
+  public void testCreateResourcesCheckHostForNonClusterAsServiceAdministrator() throws Exception {
+    testCreateResourcesForNonCluster(TestAuthenticationFactory.createServiceAdministrator(), "check_host",
+        EnumSet.of(RoleAuthorization.HOST_ADD_DELETE_HOSTS));
+  }
+
+  @Test
+  public void testCreateResourcesCheckJavaForNonClusterAsAdministrator() throws Exception {
+    testCreateResourcesForNonCluster(TestAuthenticationFactory.createAdministrator(), "check_java", null);
+  }
+
+  @Test(expected = AuthorizationException.class)
+  public void testCreateResourcesCheckJavaForNonClusterAsClusterAdministrator() throws Exception {
+    testCreateResourcesForNonCluster(TestAuthenticationFactory.createClusterAdministrator(), "check_java", null);
+  }
+
+  @Test(expected = AuthorizationException.class)
+  public void testCreateResourcesCheckJavaForNonClusterAsClusterOperator() throws Exception {
+    testCreateResourcesForNonCluster(TestAuthenticationFactory.createClusterOperator(), "check_java", null);
+  }
+
+  @Test(expected = AuthorizationException.class)
+  public void testCreateResourcesForNonClusterAsServiceAdministrator() throws Exception {
+    testCreateResourcesForNonCluster(TestAuthenticationFactory.createServiceAdministrator(), "check_java", null);
+  }
+
+  private void testCreateResourcesForNonCluster(Authentication authentication, String actionName, Set<RoleAuthorization> permissions) throws Exception {
     Resource.Type type = Resource.Type.Request;
 
     Capture<ExecuteActionRequest> actionRequest = newCapture();
     Capture<HashMap<String, String>> propertyMap = newCapture();
 
     AmbariManagementController managementController = createMock(AmbariManagementController.class);
+    AmbariMetaInfo metaInfo = createMock(AmbariMetaInfo.class);
+    ActionDefinition actionDefinition = createMock(ActionDefinition.class);
     RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
 
     expect(managementController.createAction(capture(actionRequest), capture(propertyMap)))
       .andReturn(response).anyTimes();
+    expect(managementController.getAmbariMetaInfo()).andReturn(metaInfo).anyTimes();
+    expect(metaInfo.getActionDefinition(actionName)).andReturn(actionDefinition).anyTimes();
+    expect(actionDefinition.getPermissions()).andReturn(permissions).anyTimes();
     expect(response.getMessage()).andReturn("Message").anyTimes();
 
     // replay
-    replay(managementController, response);
+    replay(managementController, metaInfo, actionDefinition, response);
 
     // add the property map to a set for the request.  add more maps for multiple creates
     Set<Map<String, Object>> propertySet = new LinkedHashSet<Map<String, Object>>();
@@ -1338,7 +1390,9 @@ public class RequestResourceProviderTest {
     propertySet.add(properties);
 
     Map<String, String> requestInfoProperties = new HashMap<String, String>();
-    requestInfoProperties.put(RequestResourceProvider.ACTION_ID, "check_java");
+    requestInfoProperties.put(RequestResourceProvider.ACTION_ID, actionName);
+
+    SecurityContextHolder.getContext().setAuthentication(authentication);
 
     // create the request
     Request request = PropertyHelper.getCreateRequest(propertySet, requestInfoProperties);
@@ -1352,7 +1406,7 @@ public class RequestResourceProviderTest {
 
     Assert.assertTrue(actionRequest.hasCaptured());
     Assert.assertFalse("expected an action", capturedRequest.isCommand());
-    Assert.assertEquals("check_java", capturedRequest.getActionName());
+    Assert.assertEquals(actionName, capturedRequest.getActionName());
     Assert.assertEquals(null, capturedRequest.getCommandName());
     Assert.assertNotNull(capturedRequest.getResourceFilters());
     Assert.assertEquals(1, capturedRequest.getResourceFilters().size());

+ 26 - 3
ambari-server/src/test/java/org/apache/ambari/server/customactions/ActionDefinitionManagerTest.java

@@ -19,22 +19,25 @@
 package org.apache.ambari.server.customactions;
 
 import java.io.File;
+import java.util.EnumSet;
 
 import junit.framework.Assert;
 import org.apache.ambari.server.actionmanager.ActionType;
 import org.apache.ambari.server.actionmanager.TargetHostType;
+import org.apache.ambari.server.security.authorization.RoleAuthorization;
 import org.junit.Test;
 
 public class ActionDefinitionManagerTest {
 
-  private final String customActionDefinitionRoot = "./src/test/resources/custom_action_definitions/";
+  private static final String CUSTOM_ACTION_DEFINITION_ROOT = "./src/test/resources/custom_action_definitions/";
+  private static final String CUSTOM_ACTION_DEFINITION_INVALID_ROOT = "./src/test/resources/custom_action_definitions_invalid/";
 
   @Test
   public void testReadCustomActionDefinitions() throws Exception {
     ActionDefinitionManager manager = new ActionDefinitionManager();
-    manager.readCustomActionDefinitions(new File(customActionDefinitionRoot));
+    manager.readCustomActionDefinitions(new File(CUSTOM_ACTION_DEFINITION_ROOT));
 
-    Assert.assertEquals(2, manager.getAllActionDefinition().size());
+    Assert.assertEquals(3, manager.getAllActionDefinition().size());
     ActionDefinition ad = manager.getActionDefinition("customAction1");
     Assert.assertNotNull(ad);
     Assert.assertEquals("customAction1", ad.getActionName());
@@ -45,6 +48,7 @@ public class ActionDefinitionManagerTest {
     Assert.assertEquals(60, (int)ad.getDefaultTimeout());
     Assert.assertEquals(TargetHostType.ALL, ad.getTargetType());
     Assert.assertEquals(ActionType.USER, ad.getActionType());
+    Assert.assertEquals(EnumSet.of(RoleAuthorization.HOST_ADD_DELETE_COMPONENTS, RoleAuthorization.HOST_ADD_DELETE_HOSTS), ad.getPermissions());
 
     ad = manager.getActionDefinition("customAction2");
     Assert.assertNotNull(ad);
@@ -56,6 +60,25 @@ public class ActionDefinitionManagerTest {
     Assert.assertEquals(60, (int)ad.getDefaultTimeout());
     Assert.assertEquals(null, ad.getTargetType());
     Assert.assertEquals(ActionType.USER, ad.getActionType());
+    Assert.assertEquals(EnumSet.of(RoleAuthorization.HOST_ADD_DELETE_COMPONENTS, RoleAuthorization.HOST_ADD_DELETE_HOSTS), ad.getPermissions());
+
+    ad = manager.getActionDefinition("customAction3");
+    Assert.assertNotNull(ad);
+    Assert.assertEquals("customAction3", ad.getActionName());
+    Assert.assertEquals("A random test", ad.getDescription());
+    Assert.assertEquals(null, ad.getInputs());
+    Assert.assertEquals("TASKTRACKER", ad.getTargetComponent());
+    Assert.assertEquals("MAPREDUCE", ad.getTargetService());
+    Assert.assertEquals(60, (int)ad.getDefaultTimeout());
+    Assert.assertEquals(null, ad.getTargetType());
+    Assert.assertEquals(ActionType.USER, ad.getActionType());
+    Assert.assertNull(ad.getPermissions());
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testReadInvalidCustomActionDefinitions() throws Exception {
+    ActionDefinitionManager manager = new ActionDefinitionManager();
+    manager.readCustomActionDefinitions(new File(CUSTOM_ACTION_DEFINITION_INVALID_ROOT));
   }
 }
 

+ 57 - 0
ambari-server/src/test/java/org/apache/ambari/server/security/TestAuthenticationFactory.java

@@ -48,10 +48,18 @@ public class TestAuthenticationFactory {
     return createClusterAdministrator("clusterAdmin", 4L);
   }
 
+  public static Authentication createClusterOperator() {
+    return createClusterOperator("clusterOp", 4L);
+  }
+
   public static Authentication createClusterAdministrator(String name, Long clusterResourceId) {
     return new TestAuthorization(name, Collections.singleton(createClusterAdministratorGrantedAuthority(clusterResourceId)));
   }
 
+  public static Authentication createClusterOperator(String name, Long clusterResourceId) {
+    return new TestAuthorization(name, Collections.singleton(createClusterOperatorGrantedAuthority(clusterResourceId)));
+  }
+
   public static Authentication createServiceAdministrator() {
     return createServiceAdministrator("serviceAdmin", 4L);
   }
@@ -92,6 +100,10 @@ public class TestAuthenticationFactory {
     return new AmbariGrantedAuthority(createClusterAdministratorPrivilegeEntity(clusterResourceId));
   }
 
+  private static GrantedAuthority createClusterOperatorGrantedAuthority(Long clusterResourceId) {
+    return new AmbariGrantedAuthority(createClusterOperatorPrivilegeEntity(clusterResourceId));
+  }
+
   private static GrantedAuthority createServiceAdministratorGrantedAuthority(Long clusterResourceId) {
     return new AmbariGrantedAuthority(createServiceAdministratorPrivilegeEntity(clusterResourceId));
   }
@@ -122,6 +134,13 @@ public class TestAuthenticationFactory {
     return privilegeEntity;
   }
 
+  private static PrivilegeEntity createClusterOperatorPrivilegeEntity(Long clusterResourceId) {
+    PrivilegeEntity privilegeEntity = new PrivilegeEntity();
+    privilegeEntity.setResource(createClusterResourceEntity(clusterResourceId));
+    privilegeEntity.setPermission(createClusterOperatorPermission());
+    return privilegeEntity;
+  }
+
   private static PrivilegeEntity createServiceAdministratorPrivilegeEntity(Long clusterResourceId) {
     PrivilegeEntity privilegeEntity = new PrivilegeEntity();
     privilegeEntity.setResource(createClusterResourceEntity(clusterResourceId));
@@ -200,6 +219,44 @@ public class TestAuthenticationFactory {
     return permissionEntity;
   }
 
+  private static PermissionEntity createClusterOperatorPermission() {
+    PermissionEntity permissionEntity = new PermissionEntity();
+    permissionEntity.setId(5);
+    permissionEntity.setResourceType(createResourceTypeEntity(ResourceType.CLUSTER));
+    permissionEntity.setAuthorizations(createAuthorizations(EnumSet.of(
+        RoleAuthorization.HOST_VIEW_CONFIGS,
+        RoleAuthorization.HOST_ADD_DELETE_COMPONENTS,
+        RoleAuthorization.HOST_VIEW_METRICS,
+        RoleAuthorization.SERVICE_DECOMMISSION_RECOMMISSION,
+        RoleAuthorization.CLUSTER_VIEW_CONFIGS,
+        RoleAuthorization.SERVICE_MANAGE_ALERTS,
+        RoleAuthorization.SERVICE_ENABLE_HA,
+        RoleAuthorization.SERVICE_VIEW_METRICS,
+        RoleAuthorization.SERVICE_RUN_CUSTOM_COMMAND,
+        RoleAuthorization.HOST_VIEW_STATUS_INFO,
+        RoleAuthorization.CLUSTER_VIEW_METRICS,
+        RoleAuthorization.SERVICE_VIEW_STATUS_INFO,
+        RoleAuthorization.CLUSTER_VIEW_STACK_DETAILS,
+        RoleAuthorization.SERVICE_COMPARE_CONFIGS,
+        RoleAuthorization.SERVICE_VIEW_ALERTS,
+        RoleAuthorization.CLUSTER_MANAGE_CONFIG_GROUPS,
+        RoleAuthorization.SERVICE_TOGGLE_ALERTS,
+        RoleAuthorization.SERVICE_MOVE,
+        RoleAuthorization.SERVICE_RUN_SERVICE_CHECK,
+        RoleAuthorization.SERVICE_MODIFY_CONFIGS,
+        RoleAuthorization.CLUSTER_VIEW_STATUS_INFO,
+        RoleAuthorization.SERVICE_VIEW_CONFIGS,
+        RoleAuthorization.HOST_ADD_DELETE_HOSTS,
+        RoleAuthorization.SERVICE_START_STOP,
+        RoleAuthorization.CLUSTER_VIEW_ALERTS,
+        RoleAuthorization.HOST_TOGGLE_MAINTENANCE,
+        RoleAuthorization.SERVICE_TOGGLE_MAINTENANCE,
+        RoleAuthorization.SERVICE_MANAGE_CONFIG_GROUPS,
+        RoleAuthorization.CLUSTER_MANAGE_USER_PERSISTED_DATA,
+        RoleAuthorization.CLUSTER_MANAGE_CREDENTIALS)));
+    return permissionEntity;
+  }
+
   private static PermissionEntity createServiceAdministratorPermission() {
     PermissionEntity permissionEntity = new PermissionEntity();
     permissionEntity.setId(5);

+ 3 - 0
ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog240Test.java

@@ -464,6 +464,7 @@ public class UpgradeCatalog240Test {
     Method addNewConfigurationsFromXml = AbstractUpgradeCatalog.class.getDeclaredMethod("addNewConfigurationsFromXml");
     Method updateAlerts = UpgradeCatalog240.class.getDeclaredMethod("updateAlerts");
     Method addManageUserPersistedDataPermission = UpgradeCatalog240.class.getDeclaredMethod("addManageUserPersistedDataPermission");
+    Method allowClusterOperatorToManageCredentials = UpgradeCatalog240.class.getDeclaredMethod("allowClusterOperatorToManageCredentials");
     Method addSettingPermission = UpgradeCatalog240.class.getDeclaredMethod("addSettingPermission");
     Method updateHDFSConfigs = UpgradeCatalog240.class.getDeclaredMethod("updateHDFSConfigs");
     Method updateHIVEConfigs = UpgradeCatalog240.class.getDeclaredMethod("updateHIVEConfigs");
@@ -495,6 +496,7 @@ public class UpgradeCatalog240Test {
             .addMockedMethod(updateAlerts)
             .addMockedMethod(addSettingPermission)
             .addMockedMethod(addManageUserPersistedDataPermission)
+            .addMockedMethod(allowClusterOperatorToManageCredentials)
             .addMockedMethod(updateHDFSConfigs)
             .addMockedMethod(updateHIVEConfigs)
             .addMockedMethod(updateAmsConfigs)
@@ -520,6 +522,7 @@ public class UpgradeCatalog240Test {
     upgradeCatalog240.updateAlerts();
     upgradeCatalog240.addSettingPermission();
     upgradeCatalog240.addManageUserPersistedDataPermission();
+    upgradeCatalog240.allowClusterOperatorToManageCredentials();
     upgradeCatalog240.updateHDFSConfigs();
     upgradeCatalog240.updateHIVEConfigs();
     upgradeCatalog240.updateAMSConfigs();

+ 9 - 0
ambari-server/src/test/resources/custom_action_definitions/cust_action_definitions1.xml

@@ -27,6 +27,7 @@
     <targetComponent>TASKTRACKER</targetComponent>
     <description>A random test</description>
     <targetType>ALL</targetType>
+    <permissions>HOST.ADD_DELETE_HOSTS,HOST.ADD_DELETE_COMPONENTS</permissions>
   </actionDefinition>
   <actionDefinition>
     <actionName>customAction2</actionName>
@@ -34,9 +35,17 @@
     <targetService>MAPREDUCE</targetService>
     <targetComponent>TASKTRACKER</targetComponent>
     <description>A random test</description>
+    <permissions> HOST.ADD_DELETE_HOSTS , HOST.ADD_DELETE_COMPONENTS </permissions> <!-- Note the spaces -->
   </actionDefinition>
   <actionDefinition>
     <actionName>customAction3</actionName>
+    <actionType>USER</actionType>
+    <targetService>MAPREDUCE</targetService>
+    <targetComponent>TASKTRACKER</targetComponent>
+    <description>A random test</description>
+  </actionDefinition>
+  <actionDefinition>
+    <actionName>customAction4</actionName>
     <actionType>USERS_OWN</actionType>
     <targetService>MAPREDUCE</targetService>
     <targetComponent>TASKTRACKER</targetComponent>

+ 33 - 0
ambari-server/src/test/resources/custom_action_definitions_invalid/cust_action_definitions_invalid.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="action_definition.xsl"?>
+
+<!--
+  ~ 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.
+  -->
+
+<actionDefinitions>
+  <actionDefinition>
+    <actionName>invalidCustomAction1</actionName>
+    <actionType>USER</actionType>
+    <inputs>threshold</inputs>
+    <targetService>MAPREDUCE</targetService>
+    <targetComponent>TASKTRACKER</targetComponent>
+    <description>A random test</description>
+    <targetType>ALL</targetType>
+    <permissions>NOT.A.PERMISSION</permissions>
+  </actionDefinition>
+</actionDefinitions>