Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically import EC2 tags #12593

Merged
merged 50 commits into from
May 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
2e167cc
Add EC2 label service
atburke Apr 25, 2022
4824f76
Add EC2 labels for ssh nodes
atburke Apr 26, 2022
da12b92
Add test for ec2 labels
atburke Apr 27, 2022
fd218c8
Make EC2 label config struct
atburke Apr 27, 2022
df4c36a
Make ec2labels shared
atburke Apr 28, 2022
bd54680
Add ec2 tags for apps, kube, and db
atburke Apr 29, 2022
b41fedd
Fix ec2 label fetch in apps and db
atburke May 3, 2022
23ae539
Add apps, kube, and db to TestLabels
atburke May 4, 2022
c915f0f
Add tests
atburke May 4, 2022
664baad
Add another test
atburke May 4, 2022
a75ae6e
Fix kube test
atburke May 6, 2022
d4fc1f9
Fix env check
atburke May 6, 2022
4b65f82
Merge branch 'master' into atburke/import-ec2-tags
atburke May 6, 2022
5a9fe9c
Add docs draft
atburke May 6, 2022
3bf6e44
Add hostname test
atburke May 9, 2022
2045847
Minor refactoring
atburke May 9, 2022
7de345f
More refactoring
atburke May 9, 2022
606cd61
Update docs
atburke May 11, 2022
6e69c15
Refactoring
atburke May 11, 2022
1b27061
Update getStaticLabels description
atburke May 11, 2022
b0d2aeb
Address review comments
atburke May 13, 2022
ff6b4cb
Lift context
atburke May 17, 2022
800e87f
Move InstanceMetadata interface
atburke May 17, 2022
f038d4e
Add cloud interface
atburke May 18, 2022
1101fb8
Don't inject labels unnecessarily
atburke May 18, 2022
c6d1aa2
Remove integration test dependency on ec2 instance
atburke May 18, 2022
324dc85
Remove debug prints
atburke May 18, 2022
dddcb64
Fix ec2 label start/close behavior
atburke May 18, 2022
cb852fc
Remove unused field
atburke May 18, 2022
84daace
Fix linting and docs
atburke May 18, 2022
258a37b
Move nitro disclaimer down in docs
atburke May 18, 2022
0c05569
Move ec2 labels to their own package
atburke May 18, 2022
a720623
Rename Cloud to LabelImporter
atburke May 18, 2022
00fbf23
Remove close chan from ec2 labels
atburke May 20, 2022
6f0b142
Remove kube dependence on lib.isinsecure
atburke May 20, 2022
69e665a
Fix kube test
atburke May 23, 2022
1842c1b
Address review comments
atburke May 24, 2022
d5bcc16
Fix copyright year
atburke May 24, 2022
3fb1bcc
Merge branch 'master' into atburke/import-ec2-tags
atburke May 24, 2022
03b4e36
Fix linting
atburke May 24, 2022
e2b6086
Address hidden feedback
atburke May 27, 2022
f74b67a
Final fixes
atburke May 31, 2022
90f164a
Merge branch 'master' into atburke/import-ec2-tags
atburke May 31, 2022
51f6991
Fix ParseMetadataClientError description
atburke May 31, 2022
3ced986
Merge branch 'master' into atburke/import-ec2-tags
atburke May 31, 2022
684860f
Merge branch 'master' into atburke/import-ec2-tags
atburke May 31, 2022
f89b43d
Merge branch 'master' into atburke/import-ec2-tags
atburke May 31, 2022
e55f2a9
Close teleport processes in tests
atburke May 31, 2022
c357594
Merge branch 'master' into atburke/import-ec2-tags
atburke May 31, 2022
97432dd
Merge branch 'master' into atburke/import-ec2-tags
atburke May 31, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions api/types/appserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,16 @@ func (s *AppServerV3) GetAllLabels() map[string]string {
return CombineLabels(staticLabels, dynamicLabels)
}

// GetStaticLabels returns the app server static labels.
func (s *AppServerV3) GetStaticLabels() map[string]string {
return s.Metadata.Labels
}

// SetStaticLabels sets the app server static labels.
func (s *AppServerV3) SetStaticLabels(sl map[string]string) {
s.Metadata.Labels = sl
}

