Aws-sdk-net: Is there support for SSO authentication in CredentialProfileStoreChain?

Created on 13 Aug 2020  路  10Comments  路  Source: aws/aws-sdk-net

The Question

At my company we are using the AWS SSO integration with Azure AD. I found out that there's a version of AWS CLI that supports this way of authenticating.

I tried using CredentialProfileStoreChain to use the profile I created using the new AWS CLI V2 and I get an exception saying that the profile is not found.

I can see that the new CLI is using a different way of storing the credentials and they don't appear in the credentials file like before, so I assume this is why it doesn't work.

My question is: do you already support the SSO authentication in the SDK? If no, is there a plant to support it?

Environment

  • SDK Version: 3.3.106.45
  • Package Version: AWSSDK.DynamoDBV2 3.3.106.45
  • OS Info: Windows 10
  • Build Environment Visual Studio
  • Targeted .NET Platform:

This is a :question: general question

feature-request modulsdk-generated

Most helpful comment

I looked at the links provided by the guys above and this is what I think of all this :)

First of all, thank you @Yottster. Your library definitely helped in understanding how all this works together.

So, now I can explain better how I expected the SDK to work :)

I wanted to use the same profiles that I have configured in my AWS CLI, but inside my applications for debugging purposes. I expected to use the same profile name that I would in the CLI and thought it would "just work". I totally forgot about the part where the system has to refresh the token though.

For my use-case, the fact that the token can be expired is fine: I just want to be able to refresh the CLI and be all set with my application.

I expected something similar to the following code available in the SDK (I stole some pieces from @Yottster's code for this example and some from this ticket: https://github.com/aws/aws-tools-for-powershell/issues/91):

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Amazon;
using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;
using Amazon.Runtime.Internal.Util;
using Amazon.SSO;
using Amazon.SSO.Model;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace ConsoleApp
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var credentials = await GetSSOProfileCredentials("my-account-sandbox");

            // Do things

            Console.ReadLine();
        }

        internal class AwsSsoCacheObject
        {
            public string startUrl { get; set; }
            public string region { get; set; }
            public string accessToken { get; set; }
            public DateTime expiresAt { get; set; }
        }

        private static string GetSha1(string url)
        {
            byte[] hash;
            using (var sha1 = new SHA1Managed())
            {
                hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(url));
            }

            var sb = new StringBuilder(hash.Length * 2);

            foreach (byte b in hash)
            {
                sb.Append(b.ToString("x2"));
            }

            return sb.ToString();
        }

        private static async Task<AWSCredentials> GetSSOProfileCredentials(string profileName)
        {
            string configFilePath = Path.Combine(SharedCredentialsFile.DefaultDirectory, "config");
            string ssoFolderPath = Path.Combine(SharedCredentialsFile.DefaultDirectory, "sso", "cache");

            var profileFile = new ProfileIniFile(configFilePath, false);
            if (!profileFile.TryGetSection(profileName, out var profile)) return null;

            var startUrl  = profile["sso_start_url"];
            var accountId = profile["sso_account_id"];
            var roleName  = profile["sso_role_name"];
            var startUrlSha = GetSha1(startUrl);
            var fullCacheFilePath = Path.Combine(ssoFolderPath, startUrlSha + ".json");

            var cacheObject = JsonConvert.DeserializeObject<AwsSsoCacheObject>(File.ReadAllText(fullCacheFilePath), new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-ddTHH:mm:ssUTC" });

            if (cacheObject.expiresAt < DateTime.Now)
            {
                return null;
            }

            using var ssoClient = new AmazonSSOClient(
                new AnonymousAWSCredentials(),
                new AmazonSSOConfig { RegionEndpoint = RegionEndpoint.GetBySystemName(cacheObject.region) });

            var getRoleCredentialsResponse = await ssoClient.GetRoleCredentialsAsync(new GetRoleCredentialsRequest
            {
                AccessToken = cacheObject.accessToken,
                AccountId = accountId,
                RoleName = roleName
            });

            AWSCredentials cred = new SessionAWSCredentials(
                getRoleCredentialsResponse.RoleCredentials.AccessKeyId,
                getRoleCredentialsResponse.RoleCredentials.SecretAccessKey,
                getRoleCredentialsResponse.RoleCredentials.SessionToken);
            return cred;
        }

    }
}

So basically I expected to get SessionCredentials for the profile, in case it's available. I can of course write something for myself and use it (something similar to the method above), I have nothing against it. Just thought if there will maybe be something available out-of-the-box, I should use it, instead of re-inventing the wheel.

My question to you @NGL321 is: do you guys have any plans on supporting either something similar to what @Yottster has or the example that I provided above?

All 10 comments

Not exactly what you're looking for, but depending on your use case you might be able to use the nuget package or use the code for inspiration. It shares the cache with the CLI, but no profile support yet.

https://github.com/mhlabs/MhLabs.AwsCliSso

@Yottster thanks, I'll check it out.
Have you thought of maybe submitting a PR to the SDK so they can have this functionality?

I dont think that it should live in the sdk, in the same sense that the matching python code lives in the CLI and not in boto.

