From 0d0a3378dd4f9b2a584d04269056bb7c10a73557 Mon Sep 17 00:00:00 2001 From: Peter Song Date: Tue, 5 Nov 2024 12:01:48 -0800 Subject: [PATCH] Fixes issue #3479 where S3ForcePathStyle is unable to be read from a credential profile --- .../666f52b5-8ab6-4817-b54c-f42d2d41bb46.json | 18 +++++ .../CredentialManagement/CredentialProfile.cs | 5 ++ .../SharedCredentialsFile.cs | 26 +++++- sdk/src/Services/S3/Custom/AmazonS3Config.cs | 33 +++++++- .../UnitTests/Custom/S3ForcePathStyleTests.cs | 81 +++++++++++++++++++ 5 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 generator/.DevConfigs/666f52b5-8ab6-4817-b54c-f42d2d41bb46.json create mode 100644 sdk/test/Services/S3/UnitTests/Custom/S3ForcePathStyleTests.cs diff --git a/generator/.DevConfigs/666f52b5-8ab6-4817-b54c-f42d2d41bb46.json b/generator/.DevConfigs/666f52b5-8ab6-4817-b54c-f42d2d41bb46.json new file mode 100644 index 000000000000..f98646481a7c --- /dev/null +++ b/generator/.DevConfigs/666f52b5-8ab6-4817-b54c-f42d2d41bb46.json @@ -0,0 +1,18 @@ +{ + "core": { + "changeLogMessages": [ + "Add S3ForcePathStyle as a config option for credential profiles." + ], + "type": "patch", + "updateMinimum": true + }, + "services": [ + { + "serviceName": "S3", + "type": "patch", + "changeLogMessages": [ + "Fixes [issue 3479](https://github.com/aws/aws-sdk-net/issues/3479) where ForcePathStyle is unable to be read from the config file" + ] + } + ] +} diff --git a/sdk/src/Core/Amazon.Runtime/CredentialManagement/CredentialProfile.cs b/sdk/src/Core/Amazon.Runtime/CredentialManagement/CredentialProfile.cs index 83e2cc39090f..365676f20004 100644 --- a/sdk/src/Core/Amazon.Runtime/CredentialManagement/CredentialProfile.cs +++ b/sdk/src/Core/Amazon.Runtime/CredentialManagement/CredentialProfile.cs @@ -93,6 +93,11 @@ internal Dictionary> NestedProperties /// If true, the use of multi-region access points is disabled. /// public bool? S3DisableMultiRegionAccessPoints { get; set; } + + /// + /// When true, S3 requests will always use path style addressing. + /// + public bool? S3ForcePathStyle { get; set; } /// /// The Sts Regional Endpoints Value as either legacy or regional diff --git a/sdk/src/Core/Amazon.Runtime/CredentialManagement/SharedCredentialsFile.cs b/sdk/src/Core/Amazon.Runtime/CredentialManagement/SharedCredentialsFile.cs index e620e5e9eea5..e8141a869d6d 100644 --- a/sdk/src/Core/Amazon.Runtime/CredentialManagement/SharedCredentialsFile.cs +++ b/sdk/src/Core/Amazon.Runtime/CredentialManagement/SharedCredentialsFile.cs @@ -71,6 +71,7 @@ public class SharedCredentialsFile : ICredentialProfileStore private const string RequestMinCompressionSizeBytesField = "request_min_compression_size_bytes"; private const string ClientAppIdField = "sdk_ua_app_id"; private const string AccountIdEndpointModeField = "account_id_endpoint_mode"; + private const string S3ForcePathStyleField = "s3_force_path_style"; private readonly Logger _logger = Logger.GetLogger(typeof(SharedCredentialsFile)); private static readonly HashSet ReservedPropertyNames = new HashSet(StringComparer.OrdinalIgnoreCase) @@ -104,7 +105,8 @@ public class SharedCredentialsFile : ICredentialProfileStore DisableRequestCompressionField, RequestMinCompressionSizeBytesField, ClientAppIdField, - AccountIdEndpointModeField + AccountIdEndpointModeField, + S3ForcePathStyleField }; /// @@ -443,9 +445,13 @@ private void RegisterProfileInternal(CredentialProfile profile) if (profile.ClientAppId != null) reservedProperties[ClientAppIdField] = profile.ClientAppId; + if (profile.AccountIdEndpointMode != null) reservedProperties[AccountIdEndpointModeField] = profile.AccountIdEndpointMode.ToString().ToLowerInvariant(); + if (profile.S3ForcePathStyle != null) + reservedProperties[S3ForcePathStyleField] = profile.S3ForcePathStyle.ToString().ToLowerInvariant(); + var profileDictionary = PropertyMapping.CombineProfileParts( profile.Options, ReservedPropertyNames, reservedProperties, profile.Properties); @@ -966,6 +972,21 @@ private bool TryGetProfile(string profileName, bool doRefresh, bool isSsoSession #endif } + + string s3ForcePathStyleString; + bool? s3ForcePathStyle = null; + if (reservedProperties.TryGetValue(S3ForcePathStyleField, out s3ForcePathStyleString)) + { + bool s3ForcePathStyleOut; + if (!bool.TryParse(s3ForcePathStyleString, out s3ForcePathStyleOut)) + { + Logger.GetLogger(GetType()).InfoFormat("Invalid value {0} for {1} in profile {2}. A boolean true/false is expected.", s3ForcePathStyleString, S3ForcePathStyleField, profileName); + profile = null; + return false; + } + s3ForcePathStyle = s3ForcePathStyleOut; + } + profile = new CredentialProfile(profileName, profileOptions) { UniqueKey = toolkitArtifactGuid, @@ -992,7 +1013,8 @@ private bool TryGetProfile(string profileName, bool doRefresh, bool isSsoSession DisableRequestCompression = disableRequestCompression, RequestMinCompressionSizeBytes = requestMinCompressionSizeBytes, ClientAppId = clientAppId, - AccountIdEndpointMode = accountIdEndpointMode + AccountIdEndpointMode = accountIdEndpointMode, + S3ForcePathStyle = s3ForcePathStyle }; if (!IsSupportedProfileType(profile.ProfileType)) diff --git a/sdk/src/Services/S3/Custom/AmazonS3Config.cs b/sdk/src/Services/S3/Custom/AmazonS3Config.cs index aab259e30159..fbdec69d4df3 100644 --- a/sdk/src/Services/S3/Custom/AmazonS3Config.cs +++ b/sdk/src/Services/S3/Custom/AmazonS3Config.cs @@ -36,7 +36,7 @@ public partial class AmazonS3Config : ClientConfig private const string AwsS3UsEast1RegionalEndpointsEnvironmentVariable = "AWS_S3_US_EAST_1_REGIONAL_ENDPOINT"; private const string DisableMRAPEnvName = "AWS_S3_DISABLE_MULTIREGION_ACCESS_POINTS"; - private bool forcePathStyle = false; + private bool? _forcePathStyle; private bool useAccelerateEndpoint = false; private S3UsEast1RegionalEndpointValue? s3UsEast1RegionalEndpointValue; private readonly string legacyUSEast1GlobalRegionSystemName = RegionEndpoint.USEast1.SystemName; @@ -61,13 +61,40 @@ public IS3ExpressCredentialProvider S3ExpressCredentialProvider set { s3ExpressCredentialProvider = value; } } + private object _forcePathStyleLock = new object(); /// /// When true, requests will always use path style addressing. /// public bool ForcePathStyle { - get { return forcePathStyle; } - set { forcePathStyle = value; } + get + { + if (_forcePathStyle.HasValue) + { + return _forcePathStyle.GetValueOrDefault(); + } + + ResolveCredentialProfile(); + + lock (_forcePathStyleLock) + { + if (_forcePathStyle.HasValue) + { + return _forcePathStyle.Value; + } + + _forcePathStyle = _profile?.S3ForcePathStyle; + return _forcePathStyle.GetValueOrDefault(); + } + } + + set + { + lock (_forcePathStyleLock) + { + _forcePathStyle = value; + } + } } /// diff --git a/sdk/test/Services/S3/UnitTests/Custom/S3ForcePathStyleTests.cs b/sdk/test/Services/S3/UnitTests/Custom/S3ForcePathStyleTests.cs new file mode 100644 index 000000000000..f48c9b25d662 --- /dev/null +++ b/sdk/test/Services/S3/UnitTests/Custom/S3ForcePathStyleTests.cs @@ -0,0 +1,81 @@ +using Amazon.S3; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using AWSSDK_DotNet.CommonTest.Utils; +using Amazon.Runtime.CredentialManagement; + +namespace AWSSDK.UnitTests +{ + [TestClass] + public class S3ForcePathStyleTests + { + private static readonly string ProfileText = + @"[enable_force_path_style] + region = us-west-2 + aws_access_key_id = default_aws_access_key_id + aws_secret_access_key = default_aws_secret_access_key + s3_force_path_style = true + [disable_force_path_style] + region = us-west-2 + aws_access_key_id = other_aws_access_key_id + aws_secret_access_key = other_aws_secret_access_key + s3_force_path_style = false"; + + private const string AWSProfileVariable = "AWS_PROFILE"; + private string _beginningAWSProfileEnvironmentValue; + private string _tempCredentialsFilePath; + + [TestInitialize] + public void Initialize() + { + // Save off current environment variable value to restore later + _beginningAWSProfileEnvironmentValue = Environment.GetEnvironmentVariable(AWSProfileVariable); + + // Then clear the current value so every test is starting from a clean slate + Environment.SetEnvironmentVariable(AWSProfileVariable, ""); + + // set credentials file and use it to load CredentialProfileStoreChain + _tempCredentialsFilePath = Path.GetTempFileName(); + File.WriteAllText(_tempCredentialsFilePath, ProfileText); + ReflectionHelpers.Invoke(typeof(AmazonS3Config), "credentialProfileChain", new CredentialProfileStoreChain(_tempCredentialsFilePath)); + ReflectionHelpers.Invoke(typeof(AmazonS3Config), "_triedToResolveProfile", false); + } + + [TestCleanup] + public void RestoreOriginalSettings() + { + Environment.SetEnvironmentVariable(AWSProfileVariable, _beginningAWSProfileEnvironmentValue); + File.Delete(_tempCredentialsFilePath); + } + + [TestMethod] + [TestCategory("S3")] + public void CredentialProfileEnable_ShouldApplyToS3Config() + { + Environment.SetEnvironmentVariable(AWSProfileVariable, "enable_force_path_style"); + var config = new AmazonS3Config(); + Assert.IsTrue(config.ForcePathStyle); + } + + [TestMethod] + [TestCategory("S3")] + public void UnsetForcePathStyleShouldDefaultToFalse() + { + var config = new AmazonS3Config(); + Assert.IsFalse(config.ForcePathStyle); + } + + [TestMethod] + [TestCategory("S3")] + public void ConfigShouldOverrideProfile() + { + Environment.SetEnvironmentVariable(AWSProfileVariable, "enable_force_path_style"); + var config = new AmazonS3Config + { + ForcePathStyle = false + }; + Assert.IsFalse(config.ForcePathStyle); + } + } +}