Skip to content

awscli_saml_sso is a command line tool that aims to get temporary credentials from SAML identity provider in order to authenticate to awscli.

License

Notifications You must be signed in to change notification settings

ali-el-moussawi-octo/awscli-saml-sso

 
 

Repository files navigation

AWSCLI SAML SSO

https://img.shields.io/pypi/v/awscli_saml_sso https://img.shields.io/pypi/l/awscli_saml_sso https://img.shields.io/pypi/pyversions/awscli_saml_sso

awscli_saml_sso is a command line tool that aims to get temporary credentials from SAML identity provider in order to authenticate to awscli.

Requirements

  • Python 3.8 to 3.11
  • Microsoft Edge web browser installed on operating system

Installation

You need a fully functional python 3 environment, then you can install tool from pypi:

pip install awscli-saml-sso

Usage

You only need to run the following command in terminal:

awscli_saml_sso

> ...

# Please choose the role you would like to assume:
# [ 0 ]:  arn:aws:iam::<account_number>:role/<role_name>
# [ 1 ]:  arn:aws:iam::<account_number>:role/<role_name>
# ...
# Selection: <select among numbered roles>


# ----------------------------------------------------------------
# Your new access key pair has been stored in the AWS configuration file /home/.aws/credentials under the saml profile.
# Note that it will expire at 2020-12-01 13:17:27+00:00.
# After this time, you may safely rerun this script to refresh your access key pair.
# To use this credential, call the AWS CLI with the --profile option (e.g. aws --profile saml ec2 describe-instances).
# ----------------------------------------------------------------


# Simple API example listing all S3 buckets:
# ['your-lovely-bucket', ...]
  1. ask you to fill in required identity provider url in the form of https://<fqdn>:<port>/adfs/ls/IdpInitiatedSignOn.aspx?loginToRp=urn:amazon:webservices
  2. opens a headless web browser to fulfil SSO authentication through your identity provider, asking you to input username, password and MFA
  3. retrieve attached AWS roles and ask you to choose role you would like to assume
  4. provide a saml profile in /home/.aws/credentials filled with temporary credentials

You can see what is happening in the web browser by appending --show-browser (headless mode is not used)

You can also use the browser to input username and passord by appending --use-browser (headless mode is not used)

After a successfull run with identiy provider nickname MyTenant, you can run this to avoid any prompt other than MFA

awscli_saml_sso --use-stored --idp-nickname=MyTenant

At the end, you just need to use AWS cofigured saml profile to authenticate your awscli calls

aws --profile saml ec2 describe-instances

OR

AWS_PROFILE=saml aws ec2 describe-instances

Features

  • Authenticate through SAML identity provider in web browser
  • Select among retrieved AWS roles you are allowed to assume
  • Store temporary credentials in aws configuration files

How it works

This section aims to explain how awscli-saml-sso works internally. When you authenticate through awscli-saml-sso, you will follow this workflow:

  • First a web browser is opened at the given identity provider start url
  • You will authenticate with your credentials (and MFA if required)
  • If authentication succeed, you will be redirected to AWS SAML REDIRECT URL which leads to several cases: * If you belong to multiple roles, a web page let you choose which one you would like to assume * If you belong to only one role, you should be automatically redirected to AWS console authenticated through the given role * If you do not belong to any role, an error page is returned to you
  • Whatever the case, your browser should close automatically and awscli-saml-sso will report the SAML authentication result to you. * Given the case, you should need to choose a role to assume * or the authenticate workflow stop here if you do not belong to any role
  • Finally awscli-saml-sso has automatically provided a saml profile in your aws credentials file which is authenticated through AWS STS temporary credentials which should by default expire in one hour.

What is the awscli-saml-sso secret sauce to make the work transparently for you?

At first, we choose to not make any assumption on the way your identity provider let you authenticate (how is named username/password fields, would you need to answer a challenge, required MFA step, ...). Instead we choose to open a web browser which will let you follow your regular SSO authentication workflow. This web browser is driven by selenium, awscli-saml-sso will try to detect which browser is installed on your system and required web driver is automatically downloaded for you.

When authentication workflow ended, you will be redirected to AWS SAML REDIRECT URL. Here, thanks to a proxy configured in the previously opened web browser, we are able to detect that you reach redirect url, thus we can close web browser from now on.

In the redirect HTTP request, we find a SAMLResponse attribute in body that is base64 encoded, which correspond to SAML response in XML format. You can find an example here.

The most interesting part for us is the saml:AttributeStatement block enclosed here, which should contains those attributes:

  • RoleSessionName: should correspond to your authenticated username
  • Role: list of AWS roles you belong to that you are authorized to assume
  • SessionDuration: optional attribute that can override default one hour session duration from identity provider side
