Authenticating as a user using X-Auth-From header

168 views
Skip to first unread message

Russ

unread,
Oct 4, 2016, 11:04:43 PM10/4/16
to confidant-users
I've been trying to get the feature going described here:

https://lyft.github.io/confidant/basics/configuration/#kms-authentication-for-end-users

The point I get to in the code is here:

https://github.com/lyft/confidant/blob/1.1/confidant/keymanager.py#L156

From reading the source code it appears the the token generated must have a from_context that is the username.

The key I am using is a plain key, with development-confidant Profile/Role having use access.

Debug output shows the initial encrypt action succeeding from the client. The failure is when confidant decrypts.

My X-Auth-From is constructed like this:

x_auth_from = '4/user/ru...@example.com.au'
from_context = 'ru...@example.com.au'
to_context = 'development-confidant'

Which seems to match what is expected here for determining user name:

https://github.com/lyft/confidant/blob/1.1/confidant/authnz/__init__.py#L87-L101

And here, For setting the from context as the username:

https://github.com/lyft/confidant/blob/1.1/confidant/authnz/__init__.py#L104-L124

My requests have the following headers:

auth_headers = {
'X-Auth-Token': token,
'X-Auth-From': x_auth_from,
}


The headers get through fine, As Confidant is aware of the user I am trying to auth as.

Oct  5 02:49:02 ip-10-99-1-65 gunicorn[24646]: File "/opt/confidant/confidant/authnz/__init__.py", line 176, in decorated
Oct  5 02:49:02 ip-10-99-1-65 gunicorn[24646]: kms_auth_data['token']
Oct  5 02:49:02 ip-10-99-1-65 gunicorn[24646]: File "/opt/confidant/confidant/keymanager.py", line 158, in decrypt_token
Oct  5 02:49:02 ip-10-99-1-65 gunicorn[24646]: raise TokenDecryptionError('Authentication error. General error.')
Oct  5 02:49:02 ip-10-99-1-65 gunicorn[24646]: TokenDecryptionError: Authentication error. General error.
Oct  5 02:49:02 ip-10-99-1-65 gunicorn[24646]: WARNING:root:Access denied for ru...@example.com.au. Authentication Failed.

Following the trace back everything looks fine except I get a general decryption error.

I set the token version 4 as debug output shows KMS encrypt using that version.

My environment is set as:

Environment="USER_AUTH_KEY=arn:aws:kms:ap-southeast-2:111111111111:key/1111111111111111111111111"
Environment="KMS_MAXIMUM_TOKEN_VERSION=4"
Environment="KMS_MINIMUM_TOKEN_VERSION=1"
Environment="KMS_AUTH_USER_TYPES=user,service" # This is parsing correctly, adding quotes break "user" as an auth option.

Any suggestions?

Ryan Lane

unread,
Oct 5, 2016, 6:23:09 PM10/5/16
to Russ, confidant-users
Have you taken a look at the python confidant client (https://github.com/lyft/python-confidant-client)? Both the client and server use the python-kmsauth library for this (https://github.com/lyft/python-kmsauth).

Right now the maximum token version is 2:


I should really write up a proper spec for kmsauth in the repo for the different versions. You want version 2, though. The username (X-Auth-From) format is:

<token_version>/<user_type>/<username>

So, for users:

2/user/rlane

For services:

2/service/example-production-iad

Something to note about the username here is that based on the docs, this is your user's aws username:


which is ${aws:username} in IAM policy.

The token also doesn't have a good spec, but it's a base64 encoded encrypted payload from a KMS encrypt action that contains a timestamp payload; here's the code for that in python-kmsauth:

https://github.com/lyft/python-kmsauth/blob/master/kmsauth/__init__.py#L424-L465

Let me know if you need any more info :)

- Ryan

--
You received this message because you are subscribed to the Google Groups "confidant-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to confidant-users+unsubscribe@googlegroups.com.
To post to this group, send email to confidant-users@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/confidant-users/0da3417d-3e76-4287-ac7f-43403533f7ba%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Russ

