# Running an HA Teleport Cluster Using AWS, EKS, and Helm

In this guide, we'll use Teleport Helm charts to set up a high-availability Teleport cluster that runs on AWS EKS.

---

TIP

Teleport Enterprise Cloud takes care of this setup for you so you can provide secure access to your infrastructure right away.

Get started with a [free trial](https://goteleport.com/signup?t_source=docs) of Teleport Enterprise Cloud.

---

## How it works

The `teleport-cluster` Helm chart deploys the Teleport Auth Service and Teleport Proxy Service on your Amazon Elastic Kubernetes Service cluster, implementing the architecture described in [Deploying a High Availability Teleport Cluster](https://goteleport.com/docs/zero-trust-access/deploy-a-cluster/deployments/high-availability.md). The chart requires the following resources, which we show you how to create in this guide:

- **IAM permissions for the Teleport Auth Service**. The Auth Service requires permissions to manage resources on its backend.
- **A system to provision TLS credentials** that the Proxy Service uses to run its HTTPS server. In this guide, we show you how to do this with `cert-manager`, the recommended approach. Depending on the method you choose, you may also need to create IAM permissions to allow the provisioning system to interact with AWS APIs.
- **Amazon S3 bucket and DynamoDB database** for the Teleport Auth Service backend.

## Prerequisites

We recommend provisioning the Teleport Proxy Service with TLS credentials using `cert-manager`, so you should become familiar with `cert-manager` before beginning this guide by reading the [documentation](https://cert-manager.io/docs/).

This guide assumes that your infrastructure is **not** hosted in AWS GovCloud or in an air-gapped AWS environment (EKS Anywhere). If it is, [contact the Teleport team](https://goteleport.com/contact-us) for assistance planning your deployment.

This guide also requires the following:

- [Kubernetes](https://kubernetes.io) >= v1.17.0
- [Helm](https://helm.sh) >= v3.4.2

Teleport's charts require the use of Helm version 3. You can [install Helm 3 by following these instructions](https://helm.sh/docs/intro/install/).

Throughout this guide, we will assume that you have the `helm` and `kubectl` binaries available in your `PATH`:

```
$ helm version
version.BuildInfo{Version:"v3.4.2"}

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"17+"}
Server Version: version.Info{Major:"1", Minor:"17+"}
```

Best practices for production security

When running Teleport in production, you should adhere to the following best practices to avoid security incidents:

- Avoid using `sudo` in production environments unless it's necessary.
- Create new, non-root, users and use test instances for experimenting with Teleport.
- Run Teleport's services as a non-root user unless required. Only the SSH Service requires root access. Note that you will need root permissions (or the `CAP_NET_BIND_SERVICE` capability) to make Teleport listen on a port numbered < `1024` (e.g. `443`).
- Follow the **principle of least privilege**. Don't give users permissive roles when more a restrictive role will do. For example, don't assign users the built-in `access,editor` roles, which give them permissions to access and edit all cluster resources. Instead, define roles with the minimum required permissions for each user and configure **Access Requests** to provide temporary elevated permissions.
- When you enroll Teleport resources—for example, new databases or applications—you should save the invitation token to a file. If you enter the token directly on the command line, a malicious user could view it by running the `history` command on a compromised system.

You should note that these practices aren't necessarily reflected in the examples used in documentation. Examples in the documentation are primarily intended for demonstration and for development environments.

### Choose a Kubernetes namespace and Helm release name

Before starting, setting your Kubernetes namespace and Helm release name here will enable easier copy/pasting of commands for installation.

If you don't know what to put here, use `teleport` for both values.

Namespace: namespace

Release name: release-name

## Step 1/7. Install Helm

Teleport's charts require the use of Helm version 3. You can [install Helm 3 by following these instructions](https://helm.sh/docs/intro/install/).

Throughout this guide, we will assume that you have the `helm` and `kubectl` binaries available in your `PATH`:

```
$ helm version
version.BuildInfo{Version:"v3.4.2"}

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"17+"}
Server Version: version.Info{Major:"1", Minor:"17+"}
```

## Step 2/7. Add the Teleport Helm chart repository

Configure Helm to fetch Teleport charts from the Teleport Helm repository:

```
$ helm repo add teleport https://charts.releases.teleport.dev
```

Refresh the local Helm cache by fetching the latest charts:

```
$ helm repo update
```

## Step 3/7. Set up AWS IAM configuration

For Teleport to be able to manage the DynamoDB tables, indexes, and the S3 storage bucket it needs, you'll need to configure AWS IAM policies to allow access.

---

NOTE

Add these IAM policies to your AWS account and then grant it to the role associated with your EKS node group(s).

---

### DynamoDB IAM policy

On startup, the Teleport Auth Service checks whether the DynamoDB table you have specified in its configuration file exists. If the table does not exist, the Auth Service attempts to create one.

The IAM permissions that the Auth Service requires to manage DynamoDB tables depends on whether you expect to create a table yourself or enable the Auth Service to create and configure one for you:

**Self-Managed**

If you choose to manage DynamoDB tables yourself, you must take the following steps, which we will explain in more detail below:

- Create a cluster state table.
- Create an audit event table.
- Create an IAM policy and attach it to the Teleport Auth Service's IAM identity.

#### Create a cluster state table

The cluster state table must have the following attribute definitions:

| Name       | Type |
| ---------- | ---- |
| `HashKey`  | `S`  |
| `FullPath` | `S`  |

The table must also have the following key schema elements:

| Name       | Type    |
| ---------- | ------- |
| `HashKey`  | `HASH`  |
| `FullPath` | `RANGE` |

#### Create an audit event table

The audit event table must have the following attribute definitions:

| Name            | Type |
| --------------- | ---- |
| `SessionID`     | `S`  |
| `EventIndex`    | `N`  |
| `CreatedAtDate` | `S`  |
| `CreatedAt`     | `N`  |

The table must have the following key schema elements:

| Name         | Type    |
| ------------ | ------- |
| `SessionID`  | `HASH`  |
| `EventIndex` | `RANGE` |

The table must also have a global secondary index named `timesearchV2` with the following key schema elements:

| Name            | Type    |
| --------------- | ------- |
| `CreatedAtDate` | `HASH`  |
| `CreatedAt`     | `RANGE` |

#### Create and attach an IAM policy

Create the following IAM policy and attach it to the Teleport Auth Service's IAM identity.

You'll need to replace these values in the policy example below:

| Placeholder value     | Replace with                                                                                       |
| --------------------- | -------------------------------------------------------------------------------------------------- |
| us-west-2             | AWS region                                                                                         |
| 1234567890            | AWS account ID                                                                                     |
| teleport-helm-backend | DynamoDB table name to use for the Teleport backend                                                |
| teleport-helm-events  | DynamoDB table name to use for the Teleport audit log (**must** be different to the backend table) |

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ClusterStateStorage",
            "Effect": "Allow",
            "Action": [
                "dynamodb:BatchWriteItem",
                "dynamodb:UpdateTimeToLive",
                "dynamodb:PutItem",
                "dynamodb:DeleteItem",
                "dynamodb:Scan",
                "dynamodb:Query",
                "dynamodb:DescribeStream",
                "dynamodb:UpdateItem",
                "dynamodb:DescribeTimeToLive",
                "dynamodb:DescribeTable",
                "dynamodb:GetShardIterator",
                "dynamodb:GetItem",
                "dynamodb:ConditionCheckItem",
                "dynamodb:UpdateTable",
                "dynamodb:GetRecords",
                "dynamodb:UpdateContinuousBackups"
            ],
            "Resource": [
                "arn:aws:dynamodb:us-west-2:1234567890:table/teleport-helm-backend",
                "arn:aws:dynamodb:us-west-2:1234567890:table/teleport-helm-backend/stream/*"
            ]
        },
        {
            "Sid": "ClusterEventsStorage",
            "Effect": "Allow",
            "Action": [
                "dynamodb:BatchWriteItem",
                "dynamodb:UpdateTimeToLive",
                "dynamodb:PutItem",
                "dynamodb:DescribeTable",
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:Scan",
                "dynamodb:Query",
                "dynamodb:UpdateItem",
                "dynamodb:DescribeTimeToLive",
                "dynamodb:UpdateTable",
                "dynamodb:UpdateContinuousBackups"
            ],
            "Resource": [
                "arn:aws:dynamodb:us-west-2:1234567890:table/teleport-helm-events",
                "arn:aws:dynamodb:us-west-2:1234567890:table/teleport-helm-events/index/*"
            ]
        }
    ]
}

```

Note that you can omit the `dynamodb:UpdateContinuousBackups` permission if disabling continuous backups.

**Created by the Auth Service**

You'll need to replace these values in the policy example below:

| Placeholder value     | Replace with                                                                                       |
| --------------------- | -------------------------------------------------------------------------------------------------- |
| us-west-2             | AWS region                                                                                         |
| 1234567890            | AWS account ID                                                                                     |
| teleport-helm-backend | DynamoDB table name to use for the Teleport backend                                                |
| teleport-helm-events  | DynamoDB table name to use for the Teleport audit log (**must** be different to the backend table) |

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ClusterStateStorage",
            "Effect": "Allow",
            "Action": [
                "dynamodb:BatchWriteItem",
                "dynamodb:UpdateTimeToLive",
                "dynamodb:PutItem",
                "dynamodb:DeleteItem",
                "dynamodb:Scan",
                "dynamodb:Query",
                "dynamodb:DescribeStream",
                "dynamodb:UpdateItem",
                "dynamodb:DescribeTimeToLive",
                "dynamodb:CreateTable",
                "dynamodb:DescribeTable",
                "dynamodb:GetShardIterator",
                "dynamodb:GetItem",
                "dynamodb:ConditionCheckItem",
                "dynamodb:UpdateTable",
                "dynamodb:GetRecords",
                "dynamodb:UpdateContinuousBackups"
            ],
            "Resource": [
                "arn:aws:dynamodb:us-west-2:1234567890:table/teleport-helm-backend",
                "arn:aws:dynamodb:us-west-2:1234567890:table/teleport-helm-backend/stream/*"
            ]
        },
        {
            "Sid": "ClusterEventsStorage",
            "Effect": "Allow",
            "Action": [
                "dynamodb:CreateTable",
                "dynamodb:BatchWriteItem",
                "dynamodb:UpdateTimeToLive",
                "dynamodb:PutItem",
                "dynamodb:DescribeTable",
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:Scan",
                "dynamodb:Query",
                "dynamodb:UpdateItem",
                "dynamodb:DescribeTimeToLive",
                "dynamodb:UpdateTable",
                "dynamodb:UpdateContinuousBackups"
            ],
            "Resource": [
                "arn:aws:dynamodb:us-west-2:1234567890:table/teleport-helm-events",
                "arn:aws:dynamodb:us-west-2:1234567890:table/teleport-helm-events/index/*"
            ]
        }
    ]
}

```

### S3 IAM policy

On startup, the Teleport Auth Service checks whether the S3 bucket you have configured for session recording storage exists. If it does not, the Auth Service attempts to create and configure the bucket.

The IAM permissions that the Auth Service requires to manage its session recording bucket depends on whether you expect to create the bucket yourself or enable the Auth Service to create and configure it for you:

**Self-Managed**

Note that Teleport will only use S3 buckets with versioning enabled. This ensures that a session log cannot be permanently altered or deleted, as Teleport will always look at the oldest version of a recording.

You'll need to replace these values in the policy example below:

| Placeholder value    | Replace with                                             |
| -------------------- | -------------------------------------------------------- |
| your-sessions-bucket | Name to use for the Teleport S3 session recording bucket |

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "BucketActions",
            "Effect": "Allow",
            "Action": [
                "s3:ListBucketVersions",
                "s3:ListBucketMultipartUploads",
                "s3:ListBucket",
                "s3:GetEncryptionConfiguration",
                "s3:GetBucketVersioning"
            ],
            "Resource": "arn:aws:s3:::your-sessions-bucket"
        },
        {
            "Sid": "ObjectActions",
            "Effect": "Allow",
            "Action": [
                "s3:GetObjectVersion",
                "s3:GetObjectRetention",
                "s3:GetObject",
                "s3:PutObject",
                "s3:ListMultipartUploadParts",
                "s3:AbortMultipartUpload"
            ],
            "Resource": "arn:aws:s3:::your-sessions-bucket/*"
        }
    ]
}

