예제 #1
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);
        }
예제 #2
0
        /// <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 > 1)
            {
                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);

            return(Task.FromResult(SaveCertificatePrivateKeyToDisk(rawCertData, certificateName, fullDirectoryPath)));
        }
        /// <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.SingleValue);
            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 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))
                    {
                        Logger.ConfigureLogger(applicationInsightsInstrumentationKeyOption.GetValueExtended(useEnvironmentVariables));
                    }

                    if (configureCertsOption.HasValueExtended(useEnvironmentVariables))
                    {
                        ExitCode certHandlerExitCode = await CertificateHandler.ProcessAsync(
                            configureCertsOption.GetValueExtended(useEnvironmentVariables),
                            certsToConfigureOption.GetValueExtended(useEnvironmentVariables),
                            keyVaultUriOption.GetValueExtended(useEnvironmentVariables),
                            keyVaultClientIdOption.GetValueExtended(useEnvironmentVariables),
                            keyVaultClientSecretOption.GetValueExtended(useEnvironmentVariables),
                            keyVaultClientCertThumbprintOption.GetValueExtended(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);
        }
 /// <summary>
 /// Logs the error.
 /// </summary>
 /// <param name="callInfo">The call information.</param>
 /// <param name="exp">The exception.</param>
 public static void LogError(CallInfo callInfo, Exception exp)
 {
     Log(callInfo, TraceLevel.Error, string.Format("Exception: {0}", exp));
 }
 /// <summary>
 /// Logs the error.
 /// </summary>
 /// <param name="callInfo">The call information.</param>
 /// <param name="messageFormat">The message format.</param>
 /// <param name="arguments">The arguments.</param>
 public static void LogError(CallInfo callInfo, string messageFormat, params object[] arguments)
 {
     Log(callInfo, TraceLevel.Error, GetMessage(messageFormat, arguments));
 }
예제 #6
0
 /// <summary>
 /// Logs the error.
 /// </summary>
 /// <param name="callInfo">The call information.</param>
 /// <param name="exp">The exception.</param>
 public static void LogError(CallInfo callInfo, Exception exp)
 {
     Log(callInfo, TraceLevel.Error, $"Exception: {exp}");
 }