<saml:AttributeStatement>
    <saml:Attribute FriendlyName="Session Duration"
                    Name="https://aws.amazon.com/SAML/Attributes/SessionDuration"
                    NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
        <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">28800
        </saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute FriendlyName="Session Name" Name="https://aws.amazon.com/SAML/Attributes/RoleSessionName"
                    NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
        <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">admin
        </saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute FriendlyName="Session Role" Name="https://aws.amazon.com/SAML/Attributes/Role"
                    NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
        <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">
            arn:aws:iam::000000000000:role/Role.User,arn:aws:iam::000000000000:saml-provider/SamlExampleProvider
        </saml:AttributeValue>
        <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">
            arn:aws:iam::000000000000:role/Role.Admin,arn:aws:iam::000000000000:saml-provider/SamlExampleProvider
        </saml:AttributeValue>
    </saml:Attribute>
</saml:AttributeStatement>

In our case, we will parse SAML Role Attribute to print to user the list of AWS roles it is allowed to assume. Each role is in the form of <aws_role_arn>,<aws_identity_provider_arn>, for instance: arn:aws:iam::000000000000:role/Role.User,arn:aws:iam::000000000000:saml-provider/SamlExampleProvider.

Finally we call aws assume_role_with_saml through boto3 python sdk which expect the following arguments:

  • role_arn: the aws_role_arn retrieved previously
  • principal_arn: the aws_identity_provider_arn retrieved previously
  • saml_assertion: the base64 encoded saml response retrieved previously

AWS STS response will be retrieved and stored in a saml profile configured this way:

config.set("saml", "aws_access_key_id", sts_response["Credentials"]["AccessKeyId"])
config.set("saml", "aws_secret_access_key", sts_response["Credentials"]["SecretAccessKey"])
config.set("saml", "aws_session_token", sts_response["Credentials"]["SessionToken"])
config.set("saml", "aws_security_token", sts_response["Credentials"]["SessionToken"])

Note that you can call assume-role-with-saml directly from awscli this way:

awslocal sts assume-role-with-saml \
    --role-arn arn:aws:iam::000000000000:role/Role.Admin \
    --principal-arn arn:aws:iam::000000000000:saml-provider/SamlExampleProvider \
    --saml-assertion $(cat docs/examples/keycloak_saml_response.xml | base64)

... which should give you response like:

{
    "Credentials": {
        "AccessKeyId": "ASIA...",
        "SecretAccessKey": "...",
        "SessionToken": "FQoGZXIvYXdzEBYaDwL8pPz/cNvhUKkibZTashetWcPahlTMbaBUvDwXxjiehDkRQGYYUQrTrMdv7+6SinGiDNBiB7ZKEoyfDja6vhHwnBP2UcY/XozN+MFFPGEMhHcsUqPApwOErN37uHAM5kIOukhGlNmIPvPVWZtDoWryAuygKbqZTWwKecCwtURG2I0KF8MpS+s6SaG6EOUl5OJf/mJJQvH725q2VOWUk7HBezFCIXO+t3L8SzMygdt2FNzwUenhazYvDs2ngSlsbFbAaeeMHikZrWgTs6GkUv1uyAknpTRnInmwBDHb7SZAqpDmc7Q9+b+NXTcO1qzx/eMarHHlFQyeEEI3BEc=",
        "Expiration": "2020-12-06T18:54:38.114Z"
    },
    "AssumedRoleUser": {
        "AssumedRoleId": "AROA3X42LBCD9KGW7O43L:benjamin.brabant",
        "Arn": "arn:aws:sts::123456789012:assumed-role/Role.Admin/benjamin.brabant"
    },
    "Subject": "AROA3X42LBCD9KGW7O43L:benjamin.brabant",
    "SubjectType": "persistent",
    "Issuer": "http://localhost:3000/",
    "Audience": "https://signin.aws.amazon.com/saml",
    "NameQualifier": "B64EncodedStringOfHashOfIssuerAccountIdAndUserId="
}

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. Please make sure to update tests as appropriate. For further information, please read CONTRIBUTING document.

Development

If you would like to setup awscli-saml-sso for local development, please read the following section. Before beginning, ensure to comply with requirements defined in Requirements section.

You should create a python virtual environment:

virtualenv -p python3 .venv
# OR
python3 -m venv .venv

# THEN
source .venv/bin/activate

You can figure out useful development requirements in requirements_dev.txt and install them:

pip install -r requirements_dev.txt

Then install a local editable version of awscli-saml-sso project with pip. Under the hood, the following command will create an awscli-saml-sso.egg-link file in .venv/lib/python3.8/site-packages/ directory which contains a path pointing to your current awscli-saml-sso project directory.

# from awscli-saml-sso project root
pip install -e .

Thus you will be able to use development version of awscli_saml_sso cli. Please check that this command correctly link to your local virtual environment:

which awscli_saml_sso
> /path/to/your/project/directory/.venv/bin/awscli_saml_sso