```

**Created by the Auth Service**

You'll need to replace these values in the policy example below:

| Placeholder value    | Replace with                                             |
| -------------------- | -------------------------------------------------------- |
| your-sessions-bucket | Name to use for the Teleport S3 session recording bucket |

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "BucketActions",
            "Effect": "Allow",
            "Action": [
                "s3:PutEncryptionConfiguration",
                "s3:PutBucketVersioning",
                "s3:ListBucketVersions",
                "s3:ListBucketMultipartUploads",
                "s3:ListBucket",
                "s3:GetEncryptionConfiguration",
                "s3:GetBucketVersioning",
                "s3:CreateBucket"
            ],
            "Resource": "arn:aws:s3:::your-sessions-bucket"
        },
        {
            "Sid": "ObjectActions",
            "Effect": "Allow",
            "Action": [
                "s3:GetObjectVersion",
                "s3:GetObjectRetention",
                "s3:*Object",
                "s3:ListMultipartUploadParts",
                "s3:AbortMultipartUpload"
            ],
            "Resource": "arn:aws:s3:::your-sessions-bucket/*"
        }
    ]
}

```

### Route53 IAM policy

This policy allows `cert-manager` to use DNS01 Let's Encrypt challenges to provision TLS certificates for your Teleport cluster.

You'll need to replace these values in the policy example below:

