Skip to content

Commit

Permalink
0.11.0
Browse files Browse the repository at this point in the history
Allow assume-role without session, various fixes, updated documentation
  • Loading branch information
meeuw committed Mar 20, 2021
1 parent a45dc60 commit 3a8b65f
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 90 deletions.
43 changes: 30 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,23 @@ Usage: aws-credential-process [OPTIONS]
Options:
--access-key-id TEXT
--secret-access-key TEXT
--mfa-oath-slot TEXT
--mfa-serial-number TEXT
--mfa-session-duration INTEGER
--mfa-oath-slot TEXT how the MFA slot is named, check using ykman
oath code
--mfa-serial-number TEXT MFA serial number, see IAM console
--mfa-session-duration INTEGER duration in seconds, use zero to assume role
without session
--assume-session-duration INTEGER
--assume-role-arn TEXT
duration in seconds
--assume-role-arn TEXT IAM Role to be assumed, optional
--force-renew
--credentials-section TEXT
--pin-entry TEXT
--credentials-section TEXT Use this section from ~/.aws/credentials
--pin-entry TEXT pin-entry helper, should be compatible with
Assuan protocol (GPG)
--log-file TEXT
--config-section TEXT
--config-section TEXT Use this section in config-file
--config-file TEXT
--help Show this message and exit.
```
Expand All @@ -59,12 +66,6 @@ aws-credential-process is meant to be used as `credential_process` in your
credential_process = /home/user/venv/aws_credential_process/bin/aws-credential-process --mfa-oath-slot "Amazon Web Services:[email protected]" --mfa-serial-number arn:aws:iam::123456789012:mfa/john.doe --assume-role-arn arn:aws:iam::123456789012:role/YourRole
```

If you've supplied the secret-access-key once you can omit it with the next call,
it will be cached in your keyring.

When you don't supply the access-key-id it will be loaded from `~/.aws/credentials`.
You can use another section than "default" by using the credentials-section argument.

## Configuration

