/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file 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. */ using System; using System.Globalization; using Amazon.Runtime.Internal.Util; using System.Collections.Generic; using Amazon.Util; using System.Linq; #if BCL || NETSTANDARD using Amazon.Runtime.CredentialManagement; #endif using System.ComponentModel; namespace Amazon.Runtime.Internal { /// /// InternalConfiguration holds the cached SDK configuration values /// obtained from the environment and profile configuration /// factories. These configuration values are loaded internally and /// are not the same as user exposed AWSConfigs. /// public class InternalConfiguration { /// /// Flag indicating if Endpoint Discovery is enabled. /// public bool? EndpointDiscoveryEnabled { get; set; } /// /// The retry mode to use: Legacy, Standard, or Adaptive. /// public RequestRetryMode? RetryMode { get; set; } /// /// The max number of request attempts. /// public int? MaxAttempts { get; set; } /// /// Endpoint of the EC2 Instance Metadata Service /// public string EC2MetadataServiceEndpoint { get; set; } /// /// Internet protocol version to be used for communicating with the EC2 Instance Metadata Service /// public EC2MetadataServiceEndpointMode? EC2MetadataServiceEndpointMode { get; set; } /// public string DefaultConfigurationModeName { get; set; } /// /// Configures the endpoint calculation for a service to go to a dual stack (ipv6 enabled) endpoint /// for the configured region. /// public bool? UseDualstackEndpoint { get; set; } /// /// Configures the endpoint calculation to go to a FIPS (https://aws.amazon.com/compliance/fips/) endpoint /// for the configured region. /// public bool? UseFIPSEndpoint { get; set; } /// /// Configures the SDK To ignore configured endpoint URLs in the configuration files or environment variables. /// The environment variable overrides the value set in the configuration file. /// public bool? IgnoreConfiguredEndpointUrls { get; set; } } #if BCL || NETSTANDARD /// /// Determines the configuration values based on environment variables. If /// no values is found for a configuration the value will be set to null. /// public class EnvironmentVariableInternalConfiguration : InternalConfiguration { private Logger _logger = Logger.GetLogger(typeof(EnvironmentVariableInternalConfiguration)); public const string ENVIRONMENT_VARIABLE_AWS_ENABLE_ENDPOINT_DISCOVERY = "AWS_ENABLE_ENDPOINT_DISCOVERY"; public const string ENVIRONMENT_VARIABLE_AWS_MAX_ATTEMPTS = "AWS_MAX_ATTEMPTS"; public const string ENVIRONMENT_VARIABLE_AWS_RETRY_MODE = "AWS_RETRY_MODE"; public const string ENVIRONMENT_VARIABLE_AWS_EC2_METADATA_SERVICE_ENDPOINT = "AWS_EC2_METADATA_SERVICE_ENDPOINT"; public const string ENVIRONMENT_VARIABLE_AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE = "AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE"; public const string ENVIRONMENT_VARIABLE_AWS_USE_DUALSTACK_ENDPOINT = "AWS_USE_DUALSTACK_ENDPOINT"; public const string ENVIRONMENT_VARIABLE_AWS_USE_FIPS_ENDPOINT = "AWS_USE_FIPS_ENDPOINT"; public const string ENVIRONMENT_VARIABLE_AWS_IGNORE_CONFIGURED_ENDPOINT_URLS = "AWS_IGNORE_CONFIGURED_ENDPOINT_URLS"; /// /// Attempts to construct a configuration instance of configuration environment /// variables. If an environment variable value isn't found then the individual value /// for that environment variable will be null. If unable to obtain a value converter /// to convert a configuration string to the appropriate type a InvalidOperationException /// is thrown. /// public EnvironmentVariableInternalConfiguration() { EndpointDiscoveryEnabled = GetEnvironmentVariable(ENVIRONMENT_VARIABLE_AWS_ENABLE_ENDPOINT_DISCOVERY); MaxAttempts = GetEnvironmentVariable(ENVIRONMENT_VARIABLE_AWS_MAX_ATTEMPTS); RetryMode = GetEnvironmentVariable(ENVIRONMENT_VARIABLE_AWS_RETRY_MODE); EC2MetadataServiceEndpoint = GetEC2MetadataEndpointEnvironmentVariable(); EC2MetadataServiceEndpointMode = GetEnvironmentVariable(ENVIRONMENT_VARIABLE_AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE); UseDualstackEndpoint = GetEnvironmentVariable(ENVIRONMENT_VARIABLE_AWS_USE_DUALSTACK_ENDPOINT); UseFIPSEndpoint = GetEnvironmentVariable(ENVIRONMENT_VARIABLE_AWS_USE_FIPS_ENDPOINT); IgnoreConfiguredEndpointUrls = GetEnvironmentVariable(ENVIRONMENT_VARIABLE_AWS_IGNORE_CONFIGURED_ENDPOINT_URLS, false); } private bool GetEnvironmentVariable(string name, bool defaultValue) { if(!TryGetEnvironmentVariable(name, out var value)) { return defaultValue; } try { return bool.Parse(value); } catch (Exception e) { _logger.Error(e, e.Message); throw new FormatException(e.Message, e.InnerException); } } private bool TryGetEnvironmentVariable(string environmentVariableName, out string value) { value = Environment.GetEnvironmentVariable(environmentVariableName); if (string.IsNullOrEmpty(value)) { _logger.InfoFormat($"The environment variable {environmentVariableName} was not set with a value."); value = null; return false; } return true; } private T? GetEnvironmentVariable(string name) where T : struct { if (!TryGetEnvironmentVariable(name, out var value)) { return null; } var converter = TypeDescriptor.GetConverter(typeof(T?)); if (converter == null) { throw new InvalidOperationException($"Unable to obtain type converter for type {typeof(T?)} " + $"to convert environment variable {name}."); } try { return (T?)converter.ConvertFromString(value); } catch (Exception e) { _logger.Error(e, $"The environment variable {name} was set with value {value}, but it could not be parsed as a valid value."); } return null; } /// /// Loads the EC2 Instance Metadata endpoint from the environment variable, and validates it is a well-formed uri /// /// Override EC2 instance metadata endpoint if valid, else an empty string private string GetEC2MetadataEndpointEnvironmentVariable() { if (!TryGetEnvironmentVariable(ENVIRONMENT_VARIABLE_AWS_EC2_METADATA_SERVICE_ENDPOINT, out var rawValue)) { return null; } if (!Uri.IsWellFormedUriString(rawValue, UriKind.Absolute)) { throw new AmazonClientException($"The environment variable {ENVIRONMENT_VARIABLE_AWS_EC2_METADATA_SERVICE_ENDPOINT} was set with value " + $"{rawValue}, but it could not be parsed as a well-formed Uri."); } return rawValue; } } /// /// Determines configuration values based on a stored in an . /// If the profile doesn't exist, the status will be logged and no value will be set in the configuration. /// public class ProfileInternalConfiguration : InternalConfiguration { private Logger _logger = Logger.GetLogger(typeof(ProfileInternalConfiguration)); /// /// Attempts to construct an instance of . /// If the AWS_PROFILE environment variable is set the instance will be constructed using that profile, /// otherwise it will use the default profile. /// /// If the profile doesn't exist status will be logged and no value will be set in the configuration. /// /// The ICredentialProfileSource to read the profile from. public ProfileInternalConfiguration(ICredentialProfileSource source) { var profileName = FallbackCredentialsFactory.GetProfileName(); Setup(source, profileName); } /// /// Attempts to construct an instance of . /// If the profile doesn't exist status will be logged and no value will be set in the configuration. /// /// The ICredentialProfileSource to read the profile from. /// The name of the profile. public ProfileInternalConfiguration(ICredentialProfileSource source, string profileName) { Setup(source, profileName); } private void Setup(ICredentialProfileSource source, string profileName) { CredentialProfile profile; if (source.TryGetProfile(profileName, out profile)) { DefaultConfigurationModeName = profile.DefaultConfigurationModeName; EndpointDiscoveryEnabled = profile.EndpointDiscoveryEnabled; RetryMode = profile.RetryMode; MaxAttempts = profile.MaxAttempts; EC2MetadataServiceEndpoint = profile.EC2MetadataServiceEndpoint; EC2MetadataServiceEndpointMode = profile.EC2MetadataServiceEndpointMode; UseDualstackEndpoint = profile.UseDualstackEndpoint; UseFIPSEndpoint = profile.UseFIPSEndpoint; IgnoreConfiguredEndpointUrls = profile.IgnoreConfiguredEndpointUrls; } else { _logger.InfoFormat("Unable to find a profile named '" + profileName + "' in store " + source.GetType()); return; } var items = new KeyValuePair[] { new KeyValuePair("defaults_mode", profile.DefaultConfigurationModeName), new KeyValuePair("endpoint_discovery_enabled", profile.EndpointDiscoveryEnabled), new KeyValuePair("retry_mode", profile.RetryMode), new KeyValuePair("max_attempts", profile.MaxAttempts), new KeyValuePair("ec2_metadata_service_endpoint", profile.EC2MetadataServiceEndpoint), new KeyValuePair("ec2_metadata_service_endpoint_mode", profile.EC2MetadataServiceEndpointMode), new KeyValuePair("use_dualstack_endpoint", profile.UseDualstackEndpoint), new KeyValuePair("use_fips_endpoint", profile.UseFIPSEndpoint), new KeyValuePair( "ignore_configured_endpoint_urls", profile.IgnoreConfiguredEndpointUrls), new KeyValuePair("endpoint_url", profile.EndpointUrl) }; foreach(var item in items) { _logger.InfoFormat(item.Value == null ? $"There is no {item.Key} set in the profile named '{profileName}' in store {source.GetType()}" : $"{item.Key} found in profile '{profileName}' in store {source.GetType()}" ); } } } #endif /// /// Probing mechanism to determine the configuration values from various sources. /// public static class FallbackInternalConfigurationFactory { #if BCL || NETSTANDARD private static CredentialProfileStoreChain _credentialProfileChain = new CredentialProfileStoreChain(); #endif private static InternalConfiguration _cachedConfiguration; static FallbackInternalConfigurationFactory() { Reset(); } private delegate InternalConfiguration ConfigGenerator(); /// /// Resets all the configuration values reloading as needed. This method will use /// the AWS_PROFILE environment variable if set to construct the instance. Otherwise /// the default profile will be used. /// public static void Reset() { #if BCL || NETSTANDARD //Preload configurations that are fast or pull all the values at the same time. Slower configurations //should be called for specific values dynamically. InternalConfiguration environmentVariablesConfiguration = new EnvironmentVariableInternalConfiguration(); InternalConfiguration profileConfiguration = new ProfileInternalConfiguration(_credentialProfileChain); #endif _cachedConfiguration = new InternalConfiguration(); var standardGenerators = new List { #if BCL || NETSTANDARD () => environmentVariablesConfiguration, () => profileConfiguration, #endif }; //Find the priority first ordered config value for each property _cachedConfiguration.DefaultConfigurationModeName = SeekString(standardGenerators, c => c.DefaultConfigurationModeName, defaultValue: null); _cachedConfiguration.EndpointDiscoveryEnabled = SeekValue(standardGenerators, (c) => c.EndpointDiscoveryEnabled); _cachedConfiguration.RetryMode = SeekValue(standardGenerators, (c) => c.RetryMode); _cachedConfiguration.MaxAttempts = SeekValue(standardGenerators, (c) => c.MaxAttempts); _cachedConfiguration.EC2MetadataServiceEndpoint = SeekString(standardGenerators, (c) => c.EC2MetadataServiceEndpoint); _cachedConfiguration.EC2MetadataServiceEndpointMode = SeekValue(standardGenerators, (c) => c.EC2MetadataServiceEndpointMode); _cachedConfiguration.UseDualstackEndpoint = SeekValue(standardGenerators, (c) => c.UseDualstackEndpoint); _cachedConfiguration.UseFIPSEndpoint = SeekValue(standardGenerators, (c) => c.UseFIPSEndpoint); _cachedConfiguration.IgnoreConfiguredEndpointUrls = SeekValue(standardGenerators, (c) => c.IgnoreConfiguredEndpointUrls); } private static T? SeekValue(List generators, Func getValue) where T : struct { //Look for the configuration value stopping at the first generator that returns the expected value. foreach (var generator in generators) { var configuration = generator(); T? value = getValue(configuration); if (value.HasValue) { return value; } } return null; } private static string SeekString(List generators, Func getValue, string defaultValue = "") { //Look for the configuration value stopping at the first generator that returns the expected value. foreach (var generator in generators) { var configuration = generator(); string value = getValue(configuration); if (!string.IsNullOrEmpty(value)) { return value; } } return defaultValue; } /// /// Flag that specifies if endpoint discovery is enabled, disabled, /// or not set. /// public static bool? EndpointDiscoveryEnabled { get { return _cachedConfiguration.EndpointDiscoveryEnabled; } } /// /// Flag that specifies which retry mode to use or if retry mode has /// not been set. /// public static RequestRetryMode? RetryMode { get { return _cachedConfiguration.RetryMode; } } /// /// Flag that specifies the max number of request attempts or if max /// attempts has not been set. /// public static int? MaxAttempts { get { return _cachedConfiguration.MaxAttempts; } } /// /// Endpoint of the EC2 Instance Metadata Service /// public static string EC2MetadataServiceEndpoint { get { return _cachedConfiguration.EC2MetadataServiceEndpoint; } } /// /// Internet protocol version to be used for communicating with the EC2 Instance Metadata Service /// public static EC2MetadataServiceEndpointMode? EC2MetadataServiceEndpointMode { get { return _cachedConfiguration.EC2MetadataServiceEndpointMode; } } /// public static string DefaultConfigurationModeName { get { return _cachedConfiguration.DefaultConfigurationModeName; } } /// /// Configures the endpoint calculation to go to a dual stack (ipv6 enabled) endpoint /// for the configured region. /// public static bool? UseDualStackEndpoint { get { return _cachedConfiguration.UseDualstackEndpoint; } } /// /// Configures the endpoint calculation to go to a FIPS (https://aws.amazon.com/compliance/fips/) endpoint /// for the configured region. /// public static bool? UseFIPSEndpoint { get { return _cachedConfiguration.UseFIPSEndpoint; } } /// /// Configures the SDK To ignore configured endpoint URLs in the configuration files or environment variables. /// The environment variable overrides the value set in the configuration file. /// public static bool? IgnoreConfiguredEndpointUrls { get { return _cachedConfiguration.IgnoreConfiguredEndpointUrls; } } } }