| Placeholder value     | Replace with                                                         |
| --------------------- | -------------------------------------------------------------------- |
| Z0159221358P96JYAUAA4 | Route 53 hosted zone ID for the domain hosting your Teleport cluster |

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "route53:GetChange",
            "Resource": "arn:aws:route53:::change/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "route53:ChangeResourceRecordSets",
                "route53:ListResourceRecordSets"
            ],
            "Resource": "arn:aws:route53:::hostedzone/Z0159221358P96JYAUAA4"
        }
    ]
}

```

## Step 4/7. Configure TLS certificates for Teleport

We now need to configure TLS certificates for Teleport to secure its communications and allow external clients to connect.

We recommend using `cert-manager` to provision and automatically renew TLS credentials by completing ACME challenges via Let's Encrypt. In this guide, we are using multiple pods to create a High Availability Teleport cluster. This setup mounts TLS credentials into each Teleport pod, and `cert-manager` automatically renews certificates and keeps them up to date.

This method uses a Kubernetes `LoadBalancer`, which will provision an underlying AWS Network Load Balancer (NLB) to handle incoming traffic.

You can also use `cert-manager` with AWS Private Certificate Authority (PCA) in EKS using the `aws-privateca-issuer` plugin.

### Install cert-manager

If you do not have `cert-manager` already configured in the Kubernetes cluster where you are installing Teleport, you should add the Jetstack Helm chart repository which hosts the `cert-manager` chart, and install the chart:

```
$ helm repo add jetstack https://charts.jetstack.io
$ helm repo update
$ helm install cert-manager jetstack/cert-manager \
--create-namespace \
--namespace cert-manager \
--set installCRDs=true \
--set global.leaderElection.namespace=cert-manager \
--set extraArgs="{--issuer-ambient-credentials}" # required to automount ambient AWS credentials when using an Issuer
```

### Create an Issuer

Next, create a `cert-manager` Issuer resource. You'll need to replace these values in the Issuer example below:

| Placeholder value     | Replace with                                                         |
| --------------------- | -------------------------------------------------------------------- |
| email\@address.com    | An email address to receive communications from Let's Encrypt        |
| example.com           | The name of the Route 53 domain hosting your Teleport cluster        |
| us-west-2             | AWS region where the cluster is running                              |
| Z0159221358P96JYAUAA4 | Route 53 hosted zone ID for the domain hosting your Teleport cluster |

Run the following command to create a manifest:

```
cat << EOF > aws-issuer.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: letsencrypt-production
  namespace: teleport
spec:
  acme:
    email: email@address.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-production
    solvers:
    - selector:
        dnsZones:
          - "example.com"
      dns01:
        route53:
          region: us-west-2
          hostedZoneID: Z0159221358P96JYAUAA4
EOF

```

After you have created the `Issuer` and updated the values, add it to your cluster using `kubectl`:

```
$ kubectl create namespace namespace
$ kubectl label namespace teleport 'pod-security.kubernetes.io/enforce=baseline'
$ kubectl --namespace namespace create -f aws-issuer.yaml
```

## Step 5/7. Set values to configure the cluster

If you run Teleport Enterprise, you will need to create a secret that contains your Teleport license information before you can install Teleport in your Kubernetes cluster.

The Teleport Auth Service reads a license file to authenticate your Teleport Enterprise account.

To obtain your license file, navigate to your Teleport account dashboard and log in. You can start at [teleport.sh](https://teleport.sh) and enter your Teleport account name (e.g. my-company). After logging in you will see a "GENERATE LICENSE KEY" button, which will generate a new license file and allow you to download it.

![License File Download](/docs/assets/images/license-927c35c955fa526748558be430c4b102.png)

Create a secret from your license file. Teleport will automatically discover this secret as long as your file is named `license.pem`.

```
$ kubectl -n namespace create secret generic license --from-file=license.pem
```

Next, configure the `teleport-cluster` Helm chart to use the `aws` mode. Create a file called `aws-values.yaml` and write the values you've chosen above to it:

```
chartMode: aws
clusterName: teleport.example.com                 # Name of your cluster. Use the FQDN you intend to configure in DNS below.
proxyListenerMode: multiplex
aws:
  region: us-west-2                # AWS region
  backendTable: teleport-helm-backend # DynamoDB table to use for the Teleport backend
  auditLogTable: teleport-helm-events             # DynamoDB table to use for the Teleport audit log (must be different to the backend table)
  auditLogMirrorOnStdout: false                   # Whether to mirror audit log entries to stdout in JSON format (useful for external log collectors)
  sessionRecordingBucket: your-sessions-bucket  # S3 bucket to use for Teleport session recordings
  backups: true                                   # Whether or not to turn on DynamoDB backups
  dynamoAutoScaling: false                        # Whether Teleport should configure DynamoDB's autoscaling.
