Sets a Cloud NGFW local rulestack's outbound TRUST certificate. One argument, no object of its own. Targets
hashicorp/azurerm ~> 4.0.
- π Creates one
azurerm_palo_alto_local_rulestack_outbound_trust_certificate_associationβ the rulestack's outbound trust certificate, used for egress decryption. - π΄ There is no association object in Azure. The provider reads the parent rulestack, sets one field in its
securityServicesproperty, writes the whole rulestack back, and commits. That single fact explains everything else about this resource. - π― One argument.
certificate_idcarries the subscription, resource group, rulestack and certificate name, so nothing else is needed β and everything else this module emits is derived from it. β οΈ A singleton per rulestack. Two instances aimed at one rulestack overwrite each other with no error, because Terraform sees two unrelated resources.- β The rulestack module does not clobber it β a checked worry, disproved: the rulestack's own update reads first and touches only the six engine fields.
π‘ Why it matters: this is the smallest resource in the family and the one whose behavior is least visible from its schema. A two-line resource block performs a read-modify-write of the entire parent rulestack and then commits it. Eleven constants exist because none of that is inferable from the one argument you can see.
If this module saves you time:
- β Star the repository β it helps others find it.
- πΌ Connect on LinkedIn β linkedin.com/in/microsoftexpert
- β Buy me a coffee β buymeacoffee.com/microsoftexpert
flowchart TB
RG["terraform-azurerm-resource-group"]
VNET["terraform-azurerm-virtual-network: the VNet a VNET-deployed firewall is placed into, with its trusted and untrusted subnets"]
VWAN["terraform-azurerm-virtual-wan"]
VHUB["terraform-azurerm-virtual-hub: the Virtual WAN hub. It supplies BOTH the resource group and the region of anything registered against it"]
PUBIP["terraform-azurerm-public-ip: the addresses a firewall presents, and its egress NAT addresses. Passed by Resource ID"]
KV["terraform-azurerm-key-vault: holds the certificate a non-self-signed rulestack certificate points at, and its private key"]
RULESTACK["terraform-azurerm-palo-alto-local-rulestack: six threat-prevention engines and nothing else. Creating it does NOT commit it"]
RULE["terraform-azurerm-palo-alto-local-rulestack-rule: the traffic policy. PRIORITY is its identity and is force-new. Creating or updating a rule COMMITS the whole rulestack -- deleting one does NOT"]
PREFIXLIST["terraform-azurerm-palo-alto-local-rulestack-prefix-list: a named set of CIDR blocks, every entry validated as a real CIDR. Usable from BOTH sides of a rule's match"]
FQDNLIST["terraform-azurerm-palo-alto-local-rulestack-fqdn-list: a named set of domain names. The provider validates only that each entry is NOT EMPTY, and the list is usable from a rule's DESTINATION ONLY"]
CERTIFICATE["terraform-azurerm-palo-alto-local-rulestack-certificate: either self-signed or a Key Vault reference, and THREE of its arguments are force-new. Its DELETE does not commit"]
TRUSTASSOC["terraform-azurerm-palo-alto-local-rulestack-outbound-trust-certificate-association: a SINGLETON that sets one property on the rulestack"]
UNTRUSTASSOC["terraform-azurerm-palo-alto-local-rulestack-outbound-untrust-certificate-association: the independent twin, and a true clone of it"]
FWVNET["terraform-azurerm-palo-alto-next-generation-firewall-virtual-network-local-rulestack: THE RESOURCE THAT MAKES ALL OF THE ABOVE INSPECT TRAFFIC. Takes its REGION from the rulestack, carries no policy of its own, and takes HOURS to create"]
FWVHUB["terraform-azurerm-palo-alto-next-generation-firewall-virtual-hub-local-rulestack: the Virtual WAN twin of the VNet firewall, and NOT a clone of it. It REQUIRES an appliance slot, and Azure -- not the caller -- assigns its trusted and untrusted subnets. FIVE force-new fields against the VNet variant's three"]
APPLIANCE["terraform-azurerm-palo-alto-virtual-network-appliance: the family's odd one out. It creates a MICROSOFT.NETWORK resource, not a Cloud NGFW one, and the only thing making it Palo Alto is a delegation string the provider hardcodes. No locks, no commits, no rulestack"]
FWVHUBPANO["terraform-azurerm-palo-alto-next-generation-firewall-virtual-hub-panorama: the same Virtual WAN plumbing as its rulestack-managed twin, with policy moved OUT of Azure. It takes NO rulestack, takes a REGION as a real argument, takes NO locks and NO commits, and has SIX force-new fields"]
MODEL["A FIREWALL PICKS EXACTLY ONE POLICY MANAGEMENT MODEL: an Azure Rulestack managed natively in Azure, Palo Alto Panorama, or Strata Cloud Manager. That single choice is why there are SIX firewall resources -- two deployment models times three management models -- and it is what decides whether this rulestack is used at all"]
PANORAMA["Palo Alto Panorama: NOT an Azure resource and NOT a Terraform resource. Where every rule, address object, service and profile lives for a Panorama-managed firewall. Terraform passes one opaque base64 registration blob and can see none of the policy"]
FWVNETPANO["terraform-azurerm-palo-alto-next-generation-firewall-virtual-network-panorama: Panorama policy on the Virtual Network deployment model, which CLOSES the Panorama pair. It needs NO appliance, its network profile has NO force-new fields so the VNet and both subnets change in place, and it is the only firewall here whose DELETE allows 3 hours"]
FWVNETSCM["terraform-azurerm-palo-alto-next-generation-firewall-virtual-network-strata-cloud-manager: policy in Palo Alto SaaS. It carries a managed identity -- a thing only the two Strata variants in this family have -- and its tenant name is validated by a LIVE SUBSCRIPTION-SCOPED API CALL AT PLAN TIME -- so a plan needs real Azure access. Its default billing plan is NOT the stop-sell one"]
FWVHUBSCM["terraform-azurerm-palo-alto-next-generation-firewall-virtual-hub-strata-cloud-manager: the sixth and last firewall, CLOSING THIS FAMILY. It combines the Virtual WAN plumbing -- appliance prerequisite, Azure-assigned subnets, SIX force-new fields -- with Strata policy and its PLAN-TIME tenant check. Every fact it states already had a name elsewhere in the family"]
SCM["Palo Alto Strata Cloud Manager: a SaaS tenant, NOT an Azure resource and NOT a Terraform resource. Where every rule lives for a Strata-managed firewall. Terraform passes only its NAME, and the provider checks that name exists at PLAN time"]
UAI["terraform-azurerm-user-assigned-identity: the identity a Strata-managed firewall can reference. USER-ASSIGNED ONLY -- there is no system-assigned option -- so it is a prerequisite resource, and it is where principal_id and client_id are read from"]
RG -->|"resource_group_name and location. BOTH force-new, along with the name"| RULESTACK
RG -->|"resource_group_name"| VNET
RG -->|"resource_group_name"| VWAN
RG -->|"resource_group_name, force-new. NOTE the firewall takes no location argument at all"| FWVNET
RG -->|"resource_group_name, force-new. It need NOT be the hub's resource group, and no location argument exists here either"| FWVHUB
RG -->|"resource_group_name AND location, both force-new. BOTH Panorama-managed firewalls take a region as an argument, because neither has a rulestack to read one from"| FWVHUBPANO
VWAN -->|"the virtual wan id"| VHUB
RULESTACK -->|"the RULESTACK ID, and a rule is where policy actually lives. A create or update here COMMITS the rulestack"| RULE
RULESTACK -->|"the rulestack id, force-new. All three writes lock AND commit, delete included"| PREFIXLIST
RULESTACK -->|"the rulestack id, force-new. Its create does NOT wait for its own write"| FQDNLIST
RULESTACK -->|"the rulestack id, force-new. Unlike the two lists, this child's DELETE does not commit"| CERTIFICATE
KV -.->|"a VERSIONLESS certificate URI. Under the pinned provider line the validator accepts any nested item"| CERTIFICATE
PREFIXLIST -->|"the prefix list ID, which BOTH a rule's source and its destination can reference"| RULE
FQDNLIST -->|"the fqdn list ID, which ONLY a rule's destination can reference"| RULE
CERTIFICATE -->|"the certificate id, for a rule's inbound SSL inspection"| RULE
CERTIFICATE -->|"the certificate ID, and also the association's own Terraform resource id"| TRUSTASSOC
CERTIFICATE -->|"the certificate ID, same contract as the trust twin"| UNTRUSTASSOC
TRUSTASSOC -.->|"writes securityServices.outboundTrustCertificate ON THE RULESTACK, then commits"| RULESTACK
UNTRUSTASSOC -.->|"writes securityServices.outboundUnTrustCertificate ON THE RULESTACK, then commits"| RULESTACK
RULESTACK -->|"THE PAYOFF EDGE: the rulestack id, NOT force-new so the policy can be swapped in place. It also DECIDES THE FIREWALL'S REGION"| FWVNET
VNET -->|"virtual_network_id, plus the trusted and untrusted subnet ids. Both subnets are OPTIONAL to the provider and needed in practice"| FWVNET
PUBIP -->|"public ip Resource IDs: the addresses presented, the egress NAT addresses, and any DNAT frontend"| FWVNET
RULESTACK -->|"THE SAME PAYOFF EDGE, on the Virtual WAN side: the rulestack id, not force-new, and it decides this firewall's region too"| FWVHUB
VHUB -->|"virtual_hub_id, FORCE-NEW here unlike anything in the VNet variant's profile. No subnet ids are passed: Azure assigns them inside the hub"| FWVHUB
PUBIP -->|"public ip Resource IDs: the addresses presented, the egress NAT addresses, and any DNAT frontend"| FWVHUB
VHUB -->|"virtual_hub_id, force-new and the ONLY input besides the name. The provider takes the resource group from this ID and READS the hub for its location"| APPLIANCE
APPLIANCE -->|"network_virtual_appliance_id: a REQUIRED and FORCE-NEW hard prerequisite. Create the appliance first, and note it needs MICROSOFT.NETWORK rights rather than Cloud NGFW ones"| FWVHUB
VHUB -->|"virtual_hub_id, force-new. THE SAME provider schema helper the rulestack-managed hub firewall uses, so the whole network profile behaves identically"| FWVHUBPANO
APPLIANCE -->|"network_virtual_appliance_id: the SAME required, force-new prerequisite. The deployment model is unchanged by moving policy out of Azure"| FWVHUBPANO
PUBIP -->|"public ip Resource IDs: the addresses presented, the egress NAT addresses, and any DNAT frontend"| FWVHUBPANO
PANORAMA -.->|"a base64-encoded registration config, supplied as ONE OPAQUE ARGUMENT. It carries the credential that authenticates the firewall, and Azure decodes it and reports its parts back as computed attributes"| FWVHUBPANO
RG -->|"resource_group_name AND location, both force-new. Like its Virtual WAN twin and unlike the rulestack-managed firewalls, this one takes a region"| FWVNETPANO
VNET -->|"virtual_network_id plus the trusted and untrusted subnet ids. NONE of them force-new here, so all three change in place -- and both subnets are OPTIONAL to the provider and needed in practice"| FWVNETPANO
PUBIP -->|"public ip Resource IDs: the addresses presented, the egress NAT addresses, and any DNAT frontend"| FWVNETPANO
PANORAMA -.->|"the SAME opaque base64 registration argument as the Virtual WAN variant. No appliance and no rulestack are involved at all"| FWVNETPANO
RG -->|"resource_group_name AND location, both force-new. Every firewall whose policy lives outside Azure takes a region, because none of them has a rulestack to read one from"| FWVNETSCM
VNET -->|"virtual_network_id plus the trusted and untrusted subnet ids. NONE force-new, and both subnets are OPTIONAL to the provider and needed in practice"| FWVNETSCM
PUBIP -->|"public ip Resource IDs: the addresses presented, the egress NAT addresses, and any DNAT frontend"| FWVNETSCM
SCM -.->|"strata_cloud_manager_tenant_name: a NAME, not a Resource ID and not a blob. The provider lists the subscription's available tenants during PLAN and fails the plan if this one is not among them"| FWVNETSCM
UAI -.->|"identity_ids, optional. What the identity GRANTS is undocumented -- the provider's own docs for this block are copied from an unrelated resource -- so this suite attaches none by default"| FWVNETSCM
RG -->|"resource_group_name AND location, both force-new. It need NOT be the hub's resource group"| FWVHUBSCM
VHUB -->|"virtual_hub_id, FORCE-NEW. The SAME provider schema helper the other two Virtual WAN firewalls use, so the whole network profile behaves identically"| FWVHUBSCM
APPLIANCE -->|"network_virtual_appliance_id: the SAME required, force-new prerequisite, needing MICROSOFT.NETWORK rights rather than Cloud NGFW ones"| FWVHUBSCM
PUBIP -->|"public ip Resource IDs: the addresses presented, the egress NAT addresses, and any DNAT frontend"| FWVHUBSCM
SCM -.->|"strata_cloud_manager_tenant_name, checked against the subscription's available tenants during PLAN -- the same live call as its Virtual Network twin"| FWVHUBSCM
UAI -.->|"identity_ids, optional and of undocumented purpose. The only two firewalls here that can take an identity are the two Strata variants"| FWVHUBSCM
MODEL -.->|"choosing Azure Rulestack is what makes this rulestack relevant"| FWVNET
MODEL -.->|"choosing Azure Rulestack, on the Virtual WAN deployment model"| FWVHUB
MODEL -.->|"choosing Panorama on the Virtual WAN deployment model: the rulestack and every module above it become irrelevant"| FWVHUBPANO
MODEL -.->|"choosing Panorama on the Virtual Network deployment model: with both Panorama firewalls authored, this branch of the family is complete"| FWVNETPANO
MODEL -.->|"choosing Strata Cloud Manager on the Virtual Network deployment model: the rulestack branch becomes irrelevant, and policy leaves Azure entirely"| FWVNETSCM
MODEL -.->|"choosing Strata Cloud Manager on the Virtual WAN deployment model. With this authored, ALL SIX firewall resources and every rulestack child are modules -- the family is COMPLETE"| FWVHUBSCM
classDef me fill:#0078D4,stroke:#004578,color:#ffffff
classDef keystone fill:#004578,stroke:#002438,color:#ffffff
classDef sibling fill:#F3F6F9,stroke:#8A9BA8,color:#1B1F23
class RULESTACK,RULE,PREFIXLIST,FQDNLIST,CERTIFICATE,TRUSTASSOC,UNTRUSTASSOC,FWVNET,FWVHUB,APPLIANCE,FWVHUBPANO,FWVNETPANO,FWVNETSCM,FWVHUBSCM me
class MODEL keystone
class RG,VNET,VWAN,VHUB,PUBIP,KV,PANORAMA,SCM,UAI sibling
βΉοΈ This diagram is shared with
terraform-azurerm-palo-alto-local-rulestack-outbound-untrust-certificate-association, deliberately. The two resources are true clones in the provider's own source β the only differences across their entire implementations are whichsecurityServicesfield they set and the wording of four error strings. Giving them different diagrams would invent a distinction a reader would then have to go and verify.
flowchart TB
IN_CERT["certificate_id: the ONLY argument, REQUIRED and FORCE-NEW. An ARM certificate Resource ID, not the Key Vault URI the certificate module itself takes -- the two end in the same words and this module rejects the wrong one"]
IN_TIMEOUTS["timeouts: THREE keys, not four. There is no update path at all, so the provider declares no update timeout"]
DERIVED["EVERYTHING ELSE IS DERIVED FROM THAT ONE ID: the rulestack, the resource group and the certificate name all live inside it. That is why the resource takes no rulestack argument"]
THIS["the association resource -- one of a TRUE CLONE PAIR. The trust and untrust modules differ only in which property they set, so they share this diagram"]
NOTOBJECT["THERE IS NO ASSOCIATION OBJECT IN AZURE. The provider reads the parent rulestack, sets one field in securityServices, writes the whole rulestack back, and commits. Hence: no name, no tags, no identity of its own"]
IDENTITY["ITS TERRAFORM ID IS THE CERTIFICATE ID. There is no separate association id to look up when importing or reading state"]
SINGLETON["A SINGLETON PER RULESTACK. Two instances aimed at one rulestack would overwrite each other with no error, because Terraform sees two unrelated resources"]
SAFE["AND THE RULESTACK MODULE DOES NOT CLOBBER IT: the rulestack's update reads first and touches only the six engine fields, so it preserves both certificates"]
OUT_ID["id and certificate_id, which are the same string under the pinned provider"]
OUT_PARENT["certificate_name, rulestack_id, rulestack_name, resource_group_name and rulestack_path_within_the_subscription, all parsed from the one argument"]
OUT_CONST["eleven constants: what it really writes, that ARM stores the NAME, the singleton rule, both locks, create and delete both committing, the three-key timeouts tail, and how an out-of-band removal surfaces"]
IN_CERT --> THIS
IN_TIMEOUTS --> THIS
IN_CERT -.-> DERIVED
THIS --> NOTOBJECT
THIS -.-> IDENTITY
THIS -.-> SINGLETON
THIS -.-> SAFE
THIS --> OUT_ID
THIS --> OUT_PARENT
THIS --> OUT_CONST
classDef me fill:#0078D4,stroke:#004578,color:#ffffff
classDef keystone fill:#004578,stroke:#002438,color:#ffffff
classDef sibling fill:#F3F6F9,stroke:#8A9BA8,color:#1B1F23
class THIS keystone
class OUT_ID,OUT_PARENT,OUT_CONST me
class IN_CERT,IN_TIMEOUTS,DERIVED,NOTOBJECT,IDENTITY,SINGLETON,SAFE sibling
Resource inventory
| Resource | Count | Notes |
|---|---|---|
azurerm_palo_alto_local_rulestack_outbound_trust_certificate_association |
1 (this) |
One argument, force-new. No name, no tags, no identity of its own |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/azurerm |
~> 4.0 |
| Provider block | None here. The caller configures the provider, its authentication, and its mandatory features {} block |
| ARM surface | PaloAltoNetworks.Cloudngfw/localRulestacks β the parent's securityServices.outboundTrustCertificate property |
Schema notes that bite
- π΄ This writes the rulestack, not a child object. Create does a GET of the parent, sets
securityServices.outboundTrustCertificate, PUTs the whole rulestack, then commits. Delete does the same with the field unset. - π΄ The Terraform resource ID IS the certificate's Resource ID. The provider calls
SetIDwith it, so there is no separate association ID β which matters when writing animportblock or reading state. - π΄ ARM stores the certificate's NAME, not its ID. The provider parses your
certificate_idand writes only the name. So Terraform identifies the certificate by ID while ARM identifies it by name, and a certificate rename is a replacement this association must follow. β οΈ certificate_idis force-new and is the only argument, so any change replaces the association.β οΈ There is no update path at all, so thetimeoutstail has three keys, not four. Every other module in this family carries four.β οΈ A singleton per rulestack. One outbound trust certificate exists; two module instances would fight over it silently.β οΈ Two locks are taken β the certificate's ID and the parent rulestack's β on both create and delete.- β Create and delete BOTH commit. So removing this association takes effect immediately β unlike removing the certificate it points at, or a rule.
- β The rulestack module does not clobber this. Its update reads the existing rulestack and modifies only the six threat-prevention engine fields, preserving both outbound certificates.
- βΉοΈ An out-of-band removal surfaces as a changed
certificate_id, not as a missing resource β the read marks the resource gone only if the rulestack is gone. Drift is still detected and corrected, as a replacement. - βΉοΈ There are no computed attributes. The provider's
Attributes()map is empty.
| Permission | Scope | Why |
|---|---|---|
PaloAltoNetworks.Cloudngfw/localRulestacks/read |
the parent rulestack | Both create and delete read the rulestack first |
PaloAltoNetworks.Cloudngfw/localRulestacks/write |
the parent rulestack | The write is on the RULESTACK, not on an association object |
PaloAltoNetworks.Cloudngfw/localRulestacks/commit/action |
the parent rulestack | Both create and delete commit |
PaloAltoNetworks.Cloudngfw/localRulestacks/certificates/read |
the parent rulestack | Practically required β the certificate must exist to be referenced |
π΄ Note the permission this resource actually needs:
localRulestacks/write. Because it modifies the parent rather than creating a child, an identity scoped to certificate operations alone cannot create this association β and an identity that can create it can also edit the rulestack's threat-prevention engines.π No certificate material passes through. This module holds an ID and nothing else; nothing is sensitive, and plan access here is not credential access.
β οΈ Whoever can set this can redirect the firewall's outbound decryption to a certificate they control. Review it with the rules.
- A Cloud NGFW by Palo Alto Networks Marketplace subscription, and the
PaloAltoNetworks.Cloudngfwresource provider registered. - An existing local rulestack, and a certificate already created inside that same rulestack β see
terraform-azurerm-palo-alto-local-rulestack-certificate. - A firewall referencing the rulestack, for outbound decryption to actually happen.
- For a Key Vault backed certificate: the firewall's own grant to read it from the vault, which no Terraform resource here configures.
terraform-azurerm-palo-alto-local-rulestack-outbound-trust-certificate-association/
βββ providers.tf # required_version + the pinned azurerm. No provider block.
βββ variables.tf # 2 variables, 4 validations
βββ main.tf # 7 locals + the single keystone resource
βββ outputs.tf # 18 outputs
βββ README.md # this file
βββ SCOPE.md # the cross-module contract
βββ LICENSE # MIT
βββ .gitignore
provider "azurerm" {
features {}
}
module "outbound_trust_certificate" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-certificate.git?ref=v1.0.0"
rulestack_id = module.rulestack.id
name = "outbound-trust"
self_signed = true
}
module "outbound_trust_association" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-outbound-trust-certificate-association.git?ref=v1.0.0"
certificate_id = module.outbound_trust_certificate.id
}βΉοΈ That is the whole module. The rulestack is not an argument β it is inside the certificate ID.
Consumes
| Input | Type | Source |
|---|---|---|
certificate_id |
string |
terraform-azurerm-palo-alto-local-rulestack-certificate β id. Force-new |
Emits
| Output | Description |
|---|---|
id |
The Terraform resource ID β which is the certificate's Resource ID |
certificate_name |
What ARM actually stores in the rulestack |
rulestack_id |
Rebuilt from the certificate ID, since there is no rulestack argument |
this_writes_the_rulestack_itself_not_a_child_object |
Constant true |
a_rulestack_update_does_not_clobber_this_association |
Constant true |
1 Β· Minimal β the whole module
module "outbound_trust_association" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-outbound-trust-certificate-association.git?ref=v1.0.0"
certificate_id = module.outbound_trust_certificate.id
}
output "where_it_landed" {
value = {
certificate = module.outbound_trust_association.certificate_name
rulestack = module.outbound_trust_association.rulestack_name
}
}βΉοΈ One argument, and no rulestack. A certificate's Resource ID already contains the subscription, resource group, rulestack name and certificate name, so the provider derives everything from it β and so does this module.
2 Β· π΄ What this actually writes
output "not_what_it_looks_like" {
value = {
writes_the_parent = module.outbound_trust_association.this_writes_the_rulestack_itself_not_a_child_object
arm_stores_a_name = module.outbound_trust_association.arm_stores_the_certificate_name_not_its_id
id_is_the_cert_id = module.outbound_trust_association.the_resource_id_is_the_certificate_id
}
}π΄ There is no association object in Azure. The create path reads the parent rulestack, sets
securityServices.outboundTrustCertificate, writes the entire rulestack back, and commits. The delete does the same with the field cleared.βΉοΈ That single fact explains the rest of this resource: why it has one argument and no name, why it has no tags, why its Terraform ID is borrowed from the certificate, why it locks the rulestack, and why it needs
localRulestacks/writerather than a certificate permission.
3 Β· π΄ The ID confusion this module rejects
module "wrong" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-outbound-trust-certificate-association.git?ref=v1.0.0"
# REJECTED -- this is the Key Vault URI the CERTIFICATE module takes as input.
# certificate_id = "https://kv-netsec-eastus.vault.azure.net/certificates/edge"
# REJECTED -- this is the rulestack, a prefix of the certificate path.
# certificate_id = module.rulestack.id
# CORRECT.
certificate_id = module.outbound_trust_certificate.id
}
β οΈ Two arguments in this family end in_certificate_idand mean different things. The certificate module's ownkey_vault_certificate_idis a Key Vault data-plane URI; this module'scertificate_idis an ARM Resource ID. Passing the first here is the easiest mistake to make, so this module rejects anything startinghttps://with a message naming both forms.
β οΈ The rulestack ID is rejected too β it is a prefix of the certificate path, so the check is anchored with$.
4 Β· β οΈ A singleton per rulestack
# WRONG -- these two fight over one property, silently.
module "cert_a" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-certificate.git?ref=v1.0.0"
rulestack_id = module.rulestack.id
name = "cert-a"
self_signed = true
}
module "cert_b" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-certificate.git?ref=v1.0.0"
rulestack_id = module.rulestack.id
name = "cert-b"
self_signed = true
}
module "trust_a" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-outbound-trust-certificate-association.git?ref=v1.0.0"
certificate_id = module.cert_a.id
}
# module "trust_b" {
# source = "...same module..."
# certificate_id = module.cert_b.id # same rulestack -- overwrites trust_a
# }
output "singleton" {
value = module.trust_a.this_is_a_singleton_per_rulestack
}
β οΈ A rulestack has exactly one outbound trust certificate. Two module instances aimed at the same rulestack each write that one property, so whichever applies last wins β and Terraform reports nothing, because it sees two unrelated resources that both succeeded.βΉοΈ The untrust association is a separate, independent singleton. One of each per rulestack is the correct arrangement, and that is example 6.
5 Β· Changing the certificate replaces the association
module "new_certificate" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-certificate.git?ref=v1.0.0"
rulestack_id = module.rulestack.id
name = "outbound-trust-v2"
self_signed = true
}
module "outbound_trust_association" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-outbound-trust-certificate-association.git?ref=v1.0.0"
# Force-new, and the only argument. Changing it destroys and recreates.
certificate_id = module.new_certificate.id
}
output "replacement" {
value = module.outbound_trust_association.changing_the_certificate_replaces_this_association
}
β οΈ Both halves of that replacement write the rulestack and commit, so unlike the certificate resource itself the replacement is clean β no stale configuration is left in force. But it is still two rulestack writes, and the outbound trust certificate is briefly unset between them.π‘ If that gap matters, plan the change during a maintenance window rather than treating it as an in-place edit.
6 Β· ποΈ Both associations on one rulestack
module "trust_certificate" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-certificate.git?ref=v1.0.0"
rulestack_id = module.rulestack.id
name = "outbound-trust"
self_signed = true
}
module "untrust_certificate" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-certificate.git?ref=v1.0.0"
rulestack_id = module.rulestack.id
name = "outbound-untrust"
self_signed = true
}
module "trust_association" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-outbound-trust-certificate-association.git?ref=v1.0.0"
certificate_id = module.trust_certificate.id
}
module "untrust_association" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-outbound-untrust-certificate-association.git?ref=v1.0.0"
certificate_id = module.untrust_certificate.id
}
check "both_associations_are_on_the_same_rulestack" {
assert {
condition = module.trust_association.rulestack_id == module.untrust_association.rulestack_id
error_message = "The trust and untrust certificates belong to different rulestacks."
}
}βΉοΈ Outbound decryption needs both. The trust certificate is what the firewall presents to clients for sessions it successfully decrypts; the untrust certificate is what it presents when it cannot verify the upstream server. Configuring only one is a common half-finished state.
β οΈ These four resources all lock the same rulestack, so they apply strictly one after another β four rulestack writes and four commits.
7 Β· β The rulestack module does not clobber this
module "rulestack" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack.git?ref=v1.0.0"
name = "rs-edge-eastus"
resource_group_name = module.rg.name
location = module.rg.location
# Changing these later does NOT remove the association below.
security_services = {
anti_virus_profile = "BestPractice"
dns_subscription = "BestPractice"
}
}
module "trust_association" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-outbound-trust-certificate-association.git?ref=v1.0.0"
certificate_id = module.trust_certificate.id
}
output "checked_and_safe" {
value = module.trust_association.a_rulestack_update_does_not_clobber_this_association
}β This is a negative finding, and it is stated because the opposite is the obvious worry. Both resources write the same
securityServicesproperty bag β so it would be reasonable to assume that editing the rulestack's threat-prevention engines wipes the certificate associations.β It does not. The rulestack's update path reads the existing rulestack first and modifies only the six engine fields, leaving the outbound trust and untrust certificates untouched. Applying the rulestack module after this association does not undo it.
8 Β· Timeouts β three keys, not four
module "trust_association" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-outbound-trust-certificate-association.git?ref=v1.0.0"
certificate_id = module.trust_certificate.id
timeouts = {
create = "45m"
read = "10m"
delete = "45m"
# update = "45m" <-- does not exist here
}
}
β οΈ There is no update path at all, because the only argument is force-new. So the provider declares three timeouts and this module'stimeoutsobject has three keys. Every other module in this family has four.π‘ The defaults are 30 minutes for create and delete and 5 minutes for read. Create and delete are the long ones because each writes the rulestack and then waits for a commit.
βΉοΈ Terraform silently discards object keys a type does not declare, so an
updatekey would vanish without an error rather than telling you it does not apply.
9 Β· Importing an existing association
import {
to = module.trust_association.azurerm_palo_alto_local_rulestack_outbound_trust_certificate_association.this
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-netsec-eastus/providers/PaloAltoNetworks.Cloudngfw/localRulestacks/rs-edge-eastus/certificates/outbound-trust"
}
module "trust_association" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-outbound-trust-certificate-association.git?ref=v1.0.0"
certificate_id = module.trust_certificate.id
}βΉοΈ The import ID is the CERTIFICATE's Resource ID, because that is what this resource's Terraform ID is. There is no separate association ID to find β which is unusual enough to be worth stating, since the obvious guess would be a path ending in something like
/outboundTrustCertificate.
β οΈ The imported certificate must be the one currently set on the rulestack. If it is not, the first plan proposes a replacement, becausecertificate_idis force-new.
10 Β· Drift, and how it surfaces
output "drift_behavior" {
value = module.trust_association.an_out_of_band_removal_shows_as_a_changed_certificate_id
}βΉοΈ The read marks this resource gone only when the RULESTACK is missing β it does not test whether the certificate property is still set. So if someone clears the outbound trust certificate in the portal, the read rebuilds a certificate ID from an empty name rather than reporting the resource as absent.
β The outcome is still correct. The next plan sees
certificate_idchanging and, because that argument is force-new, proposes a replacement β which re-establishes the association. Drift is detected and corrected; it simply presents as a replacement rather than as a missing resource.π‘ Worth knowing when reading a plan: a replacement here with no configuration change means someone changed it outside Terraform.
11 Β· Asserting the whole rulestack agrees
check "every_palo_object_is_on_one_rulestack" {
assert {
condition = length(distinct([
module.rulestack.rulestack_path_within_the_subscription,
module.trust_certificate.rulestack_path_within_the_subscription,
module.trust_association.rulestack_path_within_the_subscription,
module.decrypt_egress.rulestack_path_within_the_subscription,
])) == 1
error_message = "Palo Alto objects in this composition target different rulestacks."
}
}π‘ Every module in this family emits
rulestack_path_within_the_subscriptionin the same subscription-independent form, so onedistinct()over them proves a composition is internally consistent regardless of environment.βΉοΈ This module derives its copy from the certificate ID rather than from a rulestack argument, which is the only way it could β and it means the assertion also proves the certificate itself is on the expected rulestack.
12 Β· What a `for_each` here would mean
# There is no sensible for_each over THIS resource for one rulestack --
# it is a singleton. A for_each is only meaningful across rulestacks.
module "trust_associations" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-outbound-trust-certificate-association.git?ref=v1.0.0"
for_each = module.trust_certificates # one certificate per rulestack
certificate_id = each.value.id
}
β οΈ Afor_eachacross certificates in ONE rulestack is a bug, not a pattern β every instance would overwrite the same property. The only meaningful iteration is one instance per rulestack, keyed by rulestack.π‘ Because each instance locks its own rulestack, instances across different rulestacks do apply in parallel β unlike the four-resource sequence in example 6, which all contend for one lock.
13 Β· The Key Vault path, end to end
module "trust_certificate" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-certificate.git?ref=v1.0.0"
rulestack_id = module.rulestack.id
name = "outbound-trust"
key_vault_certificate_id = "https://kv-netsec-eastus.vault.azure.net/certificates/outbound-trust"
}
module "trust_association" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-outbound-trust-certificate-association.git?ref=v1.0.0"
# The ARM id of the certificate object -- NOT the Key Vault URI above.
certificate_id = module.trust_certificate.id
}
output "still_outstanding" {
value = module.trust_certificate.the_firewall_needs_its_own_access_to_the_key_vault
}
β οΈ Both IDs appear in this example and they are not interchangeable. The certificate module takes the Key Vault data-plane URI; this module takes the ARM Resource ID that the certificate module emits asid.π And the composition is still not finished: a Key Vault backed certificate needs the firewall's own grant on the vault, which no resource here creates. The certificate module emits that as a constant rather than letting a clean apply imply completeness.
14 Β· Removing the association
# Simply delete the module block, or:
# terraform destroy -target='module.trust_association'
output "removal_is_immediate" {
value = module.trust_association.create_and_delete_both_commit_the_rulestack
}β The delete unsets the property, writes the rulestack and commits, so removal takes effect immediately. That is not true of the certificate this points at, whose delete does not commit, nor of a rule.
β οΈ Order matters when removing both. Destroy the association before the certificate: a certificate still referenced as the outbound trust certificate is one the rulestack is actively using, and Terraform's dependency graph will get this right only if the association depends on the certificate β which it does, throughcertificate_id.
15 Β· ποΈ End-to-end composition
provider "azurerm" {
features {}
}
module "rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-netsec-eastus"
location = "eastus"
}
module "rulestack" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack.git?ref=v1.0.0"
name = "rs-edge-eastus"
resource_group_name = module.rg.name
location = module.rg.location
security_services = {
vulnerability_profile = "BestPractice"
anti_spyware_profile = "BestPractice"
anti_virus_profile = "BestPractice"
url_filtering_profile = "BestPractice"
file_blocking_profile = "BestPractice"
dns_subscription = "BestPractice"
}
}
module "trust_certificate" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-certificate.git?ref=v1.0.0"
rulestack_id = module.rulestack.id
name = "outbound-trust"
self_signed = true
description = "Presented to clients for decrypted egress sessions"
}
module "untrust_certificate" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-certificate.git?ref=v1.0.0"
rulestack_id = module.rulestack.id
name = "outbound-untrust"
self_signed = true
description = "Presented when the upstream server cannot be verified"
}
module "trust_association" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-outbound-trust-certificate-association.git?ref=v1.0.0"
certificate_id = module.trust_certificate.id
}
module "untrust_association" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-outbound-untrust-certificate-association.git?ref=v1.0.0"
certificate_id = module.untrust_certificate.id
}
module "corp_ranges" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-prefix-list.git?ref=v1.0.0"
rulestack_id = module.rulestack.id
name = "corp-ranges"
prefix_list = ["10.20.0.0/16"]
}
module "decrypt_egress" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-palo-alto-local-rulestack-rule.git?ref=v1.0.0"
rulestack_id = module.rulestack.id
name = "decrypt-egress"
priority = 1000
action = "Allow"
applications = ["ssl", "web-browsing"]
protocol = "application-default"
source_match = { local_rulestack_prefix_list_ids = [module.corp_ranges.id] }
destination_match = { cidrs = ["any"] }
decryption_rule_type = "SSLOutboundInspection"
}
check "outbound_decryption_is_completely_configured" {
assert {
condition = alltrue([
module.trust_association.rulestack_id == module.untrust_association.rulestack_id,
module.decrypt_egress.performs_decryption,
])
error_message = "Outbound decryption is half-configured: check that both certificates are associated and that a rule actually decrypts."
}
}
output "composition" {
value = {
rulestack = module.rulestack.id
trust_cert = module.trust_association.certificate_name
untrust_cert = module.untrust_association.certificate_name
decrypting = module.decrypt_egress.performs_decryption
# All six modules agree on the rulestack.
all_on_one_rulestack = length(distinct([
module.rulestack.rulestack_path_within_the_subscription,
module.trust_certificate.rulestack_path_within_the_subscription,
module.untrust_certificate.rulestack_path_within_the_subscription,
module.trust_association.rulestack_path_within_the_subscription,
module.untrust_association.rulestack_path_within_the_subscription,
module.decrypt_egress.rulestack_path_within_the_subscription,
])) == 1
# Editing the rulestack's engines later will not undo the associations.
associations_survive_a_rulestack_edit = module.trust_association.a_rulestack_update_does_not_clobber_this_association
}
}π This is the composition outbound decryption actually requires, and it takes six modules: the rulestack, two certificates, two associations, and a rule that decrypts. Configuring the certificates without the associations, or the associations without a decrypting rule, are both silent half-states β which is what the
checkblock guards.
β οΈ Every one of these locks the same rulestack, so they apply strictly in sequence and each of the writes commits. Expect the apply to take a while, and expect it to be correct rather than fast.βΉοΈ
all_on_one_rulestackcompares the family's sharedrulestack_path_within_the_subscriptionoutput across all six modules β and note that two of them derive theirs from a certificate ID rather than a rulestack argument, which is the only source they have.
| Group | Variables |
|---|---|
| The whole module | certificate_id (force-new, and the only argument) |
| Tail | timeouts β three keys, not four |
βΉοΈ There is no
tagsvariable and noname, because there is no object to tag or name: this resource is a property on the rulestack.
Full schemas
| Variable | Type | Default | Notes |
|---|---|---|---|
certificate_id |
string |
β | Force-new. An ARM certificate Resource ID, anchored at /certificates/<name> |
timeouts |
object |
{} |
create, read, delete. No update β the resource has no update path |
4 validations, listed rather than totaled. Three on certificate_id: non-blank; not a Key Vault data-plane URI (the confusion with the certificate module's own key_vault_certificate_id); and an anchored certificate Resource ID, so the rulestack ID β a prefix of the same path β is rejected. One on timeouts (duration format).
What is deliberately NOT restated. The provider validates certificate_id with its own certificate-ID validator, so the shape check here exists to name the two probable confusions rather than to be stricter. Nothing else about this resource is constrained enough to validate.
18 outputs: 2 passthrough, 5 derived, 11 constant.
| Output | Description |
|---|---|
id |
The Terraform resource ID β the certificate's Resource ID |
certificate_id |
The argument as applied; the same string as id |
certificate_name |
What ARM actually stores in the rulestack |
rulestack_id, rulestack_name, resource_group_name |
Rebuilt from the certificate ID |
rulestack_path_within_the_subscription |
The family's comparable form |
this_writes_the_rulestack_itself_not_a_child_object |
Constant true |
arm_stores_the_certificate_name_not_its_id |
Constant true |
the_resource_id_is_the_certificate_id |
Constant true |
this_is_a_singleton_per_rulestack |
Constant true |
changing_the_certificate_replaces_this_association |
Constant true |
create_and_delete_both_commit_the_rulestack |
Constant true |
both_the_certificate_and_the_rulestack_are_locked |
Constant true |
a_rulestack_update_does_not_clobber_this_association |
Constant true |
there_is_no_update_path_so_the_timeouts_tail_has_three_keys |
Constant true |
an_out_of_band_removal_shows_as_a_changed_certificate_id |
Constant true |
this_resource_has_no_tags |
Constant true |
βΉοΈ Eleven of eighteen outputs are constants, which is unusually high and is the point. This resource has one visible argument and a great deal of invisible behavior; the constants are where that behavior is written down.
Everything about this resource follows from one fact: there is no association object in Azure. The create path reads the parent rulestack, sets securityServices.outboundTrustCertificate to the certificate's name, writes the entire rulestack back, and commits. The delete does the same with the field cleared. That is why the resource has a single argument and no name, why it has no tags, why its Terraform ID is borrowed from the certificate, why it takes a lock on the rulestack as well as on the certificate, and why the permission it needs is localRulestacks/write rather than anything certificate-scoped.
Terraform identifies the certificate by ID; ARM identifies it by name. The argument is a full Resource ID, but the provider parses it and stores only the name. The practical consequence is that this association follows the name: because ARM holds no reference to update, renaming a certificate is a replacement that the association must follow in the same apply. It also means the read reconstructs the certificate ID from the name it finds β which is how drift is detected.
It is a singleton, and nothing enforces that. A rulestack has one outbound trust certificate. Two instances of this module aimed at the same rulestack each write that one property, and whichever applies last wins β with no error from Terraform, which sees two unrelated resources that both succeeded. The trust and untrust associations are separate, independent singletons, and outbound decryption normally wants one of each.
One worry checked and disproved. This resource and the rulestack module both write the same securityServices property bag, so it would be reasonable to assume that editing the rulestack's threat-prevention engines wipes the associations. It does not: the rulestack's update path reads the existing rulestack first and modifies only the six engine fields. That negative finding is emitted as a constant precisely because the opposite is the natural assumption, and because nothing in either module's schema would tell you.
The timeouts tail has three keys, and that is a schema fact rather than a style choice. The only argument is force-new, so the provider implements create, read and delete and no update at all β and declares three timeouts. Every other module in this family carries four. Supplying update here is not merely inert; the argument does not exist, and Terraform would silently discard it from the object rather than reject it.
Drift surfaces as a replacement, not as a missing resource. The read marks the resource gone only when the rulestack is gone; it does not test whether the certificate property is still set. If someone clears the association outside Terraform, the read rebuilds a certificate ID from an empty name, the next plan sees certificate_id changing, and β because that argument is force-new β proposes a replacement. The outcome is correct; the presentation is unusual, and a replacement here with no configuration change means someone changed it in the portal.
And this is a true clone of its untrust twin. The two provider implementations differ only in which securityServices field they set and in the wording of four error strings. The two modules are therefore identical in shape, share this documentation's structure and one shape diagram, and are stated as clones rather than given invented distinctions.
| Concern | This module's default | The caller's opt-out |
|---|---|---|
certificate_id |
No default. Required, and the only argument | β |
| A Key Vault URI passed here | Rejected, with both ID forms named | β |
| The rulestack ID passed here | Rejected β it is a prefix of the certificate path | β |
| The provider's own ID validator | Not restated as a stricter rule β the checks here name the probable confusions | β |
| Singleton-ness | Reported. Nothing can enforce it across separate resources | β |
| The rulestack-clobber question | Checked and reported as safe | β |
timeouts |
Three keys, matching the provider | β |
π There is nothing to make secure by default here β the module has one required argument and no choices. What it does instead is make the invisible visible: that a two-line resource block rewrites and commits the parent rulestack, that it needs rulestack-write permission rather than certificate permission, that it is a singleton, and that whoever can set it can redirect the firewall's outbound decryption. Those are the facts a reviewer needs, and none of them is in the schema.
terraform init -backend=false
terraform validate
terraform fmt -checkPin the module with ?ref=v1.0.0 β never a branch. This module is plan-only in this library; a human applies from CI.
β Removal takes effect immediately β the delete unsets the property, writes the rulestack and commits.
β οΈ Expect sequential applies. Everything touching one rulestack serializes on its lock, and each write commits.
| Covered by | What it proves |
|---|---|
terraform validate |
The resource and every expression parse and type-check against the pinned provider schema |
terraform fmt -check |
Canonical formatting |
terraform console with a root-module fixture |
Variable validations actually fire. All 4 rules were driven by deliberately bad inputs, and all 7 locals were driven to more than one value by good ones |
terraform plan (needs credentials) |
Whether the certificate exists in that rulestack |
| Nothing offline | Whether another resource already set this property, or whether a second instance is fighting over it |
| Nothing at all | That the association is a singleton β Terraform has no way to know |
β οΈ Terraform skips a validation whose referenced variable has already failed, so a short error list is not proof a rule is missing.
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-netsec-eastus/providers/PaloAltoNetworks.Cloudngfw/localRulestacks/rs-edge-eastus/certificates/outbound-trust"
certificate_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-netsec-eastus/providers/PaloAltoNetworks.Cloudngfw/localRulestacks/rs-edge-eastus/certificates/outbound-trust"
certificate_name = "outbound-trust"
rulestack_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-netsec-eastus/providers/PaloAltoNetworks.Cloudngfw/localRulestacks/rs-edge-eastus"
rulestack_name = "rs-edge-eastus"
resource_group_name = "rg-netsec-eastus"
rulestack_path_within_the_subscription = "/resourceGroups/rg-netsec-eastus/providers/PaloAltoNetworks.Cloudngfw/localRulestacks/rs-edge-eastus"
this_writes_the_rulestack_itself_not_a_child_object = true
arm_stores_the_certificate_name_not_its_id = true
the_resource_id_is_the_certificate_id = true
this_is_a_singleton_per_rulestack = true
a_rulestack_update_does_not_clobber_this_association = true
there_is_no_update_path_so_the_timeouts_tail_has_three_keys = true
| Symptom | Cause | Fix |
|---|---|---|
certificate_id must be an ARM Resource ID, not a Key Vault data-plane URI |
Passed the certificate module's key_vault_certificate_id |
Pass its id output instead |
certificate_id must be a local rulestack CERTIFICATE Resource ID |
Passed the rulestack ID, a prefix of the same path | Pass the certificate's ID |
An update timeout is ignored |
There is no update path; the key does not exist | Terraform discards undeclared object keys silently |
| A replacement is planned with no configuration change | Someone changed the association outside Terraform | Expected β this is how drift surfaces here |
| Two associations keep overwriting each other | Two module instances on one rulestack | It is a singleton; use one per rulestack |
| An apply fails on permissions despite certificate access | The write is on the rulestack | Grant localRulestacks/write and commit/action |
| Applies are slow and strictly sequential | Everything on one rulestack shares its lock | Expected; each write also commits |
| Outbound decryption still does not happen | The certificates are associated but no rule decrypts | Add a rule with decryption_rule_type = "SSLOutboundInspection" |
| A Key Vault backed certificate will not load | The firewall lacks its own grant on the vault | Grant it on the vault; no resource here does |
| Destroying the certificate fails while this exists | The association depends on it | Destroy the association first; the dependency graph does this for you |
azurerm_palo_alto_local_rulestack_outbound_trust_certificate_associationβ the provider resource- What is Cloud NGFW by Palo Alto Networks? β the Marketplace subscription and the
PaloAltoNetworks.Cloudngfwresource provider - Cloud NGFW by Palo Alto Networks FAQ β what an Azure Rulestack can configure
az palo-alto cloudngfw local-rulestackβ thecommitoperation both create and delete performterraform-azurerm-palo-alto-local-rulestack-certificateβ the certificate this associates, and where the Key Vault story livesterraform-azurerm-palo-alto-local-rulestack-outbound-untrust-certificate-associationβ the independent twin, and a true clone of this moduleterraform-azurerm-palo-alto-local-rulestackβ the parent, whosesecurityServicesproperty this actually writesterraform-azurerm-palo-alto-local-rulestack-ruleβ whereSSLOutboundInspectionis turned onterraform-azurerm-resource-groupβ the resource group- This module's
SCOPE.md
π "Infrastructure as Code should be standardized, consistent, and secure."