unread,
Oct 5, 2016, 7:05:42 PM10/5/16
to confidant-users, ru...@graphenicsystems.com.au
AWS users not Confidant user! That explains everything.

I'm getting an encrypt error however trying to use that policy. Policy simulator shows it failing using all the same params on the correct key ARN.

Re attempting simulator with the use as admin succeeds. Not sure why as the policy looks correct.

# Error
2016-10-05 15:47:45,416:  (parse)  Response body:
b'{"__type":"AccessDeniedException","Message":"User: arn:aws:iam::007205783477:user/confidant-admin is not authorized to perform: kms:Encrypt on resource: arn:aws:kms:ap-southeast-2:1111111111111:key/df312c0c-9bdb-4148-a064-7c1888379d00"}'

Re attempting with Admin access on the user ,Indicates key decryption failure by Confidant:

Oct  5 23:00:40 development-confidant gunicorn[25490]: ClientError: An error occurred (InvalidCiphertextException) when calling the Decrypt operation:
Oct  5 23:00:40 development-confidant gunicorn[25490]: ERROR:root:Failed to decrypt authentication token.
Oct  5 23:00:40 development-confidant gunicorn[25490]: Traceback (most recent call last):
Oct  5 23:00:40 development-confidant gunicorn[25490]: File "/opt/confidant/confidant/authnz/__init__.py", line 176, in decorated
Oct  5 23:00:40 development-confidant gunicorn[25490]: kms_auth_data['token']
Oct  5 23:00:40 development-confidant gunicorn[25490]: File "/opt/confidant/confidant/keymanager.py", line 158, in decrypt_token
Oct  5 23:00:40 development-confidant gunicorn[25490]: raise TokenDecryptionError('Authentication error. General error.')
Oct  5 23:00:40 development-confidant gunicorn[25490]: TokenDecryptionError: Authentication error. General error.
Oct  5 23:00:40 development-confidant gunicorn[25490]: WARNING:root:Access denied for confidant-admin. Authentication Failed.


I did notice that Boto keeps vaguely referring to version 4 during debug output, I cant see any way to force a particular token version in Boto:

2016-10-05 16:00:40,593:  (add_auth)  Calculating signature using v4 auth.
2016-10-05 16:00:40,593:  (add_auth)  CanonicalRequest:
POST
/

content-type:application/x-amz-json-1.1
host:kms.ap-southeast-2.amazonaws.com
x-amz-date:20161005T230040Z
x-amz-target:TrentService.Encrypt

content-type;host;x-amz-date;x-amz-target
8ad562d2a314223cee12c40114ff52b16ce3123c3c5a946bec554f35b9542f57
2016-10-05 16:00:40,593:  (add_auth)  StringToSign:
AWS4-HMAC-SHA256
20161005T230040Z
20161005/ap-southeast-2/kms/aws4_request

# confidant-admin attached policy

{
    "Version": "2012-10-17",
    "Statement": [{
        "Action": [
            "kms:GenerateRandom"
        ],
        "Effect": "Allow",
        "Resource": "*"
    }, {
        "Action": [
            "kms:Encrypt"
        ],
        "Effect": "Allow",
        "Resource": [
            "arn:aws:kms:ap-southeast-2:1111111111:key/df312c0c-9bdb-4148-a064-7c1888379d00"
        ],
        "Condition": {
            "StringEquals": {
                "kms:EncryptionContext:to": "development-confidant",
                "kms:EncryptionContext:user_type": "user",
                "kms:EncryptionContext:from": "confidant-admin"
            },
            "Bool": {
                "aws:MultiFactorAuthPresent": "false"
            }
        }
    }, {
        "Action": [
            "kms:Encrypt"
        ],
        "Effect": "Allow",
        "Resource": [
            "arn:aws:kms:ap-southeast-2:111111111111:key/df312c0c-9bdb-4148-a064-7c1888379d00"
        ],
        "Condition": {
            "StringLike": {
                "kms:EncryptionContext:to": "*"
            },
            "StringEquals": {
                "kms:EncryptionContext:user_type": "user",
                "kms:EncryptionContext:from": "confidant-admin"
            },
            "Bool": {
                "aws:MultiFactorAuthPresent": "false"
            }
        }
    }]
}


I'm going to retry using the actual auth key rather then the USER_AUTH_KEY.
To unsubscribe from this group and stop receiving emails from it, send an email to confidant-use...@googlegroups.com.
To post to this group, send email to confida...@googlegroups.com.

Ryan Lane

unread,
Oct 5, 2016, 7:18:11 PM10/5/16
to Russ, confidant-users
Ah, yeah. KMS auth is only peripherally related to IAM authentication. It's its own spec and implementation. It builds on top of IAM authentication. So the token version and such for KMS auth is different than IAM auth.

Are you using the confidant client to generate the username and token? If not, how are you generating the token?

To unsubscribe from this group and stop receiving emails from it, send an email to confidant-users+unsubscribe@googlegroups.com.
To post to this group, send email to confidant-users@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/confidant-users/a883058b-922b-4019-8dc1-21bd7400e90d%40googlegroups.com.

Russ

unread,
Oct 5, 2016, 7:53:37 PM10/5/16
to confidant-users, ru...@graphenicsystems.com.au
I'm using the original confidant_client.py pulled apart slightly, I could try using the newer version. I didn't have to upgrade my client in another language for service auth which used the original python client as a guide.

# Debug output from my script

Payload:
{"not_after": "20161006T155556Z", "not_before": "20161005T231656Z"}

# Ive tried using a token bytes.decoded or as bytes with the b'.....'  string representation on output
bytes.decoded() token:
AQECAHgUHoj+y8j6rD9CVdy3hd1XM2eAQ1Qbk53awkcoE7EfugAAAKUwgaIGCSqGSIb3DQEHBqCBlDCBkQIBADCBiwYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAxstEGchQ60R3k3MdkCARCAXlMsAJGQFl61oAMN+zn49AJz/U3jPPuUwGmP1UAwxZhFyprigq0/2Uz2D7iMVwr+JqbKIawNmvbAz7sIjbKglDVkfceUomqedJDy/5XeTnW2ew1O82ZzkhKSBudyBaQ=


def get_token(from_context, to_context, auth_key, token_lifetime=999):
    '''
    Read secret data from Confidant via its API.
    '''
    # Return a dict, always with an attribute that specifies whether or not the
    # function was able to successfully get a result.
    ret = {'result': False}
    # Populate the auth encryption context dict that'll be used for KMS.
    auth_context = {
        'from': from_context,
        'to': to_context
    }
    # Specify the standard time format for dates required by Confidant.
    time_format = "%Y%m%dT%H%M%SZ"
    # Generate string formatted timestamps for not_before and not_after, for
    # the lifetime specified in minutes.
    now = datetime.datetime.utcnow()
    not_before = now.strftime(time_format)
    _not_after = now + datetime.timedelta(minutes=token_lifetime)
    not_after = _not_after.strftime(time_format)
    # Generate a json string for the encryption payload contents.
    payload = json.dumps({
        'not_before': not_before,
        'not_after': not_after
    })
    print("Payload:")
    print(payload)
    try:
        kms = boto3.client('kms')
    except Exception:
        logging.exception('Failed to connect to KMS.')
        return ret
    try:
        # Generate a base64 encoded KMS encrypted token to use for
        # authentication. We encrypt the token lifetime information as the
        # payload for verification in Confidant.
        token = kms.encrypt(
            KeyId=auth_key,
            Plaintext=payload,
            EncryptionContext=auth_context
        )['CiphertextBlob']
        token = base64.b64encode(token)
        return token
    except:
        logging.exception('Failed to generate token.')