highAvailability:
  replicaCount: 2                                 # Number of replicas to configure
  certManager:
    enabled: true                                 # Enable cert-manager support to get TLS certificates
    issuerName: letsencrypt-production            # Name of the cert-manager Issuer to use (as configured above)
# Indicate that this is a Teleport Enterprise deployment. Set to false for
# Teleport Community Edition.
enterprise: true                                  
# If you are running Kubernetes 1.23 or above, disable PodSecurityPolicies
podSecurityPolicy:
  enabled: false

```

---

NOTE

If using an AWS PCA with cert-manager, you will need to [ensure you set](https://goteleport.com/docs/reference/helm-reference/teleport-cluster.md) `highAvailability.certManager.addCommonName: true` in your values file. You will also need to get the certificate authority certificate for the CA (`aws acm-pca get-certificate-authority-certificate --certificate-authority-arn <arn>`), upload the full certificate chain to a secret, and [reference the secret](https://goteleport.com/docs/reference/helm-reference/teleport-cluster.md) with `tls.existingCASecretName` in the values file.

---

Install the chart with the values from your `aws-values.yaml` file using this command:

```
$ helm install release-name teleport/teleport-cluster \
  --create-namespace \
  --namespace namespace \
  -f aws-values.yaml
```

---

NOTE

You cannot change the `clusterName` after the cluster is configured, so make sure you choose wisely. You should use the fully-qualified domain name that you'll use for external access to your Teleport cluster.

---

Once the chart is installed, you can use `kubectl` commands to view the deployment (example using `cert-manager`):

```
$ kubectl --namespace namespace get all

NAME                                 READY   STATUS    RESTARTS   AGE
pod/teleport-auth-57989d4cbd-4q2ds   1/1     Running   0          22h
pod/teleport-auth-57989d4cbd-rtrzn   1/1     Running   0          22h
pod/teleport-proxy-c6bf55cfc-w96d2   1/1     Running   0          22h
pod/teleport-proxy-c6bf55cfc-z256w   1/1     Running   0          22h

NAME                        TYPE           CLUSTER-IP     EXTERNAL-IP                         PORT(S)                                                                     AGE
service/teleport            LoadBalancer   10.40.11.180   xxxxx.elb.us-east-1.amazonaws.com   443:30258/TCP                                                               22h
service/teleport-auth       ClusterIP      10.40.8.251    <none>                              3025/TCP,3026/TCP                                                           22h
service/teleport-auth-v11   ClusterIP      None           <none>                              <none>                                                                      22h
service/teleport-auth-v12   ClusterIP      None           <none>                              <none>                                                                      22h

NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/teleport-auth    2/2     2            2           22h
deployment.apps/teleport-proxy   2/2     2            2           22h

NAME                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/teleport-auth-57989d4cbd   2         2         2       22h
replicaset.apps/teleport-proxy-c6bf55cfc   2         2         2       22h
```

## Step 6/7. Set up DNS

You'll need to set up a DNS `A` record for `teleport.example.com`. In our example, this record is an alias to an ELB.

Enrolling applications with Teleport?

Once the Teleport Application Service is proxying traffic to your web application, the Teleport Proxy Service makes the application available at the following URL:

```
https://<APPLICATION_NAME>.<TELEPORT_DOMAIN>

