cyberark / conjur

CyberArk Conjur automatically secures secrets used by privileged users and machine identities
https://conjur.org
Other
780 stars 124 forks source link

IAM Authentication #536

Closed cyberark-bizdev closed 6 years ago

cyberark-bizdev commented 6 years ago

Rationale for closing this issue

This issue has been reopened with a new, updated description as #542

Description

IAM authentication allows a host to authenticate with Conjur using headers from a signed URL request (against AWS IAM) rather than their Conjur credentials. IAM authentication works exactly as regular authentication, returning a JSON hash if the host authenticates successfully.

The idea of IAM authentication is the ability to use IAM Roles as a way to authenticate EC2 instances, Lambda Functions, etc. to Conjur by relying on AWS built-in authorization/authentication mechanism.

The signed URL request headers sent to Conjur for authentication allows Conjur to verify whether AWS recognizes the caller identity; in fact, the URL is a getCallerIdentity from AWS STS (Security Token Service.)

Workflow To enable the authenticator, the CONJUR_AUTHENTICATORS environment variable must be set on any Conjur node that may be servicing the authentication request, and it must include the string authn-iam/<authenticator-name> in its comma-separated list of allowed authenticators (for example, CONJUR_AUTHENTICATORS=authn-iam/aws,authn-iam/o365).

The authenticator webservice must be declared in Conjur policy:

---
- !policy
  id: conjur/authn-iam/aws
  body:
  - !webservice

  - !layer clients

  - !host
    id: ec2-host
    annotations:
      iam_allowed_roles: 
        - arn:aws:sts::011915987442:assumed-role/EC2-InstanceProfile

  - !permit
    role: !layer clients
    privilege: [ read, authenticate ]
    resource: !webservice

  - !grant
    role: !layer clients
    member: !host ec2-host

  - &secrets
    - !variable secret-access-key
    - !variable access-key-id
    - !variable secret

  - !permit
    resource: !variable secret
    privileges: [read, execute]
    role: !layer clients

Once the authenticator has been properly configured, a user will be able to configure their environment to use IAM authorization through the Conjur CLI client, UI, or when using Summon by setting the CONJUR_AUTHN_URL environment variable:

CONJUR_AUTHN_URL = https://conjur.company.com/authn-iam/<authenticator-name>/<service-account>/<hostname>/authenticate

To successfully use the IAM authenticator to authenticate to Conjur (if entitled to do so in Conjur policy), a host (instance) needs their Conjur hostname to pass the following criteria:

kgilpin commented 6 years ago

What I need to understand better is how the AWS IAM role associates to a Conjur role (Host).

What I'd expect to see is something like: The id of the Conjur Host must match the IAM role provided by the signed request

OR

The Conjur Host has an annotation authn-iam/role, and the request must be signed with that role.

I think that an end-to-end example in the specification here would help to flesh this out. Please expand the specification by showing:

cyberark-bizdev commented 6 years ago

@kgilpin here my attempt to explain it.

In AWS, IAM Roles will be created for the different kind (or maybe even different criteria as well) of host instances (which could be EC2 hosts or lambda functions to start.) These IAM roles will be instance profile roles, that means that Amazon automatically makes those instances to assume those roles when they are initiated/started.

The instance profile roles will need to be protected by AWS using policies, to guarantee they can only be assumed by the particular instances. A trusted policy for an EC2 instance profile will look like:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

While the one for lambda function will look like:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

By properly securing the AWS trusted policies and permissions, we should rely on the instance roles to only be assumed by the target instances.

In the Conjur policy by using the iam_allowed_roles annotation, we can define which instances will be able to authenticate to Conjur. We use the headers of a GetCallerIdentity URL request, to validate from the Conjur side the host authenticating is an instance that has assumed one of the roles from the list of roles allowed by the Conjur web service.

The flow from an EC2 instance will be like:

  1. The EC2 instance when started immediately assumes the role defined in AWS as its instance profile role.

  2. The EC2 instance signs a getCallerIdentity URL request and passes the headers as the body of the authentication URL to Conjur. In the Conjur authentication URL it also passes the service name to be matched against the web service defined in Conjur, and the "hostname" that it will assume under Conjur.

  3. During the authentication on the Conjur side, we perform the following validations:

    • We execute the getCallerIdentity action against AWS STS (Security Token Service); if successful continues
    • We validate the assumedRole from the getCallerIdentity is among the ones listed in iam_allowed_roles of the host role; if successful continues

IF all conditions pass, then we return a JSON hash (with token) for subsequent API calls.

Responding directly each of your questions:

The client signs a getCallerIdentity URL request and passes the headers as the body of the authenticate URL for conjur. In the Conjur authenticate URL it also passes the service name to be matched against the web service defined in Conjur, and the "hostname" / host role that it will assume under Conjur.

cyberark-bizdev commented 6 years ago

An example successful run will look like:

