ambari-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rle...@apache.org
Subject [4/4] ambari git commit: AMBARI-13214. Create a credentials resource used to securely set, update, and remove credentials used by Ambari (rlevas)
Date Thu, 01 Oct 2015 21:35:05 GMT
AMBARI-13214. Create a credentials resource used to securely set, update, and remove credentials used by Ambari (rlevas)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/7a962380
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/7a962380
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/7a962380

Branch: refs/heads/branch-2.1
Commit: 7a9623800b8a82e3927c08c8d059f61807869c3a
Parents: 97d7c2a
Author: Robert Levas <rlevas@hortonworks.com>
Authored: Thu Oct 1 17:34:49 2015 -0400
Committer: Robert Levas <rlevas@hortonworks.com>
Committed: Thu Oct 1 17:34:49 2015 -0400

----------------------------------------------------------------------
 ambari-server/docs/api/v1/credential-create.md  |  87 +++
 ambari-server/docs/api/v1/credential-delete.md  |  65 ++
 ambari-server/docs/api/v1/credential-get.md     |  77 +++
 ambari-server/docs/api/v1/credential-list.md    |  88 +++
 .../docs/api/v1/credential-resources.md         |  59 ++
 ambari-server/docs/api/v1/credential-update.md  |  92 +++
 ambari-server/docs/api/v1/index.md              |   5 +
 .../resources/CredentialResourceDefinition.java |  46 ++
 .../resources/ResourceInstanceFactoryImpl.java  |   4 +
 .../server/api/services/ClusterService.java     |  15 +
 .../server/api/services/CredentialService.java  | 144 ++++
 .../server/configuration/Configuration.java     | 148 +++-
 .../AmbariManagementControllerImpl.java         |  20 +
 .../server/controller/ClusterResponse.java      |  10 +
 .../server/controller/ControllerModule.java     |   6 +
 .../server/controller/KerberosHelperImpl.java   |  28 +-
 .../controller/ResourceProviderFactory.java     |   3 +
 .../AbstractControllerResourceProvider.java     |   2 +
 .../internal/ClusterResourceProvider.java       |   3 +
 .../internal/CredentialResourceProvider.java    | 428 ++++++++++++
 .../ambari/server/controller/spi/Resource.java  |   4 +-
 .../AmbariAuthorizationFilter.java              |  16 +-
 .../server/security/credential/Credential.java  |  34 +
 .../security/credential/CredentialFactory.java  |  51 ++
 .../credential/GenericKeyCredential.java        |  79 +++
 .../InvalidCredentialValueException.java        |  32 +
 .../credential/PrincipalKeyCredential.java      | 154 +++++
 .../encryption/AbstractCredentialStore.java     | 415 +++++++++++
 .../security/encryption/CredentialProvider.java |  36 +-
 .../security/encryption/CredentialStore.java    |  78 +++
 .../encryption/CredentialStoreService.java      | 111 ++-
 .../encryption/CredentialStoreServiceImpl.java  | 452 ++++++------
 .../encryption/CredentialStoreType.java         |  27 +
 .../encryption/FileBasedCredentialStore.java    | 162 +++++
 .../FileBasedCredentialStoreService.java        | 151 ----
 .../encryption/InMemoryCredentialStore.java     | 233 +++++++
 .../InMemoryCredentialStoreService.java         | 143 ----
 .../CredentialResourceDefinitionTest.java       |  62 ++
 .../api/services/CredentialServiceTest.java     |  99 +++
 .../server/configuration/ConfigurationTest.java |  17 +-
 .../AmbariManagementControllerImplTest.java     |  27 +-
 .../CredentialResourceProviderTest.java         | 683 +++++++++++++++++++
 .../internal/UserResourceProviderTest.java      |   3 +
 .../AmbariAuthorizationFilterTest.java          |  42 +-
 .../encryption/CredentialProviderTest.java      |  11 +-
 .../CredentialStoreServiceImplTest.java         | 293 ++++++++
 .../encryption/CredentialStoreServiceTest.java  | 239 -------
 .../encryption/CredentialStoreTest.java         | 248 +++++++
 48 files changed, 4422 insertions(+), 810 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/docs/api/v1/credential-create.md