```

For example, if your Teleport domain name is `teleport.example.com`, the application named `my-app` would be available at `https://my-app.teleport.example.com`. The Proxy Service must present a TLS certificate for this domain name that browsers can verify against a certificate authority.

If you are using Teleport Enterprise (Cloud), DNS records and TLS certificates for this domain name are provisioned automatically. If you are self-hosting Teleport, you must configure these yourself:

1. Create either:

   - A DNS A record that associates a wildcard subdomain of your Teleport Proxy Service domain, e.g., `*.teleport.example.com`, with the IP address of the Teleport Proxy Service.
   - A DNS CNAME record that associates a wildcard subdomain of your Proxy Service domain, e.g., `*.teleport.example.com`, with the domain name of the Teleport Proxy Service.

2. Ensure that your system provisions TLS certificates for Teleport-registered applications. The method to use depends on how you originally set up TLS for your self-hosted Teleport deployment, and is outside the scope of this guide.

   In general, the same system that provisions TLS certificates signed for the web address of the Proxy Service (e.g., `teleport.example.com`) must also provision certificates for the wildcard address used for applications (e.g., `*.teleport.example.com`).

Take care not to create DNS records that map the Teleport cluster subdomain of a registered application to the application's own host, as attempts to navigate to the application will fail.

Here's how to do this in a hosted zone with Amazon Route 53:

```
Change these parameters if you altered them above
$ NAMESPACE='namespace'
$ RELEASE_NAME='release-name'

DNS settings (change as necessary)
$ MYZONE_DNS='example.com'
$ MYDNS='teleport.example.com'
$ MY_CLUSTER_REGION='us-west-2'

Find AWS Zone ID and ELB Zone ID
$ MYZONE="$(aws route53 list-hosted-zones-by-name --dns-name="${MYZONE_DNS?}" | jq -r '.HostedZones[0].Id' | sed s_/hostedzone/__)"
$ MYELB="$(kubectl --namespace "${NAMESPACE?}" get "service/${RELEASE_NAME?}-proxy" -o jsonpath='{.status.loadBalancer.ingress[*].hostname}')"
$ MYELB_NAME="${MYELB%%-*}"
$ MYELB_ZONE="$(aws elbv2 describe-load-balancers --region "${MY_CLUSTER_REGION?}" --names "${MYELB_NAME?}" | jq -r '.LoadBalancers[0].CanonicalHostedZoneId')"

Create a JSON file changeset for AWS.
$ jq -n --arg dns "${MYDNS?}" --arg elb "${MYELB?}" --arg elbz "${MYELB_ZONE?}" \
    '{
        "Comment": "Create records",
        "Changes": [
          {
            "Action": "CREATE",
            "ResourceRecordSet": {
              "Name": $dns,
              "Type": "A",
              "AliasTarget": {
                "HostedZoneId": $elbz,
                "DNSName": ("dualstack." + $elb),
                "EvaluateTargetHealth": false
              }
            }
          },
          {
            "Action": "CREATE",
            "ResourceRecordSet": {
              "Name": ("*." + $dns),
              "Type": "A",
              "AliasTarget": {
                "HostedZoneId": $elbz,
                "DNSName": ("dualstack." + $elb),
                "EvaluateTargetHealth": false
              }
            }
          }
      ]
    }' > myrecords.json

Review records before applying.
$ cat myrecords.json | jq
Apply the records and capture change id
$ CHANGEID="$(aws route53 change-resource-record-sets --hosted-zone-id "${MYZONE?}" --change-batch file://myrecords.json | jq -r '.ChangeInfo.Id')"

Verify that change has been applied
$ aws route53 get-change --id "${CHANGEID?}" | jq '.ChangeInfo.Status'
"INSYNC"
```

## Step 7/7. Create a Teleport user

Create a user to be able to log into Teleport. This needs to be done on the Teleport Auth Service, so we can run the command using `kubectl`:

**Teleport Community Edition**

```
$ kubectl --namespace namespace exec deploy/release-name-auth -- tctl users add test --roles=access,editor

User "test" has been created but requires a password. Share this URL with the user to complete user setup, link is valid for 1h:
https://teleport.example.com:443/web/invite/91cfbd08bc89122275006e48b516cc68

NOTE: Make sure teleport.example.com:443 points at a Teleport proxy that users can access.
```

**Commercial**

