/// <summary>
        /// Extracts PFX from a certificate uploaded or generated from KeyVault. This does not support certs uploaded into KeyVault using secret.
        /// </summary>
        /// <param name="certificateName">Name of the certificate.</param>
        /// <param name="certificateSecretName">Secret name of the certificate. This is usually certificate name.</param>
        /// <param name="fullDirectoryPath">The full directory path to drop PFX at.</param>
        /// <param name="keyVaultClient">The key vault client.</param>
        /// <param name="keyVaultUrl">The key vault URL.</param>
        /// <returns>Exit code for the operation.</returns>
        private static async Task <ExitCode> KeyVaultCertHandlerAsync(
            string certificateSource,
            string certificateName,
            string certificateSecretName,
            string fullDirectoryPath,
            KeyVaultClient keyVaultClient,
            string keyVaultUrl)
        {
            if (keyVaultClient == null)
            {
                Logger.LogError(CallInfo.Site(), "KeyVaultClient was not initialized. Make sure required params for KeyVault connection were passed");
                return(ExitCode.KeyVaultConfigurationIncomplete);
            }

            if (string.IsNullOrEmpty(keyVaultUrl))
            {
                Logger.LogError(CallInfo.Site(), "Invalid KeyVault uri.");
                return(ExitCode.KeyVaultConfigurationIncomplete);
            }

            SecretBundle certificateSecret;

            try
            {
                if (string.Equals(certificateSource, "KeyVaultSecret", StringComparison.OrdinalIgnoreCase) || string.Equals(certificateSource, "KeyVault", StringComparison.OrdinalIgnoreCase))
                {
                    certificateSecret = await keyVaultClient.GetSecretAsync(keyVaultUrl, certificateSecretName).ConfigureAwait(false);
                }
                else if (string.Equals(certificateSource, "KeyVaultCertificate", StringComparison.OrdinalIgnoreCase))
                {
                    var cert = await keyVaultClient.GetCertificateAsync(keyVaultUrl, certificateSecretName).ConfigureAwait(false);

                    certificateSecret = await keyVaultClient.GetSecretAsync(cert.SecretIdentifier.ToString()).ConfigureAwait(false);
                }
                else
                {
                    Logger.LogError(CallInfo.Site(), $"Specified KeyVault source is not supported: {certificateSource}");
                    return(ExitCode.KeyVaultConfigurationIncomplete);
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(CallInfo.Site(), ex, "Failed to get certificate with secret name '{0}' from key vault '{1}'", certificateSecretName, keyVaultUrl);
                return(ExitCode.KeyVaultOperationFailed);
            }

            // Only supporting managed certs for now.
            if (certificateSecret.Managed != true)
            {
                Logger.LogError(CallInfo.Site(), "Failed to decrypt certificate. Only managed certificates are supported. Download the unmanaged cert from secret and reupload it to certificates.");
                return(ExitCode.FailedToDecodeCertFromKeyVault);
            }

            return(SaveCertificatePrivateKeyToDisk(Convert.FromBase64String(certificateSecret.Value), certificateName, fullDirectoryPath));
        }
        /// <summary>
        /// Converts the PFX into pem format and extracts the Private key into .key and public in .crt format.
        /// </summary>
        /// <param name="certificateName">Name of the certificate.</param>
        /// <param name="certDirectoryPath">The full directory path for the PFX file. This is also the same path where the PEM and CRT files will be placed.</param>
        /// <param name="opensslExeDirectory">The openssl executable directory.</param>
        /// <param name="password">PFX password.</param>
        /// <returns>Exit code for the operation.</returns>
        private static ExitCode ConvertPfxIntoPemFormat(string certificateName, string certDirectoryPath, string opensslExeDirectory, string password)
        {
            string opensslPath = Path.Combine(opensslExeDirectory, "openssl.exe");
            string pathToPfx   = Path.Combine(certDirectoryPath, certificateName + ".pfx");

            string keyExtractionProcessArgs = string.Format(
                PrivateKeyExportArguments,
                opensslPath,
                pathToPfx,
                Path.Combine(certDirectoryPath, certificateName + ".key"),
                password);

            // We have to start cmd.exe as openssl.exe exit is not read by Process class.
            Logger.LogVerbose(CallInfo.Site(), "Starting extraction of Private key for '{0}' using '{0}'", certificateName, opensslPath);
            Process exportPrivateKeyProcess = Process.Start("cmd", keyExtractionProcessArgs);

            exportPrivateKeyProcess.WaitForExit();
            Logger.LogVerbose(CallInfo.Site(), "Private key extraction for certificate '{0}' process completed with exit code '{1}'", certificateName, exportPrivateKeyProcess.ExitCode);

            if (exportPrivateKeyProcess.ExitCode != 0)
            {
                Logger.LogError(CallInfo.Site(), "Private key extraction failed for certificate name '{0}'", certificateName);
                return(ExitCode.PrivateKeyExtractionFailed);
            }

            string crtExtractionProcessArgs = string.Format(
                PublicKeyExportArguments,
                opensslPath,
                pathToPfx,
                Path.Combine(certDirectoryPath, certificateName + ".crt"),
                password);

            // We have to start cmd.exe as openssl.exe exit is not read by Process class.
            Logger.LogVerbose(CallInfo.Site(), "Starting extraction of Public key from PFX using '{0}'", opensslPath);
            Process exportPublicKeyProcess = Process.Start("cmd", crtExtractionProcessArgs);

            exportPublicKeyProcess.WaitForExit();
            Logger.LogVerbose(CallInfo.Site(), "Public key extraction for certificate '{0}' process completed with exit code '{1}'", certificateName, exportPublicKeyProcess.ExitCode);

            if (exportPublicKeyProcess.ExitCode != 0)
            {
                Logger.LogError(CallInfo.Site(), "Public key extraction failed for certificate name '{0}'", certificateName);
                return(ExitCode.PublicKeyExtractionFailed);
            }

            return(ExitCode.Success);
        }
        /// <summary>
        /// Converts the PFX into pem format and extracts the Private key into .key and public in .crt format.
        /// </summary>
        /// <param name="certificateName">Name of the certificate.</param>
        /// <param name="certDirectoryPath">The full directory path for the PFX file. This is also the same path where the PEM and CRT files will be placed.</param>
        /// <param name="opensslExeDirectory">The openssl executable directory.</param>
        /// <param name="password">PFX password.</param>
        /// <returns>Exit code for the operation.</returns>
        private static ExitCode ConvertPfxIntoPemFormat(string certificateName, string certDirectoryPath, string opensslExeDirectory, string password)
        {
            string opensslPath = Path.Combine(opensslExeDirectory, "OpenSSL", "openssl.exe");
            string pathToPfx   = Path.Combine(certDirectoryPath, certificateName + ".pfx");

            string keyExtractionProcessArgs = string.Format(
                CultureInfo.InvariantCulture,
                PrivateKeyExportArguments,
                pathToPfx,
                Path.Combine(certDirectoryPath, certificateName + ".key"),
                password);

            Logger.LogVerbose(CallInfo.Site(), "Starting extraction of Private key for '{0}' using '{0}'", certificateName, opensslPath);
            int exportPrivateKeyExitCode = ExecuteOpensslProcess(opensslPath, keyExtractionProcessArgs);

            Logger.LogVerbose(CallInfo.Site(), "Private key extraction for certificate '{0}' process completed with exit code '{1}'", certificateName, exportPrivateKeyExitCode);

            if (exportPrivateKeyExitCode != 0)
            {
                Logger.LogError(CallInfo.Site(), "Private key extraction failed for certificate name '{0}'", certificateName);
                return(ExitCode.PrivateKeyExtractionFailed);
            }

            string crtExtractionProcessArgs = string.Format(
                CultureInfo.InvariantCulture,
                PublicKeyExportArguments,
                pathToPfx,
                Path.Combine(certDirectoryPath, certificateName + ".crt"),
                password);

            Logger.LogVerbose(CallInfo.Site(), "Starting extraction of Public key from PFX using '{0}'", opensslPath);
            int exportPublicKeyExitCode = ExecuteOpensslProcess(opensslPath, crtExtractionProcessArgs);

            Logger.LogVerbose(CallInfo.Site(), "Public key extraction for certificate '{0}' process completed with exit code '{1}'", certificateName, exportPublicKeyExitCode);

            if (exportPublicKeyExitCode != 0)
            {
                Logger.LogError(CallInfo.Site(), "Public key extraction failed for certificate name '{0}'", certificateName);
                return(ExitCode.PublicKeyExtractionFailed);
            }

            return(ExitCode.Success);
        }
        /// <summary>
        /// Processes the certificate management.
        /// </summary>
        /// <param name="directoryPath">Directory to put the certificatex in.</param>
        /// <param name="certConfiguration">Certificate configuration. This is a combination of comma separated values in following format
        /// *certFileName*;*SourceOfCert*;*CertIdentifierInSource*.</param>
        /// <param name="keyVaultUri">KeyVault uri if key vault is to be used.</param>
        /// <param name="keyVaultClientId">Application client Id to access keyvault.</param>
        /// <param name="keyVaultClientSecret">Application client secret to access keyvault.</param>
        /// <param name="keyVaultClientCert">Application client certificate thumbprint if the keyvault app has certificate credentials.</param>
        /// <returns>Exit code for the operation.</returns>
        internal static async Task <ExitCode> ProcessAsync(string directoryPath, string certConfiguration, string keyVaultUri, string keyVaultClientId, string keyVaultClientSecret, string keyVaultClientCert)
        {
            if (string.IsNullOrEmpty(directoryPath))
            {
                Logger.LogError(CallInfo.Site(), "Directory path missing for the Certificate directory.");
                return(ExitCode.DirectoryPathMissing);
            }

            if (string.IsNullOrEmpty(certConfiguration))
            {
                Logger.LogError(CallInfo.Site(), "Cert configuration missing. Please specify CertsToConfigure option");
                return(ExitCode.InvalidCertConfiguration);
            }

            // 1. Initialize KeyVault Client if params were passed.
            KeyVaultClient keyVaultClient = null;

            if (!string.IsNullOrEmpty(keyVaultUri))
            {
                if (string.IsNullOrEmpty(keyVaultClientId))
                {
                    Logger.LogError(CallInfo.Site(), "If KeyVaultUri is specified, KeyVault ClientId must be specified");
                    return(ExitCode.KeyVaultConfigurationIncomplete);
                }

                if (string.IsNullOrEmpty(keyVaultClientSecret) && string.IsNullOrEmpty(keyVaultClientCert))
                {
                    Logger.LogError(CallInfo.Site(), "If KeyVaultUri is specified, KeyVault ClientSecret or KeyVault ClientCert must be specified");
                    return(ExitCode.KeyVaultConfigurationIncomplete);
                }

                if (!string.IsNullOrEmpty(keyVaultClientSecret))
                {
                    KeyVaultClient.AuthenticationCallback callback =
                        (authority, resource, scope) => GetTokenFromClientSecretAsync(authority, resource, keyVaultClientId, keyVaultClientSecret);
                    keyVaultClient = new KeyVaultClient(callback);
                }
                else
                {
                    X509Certificate2Collection keyVaultCerts = CertHelpers.FindCertificates(keyVaultClientCert, X509FindType.FindByThumbprint);

                    if (keyVaultCerts.Count == 0)
                    {
                        Logger.LogError(CallInfo.Site(), "Failed to find Client cert with thumbprint '{0}'", keyVaultClientCert);
                        return(ExitCode.KeyVaultConfigurationIncomplete);
                    }

                    KeyVaultClient.AuthenticationCallback callback =
                        (authority, resource, scope) => GetTokenFromClientCertificateAsync(authority, resource, keyVaultClientId, keyVaultCerts[0]);
                    keyVaultClient = new KeyVaultClient(callback);
                }
            }

            // 2. Figure all the certs which need processing.
            string[] certsToConfigure          = certConfiguration.Split(',');
            string   currentExeDirectory       = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            string   fullDirectoryPathForCerts = Path.Combine(currentExeDirectory, directoryPath);

            // 3. Process specified certs one by one.
            foreach (string certToConfigure in certsToConfigure)
            {
                // 3a. Split the cert configuration data to get actual details. This data is informat
                // <CertNameOnDisk>;CertSource(LocalMachine or KeyVault);<CertIdentifier(SecretName or Thumbprint)>
                string[] certConfigurationParams = certToConfigure.Split(';');

                if (certConfigurationParams.Length != 3)
                {
                    Logger.LogError(CallInfo.Site(), "Invalid certificate configuration '{0}'. Cert configuration must be in format <CertFileName>;<CertSource>;<CertIdentifier>", certToConfigure);
                    return(ExitCode.InvalidCertConfiguration);
                }

                var certConfig =
                    new { CertName = certConfigurationParams[0], CertSource = certConfigurationParams[1], CertIdentifier = certConfigurationParams[2] };

                string pfxPassword = null;

                // 3b. Depending on the source of Cert get the PFX for the certs dropped into the directory.
                if (certConfig.CertSource.Equals("MyLocalMachine", StringComparison.OrdinalIgnoreCase))
                {
                    ExitCode localMachineCertHandler = await LocalMachineCertHandlerAsync(
                        certConfig.CertName,
                        certConfig.CertIdentifier,
                        fullDirectoryPathForCerts).ConfigureAwait(false);

                    if (localMachineCertHandler != ExitCode.Success)
                    {
                        return(localMachineCertHandler);
                    }

                    pfxPassword = DefaultPfxPassword;
                }
                else if (certConfig.CertSource.Equals("KeyVault", StringComparison.OrdinalIgnoreCase))
                {
                    ExitCode keyVaultCertHandlerExitCode = await KeyVaultCertHandlerAsync(
                        certConfig.CertName,
                        certConfig.CertIdentifier,
                        fullDirectoryPathForCerts,
                        keyVaultClient,
                        keyVaultUri).ConfigureAwait(false);

                    if (keyVaultCertHandlerExitCode != ExitCode.Success)
                    {
                        return(keyVaultCertHandlerExitCode);
                    }

                    pfxPassword = string.Empty;
                }
                else
                {
                    Logger.LogError(CallInfo.Site(), "Unsupported Certificate source '{0}' for cert '{1}'", certConfig.CertSource, certConfig.CertName);
                    return(ExitCode.UnsupportedCertSource);
                }

                // 3c. Convert PFX into .Key and .Crt. We are placing openssl next to this exe hence using current directory.
                ExitCode conversionExitCode = ConvertPfxIntoPemFormat(certConfig.CertName, fullDirectoryPathForCerts, currentExeDirectory, pfxPassword);

                if (conversionExitCode != ExitCode.Success)
                {
                    return(conversionExitCode);
                }

                // 3d. Delete the PFX as it is no longer needed.
                File.Delete(Path.Combine(fullDirectoryPathForCerts, certConfig.CertName + ".pfx"));
            }

            return(ExitCode.Success);
        }
        /// <summary>
        /// Extracts PFX from a local cert present in LocalMachine store under My.
        /// </summary>
        /// <param name="certificateName">Name of the certificate.</param>
        /// <param name="certificateIdentitier">The certificate identifier.</param>
        /// <param name="fullDirectoryPath">The full directory path to drop PFX at.</param>
        /// <returns>Exit code for the operation.</returns>
        private static Task <ExitCode> LocalMachineCertHandlerAsync(string certificateName, string certificateIdentitier, string fullDirectoryPath)
        {
            // Split the Certificate identifier on :. If we don't find ':' then we assume the identifier is a thumbprint,
            // otherwise we try to figure what identifier the user gave. We support X509FindType values as string.
            string[] certIdentifierSplit = certificateIdentitier.Split(':');

            X509FindType x509FindType = X509FindType.FindByThumbprint;

            if (certIdentifierSplit.Length > 0)
            {
                if (!Enum.TryParse <X509FindType>(certIdentifierSplit[1], out x509FindType))
                {
                    Logger.LogError(CallInfo.Site(), "Invalid Find type value used '{0}' in Ceritificate identifier '{1}'", certIdentifierSplit[1], certificateIdentitier);
                    return(Task.FromResult(ExitCode.InvalidCertConfiguration));
                }
            }

            X509Certificate2Collection certificateCollection = CertHelpers.FindCertificates(
                certIdentifierSplit[0],
                x509FindType,
                StoreName.My,
                StoreLocation.LocalMachine);

            if (certificateCollection.Count == 0)
            {
                Logger.LogError(CallInfo.Site(), "Failed to find certificate with name '{0}'", certificateName);
                return(Task.FromResult(ExitCode.CertificateMissingFromSource));
            }

            X509Certificate2 selectedCertificate = certificateCollection[0];

            // If more than 1 cert is found, choose the one with latest expiry.
            if (certificateCollection.Count > 1)
            {
                Logger.LogInfo(CallInfo.Site(), "Found multiple certificates which match the search identifier, selecting the one with latest expiry");

                foreach (X509Certificate2 x509Certificate2 in certificateCollection)
                {
                    // If the first selected certificate did not have a private key on it and
                    // we found another cert with private key pick that up.
                    if (!selectedCertificate.HasPrivateKey && x509Certificate2.HasPrivateKey)
                    {
                        selectedCertificate = x509Certificate2;
                    }

                    if (x509Certificate2.NotAfter > selectedCertificate.NotAfter && x509Certificate2.HasPrivateKey)
                    {
                        selectedCertificate = x509Certificate2;
                    }
                }
            }

            if (!selectedCertificate.HasPrivateKey)
            {
                Logger.LogError(CallInfo.Site(), "Certificate with name '{0}' and thumbprint '{1}' has missing Private Key", certificateName, selectedCertificate.Thumbprint);
                return(Task.FromResult(ExitCode.PrivateKeyMissingOnCertificate));
            }

            byte[] rawCertData = selectedCertificate.Export(X509ContentType.Pfx, DefaultPfxPassword);

            return(Task.FromResult(SaveCertificatePrivateKeyToDisk(rawCertData, certificateName, fullDirectoryPath)));
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Defines the entry point of the application.
        /// </summary>
        /// <param name="args">The arguments.</param>
        /// <remarks>
        /// Possible Arguments
        /// 1) If you want to dump the certs from local machine only and make it work for one environment config.
        ///     --ConfigureCerts "Certs" --ApplicationInsightsKey "AIKeyHere" --CertsToConfigure "ClusterCert;MyLocalMachine;ClusterCertThumbprint,SSLCert;MyLocalMachine;SSLCertThumbprint"
        /// 2) If you want to dump the certs from local machine and KeyVault and make it work for one environment config.
        ///     --ConfigureCerts "Certs" --ApplicationInsightsKey "AIKeyHere" --CertsToConfigure "ClusterCert;MyLocalMachine;ClusterCertThumbprint,SSLCert;KeyVault;SSLSecretName" --KeyVaultUri "https://dummyvault.vault.azure.net/" --KeyVaultClientId "1dc8b8b3-be3e-482a-b56b-9092c91aa4b2" -KeyVaultClientSecret "keyvaultappsecret"
        /// 3) If you want to dump the certs from local machine and make it work for different environments having different configs.
        ///     a) Set the arguments to --UseEnvironmentVariables (or -UseEnv)
        ///     b) And set the Environment variables
        ///         i) ConfigureCerts to Certs
        ///         ii) ApplicationInsightsKey to AiKeyHere
        ///         iii) CertsToConfigure to ClusterCert;MyLocalMachine;ClusterCertThumbprint,SSLCert;MyLocalMachine;SSLCertThumbprint
        ///        Similarily other options can be set in environment variables to enable rest of the options like KeyVault.
        /// </remarks>
        private static void Main(string[] args)
        {
            CommandLineApplication commandLineApplication        = new CommandLineApplication(false);
            CommandOption          useEnvironmentVariablesOption = commandLineApplication.Option(
                "-UseEnv | --UseEnvironmentVariables",
#pragma warning disable SA1118 // Parameter should not span multiple lines
                "Instead of using specified options, use Environment varibles with the same name (except the -- at start). This is to enable different integrations for different environments." +
                " If you use this, command line values are ignored.",
#pragma warning restore SA1118 // Parameter should not span multiple lines
                CommandOptionType.NoValue);
            CommandOption configureCertsOption = commandLineApplication.Option(
                "--ConfigureCerts <DirectoryRelativePath>",
                "Configures certs for Traefik by dropping them into the specifiec directory (relative to executing assembly). Certs are dropped in .key and .crt format.",
                CommandOptionType.SingleValue);
            CommandOption certsToConfigureOption = commandLineApplication.Option(
                "--CertsToConfigure <FormattedCertsToConfigure>",
#pragma warning disable SA1118 // Parameter should not span multiple lines
                "The value looks something like SSLCert;MyLocalMachine;7ce597cba5ae055fa37f222aaffc1007c3d61277,ClusterCert;KeyVault;ClusterCertSecretName. The format is" +
                "NameOfTheCert1;Source1;<Cert identifier1>,NameOfCert2;Source2;<Cert identifier2>." +
                "Possible Source values are 'MyLocalMachine' which fetches certs from Personal under LocalMachine Store and " +
                "'KeyVault' which fetches the cert from KeyVault. If KeyVault is specified, Specify ClientId and secret using --KeyVaultUri, --KeyVaultClientId, --KeyVaultClientSecret or --KeyVaultClientCert",
#pragma warning restore SA1118 // Parameter should not span multiple lines
                CommandOptionType.SingleValue);
            CommandOption keyVaultUriOption = commandLineApplication.Option(
                "--KeyVaultUri <KeyVaultUri>",
                "Uri to use for KeyVault connection. Use --KeyVaultClientId to specify ClientId of the app to use to access Key Vault.",
                CommandOptionType.MultipleValue);
            CommandOption keyVaultClientIdOption = commandLineApplication.Option(
                "--KeyVaultClientId <ClientId>",
                "Client Id to use for KeyVault connection. Specify the secret by using --KeyVaultClientSecret or --KeyVaultClientCert.",
                CommandOptionType.SingleValue);
            CommandOption keyVaultClientSecretOption = commandLineApplication.Option(
                "--KeyVaultClientSecret <ClientSecret>",
                "Client secret to use for KeyVault connection. Specify the ClientId using --KeyVaultClientId.",
                CommandOptionType.SingleValue);
            CommandOption useManagedIdentity = commandLineApplication.Option(
                "--UseManagedIdentity",
                "Uses Managed Identity for authenticating with KeyVault. You can specify the client Id if you want to use one in --KeyVaultClientId option.",
                CommandOptionType.NoValue);
            CommandOption keyVaultClientCertThumbprintOption = commandLineApplication.Option(
                "--KeyVaultClientCert <ClientCertThumbprint>",
                "Cert thumbprint to be used to contact key vault. The cert needs to be present on the machine. Specify the ClientId using --KeyVaultClientId.",
                CommandOptionType.SingleValue);
            CommandOption applicationInsightsInstrumentationKeyOption = commandLineApplication.Option(
                "--ApplicationInsightsKey <InstrumentationKey>",
                "Instrumentation key to push traces for PreConfiguration into Application Insights.",
                CommandOptionType.SingleValue);

            commandLineApplication.HelpOption("-h|--help|-?");
            commandLineApplication.OnExecute(async() =>
            {
                try
                {
                    bool useEnvironmentVariables = useEnvironmentVariablesOption.HasValue();
                    if (applicationInsightsInstrumentationKeyOption.HasValueExtended(useEnvironmentVariables))
                    {
                        var instrumentationKey = applicationInsightsInstrumentationKeyOption.GetValueExtended(useEnvironmentVariables);
                        Logger.ConfigureLogger(instrumentationKey);
                    }

                    if (configureCertsOption.HasValueExtended(useEnvironmentVariables))
                    {
                        ExitCode certHandlerExitCode = await CertificateHandler.ProcessAsync(
                            configureCertsOption.GetValueExtended(useEnvironmentVariables),
                            certsToConfigureOption.GetValueExtended(useEnvironmentVariables),
                            keyVaultUriOption.GetValuesExtended(useEnvironmentVariables),
                            keyVaultClientIdOption.GetValueExtended(useEnvironmentVariables),
                            keyVaultClientSecretOption.GetValueExtended(useEnvironmentVariables),
                            keyVaultClientCertThumbprintOption.GetValueExtended(useEnvironmentVariables),
                            useManagedIdentity.IsSwitchSpecified(useEnvironmentVariables)).ConfigureAwait(false);

                        if (certHandlerExitCode != ExitCode.Success)
                        {
                            return((int)certHandlerExitCode);
                        }
                    }

                    return((int)ExitCode.Success);
                }
                catch (AggregateException aggrEx)
                {
                    foreach (Exception innerException in aggrEx.InnerExceptions)
                    {
                        Logger.LogError(CallInfo.Site(), innerException);
                    }

                    return((int)ExitCode.UnknownFailure);
                }
                catch (Exception ex)
                {
                    Logger.LogError(CallInfo.Site(), ex);

                    return((int)ExitCode.UnknownFailure);
                }
            });

            commandLineApplication.Execute(args);
            Logger.Flush();
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Processes the certificate management.
        /// </summary>
        /// <param name="directoryPath">Directory to put the certificatex in.</param>
        /// <param name="certConfiguration">Certificate configuration. This is a combination of comma separated values in following format
        /// *certFileName*;*SourceOfCert*;*CertIdentifierInSource*.</param>
        /// <param name="keyVaultUris">KeyVault uris if key vault is to be used.</param>
        /// <param name="keyVaultClientId">Application client Id to access keyvault.</param>
        /// <param name="keyVaultClientSecret">Application client secret to access keyvault.</param>
        /// <param name="keyVaultClientCert">Application client certificate thumbprint if the keyvault app has certificate credentials.</param>
        /// <param name="useManagedIdentity">Use managed identity.</param>
        /// <returns>Exit code for the operation.</returns>
        internal static async Task <ExitCode> ProcessAsync(
            string directoryPath,
            string certConfiguration,
            List <string> keyVaultUris,
            string keyVaultClientId,
            string keyVaultClientSecret,
            string keyVaultClientCert,
            bool useManagedIdentity)
        {
            if (string.IsNullOrEmpty(directoryPath))
            {
                Logger.LogError(CallInfo.Site(), "Directory path missing for the Certificate directory.");
                return(ExitCode.DirectoryPathMissing);
            }

            if (string.IsNullOrEmpty(certConfiguration))
            {
                Logger.LogError(CallInfo.Site(), "Cert configuration missing. Please specify CertsToConfigure option");
                return(ExitCode.InvalidCertConfiguration);
            }

            // 1. Initialize KeyVault Client if params were passed.
            KeyVaultClient keyVaultClient = null;
            Dictionary <string, string> keyVaultSecretMap = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);

            if (keyVaultUris.Any())
            {
                KeyVaultClient.AuthenticationCallback callback = null;

                if (useManagedIdentity)
                {
                    string connectionString = "RunAs=App";

                    if (!string.IsNullOrEmpty(keyVaultClientId))
                    {
                        connectionString = connectionString + ";AppId=" + keyVaultClientId;
                    }

                    AzureServiceTokenProvider tokenProvider = new AzureServiceTokenProvider(connectionString);
                    callback = new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback);
                }
                else
                {
                    if (string.IsNullOrEmpty(keyVaultClientId))
                    {
                        Logger.LogError(CallInfo.Site(), "If KeyVaultUri is specified and managed identity is not used, KeyVault ClientId must be specified");
                        return(ExitCode.KeyVaultConfigurationIncomplete);
                    }

                    if (string.IsNullOrEmpty(keyVaultClientSecret) && string.IsNullOrEmpty(keyVaultClientCert))
                    {
                        Logger.LogError(CallInfo.Site(), "If KeyVaultUri is specified and managed identity is not used, KeyVault ClientSecret or KeyVault ClientCert must be specified");
                        return(ExitCode.KeyVaultConfigurationIncomplete);
                    }

                    if (!string.IsNullOrEmpty(keyVaultClientSecret))
                    {
                        callback =
                            (authority, resource, scope) => GetTokenFromClientSecretAsync(authority, resource, keyVaultClientId, keyVaultClientSecret);
                    }
                    else
                    {
                        X509Certificate2Collection keyVaultCerts = CertHelpers.FindCertificates(keyVaultClientCert, X509FindType.FindByThumbprint);

                        if (keyVaultCerts.Count == 0)
                        {
                            Logger.LogError(CallInfo.Site(), "Failed to find Client cert with thumbprint '{0}'", keyVaultClientCert);
                            return(ExitCode.KeyVaultConfigurationIncomplete);
                        }

                        callback =
                            (authority, resource, scope) => GetTokenFromClientCertificateAsync(authority, resource, keyVaultClientId, keyVaultCerts[0]);
                    }
                }

                keyVaultClient = new KeyVaultClient(callback);

                foreach (string keyVaultUri in keyVaultUris)
                {
                    IPage <SecretItem> secrets = await keyVaultClient.GetSecretsAsync(keyVaultUri).ConfigureAwait(false);

                    foreach (SecretItem secret in secrets)
                    {
                        keyVaultSecretMap[secret.Identifier.Name] = keyVaultUri;
                    }

                    while (!string.IsNullOrEmpty(secrets.NextPageLink))
                    {
                        secrets = await keyVaultClient.GetSecretsNextAsync(secrets.NextPageLink).ConfigureAwait(false);

                        foreach (SecretItem secret in secrets)
                        {
                            keyVaultSecretMap[secret.Identifier.Name] = keyVaultUri;
                        }
                    }
                }
            }

            // 2. Figure all the certs which need processing.
            string[] certsToConfigure          = certConfiguration.Split(',');
            string   currentExeDirectory       = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            string   fullDirectoryPathForCerts = Path.Combine(currentExeDirectory, directoryPath);

            // 3. Process specified certs one by one.
            foreach (string certToConfigure in certsToConfigure)
            {
                // 3a. Split the cert configuration data to get actual details. This data is informat
                // <CertNameOnDisk>;CertSource(LocalMachine or KeyVault);<CertIdentifier(SecretName or Thumbprint)>
                string[] certConfigurationParams = certToConfigure.Split(';');

                if (certConfigurationParams.Length != 3)
                {
                    Logger.LogError(CallInfo.Site(), "Invalid certificate configuration '{0}'. Cert configuration must be in format <CertFileName>;<CertSource>;<CertIdentifier>", certToConfigure);
                    return(ExitCode.InvalidCertConfiguration);
                }

                var certConfig =
                    new { CertName = certConfigurationParams[0], CertSource = certConfigurationParams[1], CertIdentifier = certConfigurationParams[2] };

                // 3b. Depending on the source of Cert get the PFX for the certs dropped into the directory.
                if (certConfig.CertSource.Equals("MyLocalMachine", StringComparison.OrdinalIgnoreCase))
                {
                    ExitCode localMachineCertHandler = await LocalMachineCertHandlerAsync(
                        certConfig.CertName,
                        certConfig.CertIdentifier,
                        fullDirectoryPathForCerts).ConfigureAwait(false);

                    if (localMachineCertHandler != ExitCode.Success)
                    {
                        return(localMachineCertHandler);
                    }
                }
                else if (certConfig.CertSource.Equals("KeyVault", StringComparison.OrdinalIgnoreCase))
                {
                    if (!keyVaultSecretMap.TryGetValue(certConfig.CertIdentifier, out string keyVaultUri))
                    {
                        Logger.LogError(CallInfo.Site(), "Certificate with name '{0}' missing from all specified KeyVaults", certConfig.CertIdentifier);
                        return(ExitCode.CertificateMissingFromSource);
                    }

                    ExitCode keyVaultCertHandlerExitCode = await KeyVaultCertHandlerAsync(
                        certConfig.CertName,
                        certConfig.CertIdentifier,
                        fullDirectoryPathForCerts,
                        keyVaultClient,
                        keyVaultUri).ConfigureAwait(false);

                    if (keyVaultCertHandlerExitCode != ExitCode.Success)
                    {
                        return(keyVaultCertHandlerExitCode);
                    }
                }
                else
                {
                    Logger.LogError(CallInfo.Site(), "Unsupported Certificate source '{0}' for cert '{1}'", certConfig.CertSource, certConfig.CertName);
                    return(ExitCode.UnsupportedCertSource);
                }

                // 3c. Convert PFX into .Key and .Crt. We are placing openssl next to this exe hence using current directory.
                ExitCode conversionExitCode = ConvertPfxIntoPemFormat(certConfig.CertName, fullDirectoryPathForCerts, currentExeDirectory, password: string.Empty);

                if (conversionExitCode != ExitCode.Success)
                {
                    return(conversionExitCode);
                }

                // 3d. Delete the PFX as it is no longer needed.
                File.Delete(Path.Combine(fullDirectoryPathForCerts, certConfig.CertName + ".pfx"));
            }

            return(ExitCode.Success);
        }