[ec2-user@ip-172-31-3-12 ~]$ 
[ec2-user@ip-172-31-3-12 ~]$ export aws_signed_headers=`./sign_request.rb`
[ec2-user@ip-172-31-3-12 ~]$ 
[ec2-user@ip-172-31-3-12 ~]$ echo $aws_signed_headers
{"host":"sts.amazonaws.com","x-amz-date":"20180504T014727Z","x-amz-security-token":"FQoDYXdzEFIaDCFHQ6dey4pm779E0iK3A/YOaYPACECBB3nO6cuJq80RYXKSPvTZw4DtmmjVD1JeKYDxr7jO8L2uHQjR9s3xbnsZIuiwjtC4ig53/CM6kAFGeJM+3FBPgB37k42HB3H2c2xIPjVyDCLnc65C8pZTQ4Znv18GhNHjU7AG+o4hSbFJ31WF0Nx0PY/nVp1Aadrd4j6eAYJgYE5w1Sg3R0u6+VI7PXySOEj18Kb8JbjhtDNjiHFgs+2CL9I0RCln7sqThqgdGIs0ofePDQvzzTMGNn27RFQlD8iVKBZhwVpYIfpZRi2Cz0ZHkNWaIk64ytoqmd9MqCerc/awqQNmRN0zOkk4cXvW7wGWzaHMVW/jM6Fmus8xdSONB8MaMuH6CDVljLPqOeL3xh7mLGynl7kHpCHn++byb8SA3nYDatfX1L2aDMLDR27BlNz0PodmN9hAP99pymk3xbHJC6ZXaiXV5Ov3yVahxkQSH4j2De3yyws6uni+AsVzK/Ot0wB5N10k6yMqRIGjRM0o97fOjcHPG1MJ3w94dkVPAkrPAkFkoMr/+BESS4PkjKdR2yFTosJfYszCDRBUjTTX/j5AH9kHMjj/9Tkq6Hsomeau1wU=","x-amz-content-sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","authorization":"AWS4-HMAC-SHA256 Credential=ASIAILI5BVGKITQSP27A/20180504/us-east-1/sts/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token, Signature=ffd028dc350493c3991a0a5b861cbc47bfb2b9e60faa1bdd3495c808364f67e9"}
[ec2-user@ip-172-31-3-12 ~]$ 
[ec2-user@ip-172-31-3-12 ~]$ 
[ec2-user@ip-172-31-3-12 ~]$ export conjur_token=`curl -s -X POST --data "$aws_signed_headers" 73.202.253.45:3000/authn-iam/aws/cucumber/host%2fconjur%2fauthn-iam%2faws%2fec2-host/authenticate | base64 | tr -d "\r\n"`
[ec2-user@ip-172-31-3-12 ~]$ 
[ec2-user@ip-172-31-3-12 ~]$ echo $conjur_token
eyJwcm90ZWN0ZWQiOiJleUpoYkdjaU9pSmpiMjVxZFhJdWIzSm5MM05zYjNOcGJHOHZkaklpTENKcmFXUWlPaUkwTldNek56RTBNakZpTlRWa1lUQXlOMkppWmpObE9ERmpOemhsWldFM1l5SjkiLCJwYXlsb2FkIjoiZXlKemRXSWlPaUpvYjNOMEwyTnZibXAxY2k5aGRYUm9iaTFwWVcwdllYZHpMMlZqTWkxb2IzTjBJaXdpYVdGMElqb3hOVEkxTXprNE5EYzRmUT09Iiwic2lnbmF0dXJlIjoiSzJCd0p6UlFZeE5CaDhCSTFzSi1CV2R5M2N4ZmdWUEgyWU5kMGFROWVUa0RzNlhIbHRieElXekp4Z2VWNnBTN0hrdmhmelJ4QkNPUGU1VDY2dDAtNUpCZTBuaDVzNlFrYk92dXZzSDVlc2xqTm5Za3FoamdOcV8wQmFQbk5Cc091ZHV4U28xcGV3SnZseTA3X0phMUVHSlZYanlsSlN6ZTRnaUg0N19HRXpnZXdHSU5kMXZvUW5jaUpVNWFEYTJURVJVZGdjOExqcndYZERFcWpVU1pMRVZWZDVpNHc2bXdMdGRyQ2NuWGhua1NxY2xUMGZnRUlZRlU3OWZUazdCN01sOUZDejNOYjYzbTdxU09ab0dyWWFGdU9lWXd1Q3J5ZHo5TTB3Y0ktemFiZ2Z3aE1NMHI3UjBsalhTNGF2R2RDM2cxMV9abVBUdkE4eFllQ1huMFFZUHkzU21pTFNDS0tuVFN0UnFSY2VYTGN0XzFSSDh0V0Y1WnZGY2NRMk1zIn0=
[ec2-user@ip-172-31-3-12 ~]$ 
[ec2-user@ip-172-31-3-12 ~]$ 
[ec2-user@ip-172-31-3-12 ~]$ echo `curl -s -X GET -H "Authorization: Token token=\"$conjur_token\"" 73.202.253.45:3000/secrets/cucumber/variable/conjur/authn-iam/aws/secret`
TOPSECRET2
[ec2-user@ip-172-31-3-12 ~]$ 
cyberark-bizdev commented 6 years ago

sign_request.rb code here:

!/usr/bin/env ruby

require 'aws-sigv4'
require 'aws-sdk'

credentials = Aws::InstanceProfileCredentials.new

url = 'https://sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15'
service = 'sts'
region = 'us-east-1'
http_method = 'GET'

signer = Aws::Sigv4::Signer.new(
    service: service,
    region: region,
    credentials_provider: credentials
)

signature = signer.sign_request(
    http_method: http_method,
    url: url
)

puts signature.headers.to_json
kgilpin commented 6 years ago

Is the sign_request.rb subject to a replay attack? In other words, if I somehow obtain a signed request (possibly an old one) can I use it to obtain a Conjur access token? Is there a date stamp or other replay defense?

cyberark-bizdev commented 6 years ago

@kgilpin the signed request is time based.... the time/date-stamp is part of the signature. They expire within 5 minutes I believe.