----------------------------------------------------------------------
diff --git a/ambari-server/docs/api/v1/credential-create.md b/ambari-server/docs/api/v1/credential-create.md
new file mode 100644
index 0000000..8f3f297
--- /dev/null
+++ b/ambari-server/docs/api/v1/credential-create.md
@@ -0,0 +1,87 @@
+
+<!---
+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.
+-->
+
+Create Credential
+=====
+
+[Back to Credential Resources](credential-resources.md)
+
+**Summary**
+
+Create a new credential resource and stores it in either the persistent or temporary credential store.
+
+    POST /clusters/:clusterName/credentials/:alias
+
+**Response**
+
+<table>
+  <tr>
+    <th>HTTP CODE</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>201</td>
+    <td>Created</td>  
+  </tr>
+  <tr>
+    <td>400</td>
+    <td>Bad Request</td>  
+  </tr>
+  <tr>
+    <td>401</td>
+    <td>Unauthorized</td>  
+  </tr>
+  <tr>
+    <td>403</td>
+    <td>Forbidden</td>  
+  </tr> 
+  <tr>
+    <td>500</td>
+    <td>Internal Server Error</td>  
+  </tr>
+</table>
+
+**Examples**
+
+Create a credential with an alias of 'external.db' that is stored in the _persistent_ credential store.
+
+    POST /clusters/c1/credentials/external.db
+
+    {
+      "Credential" : {
+        "principal" : "db_admin",
+        "key" : "S3cr3tK3y!",
+        "type" : "persisted"
+      }
+    }
+    
+    201 Created
+
+Create a credential with an alias of 'kdc.admin' that is stored in the _temporary_ credential store.
+
+    POST /clusters/c1/credentials/kdc.admin
+
+    {
+      "Credential" : {
+        "principal" : "admin/admin@EXAMPLE.COM",
+        "key" : "!h4d00p!",
+        "type" : "temporary"
+      }
+    }
+    
+    201 Created

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/docs/api/v1/credential-delete.md
----------------------------------------------------------------------
diff --git a/ambari-server/docs/api/v1/credential-delete.md b/ambari-server/docs/api/v1/credential-delete.md
new file mode 100644
index 0000000..6f5294c
--- /dev/null
+++ b/ambari-server/docs/api/v1/credential-delete.md
@@ -0,0 +1,65 @@
+
+<!---
+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.
+-->
+
+Delete Credential
+=====
+
+[Back to Credential Resources](credential-resources.md)
+
+**Summary**
+
+Removes an existing credential resource from either the persistent or temporary credential stores.
+
+    DELETE /clusters/:clusterName/credentials/:alias
+
+**Response**
+
+<table>
+  <tr>
+    <th>HTTP CODE</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>200</td>
+    <td>OK</td>  
+  </tr>
+  <tr>
+    <td>400</td>
+    <td>Bad Request</td>  
+  </tr>
+  <tr>
+    <td>401</td>
+    <td>Unauthorized</td>  
+  </tr>
+  <tr>
+    <td>403</td>
+    <td>Forbidden</td>  
+  </tr> 
+  <tr>
+    <td>500</td>
+    <td>Internal Server Error</td>  
+  </tr>
+</table>
+
+**Example**
+
+Delete credential with the alias of 'external.db'.
+
+    DELETE /clusters/c1/credentials/external.db
+
+    200 OK

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/docs/api/v1/credential-get.md
----------------------------------------------------------------------
diff --git a/ambari-server/docs/api/v1/credential-get.md b/ambari-server/docs/api/v1/credential-get.md
new file mode 100644
index 0000000..1cccdf4
--- /dev/null
+++ b/ambari-server/docs/api/v1/credential-get.md
@@ -0,0 +1,77 @@
+
+<!---
+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.
+-->
+
+List Credentials
+=====
+
+[Back to Credential Resources](credential-resources.md)
+
+**Summary**
+
+Gets the details about an existing credential. Nor all information about a credential will be returned due to security concerns.
+
+    GET /clusters/:clusterName/credentials/:alias
+
+**Response**
+
+<table>
+  <tr>
+    <th>HTTP CODE</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>200</td>
+    <td>OK</td>  
+  </tr>
+  <tr>
+    <td>400</td>
+    <td>Bad Request</td>  
+  </tr>
+  <tr>
+    <td>401</td>
+    <td>Unauthorized</td>  
+  </tr>
+  <tr>
+    <td>403</td>
+    <td>Forbidden</td>  
+  </tr> 
+  <tr>
+    <td>404</td>
+    <td>Not Found</td>  
+  </tr>
+  <tr>
+    <td>500</td>
+    <td>Internal Server Error</td>  
+  </tr>
+</table>
+
+**Example**
+
+Get the credential with the alias name of 'kdc.admin.credentials'.
+
+    GET /clusters/c1/credentials/kdc.admin.credentials
+
+    200 OK
+    {
+      "href" : "http://your.ambari.server/api/v1/clusters/c1/credentials/kdc.admin.credentials",
+      "Credential" : {
+        "alias" : "kdc.admin.credentials",
+        "cluster_name" : "c1",
+        "type" : "temporary"
+      }
+    }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/docs/api/v1/credential-list.md
----------------------------------------------------------------------
diff --git a/ambari-server/docs/api/v1/credential-list.md b/ambari-server/docs/api/v1/credential-list.md
new file mode 100644
index 0000000..0a11643
--- /dev/null
+++ b/ambari-server/docs/api/v1/credential-list.md
@@ -0,0 +1,88 @@
+
+<!---
+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.
+-->
+
+List Credentials
+=====
+
+[Back to Credential Resources](credential-resources.md)
+
+**Summary**
+
+Returns a collection of the currently stored (persisted or temporary) credentials.  For each credential in the collection, only the cluster name, alias, and credential store type attributes will be returned.
+
+    GET /clusters/:clusterName/credentials
+
+**Response**
+
+<table>
+  <tr>
+    <th>HTTP CODE</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>200</td>
+    <td>OK</td>  
+  </tr>
+  <tr>
+    <td>400</td>
+    <td>Bad Request</td>  
+  </tr>
+  <tr>
+    <td>401</td>
+    <td>Unauthorized</td>  
+  </tr>
+  <tr>
+    <td>403</td>
+    <td>Forbidden</td>  
+  </tr> 
+  <tr>
+    <td>404</td>
+    <td>Not Found</td>  
+  </tr>
+  <tr>
+    <td>500</td>
+    <td>Internal Server Error</td>  
+  </tr>
+</table>
+
+**Example**
+
+Get the collection of all currently stored credentials.
+
+    GET /clusters/c1/credentials
+
+    200 OK
+    {
+      "href" : "http://your.ambari.server/api/v1/clusters/c1/credentials",
+      "items" : [
+        {
+          "href" : "http://your.ambari.server/api/v1/clusters/c1/credentials/kdc.admin.credentials",
+          "Credential" : {
+            "alias" : "kdc.admin.credentials",
+            "cluster_name" : "c1"
+          }
+        },
+        {
+          "href" : "http://your.ambari.server/api/v1/clusters/c1/credentials/service.admin.credentials",
+          "Credential" : {
+            "alias" : "service.admin.credentials",
+            "cluster_name" : "c1"
+          }
+        }
+      ]
+    }

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/docs/api/v1/credential-resources.md
----------------------------------------------------------------------
diff --git a/ambari-server/docs/api/v1/credential-resources.md b/ambari-server/docs/api/v1/credential-resources.md
new file mode 100644
index 0000000..6cc8dfe
--- /dev/null
+++ b/ambari-server/docs/api/v1/credential-resources.md
@@ -0,0 +1,59 @@
+<!---
+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.
+-->
+
+# Credential Resources
+Credential resources represent named principal/key pairs related to a particular cluster. Credentials are sub-resources of a Cluster.
+
+Credential resources may be stored in the persisted credential store or the temporary credential store. If stored in the persisted credential store, the credential will be stored until deleted. If stored in the temporary credential store, the credential will be stored until a timeout is met (default 90 minutes) or Ambari is restarted. 
+
+###API Summary
+
+- [List credentials](credential-list.md)
+- [Get credential](credential-get.md)
+- [Create credential](credential-create.md)
+- [Update credential](credential-update.md)
+- [Delete credential](credential-delete.md)
+
+###Properties
+
+<table>
+  <tr>
+    <th>Property</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>Credential/cluster_name</td>
+    <td>The cluster name</td>  
+  </tr>
+  <tr>
+    <td>Credential/alias</td>
+    <td>The alias name</td>  
+  </tr>
+  <tr>
+    <td>Credential/principal</td>
+    <td>The principal (or username) - this value is not visible when getting credentials</td>  
+  </tr>
+  <tr>
+    <td>Credential/key</td>
+    <td>The secret key (or password) - this value is not visible when getting credentials</td>  
+  </tr>
+  <tr>
+    <td>Credential/type</td>
+    <td>The type of credential store: persisted or temporary</td>  
+  </tr>
+</table>
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/docs/api/v1/credential-update.md
----------------------------------------------------------------------
diff --git a/ambari-server/docs/api/v1/credential-update.md b/ambari-server/docs/api/v1/credential-update.md
new file mode 100644
index 0000000..6ed960b
--- /dev/null
+++ b/ambari-server/docs/api/v1/credential-update.md
@@ -0,0 +1,92 @@
+
+<!---
+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.
+-->
+
+Update Credential
+=====
+
+[Back to Credential Resources](credential-resources.md)
+
+**Summary**
+
+Update an existing credential resource to change it's principal, key, and/or persist values.
+
+    PUT /clusters/:clusterName/credentials/:alias
+
+**Response**
+
+<table>
+  <tr>
+    <th>HTTP CODE</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>200</td>
+    <td>OK</td>  
+  </tr>
+  <tr>
+    <td>400</td>
+    <td>Bad Request</td>  
+  </tr>
+  <tr>
+    <td>401</td>
+    <td>Unauthorized</td>  
+  </tr>
+  <tr>
+    <td>403</td>
+    <td>Forbidden</td>  
+  </tr> 
+  <tr>
+    <td>404</td>
+    <td>Not Found</td>  
+  </tr> 
+  <tr>
+    <td>405</td>
+    <td>Method Not Allowed</td>  
+  </tr> 
+  <tr>
+    <td>500</td>
+    <td>Internal Server Error</td>  
+  </tr>
+</table>
+
+**Examples**
+
+Update a credential, changing its principal and key values, but leaving its persistence value alone.
+
+    PUT /clusters/c1/credentials/external.db
+
+    {
+      "Credential" : {
+        "principal" : "db_admin",
+        "key" : "N3WS3cr3tK3y!"
+      }
+    }
+    
+    200 OK
+
+Update a credential, changing only its storage type value.  This will remove the credential from its current storage facility and create a new entry in the requested storage facility.
+
+    POST /clusters/c1/credentials/kdc.admin
+
+    {
+      "Credential" : {
+        "type" : "temporary"
+      }
+    }
+    
+    200 OK

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/docs/api/v1/index.md
----------------------------------------------------------------------
diff --git a/ambari-server/docs/api/v1/index.md b/ambari-server/docs/api/v1/index.md
index c1e464c..8a53e39 100644
--- a/ambari-server/docs/api/v1/index.md
+++ b/ambari-server/docs/api/v1/index.md
@@ -362,6 +362,11 @@ Alert resources contain the relationships between definitions, history, and the
 
 - [Alert History](alerts.md) The current state of an alert and all of its historical events are available for querying.
 
