Repository: ambari Updated Branches: refs/heads/trunk fe0c7ab99 -> e146400e6
AMBARI-15232. Provide details on the JCE policy installed in the JVM 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/e146400e Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/e146400e Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/e146400e Branch: refs/heads/trunk Commit: e146400e661d23f7ee2f08a06b0eadcbf798e476 Parents: fe0c7ab Author: Robert Levas <[email protected]> Authored: Fri Mar 4 10:54:14 2016 -0500 Committer: Robert Levas <[email protected]> Committed: Fri Mar 4 10:54:14 2016 -0500 ---------------------------------------------------------------------- .../internal/AbstractProviderModule.java | 3 + .../RootServiceComponentPropertyProvider.java | 208 +++++++++++++++++++ ...ootServiceComponentPropertyProviderTest.java | 92 ++++++++ 3 files changed, 303 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/e146400e/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractProviderModule.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractProviderModule.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractProviderModule.java index ff9b4e4..b96addb 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractProviderModule.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractProviderModule.java @@ -772,6 +772,9 @@ public abstract class AbstractProviderModule implements ProviderModule, gpp)); } break; + case RootServiceComponent: + providers.add(new RootServiceComponentPropertyProvider()); + break; default: break; } http://git-wip-us.apache.org/repos/asf/ambari/blob/e146400e/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RootServiceComponentPropertyProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RootServiceComponentPropertyProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RootServiceComponentPropertyProvider.java new file mode 100644 index 0000000..8bf389f --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RootServiceComponentPropertyProvider.java @@ -0,0 +1,208 @@ +/* + * 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 org.apache.ambari.server.controller.RootServiceResponseFactory; +import org.apache.ambari.server.controller.spi.Predicate; +import org.apache.ambari.server.controller.spi.PropertyProvider; +import org.apache.ambari.server.controller.spi.Request; +import org.apache.ambari.server.controller.spi.Resource; +import org.apache.ambari.server.controller.spi.SystemException; +import org.apache.ambari.server.controller.utilities.PropertyHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.Cipher; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * RootServiceComponentPropertyProvider is a PropertyProvider implementation providing additional + * properties for RootServiceComponent resources, like AMBARI_SERVER and AMBARI_AGENT. + * <p/> + * This implementation conditionally calculates and returns cipher and JCE details upon request. + * The Cipher data is cached after the first request for it and held in memory until Ambari is + * restarted. The user is required to explicitly query for one or both of the following properties + * (or fields) to get access to the data: + * <ul> + * <li>RootServiceComponents/jce_policy</li> + * <li>RootServiceComponents/ciphers</li> + * </ul> + * <p/> + * For example: + * <ul> + * <li><code>GET /api/v1/services/AMBARI/components/AMBARI_SERVER?fields=RootServiceComponents/ciphers</code></li> + * <li><code>GET /api/v1/services/AMBARI/components/AMBARI_SERVER?fields=RootServiceComponents/jce_policy</code></li> + * <li><code>GET /api/v1/services/AMBARI/components/AMBARI_SERVER?fields=RootServiceComponents/jce_policy,RootServiceComponents/ciphers</code></li> + * </ul> + * <p/> + * When querying for ciphers the returned data is a listing if installed cipher algorithms and their + * maximum key lengths. If all maximum key lengths are <code>2147483647</code>, then the unlimited + * key JCE policy is installed, else each item will have some smaller value (in bytes). + * <p/> + * For example: + * <pre> + * "ciphers" : { + * "sunjce.aes" : 2147483647, + * "sunjce.aeswrap" : 2147483647, + * "sunjce.aeswrap_128" : 2147483647, + * "sunjce.aeswrap_192" : 2147483647, + * "sunjce.aeswrap_256" : 2147483647, + * "sunjce.arcfour" : 2147483647, + * "sunjce.blowfish" : 2147483647, + * ... + * } + * </pre> + * <p/> + * When querying for the JDC policy, the returned data is a structure with details about the JCE + * policy - namely whether the unlimited key length policy is installed or not. + * <p/> + * For example: + * <pre> + * "jce_policy" : { + * "unlimited_key" : true + * } + * </pre> + */ +public class RootServiceComponentPropertyProvider extends BaseProvider implements PropertyProvider { + public static final String JCE_POLICY_PROPERTY_ID = PropertyHelper + .getPropertyId("RootServiceComponents", "jce_policy"); + + public static final String CIPHER_PROPERTIES_PROPERTY_ID = PropertyHelper + .getPropertyId("RootServiceComponents", "ciphers"); + + private static final Set<String> SUPPORTED_PROPERTY_IDS; + + private final static Logger LOG = LoggerFactory.getLogger(RootServiceComponentPropertyProvider.class); + + static { + Set<String> propertyIds = new HashSet<String>(); + propertyIds.add(JCE_POLICY_PROPERTY_ID); + propertyIds.add(CIPHER_PROPERTIES_PROPERTY_ID); + SUPPORTED_PROPERTY_IDS = Collections.unmodifiableSet(propertyIds); + } + + /** + * Map of cipher algorithm names to maximum key lengths. + * <p/> + * This is cached in memory after the first time it is needed. + */ + private static final Map<String, Integer> CACHED_CIPHER_MAX_KEY_LENGTHS = new HashMap<String, Integer>(); + + /** + * Constructor + */ + public RootServiceComponentPropertyProvider() { + super(SUPPORTED_PROPERTY_IDS); + } + + @Override + public Set<Resource> populateResources(Set<Resource> resources, Request request, Predicate predicate) throws SystemException { + + Set<String> requestedIds = request.getPropertyIds(); + + for (Resource resource : resources) { + // If this resource represents the AMBARI_SERVER component, handle it's specific properties... + if (RootServiceResponseFactory.Components.AMBARI_SERVER.name().equals(resource.getPropertyValue(RootServiceComponentResourceProvider.COMPONENT_NAME_PROPERTY_ID))) { + // Attempt to fill in the cipher details only if explicitly asked for. + if (requestedIds.contains(JCE_POLICY_PROPERTY_ID) || requestedIds.contains(CIPHER_PROPERTIES_PROPERTY_ID)) { + setCipherDetails(resource, requestedIds); + } + } + } + + return resources; + } + + /** + * Retrieve details about the active JCE policy and installed ciphers, then set the resource + * properties for the relevant resource. + * <p/> + * The following properties are set: + * <dl> + * <dt>RootServiceComponents/jce_policy/unlimited_key</dt> + * <dd>true if the unlimited key JCE policy is detected; false if it is not detected; null if unknown</dd> + * <dt>RootServiceComponents/ciphers</dt> + * <dd>A list of installed ciphers and their maximum key length values</dd> + * </dl> + * + * @param resource the resource to update + * @param requestedIds the request ids to populate + */ + private void setCipherDetails(Resource resource, Set<String> requestedIds) { + // Lazily fill the cache on first request.... + synchronized (CACHED_CIPHER_MAX_KEY_LENGTHS) { + if (CACHED_CIPHER_MAX_KEY_LENGTHS.isEmpty()) { + // Get the list of cipher algorithms and determine maximum key lengths. Report as a resource property. + for (Provider provider : Security.getProviders()) { + String providerName = provider.getName(); + + for (Provider.Service service : provider.getServices()) { + String algorithmName = service.getAlgorithm(); + + if ("Cipher".equalsIgnoreCase(service.getType())) { + try { + CACHED_CIPHER_MAX_KEY_LENGTHS.put(String.format("%s.%s", providerName, algorithmName).toLowerCase(), + Cipher.getMaxAllowedKeyLength(algorithmName)); + } catch (NoSuchAlgorithmException e) { + // This is unlikely since we are getting the algorithm names from the service providers. + // In any case, if a bad algorithm is listed it can be skipped since this is only for + // informational purposes. + LOG.warn(String.format("Failed to get the max key length of cipher %s, skipping.", algorithmName), e); + } + } + } + } + } + } + + // Report each cipher as a resource property, if requested + if (requestedIds.contains(CIPHER_PROPERTIES_PROPERTY_ID)) { + for (Map.Entry<String, Integer> entry : CACHED_CIPHER_MAX_KEY_LENGTHS.entrySet()) { + setResourceProperty(resource, + PropertyHelper.getPropertyId(CIPHER_PROPERTIES_PROPERTY_ID, entry.getKey()), + entry.getValue(), + requestedIds); + } + } + + // Determine if the unlimited key JCE policy is installed. This is determined by selecting a + // valid cipher algorithm and seeing if its max allowed key length value is set to the maximum + // size of an Integer. + if (requestedIds.contains(JCE_POLICY_PROPERTY_ID)) { + Boolean unlimitedKeyJCEPolicyInstalled = null; + + Map.Entry<String, Integer> entry = CACHED_CIPHER_MAX_KEY_LENGTHS.entrySet().iterator().next(); + if (entry != null) { + unlimitedKeyJCEPolicyInstalled = (Integer.MAX_VALUE == entry.getValue()); + } + + setResourceProperty(resource, + PropertyHelper.getPropertyId(JCE_POLICY_PROPERTY_ID, "unlimited_key"), + unlimitedKeyJCEPolicyInstalled, + requestedIds); + } + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/e146400e/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/RootServiceComponentPropertyProviderTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/RootServiceComponentPropertyProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/RootServiceComponentPropertyProviderTest.java new file mode 100644 index 0000000..3b7b546 --- /dev/null +++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/RootServiceComponentPropertyProviderTest.java @@ -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. + */ + +package org.apache.ambari.server.controller.internal; + +import org.apache.ambari.server.controller.RootServiceResponseFactory; +import org.apache.ambari.server.controller.spi.Request; +import org.apache.ambari.server.controller.spi.Resource; +import org.apache.ambari.server.controller.spi.TemporalInfo; +import org.apache.ambari.server.controller.utilities.PropertyHelper; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class RootServiceComponentPropertyProviderTest { + @Test + public void testPopulateResources_AmbariServer_None() throws Exception { + testPopulateResources(RootServiceResponseFactory.Components.AMBARI_SERVER.name(), false, false, false, false); + } + + @Test + public void testPopulateResources_AmbariServer_CiphersAndJCEPolicy() throws Exception { + testPopulateResources(RootServiceResponseFactory.Components.AMBARI_SERVER.name(), true, true, true, true); + } + + @Test + public void testPopulateResources_AmbariServer_JCEPolicy() throws Exception { + testPopulateResources(RootServiceResponseFactory.Components.AMBARI_SERVER.name(), false, true, false, true); + } + + @Test + public void testPopulateResources_AmbariServer_Ciphers() throws Exception { + testPopulateResources(RootServiceResponseFactory.Components.AMBARI_SERVER.name(), true, false, true, false); + } + + @Test + public void testPopulateResources_AmbariAgent_CiphersAndJCEPolicy() throws Exception { + testPopulateResources(RootServiceResponseFactory.Components.AMBARI_AGENT.name(), true, true, false, false); + } + + public void testPopulateResources(String componentName, + boolean requestCiphers, boolean requestJCEPolicy, + boolean expectCiphers, boolean expectJCEPolicy) throws Exception { + RootServiceComponentPropertyProvider propertyProvider = new RootServiceComponentPropertyProvider(); + Resource resource = new ResourceImpl(Resource.Type.RootService); + + resource.setProperty(RootServiceComponentResourceProvider.COMPONENT_NAME_PROPERTY_ID, componentName); + resource.setProperty(RootServiceComponentResourceProvider.SERVICE_NAME_PROPERTY_ID, RootServiceResponseFactory.Services.AMBARI.name()); + + HashSet<String> requestIds = new HashSet<String>(); + + if (requestCiphers) { + requestIds.add(RootServiceComponentPropertyProvider.CIPHER_PROPERTIES_PROPERTY_ID); + } + + if (requestJCEPolicy) { + requestIds.add(RootServiceComponentPropertyProvider.JCE_POLICY_PROPERTY_ID); + } + + Request request = PropertyHelper.getReadRequest(requestIds, new HashMap<String, TemporalInfo>()); + + Set<Resource> resources = propertyProvider.populateResources(Collections.singleton(resource), request, null); + Assert.assertEquals(1, resources.size()); + + resource = resources.iterator().next(); + + Map<String, Map<String, Object>> values = resource.getPropertiesMap(); + + Assert.assertEquals(expectCiphers, values.containsKey(RootServiceComponentPropertyProvider.CIPHER_PROPERTIES_PROPERTY_ID)); + Assert.assertEquals(expectJCEPolicy, values.containsKey(RootServiceComponentPropertyProvider.JCE_POLICY_PROPERTY_ID)); + } +} \ No newline at end of file
