This Terraform module establishes a private registry for Terraform, allowing
you to publish your own modules in a location you control independent of
Terraform's public registry at registry.terraform.io
.
Terraform module addresses can include an optional hostname part which allows them to be downloaded from services other than the public registry:
module "awesomeapp" {
source = "tf.example.com/awesomecorp/awesomeapp/aws"
}
This is a fork from https://github.com/apparentlymart/terraform-aws-tf-registry, created by Martin Atkins.
- Use JWT secret creation and sharing with secret manager
- Add lambda autorizer for authentication
- automate API gateway redeployment
- Add dedicated bucket storage
- Create python client to deploy terraform module
- Tags resources
- Add dynamodb capacity management and custom naming
- Add bucket custom naming
- Usage example
- Automate API update after change
With this registy implemnentation, you can add a module source from "anywhere" like git, http server, and s3 bucket.
But for all this storage, you add to handle authentication (...).
For my point of view, the more simple is to deploy zipped terraform module on a bucket (with ad hoc CI-CD pipeline) and handle access with aws s3 signature. That's why i added a dedicated bucket in this stack.
The registry return source module url like s3::https://s3....s/vpc.zip", when you use release command from this python client.
The s3:: prefix causes Terraform to use AWS-style authentication when accessing the given URL. No need to give public access to your bucket. Read S3 Bucket
All management use case around this private terraform registry can be handled by this python client
Ths project has been battle tested in huge production workload since 2 years and cost less than 10$ per month.
- Usage of "X-Terraform-Get" for download API
- Add a dedicated lambda integration for download API : use s3 presigned url for all module which came from registry bucket
- Add tag on each resource, rewrote some iam declaration
Note:
- Using presigned s3 url, permit us to use this registry from other platform with no direct credentials with aws. They just see an https url.
- simplify sharing in a multi account context : we just manage JWT token
Reference:
You could have more specific information on original registry implementation document here.
Terraform's documented registry HTTP API is implemented via Amazon API Gateway relaying requests to a DynamoDB table that contains a simple index of modules. The module packages themselves can be stored at any non-registry module source address supported by Terraform, including in an S3 bucket with standard AWS authentication.
Terraform CLI supports bearer-token authentication credentials when making API requests. Credentials are configured on a per-hostname basis and apply to all services at that hostname.
Authorization is based on JWT token.
Users must create .terraformrc
file in their $HOME directory, with this content:
credentials "registry.my-domain.com" {
token = "Mytoken"
}
module "test" {
source = "registry.my-domain/data/kinesis-firehose/aws"
version = "0.2.0"
}
or
module "test" {
source = "registry.my-domain/data/kinesis-firehose/aws"
}
- Add a Rest API to publish module with dedicated credentials
- Add an optional way to publish an event (with AWS Event Bridge) when a new release is published
- Add a way to mark a module as deprecated ?
You could retrieve this source under example/registry
locals {
root_domain_name = "my-domain.com"
registry_domain_name = "registry.${local.root_domain_name}"
}
data "aws_route53_zone" "selected" {
name = local.root_domain_name
}
# create ACME Certificat
resource "aws_acm_certificate" "certificate" {
domain_name = local.registry_domain_name
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
# create DNS record for validate
resource "aws_route53_record" "certificate" {
allow_overwrite = true
name = tolist(aws_acm_certificate.certificate.domain_validation_options)[0].resource_record_name
records = [tolist(aws_acm_certificate.certificate.domain_validation_options)[0].resource_record_value]
type = tolist(aws_acm_certificate.certificate.domain_validation_options)[0].resource_record_type
zone_id = data.aws_route53_zone.selected.zone_id
ttl = 60
}
# Validate certificat
resource "aws_acm_certificate_validation" "certificate" {
certificate_arn = aws_acm_certificate.certificate.arn
validation_record_fqdns = [aws_route53_record.certificate.fqdn]
}
module "registry" {
source = "geronimo-iia/tf-registry/aws"
version = "1.0.2"
name_prefix = "registry"
storage = {
dynamodb = {
name : "my-domain-registry-tfe"
billing_mode : "PROVISIONED"
read : 5
write : 1
}
bucket = {
name : "my-domain-registry-tfe"
}
}
friendly_hostname = {
host = local.registry_domain_name
acm_certificate_arn = aws_acm_certificate.certificate.arn
}
tags = {
Product : "Registry"
ProductComponent : "terraform"
}
depends_on = [aws_acm_certificate.certificate]
}
resource "aws_route53_record" "registry" {
zone_id = data.aws_route53_zone.selected.zone_id
name = "${local.registry_domain_name}."
type = "A"
alias {
name = module.registry.dns_alias.hostname
zone_id = module.registry.dns_alias.route53_zone_id
evaluate_target_health = true
}
depends_on = [module.registry]
}
Name | Version |
---|---|
terraform | >= 1.5.0 |
archive | 2.4.0 |
aws | ~> 5.5.0 |
null | 3.2.1 |
Name | Version |
---|---|
null | 3.2.1 |
Name | Source | Version |
---|---|---|
authorizer | ./modules/registry-authorizer | n/a |
download | ./modules/registry-download | n/a |
jwt | ./modules/registry-jwt | n/a |
registry | ./modules/registry-service | n/a |
store | ./modules/registry-store | n/a |
Name | Type |
---|---|
null_resource.apigateway_create_deployment | resource |
Name | Description | Type | Default | Required |
---|---|---|---|---|
api_access_policy | If using a Private API requires you to have an access policy configured and accepts a string, but must be valid json. Defaults to Null | string |
null |
no |
api_type | Sets API type if you want a private API without a custom domain name, defaults to EDGE for public access | list(string) |
[ |
no |
domain_security_policy | Sets the TLS version to desired state, defaults to 1.2 | string |
"TLS_1_2" |
no |
dynamodb_enable_point_in_time_recovery | Enable DynamoDB point in time recovery | bool |
true |
no |
friendly_hostname | Configures a "friendly hostname" that will be used to reference objects in this registry. If this is set, the given hostname and certificate will be registered against the created API. Can be left unset if the service discovery information will be separately published at the friendly hostname, using the "services" output value. | object({ |
null |
no |
kms_key_id | Optional custom kms key id (default aws/secretsmanager) | string |
null |
no |
name_prefix | A name to use as the prefix for the created API Gateway REST API, DynamoDB tables, etc | string |
"terraform-registry" |
no |
s3_public_access | Bucket Public Access Block | object({ |
{ |
no |
secret_key_name | Optional AWS Secret name to store JWT secret | string |
null |
no |
storage | n/a | object({ |
{ |
no |
tags | Resource tags | map(string) |
{} |
no |
vpc_endpoint_ids | Sets the VPC endpoint ID for a private API, defaults to null | list(string) |
null |
no |
Name | Description |
---|---|
bucket_arn | Bucket arn |
bucket_name | Bucket name |
dns_alias | If the friendly_hostname input variable is set, this exports the hostname and Route53 zone id that should be used to point the friendly hostname at the registry API. If not using Route53 for DNS, you can alternatively create a regular CNAME record to the returned hostname. If friendly hostname is not enabled then this output is always null. |
dynamodb_table_arn | Dynamodb table arn |
dynamodb_table_name | Dynamodb table name |
registry_secret_key_name | JWT secret key name in aws secret manager |
rest_api_id | The id of the API Gateway REST API managed by this module. |
rest_api_stage_name | The id of the API Gateway deployment stage managed by this module. |
services | A service discovery configuration map for the deployed services. A JSON-serialized version of this should be published at /.well-known/terraform.json on an HTTPS server running at the friendly hostname for this registry. |
If you wanna use this project in production (like me...), I thinks that you should follow this tricks:
- Fork this project into your entrprise git server and add a remote branch 'github' to this repository
- Adjust variable of example 'registry' to deploy it in a quick and not so dirty mode :)
- Publish a dummy terraform module, see how it's managed in the dynamodb, test a
terraform init
etc... Use this python client. - Integrate the python client into your ci
- Have a look on notes