frostmoln_appgw_listener (Resource)
Manages a listener on a Frostmoln Application Gateway: one bound port — or, on a tcp listener, a range of them — with its TLS settings and its network firewall.
There are two shapes, and they are not interchangeable:
http/httpsare inspected, routed and (forhttps) TLS-terminated. Routes hang off the listener and each route names the backend pool it forwards to.tcpforwards bytes at layer 4. It has no routes, so it names its onebackend_pool_iddirectly; it terminates no TLS and is not inspected by the WAF. The source-CIDR, geo, rate-limit and connection controls all still apply — that is what makes it worth having over a plain load balancer.
The listener API has no update operation, so every attribute forces a new resource.
A listener is authored, not live: its ROUTING starts serving on the gateway's next configuration apply. Its port is the one exception on this whole API: it is opened on the gateway's public ingress when the listener is created and closed when it is destroyed, without an apply. So replacing a listener on the same port leaves that port closed between the destroy and the create, and traffic to it is dropped for that window — create_before_destroy where you can.
Two listeners on one gateway may not overlap in port space; a second one claiming a port an existing listener binds is refused with LISTENER_PORT_IN_USE. A few ports belong to the gateway appliance itself and are refused for every protocol.
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 — allowed_cidrs, backend_pool_id, default_certificate_id, denied_cidrs, gateway_id, geo_block_mode, geo_countries, max_connections, name, port, port_range_end, protocol, rate_limit_burst, rate_limit_rps, redirect_to_https, sni_certificate_ids, tls_cipher_profile, tls_min_version: the API has no update route for a listener — the server registers POST, GET and DELETE and nothing else — so any change destroys and re-creates it.
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
# An http listener that only redirects, and the https listener that serves.
resource "frostmoln_appgw_listener" "http" {
gateway_id = frostmoln_application_gateway.edge.id
name = "http"
protocol = "http"
port = 80
redirect_to_https = true
# Resource tags: your own metadata for search and filtering. Merged with
# the provider's default_tags; changing them updates in place.
tags = {
env = "prod"
}
}
resource "frostmoln_appgw_listener" "https" {
gateway_id = frostmoln_application_gateway.edge.id
name = "https"
protocol = "https"
port = 443
default_certificate_id = frostmoln_appgw_certificate.www.id
tls_min_version = "1.2"
tls_cipher_profile = "modern"
# Deny-by-default is NOT the behaviour here: omitting allowed_cidrs allows
# every source. Name them to restrict.
allowed_cidrs = ["0.0.0.0/0"]
# Refuse traffic from countries you do not serve.
geo_block_mode = "deny"
geo_countries = ["RU", "KP"]
rate_limit_rps = 200
rate_limit_burst = 400
}
# A tcp listener forwards bytes at layer 4: no routes, no TLS termination, no
# request inspection. It names the ONE pool it forwards to, and the source-CIDR,
# geo and rate-limit controls above all still apply.
#
# rate_limit_rps counts CONNECTIONS here, not requests. For SMTP, where one
# connection carries a whole session, an HTTP-shaped figure is orders of
# magnitude too permissive.
resource "frostmoln_appgw_listener" "smtp" {
gateway_id = frostmoln_application_gateway.edge.id
name = "smtp"
protocol = "tcp"
port = 25
backend_pool_id = frostmoln_appgw_backend_pool.mail.id
rate_limit_rps = 10
rate_limit_burst = 20
}
# A RANGE, inclusive, at most 512 ports. Each connection is forwarded to the
# SAME port on the backend that the client connected to, so 50000-50100 reaches
# 50000-50100 on your servers -- which is why the pool's health check has to name
# a port of its own: those backends have no fixed port to probe.
#
# NOTE THE SECOND POOL. A ranged listener and a single-port listener cannot share
# one pool: the ranged one forwards to whatever port the client used, the
# single-port one forwards to the port each backend declares, and a pool holds
# one answer. Pointing both at `mail` above is accepted by the API and then
# refused at every configuration apply -- including applies that change something
# else entirely -- while both listeners have already opened their public ports.
resource "frostmoln_appgw_backend_pool" "ftp" {
gateway_id = frostmoln_application_gateway.edge.id
name = "ftp-data"
protocol = "tcp"
}
resource "frostmoln_appgw_listener" "ftp_passive" {
gateway_id = frostmoln_application_gateway.edge.id
name = "ftp-passive"
protocol = "tcp"
port = 50000
port_range_end = 50100
backend_pool_id = frostmoln_appgw_backend_pool.ftp.id
}Schema
Required
gateway_id(String) The Application Gateway this listener belongs to.name(String) The name of the listener.port(Number) The port to bind, or the FIRST port of the range whenport_range_endis set.protocol(String) The listener protocol:http,httpsortcp.A
tcplistener requiresbackend_pool_idand refuses certificates,tls_min_version,tls_cipher_profile,redirect_to_https, a WAF policy and anyfrostmoln_appgw_routebeneath it — none of them exist at layer 4.
Optional
allowed_cidrs(List of String) Source CIDRs allowed to reach this listener. Omit to allow all sources.backend_pool_id(String) The pool atcplistener forwards to. Required ontcpand refused onhttp/https, where eachfrostmoln_appgw_routenames its own pool.The pool must be on this gateway, must have
protocol = "http", must not usesession_affinity = "cookie"(a cookie is an HTTP header the gateway never writes at layer 4) and must not already be the target of an http route: the gateway serves a pool in one protocol mode.default_certificate_id(String) The certificate served when no SNI matches.httpslisteners only.denied_cidrs(List of String) Source CIDRs refused by this listener.geo_block_mode(String) Country filtering:off,allow(onlygeo_countries) ordeny(everything exceptgeo_countries).geo_countries(List of String) ISO 3166-1 alpha-2 country codes. Required whengeo_block_modeisallowordeny.max_connections(Number) Maximum concurrent connections on this listener. Must not exceed the flavor'smax_concurrent_connections, which is enforced server-side — a larger value is refused at create withFLAVOR_LIMIT_EXCEEDEDnaming the flavor.port_range_end(Number) The last port of a range, inclusive.tcplisteners only; on anhttporhttpslistener it is refused rather than ignored.Omit it for a single port. It must be strictly greater than
portand the span may cover at most 512 ports — the appliance opens one socket per port in the range.A ranged listener forwards each connection to the same port on the backend that the client connected to, so
8000-8100reaches8000-8100on your servers. Those backends therefore have no fixed port to probe, and the pool'sfrostmoln_appgw_health_checkmust set its ownport.rate_limit_burst(Number) Ceiling inside any one second, aboverate_limit_rps. Same unit asrate_limit_rps: requests onhttp/https, connections ontcp.rate_limit_rps(Number) Sustained rate permitted per source address.~> The unit depends on
protocol. Onhttp/httpsit counts requests per second; ontcpit counts connections per second, because layer 4 has no request to count. For a protocol where one connection carries a whole session — SMTP, IMAP, MQTT — the same number is a far tighter limit than it is for HTTP, so set it against the connections you expect rather than reusing an HTTP figure.redirect_to_https(Boolean) Redirect requests to the https listener instead of serving them.sni_certificate_ids(List of String) Additional certificates selected by SNI.httpslisteners only.tags(Map of String) Resource tags on the listener: your own key/value metadata for search and filtering, stored by the platform only and never pushed to the appliance. Changing them updates the listener 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.tls_cipher_profile(String) The cipher profile:modernorintermediate.httpslisteners only — atcplistener terminates no TLS and reports none, so this reads null on one.tls_min_version(String) The minimum TLS version accepted:1.2or1.3.httpslisteners only — atcplistener terminates no TLS and reports none, so this reads null on one.
Read-Only
created_at(String) The creation timestamp.enabled(Boolean) Whether the listener is serving.id(String) The unique identifier of the listener.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.waf_policy_id(String) The WAF policy applied to this listener, if any — anoverlay-scoped policy. Read-only here: attach one withfrostmoln_appgw_waf_policy_attachment, which is where the attachment's lifecycle lives.