# The params for my request + get_token call

def confidant_request(req_type, endpoint, postdata={}):
    x_auth_from = '2/user/confidant-admin'
    from_context = 'confidant-admin'
    to_context = 'development-confidant'
    auth_key = 'arn:aws:kms:ap-southeast-2:111111111111111:key/df312c0c-9bdb-4148-a064-7c1888379d00'
    token = get_token(from_context, to_context, auth_key,)

    auth_headers = {
        'X-Auth-Token': token,
        'X-Auth-From': x_auth_from,
    }

    print("Post data:")
    print(postdata)
    print("Token:")
    print(token)
    print("Auth headers:")
    print(auth_headers)

    try:
        elif req_type == 'get':
            response = requests.get(
                '{url}/v1/{endpoint}'.format(url=options.url, endpoint=endpoint),
                allow_redirects=False,
                timeout=10,
                headers=auth_headers,
                verify=ca_bundle,
            )


# Output from my client , Looks to be generating correct headers. (This is with admin on the user to force ability to encrypt as the policy did not enable it )

Token:
b'AQECAHgUHoj+y8j6rD9CVdy3hd1XM2eAQ1Qbk53awkcoE7EfugAAAKUwgaIGCSqGSIb3DQEHBqCBlDCBkQIBADCBiwYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAx3l8clv6ZbE7tqjzwCARCAXk21wqt1t0mul47QU5eiXTj5Z3YkzHNwvbaP9vdQakiSutDOKdaaFm3roKUy6HGre9MWRpMgGGod0EqeNMdU28FoNNmslqk4IrwNobJs23NHribEKi1rdgEu4YshVKM='
Auth headers:
{'X-Auth-Token': b'AQECAHgUHoj+y8j6rD9CVdy3hd1XM2eAQ1Qbk53awkcoE7EfugAAAKUwgaIGCSqGSIb3DQEHBqCBlDCBkQIBADCBiwYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAx3l8clv6ZbE7tqjzwCARCAXk21wqt1t0mul47QU5eiXTj5Z3YkzHNwvbaP9vdQakiSutDOKdaaFm3roKUy6HGre9MWRpMgGGod0EqeNMdU28FoNNmslqk4IrwNobJs23NHribEKi1rdgEu4YshVKM=', 'X-Auth-From': '2/user/confidant-admin'}
2016-10-05 16:38:26,074 :  (handle_errors)  Token expired or unauthorized. #response to a 403

# Confidant logs

Oct  5 23:38:26 development-confidant gunicorn[25490]: Traceback (most recent call last):
Oct  5 23:38:26 development-confidant gunicorn[25490]: File "/opt/confidant/confidant/keymanager.py", line 128, in decrypt_token
Oct  5 23:38:26 development-confidant gunicorn[25490]: EncryptionContext=context
Oct  5 23:38:26 development-confidant gunicorn[25490]: File "/opt/confidant/venv/local/lib/python2.7/site-packages/botocore/client.py", line 301, in _api_call
Oct  5 23:38:26 development-confidant gunicorn[25490]: return self._make_api_call(operation_name, kwargs)
Oct  5 23:38:26 development-confidant gunicorn[25490]: File "/opt/confidant/venv/local/lib/python2.7/site-packages/botocore/client.py", line 398, in _make_api_call
Oct  5 23:38:26 development-confidant gunicorn[25490]: raise ClientError(parsed_response, operation_name)
Oct  5 23:38:26 development-confidant gunicorn[25490]: ClientError: An error occurred (InvalidCiphertextException) when calling the Decrypt operation:
Oct  5 23:38:26 development-confidant gunicorn[25490]: ERROR:root:Failed to decrypt authentication token.
Oct  5 23:38:26 development-confidant gunicorn[25490]: Traceback (most recent call last):
Oct  5 23:38:26 development-confidant gunicorn[25490]: File "/opt/confidant/confidant/authnz/__init__.py", line 176, in decorated
Oct  5 23:38:26 development-confidant gunicorn[25490]: kms_auth_data['token']
Oct  5 23:38:26 development-confidant gunicorn[25490]: File "/opt/confidant/confidant/keymanager.py", line 158, in decrypt_token
Oct  5 23:38:26 development-confidant gunicorn[25490]: raise TokenDecryptionError('Authentication error. General error.')
Oct  5 23:38:26 development-confidant gunicorn[25490]: TokenDecryptionError: Authentication error. General error.


# Heres my actual HTTP requests being logged by Confidant.

 - - [05/Oct/2016:16:12:31 -0700] "GET /v1/services/dev-elk HTTP/1.0" 403 234 "-" "python-requests/2.11.1"
 - - [05/Oct/2016:16:15:08 -0700] "GET /v1/services/dev-application HTTP/1.0" 403 234 "-" "python-requests/2.11.1"
 - - [05/Oct/2016:16:16:56 -0700] "GET /v1/services/dev-application HTTP/1.0" 403 234 "-" "python-requests/2.11.1"
 - - [05/Oct/2016:16:33:59 -0700] "GET /v1/services/dev-elk HTTP/1.0" 403 234 "-" "python-requests/2.11.1"
 - - [05/Oct/2016:16:35:48 -0700] "GET /v1/services/dev-application HTTP/1.0" 403 234 "-" "python-requests/2.11.1"
 - - [05/Oct/2016:16:38:26 -0700] "GET /v1/services/dev-application HTTP/1.0" 403 234 "-" "python-requests/2.11.1"


 I think it might be worth me adding a debug line to output what key confidant is trying to decrypt with.

# My confidant env related settings (others are omitted)

Environment="AUTH_CONTEXT=development-confidant"
Environment="USER_AUTH_KEY=arn:aws:kms:ap-southeast-2:111111111111111:key/df312c0c-9bdb-4148-a064-7c1888379d00"
Environment="KMS_MAXIMUM_TOKEN_VERSION=2"
Environment="KMS_MINIMUM_TOKEN_VERSION=1"
Environment="KMS_AUTH_USER_TYPES=user,service"

Regards,

Russ


On Thursday, October 6, 2016 at 10:18:11 A

Ryan Lane

unread,
Oct 5, 2016, 7:59:56 PM10/5/16
to Russ, confidant-users
One of the things that changed in v2 tokens is that we added an extra field to the auth_context, which is:

user_type: '<user_type>'

For users:

user_type: 'user'

For services:

user_type 'service'

If the encryption context doesn't match on encrypt/decrypt or doesn't match the IAM policy, you'll get an encrypt or decrypt error.

- Ryan

To unsubscribe from this group and stop receiving emails from it, send an email to confidant-users+unsubscribe@googlegroups.com.
To post to this group, send email to confidant-users@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/confidant-users/53d0a87c-f5a8-4b57-8b7d-9faa8ac8c575%40googlegroups.com.
Message has been deleted

Russ

unread,
Oct 18, 2016, 4:40:50 AM10/18/16
to confidant-users, ru...@graphenicsystems.com.au
Apologies for not getting around to a response sooner, Changing the token context to include user_type '(service|user)' fixed the issue as you described.

Regards,

Russ

Ryan Lane

unread,
Oct 18, 2016, 4:38:55 PM10/18/16
to Russ, confidant-users
That's great Russ! I'm going to open an issue in github about being able to use key ARNs in addition to just using aliases. It's a bit odd to only use aliases. I think the reason we use aliases is to be able to more easily tie a key down to a particular account, but I think we can work around that with key ARNs by looking up the aliases.

- Ryan

To unsubscribe from this group and stop receiving emails from it, send an email to confidant-users+unsubscribe@googlegroups.com.
To post to this group, send email to confidant-users@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/confidant-users/51609492-59e1-432a-853f-292aea4cd85c%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages