Repository: venth/aws-adfs

Issue:
integration to aws credential_process
Hi, 

I am trying to integrate to AWS CodeCommit. Whenever the user performs a git pull, it would authenticate with AD FS and STS and use the token to access CodeCommit. I would seek your help on this implementation.

Unfortunately, I have tried a few commands but i could not get it working

This is the configuration i have set inside my .aws/config file. I have also tried without --stdout but to no avail

[profile adfs]
credential_process = aws-adfs login --adfs-host adfs.host.com --provider-id urn:amazon:webservices --role-arn arn:aws:iam::123456789098:role/AWS_ROLE --no-ssl-verification --profile adfs --region eu-central-1 --session-duration 3600 --stdout

.gitconfig:
	helper = !aws --profile adfs codecommit credential-helper $@

Discussion:
mikereinhold: @pdecat - Glad you like the idea!

I've accepted the suggestions you've made above - let me know if there's anything else you see. 

I'll take a look at writing some tests to make sure the output matches the documentation
mikereinhold: @pdecat - Ok, so I improved the expiration by using the value from the AWS session response and reverted the unnecessary datetime changes.

Also through a few unit tests out there to validate that the required values are making it into the json output without being mangled. 
mikereinhold: > Also, the build is failing for most python versions except 3.8-dev: https://travis-ci.org/github/venth/aws-adfs/builds/703676732

It looks like the failures on the older python versions was a combination of building the json via string formatting and how I was grabbing the output via the mock. 

Switched building the json to json.dumps against an object literal and captured the output from click.echo - now tests are passing across all versions

Patch:
diff --git a/README.md b/README.md
index 38c3e7f..8e24617 100644
--- a/README.md
+++ b/README.md
@@ -166,6 +166,13 @@ aws-adfs integrates with:
     password = your_password
     ```
 
+* .aws/config profile for automatically refreshing credentials
+    ```
+    [profile example-role-ue1]
+    credential_process=aws-adfs login --region=us-east-1 --role-arn=arn:aws:iam::1234567891234:role/example-role --adfs-host=adfs.example.com --stdout
+    ```
+    Warning: see [AWS documentation](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sourcing-external.html) about security considerations to take when sourcing credentials with an external process.
+
 * help, help, help?
     ```
     $ aws-adfs --help
diff --git a/aws_adfs/login.py b/aws_adfs/login.py
index 9176b82..7a8302c 100644
--- a/aws_adfs/login.py
+++ b/aws_adfs/login.py
@@ -9,6 +9,7 @@ from os import environ
 import logging
 from platform import system
 import sys
+import json
 from . import authenticator
 from . import prepare
 from . import role_chooser
@@ -232,13 +233,13 @@ def login(
 
 
 def _emit_json(aws_session_token):
-    click.echo(
-        u"""{{"AccessKeyId": "{}", "SecretAccessKey": "{}", "SessionToken": "{}"}}""".format(
-            aws_session_token['Credentials']['AccessKeyId'],
-            aws_session_token['Credentials']['SecretAccessKey'],
-            aws_session_token['Credentials']['SessionToken']
-        )
-    )
+    click.echo(json.dumps({
+        "Version": 1,
+        "AccessKeyId": aws_session_token['Credentials']['AccessKeyId'],
+        "SecretAccessKey": aws_session_token['Credentials']['SecretAccessKey'],
+        "SessionToken": aws_session_token['Credentials']['SessionToken'],
+        "Expiration": aws_session_token['Credentials']['Expiration'].isoformat()
+    }))
 
 def _print_environment_variables(aws_session_token,config):
     envcommand = "export"


Test patch:
diff --git a/test/test_credential_process_json.py b/test/test_credential_process_json.py
new file mode 100644
index 0000000..3d34b90
--- /dev/null
+++ b/test/test_credential_process_json.py
@@ -0,0 +1,41 @@
+import datetime
+import json
+from aws_adfs import login
+from mock import patch
+
+
+class TestCredentialProcessJson:
+
+    def setup_method(self, method):
+        self.access_key = 'AKIAIOSFODNN7EXAMPLE'
+        self.secret_key = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY'
+        self.session_token = 'AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQWLWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGdQrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz+scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA=='
+        self.expiration = datetime.datetime(2020,6,20)
+
+        self.aws_session_token = {
+            'Credentials': {
+                'AccessKeyId': self.access_key,
+                'SecretAccessKey': self.secret_key,
+                'SessionToken': self.session_token,
+                'Expiration': self.expiration
+            }
+        }
+
+    capture = ''
+    def _replace_echo(self, value):
+        self.capture = value
+
+    def test_json_is_valid_credential_process_format(self):
+        with patch('click.echo', side_effect = self._replace_echo):
+            login._emit_json(self.aws_session_token)
+
+            result = json.loads(self.capture)
+            print(result)
+
+            # Version is currently hardlocked at 1, see https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sourcing-external.html
+            assert result["Version"] == 1
+            assert result["AccessKeyId"] == self.access_key
+            assert result["SecretAccessKey"] == self.secret_key
+            assert result["SessionToken"] == self.session_token
+            # Expiration must be ISO8601, see https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sourcing-external.html
+            assert result["Expiration"] == self.expiration.isoformat()