/** * Copyright 2014 Microsoft Open Technologies Inc. * * Licensed 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 com.microsoftopentechnologies.intellij.helpers.azure.sdk; import com.intellij.ide.util.PropertiesComponent; import com.microsoft.windowsazure.Configuration; import com.microsoft.windowsazure.core.pipeline.apache.ApacheConfigurationProperties; import com.microsoft.windowsazure.core.utils.Base64; import com.microsoft.windowsazure.core.utils.KeyStoreType; import com.microsoft.windowsazure.management.ManagementClient; import com.microsoft.windowsazure.management.ManagementService; import com.microsoft.windowsazure.management.compute.ComputeManagementClient; import com.microsoft.windowsazure.management.compute.ComputeManagementService; import com.microsoft.windowsazure.management.configuration.ManagementConfiguration; import com.microsoft.windowsazure.management.storage.StorageManagementClient; import com.microsoft.windowsazure.management.storage.StorageManagementService; import com.microsoftopentechnologies.intellij.components.MSOpenTechToolsApplication; import com.microsoftopentechnologies.intellij.helpers.OpenSSLHelper; import com.microsoftopentechnologies.intellij.helpers.XmlHelper; import com.microsoftopentechnologies.intellij.helpers.azure.AzureAuthenticationMode; import com.microsoftopentechnologies.intellij.helpers.azure.AzureRestAPIManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import java.io.*; import java.net.URI; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; public class AzureSDKHelper { private static class SubscriptionInfo { public String base64Certificate; public String managementURI; } @Nullable public static ComputeManagementClient getComputeManagementClient(@NotNull String subscriptionId) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, XPathExpressionException, ParserConfigurationException, SAXException { Configuration configuration = getConfiguration(subscriptionId); if (configuration == null) { return null; } ComputeManagementClient client = ComputeManagementService.create(configuration); // add a request filter for tacking on the A/D auth token if the current authentication // mode is active directory if (AzureRestAPIManager.getManager().getAuthenticationMode() == AzureAuthenticationMode.ActiveDirectory) { return client.withRequestFilterFirst(new AuthTokenRequestFilter(subscriptionId)); } return client; } @Nullable public static StorageManagementClient getStorageManagementClient(@NotNull String subscriptionId) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, XPathExpressionException, ParserConfigurationException, SAXException { Configuration configuration = getConfiguration(subscriptionId); if (configuration == null) { return null; } StorageManagementClient client = StorageManagementService.create(configuration); // add a request filter for tacking on the A/D auth token if the current authentication // mode is active directory if (AzureRestAPIManager.getManager().getAuthenticationMode() == AzureAuthenticationMode.ActiveDirectory) { return client.withRequestFilterFirst(new AuthTokenRequestFilter(subscriptionId)); } return client; } @Nullable public static ManagementClient getManagementClient(@NotNull String subscriptionId) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, XPathExpressionException, ParserConfigurationException, SAXException { Configuration configuration = getConfiguration(subscriptionId); if (configuration == null) { return null; } ManagementClient client = ManagementService.create(configuration); // add a request filter for tacking on the A/D auth token if the current authentication // mode is active directory if (AzureRestAPIManager.getManager().getAuthenticationMode() == AzureAuthenticationMode.ActiveDirectory) { return client.withRequestFilterFirst(new AuthTokenRequestFilter(subscriptionId)); } return client; } @Nullable private static Configuration getConfiguration(@NotNull String subscriptionId) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, XPathExpressionException, SAXException, ParserConfigurationException, IOException { switch (AzureRestAPIManager.getManager().getAuthenticationMode()) { case SubscriptionSettings: return getConfigurationFromPublishSettings(subscriptionId); case ActiveDirectory: return getConfigurationFromAuthToken(subscriptionId); } return null; } @Nullable private static Configuration getConfigurationFromAuthToken(@NotNull String subscriptionId) throws SAXException, ParserConfigurationException, XPathExpressionException, IOException { // NOTE: This implementation has to be considered as somewhat hacky. It relies on certain // internal implementation details of the Azure SDK for Java. For example we supply null // values for the key store location and password and specify a key store type value // though it will not be used. We also supply a no-op "credential provider". Ideally we want // the SDK to directly support the scenario we need. String azureServiceManagementUri = MSOpenTechToolsApplication.getCurrent().getSettings().getAzureServiceManagementUri(); ClassLoader old = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(AzureSDKHelper.class.getClassLoader()); try { // create a default configuration object Configuration configuration = ManagementConfiguration.configure( URI.create(azureServiceManagementUri), subscriptionId, null, null, KeyStoreType.pkcs12); if (configuration != null) { // replace the credential provider with a custom one that does nothing configuration.setProperty( ManagementConfiguration.SUBSCRIPTION_CLOUD_CREDENTIALS, new EmptyCloudCredentials(subscriptionId)); } // remove the SSL connection factory in case one was added; this is needed // in the case when the user switches from subscription based auth to A/D // sign-in because in that scenario the CertificateCloudCredentials class // would have added an SSL connection factory object to the configuration // object which would then be used when making the SSL call to the Azure // service management API. This tells us that the configuration object is // reused across calls to ManagementConfiguration.configure. The SSL connection // factory object so configured will attempt to use certificate based auth // which will fail since we don't have a certificate handy when using A/D auth. configuration.getProperties().remove(ApacheConfigurationProperties.PROPERTY_SSL_CONNECTION_SOCKET_FACTORY); return configuration; } finally { Thread.currentThread().setContextClassLoader(old); } } @Nullable private static Configuration getConfigurationFromPublishSettings(@NotNull String subscriptionId) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, ParserConfigurationException, XPathExpressionException, SAXException { SubscriptionInfo subscriptionInfo = getSubscriptionInfoFromPublishSettings(subscriptionId); if (subscriptionInfo == null) { return null; } String keyStorePath = File.createTempFile("azk", null).getPath(); initKeyStore( subscriptionInfo.base64Certificate != null ? subscriptionInfo.base64Certificate : "", OpenSSLHelper.PASSWORD, keyStorePath, OpenSSLHelper.PASSWORD); ClassLoader old = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(AzureSDKHelper.class.getClassLoader()); try { return ManagementConfiguration.configure(URI.create(subscriptionInfo.managementURI), subscriptionId, keyStorePath, OpenSSLHelper.PASSWORD, KeyStoreType.pkcs12); } finally { Thread.currentThread().setContextClassLoader(old); } } @Nullable private static SubscriptionInfo getSubscriptionInfoFromPublishSettings(@NotNull String subscriptionId) throws SAXException, ParserConfigurationException, XPathExpressionException, IOException { String publishSettings = PropertiesComponent.getInstance().getValue(MSOpenTechToolsApplication.AppSettingsNames.SUBSCRIPTION_FILE, ""); if (publishSettings.isEmpty()) { return null; } Node node = null; NodeList subsList = (NodeList) XmlHelper.getXMLValue(publishSettings, "//PublishData/PublishProfile/Subscription", XPathConstants.NODESET); for (int i = 0; i < subsList.getLength(); i++) { String id = XmlHelper.getAttributeValue(subsList.item(i), "Id"); if (id.equals(subscriptionId)) { node = subsList.item(i); break; } } if (node == null) { return null; } SubscriptionInfo subscriptionInfo = new SubscriptionInfo(); subscriptionInfo.base64Certificate = XmlHelper.getAttributeValue(node, "ManagementCertificate"); subscriptionInfo.managementURI = XmlHelper.getAttributeValue(node, "ServiceManagementUrl"); return subscriptionInfo; } private static void initKeyStore(@NotNull String base64Certificate, @NotNull String certificatePwd, @NotNull String keyStorePath, @NotNull String keyStorePwd) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { FileOutputStream keyStoreOutputStream = new FileOutputStream(keyStorePath); try { KeyStore store = KeyStore.getInstance("PKCS12"); store.load(null, null); final byte[] decode = Base64.decode(base64Certificate); InputStream sslInputStream = new ByteArrayInputStream(decode); store.load(sslInputStream, certificatePwd.toCharArray()); // we need to a create a physical key store as well here store.store(keyStoreOutputStream, keyStorePwd.toCharArray()); } finally { keyStoreOutputStream.close(); } } }