// Copy returns a copy of this app server object.
func (s *AppServerV3) Copy() AppServer {
return proto.Clone(s).(*AppServerV3)
Expand Down
3 changes: 3 additions & 0 deletions api/types/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,9 @@ const (
OriginCloud = "cloud"
)

// EC2HostnameTag is the name of the EC2 tag used to override a node's hostname.
const EC2HostnameTag = "TeleportHostname"

// OriginValues lists all possible origin values.
var OriginValues = []string{OriginDefaults, OriginConfigFile, OriginDynamic, OriginCloud}

Expand Down
10 changes: 10 additions & 0 deletions api/types/databaseserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,16 @@ func (s *DatabaseServerV3) GetAllLabels() map[string]string {
return CombineLabels(staticLabels, s.Spec.DynamicLabels)
}

// GetStaticLabels returns the database server static labels.
func (s *DatabaseServerV3) GetStaticLabels() map[string]string {
return s.Metadata.Labels
}

// SetStaticLabels sets the database server static labels.
func (s *DatabaseServerV3) SetStaticLabels(sl map[string]string) {
s.Metadata.Labels = sl
}

// Copy returns a copy of this database server object.
func (s *DatabaseServerV3) Copy() DatabaseServer {
return proto.Clone(s).(*DatabaseServerV3)
Expand Down
20 changes: 20 additions & 0 deletions api/types/desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ func (s *WindowsDesktopServiceV3) GetAllLabels() map[string]string {
return s.Metadata.Labels
}

// GetStaticLabels returns the windows desktop static labels.
func (s *WindowsDesktopServiceV3) GetStaticLabels() map[string]string {
return s.Metadata.Labels
}

// SetStaticLabels sets the windows desktop static labels.
func (s *WindowsDesktopServiceV3) SetStaticLabels(sl map[string]string) {
s.Metadata.Labels = sl
}

// GetHostname returns the windows hostname of this service.
func (s *WindowsDesktopServiceV3) GetHostname() string {
return s.Spec.Hostname
Expand Down Expand Up @@ -177,6 +187,16 @@ func (d *WindowsDesktopV3) GetAllLabels() map[string]string {
return CombineLabels(d.Metadata.Labels, nil)
}

// GetStaticLabels returns the windows desktop static labels.
func (d *WindowsDesktopV3) GetStaticLabels() map[string]string {
return d.Metadata.Labels
}

// SetStaticLabels sets the windows desktop static labels.
func (d *WindowsDesktopV3) SetStaticLabels(sl map[string]string) {
d.Metadata.Labels = sl
}

// LabelsString returns all desktop labels as a string.
func (d *WindowsDesktopV3) LabelsString() string {
return LabelsAsString(d.Metadata.Labels, nil)
Expand Down
4 changes: 4 additions & 0 deletions api/types/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ type ResourceWithLabels interface {
ResourceWithOrigin
// GetAllLabels returns all resource's labels.
GetAllLabels() map[string]string
// GetStaticLabels returns the resource's static labels.
GetStaticLabels() map[string]string
// SetStaticLabels sets the resource's static labels.
SetStaticLabels(sl map[string]string)
// MatchSearch goes through select field values of a resource
// and tries to match against the list of search values.
MatchSearch(searchValues []string) bool
Expand Down
14 changes: 14 additions & 0 deletions api/types/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,25 @@ func (s *ServerV2) GetHostname() string {
return s.Spec.Hostname
}

// GetLabels and GetStaticLabels are the same, and that is intentional. GetLabels
// exists to preserve backwards compatibility, while GetStaticLabels exists to
// implement ResourcesWithLabels.

// GetLabels returns server's static label key pairs
func (s *ServerV2) GetLabels() map[string]string {
return s.Metadata.Labels
}

// GetStaticLabels returns the server static labels.
func (s *ServerV2) GetStaticLabels() map[string]string {
return s.Metadata.Labels
}

// SetStaticLabels sets the server static labels.
func (s *ServerV2) SetStaticLabels(sl map[string]string) {
s.Metadata.Labels = sl
}

// GetCmdLabels returns command labels
func (s *ServerV2) GetCmdLabels() map[string]CommandLabel {
if s.Spec.CmdLabels == nil {
Expand Down
Binary file added docs/img/aws/allow-tags.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/aws/instance-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/aws/launch-instance-advanced-options.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 55 additions & 105 deletions docs/pages/setup/guides/ec2-tags.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,131 +4,81 @@ description: How to set up Teleport Node labels based on EC2 tags
h1: Sync EC2 Tags and Teleport Node Labels
---

This guide will explain how to set up Teleport Node labels based on Amazon EC2 tags.
When running on an AWS EC2 instance, Teleport will automatically detect and import EC2 tags as
Teleport labels for SSH nodes, Applications, Databases, and Kubernetes clusters. Labels created
this way will have the `aws/` prefix.

If the tag `TeleportHostname` (case-sensitive) is present, its value will override the node's hostname.

```bash
$ tsh ls
Node Name Address Labels
-------------------- -------------- -----------------------------------------------------------------------------------------------------------------------
fakehost.example.com 127.0.0.1:3022 env=example,hostname=ip-172-31-53-70,aws/Name=atburke-dev,aws/TagKey=TagValue,aws/TeleportHostname=fakehost.example.com
```

<Notice type="note">
For services that manage multiple resources (such as the database service), each resource will receive the
same labels from EC2.
</Notice>

## Prerequisites
atburke marked this conversation as resolved.
Show resolved Hide resolved

(!docs/pages/includes/edition-prereqs-tabs.mdx!)

- One Teleport Node running on an Amazon EC2 instance. See
[Adding Nodes](../admin/adding-nodes.mdx) for how to set up a Teleport Node.
- The following software installed on your Teleport Node: `curl`, `python`, and
the `aws` CLI, which comes from the `awscli` Python package.

## Step 1/3. Deploy the script
## Enable tags in instance metadata

You’ll need a script on your EC2 instance that can query the AWS API and get the
values of your instance's tags for you. The Teleport Node will then use these
values to execute RBAC rules.
To allow Teleport to import EC2 tags, tags must be enabled in the instance metadata. This can be done
via the AWS console or the AWS CLI. See the [AWS documentation](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html#allow-access-to-tags-in-IMDS)
for more details.

Here’s one script you can use:
<Admonition type="note" title="Note">
Only instances that are running on the [Nitro system](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html#ec2-nitro-instances)
will update their tags while running. All other instance types [must be restarted](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html#work-with-tags-in-IMDS)
to update tags.
</Admonition>

```code
#!/bin/bash
if [[ "$1" == "" ]]; then
echo "Usage: $(basename $0) <tag>"
exit 1
fi
TAG_NAME=$1

IMDS_TOKEN=$(curl -sS -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 300")
IMDS_TOKEN_HEADER="-H \"X-aws-ec2-metadata-token: ${IMDS_TOKEN}\""
INSTANCE_ID=$(curl -sS "${IMDS_TOKEN_HEADER}" http://169.254.169.254/latest/meta-data/instance-id)
REGION=$(curl -sS "${IMDS_TOKEN_HEADER}" http://169.254.169.254/latest/meta-data/placement/availability-zone | sed -e "s:\([0-9][0-9]*\)[a-z]*\$:\\1:")
TAG_VALUE="$(aws ec2 describe-tags --filters "Name=resource-id,Values=$INSTANCE_ID" "Name=key,Values=$TAG_NAME" --region $REGION --output=text | cut -f5)"

if [[ "${TAG_VALUE}" == "" ]]; then
echo "<null>"
else
echo $TAG_VALUE
fi
```
### AWS EC2 Console

Save this script to `/usr/local/bin/get-tag.sh` on your EC2 instance.
Run the command below to make it executable:
To launch a new instance with instance metadata tags enabled:
1. Open `Advanced Options` at the bottom of the page.
2. Ensure that `Metadata accessible` is not disabled.
3. Enable `Allow tags in metadata`.

```code
$ chmod +x /usr/local/bin/get-tag.sh
```
<Figure align="left" bordered caption="Advanced Options">
![Advanced Options](../../../img/aws/launch-instance-advanced-options.png)
</Figure>

## Step 2/3. Set up an IAM role
To modify an existing instance to enable instance metadata tags:
1. From the instance summary, go to `Actions > Instance Settings > Allow tags in instance metadata`.
2. Enable `Allow`.

Grant your EC2 instance an IAM role that will allow it to query tag values for EC2 instances.
<Figure align="left" bordered caption="Instance Settings">
![Instance Settings](../../../img/aws/instance-settings.png)
</Figure>

Here’s an example policy which you can add to an IAM role:
<Figure align="left" bordered caption="Allow Tags">
![Allow Tags](../../../img/aws/allow-tags.png)
</Figure>

### AWS CLI

```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2:DescribeTags",
"Resource": "*"
}
]
}
```

Once this is done, query the value of the test tag on your EC2 instance by running the following command:
To modify the instance at launch:

```code
$ /usr/local/bin/get-tag.sh test
tagValue
$ aws ec2 run-instances \
--image-id <image-id> \
--instance-type <instance-type> \
--metadata-options "InstanceMetadataTags=enabled"
...
```

## Step 3/3. Modify the Teleport Node config file
To modify a running instance:
atburke marked this conversation as resolved.
Show resolved Hide resolved

Modify the Teleport config file on your node (`/etc/teleport.yaml`) as follows:

```yaml
teleport:
ssh_service:
enabled: yes
listen_addr: 0.0.0.0:3022
commands:
- name: aws_tag_test
command: ['/usr/local/bin/get-tag.sh', 'test']
period: 1h
```

This config will add a label with the key `aws_tag_test` to your instance. Its value will be set to whatever the `test` tag is set to and it will be updated once every hour.

Restart Teleport on the node and you should see the new label appear:

```txt
Node Name Address Labels
----------------------------- ----------------------------------------------------------------------- -------------------------------------------------------------------------------------------
example 172.31.26.55:3022 aws_tag_test=tagValue
```

Now you have a label on the instance which you can use inside a Teleport role. Here’s an example role:

```yaml
kind: role
version: v5
metadata:
name: test-tag-role
spec:
allow:
logins:
- ec2-user
node_labels:
'aws_tag_test': 'tagValue'
deny: {}
options:
cert_format: standard
forward_agent: true
max_session_ttl: 2h0m0s
port_forwarding: true
```code
$ aws ec2 modify-instance-metadata-options \
--instance-id i-123456789example \
--instance-metadata-tags enabled
```

When assigned to Teleport users, this role will only allow them to log in to
Teleport Nodes which have the `aws_tag_test` label with the value of `tagValue`,
effectively gating access to infrastructure based on the value of the EC2 `test`
tag.

By adding multiple commands to a Teleport Node, setting the values of different
tags, then adding Teleport roles that reference these tags, you can build
fine-grained RBAC systems based on your EC2 tagging structure.
Loading