0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-08-22 19:38:35 +00:00

fix(firewall): fw controls bugfixes (#287)

fix(firewall): fw controls bugfixes

- fix alias examples in docs
- docs & examples improvements
- add support for security group insertion

Release-As: 0.17.0-rc2
This commit is contained in:
Pavel Boldyrev 2023-04-03 20:42:35 -04:00 committed by GitHub
parent fc08e19f86
commit 1bfc29e2cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 197 additions and 90 deletions

View File

@ -65,9 +65,10 @@ resource "proxmox_virtual_environment_cluster_firewall_security_group" "webserve
rules can use arbitrary strings. rules can use arbitrary strings.
- `log` - (Optional) Log level for this rule (`emerg`, `alert`, `crit`, - `log` - (Optional) Log level for this rule (`emerg`, `alert`, `crit`,
`err`, `warning`, `notice`, `info`, `debug`, `nolog`). `err`, `warning`, `notice`, `info`, `debug`, `nolog`).
- `macro`- (Optional) Macro name. Use predefined standard macro. - `macro`- (Optional) Macro name. Use predefined standard macro
from https://pve.proxmox.com/pve-docs/pve-admin-guide.html#_firewall_macro_definitions
- `proto` - (Optional) Restrict packet protocol. You can use protocol names - `proto` - (Optional) Restrict packet protocol. You can use protocol names
or simple numbers (0-255), as defined in '/etc/protocols'. as defined in '/etc/protocols'.
- `source` - (Optional) Restrict packet source address. This can refer - `source` - (Optional) Restrict packet source address. This can refer
to a single IP address, an IP set ('+ipsetname') or an IP alias to a single IP address, an IP set ('+ipsetname') or an IP alias
definition. You can also specify an address range like definition. You can also specify an address range like

View File

@ -1,13 +1,13 @@
--- ---
layout: page layout: page
title: proxmox_virtual_environment_cluster_firewall_alias title: proxmox_virtual_environment_firewall_alias
permalink: /resources/virtual_environment_cluster_firewall_alias permalink: /resources/virtual_environment_firewall_alias
nav_order: 7 nav_order: 7
parent: Resources parent: Resources
subcategory: Virtual Environment subcategory: Virtual Environment
--- ---
# Resource: proxmox_virtual_environment_cluster_firewall_alias # Resource: proxmox_virtual_environment_firewall_alias
Aliases are used to see what devices or group of devices are affected by a rule. Aliases are used to see what devices or group of devices are affected by a rule.
We can create aliases to identify an IP address or a network. Aliases can be We can create aliases to identify an IP address or a network. Aliases can be
@ -16,7 +16,7 @@ created on the cluster level, on VM / Container level.
## Example Usage ## Example Usage
```terraform ```terraform
resource "proxmox_virtual_environment_cluster_firewall_alias" "local_network" { resource "proxmox_virtual_environment_firewall_alias" "local_network" {
depends_on = [proxmox_virtual_environment_vm.example] depends_on = [proxmox_virtual_environment_vm.example]
node_name = proxmox_virtual_environment_vm.example.node_name node_name = proxmox_virtual_environment_vm.example.node_name
@ -27,7 +27,7 @@ resource "proxmox_virtual_environment_cluster_firewall_alias" "local_network" {
comment = "Managed by Terraform" comment = "Managed by Terraform"
} }
resource "proxmox_virtual_environment_cluster_firewall_alias" "ubuntu_vm" { resource "proxmox_virtual_environment_firewall_alias" "ubuntu_vm" {
name = "ubuntu" name = "ubuntu"
cidr = "192.168.0.1" cidr = "192.168.0.1"
comment = "Managed by Terraform" comment = "Managed by Terraform"

View File

@ -15,6 +15,11 @@ Manages firewall options on VM / Container level.
```terraform ```terraform
resource "proxmox_virtual_environment_firewall_options" "example" { resource "proxmox_virtual_environment_firewall_options" "example" {
depends_on = [proxmox_virtual_environment_vm.example]
node_name = proxmox_virtual_environment_vm.example.node_name
vm_id = proxmox_virtual_environment_vm.example.vm_id
enabled = false enabled = false
dhcp = true dhcp = true

View File

@ -18,7 +18,10 @@ level, on VM / Container level.
```terraform ```terraform
resource "proxmox_virtual_environment_firewall_rules" "inbound" { resource "proxmox_virtual_environment_firewall_rules" "inbound" {
depends_on = [proxmox_virtual_environment_vm.example] depends_on = [
proxmox_virtual_environment_vm.example,
proxmox_virtual_environment_cluster_firewall_security_group.example,
]
node_name = proxmox_virtual_environment_vm.example.node_name node_name = proxmox_virtual_environment_vm.example.node_name
vm_id = proxmox_virtual_environment_vm.example.vm_id vm_id = proxmox_virtual_environment_vm.example.vm_id
@ -42,47 +45,66 @@ resource "proxmox_virtual_environment_firewall_rules" "inbound" {
proto = "tcp" proto = "tcp"
log = "info" log = "info"
} }
rule {
security_group = proxmox_virtual_environment_cluster_firewall_security_group.example.name
comment = "From security group"
iface = "net0"
}
} }
``` ```
## Argument Reference ## Argument Reference
- `node_name` - (Optional) Node name. Leave empty for cluster level aliases. - `node_name` - (Optional) Node name. Leave empty for cluster level rules.
- `vm_id` - (Optional) VM ID. Leave empty for cluster level aliases. - `vm_id` - (Optional) VM ID. Leave empty for cluster level rules.
- `container_id` - (Optional) Container ID. Leave empty for cluster level aliases. - `container_id` - (Optional) Container ID. Leave empty for cluster level
rules.
- `rule` - (Optional) Firewall rule block (multiple blocks supported). - `rule` - (Optional) Firewall rule block (multiple blocks supported).
The provider supports two types of the `rule` blocks:
- a rule definition block, which includes the following arguments:
- `action` - (Required) Rule action (`ACCEPT`, `DROP`, `REJECT`). - `action` - (Required) Rule action (`ACCEPT`, `DROP`, `REJECT`).
- `type` - (Required) Rule type (`in`, `out`). - `type` - (Required) Rule type (`in`, `out`).
- `comment` - (Optional) Rule comment. - `comment` - (Optional) Rule comment.
- `dest` - (Optional) Restrict packet destination address. This can refer to - `dest` - (Optional) Restrict packet destination address. This can
a single IP address, an IP set ('+ipsetname') or an IP alias definition. refer to a single IP address, an IP set ('+ipsetname') or an IP alias
You can also specify an address range like `20.34.101.207-201.3.9.99`, or definition. You can also specify an address range
a list of IP addresses and networks (entries are separated by comma). like `20.34.101.207-201.3.9.99`, or a list of IP addresses and
Please do not mix IPv4 and IPv6 addresses inside such lists. networks (entries are separated by comma). Please do not mix IPv4 and
IPv6 addresses inside such lists.
- `dport` - (Optional) Restrict TCP/UDP destination port. You can use - `dport` - (Optional) Restrict TCP/UDP destination port. You can use
service names or simple numbers (0-65535), as defined in '/etc/services'. service names or simple numbers (0-65535), as defined
Port ranges can be specified with '\d+:\d+', for example `80:85`, and in `/etc/services`. Port ranges can be specified with '\d+:\d+', for
you can use comma separated list to match several ports or ranges. example `80:85`, and you can use comma separated list to match several
- `enable` - (Optional) Enable this rule. Defaults to `true`. ports or ranges.
- `enabled` - (Optional) Enable this rule. Defaults to `true`.
- `iface` - (Optional) Network interface name. You have to use network - `iface` - (Optional) Network interface name. You have to use network
configuration key names for VMs and containers ('net\d+'). Host related configuration key names for VMs and containers ('net\d+'). Host
rules can use arbitrary strings. related rules can use arbitrary strings.
- `log` - (Optional) Log level for this rule (`emerg`, `alert`, `crit`, - `log` - (Optional) Log level for this rule (`emerg`, `alert`, `crit`,
`err`, `warning`, `notice`, `info`, `debug`, `nolog`). `err`, `warning`, `notice`, `info`, `debug`, `nolog`).
- `macro`- (Optional) Macro name. Use predefined standard macro. - `macro`- (Optional) Macro name. Use predefined standard macro
- `proto` - (Optional) Restrict packet protocol. You can use protocol names from https://pve.proxmox.com/pve-docs/pve-admin-guide.html#_firewall_macro_definitions
or simple numbers (0-255), as defined in '/etc/protocols'. - `proto` - (Optional) Restrict packet protocol. You can use protocol
names as defined in '/etc/protocols'.
- `source` - (Optional) Restrict packet source address. This can refer - `source` - (Optional) Restrict packet source address. This can refer
to a single IP address, an IP set ('+ipsetname') or an IP alias to a single IP address, an IP set ('+ipsetname') or an IP alias
definition. You can also specify an address range like definition. You can also specify an address range
`20.34.101.207-201.3.9.99`, or a list of IP addresses and networks ( like `20.34.101.207-201.3.9.99`, or a list of IP addresses and
entries networks (entries are separated by comma). Please do not mix IPv4 and
are separated by comma). Please do not mix IPv4 and IPv6 addresses inside IPv6 addresses inside such lists.
such lists.
- `sport` - (Optional) Restrict TCP/UDP source port. You can use - `sport` - (Optional) Restrict TCP/UDP source port. You can use
service names or simple numbers (0-65535), as defined in '/etc/services'. service names or simple numbers (0-65535), as defined
Port ranges can be specified with '\d+:\d+', for example `80:85`, and in `/etc/services`. Port ranges can be specified with '\d+:\d+', for
you can use comma separated list to match several ports or ranges. example `80:85`, and you can use comma separated list to match several
ports or ranges.
- a security group insertion block, which includes the following arguments:
- `comment` - (Optional) Rule comment.
- `enabled` - (Optional) Enable this rule. Defaults to `true`.
- `iface` - (Optional) Network interface name. You have to use network
configuration key names for VMs and containers ('net\d+'). Host
related rules can use arbitrary strings.
- `security_group` - (Required) Security group name.
## Attribute Reference ## Attribute Reference

View File

@ -15,7 +15,7 @@ resource "proxmox_virtual_environment_cluster_firewall_security_group" "example"
rule { rule {
type = "in" type = "in"
action = "DROP" action = "DROP"
comment = "Allow SSH" comment = "Drop SSH"
dest = "192.168.1.5" dest = "192.168.1.5"
dport = "22" dport = "22"
proto = "udp" proto = "udp"

View File

@ -12,7 +12,7 @@ resource "proxmox_virtual_environment_firewall_rules" "cluster_rules" {
rule { rule {
type = "out" type = "out"
action = "DROP" action = "DROP"
comment = "Allow SSH" comment = "Drop SSH"
dest = "192.168.0.5" dest = "192.168.0.5"
dport = "22" dport = "22"
proto = "tcp" proto = "tcp"
@ -20,16 +20,26 @@ resource "proxmox_virtual_environment_firewall_rules" "cluster_rules" {
} }
resource "proxmox_virtual_environment_firewall_rules" "vm_rules" { resource "proxmox_virtual_environment_firewall_rules" "vm_rules" {
depends_on = [proxmox_virtual_environment_vm.example] depends_on = [
proxmox_virtual_environment_vm.example,
proxmox_virtual_environment_cluster_firewall_security_group.example,
]
node_name = proxmox_virtual_environment_vm.example.node_name node_name = proxmox_virtual_environment_vm.example.node_name
vm_id = proxmox_virtual_environment_vm.example.vm_id vm_id = proxmox_virtual_environment_vm.example.vm_id
rule {
security_group = proxmox_virtual_environment_cluster_firewall_security_group.example.name
enabled = true
comment = "From XXX"
iface = "net0"
}
rule { rule {
type = "in" type = "in"
action = "ACCEPT" action = "ACCEPT"
comment = "Allow FTP" comment = "Allow FTP"
dest = "192.168.1.5" dest = "192.168.1.15"
dport = "21" dport = "21"
proto = "tcp" proto = "tcp"
log = "info" log = "info"
@ -38,8 +48,8 @@ resource "proxmox_virtual_environment_firewall_rules" "vm_rules" {
rule { rule {
type = "out" type = "out"
action = "DROP" action = "DROP"
comment = "Allow SSH" comment = "Drop SSH"
dest = "192.168.1.5" dest = "192.168.1.15"
dport = "22" dport = "22"
proto = "tcp" proto = "tcp"
} }
@ -64,7 +74,7 @@ resource "proxmox_virtual_environment_firewall_rules" "container_rules" {
rule { rule {
type = "out" type = "out"
action = "DROP" action = "DROP"
comment = "Allow SSH" comment = "Drop SSH"
dest = "192.168.2.5" dest = "192.168.2.5"
dport = "22" dport = "22"
proto = "tcp" proto = "tcp"

View File

@ -22,12 +22,13 @@ import (
) )
const ( const (
dvSecurityGroup = ""
dvRuleComment = "" dvRuleComment = ""
dvRuleDPort = "" dvRuleDPort = ""
dvRuleDest = "" dvRuleDest = ""
dvRuleEnable = true dvRuleEnabled = true
dvRuleIface = "" dvRuleIface = ""
dvRuleLog = "nolog" dvRuleLog = ""
dvRuleMacro = "" dvRuleMacro = ""
dvRuleProto = "" dvRuleProto = ""
dvRuleSPort = "" dvRuleSPort = ""
@ -35,11 +36,13 @@ const (
MkRule = "rule" MkRule = "rule"
mkSecurityGroup = "security_group"
mkRuleAction = "action" mkRuleAction = "action"
mkRuleComment = "comment" mkRuleComment = "comment"
mkRuleDPort = "dport" mkRuleDPort = "dport"
mkRuleDest = "dest" mkRuleDest = "dest"
mkRuleEnable = "enable" mkRuleEnabled = "enabled"
mkRuleIFace = "iface" mkRuleIFace = "iface"
mkRuleLog = "log" mkRuleLog = "log"
mkRuleMacro = "macro" mkRuleMacro = "macro"
@ -57,16 +60,23 @@ func Rules() *schema.Resource {
Description: "Rules position", Description: "Rules position",
Computed: true, Computed: true,
}, },
mkSecurityGroup: {
Type: schema.TypeString,
Description: "Security group name",
Optional: true,
ForceNew: true,
Default: dvSecurityGroup,
},
mkRuleAction: { mkRuleAction: {
Type: schema.TypeString, Type: schema.TypeString,
Description: "Rules action ('ACCEPT', 'DROP', 'REJECT')", Description: "Rules action ('ACCEPT', 'DROP', 'REJECT')",
Required: true, Optional: true,
ValidateDiagFunc: validator.FirewallPolicy(), ValidateDiagFunc: validator.FirewallPolicy(),
}, },
mkRuleType: { mkRuleType: {
Type: schema.TypeString, Type: schema.TypeString,
Description: "Rules type ('in', 'out')", Description: "Rules type ('in', 'out')",
Required: true, Optional: true,
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"in", "out"}, true)), ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"in", "out"}, true)),
}, },
mkRuleComment: { mkRuleComment: {
@ -92,11 +102,11 @@ func Rules() *schema.Resource {
Optional: true, Optional: true,
Default: dvRuleDPort, Default: dvRuleDPort,
}, },
mkRuleEnable: { mkRuleEnabled: {
Type: schema.TypeBool, Type: schema.TypeBool,
Description: "Enable rule", Description: "Enable rule",
Optional: true, Optional: true,
Default: dvRuleEnable, Default: dvRuleEnabled,
}, },
mkRuleIFace: { mkRuleIFace: {
Type: schema.TypeString, Type: schema.TypeString,
@ -104,6 +114,7 @@ func Rules() *schema.Resource {
" and containers ('net\\d+'). Host related rules can use arbitrary strings.", " and containers ('net\\d+'). Host related rules can use arbitrary strings.",
Optional: true, Optional: true,
Default: dvRuleIface, Default: dvRuleIface,
ValidateDiagFunc: validator.FirewallIFace(),
}, },
mkRuleLog: { mkRuleLog: {
Type: schema.TypeString, Type: schema.TypeString,
@ -171,11 +182,29 @@ func RulesCreate(ctx context.Context, api firewall.Rule, d *schema.ResourceData)
rules := d.Get(MkRule).([]interface{}) rules := d.Get(MkRule).([]interface{})
for i := len(rules) - 1; i >= 0; i-- { for i := len(rules) - 1; i >= 0; i-- {
rule := rules[i].(map[string]interface{}) rule := rules[i].(map[string]interface{})
var ruleBody firewall.RuleCreateRequestBody
ruleBody := firewall.RuleCreateRequestBody{ sg := rule[mkSecurityGroup].(string)
if sg != "" {
// this is a special case of security group insertion
ruleBody = firewall.RuleCreateRequestBody{
Action: sg,
Type: "group",
BaseRule: *mapToSecurityGroupBaseRule(rule),
}
} else {
a := rule[mkRuleAction].(string)
t := rule[mkRuleType].(string)
if a == "" || t == "" {
diags = append(diags, diag.Errorf("Either '%s' OR both '%s' and '%s' must be defined for the rule #%d",
mkSecurityGroup, mkRuleAction, mkRuleType, i)...)
continue
}
ruleBody = firewall.RuleCreateRequestBody{
Action: a,
Type: t,
BaseRule: *mapToBaseRule(rule), BaseRule: *mapToBaseRule(rule),
Action: rule[mkRuleAction].(string), }
Type: rule[mkRuleType].(string),
} }
err := api.CreateRule(ctx, &ruleBody) err := api.CreateRule(ctx, &ruleBody)
@ -206,12 +235,18 @@ func RulesRead(ctx context.Context, api firewall.Rule, d *schema.ResourceData) d
return fmt.Errorf("error reading rule %d : %w", pos, err) return fmt.Errorf("error reading rule %d : %w", pos, err)
} }
baseRuleToMap(&rule.BaseRule, ruleMap)
// pos in the map should be int! // pos in the map should be int!
ruleMap[mkRulePos] = pos ruleMap[mkRulePos] = pos
if rule.Type == "group" {
// this is a special case of security group insertion
ruleMap[mkSecurityGroup] = rule.Action
securityGroupBaseRuleToMap(&rule.BaseRule, ruleMap)
} else {
ruleMap[mkRuleAction] = rule.Action ruleMap[mkRuleAction] = rule.Action
ruleMap[mkRuleType] = rule.Type ruleMap[mkRuleType] = rule.Type
baseRuleToMap(&rule.BaseRule, ruleMap)
}
return nil return nil
} }
@ -324,11 +359,9 @@ func mapToBaseRule(rule map[string]interface{}) *firewall.BaseRule {
if dport != "" { if dport != "" {
baseRule.DPort = &dport baseRule.DPort = &dport
} }
enable := rule[mkRuleEnable].(bool) enableBool := types.CustomBool(rule[mkRuleEnabled].(bool))
if enable {
enableBool := types.CustomBool(true)
baseRule.Enable = &enableBool baseRule.Enable = &enableBool
}
iface := rule[mkRuleIFace].(string) iface := rule[mkRuleIFace].(string)
if iface != "" { if iface != "" {
baseRule.IFace = &iface baseRule.IFace = &iface
@ -356,6 +389,24 @@ func mapToBaseRule(rule map[string]interface{}) *firewall.BaseRule {
return baseRule return baseRule
} }
func mapToSecurityGroupBaseRule(rule map[string]interface{}) *firewall.BaseRule {
baseRule := &firewall.BaseRule{}
comment := rule[mkRuleComment].(string)
if comment != "" {
baseRule.Comment = &comment
}
enableBool := types.CustomBool(rule[mkRuleEnabled].(bool))
baseRule.Enable = &enableBool
iface := rule[mkRuleIFace].(string)
if iface != "" {
baseRule.IFace = &iface
}
return baseRule
}
func baseRuleToMap(baseRule *firewall.BaseRule, rule map[string]interface{}) { func baseRuleToMap(baseRule *firewall.BaseRule, rule map[string]interface{}) {
if baseRule.Comment != nil { if baseRule.Comment != nil {
@ -368,7 +419,7 @@ func baseRuleToMap(baseRule *firewall.BaseRule, rule map[string]interface{}) {
rule[mkRuleDPort] = *baseRule.DPort rule[mkRuleDPort] = *baseRule.DPort
} }
if baseRule.Enable != nil { if baseRule.Enable != nil {
rule[mkRuleEnable] = *baseRule.Enable rule[mkRuleEnabled] = *baseRule.Enable
} }
if baseRule.IFace != nil { if baseRule.IFace != nil {
rule[mkRuleIFace] = *baseRule.IFace rule[mkRuleIFace] = *baseRule.IFace
@ -389,6 +440,17 @@ func baseRuleToMap(baseRule *firewall.BaseRule, rule map[string]interface{}) {
rule[mkRuleSPort] = *baseRule.SPort rule[mkRuleSPort] = *baseRule.SPort
} }
} }
func securityGroupBaseRuleToMap(baseRule *firewall.BaseRule, rule map[string]interface{}) {
if baseRule.Comment != nil {
rule[mkRuleComment] = *baseRule.Comment
}
if baseRule.Enable != nil {
rule[mkRuleEnabled] = *baseRule.Enable
}
if baseRule.IFace != nil {
rule[mkRuleIFace] = *baseRule.IFace
}
}
func invokeRuleAPI( func invokeRuleAPI(
f func(context.Context, firewall.Rule, *schema.ResourceData) diag.Diagnostics, f func(context.Context, firewall.Rule, *schema.ResourceData) diag.Diagnostics,

View File

@ -37,16 +37,14 @@ func TestRuleSchema(t *testing.T) {
ruleSchema := structure.AssertNestedSchemaExistence(t, rulesSchema, MkRule).Schema ruleSchema := structure.AssertNestedSchemaExistence(t, rulesSchema, MkRule).Schema
structure.AssertRequiredArguments(t, ruleSchema, []string{ structure.AssertOptionalArguments(t, ruleSchema, []string{
mkSecurityGroup,
mkRuleAction, mkRuleAction,
mkRuleType, mkRuleType,
})
structure.AssertOptionalArguments(t, ruleSchema, []string{
mkRuleComment, mkRuleComment,
mkRuleDest, mkRuleDest,
mkRuleDPort, mkRuleDPort,
mkRuleEnable, mkRuleEnabled,
mkRuleIFace, mkRuleIFace,
mkRuleLog, mkRuleLog,
mkRuleMacro, mkRuleMacro,
@ -62,7 +60,7 @@ func TestRuleSchema(t *testing.T) {
mkRuleComment: schema.TypeString, mkRuleComment: schema.TypeString,
mkRuleDest: schema.TypeString, mkRuleDest: schema.TypeString,
mkRuleDPort: schema.TypeString, mkRuleDPort: schema.TypeString,
mkRuleEnable: schema.TypeBool, mkRuleEnabled: schema.TypeBool,
mkRuleIFace: schema.TypeString, mkRuleIFace: schema.TypeString,
mkRuleLog: schema.TypeString, mkRuleLog: schema.TypeString,
mkRuleMacro: schema.TypeString, mkRuleMacro: schema.TypeString,

View File

@ -13,7 +13,10 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
) )
var rateExpression = regexp.MustCompile(`[1-9][0-9]*/(second|minute|hour|day)`) var (
rateExpression = regexp.MustCompile(`[1-9][0-9]*/(second|minute|hour|day)`)
ifaceExpression = regexp.MustCompile(`net\d+`)
)
func FirewallRate() schema.SchemaValidateDiagFunc { func FirewallRate() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringMatch( return validation.ToDiagFunc(validation.StringMatch(
@ -21,6 +24,12 @@ func FirewallRate() schema.SchemaValidateDiagFunc {
"Must be a valid rate expression, e.g. '1/second'", "Must be a valid rate expression, e.g. '1/second'",
)) ))
} }
func FirewallIFace() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringMatch(
ifaceExpression,
"Must be a valid VM/Container iface key, e.g. 'net0'",
))
}
func FirewallPolicy() schema.SchemaValidateDiagFunc { func FirewallPolicy() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice( return validation.ToDiagFunc(validation.StringInSlice(