-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Security group and Security group rules validation #165
Conversation
b25cf0a
to
dcf0f04
Compare
dcf0f04
to
f440e1a
Compare
@itzikb-redhat I just rebased this so it no longer includes #167, no other changes. |
api/v1alpha1/securitygroup_types.go
Outdated
// +kubebuilder:validation:Enum:=IPv4;IPv6 | ||
// +kubebuilder:validation:MinLength:=1 | ||
// +kubebuilder:validation:MaxLength:=16 | ||
// +kubebuilder:validation:MaxLength:=4 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious: what does MaxLength do when the field is an enum?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also wondered this. It's possible that even though it's technically redundant for validation purposes, it may still be used in the runtime complexity calculations? I'll bet @JoelSpeed would know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For runtime complexity, the openapi schema shows that the field is an enum, so the estimated worst case is based on the maximum length of the enum values. You don't actually need a maxlength on an enum WRT CEL, but it also cannot hurt to include it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll remove both length rules
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Still there, btw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed. Also from RuleDirection
api/v1alpha1/securitygroup_types.go
Outdated
Ethertype *Ethertype `json:"ethertype"` | ||
// If the protocol is [tcp, udp, dccp sctp,udplite] this value must be less than | ||
// or equal to the PortRangeMax attribute value. | ||
// If the protocol is ICMP, this value must be an ICMP type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the protocol is ICMP, this value must be an ICMP type
Before reading the code, I can't immediately find a meaning to this sentence
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When setting up ICMP, I would naïvely leave PortRangeMin and PortRangeMax empty. Would I be wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First I have a typo, PorRangeMin -> icmp type, PortRangeMax - icmp code
Can you suggest an alternative to the comment?
At least from my testing with the openstack client you can leave them out (Then I think the rule catch all the ICMP types)
api/v1alpha1/securitygroup_types.go
Outdated
// Protocol is the IP protocol can be represented by a string, an | ||
// integer, or null | ||
// +optional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I'm commenting the comment) I don't believe you can represent the protocol as an integer here. You probably could if type Protocol
's underlying type was an integer and you overrode UnmarshalJSON
to also accept strings. But I am not sure that Kubebuilder would like it.
Do you really plan on accepting integers here? And if not, how do I set e.g. vrrp?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what the concern is. If you want vrrp to be set as a number you set the number "112" (as a string)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want users to pass numbers as strings, you should tell them explicitly in the comment. Also would null be passed as "null"
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding null I think I'll just remove it. About numbers as strings - Do we want to use also allow integers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think:
- We don't allow numbers and just use the strings
- We should validate
Protocol
as an enum
As for null
, what does OpenStack do with that? Is it the same as any
? If so, I think we should support either any
or null
(meaning unspecified here), but not both.
If we go with null
we should write it as 'if not specified...' or similar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok we'll go with not specified and use strings. As for enum - we use a pattern instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mdbooth
May I humour you with this
(also cf gophercloud/gophercloud#2442 (comment) )
96f711f
to
d6864c2
Compare
api/v1alpha1/securitygroup_types.go
Outdated
@@ -26,37 +26,84 @@ type RuleDirection string | |||
// +kubebuilder:validation:MaxLength:=16 | |||
type Protocol string | |||
|
|||
const ( | |||
ProtocolANY Protocol = "any" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enums in Kube APIs are generally PascalCase, these probably want to follow that convention? Any
, ICMP
, ICMPv6
, IPv6Encap
etc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JoelSpeed I previously gave this some thought in the Image API. These constants are defined by the OpenStack API. In my first version of the Image API I translated the constants, but:
- It's a huge mess in the code
- It's another source of errors
- I suspect it's more confusing for users who are likely using the OpenStack docs as a primary source for more complex things like this
So my current thinking is that we don't translate constants which are defined by the OpenStack API.
This is itself a source of confusion because now users need to understand which constants are OpenStack constants and which are not. I think either choice has trade offs, but my current feeling is that this has the better balance for this API.
@mandre Incidentally, if we agree with this we should document it explicitly in our API conventions.
Perhaps we could have a convention that we point out explicitly in the Godocs that these are 'passthrough' values which correlate exactly to OpenStack values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I've added the following to our API convention doc:
* Constants coming from OpenStack should be copied verbatim.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If that's the direction of the project, then that is fair. A lot of other projects around clouds have made the same decision. Though of course, you do then juxtapose with core Kube APIs and other APIs that follow kube conventions
If a user knows openstack uses snake case, and knows Kube uses pascal case, I guess they have a 50/50 of getting it right
api/v1alpha1/securitygroup_types.go
Outdated
// SecurityGroupRule defines a Security Group rule | ||
// +kubebuilder:validation:MinProperties:=1 | ||
// +kubebuilder:validation:XValidation:rule="has(self.portRangeMin) == has(self.portRangeMax)",message="PortRangeMin and PortRangeMax should exist together" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the serialised version of the field name in the user facing error, also, maybe prefer must
to should
?
// +kubebuilder:validation:XValidation:rule="has(self.portRangeMin) == has(self.portRangeMax)",message="PortRangeMin and PortRangeMax should exist together" | |
// +kubebuilder:validation:XValidation:rule="has(self.portRangeMin) == has(self.portRangeMax)",message="portRangeMin and portRangeMax must only exist together" |
Have you considered making an optional portRange
struct, with min and max fields within that are required?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
api/v1alpha1/securitygroup_types.go
Outdated
PortRangeMin *int32 `json:"portRangeMin,omitempty"` | ||
PortRangeMax *int32 `json:"portRangeMax,omitempty"` | ||
// +kubebuilder:validation:Required | ||
Ethertype *Ethertype `json:"ethertype"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why a pointer for a required field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
api/v1alpha1/securitygroup_types.go
Outdated
PortRangeMin *uint16 `json:"portRangeMin,omitempty"` | ||
// If the protocol is [tcp, udp, dccp sctp,udplite] this value must be greater than | ||
// or equal to the PortRangeMin attribute value. | ||
// If the protocol is ICMP, this value must be an ICMP type | ||
// +optional | ||
PortRangeMax *uint16 `json:"portRangeMax,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use uints, they don't translate into an openapi schema in any way that correctly validates the unsigned nature, use int32
and set a minimum value of zero using the appropriate kubebuilder marker
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -26,37 +26,85 @@ type RuleDirection string | |||
// +kubebuilder:validation:MaxLength:=16 | |||
type Protocol string | |||
|
|||
const ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why aren't you using an enum for protocol?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please clarify?
5d4cef4
to
a09c695
Compare
1713d4c
to
82835d0
Compare
3c7172b
to
8335adf
Compare
api/v1alpha1/securitygroup_types.go
Outdated
@@ -26,41 +26,100 @@ type RuleDirection string | |||
// +kubebuilder:validation:MaxLength:=16 | |||
type Protocol string | |||
|
|||
const ( | |||
ProtocolANY Protocol = "any" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@itzikb-redhat We should delete ProtocolANY
now we've decided not to use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Min PortNumber `json:"min"` | ||
// +required | ||
Max PortNumber `json:"max"` | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can we have a blank line between these. Surprised the linter didn't complain 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
// 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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please can you move this validation to PortRangeSpec
? We can just implement it unconditionally there. i.e. Everywhere that PortRangeSpec is defined:
- It must have min and max
- Max >= min
Anywhere that PortRangeSpec is not defined doesn't need to worry about these rules.
Edit: See below. There might actually be a case where max can be less than min? My feeling that we should put ICMP in its own field grows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved. IIUC Max should be >=0 for specific protocols
// +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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been tempted to say that we should separately define an ICMP Protocol
field in our API which we marshal into portRange internally. I'm still tempted, tbh. However, I worry that may be straying too far from the admittedly terrible OpenStack API. Would we be creating unnecessary maintenance for ourselves and impedance for our users? I don't know how much I want to do this kind of thing in general. Would an abundance of this kind of difference it make it harder for OpenStack devs to contribute?
Thoughts @mandre @JoelSpeed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect it will indeed make our (and our users') life miserable if we differ too much from the OpenStack API. Wasn't it the reason to align with OpenStack constants?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For posterity, this was the balance we agreed on. We'll stay close to the terrible OpenStack API.
// If the protocol is ICMP, this PortRamge.Min must be an ICMP code and PortRange.Max | ||
// should be an ICMP type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WHA? So for ICMP max might be less than min?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC yes
Entry(string(orcv1alpha1.ProtocolIPV6NONXT), orcv1alpha1.ProtocolIPV6NONXT, orcv1alpha1.EthertypeIPv6), | ||
Entry(string(orcv1alpha1.ProtocolIPV6OPTS), orcv1alpha1.ProtocolIPV6OPTS, orcv1alpha1.EthertypeIPv6), | ||
Entry(string(orcv1alpha1.ProtocolIPV6ROUTE), orcv1alpha1.ProtocolIPV6ROUTE, orcv1alpha1.EthertypeIPv6), | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a negative test here: it should not permit invalid protocol.
Edit: it's below.
nit: Might be better to move it here so it's next to the other protocol tests.
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))}))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could do with a comment here. The reason this should fail is that min > 255, not (bizarrely, see ICMP portRange abuse) because min > max.
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") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incidentally, "create security group" is context we'll get if this fails. As we write more of these it might be good to add better context. e.g. Here we might write something like "ICMP security group with portRange.min > 255".
// Protocol is the IP protocol can be represented by a string or an | ||
// integer represented as a string. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should remove the reference to an integer here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we still create a security group rule for a protocol that is not defined in ORC and part of the enum? OpenStack allows to pass the protocol number.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we agreed on working only with defined protocols and without numbers
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"))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't work out what would make this test fail. If you change the NotTo
to To
below this test should fail and we should see which validation is tripping it. I'm curious, because we didn't make protocol
an enum yet unless I missed that.
api/v1alpha1/securitygroup_types.go
Outdated
EthertypeIPv6 Ethertype = "IPv6" | ||
) | ||
|
||
// +kubebuilder:validation:Minimum:=1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The port number can also represent the ICMP type when the protocol is ICMP, and the type can be 0.
// +kubebuilder:validation:Minimum:=1 | |
// +kubebuilder:validation:Minimum:=0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
// +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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect it will indeed make our (and our users') life miserable if we differ too much from the OpenStack API. Wasn't it the reason to align with OpenStack constants?
@@ -13,13 +13,14 @@ spec: | |||
rules: | |||
- direction: ingress | |||
protocol: tcp | |||
portRangeMin: 22 | |||
portRangeMax: 22 | |||
ethertype: IPv4 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should not remove the ethertype in this example. This PR makes it required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. How didn't it fail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because this example is not exercised in CI. Only the cirros
one is.
@@ -0,0 +1,52 @@ | |||
/* | |||
Copyright 2024 The ORC Authors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the copyright say 2025 now, at least for new files?
// Protocol is the IP protocol can be represented by a string or an | ||
// integer represented as a string. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we still create a security group rule for a protocol that is not defined in ORC and part of the enum? OpenStack allows to pass the protocol number.
Add/Update Validations and validations tests to SecurityGroup and SecurityGroupRule
8335adf
to
f23d352
Compare
// +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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For posterity, this was the balance we agreed on. We'll stay close to the terrible OpenStack API.
@mandre FWIW I manually removed the |
Add/Update Validations and validations tests to SecurityGroup and SecurityGroupRule