Open ravron opened 1 week ago
Thanks for submitting - this is an interesting, but I think valid use case.
In general resolution of defaults (including the profile, credential/token providers, ect) is complex and we generally want to keep those details as api private so I don't necessarily think we should make SharedConfig#sso_token_from_config
public. In general though, you could determine the "default" token provider by creating a client and accessing client.config.token_provider
. I do think its reasonable to be able to construct SSOCredentials
with a SSOTokenProvider
though and you could then get the users default token provider off a client and use that.
Can you provide more details on how you are creating and using clients? In general, a user may have more than 1 sso-sessions configured and which would get used is based on configured profile (which in client creation, is resolved from various possible locations).
Thanks for the the response!
In general resolution of defaults (including the profile, credential/token providers, ect) is complex and we generally want to keep those details as api private
I totally understand. I would much rather rely on the SDK to do the right thing with respect to finding the right credentials. I didn't know that I can access a client's token_provider
to get the resolved token provider — that's a great step in the right direction.
Can you provide more details on how you are creating and using clients?
Sure. This is a utility script used by many different developers at my organization to make changes to SSM parameters in one of several AWS accounts (for example, the sandbox
or production
accounts). In our previous IAM configuration, developers had IAM users in the management account, and those users could assume IAM roles in the member accounts to make changes. In this configuration, creating SDK clients was easy. The developer would call the script:
./config.rb --account sandbox
and I could hard-code the appropriate roles:
account_name = ... # get account name from command line
roles = {
sandbox: 'arn:aws:iam::111111111111:role/ConfigureSSM',
production: 'arn:aws:iam::222222222222:role/ConfigureSSM'
}
ssm = Aws::SSM::Client.new(role_arn: roles[account_name])
This meant that the script didn't care about the developer's AWS CLI configuration. As long as the developer provided credentials that could assume the necessary role, the script worked.
Now, we'd like to switch from ordinary IAM roles in the member accounts to SSO permission sets, while keeping the same convenient developer experience. The developer will still call the script as before, but now script will hard-code permission sets and account IDs:
account_name = ... # get account name from command line
permission_sets = {
sandbox: {
sso_account_id: '111111111111',
sso_role_name: 'ConfigureSSM',
sso_region: 'us-west-2',
sso_session: '' # required - but what do I put here?
},
production: {
sso_account_id: '111111111111',
sso_role_name: 'ConfigureSSM',
sso_region: 'us-west-2',
sso_session: '' # required - but what do I put here?
},
}
credentials = Aws::SSOCredentials.new(**permission_sets[account_name])
ssm = Aws::SSM::Client.new(credentials:)
I opened this issue when I could not figure out how to keep the same convenient developer experience we had when using roles directly.
In general, a user may have more than 1 sso-sessions configured and which would get used is based on configured profile.
Absolutely, and I would rather my script not know anything about which sso-session is used. Ideally, the script just knows it needs to use permission set ConfigureSSM
in account 111111111111 to do its work, and the SDK figures out whether it can get sigv4 credentials, and does so.
That makes sense and I think I may have a relatively simple solution for you: AssumeRoleCredentials. You can provide a role_arn
(combination of the account + role) and role_session_name
and it will use an STS
client with default credentials (meaning that whatever credentials the user has configured, including if they have sso credentials) will be used and as long as those credentials have permission to assume the role, it should work. Something like:
credentials = Aws::AssumeRoleCredentials.new(role_arn: roles[account_name], role_session_name: 'whatever-session-name-you-want') # will use the users default credentials to assume the role
ssm = Aws::SSM::Client.new(credentials:)
Thanks for the suggestion! I don't think I was clear, though: we currently use role assumption with no issue.
The problem is that we want to switch from using roles we provision in each account, to using IAM Identity Center permission sets, and eliminate the existing roles. As you demonstrated, it's easy and seamless to assume a role whose ARN you know, but it's much more complex to assume a permission set whose account ID and name you know, without relying on details of the developer's ~/.aws/config
file — hence this issue.
That makes sense. I think we don't want to expose any of the shared config or default resolution behavior required for this.
However, I believe you can get this functionality, using only public functionality by extending the SSOCredentials. Something like:
class SSOCredsDefaultToken < Aws::SSOCredentials
def initialize(options = nil)
@legacy = false
@sso_role_name = options.delete(:sso_role_name)
@sso_account_id = options.delete(:sso_account_id)
@sso_region = options.delete(:sso_region)
@client = options.delete(:client)
unless @client
client_opts = {}
options.each_pair { |k,v| client_opts[k] = v unless CLIENT_EXCLUDE_OPTIONS.include?(k) }
client_opts[:region] = @sso_region
client_opts[:credentials] = nil
@client = Aws::SSO::Client.new(client_opts)
end
# get default token_provider resolved by normal chain
@token_provider = @client.config.token_provider
raise 'No token provider configured' unless @token_provider
# do not call super, instead just call refresh
@async_refresh = true
refresh
end
end
Its then fairly easy to use as:
credentials = SSOCredsDefaultToken.new(
sso_account_id: '222222222222',
sso_role_name: 'PermissionSetB',
sso_region: 'us-west-2'
# No need to put sso_session
)
sts = Aws::STS::Client.new(credentials:)
I think the other alternative would be to just rely on private functionality and do something like:
default_sso_session = Aws.shared_config.send(:get_config_value, 'sso_session', {})
Describe the feature
When creating an instance of
SSOCredentials
to provide to an API client, I would like a way to indicate that the instance should look up and use thesso_session
associated with the current profile, rather than passing it myself.Use Case
When creating an instance of
SSOCredentials
to provide to an API client, I would like to select the permission set used by providing thesso_role_name
. However, doing so requires that I know the name of the user's configuredsso_session
, which I generally do not, so I must needlessly require that users of the tool I'm building name theirsso_session
a particular string.For example, imagine a user has this
~/.aws/config
:If
PermissionSetA
has the necessary permissions to perform my task, this is easy. The user first runsaws sso login
and the SSO token is cached to disk. My code can simply create a client without any options and rely on theCredentialProviderChain
to find the SSO token and create a validSSOCredentials
instance:However, consider the case where I would like to use
PermissionSetB
in account 222222222222. I expect that the user's existing SSO token should work to assume thatPermissionSetB
, so I simply need to specify a different permission set:The user's SSO session is named
unusual-session-name
, and theSharedConfig
knows it. But, as far as I can tell, that information isn't exposed.SharedConfig
has bothsso_credentials_from_config
andsso_token_from_config
, but both are private. Even if they were public, they wouldn't quite work:sso_credentials_from_config
returns anSSOCredentials
that has the permission set baked in, whilesso_token_from_config
returns anSSOTokenProvider
that cannot be used to construct anSSOCredentials
.Proposed Solution
The first solution that comes to mind requires two changes:
SharedConfig#sso_token_from_config
public. This would make it possible to get anSSOTokenProvider
corresponding to the user's current AWS profile and SSO session. This is challenging, though, because all ofSharedConfig
is currently a private API.SSOCredentials.initialize
to accept a:token_provider
option. If passed,:sso_session
is no longer needed, and the providedSSOTokenProvider
is used instead of creating a new one.I'm not sure what's a clean option here, though, and I'm looking for feedback.
Other Information
No response
Acknowledgements
SDK version used
3.188.0
Environment details (OS name and version, etc.)
macOS 14.6.1