Skip to content

Commit

Permalink
Merge pull request #64 from dclayton-godaddy/secondary-role
Browse files Browse the repository at this point in the history
feat(auth): add secondary role option
  • Loading branch information
thoward-godaddy authored Aug 16, 2023
2 parents 64cb312 + b672b62 commit 96a538a
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 45 deletions.
12 changes: 4 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,11 @@ jobs:
strategy:
matrix:
platform: [ ubuntu-latest, macos-latest, windows-latest ]
python-version: [ 3.6, 3.7, 3.8, 3.9, "3.10" ]
exclude:
- platform: macos-latest
python-version: [ 3.8, 3.9, "3.10" ]
include:
- platform: ubuntu-20.04
python-version: 3.6
- platform: macos-latest
python-version: 3.7
- platform: windows-latest
python-version: 3.6
- platform: windows-latest
- platform: ubuntu-latest
python-version: 3.7
steps:
- uses: actions/checkout@v2
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,6 @@ target/
.DS_Store

# VS Code
.vscode/
.vscode/

.venv
80 changes: 46 additions & 34 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,40 +126,42 @@ Additional variables can also be passed to aws-okta-processors ``authenticate``
as options or environment variables as outlined in the table below.

============= =============== ====================== ========================================
Variable Option Environment Variable Description
============= =============== ====================== ========================================
user --user AWS_OKTA_USER Okta user name
------------- --------------- ---------------------- ----------------------------------------
password --pass AWS_OKTA_PASS Okta user password
------------- --------------- ---------------------- ----------------------------------------
organization --organization AWS_OKTA_ORGANIZATION Okta FQDN for Organization
------------- --------------- ---------------------- ----------------------------------------
application --application AWS_OKTA_APPLICATION Okta AWS application URL
------------- --------------- ---------------------- ----------------------------------------
role --role AWS_OKTA_ROLE AWS Role ARN
------------- --------------- ---------------------- ----------------------------------------
account_alias --account-alias AWS_OKTA_ACCOUNT_ALIAS AWS Account Filter
------------- --------------- ---------------------- ----------------------------------------
region --region AWS_OKTA_REGION AWS Region
------------- --------------- ---------------------- ----------------------------------------
duration --duration AWS_OKTA_DURATION Duration in seconds for AWS session
------------- --------------- ---------------------- ----------------------------------------
key --key AWS_OKTA_KEY Key used in generating AWS session cache
------------- --------------- ---------------------- ----------------------------------------
environment --environment Output command to set ENV variables
------------- --------------- ---------------------- ----------------------------------------
silent --silent Silence Info output
------------- --------------- ---------------------- ----------------------------------------
factor --factor AWS_OKTA_FACTOR MFA type. `push:okta`, `token:software:totp:okta`, `token:software:totp:google` and `token:hardware:yubico` are supported.
------------- --------------- ---------------------- ----------------------------------------
no_okta_cache --no-okta-cache AWS_OKTA_NO_OKTA_CACHE Do not read okta cache
------------- --------------- ---------------------- ----------------------------------------
no_aws_cache --no-aws-cache AWS_OKTA_NO_AWS_CACHE Do not read aws cache
------------- --------------- ---------------------- ----------------------------------------
target_shell --target-shell AWS_OKTA_TARGET_SHELL Target shell to format export command
------------- --------------- ---------------------- ----------------------------------------
sign_in_url --sign-in-url AWS_OKTA_SIGN_IN_URL AWS Sign In URL
============= =============== ====================== ========================================
Variable Option Environment Variable Description
============== ================ ======================= ========================================
user --user AWS_OKTA_USER Okta user name
-------------- ---------------- ----------------------- ----------------------------------------
password --pass AWS_OKTA_PASS Okta user password
-------------- ---------------- ----------------------- ----------------------------------------
organization --organization AWS_OKTA_ORGANIZATION Okta FQDN for Organization
-------------- ---------------- ----------------------- ----------------------------------------
application --application AWS_OKTA_APPLICATION Okta AWS application URL
-------------- ---------------- ----------------------- ----------------------------------------
role --role AWS_OKTA_ROLE AWS Role ARN
-------------- ---------------- ----------------------- ----------------------------------------
secondary_role --secondary-role AWS_OKTA_SECONDARY_ROLE Secondary AWS Role ARN
-------------- ---------------- ----------------------- ----------------------------------------
account_alias --account-alias AWS_OKTA_ACCOUNT_ALIAS AWS Account Filter
-------------- ---------------- ----------------------- ----------------------------------------
region --region AWS_OKTA_REGION AWS Region
-------------- ---------------- ----------------------- ----------------------------------------
duration --duration AWS_OKTA_DURATION Duration in seconds for AWS session
-------------- ---------------- ----------------------- ----------------------------------------
key --key AWS_OKTA_KEY Key used in generating AWS session cache
-------------- ---------------- ----------------------- ----------------------------------------
environment --environment Output command to set ENV variables
-------------- ---------------- ----------------------- ----------------------------------------
silent --silent Silence Info output
-------------- ---------------- ----------------------- ----------------------------------------
factor --factor AWS_OKTA_FACTOR MFA type. `push:okta`, `token:software:totp:okta`, `token:software:totp:google` and `token:hardware:yubico` are supported.
-------------- ---------------- ----------------------- ----------------------------------------
no_okta_cache --no-okta-cache AWS_OKTA_NO_OKTA_CACHE Do not read okta cache
-------------- ---------------- ----------------------- ----------------------------------------
no_aws_cache --no-aws-cache AWS_OKTA_NO_AWS_CACHE Do not read aws cache
-------------- ---------------- ----------------------- ----------------------------------------
target_shell --target-shell AWS_OKTA_TARGET_SHELL Target shell to format export command
-------------- ---------------- ----------------------- ----------------------------------------
sign_in_url --sign-in-url AWS_OKTA_SIGN_IN_URL AWS Sign In URL
============== ================ ======================= ========================================

^^^^^^^^
Examples
Expand Down Expand Up @@ -213,6 +215,16 @@ To clear all AWS session caches run::

$ rm ~/.aws/boto/cache/*

-------------------------
Assuming a Secondary Role
-------------------------

If you can only assume a role from another role, you can assume both roles using ``--role`` and ``--secondary-role``. Use
``--role`` to specify the first role ARN, then ``--secondary-role`` to specify the role ARN assumed from ``--role``.

Example::

aws-okta-processor authenticate --user jdoe ... --role arn:aws:iam::111111111:role/OpsUser --secondary-role arn:aws:iam::111111111:role/SecretsAdmin

-----------------------------
Project or User Configuration
Expand Down
3 changes: 3 additions & 0 deletions aws_okta_processor/commands/authenticate.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
-o <okta_organization>, --organization=<okta_organization> Okta organization domain.
-a <okta_application>, --application=<okta_application> Okta application url.
-r <role_name>, --role=<role_name> AWS role ARN.
--secondary-role <secondary_role_arn> Secondary AWS role ARN.
-R <region_name>, --region=<region_name> AWS region name.
-U <sign_in_url>, --sign-in-url=<sign_in_url> AWS Sign In URL.
[default: https://signin.aws.amazon.com/saml]
Expand Down Expand Up @@ -52,6 +53,7 @@
"--organization": "AWS_OKTA_ORGANIZATION",
"--application": "AWS_OKTA_APPLICATION",
"--role": "AWS_OKTA_ROLE",
"--secondary-role": "AWS_OKTA_SECONDARY_ROLE",
"--region": "AWS_OKTA_REGION",
"--sign-in-url": "AWS_OKTA_SIGN_IN_URL",
"--duration": "AWS_OKTA_DURATION",
Expand All @@ -71,6 +73,7 @@
"AWS_OKTA_ORGANIZATION": "organization",
"AWS_OKTA_APPLICATION": "application",
"AWS_OKTA_ROLE": "role",
"AWS_OKTA_SECONDARY_ROLE": "secondary-role",
"AWS_OKTA_REGION": "region",
"AWS_OKTA_SIGN_IN_URL": "sign_in_url",
"AWS_OKTA_DURATION": "duration",
Expand Down
22 changes: 20 additions & 2 deletions aws_okta_processor/core/fetcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def _get_credentials(self):
region_name=self._configuration["AWS_OKTA_REGION"]
)

aws_roles, saml_assertion, _application_url, _user, _organization = self._get_app_roles()
aws_roles, saml_assertion, _application_url, user, _organization = self._get_app_roles()

aws_role = prompt.get_item(
items=aws_roles,
Expand All @@ -153,9 +153,27 @@ def _get_credentials(self):
DurationSeconds=int(self._configuration["AWS_OKTA_DURATION"])
)

if self._configuration.get('AWS_OKTA_SECONDARY_ROLE', None) is not None:
role_session_name = user
secondary_role_arn = self._configuration['AWS_OKTA_SECONDARY_ROLE']

print_tty(f"Assuming secondary role {secondary_role_arn}")
credentials = response['Credentials']
client = boto3.client(
'sts',
aws_access_key_id=credentials['AccessKeyId'],
aws_secret_access_key=credentials["SecretAccessKey"],
aws_session_token=credentials["SessionToken"],
region_name=self._configuration["AWS_OKTA_REGION"],
)
response = client.assume_role(
RoleArn=secondary_role_arn,
DurationSeconds=int(self._configuration["AWS_OKTA_DURATION"]),
RoleSessionName=role_session_name,
)

expiration = (response['Credentials']['Expiration']
.isoformat().replace("+00:00", "Z"))

response['Credentials']['Expiration'] = expiration

return response
71 changes: 71 additions & 0 deletions tests/core/test_fetcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,74 @@ def assume_role_side_effect(*args, **kwargs):
call('[ 3 ] Role-One', indents=1),
call('Selection: ', newline=False)
])

@patch("boto3.client")
@patch('aws_okta_processor.core.fetcher.print_tty')
@patch('aws_okta_processor.core.fetcher.prompt.print_tty')
@patch('aws_okta_processor.core.fetcher.prompt.input_tty', return_value='1')
@patch('aws_okta_processor.core.fetcher.Okta')
def test_fetcher_should_assume_secondary_role(
self,
mock_okta,
mock_prompt,
mock_prompt_print_tty,
mock_print_tty,
mock_client
):

self.OPTIONS["--secondary-role"] = "arn:aws:iam::1:role/Role-Two"

def assume_role_saml_side_effect(*args, **kwargs):
if kwargs['RoleArn'] == 'arn:aws:iam::1:role/Role-One':
return {
'Credentials': {
'AccessKeyId': 'test-key1',
'SecretAccessKey': 'test-secret1',
'SessionToken': 'test-token1',
'Expiration': datetime(2020, 4, 17, 12, 0, 0, 0)
}
}
raise RuntimeError('invalid RoleArn')

def assume_role_side_effect(*args, **kwargs):
if kwargs['RoleArn'] == 'arn:aws:iam::1:role/Role-Two':
return {
'Credentials': {
'AccessKeyId': 'test-key2',
'SecretAccessKey': 'test-secret2',
'SessionToken': 'test-token2',
'Expiration': datetime(2020, 4, 17, 13, 0, 0, 0)
}
}
raise RuntimeError('invalid RoleArn')

self.OPTIONS["--pass"] = 'testpass'

mock_c = mock.Mock()
mock_c.assume_role_with_saml.side_effect = assume_role_saml_side_effect
mock_c.assume_role.side_effect = assume_role_side_effect
mock_okta().get_saml_response.return_value = SAML_RESPONSE
mock_client.return_value = mock_c

authenticate = Authenticate(self.OPTIONS)
fetcher = SAMLFetcher(authenticate, cache={})

creds = fetcher.fetch_credentials()
self.assertDictEqual({
'AccessKeyId': 'test-key2',
'Expiration': '2020-04-17T13:00:00',
'SecretAccessKey': 'test-secret2',
'SessionToken': 'test-token2'
}, creds)

self.assertEqual(7, mock_prompt_print_tty.call_count)

MagicMock.assert_has_calls(mock_prompt_print_tty, [
call('Select AWS Role:'),
call('Account: 1', indents=0),
call('[ 1 ] Role-One', indents=1),
call('[ 2 ] Role-Two', indents=1),
call('Account: 2', indents=0),
call('[ 3 ] Role-One', indents=1),
call('Selection: ', newline=False)
])

0 comments on commit 96a538a

Please sign in to comment.