frostmoln_appgw_certificate (Resource)
Manages a TLS certificate on a Frostmoln Application Gateway.
~> private_key_pem is written to Terraform state. The provider never adopts a key from an API response, so on that attribute your state is the only place it is kept across refreshes — which means your state file holds key material and must be treated as a secret: a remote backend with encryption at rest and restricted access. Keeping the PEM out of your .tf files does not keep it out of state — a value read from a variable or a secret store is persisted exactly like a literal once it is assigned to private_key_pem. Use private_key_pem_wo instead: it carries the same key on apply and is never written to the plan or to state. The other way to keep a key out of Terraform is not to hand it one — a platform-issued ACME certificate (source = acmeDns01, obtained out of band — this resource only uploads) is attached to a listener by ID and never carries key material. See the Secrets in Terraform state guide.
The certificate API has no update operation, so every attribute forces a new resource. Rotating a certificate therefore creates a new one and destroys the old — attach it to the listener via create_before_destroy if you cannot take the interruption.
Authoritative scope — who owns what on this resource, declared in internal/scopedecl and machine-checked against the schema.
Enacted and reconciled — every configurable attribute not listed below: the platform applies it, and a refresh reads the truth back.
Create-immutable — chain_pem, gateway_id, name, private_key_pem, private_key_pem_wo_version: certificate material and its name are fixed once uploaded — changing one uploads a new certificate.
Not enacted state — private_key_pem_wo: write-only: sent, never stored or read back — the private_key_pem_wo_version companion carries change detection.
Observed, not enacted — tags_all: keys set outside Terraform — in the portal, by the fm CLI, or stamped by the platform — appear only here and are kept on every apply, never removed.
Example Usage
# The private key is written to Terraform state: the provider never adopts one
# from an API response, so state is the only place it is kept across refreshes. Use a remote backend
# with encryption at rest, and source the material from a variable or a secret
# store rather than committing PEM to your configuration. Prefer
# private_key_pem_wo (the second block below), which keeps the key out of state
# entirely.
resource "frostmoln_appgw_certificate" "www" {
gateway_id = frostmoln_application_gateway.edge.id
name = "www"
chain_pem = var.www_certificate_chain
private_key_pem = var.www_certificate_key
# Resource tags: your own metadata for search and filtering. Merged with
# the provider's default_tags; changing them updates in place.
tags = {
env = "prod"
}
}
# The write-only form. private_key_pem_wo never reaches the plan or state; it
# needs Terraform 1.11 or later. Exactly one of private_key_pem and
# private_key_pem_wo must be set.
#
# Because Terraform cannot see a write-only value change, bumping
# private_key_pem_wo_version is what uploads the current key — and it does so by
# REPLACING the certificate, because the certificate API has no update
# operation. Attach it with create_before_destroy if the listener cannot take
# the interruption.
resource "frostmoln_appgw_certificate" "api" {
gateway_id = frostmoln_application_gateway.edge.id
# The name carries the version, so create_before_destroy has a distinct name
# to create under. With a fixed name the replacement is created while the old
# certificate still exists — and if the gateway treats (gateway, name) as an
# identity, that collides and the rotation fails at the worst moment.
name = "api-${var.api_certificate_version}"
chain_pem = var.api_certificate_chain
private_key_pem_wo = var.api_certificate_key
private_key_pem_wo_version = var.api_certificate_version
lifecycle {
create_before_destroy = true
}
}
variable "www_certificate_chain" {
type = string
description = "PEM chain, leaf first."
}
variable "www_certificate_key" {
type = string
sensitive = true
description = "PEM private key for the chain."
}
variable "api_certificate_chain" {
type = string
description = "PEM chain, leaf first."
}
variable "api_certificate_key" {
type = string
sensitive = true
description = "PEM private key for the chain. Never reaches Terraform state."
}
variable "api_certificate_version" {
type = string
default = "1"
description = "Bump to rotate. Do NOT derive it from the key — it is not sensitive and is printed in plan output."
}Schema
Required
chain_pem(String) The PEM certificate chain, leaf first.gateway_id(String) The Application Gateway this certificate belongs to.name(String) The name of the certificate.
Optional
NOTE: Write-only arguments are supported in Terraform 1.11 and later.
private_key_pem(String, Sensitive) The PEM private key forchain_pem. The provider preserves it from prior state on every refresh and never adopts one from an API response. Stored in Terraform state in plaintext —sensitiveredacts CLI output, not the state file. See the Secrets in Terraform state guide. Preferprivate_key_pem_wo, which carries the same key but is never written to state; exactly one of the two must be set.private_key_pem_wo(String, Sensitive, Write-only) The PEM private key forchain_pem, as a write-only argument: the key reaches the provider on apply and is never written to the plan or to state. Requires Terraform 1.11 or later. Exactly one ofprivate_key_pemorprivate_key_pem_womust be set, andprivate_key_pem_wo_versionis required whenever this one is. The key must be at least one character — omitting it is not a way to upload a certificate without one. Terraform cannot see a write-only value, so it cannot detect a change to the key: changingprivate_key_pem_wo_versionis what makes the next apply send the current key, and it does so by REPLACING the certificate, because the certificate API has no update operation. Editing the key without touching the version does nothing.private_key_pem_wo_version(String) Change tracker forprivate_key_pem_wo, required whenever that attribute is set. Changing this value REPLACES the certificate and uploads the currentprivate_key_pem_wo— the same lifecycle a change toprivate_key_pemhas, since the certificate API has no update operation. Leaving it alone leaves the uploaded certificate untouched however much the write-only key changes. Its content is arbitrary — a counter or a date is typical — and unlike the key it is stored in state, so do not derive it from the key or from anything in it: a digest of the key is printed verbatim interraform planoutput, CI job logs and PR plan comments, and a digest is an offline confirmation oracle — it lets anyone holding it test a guessed key and correlate the same key across environments.terraform importleaves this unset, so the first apply against an imported certificate plans a REPLACEMENT.tags(Map of String) Resource tags on the certificate: your own key/value metadata for search and filtering, stored by the platform only and never pushed to the appliance. Changing them updates the certificate in place (every other attribute replaces it): no config apply, noconfig_generationchange. At most 32 keys including the tenant's default tags, which the platform merges in at create; a key is 1-64 bytes matching^[a-zA-Z0-9]([a-zA-Z0-9._:/-]*[a-zA-Z0-9])?$, a value at most 256 bytes; keys beginningfrostmoln_orfrostmoln-(any case) are reserved. Merged with the provider'sdefault_tagson every write (a key set here wins). Holds only the keys this configuration sets; the full set is intags_all.
Read-Only
auto_renew(Boolean) Whether the platform renews it.common_name(String) The certificate's common name.created_at(String) The creation timestamp.dns_zone_id(String) The DNS zone used for ACME validation, if any.fingerprint_sha256(String) The SHA-256 fingerprint.id(String) The unique identifier of the certificate.issuer(String) The issuing CA.not_after(String) The end of the validity window.not_before(String) The start of the validity window.sans(List of String) The certificate's subject alternative names.serial_number(String) The certificate's serial number.source(String) How the platform obtained the certificate:uploadedoracmeDns01.status(String) The certificate's lifecycle state.tags_all(Map of String) Every tag the platform holds on this resource: the provider'sdefault_tags, this resource's owntags(which win on a shared key), and any key set outside Terraform — in the portal, by the fm CLI, or stamped by the platform. Keys set outside Terraform appear only here and are kept on every apply, never removed.updated_at(String) The last update timestamp.