Ngày nay, thật khó để tưởng tượng các hệ thống có điểm cuối API công khai mà không có sự bảo vệ chứng chỉ TLS. Có một số cách để cấp chứng chỉ:
Trong bối cảnh của bài đăng này, tôi sẽ chủ yếu thảo luận về các chứng chỉ miễn phí có thể được sử dụng bên trong AWS chứ không chỉ bởi các dịch vụ AWS. Rõ ràng, việc sử dụng bất kỳ thứ gì khác ngoài AWS Certification Manager sẽ vô nghĩa nếu bạn chỉ sử dụng các dịch vụ AWS được quản lý và không có các yêu cầu bảo mật nghiêm ngặt. Trình quản lý chứng chỉ AWS cung cấp một phương pháp cấp chứng chỉ rất thuận tiện và nhanh chóng thông qua các thử thách DNS hoặc HTTP; tuy nhiên, bạn sẽ gặp phải những hạn chế cơ bản của AWS nếu cần sử dụng các chứng chỉ này bên ngoài dịch vụ AWS (API Gateway, ALB, NLB, v.v.), chẳng hạn như phiên bản EC2 chạy Nginx cần tệp chứng chỉ vật lý. Ngoài ra, ngay cả khi bạn yêu cầu, AWS Certification Manager cũng không hiển thị nội dung chứng chỉ.
Tại thời điểm này, đây là thời điểm thích hợp để nhắc bạn về LetsEncrypt , một công cụ được sử dụng rộng rãi hơn Trình quản lý chứng chỉ—ít nhất là vì nó không phụ thuộc vào đám mây. Rất tiếc, không có kỹ thuật cấp chứng chỉ LetsEncrypt tích hợp sẵn trong AWS. Có thể sử dụng công cụ certbot cho các dịch vụ EC2 hoặc ECS của bạn, nhưng trong trường hợp đó, bạn sẽ cần xem xét cách định cấu hình quy trình gia hạn. Tôi cũng không muốn kết hợp các chiến lược khác nhau vì tôi nghĩ tốt hơn nên có một quy trình duy nhất cho mọi thứ vì nó làm giảm độ phức tạp của toàn bộ hệ thống.
Cân nhắc điều đó, tôi đã tạo một hàm Lambda tự động cấp và gia hạn chứng chỉ LetsEncrypt mà không yêu cầu cấu hình phức tạp. Chứng chỉ có thể được sử dụng ở bất kỳ dịch vụ AWS nào sử dụng ARN cùng với chứng chỉ Trình quản lý chứng chỉ AWS sau khi cấp chứng chỉ lần đầu. Ngoài ra, bạn có thể sử dụng phiên bản chứng chỉ vật lý được lưu giữ trong AWS Secrets Manager ở bất kỳ vị trí nào bạn chọn, cho dù đó là phiên bản EC2 chạy Nginx hay một nơi khác.
Trong bài viết này, tôi giả định rằng vùng DNS của bạn được quản lý bởi AWS Route53.
Hàm Lambda được mô tả trong bài viết này được viết trên Go v1.22. Tất cả các tài nguyên kết quả như bản ghi DNS, bí mật hoặc chứng chỉ đều được kiểm soát bởi vai trò IAM của Amazon, được tạo thông qua mã Terraform theo mặc định. Trình tự các hành động của Lambda như sau:
aws_cloudwatch_event_target
. Ví dụ sự kiện: { "domainName": "hackernoon.referrs.me", "acmeUrl": "prod", "acmeEmail": "[email protected]", "reImportThreshold": 10, "issueType": "default", "storeCertInSecretsManager" : true }
DNS-01
nếu số ngày cho đến ngày hết hạn ít hơn reImportThreshold
. Bước này bao gồm việc Lambda tạo bản ghi TXT
khớp tên miền với vùng AWS Route53 và chờ chứng chỉ của bạn sẵn sàng.storeCertInSecretsManager
là đúng.
Mật mã
Lambda được viết trên Go 1.22. Việc sử dụng càng ít thư viện càng tốt đã giúp tôi duy trì mục tiêu giữ cho mã luôn khô ráo. Danh sách đầy đủ các thư viện go cần thiết:
URL | Sự miêu tả |
---|---|
Thư viện, mẫu và công cụ giúp nhà phát triển Go phát triển các chức năng AWS Lambda. | |
AWS SDK dành cho ngôn ngữ lập trình Go. | |
Máy khách và thư viện LetsEncrypt / ACME. | |
Xử lý hợp lý các giá trị null. | |
Ghi nhật ký có cấu trúc, có thể cắm được cho Go. |
Hình ảnh docker
Tôi đã sử dụng gcr.io/distroless/static:nonroot làm hình ảnh docker cơ bản. Đối với các ứng dụng Go không yêu cầu libc, hình ảnh này là hoàn hảo. Nó không hoàn toàn trống scratch
và bao gồm những điều sau đây:
Quá trình xây dựng
Trong các dự án phần mềm lớn, việc giám sát quá trình xây dựng có thể trở thành một công việc tốn nhiều công sức và thời gian. Makefiles có thể giúp tự động hóa và hợp lý hóa quy trình này, đảm bảo rằng dự án của bạn được xây dựng hiệu quả và nhất quán. Vì lý do đó, tôi thích sử dụng Makefile cho tất cả các dự án Golang của mình hơn. Tệp này rất đơn giản:
##@ General help: ## Display this help. @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) fmt: ## Run go fmt against code. go fmt ./... vet: ## Run go vet against code. go vet ./... ##@ Build build: fmt vet ## Build service binary. go build -o bin/lambda main.go run: vet ## Run service from your laptop. go run ./main.go ##@ Test lint: ## Run Go linter golangci-lint run ./... test: ## Run Go tests go test ./...
Từ phía CICD, tôi đã sử dụng thiết lập điển hình cho ứng dụng Go
GitHub Actions dưới dạng tích hợp liên tục.
ghcr.io làm đăng ký docker. So với DockerHub, cái này cung cấp hai tính năng chính khiến tôi thích sử dụng:
kvendingoldo/semver-action : plugin GitHub Actions của tôi để tạo phiên bản tự động. Đó là Hành động GitHub tạo ra các thẻ tương thích SemVer cho các cam kết của kho lưu trữ. Hành động này có thể quản lý các phiên bản, tạo bản phát hành GitHub và kiểm soát các nhánh phát hành bên trong kho lưu trữ. Nó hoạt động tuyệt vời với cả đơn và monorepos.
Tự động tạo nhật ký thay đổi. Tôi thích nhật ký thay đổi! Trong bối cảnh các dự án OpenSource khác mà tôi quản lý (ví dụ: https://github.com/tofuutils/tenv , https://github.com/tofuutils/tofuenv , v.v.), nhóm của tôi đã nhận ra tầm quan trọng của việc thông báo cho người dùng về những thay đổi.
golangci-lint . Theo quan điểm của tôi, tất cả mã phải được xem xét bởi máy phân tích mã tĩnh. SonarQube không thể được thiết lập cho tất cả các dự án, tuy nhiên theo ý kiến của tôi, golangci là đủ cho các dự án Go vừa và nhỏ.
Mã được thảo luận trong trang này giống với Terraform và OpenTofu, nhưng bắt đầu với Terraform v1.6, Hashicorp đã được sửa đổi giấy phép Terraform thành Giấy phép Nguồn Doanh nghiệp (BSL) v1.1. Nếu bạn đang phát triển thứ gì đó mang tính thương mại trên Terraform, hãy chuyển sang OpenTofu càng sớm càng tốt.
Nếu bạn cần quản lý nhiều phiên bản OpenTofu hoặc Terraform, hãy sử dụng
Bạn có thể tìm thấy nhiều ví dụ về Terraform / OpenTofu hơn trong thư mục ví dụ trong kho Git .
Để làm việc với AWS LetsEncrypt Lambda qua OpenTofu, bạn cần thực hiện các bước sau:
Thêm mô-đun vào mã OpenTofu/Terraform của bạn
module "letsencrypt_lambda" { source = "[email protected]:kvendingoldo/aws-letsencrypt-lambda.git//files/terraform/module?ref=0.31.4" blank_name = "kvendingoldo-letsencrypt-lambda" tags = var.tags cron_schedule = var.letsencrypt_lambda_cron_schedule events = var.letsencrypt_lambda_events ecr_proxy_username = var.ecr_proxy_username ecr_proxy_access_token = var.ecr_proxy_access_token }
Chỉ định các biến
variable "tags" { default = { hackernoon : "demo" } } variable "ecr_proxy_username" { default = "kvendingoldo" } variable "ecr_proxy_access_token" { default = "ghp_xxx" } variable "letsencrypt_lambda_cron_schedule" { default = "rate(168 hours)" } variable "letsencrypt_lambda_events" { default = [ { "acmRegion" : "us-east-1", "route53Region" : "us-east-1", "domainName" : "hackernoon.referrs.me", "acmeUrl" : "stage", "acmeEmail" : "[email protected]", "reImportThreshold" : 100, "issueType" : "default", "storeCertInSecretsManager" : false } ] }
Hãy chú ý đến các biến ecr_proxy_username
và ecr_proxy_access_token
. Theo mặc định, AWS Lambda không thể lấy hình ảnh từ các nguồn không phải AWS ECR. May mắn thay, nhóm AWS đã tạo bộ đệm ECR Proxy, bộ đệm này có thể tìm nạp hình ảnh từ các cơ quan đăng ký có sẵn công khai như DockerHub hoặc GHCR và lưu trữ chúng bên trong ECR. Bất chấp khả năng này, AWS không cho phép kéo hình ảnh mà không có mã thông báo, ngay cả từ các kho lưu trữ công khai mở, do đó bạn phải có mã thông báo GitHub cá nhân để có quyền truy cập vào hình ảnh Docker dựng sẵn. Ngoài ra, bạn có thể lấy kho lưu trữ GitHub của tôi, xây dựng hình ảnh cục bộ, sau đó tải nó lên kho lưu trữ ECR hiện có của bạn. Trong trường hợp này, ví dụ có thể được sửa đổi như sau:
module "letsencrypt_lambda" { source = "../../" blank_name = "kvendingoldo-letsencrypt-lambda" tags = var.tags cron_schedule = var.letsencrypt_lambda_cron_schedule events = var.letsencrypt_lambda_events ecr_proxy_enabled = false ecr_image_uri = "<YOUR_ACCOUNT_ID>.dkr.ecr.us-east-2.amazonaws.com/aws_letsencrypt_lambda:<VERSION>" }
Khi bạn hoàn tất việc thay đổi mã, hãy chạy lệnh sau để cài đặt OpenTofu bằng trình chuyển đổi phiên bản OpenTofu tenv :
$ tenv tofu install
Và cuối cùng, thực hiện các lệnh sau để áp dụng mã được tạo:
$ tofu init $ tofu plan $ tofu apply
Đợi cho đến khi mã được triển khai lên AWS và các sự kiện được kích hoạt. Sau vài phút, bạn sẽ thấy các chứng chỉ sẵn sàng bên trong trình quản lý chứng chỉ. Ví dụ:
Bắt đầu từ bây giờ, AWS có thể sử dụng chứng chỉ do ARN cấp ở bất kỳ dịch vụ nào.
Nếu bạn cần sử dụng chứng chỉ bên ngoài dịch vụ AWS hoặc có quyền truy cập vào nội dung của chứng chỉ đó, hãy đặt tùy chọn sự kiện storeCertInSecretsManager
thành true
. Trong tình huống này, khi Lambda hoàn thành quá trình thực thi cơ bản, chứng chỉ sẽ được lưu trong AWS Secrets Manager. Nó giúp người dùng linh hoạt hơn: họ có thể kiểm tra nội dung của chứng chỉ, làm việc trực tiếp với chứng chỉ đó từ EC2, v.v. Để tìm hiểu thêm về AWS Secrets Manager, hãy đọc hướng dẫn chính thức.
Tên | Sự miêu tả | Những giá trị khả thi | Giá trị mặc định | Ví dụ | Yêu cầu |
---|---|---|---|---|---|
| Loại định dạng cho nhật ký | JSON | CHỮ | CHỮ | JSON | ❌ |
| Chế độ ứng dụng. Đặt chế độ | đám mây | địa phương | đám mây | đám mây | ✅ |
| Mức độ ghi nhật ký | hoảng loạn|tử vong|lỗi|cảnh báo|thông tin|gỡ lỗi|dấu vết | cảnh báo | cảnh báo | ❌ |
| Khu vực AWS mặc định. Sau khi triển khai lên AWS, nó sẽ tự động cài đặt. | <bất kỳ khu vực AWS hợp lệ nào> | - | chúng tôi-đông-1 | ✅ |
| Tên miền mà chứng chỉ đang được cấp hoặc gia hạn | bất kỳ tên miền hợp lệ nào | - | hackernoon. giới thiệu.me | ✅ |
| URL LetsEncrypt sản xuất sẽ được sử dụng nếu nó được đặt thành | sản phẩm | sân khấu | sản phẩm | sản phẩm | ✅ |
| Địa chỉ email được liên kết với chứng chỉ LetsEncrypt | bất kỳ email hợp lệ | ✅ | ||
| Chứng chỉ sẽ được gia hạn nếu thời gian tồn tại (TTL) bằng | bất kỳ int nào > 0 | 10 | 10 | ✅ |
| Nếu | “đúng” | "SAI" | "SAI" | "SAI" | ❌ |
Trong phạm vi công việc với aws-letsencrypt-lambda , đôi khi bạn có thể muốn xem lại nhật ký. Nó khá dễ dàng để thực hiện:
/aws/lambda/kvendingoldo-letsencrypt-lambda
Chuyển đến hàm Lambda đã được tạo thông qua OpenTofu. Nhấp vào nút “Thử nghiệm”.
Điền vào Test Event
và nhấp vào Test
{ "domainName": "<YOUR_VALID_DOMAIN>", "acmeUrl": <stage | prod>, "acmeEmail": "<ANY_VALID_EMAIL>", "reImportThreshold": 10, "issueType": "<default | force>", "storeCertInSecretsManager" : <true | false> }
Ví dụ 1:
{ "domainName": "hackernoon.referrs.me", "acmeUrl": "prod", "acmeEmail": "[email protected]", "reImportThreshold": 10, "issueType": "default" }
Chờ cho đến khi việc thực hiện được hoàn thành. Bạn có thể ghi nhật ký thực thi có sẵn trong Cloudwatch. Thông thường vấn đề ban đầu mất khoảng 5 phút.
Sao chép kho lưu trữ https://github.com/kvendingoldo/aws-letsencrypt-lambda vào máy tính xách tay của bạn
Định cấu hình thông tin xác thực AWS Cli thông qua hướng dẫn chính thức .
Kiểm tra phần biến môi trường và đặt số lượng biến tối thiểu cần thiết. Vì LetsEncrypt sẽ giới hạn số lần thử lại mỗi giờ đối với ACME_URL="prod"
nên tôi khuyên bạn nên sử dụng ACME_URL="stage"
để thử nghiệm. Ví dụ về biến môi trường:
export AWS_REGION="us-east-2" export MODE=local export DOMAIN_NAME="hackernoon.referrs.me" export ACME_URL="stage" export ACME_EMAIL="[email protected]" export REIMPORT_THRESHOLD=10 export ISSUE_TYPE="default" export STORE_CERT_IN_SECRETSMANAGER="true"
Thực thi lambda cục bộ thông qua lệnh sau:
go run main.go
Sau khi Lambda thực hiện thành công, nhật ký sau sẽ xuất hiện.
INFO[0000] Starting lambda execution ... INFO[0000] Lambda will use STAGING ACME URL; If you need to use PROD URL specify it via 'ACME_URL' or pass in event body INFO[0000] Certificate found, arn is arn:aws:acm:us-east-2:004867756392:certificate/72f872fd-e577-43f4-ae38-6833962630af. Trying to renew ... INFO[0000] Checking certificate for domain 'hackernoon.referrs.me' with arn 'arn:aws:acm:us-east-2:004867756392:certificate/72f872fd-e577-43f4-ae38-6833962630af' INFO[0000] Certificate status is 'ISSUED' INFO[0000] Certificate in use by [] INFO[0000] Certificate valid until 2024-08-31 13:50:49 +0000 UTC (89 days left) INFO[0000] Try to get certificate for hackernoon.referrs.me domain 2024/06/02 17:56:23 [INFO] acme: Registering account for [email protected] 2024/06/02 17:56:24 [INFO] [hackernoon.referrs.me, www.hackernoon.referrs.me] acme: Obtaining bundled SAN certificate 2024/06/02 17:56:25 [INFO] [hackernoon.referrs.me] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/12603809394 2024/06/02 17:56:25 [INFO] [www.hackernoon.referrs.me] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/12603809404 2024/06/02 17:56:25 [INFO] [hackernoon.referrs.me] acme: Could not find solver for: tls-alpn-01 2024/06/02 17:56:25 [INFO] [hackernoon.referrs.me] acme: Could not find solver for: http-01 2024/06/02 17:56:25 [INFO] [hackernoon.referrs.me] acme: use dns-01 solver 2024/06/02 17:56:25 [INFO] [www.hackernoon.referrs.me] acme: Could not find solver for: tls-alpn-01 2024/06/02 17:56:25 [INFO] [www.hackernoon.referrs.me] acme: Could not find solver for: http-01 2024/06/02 17:56:25 [INFO] [www.hackernoon.referrs.me] acme: use dns-01 solver 2024/06/02 17:56:25 [INFO] [hackernoon.referrs.me] acme: Preparing to solve DNS-01 2024/06/02 17:56:26 [INFO] Wait for route53 [timeout: 5m0s, interval: 4s] 2024/06/02 17:57:00 [INFO] [www.hackernoon.referrs.me] acme: Preparing to solve DNS-01 2024/06/02 17:57:00 [INFO] Wait for route53 [timeout: 5m0s, interval: 4s] 2024/06/02 17:57:30 [INFO] [hackernoon.referrs.me] acme: Trying to solve DNS-01 2024/06/02 17:57:30 [INFO] [hackernoon.referrs.me] acme: Checking DNS record propagation. [nameservers=109.122.99.130:53,109.122.99.129:53] 2024/06/02 17:57:34 [INFO] Wait for propagation [timeout: 5m0s, interval: 4s] 2024/06/02 17:57:46 [INFO] [hackernoon.referrs.me] The server validated our request 2024/06/02 17:57:46 [INFO] [www.hackernoon.referrs.me] acme: Trying to solve DNS-01 2024/06/02 17:57:46 [INFO] [www.hackernoon.referrs.me] acme: Checking DNS record propagation. [nameservers=109.122.99.130:53,109.122.99.129:53] 2024/06/02 17:57:50 [INFO] Wait for propagation [timeout: 5m0s, interval: 4s] 2024/06/02 17:58:30 [INFO] [www.hackernoon.referrs.me] The server validated our request 2024/06/02 17:58:30 [INFO] [hackernoon.referrs.me] acme: Cleaning DNS-01 challenge 2024/06/02 17:58:30 [INFO] Wait for route53 [timeout: 5m0s, interval: 4s] 2024/06/02 17:59:09 [INFO] [www.hackernoon.referrs.me] acme: Cleaning DNS-01 challenge 2024/06/02 17:59:09 [INFO] Wait for route53 [timeout: 5m0s, interval: 4s] 2024/06/02 17:59:43 [INFO] [hackernoon.referrs.me, www.hackernoon.referrs.me] acme: Validations succeeded; requesting certificates 2024/06/02 17:59:43 [INFO] Wait for certificate [timeout: 30s, interval: 500ms] 2024/06/02 17:59:45 [INFO] [hackernoon.referrs.me] Server responded with a certificate. INFO[0203] Certificate has been successfully imported. Arn is arn:aws:acm:us-east-2:004867756392:certificate/72f872fd-e577-43f4-ae38-6833962630af INFO[0204] Secret updated successfully. SecretId: arn:aws:secretsmanager:us-east-2:004867756392:secret:hackernoon.referrs.me-NioT77 INFO[0204] Lambda has been completed
Đó là nó. Bắt đầu từ bây giờ, AWS có thể sử dụng chứng chỉ được cấp tại bất kỳ dịch vụ nào của ARN hoặc tại các địa điểm khác nơi cần thiết về mặt vật lý bằng cách lấy chứng chỉ từ AWS Secrets Manager.
Tôi đã sử dụng hàm Lambda trong sản xuất được gần bốn năm. Qua nhiều năm, nhiều khía cạnh khác nhau của việc triển khai ban đầu đã thay đổi:
Trước đây, AWS đã cấm sử dụng bất kỳ cơ quan đăng ký không phải ECR nào làm nguồn Lambda. Điều này không thay đổi, tuy nhiên AWS đã thêm proxy ECR cho GitHub, DockerHub và một số cơ quan đăng ký bổ sung. Nếu không có chức năng này, chúng tôi phải đẩy hình ảnh Lambda vào ECR cá nhân của mình theo cách thủ công và thay thế URL thành hình ảnh bằng mã Terraform. Bây giờ mã OpenTofu thực hiện tự động thông qua ECR Proxy.
Lúc đầu, tôi đã cân nhắc việc giới thiệu nhiều thử thách khác nhau như http-01
hoặc tls-alpn-01
, nhưng không ai hỏi tôi về điều đó trong suốt 4 năm. Nó vẫn còn tồn tại trong các vấn đề của GitHub và nếu cần khả năng này, chúng ta có thể làm việc cùng nhau để tạo ra nó.
Tôi không muốn sử dụng chứng chỉ LetsEncrypt ở các phiên bản EC2 thuần túy khi dự án mới bắt đầu, nhưng ngày nay, đó là thông lệ tiêu chuẩn. Như tôi đã nêu trước đây, trong một số trường hợp nhất định, bạn có thể truy xuất chứng chỉ từ AWS Secrets Managed bằng AWS cli.
Tôi đã viết rất nhiều mã Go mới trong nhiều năm, vì vậy tôi có thể nói rằng mã Lambda ban đầu trong kho lưu trữ của tôi không đẹp mắt như mong đợi. Có một sự khác biệt đáng kể giữa nó và dự án Go gần đây nhất của tôi, tenv (OpenTofu, Terraform, Terragrunt và trình quản lý phiên bản Atmos, được viết bằng Go), nhưng trong mọi trường hợp, mã này nhìn chung vẫn được hỗ trợ, vì vậy việc sửa đổi nó sẽ mang lại chiến thắng. Sẽ không quá rắc rối. Thỉnh thoảng, tôi sẽ tiến hành tái cấu trúc đáng kể để làm cho mã trở nên trang nhã hơn.
Lambda tương tự đã được sử dụng trong nhiều năm ở một số dự án khác nhau. Ngoài ra, tôi còn là người đồng sáng lập nền tảng DevOps cloudexpress.app , nơi nhóm của chúng tôi quản lý chứng chỉ TLS cho tất cả khách hàng sử dụng AWS LetsEncrypt Lambda để đơn giản hóa các quy trình tự động hóa.
Bây giờ hãy nói về những con số. Trong khoảng thời gian 4 năm , dự án này đã giúp được nhiều người và được sử dụng trong nhiều dự án OpenSource và hơn 30 dự án thương mại . Lambda cấp hơn 2000 chứng chỉ và không muốn dừng lại ở đó.
AWS LetsEncrypt Lambda là giải pháp phù hợp cho bạn nếu
Nếu bạn phát hiện ra rằng ít nhất một trong những điểm này áp dụng cho trường hợp của mình thì bạn có thể sử dụng AWS Lambda. Ngoài ra, nếu bạn muốn tham gia phát triển, tôi luôn sẵn sàng đón nhận các vấn đề mới và Yêu cầu kéo tại GitHub. URL dự án: https://github.com/kvendingoldo/aws-letsencrypt-lambda .