+#### credentials
+Credential resources are principal (or username) and password pairs that are tagged with an alias and stored either in a _temporary_ or _persisted_ storage facility.  These resources may be created, updated, and deleted; however (for security reasons) when getting credential resources, only the alias and an indicator of whether the credential is stored in the temporary or persisted store is returned.  Credentials are sub-resources of Clusters.
+
+[Credential Resources](credential-resources.md)
+
 Partial Response
 ----
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/api/resources/CredentialResourceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/CredentialResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/CredentialResourceDefinition.java
new file mode 100644
index 0000000..6d4f83e
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/CredentialResourceDefinition.java
@@ -0,0 +1,46 @@
+/*
+ * 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.api.resources;
+
+import org.apache.ambari.server.controller.spi.Resource;
+
+import java.util.Collection;
+
+
+/**
+ * Credential resource definition.
+ */
+public class CredentialResourceDefinition extends BaseResourceDefinition {
+  /**
+   * Constructor.
+   */
+  public CredentialResourceDefinition() {
+    super(Resource.Type.Credential);
+  }
+
+  @Override
+  public String getPluralName() {
+    return "credentials";
+  }
+
+  @Override
+  public String getSingularName() {
+    return "credential";
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
index 1e219ff..0499590 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
@@ -388,6 +388,10 @@ public class ResourceInstanceFactoryImpl implements ResourceInstanceFactory {
         resourceDefinition = new HostKerberosIdentityResourceDefinition();
         break;
 
+      case Credential:
+        resourceDefinition = new CredentialResourceDefinition();
+        break;
+
       default:
         throw new IllegalArgumentException("Unsupported resource type: " + type);
     }

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
index 7bb0a72..4954a96 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
@@ -670,6 +670,21 @@ public class ClusterService extends BaseService {
     return new WidgetService(clusterName);
   }
 
+  /**
+   * Gets the credentials service.
+   *
+   * @param request          the request.
+   * @param clusterName         the cluster name.
+   * @return the credentials service.
+   */
+  @Path("{clusterName}/credentials")
+  public CredentialService getCredentials(
+      @Context javax.ws.rs.core.Request request,
+      @PathParam("clusterName") String clusterName) {
+    hasPermission(Request.Type.valueOf(request.getMethod()), clusterName);
+    return new CredentialService(clusterName);
+  }
+
   // ----- helper methods ----------------------------------------------------
 
   /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/api/services/CredentialService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/CredentialService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/CredentialService.java
new file mode 100644
index 0000000..01f8e8f
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/CredentialService.java
@@ -0,0 +1,144 @@
+/*
+ * 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.api.services;
+
+import org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.controller.spi.Resource;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Service responsible for handling REST requests for the /clusters/{cluster_name}/credentials endpoint.
+ */
+public class CredentialService extends BaseService {
+
+  private final String clusterName;
+
+  public CredentialService(String clusterName) {
+    this.clusterName = clusterName;
+  }
+
+  /**
+   * Handles: GET  /clusters/{cluster_name}/credentials
+   * Get all credentials.
+   *
+   * @param headers http headers
+   * @param ui      uri info
+   * @return credential collection resource representation
+   */
+  @GET
+  @Produces("text/plain")
+  public Response getCredentials(@Context HttpHeaders headers, @Context UriInfo ui) {
+    return handleRequest(headers, null, ui, Request.Type.GET, createCredentialResource(null));
+  }
+
+  /**
+   * Handles: GET  /clusters/{cluster_name}/credentials/{alias}
+   * Get a specific credential.
+   *
+   * @param headers http headers
+   * @param ui      uri info
+   * @param alias   alias (or credential ID)
+   * @return credential instance representation
+   */
+  @GET
+  @Path("{alias}")
+  @Produces("text/plain")
+  public Response getCredential(@Context HttpHeaders headers, @Context UriInfo ui,
+                                @PathParam("alias") String alias) {
+    return handleRequest(headers, null, ui, Request.Type.GET, createCredentialResource(alias));
+  }
+
+  /**
+   * Handles: POST /clusters/{cluster_name}/credentials/{alias}
+   * Create a specific credential.
+   *
+   * @param headers http headers
+   * @param ui      uri info
+   * @param alias   alias (or credential ID)
+   * @return information regarding the created credential
+   */
+  @POST
+  @Path("{alias}")
+  @Produces("text/plain")
+  public Response createCredential(String body, @Context HttpHeaders headers, @Context UriInfo ui,
+                                   @PathParam("alias") String alias) {
+    return handleRequest(headers, body, ui, Request.Type.POST, createCredentialResource(alias));
+  }
+
+  /**
+   * Handles: PUT /clusters/{cluster_name}/credentials/{alias}
+   * Create a specific credential.
+   *
+   * @param headers http headers
+   * @param ui      uri info
+   * @param alias   alias (or credential ID)
+   * @return information regarding the created credential
+   */
+  @PUT
+  @Path("{alias}")
+  @Produces("text/plain")
+  public Response updateCredential(String body, @Context HttpHeaders headers, @Context UriInfo ui,
+                                   @PathParam("alias") String alias) {
+    return handleRequest(headers, body, ui, Request.Type.PUT, createCredentialResource(alias));
+  }
+
+  /**
+   * Handles: DELETE /clusters/{cluster_name}/credentials/{alias}
+   * Delete a specific credential.
+   *
+   * @param headers http headers
+   * @param ui      uri info
+   * @param alias   alias (or credential ID)
+   * @return information regarding the deleted credential
+   */
+  @DELETE
+  @Path("{alias}")
+  @Produces("text/plain")
+  public Response deleteCredential(@Context HttpHeaders headers, @Context UriInfo ui,
+                                   @PathParam("alias") String alias) {
+    return handleRequest(headers, null, ui, Request.Type.DELETE, createCredentialResource(alias));
+  }
+
+  /**
+   * Create a credential resource instance.
+   *
+   * @param alias alias (or credential ID)
+   * @return a credential resource instance
+   */
+  ResourceInstance createCredentialResource(String alias) {
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.Cluster, this.clusterName);
+    mapIds.put(Resource.Type.Credential, alias);
+
+    return createResource(Resource.Type.Credential, mapIds);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
index 5bc3276..04c1c19 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
@@ -246,8 +246,14 @@ public class Configuration {
   public static final String HIVE_METASTORE_PASSWORD_PROPERTY = "javax.jdo.option.ConnectionPassword";
   public static final String MASTER_KEY_PERSISTED = "security.master.key.ispersisted";
   public static final String MASTER_KEY_LOCATION = "security.master.key.location";
+  public static final String MASTER_KEYSTORE_LOCATION = "security.master.keystore.location";
   public static final String MASTER_KEY_ENV_PROP = "AMBARI_SECURITY_MASTER_KEY";
   public static final String MASTER_KEY_FILENAME_DEFAULT = "master";
+  public static final String MASTER_KEYSTORE_FILENAME_DEFAULT = "credentials.jceks";
+  public static final String TEMPORARY_KEYSTORE_RETENTION_MINUTES = "security.temporary.keystore.retention.minutes";
+  public static final long TEMPORARY_KEYSTORE_RETENTION_MINUTES_DEFAULT = 90;
+  public static final String TEMPORARY_KEYSTORE_ACTIVELY_PURGE = "security.temporary.keystore.actibely.purge";
+  public static final boolean TEMPORARY_KEYSTORE_ACTIVELY_PURGE_DEFAULT = true;
 
   /**
    * Key for repo validation suffixes.
@@ -770,7 +776,9 @@ public class Configuration {
     if (!credentialProviderInitialized) {
       try {
         credentialProvider = new CredentialProvider(null,
-          getMasterKeyLocation(), isMasterKeyPersisted());
+            getMasterKeyLocation(),
+            isMasterKeyPersisted(),
+            getMasterKeyStoreLocation());
       } catch (Exception e) {
         LOG.info("Credential provider creation failed. Reason: " + e.getMessage());
         if (LOG.isDebugEnabled()) {
@@ -1367,15 +1375,135 @@ public class Configuration {
   }
 
   public boolean isMasterKeyPersisted() {
-    String masterKeyLocation = getMasterKeyLocation();
-    File f = new File(masterKeyLocation);
-    return f.exists();
+    File masterKeyFile = getMasterKeyLocation();
+    return (masterKeyFile != null) && masterKeyFile.exists();
   }
 
-  public String getMasterKeyLocation() {
-    String defaultDir = properties.getProperty(MASTER_KEY_LOCATION,
-      properties.getProperty(SRVR_KSTR_DIR_KEY, SRVR_KSTR_DIR_DEFAULT));
-    return defaultDir + File.separator + MASTER_KEY_FILENAME_DEFAULT;
+  public File getServerKeyStoreDirectory() {
+    String path = properties.getProperty(SRVR_KSTR_DIR_KEY, SRVR_KSTR_DIR_DEFAULT);
+    return ((path == null) || path.isEmpty())
+        ? new File(".")
+        : new File(path);
+  }
+
+  /**
+   * Returns a File pointing where master key file is expected to be
+   * <p/>
+   * The master key file is named 'master'.  The directory that this file is to be found in is
+   * calculated by obtaining the directory path assigned to the Ambari property
+   * 'security.master.key.location'; else if that value is empty, then the directory is determined
+   * by calling {@link #getServerKeyStoreDirectory()}.
+   * <p/>
+   * If it exists, this file contains the key used to decrypt values stored in the master keystore.
+   *
+   * @return a File that points to the master key file
+   * @see #getServerKeyStoreDirectory()
+   * @see #MASTER_KEY_FILENAME_DEFAULT
+   */
+  public File getMasterKeyLocation() {
+    File location;
+    String path = properties.getProperty(MASTER_KEY_LOCATION);
+
+    if (StringUtils.isEmpty(path)) {
+      location = new File(getServerKeyStoreDirectory(), MASTER_KEY_FILENAME_DEFAULT);
+      LOG.debug("Value of {} is not set, using {}", MASTER_KEY_LOCATION, location.getAbsolutePath());
+    } else {
+      location = new File(path, MASTER_KEY_FILENAME_DEFAULT);
+      LOG.debug("Value of {} is {}", MASTER_KEY_LOCATION, location.getAbsolutePath());
+    }
+
+    return location;
+  }
+
+  /**
+   * Returns the location of the master keystore file.
+   * <p/>
+   * The master keystore file is named 'credentials.jceks'.  The directory that this file is to be
+   * found in is calculated by obtaining the directory path assigned to the Ambari property
+   * 'security.master.keystore.location'; else if that value is empty, then the directory is determined
+   * by calling {@link #getServerKeyStoreDirectory()}.
+   * <p/>
+   * The location is calculated by obtaining the Ambari property directory path assigned to the key
+   * 'security.master.keystore.location'. If that value is empty, then the directory is determined
+   * by {@link #getServerKeyStoreDirectory()}.
+   *
+   * @return a File that points to the master keystore file
+   * @see #getServerKeyStoreDirectory()
+   * @see #MASTER_KEYSTORE_FILENAME_DEFAULT
+   */
+  public File getMasterKeyStoreLocation() {
+    File location;
+    String path = properties.getProperty(MASTER_KEYSTORE_LOCATION);
+
+    if (StringUtils.isEmpty(path)) {
+      location = new File(getServerKeyStoreDirectory(), MASTER_KEYSTORE_FILENAME_DEFAULT);
+      LOG.debug("Value of {} is not set, using {}", MASTER_KEYSTORE_LOCATION, location.getAbsolutePath());
+    } else {
+      location = new File(path, MASTER_KEYSTORE_FILENAME_DEFAULT);
+      LOG.debug("Value of {} is {}", MASTER_KEYSTORE_LOCATION, location.getAbsolutePath());
+    }
+
+    return location;
+  }
+
+  /**
+   * Gets the temporary keystore retention time in minutes.
+   * <p/>
+   * This value is retrieved from the Ambari property named 'security.temporary.keystore.retention.minutes'.
+   * If not set, the default value of 90 (minutes) will be returned.
+   *
+   * @return a timeout value (in minutes)
+   */
+  public long getTemporaryKeyStoreRetentionMinutes() {
+    long minutes;
+    String value = properties.getProperty(TEMPORARY_KEYSTORE_RETENTION_MINUTES);
+
+    if(StringUtils.isEmpty(value)) {
+      LOG.debug("Value of {} is not set, using default value ({})",
+          TEMPORARY_KEYSTORE_RETENTION_MINUTES, TEMPORARY_KEYSTORE_RETENTION_MINUTES_DEFAULT);
+      minutes = TEMPORARY_KEYSTORE_RETENTION_MINUTES_DEFAULT;
+    }
+    else {
+      try {
+        minutes = Long.parseLong(value);
+        LOG.debug("Value of {} is {}", TEMPORARY_KEYSTORE_RETENTION_MINUTES, value);
+      } catch (NumberFormatException e) {
+        LOG.warn("Value of {} ({}) should be a number, falling back to default value ({})",
+            TEMPORARY_KEYSTORE_RETENTION_MINUTES, value, TEMPORARY_KEYSTORE_RETENTION_MINUTES_DEFAULT);
+        minutes = TEMPORARY_KEYSTORE_RETENTION_MINUTES_DEFAULT;
+      }
+    }
+
+    return minutes;
+  }
+
+  /**
+   * Gets a boolean value indicating whether to actively purge the temporary keystore when the retention
+   * time expires (true) or to passively purge when credentials are queried (false).
+   * <p/>
+   * This value is retrieved from the Ambari property named 'security.temporary.keystore.actibely.purge'.
+   * If not set, the default value of true.
+   *
+   * @return a Boolean value declaring whether to actively (true) or passively (false) purge the temporary keystore
+   */
+  public boolean isActivelyPurgeTemporaryKeyStore() {
+    String value = properties.getProperty(TEMPORARY_KEYSTORE_ACTIVELY_PURGE);
+
+    if (StringUtils.isEmpty(value)) {
+      LOG.debug("Value of {} is not set, using default value ({})",
+          TEMPORARY_KEYSTORE_ACTIVELY_PURGE, TEMPORARY_KEYSTORE_ACTIVELY_PURGE_DEFAULT);
+      return TEMPORARY_KEYSTORE_ACTIVELY_PURGE_DEFAULT;
+    } else if ("true".equalsIgnoreCase(value)) {
+      LOG.debug("Value of {} is {}", TEMPORARY_KEYSTORE_ACTIVELY_PURGE, value);
+      return true;
+    } else if ("false".equalsIgnoreCase(value)) {
+      LOG.debug("Value of {} is {}", TEMPORARY_KEYSTORE_ACTIVELY_PURGE, value);
+      return false;
+    } else {
+      LOG.warn("Value of {} should be either \"true\" or \"false\" but is \"{}\", falling back to default value ({})",
+          TEMPORARY_KEYSTORE_ACTIVELY_PURGE, value, TEMPORARY_KEYSTORE_ACTIVELY_PURGE_DEFAULT);
+      return TEMPORARY_KEYSTORE_ACTIVELY_PURGE_DEFAULT;
+    }
   }
 
   public String getSrvrDisabledCiphers() {
@@ -1844,7 +1972,7 @@ public class Configuration {
    * Gets the number of times connections should be retried to be acquired from
    * the database before giving up.
    *
-   * @return default of {@value #DEFAULT_JDBC_POOL_AQUISITION_RETRY_ATTEMPTS}
+   * @return default of {@value #DEFAULT_JDBC_POOL_ACQUISITION_RETRY_ATTEMPTS}
    */
   public int getConnectionPoolAcquisitionRetryAttempts() {
     return Integer.parseInt(properties.getProperty(
@@ -1855,7 +1983,7 @@ public class Configuration {
   /**
    * Gets the delay in milliseconds between connection acquire attempts.
    *
-   * @return default of {@value #DEFAULT_JDBC_POOL_AQUISITION_RETRY_DELAY}
+   * @return default of {@value #DEFAULT_JDBC_POOL_ACQUISITION_RETRY_DELAY}
    */
   public int getConnectionPoolAcquisitionRetryDelay() {
     return Integer.parseInt(properties.getProperty(

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
index ddbc26d..a4da554 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
@@ -113,6 +113,8 @@ import org.apache.ambari.server.security.authorization.AuthorizationHelper;
 import org.apache.ambari.server.security.authorization.Group;
 import org.apache.ambari.server.security.authorization.User;
 import org.apache.ambari.server.security.authorization.Users;
+import org.apache.ambari.server.security.encryption.CredentialStoreService;
+import org.apache.ambari.server.security.encryption.CredentialStoreType;
 import org.apache.ambari.server.security.ldap.AmbariLdapDataPopulator;
 import org.apache.ambari.server.security.ldap.LdapBatchDto;
 import org.apache.ambari.server.security.ldap.LdapSyncDto;
@@ -248,6 +250,8 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
   private WidgetLayoutDAO widgetLayoutDAO;
   @Inject
   private ClusterDAO clusterDAO;
+  @Inject
+  private CredentialStoreService credentialStoreService;
 
   private MaintenanceStateHelper maintenanceStateHelper;
 
@@ -921,6 +925,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
       ClusterResponse cr = singleCluster.convertToResponse();
       cr.setDesiredConfigs(singleCluster.getDesiredConfigs());
       cr.setDesiredServiceConfigVersions(singleCluster.getActiveServiceConfigVersions());
+      cr.setCredentialStoreServiceProperties(getCredentialStoreServiceProperties());
       response.add(cr);
       return response;
     }
@@ -4366,4 +4371,19 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
   public TimelineMetricCacheProvider getTimelineMetricCacheProvider() {
     return injector.getInstance(TimelineMetricCacheProvider.class);
   }
+
+  /**
+   * Queries the CredentialStoreService to gather properties about it.
+   * <p/>
+   * In particular, the details about which storage facilities are avaialble are returned via Boolean
+   * properties.
+   *
+   * @return a map of properties
+   */
+  public Map<String,String> getCredentialStoreServiceProperties() {
+    Map<String,String> properties = new HashMap<String, String>();
+    properties.put("storage.persistent", String.valueOf(credentialStoreService.isInitialized(CredentialStoreType.PERSISTED)));
+    properties.put("storage.temporary", String.valueOf(credentialStoreService.isInitialized(CredentialStoreType.TEMPORARY)));
+    return properties;
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/controller/ClusterResponse.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ClusterResponse.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ClusterResponse.java
index bb6d88e..e19f8f6 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/ClusterResponse.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ClusterResponse.java
@@ -56,6 +56,8 @@ public class ClusterResponse {
 
   private ClusterHealthReport clusterHealthReport;
 
+  private Map<String, String> credentialStoreServiceProperties = null;
+
   public ClusterResponse(Long clusterId, String clusterName,
                          State provisioningState, SecurityType securityType, Set<String> hostNames, Integer totalHosts,
                          String desiredStackVersion, ClusterHealthReport clusterHealthReport) {
@@ -231,4 +233,12 @@ public class ClusterResponse {
   public void setDesiredServiceConfigVersions(Map<String, Collection<ServiceConfigVersionResponse>> desiredServiceConfigVersions) {
     this.desiredServiceConfigVersions = desiredServiceConfigVersions;
   }
+
+  public void setCredentialStoreServiceProperties(Map<String, String> credentialServiceProperties) {
+    this.credentialStoreServiceProperties = credentialServiceProperties;
+  }
+
+  public Map<String, String> getCredentialStoreServiceProperties() {
+    return credentialStoreServiceProperties;
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
index a40fae6..96eb7f9 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
@@ -62,6 +62,7 @@ import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.configuration.Configuration.ConnectionPoolType;
 import org.apache.ambari.server.configuration.Configuration.DatabaseType;
 import org.apache.ambari.server.controller.internal.ComponentResourceProvider;
+import org.apache.ambari.server.controller.internal.CredentialResourceProvider;
 import org.apache.ambari.server.controller.internal.HostComponentResourceProvider;
 import org.apache.ambari.server.controller.internal.HostKerberosIdentityResourceProvider;
 import org.apache.ambari.server.controller.internal.HostResourceProvider;
@@ -81,6 +82,8 @@ 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.SecurityHelperImpl;
+import org.apache.ambari.server.security.encryption.CredentialStoreService;
+import org.apache.ambari.server.security.encryption.CredentialStoreServiceImpl;
 import org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandlerFactory;
 import org.apache.ambari.server.stack.StackManagerFactory;
 import org.apache.ambari.server.stageplanner.RoleGraphFactory;
@@ -290,6 +293,8 @@ public class ControllerModule extends AbstractModule {
     bind(KerberosServiceDescriptorFactory.class);
     bind(KerberosHelper.class).to(KerberosHelperImpl.class);
 
+    bind(CredentialStoreService.class).to(CredentialStoreServiceImpl.class);
+
     bind(Configuration.class).toInstance(configuration);
     bind(OsFamily.class).toInstance(os_family);
     bind(HostsMap.class).toInstance(hostsMap);
@@ -400,6 +405,7 @@ public class ControllerModule extends AbstractModule {
         .implement(ResourceProvider.class, Names.named("member"), MemberResourceProvider.class)
         .implement(ResourceProvider.class, Names.named("repositoryVersion"), RepositoryVersionResourceProvider.class)
         .implement(ResourceProvider.class, Names.named("hostKerberosIdentity"), HostKerberosIdentityResourceProvider.class)
+        .implement(ResourceProvider.class, Names.named("credential"), CredentialResourceProvider.class)
         .build(ResourceProviderFactory.class));
 
     install(new FactoryModuleBuilder().implement(

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
index 9b5ff3a..262467c 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
@@ -61,7 +61,9 @@ import org.apache.ambari.server.controller.utilities.ClusterControllerHelper;
 import org.apache.ambari.server.controller.utilities.PredicateBuilder;
 import org.apache.ambari.server.metadata.RoleCommandOrder;
 import org.apache.ambari.server.security.SecurePasswordHelper;
-import org.apache.ambari.server.security.encryption.InMemoryCredentialStoreService;
+import org.apache.ambari.server.security.credential.Credential;
+import org.apache.ambari.server.security.credential.PrincipalKeyCredential;
+import org.apache.ambari.server.security.encryption.InMemoryCredentialStore;
 import org.apache.ambari.server.security.encryption.MasterKeyServiceImpl;
 import org.apache.ambari.server.serveraction.ServerAction;
 import org.apache.ambari.server.serveraction.kerberos.CleanupServerAction;
@@ -203,16 +205,16 @@ public class KerberosHelperImpl implements KerberosHelper {
 
   /**
    * The secure storage facility to use to store KDC administrator credentials. This implementation
-   * is uses an InMemoryCredentialStoreService to keep the credentials in memory rather than
+   * is uses an InMemoryCredentialStore to keep the credentials in memory rather than
    * storing them on disk.
    */
-  private final InMemoryCredentialStoreService kdcCredentialStoreService;
+  private final InMemoryCredentialStore kdcCredentialStoreService;
 
   /**
    * Default KerberosHelperImpl constructor
    */
   public KerberosHelperImpl() {
-    kdcCredentialStoreService = new InMemoryCredentialStoreService(DEFAULT_KDC_ADMINISTRATOR_CREDENTIALS_RETENTION_MINUTES, TimeUnit.MINUTES, true);
+    kdcCredentialStoreService = new InMemoryCredentialStore(DEFAULT_KDC_ADMINISTRATOR_CREDENTIALS_RETENTION_MINUTES, TimeUnit.MINUTES, true);
   }
 
   @Override
@@ -931,12 +933,8 @@ public class KerberosHelperImpl implements KerberosHelper {
     kdcCredentialStoreService.removeCredential(KDC_ADMINISTRATOR_CREDENTIAL_ALIAS);
 
     if (credentials != null) {
-      String jsonValue = credentials.toJSON();
-
-      if (jsonValue != null) {
-        kdcCredentialStoreService.setMasterKeyService(new MasterKeyServiceImpl(securePasswordHelper.createSecurePassword()));
-        kdcCredentialStoreService.addCredential(KDC_ADMINISTRATOR_CREDENTIAL_ALIAS, jsonValue.toCharArray());
-      }
+      kdcCredentialStoreService.setMasterKeyService(new MasterKeyServiceImpl(securePasswordHelper.createSecurePassword()));
+      kdcCredentialStoreService.addCredential(KDC_ADMINISTRATOR_CREDENTIAL_ALIAS, new PrincipalKeyCredential(credentials.getPrincipal(), credentials.getPassword()));
     }
   }
 
@@ -967,11 +965,13 @@ public class KerberosHelperImpl implements KerberosHelper {
    */
   @Override
   public KerberosCredential getKDCCredentials() throws AmbariException {
-    char[] credentials = kdcCredentialStoreService.getCredential(KDC_ADMINISTRATOR_CREDENTIAL_ALIAS);
+    Credential credentials = kdcCredentialStoreService.getCredential(KDC_ADMINISTRATOR_CREDENTIAL_ALIAS);
 
-    return (credentials == null)
-        ? null
-        : KerberosCredential.fromJSON(new String(credentials));
+    if (credentials instanceof PrincipalKeyCredential) {
+      PrincipalKeyCredential principalKeyCredential = (PrincipalKeyCredential) credentials;
+      return new KerberosCredential(principalKeyCredential.getPrincipal(), principalKeyCredential.getKey(), null);
+    } else
+      return null;
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java
index 5d1143a..f1e2ee7 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java
@@ -56,6 +56,9 @@ public interface ResourceProviderFactory {
   @Named("hostKerberosIdentity")
   ResourceProvider getHostKerberosIdentityResourceProvider(AmbariManagementController managementController);
 
+  @Named("credential")
+  ResourceProvider getCredentialResourceProvider(AmbariManagementController managementController);
+
   @Named("repositoryVersion")
   ResourceProvider getRepositoryVersionResourceProvider();
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
index 9163656..321b45b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
@@ -169,6 +169,8 @@ public abstract class AbstractControllerResourceProvider extends AbstractResourc
         return new WidgetResourceProvider(managementController);
       case HostKerberosIdentity:
         return resourceProviderFactory.getHostKerberosIdentityResourceProvider(managementController);
+      case Credential:
+        return resourceProviderFactory.getCredentialResourceProvider(managementController);
 
       default:
         throw new IllegalArgumentException("Unknown type " + type);

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java
index 7e75a75..e2f132e 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java
@@ -65,6 +65,7 @@ public class ClusterResourceProvider extends AbstractControllerResourceProvider
   public static final String CLUSTER_DESIRED_SERVICE_CONFIG_VERSIONS_PROPERTY_ID = PropertyHelper.getPropertyId("Clusters", "desired_service_config_versions");
   public static final String CLUSTER_TOTAL_HOSTS_PROPERTY_ID = PropertyHelper.getPropertyId("Clusters", "total_hosts");
   public static final String CLUSTER_HEALTH_REPORT_PROPERTY_ID = PropertyHelper.getPropertyId("Clusters", "health_report");
+  public static final String CLUSTER_CREDENTIAL_STORE_PROPERTIES_PROPERTY_ID = PropertyHelper.getPropertyId("Clusters", "credential_store_properties");
   public static final String BLUEPRINT_PROPERTY_ID = PropertyHelper.getPropertyId(null, "blueprint");
   public static final String SESSION_ATTRIBUTES_PROPERTY_ID = "session_attributes";
 
@@ -118,6 +119,7 @@ public class ClusterResourceProvider extends AbstractControllerResourceProvider
     propertyIds.add(CLUSTER_DESIRED_SERVICE_CONFIG_VERSIONS_PROPERTY_ID);
     propertyIds.add(CLUSTER_TOTAL_HOSTS_PROPERTY_ID);
     propertyIds.add(CLUSTER_HEALTH_REPORT_PROPERTY_ID);
+    propertyIds.add(CLUSTER_CREDENTIAL_STORE_PROPERTIES_PROPERTY_ID);
     propertyIds.add(BLUEPRINT_PROPERTY_ID);
     propertyIds.add(SESSION_ATTRIBUTES_PROPERTY_ID);
   }
@@ -204,6 +206,7 @@ public class ClusterResourceProvider extends AbstractControllerResourceProvider
         response.getDesiredServiceConfigVersions(), requestedIds);
       setResourceProperty(resource, CLUSTER_TOTAL_HOSTS_PROPERTY_ID, response.getTotalHosts(), requestedIds);
       setResourceProperty(resource, CLUSTER_HEALTH_REPORT_PROPERTY_ID, response.getClusterHealthReport(), requestedIds);
+      setResourceProperty(resource, CLUSTER_CREDENTIAL_STORE_PROPERTIES_PROPERTY_ID, response.getCredentialStoreServiceProperties(), requestedIds);
 
       resource.setProperty(CLUSTER_VERSION_PROPERTY_ID,
           response.getDesiredStackVersion());

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/CredentialResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/CredentialResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/CredentialResourceProvider.java
new file mode 100644
index 0000000..55f26a0
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/CredentialResourceProvider.java
@@ -0,0 +1,428 @@
+/*
+ * 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.controller.internal;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.StaticallyInject;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.NoSuchResourceException;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.RequestStatus;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.Resource.Type;
+import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.security.credential.Credential;
+import org.apache.ambari.server.security.credential.PrincipalKeyCredential;
+import org.apache.ambari.server.security.encryption.CredentialStoreService;
+import org.apache.ambari.server.security.encryption.CredentialStoreType;
+import org.apache.commons.lang.StringUtils;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A write-only resource provider for securely stored credentials
+ */
+@StaticallyInject
+public class CredentialResourceProvider extends AbstractControllerResourceProvider {
+
+  // ----- Property ID constants ---------------------------------------------
+
+  public static final String CREDENTIAL_CLUSTER_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("Credential", "cluster_name");
+  public static final String CREDENTIAL_ALIAS_PROPERTY_ID = PropertyHelper.getPropertyId("Credential", "alias");
+  public static final String CREDENTIAL_PRINCIPAL_PROPERTY_ID = PropertyHelper.getPropertyId("Credential", "principal");
+  public static final String CREDENTIAL_KEY_PROPERTY_ID = PropertyHelper.getPropertyId("Credential", "key");
+  public static final String CREDENTIAL_TYPE_PROPERTY_ID = PropertyHelper.getPropertyId("Credential", "type");
+
+  private static final Set<String> PK_PROPERTY_IDS;
+  private static final Set<String> PROPERTY_IDS;
+  private static final Map<Type, String> KEY_PROPERTY_IDS;
+
+  static {
+    Set<String> set;
+    set = new HashSet<String>();
+    set.add(CREDENTIAL_CLUSTER_NAME_PROPERTY_ID);
+    set.add(CREDENTIAL_ALIAS_PROPERTY_ID);
+    PK_PROPERTY_IDS = Collections.unmodifiableSet(set);
+
+    set = new HashSet<String>();
+    set.add(CREDENTIAL_CLUSTER_NAME_PROPERTY_ID);
+    set.add(CREDENTIAL_ALIAS_PROPERTY_ID);
+    set.add(CREDENTIAL_PRINCIPAL_PROPERTY_ID);
+    set.add(CREDENTIAL_KEY_PROPERTY_ID);
+    set.add(CREDENTIAL_TYPE_PROPERTY_ID);
+    PROPERTY_IDS = Collections.unmodifiableSet(set);
+
+    HashMap<Type, String> map = new HashMap<Type, String>();
+    map.put(Type.Cluster, CREDENTIAL_CLUSTER_NAME_PROPERTY_ID);
+    map.put(Type.Credential, CREDENTIAL_ALIAS_PROPERTY_ID);
+    KEY_PROPERTY_IDS = Collections.unmodifiableMap(map);
+  }
+
+  /**
+   * The secure storage facility to use to store credentials.
+   */
+  @Inject
+  private CredentialStoreService credentialStoreService;
+
+
+  /**
+   * Create a new resource provider.
+   */
+  @AssistedInject
+  public CredentialResourceProvider(@Assisted AmbariManagementController managementController) {
+    super(PROPERTY_IDS, KEY_PROPERTY_IDS, managementController);
+  }
+
+  @Override
+  public RequestStatus createResources(final Request request)
+      throws SystemException, UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException {
+
+    for (final Map<String, Object> properties : request.getProperties()) {
+      createResources(new CreateResourcesCommand(properties));
+    }
+
+    notifyCreate(Type.Credential, request);
+    return getRequestStatus(null);
+  }
+
+  @Override
+  public Set<Resource> getResources(Request request, Predicate predicate)
+      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+
+    Set<String> requestedIds = getRequestPropertyIds(request, predicate);
+    Set<Resource> resources = new HashSet<Resource>();
+
+    for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
+      String clusterName = (String) propertyMap.get(CREDENTIAL_CLUSTER_NAME_PROPERTY_ID);
+
+      if (null == clusterName || clusterName.isEmpty()) {
+        throw new IllegalArgumentException("Invalid argument, cluster name is required");
+      }
+
+      String alias = (String) propertyMap.get(CREDENTIAL_ALIAS_PROPERTY_ID);
+      if (!StringUtils.isEmpty(alias)) {
+        try {
+          if (credentialStoreService.containsCredential(clusterName, alias)) {
+            resources.add(toResource(clusterName, alias, credentialStoreService.getCredentialStoreType(clusterName, alias), requestedIds));
+          }
+        } catch (AmbariException e) {
+          throw new SystemException(e.getLocalizedMessage(), e);
+        }
+      } else {
+        try {
+          Map<String, CredentialStoreType> results = credentialStoreService.listCredentials(clusterName);
+          if (results != null) {
+            for (Map.Entry<String, CredentialStoreType> entry : results.entrySet()) {
+              resources.add(toResource(clusterName, entry.getKey(), entry.getValue(), requestedIds));
+            }
+          }
+        } catch (AmbariException e) {
+          throw new SystemException(e.getLocalizedMessage(), e);
+        }
+      }
+    }
+
+    if ((predicate != null) && resources.isEmpty()) {
+      throw new NoSuchResourceException("The requested resource doesn't exist: Credential not found, " + predicate);
+    }
+
+    return resources;
+  }
+
+  @Override
+  public RequestStatus updateResources(Request request, Predicate predicate)
+      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+
+    for (Map<String, Object> requestPropMap : request.getProperties()) {
+      for (Map<String, Object> propertyMap : getPropertyMaps(requestPropMap, predicate)) {
+        if (modifyResources(new ModifyResourcesCommand(propertyMap)) == null) {
+          throw new NoSuchResourceException("The requested resource doesn't exist: Credential not found, " + getAlias(propertyMap));
+        }
+      }
+    }
+
+    notifyUpdate(Type.Credential, request, predicate);
+    return getRequestStatus(null);
+  }
+
+  @Override
+  public RequestStatus deleteResources(Predicate predicate)
+      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+
+    final Set<Map<String, Object>> propertyMaps = getPropertyMaps(predicate);
+
+    for (final Map<String, Object> properties : propertyMaps) {
+      modifyResources(new DeleteResourcesCommand(properties));
+    }
+
+    notifyDelete(Type.Credential, predicate);
+    return getRequestStatus(null);
+  }
+
+  @Override
+  protected Set<String> getPKPropertyIds() {
+    return PK_PROPERTY_IDS;
+  }
+
+  /**
+   * Give a map of credential-related properties attempts to create a Credential (PrincipalKeyCredential)
+   * after validating that the required properties are present.
+   * <p/>
+   * The credential's principal is required, however a warning will be logged if a value for the key
+   * is not supplied.
+   *
+   * @param properties a map of properties
+   * @return a new Credential
+   * @throws IllegalArgumentException if the map of properties does not contain enough information to create
+   *                                  a new PrincipalKeyCredential instance
+   */
+  private Credential createCredential(Map<String, Object> properties) throws IllegalArgumentException {
+    String principal;
+    String key;
+
+    if (properties.get(CREDENTIAL_PRINCIPAL_PROPERTY_ID) == null) {
+      throw new IllegalArgumentException("Property " + CREDENTIAL_PRINCIPAL_PROPERTY_ID + " must be provided");
+    } else {
+      principal = String.valueOf(properties.get(CREDENTIAL_PRINCIPAL_PROPERTY_ID));
+    }
+
+    if (properties.get(CREDENTIAL_KEY_PROPERTY_ID) == null) {
+      LOG.warn("The credential is being added without a key");
+      key = null;
+    } else {
+      key = String.valueOf(properties.get(CREDENTIAL_KEY_PROPERTY_ID));
+    }
+
+    return new PrincipalKeyCredential(principal, key);
+  }
+
+  /**
+   * Retrieves the <code>Credential/persist</code> property from the property map and determined if the value
+   * represents Boolean true or false.
+   * <p/>
+   * If the <code>Credential/type</code> property is not is not available or is <code>null</code>,
+   * an AmbariException will be thrown.
+   *
+   * @param properties a map of properties
+   * @return true or false
+   * @throws IllegalArgumentException if the <code>Credential/type</code> property is not set to
+   *                                  either <code>persisted</code> or <code>temporary</code>
+   */
+  private CredentialStoreType getCredentialStoreType(Map<String, Object> properties) throws IllegalArgumentException {
+    Object propertyValue = properties.get(CREDENTIAL_TYPE_PROPERTY_ID);
+    if (propertyValue == null) {
+      throw new IllegalArgumentException("Property " + CREDENTIAL_TYPE_PROPERTY_ID + " must be provided");
+    } else if (propertyValue instanceof String) {
+      try {
+        return CredentialStoreType.valueOf(((String) propertyValue).toUpperCase());
+      } catch (IllegalArgumentException e) {
+        throw new IllegalArgumentException("Property " + CREDENTIAL_TYPE_PROPERTY_ID + " must be either 'persisted' or 'temporary'", e);
+      }
+    } else {
+      throw new IllegalArgumentException("Property " + CREDENTIAL_TYPE_PROPERTY_ID + " must be a String");
+    }
+  }
+
+  /**
+   * Retrieves the <code>Credential/cluster_name</code> property from the property map
+   *
+   * @param properties a map of properties
+   * @return the cluster name
+   * @throws IllegalArgumentException if <code>Credential/cluster_name</code> is not in the map or
+   *                                  <code>null</code>
+   */
+  private String getClusterName(Map<String, Object> properties) throws IllegalArgumentException {
+    if (properties.get(CREDENTIAL_CLUSTER_NAME_PROPERTY_ID) == null) {
+      throw new IllegalArgumentException("Property " + CREDENTIAL_CLUSTER_NAME_PROPERTY_ID + " must be provided");
+    } else {
+      return String.valueOf(properties.get(CREDENTIAL_CLUSTER_NAME_PROPERTY_ID));
+    }
+  }
+
+  /**
+   * Retrieves the <code>Credential/alias</code> property from the property map
+   *
+   * @param properties a map of properties
+   * @return the alias name
+   * @throws IllegalArgumentException if <code>Credential/alias</code> is not in the map or
+   *                                  <code>null</code>
+   */
+  private String getAlias(Map<String, Object> properties) throws IllegalArgumentException {
+    if (properties.get(CREDENTIAL_ALIAS_PROPERTY_ID) == null) {
+      throw new IllegalArgumentException("Property " + CREDENTIAL_ALIAS_PROPERTY_ID + " must be provided");
+    } else {
+      return String.valueOf(properties.get(CREDENTIAL_ALIAS_PROPERTY_ID));
+    }
+  }
+
+  /**
+   * Validates that the CredentialStoreService is available and has been initialized to support the
+   * requested persisted or temporary storage facility.
+   *
+   * @param credentialStoreType a CredentialStoreType indicating which credential store facility to use
+   * @throws IllegalArgumentException if the requested facility has not been initialized
+   */
+  private void validateForCreateOrModify(CredentialStoreType credentialStoreType) throws IllegalArgumentException {
+    if (!credentialStoreService.isInitialized(credentialStoreType)) {
+      if (CredentialStoreType.PERSISTED == credentialStoreType) {
+        // Throw IllegalArgumentException to cause a 400 error to be returned...
+        throw new IllegalArgumentException("Credentials cannot be stored in Ambari's persistent secure " +
+            "credential store since secure persistent storage has not yet be configured.  " +
+            "Use ambari-server setup-security to enable this feature.");
+      } else if (CredentialStoreType.TEMPORARY == credentialStoreType) {
+        // Throw IllegalArgumentException to cause a 400 error to be returned...
+        throw new IllegalArgumentException("Credentials cannot be stored in Ambari's temporary secure " +
+            "credential store since secure temporary storage has not yet be configured.");
+      }
+    }
+  }
+
+  /**
+   * Creates a new resource from the given cluster name, alias, and persist values.
+   *
+   * @param clusterName         a cluster name
+   * @param alias               an alias
+   * @param credentialStoreType the relevant credential store type
+   * @param requestedIds        the properties to include in the resulting resource instance
+   * @return a resource
+   */
+  private Resource toResource(String clusterName, String alias, CredentialStoreType credentialStoreType, Set<String> requestedIds) {
+    Resource resource = new ResourceImpl(Type.Credential);
+    setResourceProperty(resource, CREDENTIAL_CLUSTER_NAME_PROPERTY_ID, clusterName, requestedIds);
+    setResourceProperty(resource, CREDENTIAL_ALIAS_PROPERTY_ID, alias, requestedIds);
+    setResourceProperty(resource, CREDENTIAL_TYPE_PROPERTY_ID, credentialStoreType.name().toLowerCase(), requestedIds);
+    return resource;
+  }
+
+  /**
+   * CreateResourcesCommand implements a Command that performs the steps to create a new credential
+   * resources and store the data into the persisted or temporary credential stores, as indicated.
+   */
+  private class CreateResourcesCommand implements Command<String> {
+    private final Map<String, Object> properties;
+
+    public CreateResourcesCommand(Map<String, Object> properties) {
+      this.properties = properties;
+    }
+
+    @Override
+    public String invoke() throws AmbariException {
+      CredentialStoreType credentialStoreType = getCredentialStoreType(properties);
+
+      validateForCreateOrModify(credentialStoreType);
+
+      String clusterName = getClusterName(properties);
+      String alias = getAlias(properties);
+
+      if (credentialStoreService.containsCredential(clusterName, alias)) {
+        throw new AmbariException("A credential with the alias of " + alias + " already exists");
+      }
+
+      credentialStoreService.setCredential(clusterName, alias, createCredential(properties), credentialStoreType);
+      return alias;
+    }
+  }
+
+  /**
+   * ModifyResourcesCommand implements a Command that performs the steps to update existing credential
+   * resources.
+   */
+  private class ModifyResourcesCommand implements Command<String> {
+    private final Map<String, Object> properties;
+
+    public ModifyResourcesCommand(Map<String, Object> properties) {
+      this.properties = properties;
+    }
+
+    @Override
+    public String invoke() throws AmbariException {
+      String clusterName = getClusterName(properties);
+      String alias = getAlias(properties);
+
+      CredentialStoreType credentialStoreType = properties.containsKey(CREDENTIAL_TYPE_PROPERTY_ID)
+          ? getCredentialStoreType(properties)
+          : credentialStoreService.getCredentialStoreType(clusterName, alias);
+
+      validateForCreateOrModify(credentialStoreType);
+
+      Credential credential = credentialStoreService.getCredential(clusterName, alias);
+      if (credential instanceof PrincipalKeyCredential) {
+        PrincipalKeyCredential principalKeyCredential = (PrincipalKeyCredential) credential;
+
+        Map<String, Object> credentialProperties = new HashMap<String, Object>();
+
+        // Make sure the credential to update is removed from the persisted or temporary store... the
+        // updated data may change the persistence type.
+        credentialStoreService.removeCredential(clusterName, alias);
+
+        if (properties.containsKey(CREDENTIAL_PRINCIPAL_PROPERTY_ID)) {
+          credentialProperties.put(CREDENTIAL_PRINCIPAL_PROPERTY_ID, properties.get(CREDENTIAL_PRINCIPAL_PROPERTY_ID));
+        } else {
+          credentialProperties.put(CREDENTIAL_PRINCIPAL_PROPERTY_ID, principalKeyCredential.getPrincipal());
+        }
+
+        if (properties.containsKey(CREDENTIAL_KEY_PROPERTY_ID)) {
+          credentialProperties.put(CREDENTIAL_KEY_PROPERTY_ID, properties.get(CREDENTIAL_KEY_PROPERTY_ID));
+        } else {
+          char[] credentialKey = principalKeyCredential.getKey();
+          if (credentialKey != null) {
+            credentialProperties.put(CREDENTIAL_KEY_PROPERTY_ID, String.valueOf(credentialKey));
+          }
+        }
+
+        credentialStoreService.setCredential(clusterName, alias, createCredential(credentialProperties), credentialStoreType);
+        return alias;
+      } else {
+        return null;
+      }
+    }
+  }
+
+  /**
+   * DeleteResourcesCommand implements a Command that performs the steps to delete existing credential
+   * resources. Credential resources will be deleted from both storage facilities, if necessary.
+   */
+  private class DeleteResourcesCommand implements Command<String> {
+    private final Map<String, Object> properties;
+
+    public DeleteResourcesCommand(Map<String, Object> properties) {
+      this.properties = properties;
+    }
+
+    @Override
+    public String invoke() throws AmbariException {
+      String clusterName = getClusterName(properties);
+      String alias = getAlias(properties);
+      credentialStoreService.removeCredential(clusterName, alias);
+      return alias;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
index 1b208fb..3c46ded 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
@@ -144,7 +144,8 @@ public interface Resource {
     WidgetLayout,
     ActiveWidgetLayout,
     Theme,
-    HostKerberosIdentity;
+    HostKerberosIdentity,
+    Credential;
 
     /**
      * Get the {@link Type} that corresponds to this InternalType.
@@ -250,6 +251,7 @@ public interface Resource {
     public static final Type WidgetLayout = InternalType.WidgetLayout.getType();
     public static final Type ActiveWidgetLayout = InternalType.ActiveWidgetLayout.getType();
     public static final Type HostKerberosIdentity = InternalType.HostKerberosIdentity.getType();
+    public static final Type Credential = InternalType.Credential.getType();
 
     /**
      * The type name.

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java
index 44c9613..0abbf45 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java
@@ -64,6 +64,8 @@ public class AmbariAuthorizationFilter implements Filter {
   private static final String API_VIEWS_ALL_PATTERN            = API_VERSION_PREFIX + "/views.*";
   private static final String API_PERSIST_ALL_PATTERN          = API_VERSION_PREFIX + "/persist.*";
   private static final String API_LDAP_SYNC_EVENTS_ALL_PATTERN = API_VERSION_PREFIX + "/ldap_sync_events.*";
+  private static final String API_CREDENTIALS_ALL_PATTERN      = API_VERSION_PREFIX + "/clusters/.*?/credentials.*";
+  private static final String API_CREDENTIALS_AMBARI_PATTERN   = API_VERSION_PREFIX + "/clusters/.*?/credentials/ambari\\..*";
 
   protected static final String LOGIN_REDIRECT_BASE = "/#/login?targetURI=";
 
@@ -120,7 +122,18 @@ public class AmbariAuthorizationFilter implements Filter {
           }
 
           // clusters require permission
-          if (requestURI.matches(API_CLUSTERS_ALL_PATTERN)) {
+          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_ADMIN_PERMISSION)) {
+              authorized = true;
+              break;
+            }
+          } else if (requestURI.matches(API_CREDENTIALS_ALL_PATTERN)) {
+            if (permissionId.equals(PermissionEntity.CLUSTER_OPERATE_PERMISSION)) {
+              authorized = true;
+              break;
+            }
+          } else if (requestURI.matches(API_CLUSTERS_ALL_PATTERN)) {
             if (permissionId.equals(PermissionEntity.CLUSTER_READ_PERMISSION) ||
               permissionId.equals(PermissionEntity.CLUSTER_OPERATE_PERMISSION)) {
               authorized = true;
@@ -167,6 +180,7 @@ public class AmbariAuthorizationFilter implements Filter {
             || requestURI.matches(VIEWS_CONTEXT_ALL_PATTERN)
             || requestURI.matches(API_USERS_ALL_PATTERN)
             || requestURI.matches(API_GROUPS_ALL_PATTERN)
+            || requestURI.matches(API_CREDENTIALS_ALL_PATTERN)
             || requestURI.matches(API_LDAP_SYNC_EVENTS_ALL_PATTERN))) {
 
         httpResponse.setHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/security/credential/Credential.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/credential/Credential.java b/ambari-server/src/main/java/org/apache/ambari/server/security/credential/Credential.java
new file mode 100644
index 0000000..051dd26
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/credential/Credential.java
@@ -0,0 +1,34 @@
+/*
+ * 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.security.credential;
+
+/**
+ * A Credential is some data related an identity.
+ * <p/>
+ * This interface is to be implemented by specific types of credentials. For example a single key
+ * or a username/password combination.
+ */
+public interface Credential {
+  /**
+   * Returns a value representation of this Credential
+   *
+   * @return a the value representation of this Credential
+   */
+  char[] toValue();
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/security/credential/CredentialFactory.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/credential/CredentialFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/security/credential/CredentialFactory.java
new file mode 100644
index 0000000..b0b7f89
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/credential/CredentialFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.security.credential;
+
+/**
+ * CredentialFactory create Credential instances after determing what type of Credential is represented
+ * by the value data.
+ */
+public class CredentialFactory {
+  /**
+   * Give a credential value (assumed from a keystore), attempt to determine what type of Credential
+   * is represented and create the appropriate Credential implementation.
+   * <p/>
+   * For example, if the raw value starts with "PrincipalKeyCredential" it is determined to be
+   * a PrincipalKeyCredential.  A new PrincipalKeyCredential is created and then returned.
+   *
+   * @param value the raw credential value
+   * @return a Credential
+   * @throws InvalidCredentialValueException if an error occurs while parsing the raw credential value
+   */
+  public static Credential createCredential(char[] value) throws InvalidCredentialValueException {
+
+    if (value == null) {
+      return null;
+    } else {
+      String valueString = String.valueOf(value);
+
+      if (PrincipalKeyCredential.isValidValue(valueString)) {
+        return PrincipalKeyCredential.fromValue(valueString);
+      } else {
+        return GenericKeyCredential.fromValue(valueString);
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/security/credential/GenericKeyCredential.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/credential/GenericKeyCredential.java b/ambari-server/src/main/java/org/apache/ambari/server/security/credential/GenericKeyCredential.java
new file mode 100644
index 0000000..ac74017
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/credential/GenericKeyCredential.java
@@ -0,0 +1,79 @@
+/*
+ * 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.security.credential;
+
+/**
+ * GenericKeyCredential encapsulates a credential consisting of single key (for example, a password).
+ */
+public class GenericKeyCredential implements Credential {
+
+  /**
+   * The plaintext password value
+   */
+  private char[] key = null;
+
+  /**
+   * Creates an empty GenericKeyCredential
+   */
+  public GenericKeyCredential() {
+  }
+
+  /**
+   * Creates a new GenericKeyCredential
+   *
+   * @param key a char array containing the key for this credential
+   */
+  public GenericKeyCredential(char[] key) {
+    this.key = key;
+  }
+
+  /**
+   * @return a char array containing the key for this credential
+   */
+  public char[] getKey() {
+    return key;
+  }
+
+  /**
+   * @param key a char array containing the key for this credential
+   */
+  public void setKey(char[] key) {
+    this.key = key;
+  }
+
+
+  /**
+   * Returns a value representation of this GenericKeyCredential
+   *
+   * @return a String containing the value representation of this GenericKeyCredential
+   */
+  public char[] toValue() {
+    return this.key;
+  }
+
+  /**
+   * Renders a new GenericKeyCredential from its value representation
+   *
+   * @param value a String containing the value representation of this GenericKeyCredential
+   * @return a new GenericKeyCredential
+   */
+  public static GenericKeyCredential fromValue(String value) throws InvalidCredentialValueException {
+    return new GenericKeyCredential((value == null) ? null : value.toCharArray());
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7a962380/ambari-server/src/main/java/org/apache/ambari/server/security/credential/InvalidCredentialValueException.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/credential/InvalidCredentialValueException.java b/ambari-server/src/main/java/org/apache/ambari/server/security/credential/InvalidCredentialValueException.java
new file mode 100644
index 0000000..b730754
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/credential/InvalidCredentialValueException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.security.credential;
+
+import org.apache.ambari.server.AmbariException;
+
+public class InvalidCredentialValueException extends AmbariException {
+
+  public InvalidCredentialValueException(String msg) {
+    super(msg);
+  }
+
+  public InvalidCredentialValueException(String msg, Throwable throwable) {
+    super(msg, throwable);
+  }
+}


Mime
View raw message