cyberark / conjur

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

AWS IAM Authenticator #542

Closed kgilpin closed 6 years ago

kgilpin commented 6 years ago

Feature Overview The Conjur IAM Authenticator allows an AWS resource to use its AWS IAM role to authenticate with Conjur. This approach enables EC2 instances and Lambda functions to access credentials stored in Conjur without a pre-configured Conjur identity.

Setup To enable an IAM Authenticator called prod, we'll set the following environment variable when we start Conjur:

CONJUR_AUTHENTICATORS=authn-iam/prod

In this context, prod is the called the service ID. Multiple authenticators can be configured with different service IDs. Each one implements a separate "zone" of authentication.

After Conjur is started, we'll create a policy to enable our prod IAM Authenticator:

# policy id needs to match the convention `conjur/authn-iam/<service ID>`
- !policy
  id: conjur/authn-iam/prod
  body:
  - !webservice

  - !group clients

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

Next, let's create an application policy to provide a database username and password to an AWS services. The IAM role in this example is 011915987442:MyApp:

- !policy 
  id: myapp
  body:
  - &variables
    - !variable database/username
    - !variable database/password

  # Create a group that will have permission to retrieve variables
  - !group secrets-users

  # Give the `secrets-users` group permission to retrieve variables
  - !permit
    role: !group secrets-users
    privilege: [ read, execute ]
    resource: *variables

  # Create a layer to hold this application's hosts
  - !layer

  # The host ID needs to match the AWS ARN of the role we wish to authenticate.
  - !host 011915987442/MyApp

  # Add our host into our layer
  - !grant
    role: !layer
    member: 011915987442/MyApp

  # Give the host in our layer permission to retrieve variables
  - !grant
    member: !layer
    role: !group secrets-users

Important: Note above host has an ID composed of a prefix/namespace followed by the AWS account ID followed by the name of the AWS IAM role. The AWS Account ID and name of the role is extracted from the getCallerIdentity by the authenticator

Finally, let's give our myapp host permission to authenticate using the IAM Authenticator:

- !grant
  role: !group conjur/authn-iam/prod/clients
  member: !host myapp/011915987442/MyApp

Workflow

Now that the IAM Authenticator has been configure and we've permitted an IAM role to authenticate, let's look at the authentication flow of an EC2 instance or Lambda function.

  1. The instance or function starts, assuming the IAM role it was provided.

  2. From the instance of function, generate a signed request to the STS service, to get the identify of the requestor. This request is signed using the instance or function's access key. The signed request is valid for five minutes. Below is an example of how a signed request would be generated (using Ruby):

    require 'aws-sigv4'
    require 'aws-sdk'
    
    request = Aws::Sigv4::Signer.new(
      service: 'sts',
      region: 'us-east-1',
      credentials_provider: Aws::InstanceProfileCredentials.new
    ).sign_request(
      http_method: 'GET',
      url: 'https://sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15'
    ).headers

3) Using the signed request, the instance or function authenticates with Conjur using the following:

```ruby
require 'conjur-api'

Conjur.configuration.account = 'my-account'
Conjur.configuration.appliance_url = 'https://conjur.mydomain.com/authn-iam/prod'
Conjur.configuration.cert_file = '<cert>/<path>/conjur-yourorg.pem'
Conjur.configuration.apply_cert_config!

conjur = Conjur::API.new_from_key 'host/aws/011915987442:assumed-role/MyApp', request.to_json
```
  1. When Conjur receives this authentication request, it performs the following:

    1. Validates the host myapp/011915987442/MyApp has permission to authenticate using the prod IAM Authenticator.

    2. Extracts the signed request from the POST body.

    3. Creates a request to the AWS STS service, using the provided signed request as its header.

    4. If the STS request is successful, the requesting instance or function's IAM role is returned. The role is Validated against the requesting role. If the two roles match, an authentication token is returned. Below is an example of a successful STS response:

      <GetCallerIdentityResponse xmlns=\"https://sts.amazonaws.com/doc/2011-06-15/\">
        <GetCallerIdentityResult>
          <Arn>arn:aws:sts::011915987442:MyApp/i-0a5702a5a078e1a00</Arn>
          <UserId>AROAJYAZ7DBLU2PE4DWOW:i-0a5702a5a078e1a00</UserId>
          <Account>011915987442</Account>
        </GetCallerIdentityResult>
        <ResponseMetadata>
          <RequestId>88278d14-4f3e-11e8-88a1-a9dc7b9cbe6c</RequestId>
        </ResponseMetadata>
      </GetCallerIdentityResponse>

Authentication may fail for a number of reasons. In each case, a 401 Unauthorized response will be returned.

Reasons for failing authentication include:

See also

Previous (deprecated) design: #536

kgilpin commented 6 years ago

Maybe I am misunderstanding something, but why would "assumed-role" be part of the role name, and why would "-InstanceProfile" be part of the role name? Wouldn't the role name just be a simple logical name that describes the role, such as "MyApp"?

kgilpin commented 6 years ago

Maybe “arn:aws:sts::” should be assumed, simplifying the host to “!host 011915987442/MyApp”

ryanprior commented 6 years ago

Loving this concept. I was working on a proof of concept lambda using the Conjur API the other day and was wishing I could use an IAM role for auth. I need to learn enough about operating AWS to create a demo for this authenticator as soon as it's written.

cyberark-bizdev commented 6 years ago

I like the suggestion of removing "arn:aws:sts::" to simplify policy... and at the end also simplifies validation... as the hostname needs to be passed (login role) as part of the conjur API authenticate call, and the authentication framework already validates that the webservice has authenticate permissions on the role... in the authentication validation I reconstruct a hostname by using the information received from the getCallerIdentity and match it against the login role passed.

I will update the description in the policy above to adjust to it.

I did a quick test, and it worked fine from an EC2 instance.

@kgilpin @jvanderhoof @Daniel-Warner-X