aws-credential-process can also use a configuration file, the default location of
Expand Down Expand Up @@ -133,3 +134,19 @@ credential_process = /home/user/venv/aws_credential_process/bin/aws-credential-p
[profile profile2]
credential_process = /home/user/venv/aws_credential_process/bin/aws-credential-process --config-section=567890123456
```

## Optional arguments

If you've supplied the secret-access-key once you can omit it with the next call,
it will be cached in your keyring.

When you don't supply the access-key-id it will be loaded from `~/.aws/credentials`.
You can use another section than "default" by using the credentials-section argument.

If you don't specify `*-session-duration` the default value from AWS will be used
(3600 seconds). When `--mfa-session-duration` is set to `0` and you use `--assume-role-arn`
a role will be assumed without using a session. Some API calls can't be made when the role
is assumed using an MFA session.

You can also omit the `--assume-role-arn`, then you can use an MFA authenticated session
using your permanent IAM credentials.
135 changes: 97 additions & 38 deletions aws_credential_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,10 @@ def get_mfa_session(
if duration_seconds is not None:
request["DurationSeconds"] = duration_seconds

request["SerialNumber"] = serial_number
request["TokenCode"] = token_code()
if serial_number:
request["SerialNumber"] = serial_number
if token_code:
request["TokenCode"] = token_code()

client = boto3.client(
"sts",
Expand Down Expand Up @@ -182,7 +184,14 @@ def get_mfa_session_cached(
return mfa_session


def get_assume_session(access_key, session, role_arn, duration_seconds=None):
def get_assume_session(
access_key,
session,
role_arn,
duration_seconds=None,
serial_number=None,
token_code=None,
):
"""
Get session for assumed role
"""
Expand All @@ -191,12 +200,25 @@ def get_assume_session(access_key, session, role_arn, duration_seconds=None):
if duration_seconds is not None:
request["DurationSeconds"] = duration_seconds

client = boto3.client(
"sts",
aws_access_key_id=session.awscred.access_key_id,
aws_secret_access_key=session.awscred.secret_access_key,
aws_session_token=session.session_token,
)
if serial_number:
request["SerialNumber"] = serial_number

if token_code:
request["TokenCode"] = token_code()

if session is None:
client = boto3.client(
"sts",
aws_access_key_id=access_key.access_key_id,
aws_secret_access_key=access_key.secret_access_key,
)
else:
client = boto3.client(
"sts",
aws_access_key_id=session.awscred.access_key_id,
aws_secret_access_key=session.awscred.secret_access_key,
aws_session_token=session.session_token,
)

response = client.assume_role(**request)

Expand All @@ -208,7 +230,9 @@ def get_assume_session(access_key, session, role_arn, duration_seconds=None):
return assume_session


def get_assume_session_cached(access_key, session, role_arn, duration_seconds):
def get_assume_session_cached(
access_key, session, role_arn, duration_seconds, serial_number=None, token_code=None
):
"""
Get session for assumed role with caching
"""
Expand All @@ -218,7 +242,7 @@ def get_assume_session_cached(access_key, session, role_arn, duration_seconds):

if assume_session is None:
assume_session = get_assume_session(
access_key, session, role_arn, duration_seconds
access_key, session, role_arn, duration_seconds, serial_number, token_code
)
return assume_session

Expand Down Expand Up @@ -302,6 +326,9 @@ def main(

access_key = AWSCred(access_key_id, secret_access_key)

if mfa_session_duration is not None:
mfa_session_duration = int(mfa_session_duration)

def token_code():
for _ in range(5):
token_code = None
Expand Down Expand Up @@ -330,12 +357,18 @@ def token_code():

return token_code

mfa_session_request = (
access_key,
mfa_session_duration,
mfa_serial_number,
token_code,
)
if mfa_session_duration == 0:
mfa_session_request = (
access_key,
mfa_session_duration,
)
else:
mfa_session_request = (
access_key,
mfa_session_duration,
mfa_serial_number,
token_code,
)

if assume_role_arn:
if force_renew:
Expand All @@ -346,25 +379,42 @@ def token_code():
)
if assume_session is None:

if force_renew:
mfa_session = get_mfa_session(*mfa_session_request)
if mfa_session_duration == 0:
mfa_session = None
else:
mfa_session = get_mfa_session_cached(*mfa_session_request)

if mfa_session is None:
logging.warning("Failed to get MFA session")
sys.exit(1)

assume_session = get_assume_session(
access_key, mfa_session, assume_role_arn, assume_session_duration
)
if force_renew:
mfa_session = get_mfa_session(*mfa_session_request)
else:
mfa_session = get_mfa_session_cached(*mfa_session_request)

if mfa_session is None:
logging.warning("Failed to get MFA session")
sys.exit(1)

if mfa_session_duration == 0:
assume_session = get_assume_session(
access_key,
mfa_session,
assume_role_arn,
assume_session_duration,
mfa_serial_number,
token_code,
)
else:
assume_session = get_assume_session(
access_key, mfa_session, assume_role_arn, assume_session_duration
)

if assume_session is None:
logging.warning("Failed to get assume session")
sys.exit(1)
else:
print(assume_session.json_credentials())
else:
if mfa_session_duration == 0:
logging.warning("Cannot do MFA without session")
sys.exit(1)

if force_renew:
mfa_session = get_mfa_session(*mfa_session_request)
else:
Expand All @@ -380,16 +430,25 @@ def token_code():
@click.command()
@click.option("--access-key-id")
@click.option("--secret-access-key")
@click.option("--mfa-oath-slot")
@click.option("--mfa-serial-number")
@click.option("--mfa-session-duration", type=int)
@click.option("--assume-session-duration", type=int)
@click.option("--assume-role-arn")
@click.option(
"--mfa-oath-slot", help="how the MFA slot is named, check using ykman oath code"
)
@click.option("--mfa-serial-number", help="MFA serial number, see IAM console")
@click.option(
"--mfa-session-duration",
type=int,
help="duration in seconds, use zero to assume role without session",
)
@click.option("--assume-session-duration", help="duration in seconds", type=int)
@click.option("--assume-role-arn", help="IAM Role to be assumed, optional")
@click.option("--force-renew", is_flag=True)
@click.option("--credentials-section")
@click.option("--pin-entry")
@click.option("--credentials-section", help="Use this section from ~/.aws/credentials")
@click.option(
"--pin-entry",
help="pin-entry helper, should be compatible with Assuan protocol (GPG)",
)
@click.option("--log-file")
@click.option("--config-section")
@click.option("--config-section", help="Use this section in config-file")
@click.option("--config-file", default="~/.config/aws-credential-process/config.toml")
def click_main(
access_key_id,
Expand Down Expand Up @@ -434,7 +493,7 @@ def click_main(
config["mfa_serial_number"] = mfa_serial_number
if mfa_oath_slot:
config["mfa_oath_slot"] = mfa_oath_slot
if mfa_session_duration:
if mfa_session_duration is not None:
config["mfa_session_duration"] = mfa_session_duration
if secret_access_key:
config["secret_access_key"] = secret_access_key
Expand All @@ -443,7 +502,7 @@ def click_main(
if assume_role_arn:
config["assume_role_arn"] = assume_role_arn
if force_renew:
config["force_renew=False"] = force_renew
config["force_renew"] = force_renew
if credentials_section:
config["credentials_section"] = credentials_section
if pin_entry:
Expand Down
Loading

0 comments on commit 3a8b65f

Please sign in to comment.