From 43ed6659a1763d639d7119758238e8ee5b5e27a4 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Tue, 3 Sep 2024 21:02:09 +0100 Subject: [PATCH] AWS tutorial Signed-off-by: Richard Wall --- .spelling | 1 + .../docs/configuration/acme/dns01/route53.md | 14 +- content/docs/getting-started/README.md | 11 + content/docs/manifest.json | 6 +- content/docs/tutorials/README.md | 2 + .../getting-started-aws-letsencrypt/README.md | 652 ++++++++++++++++++ .../certificate.yaml | 20 + ...clusterissuer-lets-encrypt-production.yaml | 20 + .../clusterissuer-lets-encrypt-staging.yaml | 20 + .../clusterissuer-selfsigned.yaml | 7 + .../deployment.yaml | 37 + .../getting-started-aws-letsencrypt/rbac.yaml | 24 + .../service.yaml | 14 + .../amazon_elastic_kubernetes_service.svg | 7 + 14 files changed, 829 insertions(+), 6 deletions(-) create mode 100644 content/docs/tutorials/getting-started-aws-letsencrypt/README.md create mode 100644 public/docs/tutorials/getting-started-aws-letsencrypt/certificate.yaml create mode 100644 public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-lets-encrypt-production.yaml create mode 100644 public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-lets-encrypt-staging.yaml create mode 100644 public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-selfsigned.yaml create mode 100644 public/docs/tutorials/getting-started-aws-letsencrypt/deployment.yaml create mode 100644 public/docs/tutorials/getting-started-aws-letsencrypt/rbac.yaml create mode 100644 public/docs/tutorials/getting-started-aws-letsencrypt/service.yaml create mode 100644 public/images/icons/amazon_elastic_kubernetes_service.svg diff --git a/.spelling b/.spelling index e13f62bd22c..43c29f2f97f 100644 --- a/.spelling +++ b/.spelling @@ -353,6 +353,7 @@ enablement erikgb errored erroring +ExternalDNS external-dns finalizer finalizers diff --git a/content/docs/configuration/acme/dns01/route53.md b/content/docs/configuration/acme/dns01/route53.md index 55ae5c27892..296ac1f1978 100644 --- a/content/docs/configuration/acme/dns01/route53.md +++ b/content/docs/configuration/acme/dns01/route53.md @@ -8,8 +8,12 @@ Route53 to solve DNS01 ACME challenges. It's advised you read the [DNS01 Challenge Provider](./README.md) page first for a more general understanding of how cert-manager handles DNS01 challenges. -> Note: This guide assumes that your cluster is hosted on Amazon Web Services +> ℹī¸ This guide assumes that your cluster is hosted on Amazon Web Services > (AWS) and that you already have a hosted zone in Route53. +> +> 📖 Read +> [Tutorial: Deploy cert-manager on Amazon Elastic Kubernetes (EKS) and use Let's Encrypt to sign a certificate for an HTTPS website](../../../tutorials/getting-started-aws-letsencrypt/README.md), +> which contains end-to-end instructions for those who are new to cert-manager and AWS. ## Set up an IAM Role @@ -188,7 +192,7 @@ Note that, as mentioned above, the pod is using `arn:aws:iam::XXXXXXXXXXX:role/c While [`kiam`](https://github.com/uswitch/kiam) / [`kube2iam`](https://github.com/jtblin/kube2iam) work directly with cert-manager, some special attention is needed for using the [IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) feature available on EKS. -This feature uses Kubernetes `ServiceAccount` tokens to authenticate with AWS using the [API_AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html). +This feature uses Kubernetes `ServiceAccount` tokens to authenticate with AWS using the [API_AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html). > **Note**: For using IRSA with cert-manager you must first enable the feature for your cluster. You can do this by > following the [official documentation(https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html). @@ -265,7 +269,7 @@ securityContext: **Note:** If you're following the Cross Account example above, modify the `ClusterIssuer` in the same way as above with the role from Account Y. -### Referencing your own ServiceAccount within Issuer/ClusterIssuer config +### Referencing your own ServiceAccount within Issuer/ClusterIssuer config In this configuration you can reference your own `ServiceAccounts` within your `Issuer`/`ClusterIssuer` and cert-manager will issue itself temporary credentials using these `ServiceAccounts`. Because each issuer can reference a different `ServiceAccount` you can lock down permissions much more, with each `ServiceAccount` mapped to an IAM role that only has permission on the zones it needs for that particular issuer. @@ -379,6 +383,6 @@ spec: role: # This must be set so cert-manager what role to attempt to authenticate with auth: kubernetes: - serviceAccountRef: + serviceAccountRef: name: # The name of the service account created -``` \ No newline at end of file +``` diff --git a/content/docs/getting-started/README.md b/content/docs/getting-started/README.md index c358360f498..fdfff463b95 100644 --- a/content/docs/getting-started/README.md +++ b/content/docs/getting-started/README.md @@ -34,3 +34,14 @@ description: Quick start guides for cert-manager title="Let's Encrypt" /> Learn how to deploy cert-manager on **Azure Kubernetes Service (AKS)** and how to configure it to get certificates for an HTTPS web server, from **Let's Encrypt**. + + + Amazon Elastic Kubernetes Services icon + Let's Encrypt icon 292Jacob, CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0>, via Wikimedia Commons + Learn how to deploy cert-manager on **Amazon Elastic Kubernetes Service (EKS)** and how to configure it to get certificates for an HTTPS web server, from **Let's Encrypt**. + diff --git a/content/docs/manifest.json b/content/docs/manifest.json index d8f5c0c3aa6..a9a340d8d54 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -18,7 +18,7 @@ { "title": "Supported Releases", "path": "/docs/releases/README.md" - }, + }, { "title": "1.15", "path": "/docs/releases/release-notes/release-notes-1.15.md" @@ -598,6 +598,10 @@ "title": "AKS + LoadBalancer + Let's Encrypt", "path": "/docs/tutorials/getting-started-aks-letsencrypt/README.md" }, + { + "title": "AWS + LoadBalancer + Let's Encrypt", + "path": "/docs/tutorials/getting-started-aws-letsencrypt/README.md" + }, { "title": "Migrating from Kube-LEGO", "path": "/docs/tutorials/acme/migrating-from-kube-lego.md" diff --git a/content/docs/tutorials/README.md b/content/docs/tutorials/README.md index 4fb769f8008..fe697e2f0bf 100644 --- a/content/docs/tutorials/README.md +++ b/content/docs/tutorials/README.md @@ -12,6 +12,8 @@ for you to learn from. Take a look! Learn how to deploy cert-manager on Google Kubernetes Engine and how to configure it to get certificates for Ingress, from Let's Encrypt. - [AKS + LoadBalancer + Let's Encrypt](getting-started-aks-letsencrypt/README.md): Learn how to deploy cert-manager on Azure Kubernetes Service (AKS) and how to configure it to get certificates for an HTTPS web server, from Let's Encrypt. +- [EKS + LoadBalancer + Let's Encrypt](getting-started-aws-letsencrypt/README.md): + Learn how to deploy cert-manager on Amazon Elastic Kubernetes Service (EKS) and how to configure it to get certificates for an HTTPS web server, from Let's Encrypt. - [Pomerium Ingress](./acme/pomerium-ingress.md): Tutorial on using the Pomerium Ingress Controller with cert-manager. - [Issuing an ACME Certificate using DNS Validation](./acme/dns-validation.md): Tutorial on how to resolve DNS ownership validation using DNS01 challenges. diff --git a/content/docs/tutorials/getting-started-aws-letsencrypt/README.md b/content/docs/tutorials/getting-started-aws-letsencrypt/README.md new file mode 100644 index 00000000000..87110e8d49a --- /dev/null +++ b/content/docs/tutorials/getting-started-aws-letsencrypt/README.md @@ -0,0 +1,652 @@ +--- +title: Deploy cert-manager on AWS Elastic Kubernetes Service (EKS) and use Let's Encrypt to sign a TLS certificate for an HTTPS website +description: | + Learn how to deploy cert-manager on AWS Elastic Kubernetes Service (EKS) + and configure it to get a signed TLS (SSL) certificate from Let's Encrypt for an HTTPS web server, + using the DNS-01 protocol and AWS Route53 DNS. +--- + +*Last Verified: 9 September 2024* + +In this tutorial you will learn how to deploy and configure cert-manager on AWS Elastic Kubernetes Service (EKS) +and how to deploy an HTTPS web server and make it available on the Internet. +You will learn how to configure cert-manager to get a signed certificate from Let's Encrypt, +which will allow clients to connect to your HTTPS website securely. +You will configure cert-manager to use the [Let's Encrypt DNS-01 challenge protocol](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) with AWS Route53 DNS, +using IAM Roles for Service Accounts (IRSA) to authenticate to AWS. + +# Part 1 + +In the first part of this tutorial you will learn the basics required to deploy an HTTPS website on an AWS Elastic Kubernetes Service (EKS) cluster, using cert-manager to create the SSL certificate for the web server. +You will create a DNS domain for your website, create an EKS cluster, install cert-manager, create a TLS certificate and then deploy a web server which responds to HTTPS requests from clients on the Internet. +The TLS certificate in part 1 is only for testing purposes; in part 2 you will learn how to configure cert-manager to use Let's Encrypt and Route53 DNS to create a trusted certificate which you can use in production. + +## Configure the AWS CLI (`aws`) + +If your have not already done so, [download and install the AWS CLI (`aws`)](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). + +Set up the `aws` command for interactive use: + +```bash +aws configure +``` + +Set the default [output format](https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-output-format.html) and region: + +```bash +export AWS_DEFAULT_OUTPUT=json # ❗ Use JSON output for this tutorial +export AWS_DEFAULT_REGION=us-west-2 # ❗ Your AWS region. +``` + +> 📖 Read +> [Set up the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html), and +> [Configure the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html), +> to learn more about configuring `aws`. + +## Create a public domain name + +In this tutorial you will deploy an HTTPS website with a publicly accessible domain name, so you will need to register a domain unless you already have one. +You could use any [domain name registrar](https://www.cloudflare.com/en-gb/learning/dns/glossary/what-is-a-domain-name-registrar/) to register a domain name for your site. +For example you could use `Gandi` and register a cheap domain name for the purposes of this tutorial. + +Now that you know your domain name, save it in an environment variable: + +```bash +export DOMAIN_NAME=example.com # ❗ Replace this with your own DNS domain name +``` + +And add it to AWS Route53 as a zone: + +```bash +aws route53 create-hosted-zone --caller-reference $(uuidgen) --name $DOMAIN_NAME +``` + +The details of the created zone will be printed to the console: + +```json +{ + "Location": "https://route53.amazonaws.com/2013-04-01/hostedzone/Z0984294TRL0R8AT3SQA", + "HostedZone": { + "Id": "/hostedzone/Z0984294TRL0R8AT3SQA", + "Name": "cert-manager-aws-tutorial.richard-gcp.jetstacker.net.", + "CallerReference": "77274711-b648-4da5-81b7-74512897d0db", + "Config": { + "PrivateZone": false + }, + "ResourceRecordSetCount": 2 + }, + "ChangeInfo": { + "Id": "/change/C04685872DX6N6587E1TL", + "Status": "PENDING", + "SubmittedAt": "2024-09-03T16:29:11.960000+00:00" + }, + "DelegationSet": { + "NameServers": [ + "ns-1504.awsdns-60.org", + "ns-538.awsdns-03.net", + "ns-278.awsdns-34.com", + "ns-1765.awsdns-28.co.uk" + ] + } +} +``` + +Log in to the control panel for your domain registrar and set the NS records for your domain to match the DNS names of the [authoritative DNS servers](https://www.cloudflare.com/en-gb/learning/dns/dns-server-types/) for your Route53 hosted zone. +See `NameServers` in the output of `aws route53 create-hosted-zone` (above) or you can find the name servers later: + +```bash +HOSTED_ZONE_ID=$(aws route53 list-hosted-zones-by-name --dns-name $DOMAIN_NAME --query "HostedZones[0].Id" --output text) +aws route53 get-hosted-zone --id ${HOSTED_ZONE_ID} +``` + +You can check that the NS records have been updated by using `dig` to "trace" the hierarchy of NS records: + +```bash +dig $DOMAIN_NAME ns +trace +nodnssec +``` + +> ⏲ It **may** take more than 1 hour for the NS records to be updated in the parent zone, +> and it may take some time for the old NS records to be replaced in the caches of DNS resolver servers, +> if you looked up the DNS name before updating the NS records. +> +> 📖 Read [How do I Update My DNS Records?](https://docs.gandi.net/en/domain_names/common_operations/dns_records.html) in the `Gandi.net` docs, +> or seek the equivalent documentation for your own domain name registrar. + +## Create an EKS Kubernetes cluster + +To get started, let's create a Kubernetes cluster using EKS. +The easiest way to create an EKS cluster is with `eksctl`. +[Download and install `eksctl`](https://eksctl.io/installation/). + +Pick a name for your cluster and save it in an environment variable: + +```bash +export CLUSTER=test-cluster-1 +``` + +Now, create the cluster using the following command: + +```bash +eksctl create cluster \ + --name $CLUSTER \ + --nodegroup-name node-group-1 \ + --node-type t3.small \ + --nodes 3 \ + --nodes-min 1 \ + --nodes-max 3 \ + --managed \ + --spot +``` + +This will update your `kubectl` config file with the credentials for your new cluster. + +Check that you can connect to the cluster: + +```bash +kubectl get nodes -o wide +``` + +> ⏲ It will take 15-20 minutes to create the cluster. Why? +> See [Reduction in EKS cluster creation time](https://github.com/aws/containers-roadmap/issues/1227). +> +> đŸ’ĩ To minimize your cloud bill, this command creates a 3-node cluster using +> [low cost virtual machines](https://aws.amazon.com/ec2/instance-types/t3) and +> [spot instances](https://eksctl.io/usage/spot-instances/). +> +> ⚠ī¸ This cluster is only suitable for learning purposes it is not suitable for production use. + +## Install cert-manager + +Now you can install and configure cert-manager. + +Install cert-manager using `helm` as follows: + +```bash +helm install cert-manager cert-manager \ + --repo https://charts.jetstack.io \ + --namespace cert-manager \ + --create-namespace \ + --set crds.enabled=true +``` + +This will create three Deployments and some Services and Pods in a new namespace called `cert-manager`. +It also installs various cluster scoped supporting resources such as RBAC roles and Custom Resource Definitions. + +You can view some of the resources that have been installed as follows: + +```bash +kubectl -n cert-manager get all +``` + +And you can explore the Custom Resource Definitions (cert-manager's API) using `kubectl explain`, as follows: + +```bash +kubectl explain Certificate +kubectl explain CertificateRequest +kubectl explain Issuer +``` + +> 📖 Read about [other ways to install cert-manager](../../installation/README.md). +> +> 📖 Read more about [Certificates and Issuers](../../concepts/README.md). + +## Create a test ClusterIssuer and a Certificate + +Now everything is ready for you to create your first certificate. +This will be a self-signed certificate but later we'll replace it with a Let's Encrypt signed certificate. + +```yaml file=../../../../public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-selfsigned.yaml +``` +🔗 `clusterissuer-selfsigned.yaml` + +```bash +kubectl apply -f clusterissuer-selfsigned.yaml +``` + +Then use `envsubst` to substitute your chosen domain name into the following Certificate template: + +```yaml file=../../../../public/docs/tutorials/getting-started-aws-letsencrypt/certificate.yaml +``` +🔗 `certificate.yaml` + +```bash +envsubst < certificate.yaml | kubectl apply -f - +``` + +> 🔗 If you don't already have `envsubst` installed you can [download and install a Go implementation of `envsubst`](https://github.com/a8m/envsubst). + +Use `cmctl status certificate` to check the status of the Certificate: + +```bash +cmctl status certificate www +``` + +If successful, the private key and the signed certificate will be stored in a Secret called `www-tls`. +You can use `cmctl inspect secret www-tls` to decode the base64 encoded X.509 content of the Secret: + +```terminal +$ cmctl inspect secret www-tls +... +Valid for: + DNS Names: + - www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net + URIs: + IP Addresses: + Email Addresses: + Usages: + - digital signature + - key encipherment + - server auth +... +``` + +## Deploy a sample web server + +Now deploy a simple web server which responds to HTTPS requests with "hello world!". +The TLS key and certificate are supplied to the web server by using the `www-tls` Secret as a volume +and by mounting its contents into the file system of the `hello-app` container in the Pod: + +```yaml file=../../../../public/docs/tutorials/getting-started-aws-letsencrypt/deployment.yaml +``` +🔗 `deployment.yaml` + +```bash +kubectl apply -f deployment.yaml +``` + +You also need to create a Kubernetes LoadBalancer Service, so that connections from the Internet can be routed to the web server Pod. +When you create the following Kubernetes Service, an AWS classic load balancer with an ephemeral public IP address will also be created: + +```yaml file=../../../../public/docs/tutorials/getting-started-aws-letsencrypt/service.yaml +``` +🔗 `service.yaml` + +```bash +kubectl apply -f service.yaml +``` + +Within 2-3 minutes, a load balancer should have been provisioned with a public IP. + +```bash +kubectl get service helloweb +``` + +Sample output + +```terminal +$ kubectl get service helloweb +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +helloweb LoadBalancer 10.100.175.247 ae25d292150aa4e3e90e6c25376f9a7d-496307726.us-west-2.elb.amazonaws.com 443:32184/TCP 6m +``` + +The `EXTERNAL-IP` will be different for you and it may be different each time you re-create the LoadBalancer service, +but it will have a stable DNS host name associated with it. + +> ℹī¸ By default, EKS creates [classic](https://aws.amazon.com/elasticloadbalancing/features/#Product_comparisons) +> load balancers for LoadBalancer Services in the cluster, +> using the [Legacy Cloud Provider Load balancer Controller](https://docs.aws.amazon.com/eks/latest/userguide/aws-load-balancer-controller.html#lbc-legacy). +> This is convenient for this tutorial because it does not require any additional software or configuration, but +> [AWS Cloud Provider Load balancer Controller is legacy and is currently only receiving critical bug fixes](https://aws.github.io/aws-eks-best-practices/networking/loadbalancing/loadbalancing/#choosing-load-balancer-type) +> according to the [EKS Best Practices Guide](https://aws.github.io/aws-eks-best-practices/). +> Consider using the [AWS Load Balancer Controller](https://docs.aws.amazon.com/eks/latest/userguide/aws-load-balancer-controller.html) instead. + +The stable DNS host name of the load balancer can be used as an alias for the `www` record in your chosen `$DOMAIN_NAME` +by creating a Route53 [Alias Record](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resource-record-sets-choosing-alias-non-alias.html): + +```bash +HOSTED_ZONE_ID=$(aws route53 list-hosted-zones-by-name --dns-name $DOMAIN_NAME --query "HostedZones[0].Id" --output text) +ELB_CANONICAL_HOSTED_ZONE_NAME=$(kubectl get svc helloweb --output=jsonpath='{ .status.loadBalancer.ingress[0].hostname }') +aws elb describe-load-balancers --query "LoadBalancerDescriptions[?CanonicalHostedZoneName == '$ELB_CANONICAL_HOSTED_ZONE_NAME'] | [0]" \ +| jq '{ + "Comment": "Creating an alias record", + "Changes": [ + { + "Action": "CREATE", + "ResourceRecordSet": { + "Name": "www.\($DOMAIN_NAME)", + "Type": "A", + "AliasTarget": { + "HostedZoneId": .CanonicalHostedZoneNameID, + "DNSName": .CanonicalHostedZoneName, + "EvaluateTargetHealth": false + } + } + } + ] +}' \ + --arg DOMAIN_NAME "${DOMAIN_NAME}" \ +| aws route53 change-resource-record-sets --hosted-zone-id $HOSTED_ZONE_ID --change-batch file:///dev/stdin +``` + +> ℹī¸ Read [Routing traffic to an ELB load balancer](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-elb-load-balancer.html) +> to learn more about this task. +> +> ℹī¸ The script uses a [`JMESPath` query](https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-filter.html) +> to get the ELB for the Kubernetes Service by matching against the DNS name. +> +> 📖 There is an alternative way to manage the DNS record for the load balancer, using [ExternalDNS](https://kubernetes-sigs.github.io/external-dns/latest/). +> ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers. +> Read [ExternalDNS for usage within a Kubernetes cluster on AWS](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md) to learn more. + + +Check that `www.$DOMAIN_NAME` now resolves to the ephemeral public IP address of the load balancer: + +```terminal +$ dig www.$DOMAIN_NAME A +... +;; QUESTION SECTION: +;www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net. IN A + +;; ANSWER SECTION: +www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net. 60 IN A 34.212.236.229 +www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net. 60 IN A 44.232.234.71 +www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net. 60 IN A 35.164.69.198 +``` + +If the DNS is correct and the load balancer is working and the hello world web server is running, +you should now be able to connect to it using curl or using your web browser: + +```bash +curl --insecure -v https://www.$DOMAIN_NAME +``` + +> ⚠ī¸ We used curl's `--insecure` option because curl will reject the untrusted certificate we generated otherwise. +> Later you will learn how to create a trusted certificate signed by Let's Encrypt. + +You should see that the certificate has the expected DNS names and that it is self-signed: + +```terminal +... +* Server certificate: +* subject: CN=www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net +* start date: Sep 4 08:43:56 2024 GMT +* expire date: Dec 3 08:43:56 2024 GMT +* issuer: CN=www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net +* SSL certificate verify result: self-signed certificate (18), continuing anyway. +... +Hello, world! +Protocol: HTTP/2.0! +Hostname: helloweb-55cb4cd887-tjlvh +``` + +> 📖 Read more about [Using a Service to Expose Your App](https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/). + +# Part 2 + +In part 1 you created a test certificate. +Now you will learn how to configure cert-manager to use Let's Encrypt and AWS Route53 DNS to create a trusted certificate which you can use in production. +You need to prove to Let's Encrypt that you own the domain name of the certificate and one way to do this is to create a special DNS record in that domain. +This is known as the [DNS-01 challenge type](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge). + +cert-manager can create that DNS record for you in by using the AWS Route53 API but it needs to authenticate first, +and currently the most secure method of authentication is to use [IAM roles for service accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). +The advantages of this method are that cert-manager will use an ephemeral Kubernetes ServiceAccount Token to authenticate to AWS and the token need not be stored in a Kubernetes Secret. + +> 📖 Read about [other ways to configure the ACME issuer with AWS Route53 DNS](../../configuration/acme/dns01/route53.md). + +## Create an IAM OIDC provider for your cluster + +```sh +eksctl utils associate-iam-oidc-provider --cluster $CLUSTER --approve +``` + +> ℹī¸ Read [Create an IAM OIDC provider for your cluster](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html) for more details. + +## Create an IAM policy + +```bash +aws iam create-policy \ + --policy-name cert-manager-acme-dns01-route53 \ + --description "This policy allows cert-manager to manage ACME DNS01 records in Route53 hosted zones. See https://cert-manager.io/docs/configuration/acme/dns01/route53" \ + --policy-document file:///dev/stdin < ℹī¸ Read the [cert-manager ACME DNS01 Route53 configuration documentation](https://cert-manager.io/docs/configuration/acme/dns01/route53), +> for more details of this IAM policy. + +## Create an IAM role and associate it with a Kubernetes service account + +The following command performs three tasks: +1. creates a new dedicated Kubernetes ServiceAccount in the cert-manager namespace, and +1. configures a new AWS Role with the permissions defined in the policy from the previous step. +1. configures the Role so that it can be only be assumed by clients with tokens for new dedicated Kubernetes ServiceAccount in this EKS cluster. + +```sh +AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) +eksctl create iamserviceaccount \ + --name cert-manager-acme-dns01-route53 \ + --namespace cert-manager \ + --cluster ${CLUSTER} \ + --role-name cert-manager-acme-dns01-route53 \ + --attach-policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/cert-manager-acme-dns01-route53 \ + --approve +``` + +> ℹī¸ Read [Assign IAM roles to Kubernetes service accounts](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html), +> for more details. + +## Grant permission for cert-manager to create ServiceAccount tokens + +cert-manager needs permission to generate a JWT token for the Kubernetes ServiceAccount that you created in the previous step. +Apply the following RBAC Role and RoleBinding in the cert-manager namespace: + +```yaml file=../../../../public/docs/tutorials/getting-started-aws-letsencrypt/rbac.yaml +``` +🔗 `rbac.yaml` + +```sh +kubectl apply -f rbac.yaml +``` + +## Create a ClusterIssuer for Let's Encrypt Staging + +A ClusterIssuer is a custom resource which tells cert-manager how to sign a Certificate. +In this case the ClusterIssuer will be configured to connect to the Let's Encrypt staging server, +which allows us to test everything without using up our Let's Encrypt certificate quota for the domain name. + +Save the following content to a file called `clusterissuer-lets-encrypt-staging.yaml`, change the `email` field to use your email address and apply it: + +```yaml file=../../../../public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-lets-encrypt-staging.yaml +``` +🔗 `clusterissuer-lets-encrypt-staging.yaml` + + +As you can see there are some variables in the `clusterissuer-lets-encrypt-staging.yaml` which need to be filled in before we apply it; +most have been defined earlier in this tutorial but you need to set the following: + +```bash +export EMAIL_ADDRESS= # ❗ Replace this with your email address +``` + +Now use `envsubst` to fill in the variables and pipe it into `kubectl apply`, as follows: + +```bash +export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) +envsubst < clusterissuer-lets-encrypt-staging.yaml | kubectl apply -f - +``` + +You can check the status of the ClusterIssuer: + +```bash +kubectl describe clusterissuer letsencrypt-staging +``` + +Example output + +```console +Status: + Acme: + Last Registered Email: firstname.lastname@example.com + Uri: https://acme-staging-v02.api.letsencrypt.org/acme/acct/77882854 + Conditions: + Last Transition Time: 2024-09-04T15:41:18Z + Message: The ACME account was registered with the ACME server + Observed Generation: 1 + Reason: ACMEAccountRegistered + Status: True + Type: Ready +``` + +> ℹī¸ Let's Encrypt uses the Automatic Certificate Management Environment (ACME) protocol +> which is why the configuration above is under a key called `acme`. +> +> ℹī¸ The email address is only used by Let's Encrypt to remind you to renew the certificate after 30 days before expiry. You will only receive this email if something goes wrong when renewing the certificate with cert-manager. +> +> ℹī¸ The Let's Encrypt production issuer has [very strict rate limits](https://letsencrypt.org/docs/rate-limits/). +> When you're experimenting and learning, it can be very easy to hit those limits. Because of that risk, +> we'll start with the Let's Encrypt staging issuer, and once we're happy that it's working +> we'll switch to the production issuer. +> +> 📖 Read more about [configuring the ACME Issuer](../../configuration/acme/README.md). +> + +## Re-issue the Certificate using Let's Encrypt + +Patch the Certificate to use the staging ClusterIssuer: + +```bash +kubectl patch certificate www --type merge -p '{"spec":{"issuerRef":{"name":"letsencrypt-staging"}}}' +``` + +That should trigger cert-manager to renew the certificate: +Use `cmctl` to check: + +```bash +cmctl status certificate www +cmctl inspect secret www-tls +``` + +And finally, when the new certificate has been issued, you must restart the web server to use it: + +```bash +kubectl rollout restart deployment helloweb +``` + +You should once again be able to connect to the website, but this time you will see the Let's Encrypt staging certificate: + +```terminal +$ curl -v --insecure https://www.$DOMAIN_NAME +... +* Server certificate: +* subject: CN=www.cert-manager-tutorial-22.site +* start date: Jan 5 12:41:14 2023 GMT +* expire date: Apr 5 12:41:13 2023 GMT +* issuer: C=US; O=(STAGING) Let's Encrypt; CN=(STAGING) Artificial Apricot R3 +* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway. +... +Hello, world! +Protocol: HTTP/2.0! +Hostname: helloweb-9b8bcdd56-6rxm8 +``` + +> ⚠ī¸ We used curl's `--insecure` option again here because the Let's Encrypt staging issuer creates untrusted certificates. +> Next you will learn how to create a trusted certificate signed by the Let's Encrypt production issuer. + +## Create a production ready certificate + +Now that everything is working with the Let's Encrypt staging server, we can switch to the production server and get a trusted certificate. + +Create a Let's Encrypt production Issuer by copying the staging ClusterIssuer YAML and modifying the server URL and the names, +then apply it: + +```yaml file=../../../../public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-lets-encrypt-production.yaml +``` +🔗 `clusterissuer-lets-encrypt-production.yaml` + + +```bash +export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) +envsubst < clusterissuer-lets-encrypt-production.yaml | kubectl apply -f - +``` + +Check the status of the ClusterIssuer: + +```bash +kubectl describe clusterissuer letsencrypt-production +``` + +Patch the Certificate to use the production ClusterIssuer: + +```bash +kubectl patch certificate www --type merge -p '{"spec":{"issuerRef":{"name":"letsencrypt-production"}}}' +``` + +That should trigger cert-manager to renew the certificate: +Use `cmctl` to check: + +```bash +cmctl status certificate www +cmctl inspect secret www-tls +``` + +And finally, when the new certificate has been issued, you must restart the web server to use it: + +```bash +kubectl rollout restart deployment helloweb +``` + +Now you should be able to connect to the web server securely, without the `--insecure` flag, +and if you visit the site in your web browser, it should show a padlock (🔒) symbol next to the URL. + +```bash +curl -v https://www.$DOMAIN_NAME +``` + +```terminal +... + +* Server certificate: +* subject: CN=www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net +* start date: Sep 4 19:32:24 2024 GMT +* expire date: Dec 3 19:32:23 2024 GMT +* subjectAltName: host "www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net" matched cert's "www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net" +* issuer: C=US; O=Let's Encrypt; CN=R11 +* SSL certificate verify ok. +... +``` + +That concludes this tutorial. +You have learned how to deploy cert-manager on AWS EKS and how to configure it to issue Let's Encrypt signed certificates using the DNS-01 protocol with Route53 DNS. +You have learned about IAM Roles for service accounts (IRSA) and learned how to configure cert-manager to authenticate to AWS Route53 using a Kubernetes ServiceAccount token. + +# Cleanup + +After completing the tutorial you can clean up by deleting the EKS cluster and the Route53 hosted zone, as follows: + +``` +eksctl delete cluster --name $CLUSTER +HOSTED_ZONE_ID=$(aws route53 list-hosted-zones-by-name --dns-name $DOMAIN_NAME --query "HostedZones[0].Id" --output text) +aws route53 delete-hosted-zone --id ${HOSTED_ZONE_ID} +``` + +The IAM policy, role, and identity provider can be deleted manually from the AWS web UI. + +# Next Steps + +> 📖 Read other [cert-manager tutorials](../README.md) and [getting started guides](../../getting-started/README.md). +> +> 📖 Read more about [configuring the cert-manager ACME issuer with Route53 DNS](../../configuration/acme/dns01/route53.md). diff --git a/public/docs/tutorials/getting-started-aws-letsencrypt/certificate.yaml b/public/docs/tutorials/getting-started-aws-letsencrypt/certificate.yaml new file mode 100644 index 00000000000..b4563d82a09 --- /dev/null +++ b/public/docs/tutorials/getting-started-aws-letsencrypt/certificate.yaml @@ -0,0 +1,20 @@ +# certificate.yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: www +spec: + secretName: www-tls + revisionHistoryLimit: 1 + privateKey: + rotationPolicy: Always + commonName: www.$DOMAIN_NAME + dnsNames: + - www.$DOMAIN_NAME + usages: + - digital signature + - key encipherment + - server auth + issuerRef: + name: selfsigned + kind: ClusterIssuer diff --git a/public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-lets-encrypt-production.yaml b/public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-lets-encrypt-production.yaml new file mode 100644 index 00000000000..114de5a2cc9 --- /dev/null +++ b/public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-lets-encrypt-production.yaml @@ -0,0 +1,20 @@ +# clusterissuer-lets-encrypt-production.yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-production +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: $EMAIL_ADDRESS + privateKeySecretRef: + name: letsencrypt-production + solvers: + - dns01: + route53: + region: ${AWS_DEFAULT_REGION} + role: arn:aws:iam::${AWS_ACCOUNT_ID}:role/cert-manager-acme-dns01-route53 + auth: + kubernetes: + serviceAccountRef: + name: cert-manager-acme-dns01-route53 diff --git a/public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-lets-encrypt-staging.yaml b/public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-lets-encrypt-staging.yaml new file mode 100644 index 00000000000..c37faa16666 --- /dev/null +++ b/public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-lets-encrypt-staging.yaml @@ -0,0 +1,20 @@ +# clusterissuer-lets-encrypt-staging.yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + server: https://acme-staging-v02.api.letsencrypt.org/directory + email: ${EMAIL_ADDRESS} + privateKeySecretRef: + name: letsencrypt-staging + solvers: + - dns01: + route53: + region: ${AWS_DEFAULT_REGION} + role: arn:aws:iam::${AWS_ACCOUNT_ID}:role/cert-manager-acme-dns01-route53 + auth: + kubernetes: + serviceAccountRef: + name: cert-manager-acme-dns01-route53 diff --git a/public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-selfsigned.yaml b/public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-selfsigned.yaml new file mode 100644 index 00000000000..b3072419f80 --- /dev/null +++ b/public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-selfsigned.yaml @@ -0,0 +1,7 @@ +# clusterissuer-selfsigned.yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned +spec: + selfSigned: {} diff --git a/public/docs/tutorials/getting-started-aws-letsencrypt/deployment.yaml b/public/docs/tutorials/getting-started-aws-letsencrypt/deployment.yaml new file mode 100644 index 00000000000..a16dc865efc --- /dev/null +++ b/public/docs/tutorials/getting-started-aws-letsencrypt/deployment.yaml @@ -0,0 +1,37 @@ +# deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: helloweb + labels: + app: hello +spec: + selector: + matchLabels: + app: hello + tier: web + template: + metadata: + labels: + app: hello + tier: web + spec: + containers: + - name: hello-app + image: us-docker.pkg.dev/google-samples/containers/gke/hello-app-tls:1.0 + imagePullPolicy: Always + ports: + - containerPort: 8443 + volumeMounts: + - name: tls + mountPath: /etc/tls + readOnly: true + env: + - name: TLS_CERT + value: /etc/tls/tls.crt + - name: TLS_KEY + value: /etc/tls/tls.key + volumes: + - name: tls + secret: + secretName: www-tls diff --git a/public/docs/tutorials/getting-started-aws-letsencrypt/rbac.yaml b/public/docs/tutorials/getting-started-aws-letsencrypt/rbac.yaml new file mode 100644 index 00000000000..7f2e80b979d --- /dev/null +++ b/public/docs/tutorials/getting-started-aws-letsencrypt/rbac.yaml @@ -0,0 +1,24 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: cert-manager-acme-dns01-route53-tokenrequest + namespace: cert-manager +rules: + - apiGroups: [''] + resources: ['serviceaccounts/token'] + resourceNames: ['cert-manager-acme-dns01-route53'] + verbs: ['create'] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: cert-manager-acme-dns01-route53-tokenrequest + namespace: cert-manager +subjects: + - kind: ServiceAccount + name: cert-manager + namespace: cert-manager +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: cert-manager-acme-dns01-route53-tokenrequest diff --git a/public/docs/tutorials/getting-started-aws-letsencrypt/service.yaml b/public/docs/tutorials/getting-started-aws-letsencrypt/service.yaml new file mode 100644 index 00000000000..ffac52325ba --- /dev/null +++ b/public/docs/tutorials/getting-started-aws-letsencrypt/service.yaml @@ -0,0 +1,14 @@ +# service.yaml +apiVersion: v1 +kind: Service +metadata: + name: helloweb +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 8443 + selector: + app: hello + tier: web + type: LoadBalancer diff --git a/public/images/icons/amazon_elastic_kubernetes_service.svg b/public/images/icons/amazon_elastic_kubernetes_service.svg new file mode 100644 index 00000000000..6bb9db41091 --- /dev/null +++ b/public/images/icons/amazon_elastic_kubernetes_service.svg @@ -0,0 +1,7 @@ + + + Icon-Resource/Containers/Res_Amazon-Elastic-Kubernetes-Service_EKS-on-Outposts_48 + + + + \ No newline at end of file