When approaching a new service provider, sometimes it can be confusing on how to get set up to best communicate with them – some processes involve multiple steps, multiple interfaces, confusing terminology, and
Amazon Web Services is an amazing cloud services provider, and in order to allow access informational services inside a customer’s account, a couple of known mechanisms exist to delegate access:
- Account Keys, where you generate a key and secret and share them. The other party stores these (usually in either clear text or using reversible encryption) and uses them as needed to make API calls
- Role Delegation, where you create a Role and shared secret to provide to a the external service provider, who then is allowed to use their own internal security credentials to request temporary access to your account’s resources via API calls
In the former model, the keys are exchanged once, and once out of your immediate domain, you have little idea what happens to them.
In the latter, a rule is put into place that requires ongoing authenticated access to request assumption of a known role with a shared secret.
Luckily, in both scenarios, a restrictive IAM Policy is in place that allows only the actions you’ve decided to allow ahead of time.
Setting up the desired access is made simpler by having good documentation on how to do this manually. In this modern era, we likely want to keep our infrastructure as code where possible, as well as have a mechanism to apply the rules and test later if they are still valid.
Here’s a quick example I cooked up using Terraform, a new, popular tool to compose cloud infrastructure as code and execute to create the desired state.
# Read more about variables and how to override them here: | |
# https://www.terraform.io/docs/configuration/variables.html | |
variable "aws_region" { | |
type = "string" | |
default = "us-east-1" | |
} | |
variable "shared_secret" { | |
type = "string" | |
default = "SOOPERSEKRET" | |
} | |
provider "aws" { | |
region = "${var.aws_region}" | |
} | |
resource "aws_iam_policy" "dd_integration_policy" { | |
name = "DatadogAWSIntegrationPolicy" | |
path = "/" | |
description = "DatadogAWSIntegrationPolicy" | |
policy = <<EOF | |
{ | |
"Version": "2012-10-17", | |
"Statement": [ | |
{ | |
"Action": [ | |
"autoscaling:Describe*", | |
"cloudtrail:DescribeTrails", | |
"cloudtrail:GetTrailStatus", | |
"cloudwatch:Describe*", | |
"cloudwatch:Get*", | |
"cloudwatch:List*", | |
"ec2:Describe*", | |
"ec2:Get*", | |
"ecs:Describe*", | |
"ecs:List*", | |
"elasticache:Describe*", | |
"elasticache:List*", | |
"elasticloadbalancing:Describe*", | |
"elasticmapreduce:List*", | |
"iam:Get*", | |
"iam:List*", | |
"kinesis:Get*", | |
"kinesis:List*", | |
"kinesis:Describe*", | |
"logs:Get*", | |
"logs:Describe*", | |
"logs:TestMetricFilter", | |
"rds:Describe*", | |
"rds:List*", | |
"route53:List*", | |
"s3:GetBucketTagging", | |
"ses:Get*", | |
"ses:List*", | |
"sns:List*", | |
"sns:Publish", | |
"sqs:GetQueueAttributes", | |
"sqs:ListQueues", | |
"sqs:ReceiveMessage" | |
], | |
"Effect": "Allow", | |
"Resource": "*" | |
} | |
] | |
} | |
EOF | |
} | |
resource "aws_iam_role" "dd_integration_role" { | |
name = "DatadogAWSIntegrationRole" | |
assume_role_policy = <<EOF | |
{ | |
"Version": "2012-10-17", | |
"Statement": { | |
"Effect": "Allow", | |
"Principal": { "AWS": "arn:aws:iam::464622532012:root" }, | |
"Action": "sts:AssumeRole", | |
"Condition": { "StringEquals": { "sts:ExternalId": "${var.shared_secret}" } } | |
} | |
} | |
EOF | |
} | |
resource "aws_iam_policy_attachment" "allow_dd_role" { | |
name = "Allow Datadog PolicyAccess via Role" | |
roles = ["${aws_iam_role.dd_integration_role.name}"] | |
policy_arn = "${aws_iam_policy.dd_integration_policy.arn}" | |
} | |
output "AWS Account ID" { | |
value = "${aws_iam_role.dd_integration_role.arn}" | |
} | |
output "AWS Role Name" { | |
value = "${aws_iam_role.dd_integration_role.name}" | |
} | |
output "AWS External ID" { | |
value = "${var.shared_secret}" | |
} |
The output should look a lot like this:
The Account ID is actually a full ARN, and you can copy your Account ID from there.
Terraform doesn’t have a mechanism to emit only the Account ID yet – so if you have some ideas, contribute!
Use the Account ID, Role Name and External ID and paste those into the Datadog Integrations dialog, after selecting Role Delegation. This will immediately validate that the permissions are correct, and return an error otherwise.
Don’t forget to click “Install Integration” when you’re done (it’s at the very bottom of the screen).
Now metrics and events will be collected by Datadog from any allowed AWS services, and you can keep this setup instruction in any revision system of your choice.
P.S. I tried to set this up via CloudFormation (Sparkleformation, too!). I ended up writing it “freehand” and took more than 3 times as long to get similar functionality.
You can see the CloudFormation Stack here, and decide which works for you.
Further reading: