diff --git a/api/v1alpha1/securitygroup_types.go b/api/v1alpha1/securitygroup_types.go index 70fb94ee..8a06825a 100644 --- a/api/v1alpha1/securitygroup_types.go +++ b/api/v1alpha1/securitygroup_types.go @@ -17,22 +17,68 @@ limitations under the License. package v1alpha1 // +kubebuilder:validation:Enum:=ingress;egress -// +kubebuilder:validation:MinLength:=1 -// +kubebuilder:validation:MaxLength:=16 type RuleDirection string -// +kubebuilder:validation:Pattern:=\b([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\b|any|ah|dccp|egp|esp|gre|icmp|icmpv6|igmp|ipip|ipv6-encap|ipv6-frag|ipv6-icmp|ipv6-nonxt|ipv6-opts|ipv6-route|ospf|pgm|rsvp|sctp|tcp|udp|udplite|vrrp -// +kubebuilder:validation:MinLength:=1 -// +kubebuilder:validation:MaxLength:=16 +// +kubebuilder:validation:Enum:=ah;dccp;egp;esp;gre;icmp;icmpv6;igmp;ipip;ipv6-encap;ipv6-frag;ipv6-icmp;ipv6-nonxt;ipv6-opts;ipv6-route;ospf;pgm;rsvp;sctp;tcp;udp;udplite;vrrp type Protocol string +const ( + ProtocolAH Protocol = "ah" + ProtocolDCCP Protocol = "dccp" + ProtocolEGP Protocol = "egp" + ProtocolESP Protocol = "esp" + ProtocolGRE Protocol = "gre" + ProtocolICMP Protocol = "icmp" + ProtocolICMPV6 Protocol = "icmpv6" + ProtocolIGMP Protocol = "igmp" + ProtocolIPIP Protocol = "ipip" + ProtocolIPV6ENCAP Protocol = "ipv6-encap" + ProtocolIPV6FRAG Protocol = "ipv6-frag" + ProtocolIPV6ICMP Protocol = "ipv6-icmp" + ProtocolIPV6NONXT Protocol = "ipv6-nonxt" + ProtocolIPV6OPTS Protocol = "ipv6-opts" + ProtocolIPV6ROUTE Protocol = "ipv6-route" + ProtocolOSPF Protocol = "ospf" + ProtocolPGM Protocol = "pgm" + ProtocolRSVP Protocol = "rsvp" + ProtocolSCTP Protocol = "sctp" + ProtocolTCP Protocol = "tcp" + ProtocolUDP Protocol = "udp" + ProtocolUDPLITE Protocol = "udplite" + ProtocolVRRP Protocol = "vrrp" +) + // +kubebuilder:validation:Enum:=IPv4;IPv6 -// +kubebuilder:validation:MinLength:=1 -// +kubebuilder:validation:MaxLength:=16 +// +required type Ethertype string +const ( + EthertypeIPv4 Ethertype = "IPv4" + EthertypeIPv6 Ethertype = "IPv6" +) + +// +kubebuilder:validation:Minimum:=0 +// +kubebuilder:validation:Maximum:=65535 +type PortNumber int32 + +type PortRangeSpec struct { + // +required + Min PortNumber `json:"min"` + // +required + Max PortNumber `json:"max"` +} + +type PortRangeStatus struct { + Min int32 `json:"min"` + Max int32 `json:"max"` +} + // SecurityGroupRule defines a Security Group rule // +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:XValidation:rule="(!has(self.portRange)|| !(self.protocol == 'tcp'|| self.protocol == 'udp' || self.protocol == 'dccp' || self.protocol == 'sctp' || self.protocol == 'udplite') || (self.portRange.min <= self.portRange.max))",message="portRangeMax should be equal or greater than portRange.min" +// +kubebuilder:validation:XValidation:rule="!(self.protocol == 'icmp' || self.protocol == 'icmpv6') || !has(self.portRange)|| (self.portRange.min >= 0 && self.portRange.min <= 255)",message="When protocol is ICMP or ICMPv6 portRange.min should be between 0 and 255" +// +kubebuilder:validation:XValidation:rule="!(self.protocol == 'icmp' || self.protocol == 'icmpv6') || !has(self.portRange)|| (self.portRange.max >= 0 && self.portRange.max <= 255)",message="When protocol is ICMP or ICMPv6 portRange.max should be between 0 and 255" +// +kubebuilder:validation:XValidation:rule="!has(self.remoteIPPrefix) || (isCIDR(self.remoteIPPrefix) && cidr(self.remoteIPPrefix).ip().family() == 4 && self.ethertype == 'IPv4') || (isCIDR(self.remoteIPPrefix) && cidr(self.remoteIPPrefix).ip().family() == 6 && self.ethertype == 'IPv6')",message="remoteIPPrefix should be a valid CIDR and match the ethertype" type SecurityGroupRule struct { // Description of the existing resource // +optional @@ -40,27 +86,33 @@ type SecurityGroupRule struct { // Direction represents the direction in which the security group rule // is applied. Can be ingress or egress. + // +optional Direction *RuleDirection `json:"direction,omitempty"` - // RemoteAddressGroupId (Not in gophercloud) - - // RemoteIPPrefix + // RemoteIPPrefix is an IP address block. Should match the Ethertype (IPv4 or IPv6) + // +optional RemoteIPPrefix *CIDR `json:"remoteIPPrefix,omitempty"` - // Protocol is the IP protocol can be represented by a string, an - // integer, or null + // Protocol is the IP protocol can be represented by a string or an + // integer represented as a string. + // +optional Protocol *Protocol `json:"protocol,omitempty"` - // EtherType must be IPv4 or IPv6, and addresses represented in CIDR + // Ethertype must be IPv4 or IPv6, and addresses represented in CIDR // must match the ingress or egress rules. - Ethertype *Ethertype `json:"ethertype,omitempty"` - - PortRangeMin *int32 `json:"portRangeMin,omitempty"` - PortRangeMax *int32 `json:"portRangeMax,omitempty"` + // +kubebuilder:validation:Required + Ethertype Ethertype `json:"ethertype"` + // If the protocol is [tcp, udp, dccp sctp,udplite] PortRange.Min must be less than + // or equal to the PortRange.Max attribute value. + // If the protocol is ICMP, this PortRamge.Min must be an ICMP code and PortRange.Max + // should be an ICMP type + // +optional + PortRange *PortRangeSpec `json:"portRange,omitempty"` } type SecurityGroupRuleStatus struct { // ID is the ID of the security group rule. + // +required ID string `json:"id,omitempty"` // Description of the existing resource @@ -87,9 +139,7 @@ type SecurityGroupRuleStatus struct { // must match the ingress or egress rules. Ethertype string `json:"ethertype,omitempty"` - PortRangeMin int `json:"portRangeMin,omitempty"` - PortRangeMax int `json:"portRangeMax,omitempty"` - + PortRange *PortRangeStatus `json:"portRange,omitempty"` // FIXME(mandre) This field is not yet returned by gophercloud // BelongsToDefaultSG bool `json:"belongsToDefaultSG,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index f453583e..e0598461 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1392,6 +1392,36 @@ func (in *PortList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PortRangeSpec) DeepCopyInto(out *PortRangeSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortRangeSpec. +func (in *PortRangeSpec) DeepCopy() *PortRangeSpec { + if in == nil { + return nil + } + out := new(PortRangeSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PortRangeStatus) DeepCopyInto(out *PortRangeStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortRangeStatus. +func (in *PortRangeStatus) DeepCopy() *PortRangeStatus { + if in == nil { + return nil + } + out := new(PortRangeStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PortRefs) DeepCopyInto(out *PortRefs) { *out = *in @@ -2104,7 +2134,9 @@ func (in *SecurityGroupResourceStatus) DeepCopyInto(out *SecurityGroupResourceSt if in.Rules != nil { in, out := &in.Rules, &out.Rules *out = make([]SecurityGroupRuleStatus, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } in.NeutronStatusMetadata.DeepCopyInto(&out.NeutronStatusMetadata) } @@ -2142,19 +2174,9 @@ func (in *SecurityGroupRule) DeepCopyInto(out *SecurityGroupRule) { *out = new(Protocol) **out = **in } - if in.Ethertype != nil { - in, out := &in.Ethertype, &out.Ethertype - *out = new(Ethertype) - **out = **in - } - if in.PortRangeMin != nil { - in, out := &in.PortRangeMin, &out.PortRangeMin - *out = new(int32) - **out = **in - } - if in.PortRangeMax != nil { - in, out := &in.PortRangeMax, &out.PortRangeMax - *out = new(int32) + if in.PortRange != nil { + in, out := &in.PortRange, &out.PortRange + *out = new(PortRangeSpec) **out = **in } } @@ -2172,6 +2194,11 @@ func (in *SecurityGroupRule) DeepCopy() *SecurityGroupRule { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SecurityGroupRuleStatus) DeepCopyInto(out *SecurityGroupRuleStatus) { *out = *in + if in.PortRange != nil { + in, out := &in.PortRange, &out.PortRange + *out = new(PortRangeStatus) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityGroupRuleStatus. diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 34d3824d..f0ff4feb 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -79,6 +79,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/api/v1alpha1.PortFilter": schema_k_orc_openstack_resource_controller_api_v1alpha1_PortFilter(ref), "github.com/k-orc/openstack-resource-controller/api/v1alpha1.PortImport": schema_k_orc_openstack_resource_controller_api_v1alpha1_PortImport(ref), "github.com/k-orc/openstack-resource-controller/api/v1alpha1.PortList": schema_k_orc_openstack_resource_controller_api_v1alpha1_PortList(ref), + "github.com/k-orc/openstack-resource-controller/api/v1alpha1.PortRangeSpec": schema_k_orc_openstack_resource_controller_api_v1alpha1_PortRangeSpec(ref), + "github.com/k-orc/openstack-resource-controller/api/v1alpha1.PortRangeStatus": schema_k_orc_openstack_resource_controller_api_v1alpha1_PortRangeStatus(ref), "github.com/k-orc/openstack-resource-controller/api/v1alpha1.PortRefs": schema_k_orc_openstack_resource_controller_api_v1alpha1_PortRefs(ref), "github.com/k-orc/openstack-resource-controller/api/v1alpha1.PortResourceSpec": schema_k_orc_openstack_resource_controller_api_v1alpha1_PortResourceSpec(ref), "github.com/k-orc/openstack-resource-controller/api/v1alpha1.PortResourceStatus": schema_k_orc_openstack_resource_controller_api_v1alpha1_PortResourceStatus(ref), @@ -2702,6 +2704,60 @@ func schema_k_orc_openstack_resource_controller_api_v1alpha1_PortList(ref common } } +func schema_k_orc_openstack_resource_controller_api_v1alpha1_PortRangeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "min": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "max": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"min", "max"}, + }, + }, + } +} + +func schema_k_orc_openstack_resource_controller_api_v1alpha1_PortRangeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "min": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "max": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"min", "max"}, + }, + }, + } +} + func schema_k_orc_openstack_resource_controller_api_v1alpha1_PortRefs(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -4314,40 +4370,38 @@ func schema_k_orc_openstack_resource_controller_api_v1alpha1_SecurityGroupRule(r }, "remoteIPPrefix": { SchemaProps: spec.SchemaProps{ - Description: "RemoteIPPrefix", + Description: "RemoteIPPrefix is an IP address block. Should match the Ethertype (IPv4 or IPv6)", Type: []string{"string"}, Format: "", }, }, "protocol": { SchemaProps: spec.SchemaProps{ - Description: "Protocol is the IP protocol can be represented by a string, an integer, or null", + Description: "Protocol is the IP protocol can be represented by a string or an integer represented as a string.", Type: []string{"string"}, Format: "", }, }, "ethertype": { SchemaProps: spec.SchemaProps{ - Description: "EtherType must be IPv4 or IPv6, and addresses represented in CIDR must match the ingress or egress rules.", + Description: "Ethertype must be IPv4 or IPv6, and addresses represented in CIDR must match the ingress or egress rules.", + Default: "", Type: []string{"string"}, Format: "", }, }, - "portRangeMin": { + "portRange": { SchemaProps: spec.SchemaProps{ - Type: []string{"integer"}, - Format: "int32", - }, - }, - "portRangeMax": { - SchemaProps: spec.SchemaProps{ - Type: []string{"integer"}, - Format: "int32", + Description: "If the protocol is [tcp, udp, dccp sctp,udplite] PortRange.Min must be less than or equal to the PortRange.Max attribute value. If the protocol is ICMP, this PortRamge.Min must be an ICMP code and PortRange.Max should be an ICMP type", + Ref: ref("github.com/k-orc/openstack-resource-controller/api/v1alpha1.PortRangeSpec"), }, }, }, + Required: []string{"ethertype"}, }, }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/api/v1alpha1.PortRangeSpec"}, } } @@ -4406,21 +4460,17 @@ func schema_k_orc_openstack_resource_controller_api_v1alpha1_SecurityGroupRuleSt Format: "", }, }, - "portRangeMin": { + "portRange": { SchemaProps: spec.SchemaProps{ - Type: []string{"integer"}, - Format: "int32", - }, - }, - "portRangeMax": { - SchemaProps: spec.SchemaProps{ - Type: []string{"integer"}, - Format: "int32", + Ref: ref("github.com/k-orc/openstack-resource-controller/api/v1alpha1.PortRangeStatus"), }, }, }, + Required: []string{"id"}, }, }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/api/v1alpha1.PortRangeStatus"}, } } diff --git a/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml b/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml index 48e57999..f0a958ee 100644 --- a/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml @@ -245,40 +245,97 @@ spec: enum: - ingress - egress - maxLength: 16 - minLength: 1 type: string ethertype: description: |- - EtherType must be IPv4 or IPv6, and addresses represented in CIDR + Ethertype must be IPv4 or IPv6, and addresses represented in CIDR must match the ingress or egress rules. enum: - IPv4 - IPv6 - maxLength: 16 - minLength: 1 type: string - portRangeMax: - format: int32 - type: integer - portRangeMin: - format: int32 - type: integer + portRange: + description: |- + If the protocol is [tcp, udp, dccp sctp,udplite] PortRange.Min must be less than + or equal to the PortRange.Max attribute value. + If the protocol is ICMP, this PortRamge.Min must be an ICMP code and PortRange.Max + should be an ICMP type + properties: + max: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + min: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + required: + - max + - min + type: object protocol: description: |- - Protocol is the IP protocol can be represented by a string, an - integer, or null - maxLength: 16 - minLength: 1 - pattern: \b([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\b|any|ah|dccp|egp|esp|gre|icmp|icmpv6|igmp|ipip|ipv6-encap|ipv6-frag|ipv6-icmp|ipv6-nonxt|ipv6-opts|ipv6-route|ospf|pgm|rsvp|sctp|tcp|udp|udplite|vrrp + Protocol is the IP protocol can be represented by a string or an + integer represented as a string. + enum: + - ah + - dccp + - egp + - esp + - gre + - icmp + - icmpv6 + - igmp + - ipip + - ipv6-encap + - ipv6-frag + - ipv6-icmp + - ipv6-nonxt + - ipv6-opts + - ipv6-route + - ospf + - pgm + - rsvp + - sctp + - tcp + - udp + - udplite + - vrrp type: string remoteIPPrefix: - description: RemoteIPPrefix + description: RemoteIPPrefix is an IP address block. Should + match the Ethertype (IPv4 or IPv6) format: cidr maxLength: 49 minLength: 1 type: string + required: + - ethertype type: object + x-kubernetes-validations: + - message: portRangeMax should be equal or greater than portRange.min + rule: (!has(self.portRange)|| !(self.protocol == 'tcp'|| self.protocol + == 'udp' || self.protocol == 'dccp' || self.protocol == + 'sctp' || self.protocol == 'udplite') || (self.portRange.min + <= self.portRange.max)) + - message: When protocol is ICMP or ICMPv6 portRange.min should + be between 0 and 255 + rule: '!(self.protocol == ''icmp'' || self.protocol == ''icmpv6'') + || !has(self.portRange)|| (self.portRange.min >= 0 && self.portRange.min + <= 255)' + - message: When protocol is ICMP or ICMPv6 portRange.max should + be between 0 and 255 + rule: '!(self.protocol == ''icmp'' || self.protocol == ''icmpv6'') + || !has(self.portRange)|| (self.portRange.max >= 0 && self.portRange.max + <= 255)' + - message: remoteIPPrefix should be a valid CIDR and match the + ethertype + rule: '!has(self.remoteIPPrefix) || (isCIDR(self.remoteIPPrefix) + && cidr(self.remoteIPPrefix).ip().family() == 4 && self.ethertype + == ''IPv4'') || (isCIDR(self.remoteIPPrefix) && cidr(self.remoteIPPrefix).ip().family() + == 6 && self.ethertype == ''IPv6'')' maxItems: 256 type: array x-kubernetes-list-type: atomic @@ -441,10 +498,18 @@ spec: id: description: ID is the ID of the security group rule. type: string - portRangeMax: - type: integer - portRangeMin: - type: integer + portRange: + properties: + max: + format: int32 + type: integer + min: + format: int32 + type: integer + required: + - max + - min + type: object protocol: description: |- Protocol is the IP protocol can be represented by a string, an @@ -456,6 +521,8 @@ spec: remoteIPPrefix: description: RemoteIPPrefix type: string + required: + - id type: object type: array x-kubernetes-list-type: atomic diff --git a/examples/bases/cirros/securitygroup.yaml b/examples/bases/cirros/securitygroup.yaml index 553bba07..da284d95 100644 --- a/examples/bases/cirros/securitygroup.yaml +++ b/examples/bases/cirros/securitygroup.yaml @@ -13,6 +13,7 @@ spec: rules: - direction: ingress protocol: tcp - portRangeMin: 22 - portRangeMax: 22 + portRange: + min: 22 + max: 22 ethertype: IPv4 diff --git a/examples/bases/devstack/securitygroup.yaml b/examples/bases/devstack/securitygroup.yaml index e6ca6ffc..59257d22 100644 --- a/examples/bases/devstack/securitygroup.yaml +++ b/examples/bases/devstack/securitygroup.yaml @@ -13,13 +13,15 @@ spec: rules: - direction: ingress protocol: tcp - portRangeMin: 22 - portRangeMax: 22 + portRange: + min: 22 + max: 22 ethertype: IPv4 - direction: ingress protocol: tcp - portRangeMin: 22 - portRangeMax: 22 + portRange: + min: 22 + max: 22 ethertype: IPv6 --- apiVersion: openstack.k-orc.cloud/v1alpha1 @@ -36,21 +38,25 @@ spec: rules: - direction: ingress protocol: tcp - portRangeMin: 80 - portRangeMax: 80 + portRange: + min: 80 + max: 80 ethertype: IPv4 - direction: ingress protocol: tcp - portRangeMin: 80 - portRangeMax: 80 + portRange: + min: 80 + max: 80 ethertype: IPv6 - direction: ingress protocol: tcp - portRangeMin: 9696 - portRangeMax: 9696 + portRange: + min: 9696 + max: 9696 ethertype: IPv4 - direction: ingress protocol: tcp - portRangeMin: 9696 - portRangeMax: 9696 + portRange: + min: 9696 + max: 9696 ethertype: IPv6 diff --git a/examples/bases/managed-network/securitygroup.yaml b/examples/bases/managed-network/securitygroup.yaml index fabc011d..a57beba7 100644 --- a/examples/bases/managed-network/securitygroup.yaml +++ b/examples/bases/managed-network/securitygroup.yaml @@ -16,11 +16,13 @@ spec: - direction: ingress protocol: tcp ethertype: IPv4 - portRangeMin: 21 - portRangeMax: 21 + portRange: + min: 21 + max: 21 remoteIPPrefix: "192.168.0.0/24" - direction: egress protocol: "6" ethertype: IPv6 - portRangeMin: 3838 - portRangeMax: 3840 + portRange: + min: 3838 + max: 3840 diff --git a/internal/controllers/securitygroup/actuator.go b/internal/controllers/securitygroup/actuator.go index 983ce8f8..117fc1ca 100644 --- a/internal/controllers/securitygroup/actuator.go +++ b/internal/controllers/securitygroup/actuator.go @@ -147,9 +147,11 @@ func (obj securityGroupActuator) CreateResource(ctx context.Context) ([]generic. Direction: rules.RuleDirection(ptr.Deref(resource.Rules[i].Direction, "")), RemoteIPPrefix: string(ptr.Deref(resource.Rules[i].RemoteIPPrefix, "")), Protocol: rules.RuleProtocol(ptr.Deref(resource.Rules[i].Protocol, "")), - EtherType: rules.RuleEtherType(ptr.Deref(resource.Rules[i].Ethertype, "")), - PortRangeMin: int(*resource.Rules[i].PortRangeMin), - PortRangeMax: int(*resource.Rules[i].PortRangeMax), + EtherType: rules.RuleEtherType(resource.Rules[i].Ethertype), + } + if resource.Rules[i].PortRange != nil { + ruleCreateOpts[i].PortRangeMin = int(resource.Rules[i].PortRange.Min) + ruleCreateOpts[i].PortRangeMax = int(resource.Rules[i].PortRange.Max) } } diff --git a/internal/controllers/securitygroup/status.go b/internal/controllers/securitygroup/status.go index f30cb538..4c018c21 100644 --- a/internal/controllers/securitygroup/status.go +++ b/internal/controllers/securitygroup/status.go @@ -24,6 +24,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/groups" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -71,6 +72,8 @@ func withProgressMessage(message string) updateStatusOpt { } func getOSResourceStatus(_ logr.Logger, osResource *groups.SecGroup) *orcapplyconfigv1alpha1.SecurityGroupResourceStatusApplyConfiguration { + var pRange *orcapplyconfigv1alpha1.PortRangeStatusApplyConfiguration + securitygroupResourceStatus := (&orcapplyconfigv1alpha1.SecurityGroupResourceStatusApplyConfiguration{}). WithName(osResource.Name). WithDescription(osResource.Description). @@ -83,6 +86,12 @@ func getOSResourceStatus(_ logr.Logger, osResource *groups.SecGroup) *orcapplyco if len(osResource.Rules) > 0 { rules := make([]*orcapplyconfigv1alpha1.SecurityGroupRuleStatusApplyConfiguration, len(osResource.Rules)) for i := range osResource.Rules { + if osResource.Rules[i].PortRangeMin != 0 || osResource.Rules[i].PortRangeMax != 0 { + pRange = &orcapplyconfigv1alpha1.PortRangeStatusApplyConfiguration{ + Min: ptr.To(int32(osResource.Rules[i].PortRangeMin)), + Max: ptr.To(int32(osResource.Rules[i].PortRangeMax)), + } + } rules[i] = orcapplyconfigv1alpha1.SecurityGroupRuleStatus(). WithID(osResource.Rules[i].ID). WithDescription(osResource.Rules[i].Description). @@ -91,8 +100,7 @@ func getOSResourceStatus(_ logr.Logger, osResource *groups.SecGroup) *orcapplyco WithRemoteIPPrefix(osResource.Rules[i].RemoteIPPrefix). WithProtocol(osResource.Rules[i].Protocol). WithEthertype(osResource.Rules[i].EtherType). - WithPortRangeMin(osResource.Rules[i].PortRangeMin). - WithPortRangeMax(osResource.Rules[i].PortRangeMax) + WithPortRange(pRange) } securitygroupResourceStatus.WithRules(rules...) } diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portrangespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portrangespec.go new file mode 100644 index 00000000..0bb1c83c --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portrangespec.go @@ -0,0 +1,52 @@ +/* +Copyright 2024 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/k-orc/openstack-resource-controller/api/v1alpha1" +) + +// PortRangeSpecApplyConfiguration represents a declarative configuration of the PortRangeSpec type for use +// with apply. +type PortRangeSpecApplyConfiguration struct { + Min *v1alpha1.PortNumber `json:"min,omitempty"` + Max *v1alpha1.PortNumber `json:"max,omitempty"` +} + +// PortRangeSpecApplyConfiguration constructs a declarative configuration of the PortRangeSpec type for use with +// apply. +func PortRangeSpec() *PortRangeSpecApplyConfiguration { + return &PortRangeSpecApplyConfiguration{} +} + +// WithMin sets the Min field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Min field is set to the value of the last call. +func (b *PortRangeSpecApplyConfiguration) WithMin(value v1alpha1.PortNumber) *PortRangeSpecApplyConfiguration { + b.Min = &value + return b +} + +// WithMax sets the Max field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Max field is set to the value of the last call. +func (b *PortRangeSpecApplyConfiguration) WithMax(value v1alpha1.PortNumber) *PortRangeSpecApplyConfiguration { + b.Max = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portrangestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/portrangestatus.go new file mode 100644 index 00000000..9a63a894 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portrangestatus.go @@ -0,0 +1,48 @@ +/* +Copyright 2024 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// PortRangeStatusApplyConfiguration represents a declarative configuration of the PortRangeStatus type for use +// with apply. +type PortRangeStatusApplyConfiguration struct { + Min *int32 `json:"min,omitempty"` + Max *int32 `json:"max,omitempty"` +} + +// PortRangeStatusApplyConfiguration constructs a declarative configuration of the PortRangeStatus type for use with +// apply. +func PortRangeStatus() *PortRangeStatusApplyConfiguration { + return &PortRangeStatusApplyConfiguration{} +} + +// WithMin sets the Min field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Min field is set to the value of the last call. +func (b *PortRangeStatusApplyConfiguration) WithMin(value int32) *PortRangeStatusApplyConfiguration { + b.Min = &value + return b +} + +// WithMax sets the Max field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Max field is set to the value of the last call. +func (b *PortRangeStatusApplyConfiguration) WithMax(value int32) *PortRangeStatusApplyConfiguration { + b.Max = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprule.go b/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprule.go index 0d57f9e4..52482bd5 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprule.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprule.go @@ -25,13 +25,12 @@ import ( // SecurityGroupRuleApplyConfiguration represents a declarative configuration of the SecurityGroupRule type for use // with apply. type SecurityGroupRuleApplyConfiguration struct { - Description *v1alpha1.OpenStackDescription `json:"description,omitempty"` - Direction *v1alpha1.RuleDirection `json:"direction,omitempty"` - RemoteIPPrefix *v1alpha1.CIDR `json:"remoteIPPrefix,omitempty"` - Protocol *v1alpha1.Protocol `json:"protocol,omitempty"` - Ethertype *v1alpha1.Ethertype `json:"ethertype,omitempty"` - PortRangeMin *int32 `json:"portRangeMin,omitempty"` - PortRangeMax *int32 `json:"portRangeMax,omitempty"` + Description *v1alpha1.OpenStackDescription `json:"description,omitempty"` + Direction *v1alpha1.RuleDirection `json:"direction,omitempty"` + RemoteIPPrefix *v1alpha1.CIDR `json:"remoteIPPrefix,omitempty"` + Protocol *v1alpha1.Protocol `json:"protocol,omitempty"` + Ethertype *v1alpha1.Ethertype `json:"ethertype,omitempty"` + PortRange *PortRangeSpecApplyConfiguration `json:"portRange,omitempty"` } // SecurityGroupRuleApplyConfiguration constructs a declarative configuration of the SecurityGroupRule type for use with @@ -80,18 +79,10 @@ func (b *SecurityGroupRuleApplyConfiguration) WithEthertype(value v1alpha1.Ether return b } -// WithPortRangeMin sets the PortRangeMin field in the declarative configuration to the given value +// WithPortRange sets the PortRange field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the PortRangeMin field is set to the value of the last call. -func (b *SecurityGroupRuleApplyConfiguration) WithPortRangeMin(value int32) *SecurityGroupRuleApplyConfiguration { - b.PortRangeMin = &value - return b -} - -// WithPortRangeMax sets the PortRangeMax field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the PortRangeMax field is set to the value of the last call. -func (b *SecurityGroupRuleApplyConfiguration) WithPortRangeMax(value int32) *SecurityGroupRuleApplyConfiguration { - b.PortRangeMax = &value +// If called multiple times, the PortRange field is set to the value of the last call. +func (b *SecurityGroupRuleApplyConfiguration) WithPortRange(value *PortRangeSpecApplyConfiguration) *SecurityGroupRuleApplyConfiguration { + b.PortRange = value return b } diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprulestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprulestatus.go index d0fe29e7..6eff1929 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprulestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprulestatus.go @@ -21,15 +21,14 @@ package v1alpha1 // SecurityGroupRuleStatusApplyConfiguration represents a declarative configuration of the SecurityGroupRuleStatus type for use // with apply. type SecurityGroupRuleStatusApplyConfiguration struct { - ID *string `json:"id,omitempty"` - Description *string `json:"description,omitempty"` - Direction *string `json:"direction,omitempty"` - RemoteGroupID *string `json:"remoteGroupID,omitempty"` - RemoteIPPrefix *string `json:"remoteIPPrefix,omitempty"` - Protocol *string `json:"protocol,omitempty"` - Ethertype *string `json:"ethertype,omitempty"` - PortRangeMin *int `json:"portRangeMin,omitempty"` - PortRangeMax *int `json:"portRangeMax,omitempty"` + ID *string `json:"id,omitempty"` + Description *string `json:"description,omitempty"` + Direction *string `json:"direction,omitempty"` + RemoteGroupID *string `json:"remoteGroupID,omitempty"` + RemoteIPPrefix *string `json:"remoteIPPrefix,omitempty"` + Protocol *string `json:"protocol,omitempty"` + Ethertype *string `json:"ethertype,omitempty"` + PortRange *PortRangeStatusApplyConfiguration `json:"portRange,omitempty"` } // SecurityGroupRuleStatusApplyConfiguration constructs a declarative configuration of the SecurityGroupRuleStatus type for use with @@ -94,18 +93,10 @@ func (b *SecurityGroupRuleStatusApplyConfiguration) WithEthertype(value string) return b } -// WithPortRangeMin sets the PortRangeMin field in the declarative configuration to the given value +// WithPortRange sets the PortRange field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the PortRangeMin field is set to the value of the last call. -func (b *SecurityGroupRuleStatusApplyConfiguration) WithPortRangeMin(value int) *SecurityGroupRuleStatusApplyConfiguration { - b.PortRangeMin = &value - return b -} - -// WithPortRangeMax sets the PortRangeMax field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the PortRangeMax field is set to the value of the last call. -func (b *SecurityGroupRuleStatusApplyConfiguration) WithPortRangeMax(value int) *SecurityGroupRuleStatusApplyConfiguration { - b.PortRangeMax = &value +// If called multiple times, the PortRange field is set to the value of the last call. +func (b *SecurityGroupRuleStatusApplyConfiguration) WithPortRange(value *PortRangeStatusApplyConfiguration) *SecurityGroupRuleStatusApplyConfiguration { + b.PortRange = value return b } diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 4fb56bad..22bf9e3a 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -762,6 +762,28 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string +- name: com.github.k-orc.openstack-resource-controller.api.v1alpha1.PortRangeSpec + map: + fields: + - name: max + type: + scalar: numeric + default: 0 + - name: min + type: + scalar: numeric + default: 0 +- name: com.github.k-orc.openstack-resource-controller.api.v1alpha1.PortRangeStatus + map: + fields: + - name: max + type: + scalar: numeric + default: 0 + - name: min + type: + scalar: numeric + default: 0 - name: com.github.k-orc.openstack-resource-controller.api.v1alpha1.PortResourceSpec map: fields: @@ -1281,12 +1303,10 @@ var schemaYAML = typed.YAMLObject(`types: - name: ethertype type: scalar: string - - name: portRangeMax - type: - scalar: numeric - - name: portRangeMin + default: "" + - name: portRange type: - scalar: numeric + namedType: com.github.k-orc.openstack-resource-controller.api.v1alpha1.PortRangeSpec - name: protocol type: scalar: string @@ -1308,12 +1328,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string - - name: portRangeMax + - name: portRange type: - scalar: numeric - - name: portRangeMin - type: - scalar: numeric + namedType: com.github.k-orc.openstack-resource-controller.api.v1alpha1.PortRangeStatus - name: protocol type: scalar: string diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 9bbc6220..8d877955 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -122,6 +122,10 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.PortFilterApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("PortImport"): return &apiv1alpha1.PortImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("PortRangeSpec"): + return &apiv1alpha1.PortRangeSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("PortRangeStatus"): + return &apiv1alpha1.PortRangeStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("PortRefs"): return &apiv1alpha1.PortRefsApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("PortResourceSpec"): diff --git a/test/apivalidations/securitygroup_test.go b/test/apivalidations/securitygroup_test.go new file mode 100644 index 00000000..4b51ab35 --- /dev/null +++ b/test/apivalidations/securitygroup_test.go @@ -0,0 +1,356 @@ +/* +Copyright 2024 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package apivalidations + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/k-orc/openstack-resource-controller/api/v1alpha1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/api/v1alpha1" + applyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/pkg/clients/applyconfiguration/api/v1alpha1" + "k8s.io/utils/ptr" +) + +const ( + securityGroupName = "sg-foo" + securityGroupID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae12f" +) + +func securityGroupStub(namespace *corev1.Namespace) *orcv1alpha1.SecurityGroup { + obj := &orcv1alpha1.SecurityGroup{} + obj.Name = securityGroupName + obj.Namespace = namespace.Name + return obj +} + +func testSecurityGroupResource() *applyconfigv1alpha1.SecurityGroupResourceSpecApplyConfiguration { + return applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules() +} + +func baseSecurityGroupPatch(securityGroup client.Object) *applyconfigv1alpha1.SecurityGroupApplyConfiguration { + return applyconfigv1alpha1.SecurityGroup(securityGroup.GetName(), securityGroup.GetNamespace()). + WithSpec(applyconfigv1alpha1.SecurityGroupSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func baseSGRulePatchSpec() *applyconfigv1alpha1.SecurityGroupRuleApplyConfiguration { + return applyconfigv1alpha1.SecurityGroupRule().WithEthertype(orcv1alpha1.EthertypeIPv4).WithProtocol(orcv1alpha1.ProtocolTCP) +} + +func testSecurityGroupImport() *applyconfigv1alpha1.SecurityGroupImportApplyConfiguration { + return applyconfigv1alpha1.SecurityGroupImport().WithID(securityGroupID) +} + +var _ = Describe("ORC SecurityGroup API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal security group and managementPolicy should default to managed", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec()) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed()) + Expect(securityGroup.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed()) + + patch.Spec.WithImport(testSecurityGroupImport()) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testSecurityGroupImport()). + WithResource(testSecurityGroupResource()) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed()) + }) + + It("should not permit empty import", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.SecurityGroupImport()) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed()) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.SecurityGroupImport(). + WithFilter(applyconfigv1alpha1.SecurityGroupFilter())) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed()) + }) + + It("should permit import filter with name", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.SecurityGroupImport(). + WithFilter(applyconfigv1alpha1.SecurityGroupFilter().WithName("foo"))) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed()) + + patch.Spec.WithResource(testSecurityGroupResource()) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec. + WithImport(testSecurityGroupImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testSecurityGroupResource()) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed()) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec. + WithImport(testSecurityGroupImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed()) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec()) + patch.Spec. + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)).WithResource( + applyconfigv1alpha1.SecurityGroupResourceSpec()) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed()) + Expect(securityGroup.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) + + It("should not permit invalid direction", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(baseSGRulePatchSpec().WithDirection("foo"))) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed(), "create security group") + }) + + It("should permit valid direction", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(baseSGRulePatchSpec().WithDirection("ingress"))) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed(), "create security group") + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(baseSGRulePatchSpec().WithDirection("egress"))) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed(), "create security group") + }) + + It("should not permit invalid ethertype", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(baseSGRulePatchSpec().WithEthertype("foo"))) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed(), "create security group") + }) + + It("should not permit no ethertype", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + sgSpecRule := applyconfigv1alpha1.SecurityGroupRule().WithProtocol(orcv1alpha1.ProtocolTCP) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgSpecRule)) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed(), "create security group") + }) + + It("should permit valid ethertype", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(baseSGRulePatchSpec().WithEthertype(orcv1alpha1.EthertypeIPv6))) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed(), "create security group") + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(baseSGRulePatchSpec().WithEthertype(orcv1alpha1.EthertypeIPv6))) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed(), "create security group") + }) + + DescribeTable("should permit valid protocol", + func(ctx context.Context, protocol orcv1alpha1.Protocol, ethertype orcv1alpha1.Ethertype) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + sgRuleSpecPatch := applyconfigv1alpha1.SecurityGroupRule().WithEthertype(ethertype).WithProtocol(protocol) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgRuleSpecPatch)) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed(), "create security group") + }, + Entry(string(orcv1alpha1.ProtocolAH), orcv1alpha1.ProtocolAH, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolDCCP), orcv1alpha1.ProtocolDCCP, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolEGP), orcv1alpha1.ProtocolEGP, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolESP), orcv1alpha1.ProtocolESP, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolGRE), orcv1alpha1.ProtocolGRE, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolICMP), orcv1alpha1.ProtocolICMP, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolICMPV6), orcv1alpha1.ProtocolICMPV6, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolIGMP), orcv1alpha1.ProtocolIGMP, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolIPIP), orcv1alpha1.ProtocolIPIP, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolOSPF), orcv1alpha1.ProtocolOSPF, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolPGM), orcv1alpha1.ProtocolPGM, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolRSVP), orcv1alpha1.ProtocolRSVP, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolSCTP), orcv1alpha1.ProtocolSCTP, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolTCP), orcv1alpha1.ProtocolTCP, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolUDP), orcv1alpha1.ProtocolUDP, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolUDPLITE), orcv1alpha1.ProtocolUDPLITE, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolVRRP), orcv1alpha1.ProtocolVRRP, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolIPV6ENCAP), orcv1alpha1.ProtocolIPV6ENCAP, orcv1alpha1.EthertypeIPv6), + Entry(string(orcv1alpha1.ProtocolIPV6FRAG), orcv1alpha1.ProtocolIPV6FRAG, orcv1alpha1.EthertypeIPv6), + Entry(string(orcv1alpha1.ProtocolIPV6ICMP), orcv1alpha1.ProtocolIPV6ICMP, orcv1alpha1.EthertypeIPv6), + Entry(string(orcv1alpha1.ProtocolIPV6NONXT), orcv1alpha1.ProtocolIPV6NONXT, orcv1alpha1.EthertypeIPv6), + Entry(string(orcv1alpha1.ProtocolIPV6OPTS), orcv1alpha1.ProtocolIPV6OPTS, orcv1alpha1.EthertypeIPv6), + Entry(string(orcv1alpha1.ProtocolIPV6ROUTE), orcv1alpha1.ProtocolIPV6ROUTE, orcv1alpha1.EthertypeIPv6), + ) + + DescribeTable("should only allow valid port range for ICMP and ICMPv6 valid protocol", + func(ctx context.Context, protocol orcv1alpha1.Protocol, ethertype orcv1alpha1.Ethertype) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + sgRulePatchSpec := applyconfigv1alpha1.SecurityGroupRule().WithEthertype(ethertype).WithProtocol(protocol) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgRulePatchSpec.WithPortRange( + &applyconfigv1alpha1.PortRangeSpecApplyConfiguration{ + Min: ptr.To(v1alpha1.PortNumber(256)), Max: ptr.To(v1alpha1.PortNumber(0))}))) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed(), "create security group") + + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgRulePatchSpec.WithPortRange( + &applyconfigv1alpha1.PortRangeSpecApplyConfiguration{ + Min: ptr.To(v1alpha1.PortNumber(0)), Max: ptr.To(v1alpha1.PortNumber(256))}))) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed(), "create security group") + + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgRulePatchSpec.WithPortRange( + &applyconfigv1alpha1.PortRangeSpecApplyConfiguration{ + Min: ptr.To(v1alpha1.PortNumber(255)), Max: ptr.To(v1alpha1.PortNumber(255))}))) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed(), "create security group") + }, + Entry(string(orcv1alpha1.ProtocolICMP), orcv1alpha1.ProtocolICMP, orcv1alpha1.EthertypeIPv4), + Entry(string(orcv1alpha1.ProtocolICMPV6), orcv1alpha1.ProtocolICMPV6, orcv1alpha1.EthertypeIPv4), + ) + + It("should not permit invalid protocol", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(baseSGRulePatchSpec().WithProtocol("foo"))) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed(), "create security group") + }) + + It("should permit without protocol", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + sgRulePatchSpec := applyconfigv1alpha1.SecurityGroupRule().WithEthertype(orcv1alpha1.EthertypeIPv4) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgRulePatchSpec)) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed(), "create security group") + }) + + It("should permit valid port range min and max", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + sgRulePatchSpec := baseSGRulePatchSpec().WithProtocol(orcv1alpha1.ProtocolTCP) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgRulePatchSpec.WithPortRange( + &applyconfigv1alpha1.PortRangeSpecApplyConfiguration{ + Min: ptr.To(v1alpha1.PortNumber(22)), Max: ptr.To(v1alpha1.PortNumber(23))}))) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed(), "create security group") + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgRulePatchSpec.WithPortRange( + &applyconfigv1alpha1.PortRangeSpecApplyConfiguration{ + Min: ptr.To(v1alpha1.PortNumber(22)), Max: ptr.To(v1alpha1.PortNumber(22))}))) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed(), "create security group") + }) + + It("should reject invalid port range min or max", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + sgRulePatchSpec := baseSGRulePatchSpec().WithProtocol(orcv1alpha1.ProtocolTCP) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgRulePatchSpec.WithPortRange( + &applyconfigv1alpha1.PortRangeSpecApplyConfiguration{ + Min: ptr.To(v1alpha1.PortNumber(51)), Max: ptr.To(v1alpha1.PortNumber(50))}))) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed(), "create security group") + + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgRulePatchSpec.WithPortRange( + &applyconfigv1alpha1.PortRangeSpecApplyConfiguration{ + Min: ptr.To(v1alpha1.PortNumber(-1))}))) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed(), "create security group") + + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgRulePatchSpec.WithPortRange( + &applyconfigv1alpha1.PortRangeSpecApplyConfiguration{ + Min: ptr.To(v1alpha1.PortNumber(50))}))) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed(), "create security group") + + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgRulePatchSpec.WithPortRange( + &applyconfigv1alpha1.PortRangeSpecApplyConfiguration{ + Max: ptr.To(v1alpha1.PortNumber(50))}))) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed(), "create security group") + }) + + It("should reject invalid CIDR for RemoteIPPrefix", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + sgRulePatchSpec := baseSGRulePatchSpec().WithProtocol(orcv1alpha1.ProtocolTCP).WithEthertype(orcv1alpha1.EthertypeIPv4) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgRulePatchSpec.WithPortRange( + &applyconfigv1alpha1.PortRangeSpecApplyConfiguration{ + Min: ptr.To(v1alpha1.PortNumber(22)), Max: ptr.To(v1alpha1.PortNumber(22))}).WithRemoteIPPrefix("foo"))) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed(), "create security group") + }) + + It("should reject CIDR for RemoteIPPrefix that doesn't match the ethertype", func(ctx context.Context) { + var sgRulePatchSpec *applyconfigv1alpha1.SecurityGroupRuleApplyConfiguration + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + sgRulePatchSpec = applyconfigv1alpha1.SecurityGroupRule().WithEthertype(orcv1alpha1.EthertypeIPv6) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgRulePatchSpec.WithPortRange( + &applyconfigv1alpha1.PortRangeSpecApplyConfiguration{ + Min: ptr.To(v1alpha1.PortNumber(22)), Max: ptr.To(v1alpha1.PortNumber(22))}).WithRemoteIPPrefix("192.168.0.1/24"))) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed(), "create security group") + + sgRulePatchSpec = applyconfigv1alpha1.SecurityGroupRule().WithEthertype(orcv1alpha1.EthertypeIPv4) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgRulePatchSpec.WithPortRange( + &applyconfigv1alpha1.PortRangeSpecApplyConfiguration{ + Min: ptr.To(v1alpha1.PortNumber(22)), Max: ptr.To(v1alpha1.PortNumber(22))}).WithRemoteIPPrefix("2001:db8::/47"))) + Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed(), "create security group") + }) + + It("should permit valid CIDR that matches the ethertype", func(ctx context.Context) { + securityGroup := securityGroupStub(namespace) + patch := baseSecurityGroupPatch(securityGroup) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(baseSGRulePatchSpec().WithRemoteIPPrefix("192.168.0.1/24"))) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed(), "create security group") + sgRulePatchSpec := applyconfigv1alpha1.SecurityGroupRule().WithEthertype(orcv1alpha1.EthertypeIPv6).WithProtocol(orcv1alpha1.ProtocolTCP) + patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec().WithRules(sgRulePatchSpec.WithRemoteIPPrefix("2001:db8::/47"))) + Expect(applyObj(ctx, securityGroup, patch)).To(Succeed(), "create security group") + }) +})