diff --git a/README.md b/README.md index f013236..0db1003 100644 --- a/README.md +++ b/README.md @@ -16,33 +16,73 @@ To invoke the cli, there are 2 option 1. Directly use `aws-fusion` command 2. Use it via [aws cli alias](https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-alias.html) with `aws fusion` -## Commands -- [init](#usage-of-init) -- [open-browser](#usage-of-open-browser) -- store-iam-user-credentials - - [store](#usage-of-iam-user-credentials-store) - - [get](#usage-of-iam-user-credentials-get) -- [get-iam-user-credentials](#usage-of-get-iam-user-credentials) -- [generate-okta-device-auth-credentials](#usage-of-generate-okta-device-auth-credentials) -- [config-switch](#usage-of-config-switch) - - profile - - region +## Usage ---- -## Usage of `init` -> Try `aws-fusion init --help` for detailed parameter - -Initilize fusion app with creation of aws fusion alias +```commandline +usage: aws-fusion [] ... ---- -## Usage of `open-browser` -> Try `aws-fusion open-browser --help` for detailed parameter +Unified CLI tool for streamlined AWS operations, enhancing developer productivity -- Make AWS credentials available via aws profile -- Execute the script: `aws-fusion open-browser --profile my-profile` -- :tada: Your browser opens, and you are signed in into the AWS console +Flags: + -h, --help show this help message and exit + -v, --version Display the version of this tool + --debug Turn on debug logging + +Command: + init [] + Initialize fusion app with creation of aws fusion alias. + + open-browser [] [] + Open a web browser for graphical access to the AWS Console. + + -p, --profile PROFILE The AWS profile to create the pre-signed URL with + -r, --region REGION The AWS Region to send the request to + --clip Don't open the web browser, but copy the signin URL to clipboard + --stdout Don't open the web browser, but echo the signin URL to stdout + + iam-user-credentials [] + IAM User credential helper. + + iam-user-credentials get [] [] + Retrieve IAM user credentials for AWS CLI profiles or application authentication. + + --access-key ACCESS_KEY AWS access key + --account-id ACCOUNT_ID AWS Account ID for the name + --username USERNAME Username of a AWS user associated with the access key for the name + --credential-process Output the credential in AWS credential process syntax + + iam-user-credentials store [] [] + Store IAM user access key and secret key securely for streamlined authentication. + + --access-key ACCESS_KEY AWS access key + --account-id ACCOUNT_ID AWS Account ID for the name + --username USERNAME Username of a AWS user associated with the access key for the name + --secret-key SECRET_KEY AWS secret key + + okta [] + Generate AWS session credentials from Okta. + + okta device-auth [] [] + Generate AWS session credentials using SAML assertion from Okta device authentication. + + --org-domain ORG_DOMAIN Full domain hostname of the Okta org e.g. example.okta.com + --oidc-client-id OIDC_CLIENT_ID The ID is the identifier of the client is Okta app acting as the IdP for AWS + --aws-acct-fed-app-id AWS_ACCT_FED_APP_ID The ID for the AWS Account Federation integration app + --aws-iam-role AWS_IAM_ROLE The AWS IAM Role ARN to assume + --credential-process Output the credential in AWS credential process syntax + + config-switch [] + Switching between AWS config. + + config-switch profile [] + Switch between available aws profile. + + config-switch region [] + Switch between available aws region. +``` -### Use cases +--- +## Use case of `open-browser` This only works with assume-role and federated-login, doesn't work with IAM user or user session. #### IAM assume role @@ -98,12 +138,7 @@ The docs - https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html --- -## Usage of `iam-user-credentials store` -> Try `aws-fusion iam-user-credentials store --help` for detailed parameter - -Store AWS credentials in system default credential store - -### Use cases +## Usa case of `iam-user-credentials store` To store IAM user credential in the system credential store for best security rather than plain text `~/.aws/credentials` file. Manually the save the credential in the store using @@ -116,12 +151,7 @@ aws-fusion iam-user-credentials store \ ``` --- -## Usage of `iam-user-credentials get` -> Try `aws-fusion iam-user-credentials get --help` for detailed parameter - -Retrieve AWS credentials from system default credential store. Optionally plug the CLI to aws external credential process. - -### Use cases +## Use case of `iam-user-credentials get` Configure aws config file to use credential process **Config file** @@ -137,12 +167,7 @@ The docs - https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sourcing-external.html --- -## Usage of `generate-okta-device-auth-credentials` -> Try `aws-fusion generate-okta-device-auth-credentials --help` for detailed parameter - -Simplifies the process of obtaining AWS session credentials using SAML assertion from Okta device authentication - -### Use cases +## Use case of `okta device-auth` Configure aws config file to use credential process **Config file** @@ -154,7 +179,7 @@ credential_process = aws-fusion generate-okta-device-auth-credentials --org-doma ``` --- -## Usage of `config-switch` +## Use case of `config-switch` A special of utility script to help easily switch `profile` and `region` This works with 2 bash script, namely `_awsp` and `_awsr` @@ -167,6 +192,8 @@ alias awsp="source _awsp" alias awsr="source _awsr" ``` +config-switch-image + --- ## License This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details. diff --git a/aws_fusion/app.py b/aws_fusion/app.py index 3a2fa9d..3a8e03a 100644 --- a/aws_fusion/app.py +++ b/aws_fusion/app.py @@ -2,11 +2,13 @@ import logging from importlib.metadata import version -from .commands import config_switch -from .commands import generate_okta_device_auth_credentials -from .commands import iam_user_credentials -from .commands import init -from .commands import open_browser +from .commands import ( + init, + open_browser, + config_switch, + iam_user_credentials, + okta +) def main(): @@ -21,7 +23,7 @@ def main(): init, open_browser, iam_user_credentials, - generate_okta_device_auth_credentials, + okta, config_switch ] [command.setup(subparsers, global_parser) for command in commands] diff --git a/aws_fusion/commands/config_switch.py b/aws_fusion/commands/config_switch.py index ba25436..7ded0a0 100644 --- a/aws_fusion/commands/config_switch.py +++ b/aws_fusion/commands/config_switch.py @@ -9,15 +9,15 @@ def setup(subparsers, parent_parser): - summary = 'Switching between AWS config' + summary = 'Switching between AWS config.' parser = subparsers.add_parser('config-switch', description=summary, help=summary, parents=[parent_parser]) switch_subparsers = parser.add_subparsers(dest='config_switch_command', required=True, help='Available AWS config switch commands') - profile_switch_summary = "Switch between available aws profile" + profile_switch_summary = "Switch between available aws profile." profile_switch_parser = switch_subparsers.add_parser('profile', description=profile_switch_summary, help=profile_switch_summary, parents=[parent_parser]) profile_switch_parser.set_defaults(func=switch_profile) - region_switch_summary = "Switch between available aws region" + region_switch_summary = "Switch between available aws region." region_switch_parser = switch_subparsers.add_parser('region', description=region_switch_summary, help=region_switch_summary, parents=[parent_parser]) region_switch_parser.set_defaults(func=switch_region) diff --git a/aws_fusion/commands/generate_okta_device_auth_credentials.py b/aws_fusion/commands/generate_okta_device_auth_credentials.py deleted file mode 100644 index 16e61b9..0000000 --- a/aws_fusion/commands/generate_okta_device_auth_credentials.py +++ /dev/null @@ -1,40 +0,0 @@ -import logging - -from ..aws.assume_role import AssumeRoleWithSamlCache -from ..okta.api import device_auth -from ..okta.api import saml_assertion -from ..okta.api import session_and_token -from ..okta.api import verification_and_token - - -LOG = logging.getLogger(__name__) - - -def setup(subparsers, parent_parser): - summary = 'Generate AWS session credentials using SAML assertion from Okta device authentication' - parser = subparsers.add_parser('generate-okta-device-auth-credentials', description=summary, help=summary, parents=[parent_parser]) - - parser.add_argument('--org-domain', required=True, help="Full domain hostname of the Okta org e.g. example.okta.com") - parser.add_argument('--oidc-client-id', required=True, help="The ID is the identifier of the client is Okta app acting as the IdP for AWS") - parser.add_argument('--aws-acct-fed-app-id', required=True, help="ID for the AWS Account Federation integration app") - parser.add_argument('--aws-iam-role', required=True, help="The AWS IAM Role ARN to assume") - parser.add_argument('--credential-process', action='store_true', help='Output the credential in AWS credential process syntax') - - parser.set_defaults(func=run) - - -def run(args): - assume_role_with_cache = AssumeRoleWithSamlCache(args.aws_iam_role) - - if not assume_role_with_cache.does_valid_token_cache_exists(): - LOG.debug('Credential cache not found, invoking SAML') - device_code, expires_in = device_auth(args.org_domain, args.oidc_client_id) - access_token, id_token = verification_and_token(args.org_domain, args.oidc_client_id, device_code, expires_in) - session_token = session_and_token(args.org_domain, args.oidc_client_id, access_token, id_token, args.aws_acct_fed_app_id) - saml_response, roles, session_duration = saml_assertion(args.org_domain, session_token) - assume_role_with_cache.assume_role_with_saml(saml_response, roles, session_duration) - - if args.credential_process: - print(assume_role_with_cache.credential_process()) - else: - print(assume_role_with_cache.environment_variable()) diff --git a/aws_fusion/commands/iam_user_credentials.py b/aws_fusion/commands/iam_user_credentials.py index ababbf3..ddaec63 100644 --- a/aws_fusion/commands/iam_user_credentials.py +++ b/aws_fusion/commands/iam_user_credentials.py @@ -11,16 +11,16 @@ def setup(subparsers, parent_parser): common_parser.add_argument('--account-id', required=False, help='AWS Account ID for the name') common_parser.add_argument('--username', required=False, help='Username of a AWS user associated with the access key for the name') - summary = 'IAM User credential helper' + summary = 'IAM User credential helper.' parser = subparsers.add_parser('iam-user-credentials', description=summary, help=summary, parents=[parent_parser]) credential_subparsers = parser.add_subparsers(dest='iam_user_credential_command', required=True, help='Available IAM User credential commands') - store_summary = 'Store IAM user access key and secret key securely for streamlined authentication' + store_summary = 'Store IAM user access key and secret key securely for streamlined authentication.' store_parser = credential_subparsers.add_parser('store', description=store_summary, help=store_summary, parents=[parent_parser, common_parser]) store_parser.set_defaults(func=run_store) store_parser.add_argument('--secret-key', required=True, help='AWS secret key') - get_summary = 'Retrieve IAM user credentials for AWS CLI profiles or application authentication' + get_summary = 'Retrieve IAM user credentials for AWS CLI profiles or application authentication.' get_parser = credential_subparsers.add_parser('get', description=get_summary, help=get_summary, parents=[parent_parser, common_parser]) get_parser.set_defaults(func=run_get) get_parser.add_argument('--credential-process', action='store_true', help='Output the credential in AWS credential process syntax') diff --git a/aws_fusion/commands/init.py b/aws_fusion/commands/init.py index 2da4104..8ea2bf5 100644 --- a/aws_fusion/commands/init.py +++ b/aws_fusion/commands/init.py @@ -8,7 +8,7 @@ def setup(subparsers, parent_parser): - summary = 'Initialize fusion app with creation of aws fusion alias' + summary = 'Initialize fusion app with creation of aws fusion alias.' parser = subparsers.add_parser('init', description=summary, help=summary, parents=[parent_parser]) parser.set_defaults(func=run) diff --git a/aws_fusion/commands/okta.py b/aws_fusion/commands/okta.py new file mode 100644 index 0000000..bcd013c --- /dev/null +++ b/aws_fusion/commands/okta.py @@ -0,0 +1,47 @@ +import logging +from argparse import ArgumentParser + +from ..aws.assume_role import AssumeRoleWithSamlCache +from ..okta.api import device_auth +from ..okta.api import saml_assertion +from ..okta.api import session_and_token +from ..okta.api import verification_and_token + + +LOG = logging.getLogger(__name__) + + +def setup(subparsers, parent_parser): + common_parser = ArgumentParser(add_help=False) + common_parser.add_argument('--org-domain', required=True, help="Full domain hostname of the Okta org e.g. example.okta.com") + + summary = 'Generate AWS session credentials from Okta.' + parser: ArgumentParser = subparsers.add_parser('okta', description=summary, help=summary, parents=[parent_parser]) + okta_subparsers = parser.add_subparsers(dest='okta_command', required=True, help='Available Okta commands') + + device_auth_summary = 'Generate AWS session credentials using SAML assertion from Okta device authentication.' + device_auth_parser = okta_subparsers.add_parser('device-auth', description=device_auth_summary, help=device_auth_summary, parents=[parent_parser, common_parser]) + device_auth_parser.set_defaults(func=run_device_auth) + device_auth_parser.add_argument('--oidc-client-id', required=True, help="The ID is the identifier of the client is Okta app acting as the IdP for AWS") + device_auth_parser.add_argument('--aws-acct-fed-app-id', required=True, help="The ID for the AWS Account Federation integration app") + device_auth_parser.add_argument('--aws-iam-role', required=True, help="The AWS IAM Role ARN to assume") + device_auth_parser.add_argument('--credential-process', action='store_true', help='Output the credential in AWS credential process syntax') + + +def run_device_auth(args): + assume_role_with_cache = AssumeRoleWithSamlCache(args.aws_iam_role) + + if not assume_role_with_cache.does_valid_token_cache_exists(): + LOG.debug('Credential cache not found, invoking SAML') + device_code, expires_in = device_auth(args.org_domain, args.oidc_client_id) + access_token, id_token = verification_and_token(args.org_domain, args.oidc_client_id, device_code, expires_in) + session_token = session_and_token(args.org_domain, args.oidc_client_id, access_token, id_token, args.aws_acct_fed_app_id) + saml_response, roles, session_duration = saml_assertion(args.org_domain, session_token) + assume_role_with_cache.assume_role_with_saml(saml_response, roles, session_duration) + + if args.credential_process: + print(assume_role_with_cache.credential_process()) + else: + print(assume_role_with_cache.environment_variable()) + + diff --git a/aws_fusion/commands/open_browser.py b/aws_fusion/commands/open_browser.py index d220408..f96dbca 100644 --- a/aws_fusion/commands/open_browser.py +++ b/aws_fusion/commands/open_browser.py @@ -13,16 +13,16 @@ def setup(subparsers, parent_parser): - summary = 'Open a web browser for graphical access to the AWS Console' + summary = 'Open a web browser for graphical access to the AWS Console.' parser = subparsers.add_parser('open-browser', description=summary, help=summary, parents=[parent_parser]) parser.set_defaults(func=run) - parser.add_argument('-P', '--profile', default=os.getenv("AWS_PROFILE"), help="The AWS profile to create the pre-signed URL with") - parser.add_argument('-R', '--region', default=os.getenv("AWS_REGION", os.getenv("AWS_DEFAULT_REGION")), help="The AWS Region to send the request to") + parser.add_argument('-p', '--profile', default=os.getenv("AWS_PROFILE"), help="The AWS profile to create the pre-signed URL with") + parser.add_argument('-r', '--region', default=os.getenv("AWS_REGION", os.getenv("AWS_DEFAULT_REGION")), help="The AWS Region to send the request to") no_browser_group = parser.add_mutually_exclusive_group() - no_browser_group.add_argument('--clip', action='store_true', help="don't open the web browser, but copy the signin URL to clipboard") - no_browser_group.add_argument('--stdout', action='store_true', help="don't open the web browser, but echo the signin URL to stdout") + no_browser_group.add_argument('--clip', action='store_true', help="Don't open the web browser, but copy the signin URL to clipboard") + no_browser_group.add_argument('--stdout', action='store_true', help="Don't open the web browser, but echo the signin URL to stdout") def run(args): diff --git a/doc/images/config-switch.png b/doc/images/config-switch.png new file mode 100644 index 0000000..542baa5 Binary files /dev/null and b/doc/images/config-switch.png differ