Limn is a reflective description service, providing a secure way to discover tags, hostnames, and other metadata about an EC2 Instance. It's useful for bootstrapping configuration and platform automation.
Limn is a serverless app for AWS Lambda. It requires
some python packages (such as M2Crypto) which aren't available in the base
Lambda Execution Environment.
The included build.sh
uses Docker to compile dependencies so they are
compatible with the Lambda environment, installing them under the vendored/
directory.
$ ./build.sh
Modify the included serverless.yml as necessary to support your environment.
Limn supports cross-account resource description using sts:AssumeRole
. To
enable this feature you should update the iamRoleStatements
and environment
in serverless.yml
to include the cross-account roles limn can assume:
provider:
name: aws
iamRoleStatements:
- Effect: "Allow"
Action:
- "sts:AssumeRole"
Resource:
- "arn:aws:iam::123456789012:role/limn"
- "arn:aws:iam::345678901234:role/limn"
- "arn:aws:iam::567890123456:role/limn"
environment:
AWS_ASSUME_ROLES: >
arn:aws:iam::123456789012:role/limn
arn:aws:iam::345678901234:role/limn
arn:aws:iam::567890123456:role/limn
The limn API is simple. POST
your EC2 instance's pkcs7 signed
instance identity document against the limn server. If the signature is valid limn responds with the decoded identity document and any additional
resources it discovers (tags
, hostnames
, dhcpDomainName
, etc).
(~)$ PKCS7="$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/pkcs7)"
(~)$ curl -s -XPOST --data-urlencode "$PKCS7" https://limn.server.url/|jq '.'
{
"availabilityZone": "us-west-2c",
"tags": {
"Name": "test-089ffe0ecf7fcc169-swankymuskrat",
"ClusterId": "test",
"env:datacenter": "example-usw2",
"aws:autoscaling:groupName": "test-v000",
},
"instanceId": "i-089ffe0ecf7fcc169",
"region": "us-west-2",
"dhcpDomainName": "example-usw2.mydomain.com",
"imageId": "ami-a03facc8",
"vpcId": "vpc-fd43b248",
"subnetId": "subnet-9036b7d0",
"instanceType": "t2.small",
"hostnames": [
"i-089ffe0ecf7fcc169",
"ip-10-21-32-193.us-west-2.compute.internal",
"10.21.32.193",
"test-089ffe0ecf7fcc169-swankymuskrat.example-usw2.mydomain.com"
],
"accountId": "123456789012"
Configuration 🔗
Limn configuration is passed as environment variables. Multiple values should take the form of space-separated strings.
var | defaults | cast | description |
---|---|---|---|
AWS_ASSUME_ROLES |
(empty string) |
str.split() |
List of roles limn can assume to lookup resources in different accounts. If this is not set, or limn can't find a configured role associated with the instance's accountId it will attempt to use boto3 credentials. |
INHERIT_TAGS |
env: opt: |
str.split() |
Whitelist of tag prefixes allowed to be inherited from parent resources. See Tag Discovery for details. |
ROLE_TAGS |
opt:cluster clusterid elasticbeanstalk:environment-name |
str.split() |
Used for dynamic hostname generation. See Hostname Discovery for details. |
Limn needs the following IAM permissions to run:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeDhcpOptions",
"ec2:DescribeInstances",
"ec2:DescribeTags",
"ec2:DescribeVpcs",
"autoscaling:DescribeAutoScalingInstances",
"autoscaling:DescribeTags"
],
"Resource": [
"*"
]
}
]
}
To enable cross-account lookups with limn, update its IAM policy to include an
appropriate sts:AssumeRole
statement for each account (including the one where
your limn server is running):
{
"Version": "2012-10-17",
"Statement": [
{"": "..."},
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": [
"arn:aws:iam::<primary-account-number>:role/limn",
"arn:aws:iam::<secondary-account-number>:role/limn"
]
}
]
}
For each role limn can assume, attach a Trust Relationship to enable cross-account access:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<primary-account-number>:role/limn"
},
"Action": "sts:AssumeRole"
}
]
}
Configure the environment variable AWS_ASSUME_ROLES
with the Role ARNs for
each account as a space-separated string.
Tag Discovery 🔗
Limn uses a hierarchical override to resolve tags for EC2 instances. If the tags
for any parent resource match a prefix defined by INHERIT_TAGS
they're
returned as part of the instance description.
Inherited tags are discovered and overridden in the following order: image
,
vpc
, subnet
, auto-scaling-group
, and finally the instance
itself. The
following graph shows tag inheritance and overriding in action. Instances in
each subnet inherit tags from their parent subnet, and their subnet's parent
vpc.
The tags set against resources are:
resource | tag | value |
---|---|---|
vpc |
env:CASSH_URL |
cassh.default-usw2 |
vpc |
env:CONSUL_DC |
usw2 |
subnet-prod |
env:CASSH_URL |
cassh.prod-usw2 |
subnet-stage |
env:CONSUL_DC |
staging-usw2 |
The tags resolve with inheritance as:
resource | tag | value |
---|---|---|
prod-instance(s) |
env:CONSUL_DC |
usw2 |
env:CASSH_URL |
cassh.prod-usw2 |
|
stage-instance(s) |
env:CONSUL_DC |
staging-usw2 |
env:CASSH_URL |
cassh.default-usw2 |
Hostname Discovery 🔗
Limn provides an opinionated hostname generator returning the instance's 'Name' tag (if set), and a programmatic hostname with the format:
<role>-<instance_id>-<adjective><animal>
-
role
is discovered from the first resolved instance tag matchingROLE_TAGS
(case insensitive). By default it looks for the following tags in order:opt:cluster
,clusterid
, andelasticbeanstalk:environment-name
. -
instance_id
is stripped of leadingi-
, soi-abcdefgh
becomesabcdefgh
. -
adjective
andanimal
are generated from wordlists using a consistent hash function based on the instance id. -
vpcDhcpDomainName
is discovered from the DHCP Options attached the the VPC associated with the instance.