```
$ kubectl --namespace namespace exec deploy/release-name-auth -- tctl users add test --roles=access,editor,reviewer

User "test" has been created but requires a password. Share this URL with the user to complete user setup, link is valid for 1h:
https://teleport.example.com:443/web/invite/91cfbd08bc89122275006e48b516cc68

NOTE: Make sure teleport.example.com:443 points at a Teleport proxy that users can access.
```

Load the user creation link to create a password and set up multi-factor authentication for the Teleport user via the web UI.

### High Availability

In this guide, we have configured two replicas. This can be changed after cluster creation by altering the `highAvailability.replicaCount` value [using `helm upgrade` as detailed below](#upgrading-the-cluster-after-deployment).

### Upgrading the cluster after deployment

To make changes to your Teleport cluster after deployment, you can use `helm upgrade`.

Helm defaults to using the latest version of the chart available in the repo, which will also correspond to the latest version of Teleport. You can make sure that the repo is up to date by running `helm repo update`.

Here's an example where we set the chart to use 2 replicas:

Edit your `aws-values.yaml` file from above and make the appropriate changes:

```
highAvailability:
  replicaCount: 2

```

Upgrade the deployment with the values from your `aws-values.yaml` file using this command:

```
$ helm upgrade release-name teleport/teleport-cluster \
  --namespace namespace \
  -f aws-values.yaml
```

---

NOTE

To change `chartMode`, `clusterName`, or any `aws` settings, you must first uninstall the existing chart and then install a new version with the appropriate values.

---

Then perform a cluster upgrade with the new values:

```
$ helm upgrade release-name teleport/teleport-cluster \
  --namespace namespace \
  -f aws-values.yaml
```

## Uninstalling Teleport

To uninstall the `teleport-cluster` chart, use `helm uninstall <release-name>`. For example:

```
$ helm --namespace namespace uninstall release-name
```

### Uninstalling cert-manager

If you want to remove the `cert-manager` installation later, you can use this command:

```
$ helm --namespace cert-manager uninstall cert-manager
```

## Troubleshooting AWS quotas

If your deployment of Teleport services brings you over your default service quotas, you can request a quota increase from the AWS Support Center. See Amazon's [AWS service quotas](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html) documentation for more information.

For example, when using DynamoDB as the backend for Teleport cluster state, you may need to request increases for read/write quotas.

## Additional ways to provision TLS certificates

We recommend using `cert-manager` to provision TLS credentials and an AWS Network Load Balancer to provide connectivity to the Teleport Proxy Service, as shown in this guide. This approach allows Teleport to operate at the expected performance and support native clients (like `kubectl` and database clients) via server name indication (SNI).

If your infrastructure does not support the approach we outline in this guide, we recommend that you [contact the Teleport team](https://goteleport.com/contact-us/). This section includes examples of taking two alternative approaches: static TLS certificates and Amazon Certificate Manager.

### Static TLS certificates

When you provision Teleport with static TLS certificates, you are responsible for determining how to obtain a TLS certificate and private key for your Teleport cluster, and for renewing your credentials periodically. Use this approach if you would like to use a trusted internal certificate authority instead of Let's Encrypt. As with `cert-manager`, this method uses a Kubernetes `LoadBalancer` and will provision an underlying AWS NLB.

Configure the `teleport-cluster` Helm chart to secure the Teleport Web UI using existing TLS credentials within a Kubernetes secret.

Use the following command to create your secret:

```
$ kubectl -n namespace create secret tls my-tls-secret --cert=/path/to/cert/file --key=/path/to/key/file
```

Edit the `teleport-cluster` chart values file to refer to the name of your secret:

```
  tls:
    existingSecretName: my-tls-secret

```

### AWS Certificate Manager

If your organization cannot accommodate using `cert-manager` or a trusted internal certificate authority with a Network Load Balancer, you can use AWS Certificate Manager to handle TLS termination with AWS-managed certificates. This method uses a Kubernetes `Ingress`, which can provision an underlying AWS Application Load Balancer (ALB) to handle incoming traffic if one does not already exist. It also requires the installation and setup of the AWS Load Balancer controller.

You should be aware of these potential limitations and differences when using Layer 7 load balancers with Teleport:

- Connecting to Kubernetes clusters at the command line requires the use of the `tsh proxy kube` or `tsh kubectl` commands and `tsh proxy db`/`tsh db connect` commands respectively. It is not possible to connect `kubectl` directly to Teleport listeners without the use of `tsh` as a proxy client in this mode.
- Connecting to databases at the command line requires the use of the `tsh proxy db` or `tsh db connect` commands. It is not possible to connect database clients directly to Teleport listeners without the use of `tsh` as a proxy client in this mode.
- The reason for both of these requirements is that Teleport uses X509 certificates for authentication, which requires that it terminate all inbound TLS traffic itself on the Teleport proxy. This is not directly possible when using a Layer 7 load balancer, so the `tsh` client implements this flow itself [using ALPN connection upgrades](https://goteleport.com/docs/reference/architecture/tls-routing.md).

To use ACM in your cluster, make the following adjustments to the steps you followed above:

1. Either follow the [AWS-maintained documentation on installing the AWS Load Balancer Controller](https://docs.aws.amazon.com/eks/latest/userguide/aws-load-balancer-controller.html) or ensure that you already have a working installation of the AWS LB controller before continuing with these instructions. Failure to do this will result in an unusable Teleport cluster.

   Assuming you follow the AWS guide linked above, you can check whether the AWS LB controller is running in your cluster by looking for pods with the `app.kubernetes.io/name=aws-load-balancer-controller` label:

   ```
   $ kubectl get pods -A -l app.kubernetes.io/name=aws-load-balancer-controller
   NAMESPACE     NAME                                            READY   STATUS    RESTARTS   AGE
   kube-system   aws-load-balancer-controller-655f647b95-5vz56   1/1     Running   0          109d
   kube-system   aws-load-balancer-controller-655f647b95-b4brx   1/1     Running   0          109d
   ```

   You can also check whether `alb` is registered as an `IngressClass` in your cluster:

   ```
   $ kubectl get ingressclass
   NAME    CONTROLLER             PARAMETERS   AGE
   alb     ingress.k8s.aws/alb    <none>       109d
   ```

2. Add annotations to the chart using the `teleport-cluster` values file to allow ACM to handle TLS. These specify the ACM certificate ARN to use, the port it should be served on and other ALB configuration parameters.

   Replace arn:aws:acm:us-west-2:1234567890:certificate/12345678-43c7-4dd1-a2f6-c495b91ebece with your actual ACM certificate ARN:

   ```
   service:
     type: ClusterIP
   ingress:
     enabled: true
     spec:
       ingressClassName: alb
   annotations:
     ingress:
       alb.ingress.kubernetes.io/target-type: ip
       alb.ingress.kubernetes.io/backend-protocol: HTTPS
       alb.ingress.kubernetes.io/scheme: internet-facing
       alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=350
       alb.ingress.kubernetes.io/healthcheck-protocol: HTTPS
       alb.ingress.kubernetes.io/success-codes: 200,301,302
       # Replace with your AWS certificate ARN
       alb.ingress.kubernetes.io/certificate-arn: "arn:aws:acm:us-west-2:1234567890:certificate/12345678-43c7-4dd1-a2f6-c495b91ebece"

   ```

   To use an internal AWS Application Load Balancer (as opposed to an internet-facing ALB), you should edit the `alb.ingress.kubernetes.io/scheme` annotation:

   ```
     alb.ingress.kubernetes.io/scheme: internal

   ```

   To automatically redirect HTTP requests on port 80 to HTTPS requests on port 443, you can also optionally provide these two values under `annotations.ingress`:

   ```
     alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
     alb.ingress.kubernetes.io/ssl-redirect: '443'

   ```

3. Determine the address of your load balancer so you can create a DNS record as explained in [Step 6](#step-67-set-up-dns). In this case, the address is the host name of the Kubernetes ingress created by the `teleport-cluster` chart:

   ```
   $ MYELB="$(kubectl --namespace "${NAMESPACE?}" get "ingress/${RELEASE_NAME?}-proxy" -o jsonpath='{.status.loadBalancer.ingress[*].hostname}')"
   $ MYELB_ROOT="${MYELB%%.*}"
   $ MYELB_NAME="${MYELB_ROOT%-*}"
   ```

## Next steps

Now that you have deployed a Teleport cluster, read the [Manage Access](https://goteleport.com/docs/zero-trust-access/authentication.md) section to get started enrolling users and setting up RBAC.

See the [high availability section of our Helm chart reference](https://goteleport.com/docs/reference/helm-reference/teleport-cluster.md) for more details on high availability.
