The Certer Terraform provider allows you to manage SSL/TLS certificate configurations, provision access keys, and fetch issued certificates directly within your Terraform workflows.
This provider is designed to interface with certer, a custom, containerized certificate manager solution currently supporting Let's Encrypt and ZeroSSL. Built on top of the lego Go library, it has the capacity to support more ACME providers in the future. Both the Certer control plane and this Terraform provider are open-source projects.
The Certer provider is published on the official Terraform Registry at Menschomat/certer.
To use the provider in your Terraform configuration, add it to your required_providers block:
terraform {
required_providers {
certer = {
source = "Menschomat/certer"
version = "~> 1.0"
}
}
}
provider "certer" {
address = "http://localhost:8080"
token = "your_admin_api_token"
}For local development or custom deployments, you can use the provider through either a Gitea Private Registry or a Local Plugin Directory.
Gitea has a built-in, Terraform-compliant package registry. When you write:
terraform {
required_providers {
certer = {
source = "gitea.dmz.k8s.menscho.space/m0space/certer"
version = "~> 1.0.0"
}
}
}Terraform resolves this by contacting gitea.dmz.k8s.menscho.space to download the pre-compiled binary matching your platform (e.g., linux_amd64 or darwin_arm64).
To publish your compiled provider to Gitea's registry:
- Compile and zip the provider binary:
cd terraform-provider-certer go build -o terraform-provider-certer zip terraform-provider-certer_1.0.0_darwin_arm64.zip terraform-provider-certer - Upload it to your Gitea instance using
curl(replaceYOUR_TOKENwith a Gitea personal access token):curl --header "Authorization: token YOUR_TOKEN" \ --upload-file terraform-provider-certer_1.0.0_darwin_arm64.zip \ https://gitea.dmz.k8s.menscho.space/api/packages/m0space/terraform-provider/certer/1.0.0/darwin_arm64.zip
Note: Gitea automatically handles Terraform service discovery under the hood at your domain's .well-known/terraform.json endpoint.
For offline use or local development, you can compile the provider and place it directly into your local Terraform plugin directory:
-
Build and copy the binary to your local plugin mirror directory (adjusting architecture name as needed):
cd terraform-provider-certer go build -o terraform-provider-certer # For macOS (Apple Silicon): mkdir -p ~/.terraform.d/plugins/gitea.dmz.k8s.menscho.space/m0space/certer/1.0.0/darwin_arm64/ cp terraform-provider-certer ~/.terraform.d/plugins/gitea.dmz.k8s.menscho.space/m0space/certer/1.0.0/darwin_arm64/terraform-provider-certer_v1.0.0
-
Reference your local source in your Terraform configuration:
terraform { required_providers { certer = { source = "gitea.dmz.k8s.menscho.space/m0space/certer" version = "1.0.0" } } }
When running
terraform init, it will resolve the provider directly from your local cache directory.
For either option, you must configure the provider block with your certer endpoint and admin API key:
provider "certer" {
address = "http://localhost:8080"
token = "your_admin_api_token"
}Manages a team configuration in certer. On creation, the server automatically generates a unique UUID v7 identifier for the team.
resource "certer_team" "example" {
name = "example-team"
description = "Example Team Description"
}name(String, Required) - The name of the team.description(String, Optional) - A description of the team.
id(String, Computed) - The unique UUID identifier of the team.
Manages a certificate configuration in the background renewal scheduler. When created, certer automatically schedules DNS-01/HTTP-01 ACME challenges to issue the certificate.
resource "certer_certificate" "example" {
primary = "example.com"
team_id = certer_team.example.id
sans = [
"*.example.com",
"www.example.com"
]
}primary(String, Required) - The primary domain name for the certificate. Changing this triggers resource replacement.team_id(String, Required) - The unique UUID identifier of the team that owns this certificate configuration.sans(List of String, Optional) - Subject Alternative Names (SANs) for the certificate.
Manages client API keys and access scopes in certer. On creation, the server automatically generates a secure 32-byte token and returns it in cleartext.
resource "certer_api_key" "web_client" {
description = "web-client-token"
allowed_certificates = [certer_certificate.example.id]
allowed_teams = [certer_team.example.id]
admin = false
}
# The generated cleartext token can be retrieved from state:
output "web_client_token" {
value = certer_api_key.web_client.cleartext_token
sensitive = true
}description(String, Optional) - A description of the API key configuration.allowed_certificates(List of String, Optional) - The list of certificate configuration UUIDs this standard token is authorized to fetch (ignored for admin tokens).allowed_teams(List of String, Optional) - The list of team UUIDs this token is scoped to (scopes configuration management if admin=true, or certificate retrieval if admin=false).admin(Boolean, Required) - Iftrue, this token is used for configuration management (control plane). Iffalse, this is a standard fetch token used to pull raw certificate and private key files.
The combination of the admin flag and the allowed_teams list dictates the token scoping level:
| Key Type | admin |
allowed_teams |
Authorized Actions |
|---|---|---|---|
| Root Admin | true |
Empty / Omitted | Full CRUD access to all configurations (/api/v1/config/*) across all teams. |
| Scoped Admin | true |
List of Team UUIDs | Manage configurations (certificates, keys) ONLY within the specified teams. Cannot manage root admin keys. |
| Fetch Key (Standard) | false |
List of Team UUIDs | Retrieves raw PEM certificate chain and private key assets (/api/v1/certificates) for certificates in allowed_certificates AND team_id in allowed_teams. Cannot access configuration. |
cleartext_token(String, Computed, Sensitive) - The generated plaintext token value. This is only returned by the server on creation and cannot be retrieved on subsequent reads.
Retrieves PEM-encoded certificate chains and private keys once they are successfully issued by certer. You can pass these to load balancers, CDN, or file structures.
data "certer_certificate_data" "example" {
certificate_id = certer_certificate.example.id
}
# Example: Output the certificate body
output "certificate_pem" {
value = data.certer_certificate_data.example.certificate
sensitive = true
}
# Example: Pass details to another resource (e.g. AWS ACM)
resource "aws_acm_certificate" "imported" {
private_key = data.certer_certificate_data.example.private_key
certificate_body = data.certer_certificate_data.example.certificate
}certificate_id(String, Required) - The unique configuration UUID of the certificate to fetch.domain(String, Computed) - The primary domain of the certificate.sans(List of String, Computed) - SANs associated with the certificate.issued(Boolean, Computed) -trueif the certificate has been successfully issued.certificate(String, Computed, Sensitive) - The PEM-encoded certificate chain.private_key(String, Computed, Sensitive) - The PEM-encoded private key.cert_filename(String, Computed) - The filename of the certificate stored on the server.key_filename(String, Computed) - The filename of the private key stored on the server.issued_at(String, Computed) - The start of validity (NotBefore) in RFC3339 format.expires_at(String, Computed) - The end of validity (NotAfter) in RFC3339 format.days_remaining(Integer, Computed) - Number of days remaining before expiration.is_valid(Boolean, Computed) -trueif current time is within validity bounds.serial_number(String, Computed) - Hexadecimal serial number of the certificate.issuer_common_name(String, Computed) - Common Name (CN) of the issuing authority.signature_algorithm(String, Computed) - Name of signature algorithm (e.g.SHA256-RSA).key_algorithm(String, Computed) - Public key algorithm (e.g.RSA,ECDSA).key_size(Integer, Computed) - Key size in bits (e.g.2048,256).
To build the provider binary locally, run:
cd terraform-provider-certer
go build -o terraform-provider-certerTo run provider and client unit tests, execute:
go test -v ./...This repository uses GoReleaser and GitHub Actions to automate compiles, checksum signatures, and release generation.
When you tag a commit on the main branch with a semantic version (e.g., v1.0.0), the Release GitHub Action workflow is automatically triggered.
This workflow will:
- Checkout the code at the tagged commit.
- Set up the Go environment based on the version in
go.mod. - Import the private GPG key used to sign the release (using the
GPG_PRIVATE_KEYandPASSPHRASErepository secrets). - Run GoReleaser to compile binaries for
freebsd,windows,linux, anddarwin, calculate SHA256 checksums, sign the checksums, and publish the release and its artifacts to GitHub (fully compliant with the Terraform Registry layout requirements).
To test the GoReleaser configuration locally without publishing or signing, install GoReleaser and run:
goreleaser build --snapshot --clean