To ensure that awscli_saml_sso work properly, you will need:

  • A configured SAML identity provider
  • An access to AWS account

To prevent having to manually setup these requirements, you will find a ready to use local setup configured through docker-compose.yml. This configuration will setup the following environment:

  • An instance of localstack which aims to replicate AWS services locally
  • A configured keycloak server, which will act as your identity provider
  • A postgresql instance as a database backend required for keycloak server

To setup this environment, just execute the following command:

docker-compose up -d

After waiting few minutes, complete environment should be up and running. You can run awscli-saml-sso this way to target localstack services endpoint instead of AWS default ones:

awscli_saml_sso --endpoint-url=http://localhost:4566 --use-browser --show-browser
# OR
ASS_ENDPOINT_URL=http://localhost:4566 awscli_saml_sso --use-browser --show-browser

Then create a new IDP by enetering +, providing a name such as LocalStack and url http://localhost:8080/auth/realms/master/protocol/saml/clients/amazon-aws

Local run

Once the Keycloack page is displayed on Edge browser, you can use one of these credentials :

Username Password Behavior
aws_user aws_user You should get credentials for Role.User
aws_admin aws_admin You should be able to choose between two AWS roles : Role.User and Role.Admin
aws_void aws_void You should get no credentials because the account is not associated to any role

Localstack

The provided localstack instance setup a local server on port 4566 that can be used as an AWS backend for required services. You can override the local exposed port by defining LOCALSTACK_EXPOSED_PORT environment variable.

You can interact with localstack this way, for instance to list existing buckets:

AWS_ACCESS_KEY_ID='_not_needed_locally_' AWS_SECRET_ACCESS_KEY='_not_needed_locally_' aws --endpoint-url=http://localhost:4566 s3 ls

To ease local usage, you can leverage awslocal cli which is configured properly to rely on localstack backend:

awslocal s3 ls

Warning

note the awslocal command will only target default 4566 port, please stick to first method if overriding exposed port

On container startup, localstack will automatically execute localstack-setup.sh script which will provision default resources:

  • An AWS S3 bucket named example-bucket
  • An AWS SAML provider named SamlExampleProvider
  • AWS roles named Role.User and Role.Admin which would be assumed by SSO users after authentication

Keycloak

The provided keycloak instance setup a local server on port 8080 that can be used as an identity provider backend. You can override the local exposed port by defining KEYCLOAK_EXPOSED_PORT environment variable.

Keycloak expose a web interface that can be accessed at http://localhost:8080.

Keycloak Welcome Page

You can authenticate to keycloak administration console with following credentials:

  • username: admin
  • password: admin

On container startup, keycloak will automatically import master-realm-with-users.json configuration which will provision default resources:

  • An urn:amazon:webservices client aims to register AWS as a SAML service provider
  • Role mapping has been properly defined with default provided users and groups.

Following users has been defined:

  • AWS ADMIN * username: aws_admin * password: aws_admin * groups: AWS_ADMINS, AWS_USERS
  • AWS USER * username: aws_user * password: aws_user * groups: AWS_USERS
  • AWS VOID * username: aws_void * password: aws_void * groups: N/A (not attached to any group)

Thus you can now use the following url as your identity provider url when asked by awscli-saml-sso: http://localhost:8080/auth/realms/master/protocol/saml/clients/amazon-aws

Please feel free to update keycloak configuration from administration console to fulfil your needs. If you think that your configuration should be setup by default, you can export it this way, replace master-realm-with-users.json content then submit your pull request :)

docker-compose run --rm -v $(pwd)/export:/tmp/export keycloak -Djboss.socket.binding.port-offset=100 -Dkeycloak.migration.action=export -Dkeycloak.migration.provider=singleFile -Dkeycloak.migration.file=/tmp/export/master-realm-with-users.json

> [...]
> 13:21:15,119 INFO  [org.keycloak.services] (ServerService Thread Pool -- 67) KC-SERVICES0033: Full model export requested
> 13:21:15,925 INFO  [org.keycloak.services] (ServerService Thread Pool -- 67) KC-SERVICES0035: Export finished successfully
> 13:21:15,119 INFO  [org.keycloak.exportimport.singlefile.SingleFileExportProvider] (ServerService Thread Pool -- 67) Exporting model into file /tmp/export/master-realm-with-users.json
> [...]

When you read above logs, you can hit CTRL+C to stop running instance. You will find a master-realm-with-users.json file in export directory created in your current path.

Credits

AWS - How to Implement Federated API and CLI Access Using SAML 2.0 and AD FS AWS SAML based User Federation using Keycloak

License

awscli_saml_sso is open source software released under the GNU GPLv3.

About

awscli_saml_sso is a command line tool that aims to get temporary credentials from SAML identity provider in order to authenticate to awscli.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Python 93.1%
  • Makefile 2.9%
  • Shell 2.8%
  • HTML 1.2%