diff --git a/api/v2/types_firewall.go b/api/v2/types_firewall.go index 86cd8f1..151ab0e 100644 --- a/api/v2/types_firewall.go +++ b/api/v2/types_firewall.go @@ -110,6 +110,19 @@ type FirewallSpec struct { DNSServerAddress string `json:"dnsServerAddress,omitempty"` // DNSPort specifies port to which DNS proxy should be bound DNSPort *uint `json:"dnsPort,omitempty"` + + // AllowedNetworks defines dedicated networks for which the firewall allows in- and outgoing traffic. + // The firewall-controller only enforces this setting in combination with NetworkAccessType set to forbidden. + // The node network is always allowed. + AllowedNetworks AllowedNetworks `json:"allowedNetworks,omitempty"` +} + +// AllowedNetworks is a list of networks which are allowed to connect when NetworkAccessType is forbidden. +type AllowedNetworks struct { + // Ingress defines a list of cidrs which are allowed for incoming traffic like service type loadbalancer. + Ingress []string `json:"ingress,omitempty"` + // Egress defines a list of cidrs which are allowed for outgoing traffic. + Egress []string `json:"egress,omitempty"` } // FirewallTemplateSpec describes the data a firewall should have when created from a template diff --git a/api/v2/validation/firewall.go b/api/v2/validation/firewall.go index eb43789..4ac05fc 100644 --- a/api/v2/validation/firewall.go +++ b/api/v2/validation/firewall.go @@ -95,6 +95,20 @@ func (*firewallValidator) validateSpec(f *v2.FirewallSpec, fldPath *field.Path) allErrs = append(allErrs, r.check()...) } + for _, cidr := range f.AllowedNetworks.Egress { + _, err := netip.ParsePrefix(cidr) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("allowedNetworks").Child("egress"), cidr, fmt.Sprintf("given network must be a cidr: %v", err))) + } + } + + for _, cidr := range f.AllowedNetworks.Ingress { + _, err := netip.ParsePrefix(cidr) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("allowedNetworks").Child("ingress"), cidr, fmt.Sprintf("given network must be a cidr: %v", err))) + } + } + return allErrs } diff --git a/api/v2/validation/firewall_test.go b/api/v2/validation/firewall_test.go index 22ce425..ea153f0 100644 --- a/api/v2/validation/firewall_test.go +++ b/api/v2/validation/firewall_test.go @@ -86,6 +86,34 @@ func Test_firewallValidator_ValidateCreate(t *testing.T) { }, }, }, + { + name: "invalid allowed network egress cidr", + mutateFn: func(f *v2.Firewall) *v2.Firewall { + f.Spec.AllowedNetworks = v2.AllowedNetworks{ + Egress: []string{"1.2.3.4", "1.2.3.5/32"}, + } + return f + }, + wantErr: &apierrors.StatusError{ + ErrStatus: metav1.Status{ + Message: ` "firewall-123" is invalid: spec.allowedNetworks.egress: Invalid value: "1.2.3.4": given network must be a cidr: netip.ParsePrefix("1.2.3.4"): no '/'`, + }, + }, + }, + { + name: "invalid allowed network ingress cidr", + mutateFn: func(f *v2.Firewall) *v2.Firewall { + f.Spec.AllowedNetworks = v2.AllowedNetworks{ + Ingress: []string{"foo"}, + } + return f + }, + wantErr: &apierrors.StatusError{ + ErrStatus: metav1.Status{ + Message: ` "firewall-123" is invalid: spec.allowedNetworks.ingress: Invalid value: "foo": given network must be a cidr: netip.ParsePrefix("foo"): no '/'`, + }, + }, + }, } for _, tt := range tests { tt := tt diff --git a/api/v2/zz_generated.deepcopy.go b/api/v2/zz_generated.deepcopy.go index ee5eba4..e16de6a 100644 --- a/api/v2/zz_generated.deepcopy.go +++ b/api/v2/zz_generated.deepcopy.go @@ -9,6 +9,31 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AllowedNetworks) DeepCopyInto(out *AllowedNetworks) { + *out = *in + if in.Ingress != nil { + in, out := &in.Ingress, &out.Ingress + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Egress != nil { + in, out := &in.Egress, &out.Egress + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedNetworks. +func (in *AllowedNetworks) DeepCopy() *AllowedNetworks { + if in == nil { + return nil + } + out := new(AllowedNetworks) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in @@ -598,6 +623,7 @@ func (in *FirewallSpec) DeepCopyInto(out *FirewallSpec) { *out = new(uint) **out = **in } + in.AllowedNetworks.DeepCopyInto(&out.AllowedNetworks) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallSpec. diff --git a/config/crds/firewall.metal-stack.io_firewalldeployments.yaml b/config/crds/firewall.metal-stack.io_firewalldeployments.yaml index 584cf0f..bdbf4f2 100644 --- a/config/crds/firewall.metal-stack.io_firewalldeployments.yaml +++ b/config/crds/firewall.metal-stack.io_firewalldeployments.yaml @@ -99,6 +99,26 @@ spec: spec: description: Spec contains the firewall specification. properties: + allowedNetworks: + description: AllowedNetworks defines dedicated networks for + which the firewall allows in- and outgoing traffic. The + firewall-controller only enforces this setting in combination + with NetworkAccessType set to forbidden. The node network + is always allowed. + properties: + egress: + description: Egress defines a list of cidrs which are + allowed for outgoing traffic. + items: + type: string + type: array + ingress: + description: Ingress defines a list of cidrs which are + allowed for incoming traffic like service type loadbalancer. + items: + type: string + type: array + type: object controllerURL: description: ControllerURL points to the downloadable binary artifact of the firewall controller. diff --git a/config/crds/firewall.metal-stack.io_firewalls.yaml b/config/crds/firewall.metal-stack.io_firewalls.yaml index 3c9cd32..bf2cb43 100644 --- a/config/crds/firewall.metal-stack.io_firewalls.yaml +++ b/config/crds/firewall.metal-stack.io_firewalls.yaml @@ -66,6 +66,25 @@ spec: spec: description: Spec contains the firewall specification. properties: + allowedNetworks: + description: AllowedNetworks defines dedicated networks for which + the firewall allows in- and outgoing traffic. The firewall-controller + only enforces this setting in combination with NetworkAccessType + set to forbidden. The node network is always allowed. + properties: + egress: + description: Egress defines a list of cidrs which are allowed + for outgoing traffic. + items: + type: string + type: array + ingress: + description: Ingress defines a list of cidrs which are allowed + for incoming traffic like service type loadbalancer. + items: + type: string + type: array + type: object controllerURL: description: ControllerURL points to the downloadable binary artifact of the firewall controller. diff --git a/config/crds/firewall.metal-stack.io_firewallsets.yaml b/config/crds/firewall.metal-stack.io_firewallsets.yaml index bb817d4..e752c9a 100644 --- a/config/crds/firewall.metal-stack.io_firewallsets.yaml +++ b/config/crds/firewall.metal-stack.io_firewallsets.yaml @@ -102,6 +102,26 @@ spec: spec: description: Spec contains the firewall specification. properties: + allowedNetworks: + description: AllowedNetworks defines dedicated networks for + which the firewall allows in- and outgoing traffic. The + firewall-controller only enforces this setting in combination + with NetworkAccessType set to forbidden. The node network + is always allowed. + properties: + egress: + description: Egress defines a list of cidrs which are + allowed for outgoing traffic. + items: + type: string + type: array + ingress: + description: Ingress defines a list of cidrs which are + allowed for incoming traffic like service type loadbalancer. + items: + type: string + type: array + type: object controllerURL: description: ControllerURL points to the downloadable binary artifact of the firewall controller.