Hey @derwasp,

I found this in our documentation. This is the only method of SSO I am aware of for the SDK. Is this what you were looking for?

If not, and this is a feature you would like to contribute, I can find out if this is something we are interested in merging into the codebase.

馃樃 馃樂

This issue has not recieved a response in 2 weeks. If you want to keep this issue open, please just leave a comment below and auto-close will be canceled.

We're in the process of switching to AWS SSO and this is something we're running into also. It would certainly be nice to have something like what @Yottster linked in the SDK and allowing it to be used by the FallbackCredentialsFactory. Or at the very least, allowing the cached login from the CLI to be used.

I looked at the links provided by the guys above and this is what I think of all this :)

First of all, thank you @Yottster. Your library definitely helped in understanding how all this works together.

So, now I can explain better how I expected the SDK to work :)

I wanted to use the same profiles that I have configured in my AWS CLI, but inside my applications for debugging purposes. I expected to use the same profile name that I would in the CLI and thought it would "just work". I totally forgot about the part where the system has to refresh the token though.

For my use-case, the fact that the token can be expired is fine: I just want to be able to refresh the CLI and be all set with my application.

I expected something similar to the following code available in the SDK (I stole some pieces from @Yottster's code for this example and some from this ticket: https://github.com/aws/aws-tools-for-powershell/issues/91):

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Amazon;
using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;
using Amazon.Runtime.Internal.Util;
using Amazon.SSO;
using Amazon.SSO.Model;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace ConsoleApp
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var credentials = await GetSSOProfileCredentials("my-account-sandbox");

            // Do things

            Console.ReadLine();
        }

        internal class AwsSsoCacheObject
        {
            public string startUrl { get; set; }
            public string region { get; set; }
            public string accessToken { get; set; }
            public DateTime expiresAt { get; set; }
        }

        private static string GetSha1(string url)
        {
            byte[] hash;
            using (var sha1 = new SHA1Managed())
            {
                hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(url));
            }

            var sb = new StringBuilder(hash.Length * 2);

            foreach (byte b in hash)
            {
                sb.Append(b.ToString("x2"));
            }

            return sb.ToString();
        }

        private static async Task<AWSCredentials> GetSSOProfileCredentials(string profileName)
        {
            string configFilePath = Path.Combine(SharedCredentialsFile.DefaultDirectory, "config");
            string ssoFolderPath = Path.Combine(SharedCredentialsFile.DefaultDirectory, "sso", "cache");

            var profileFile = new ProfileIniFile(configFilePath, false);
            if (!profileFile.TryGetSection(profileName, out var profile)) return null;

            var startUrl  = profile["sso_start_url"];
            var accountId = profile["sso_account_id"];
            var roleName  = profile["sso_role_name"];
            var startUrlSha = GetSha1(startUrl);
            var fullCacheFilePath = Path.Combine(ssoFolderPath, startUrlSha + ".json");

            var cacheObject = JsonConvert.DeserializeObject<AwsSsoCacheObject>(File.ReadAllText(fullCacheFilePath), new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-ddTHH:mm:ssUTC" });

            if (cacheObject.expiresAt < DateTime.Now)
            {
                return null;
            }

            using var ssoClient = new AmazonSSOClient(
                new AnonymousAWSCredentials(),
                new AmazonSSOConfig { RegionEndpoint = RegionEndpoint.GetBySystemName(cacheObject.region) });

            var getRoleCredentialsResponse = await ssoClient.GetRoleCredentialsAsync(new GetRoleCredentialsRequest
            {
                AccessToken = cacheObject.accessToken,
                AccountId = accountId,
                RoleName = roleName
            });

            AWSCredentials cred = new SessionAWSCredentials(
                getRoleCredentialsResponse.RoleCredentials.AccessKeyId,
                getRoleCredentialsResponse.RoleCredentials.SecretAccessKey,
                getRoleCredentialsResponse.RoleCredentials.SessionToken);
            return cred;
        }

    }
}

So basically I expected to get SessionCredentials for the profile, in case it's available. I can of course write something for myself and use it (something similar to the method above), I have nothing against it. Just thought if there will maybe be something available out-of-the-box, I should use it, instead of re-inventing the wheel.

My question to you @NGL321 is: do you guys have any plans on supporting either something similar to what @Yottster has or the example that I provided above?

I implemented something similar to what you did @derwasp, but I also added the credentials to the FallbackCredentialsFactory:

            AWSCredentials ssoCredentials = await GetSSOCredentials();

            if (ssoCredentials != null)
            {
                FallbackCredentialsFactory.CredentialsGenerators.Insert(0, () => ssoCredentials);
            }

This way everything still works with roles when running on our Fargate/Lambda/EC2 instances.

@LTAcosta this is an awesome idea, thanks for sharing!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

berkeleybross picture berkeleybross  路  3Comments

AlfredoDiaz90 picture AlfredoDiaz90  路  4Comments

krzysztof-at-mp picture krzysztof-at-mp  路  4Comments

genifycom picture genifycom  路  4Comments

akatz0813 picture akatz0813  路  4Comments