class Cerberus::AwsAssumeRoleCredentialsProvider

The AWS IAM role credentials provider

Tries to authenticate with Cerberus using the given IAM role

Constants

CERBERUS_AUTH_DATA_CLIENT_TOKEN_KEY

reference into the decrypted auth data json we get from Cerberus

CERBERUS_AUTH_DATA_LEASE_DURATION_KEY
CERBERUS_AUTH_DATA_POLICIES_KEY
LOGGER
ROLE_AUTH_REL_URI

relative URI to get encrypted auth data from Cerberus

Public Class Methods

new(cerberus_url_resolver, iam_role_arn, region) click to toggle source

Init AWS role provider - needs cerberus base url. Instance metadata service url is optional to make unit tests easier and so we can provide a hook to set this via config as needed

# File lib/cerberus/aws_assumed_role_credentials_provider.rb, line 39
def initialize(cerberus_url_resolver, iam_role_arn, region)
  @cerberus_base_url = CerberusUtils::get_url_from_resolver(cerberus_url_resolver)
  @client_token = nil
  @cerberus_auth_info = get_assumed_role_info(iam_role_arn, region)

  LOGGER.debug("AwsAssumeRoleCredentialsProvider initialized with cerberus base url #{@cerberus_base_url}")
end

Public Instance Methods

get_client_token() click to toggle source

Get credentials using AWS IAM role

# File lib/cerberus/aws_assumed_role_credentials_provider.rb, line 50
def get_client_token

  if (@cerberus_auth_info.nil?)
    LOGGER.warn("Instance metadata URL is nil for role provider!")
    raise Cerberus::Exception::NoValueError
  end

  if (@client_token.nil?)
    @client_token = get_credentials_from_cerberus
  end

  # using two if statements here just to make the logging easier..
  # the above we expect on startup, expiration is an interesting event worth a debug log all its own
  if (@client_token.expired?)
    LOGGER.debug("Existing ClientToken has expired - refreshing from Cerberus...")
    @client_token = get_credentials_from_cerberus
  end

  return @client_token.authToken

end

Private Instance Methods

do_auth_with_cerberus(iam_principal_arn, region) click to toggle source
# File lib/cerberus/aws_assumed_role_credentials_provider.rb, line 134
def do_auth_with_cerberus(iam_principal_arn, region)
  post_json_data = JSON.generate({:iam_principal_arn => iam_principal_arn, :region => region})
  auth_url = URI(@cerberus_base_url + ROLE_AUTH_REL_URI)
  use_ssl = ! @cerberus_base_url.include?("localhost")
  auth_response = CerberusUtils::Http.new.make_http_call(auth_url, 'POST', use_ssl, post_json_data)
  # if we got this far, we should have a valid response with encrypted data
  # send back the encrypted data
  JSON.parse(auth_response.body)['auth_data']
end
get_assumed_role_info(iam_role_arn, region) click to toggle source

Get an CerberusAuthInfo object from the provided data

# File lib/cerberus/aws_assumed_role_credentials_provider.rb, line 77
def get_assumed_role_info(iam_role_arn, region)

  begin
    num_chars = 10  # magic number - shouldn't be too long to avoide session name length limits
    random_string = SecureRandom.hex(num_chars/2)  # hex method produces double the inputted num
    assume_role_session_name = "cerb-assume-role-session-#{random_string}"

    LOGGER.debug("role: #{iam_role_arn}")
    LOGGER.debug("region: #{region}")
    LOGGER.debug("session name: #{assume_role_session_name}")

    role_creds = Aws::AssumeRoleCredentials.new(
        client: Aws::STS::Client.new(region: region),
        role_arn: iam_role_arn,
        role_session_name: assume_role_session_name)

    return CerberusAuthInfo.new(iam_role_arn, region, credentials: role_creds)
  rescue
    LOGGER.error("Failed to assume role: #{iam_role_arn}, region: #{region}")
    return nil
  end
end
get_credentials_from_cerberus() click to toggle source

Reach out to the Cerberus management service and get an auth token

# File lib/cerberus/aws_assumed_role_credentials_provider.rb, line 103
def get_credentials_from_cerberus
  LOGGER.debug("Authenticating with assumed role...")
  begin
    authData = do_auth_with_cerberus(@cerberus_auth_info.iam_principal_arn, @cerberus_auth_info.region)

    LOGGER.debug("Got auth data from Cerberus. Attempting to decrypt...")

    # decrypt the data we got from cerberus to get the cerberus token
    kms = Aws::KMS::Client.new(region: @cerberus_auth_info.region, credentials: @cerberus_auth_info.credentials[:credentials])

    decryptedAuthDataJson = JSON.parse(kms.decrypt(ciphertext_blob: Base64.decode64(authData)).plaintext)

    LOGGER.debug("Decrypt successful. Passing back Cerberus auth token.")
    # pass back a credentials object that will allow us to reuse it until it expires
    CerberusClientToken.new(decryptedAuthDataJson[CERBERUS_AUTH_DATA_CLIENT_TOKEN_KEY],
                            decryptedAuthDataJson[CERBERUS_AUTH_DATA_LEASE_DURATION_KEY],
                            decryptedAuthDataJson[CERBERUS_AUTH_DATA_POLICIES_KEY])

  rescue Cerberus::Exception::HttpError
    # catch http errors here and assert no value
    # this may not actually be the case, there are legitimate reasons HTTP can fail when it "should" work
    # but this is handled by logging - a warning is set in the log in during the HTTP call
    LOGGER.error("Failed to authenticate with assumed role: #{@cerberus_auth_info.iam_principal_arn},
       region: #{@cerberus_auth_info.region}.")
    raise Cerberus::Exception::NoValueError
  end
end