Repository navigation
feat: Implement MacSec Controller #366
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,181 @@ | ||
| // SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package v1alpha1 | ||
|
|
||
| import ( | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/apimachinery/pkg/runtime" | ||
| ) | ||
|
|
||
| const InterfaceRefIndexKey = "spec.interfaceRef.name" | ||
|
|
||
| // / ConfidentialityOffset represents the number of octets in an Ethernet frame that are sent in unencrypted plain-text. | ||
| type ConfidentialityOffset uint8 | ||
|
|
||
| // +kubebuilder:validation:Enum=0;30;50 | ||
| const ( | ||
| // ZeroBytes represents broadcast traffic. | ||
| ZeroBytes ConfidentialityOffset = 0 | ||
| // ThirtyBytes represents multicast traffic. | ||
| ThirtyBytes ConfidentialityOffset = 30 | ||
| // FiftyBytes represents unicast traffic. | ||
| FiftyBytes ConfidentialityOffset = 50 | ||
| ) | ||
|
|
||
| // MacSecSpec defines the desired state of MacSec. | ||
| type MacSecSpec struct { | ||
| // DeviceName is the name of the Device this object belongs to. The Device object must exist in the same namespace. | ||
| // Immutable. | ||
| // +required | ||
| // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="DeviceRef is immutable" | ||
| DeviceRef LocalObjectReference `json:"deviceRef"` | ||
|
|
||
| // ProviderConfigRef is a reference to a resource holding the provider-specific configuration. | ||
| // This reference is used to link the MacSec to its provider-specific configuration. | ||
| // +optional | ||
| ProviderConfigRef *TypedLocalObjectReference `json:"providerConfigRef,omitempty"` | ||
|
|
||
| // Name is the name of the MacSec configuration. | ||
| // Immutable. | ||
| // +required | ||
| // +kubebuilder:validation:MinLength=1 | ||
| // +kubebuilder:validation:MaxLength=255 | ||
| // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Name is immutable" | ||
| Name string `json:"name"` | ||
|
|
||
| // InterfaceRef is a reference to the interface on which MACsec is enabled. The interface must exist on the Device specified by deviceRef. | ||
| // Immutable. | ||
| // +required | ||
| // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="InterfaceRef is immutable" | ||
|
nikatza marked this conversation as resolved.
Resolved
|
||
| InterfaceRef LocalObjectReference `json:"interfaceRef"` | ||
|
|
||
| // PreSharedKeys is a list of pre-shared keys used for MACSec encryption. | ||
| // Specify multiple keys if needed for redundancy rotation. | ||
| // Sort them in the order they should be used for redundancy rotation. | ||
| // +kubebuilder:validation:MinItems=1 | ||
| // +required | ||
| PreSharedKeys []PreSharedKey `json:"preSharedKeys"` | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this resource internally handle the rotation? I feel like the rotation of keys would be something a higher-level controller would orchestrate and is rather unusual to have on a Brick.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would say no. A higher level resource should make sure that there is a valid key associated with the macsec connection. supporting multiple keys allows us to hitless rollover to the new key. |
||
|
|
||
| // Policy defines the MACSec policy configuration. | ||
| // +required | ||
| Policy *MacSecPolicy `json:"policy"` | ||
| } | ||
|
|
||
| // CryptographicAlgorithm is the MKA key-derivation algorithm used for a pre-shared key. | ||
| // +kubebuilder:validation:Enum=AES-128-CMAC;AES-256-CMAC | ||
| type CryptographicAlgorithm string | ||
|
|
||
| const ( | ||
| // AES128CMAC is the AES-128-CMAC key-derivation algorithm. | ||
| AES128CMAC CryptographicAlgorithm = "AES-128-CMAC" | ||
| // AES256CMAC is the AES-256-CMAC key-derivation algorithm. | ||
| AES256CMAC CryptographicAlgorithm = "AES-256-CMAC" | ||
| ) | ||
|
|
||
| // PreSharedKey defines a single MACsec pre-shared key: a reference to the Secret | ||
| // holding the raw key material plus the key metadata. | ||
| type PreSharedKey struct { | ||
| // SecretRef references the Secret (in the same namespace) holding the raw key material | ||
| // under the data key "key". The Secret must contain only the key material. | ||
| // +required | ||
| SecretRef LocalObjectReference `json:"secretRef"` | ||
|
|
||
| // ConnectivityKeyName is the connectivity association key name (CKN). | ||
| // +required | ||
| // +kubebuilder:validation:MinLength=1 | ||
| // +kubebuilder:validation:MaxLength=64 | ||
| ConnectivityKeyName string `json:"connectivityKeyName"` | ||
|
|
||
| // Algorithm is the cryptographic algorithm used for this key. | ||
| // +required | ||
| Algorithm CryptographicAlgorithm `json:"algorithm"` | ||
|
|
||
| // StartTime is when this key becomes active. | ||
| // The clock and date rendered into the device configuration are taken in UTC, | ||
| // so a value with any timezone offset is normalized to UTC before being realized. | ||
| // +required | ||
| StartTime metav1.Time `json:"startTime"` | ||
|
|
||
| // EndTime is when this key expires. Must be after startTime. | ||
| // The clock and date rendered into the device configuration are taken in UTC, | ||
| // so a value with any timezone offset is normalized to UTC before being realized. | ||
| // +required | ||
| // Validates if the string is a valid datetime in RFC3339 format. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could add this:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved the lifetime validation to a webhoook
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The CEL validation is still a cheap thing we could do (while keeping the webhook, which is already useful). |
||
| EndTime metav1.Time `json:"endTime"` | ||
| } | ||
|
|
||
| type MacSecPolicy struct { | ||
| // CipherSuite is the MACSec cipher suite to be used. | ||
| // +required | ||
| CipherSuite string `json:"cipherSuite"` | ||
|
|
||
| // ConfidentialityOffset specifies the number of octets in an Ethernet frame that are sent in unencrypted plain-text | ||
| // +required | ||
| ConfidentialityOffset ConfidentialityOffset `json:"confidentialityOffset"` | ||
|
|
||
| // KeyServerPriority specifies the key server priority used by the MACsec Key Agreement (MKA) protocol to select the key server when | ||
| // MACsec is enabled using static connectivity association key (CAK) security mode. | ||
| // +required | ||
| KeyServerPriority int `json:"keyServerPriority"` | ||
|
|
||
| // RelayProtection specifies the number of out-of-order packets that can be received before the secure channel is considered to be inoperable. | ||
| // A value of 0 means that frames are accepted only in the correct order. | ||
| // +required | ||
| RelayProtection int `json:"relayProtection"` | ||
| } | ||
|
|
||
| // MacSecStatus defines the observed state of MacSec. | ||
| type MacSecStatus struct { | ||
| // The conditions are a list of status objects that describe the state of the MacSec. | ||
| //+listType=map | ||
| //+listMapKey=type | ||
| //+patchStrategy=merge | ||
| //+patchMergeKey=type | ||
| //+optional | ||
| Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` | ||
| } | ||
|
|
||
| // +kubebuilder:object:root=true | ||
| // +kubebuilder:subresource:status | ||
| // +kubebuilder:printcolumn:name="Device",type="string",JSONPath=".spec.deviceRef.name",description="The device this MacSec is configured on" | ||
| // +kubebuilder:printcolumn:name="Interface",type="string",JSONPath=".spec.interfaceRef.name",description="The interface this MacSec is configured on" | ||
| // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status",description="Ready status of the MacSec" | ||
|
sven-rosenzweig marked this conversation as resolved.
Resolved
|
||
| // +kubebuilder:printcolumn:name="Configured",type=string,JSONPath=`.status.conditions[?(@.type=="Configured")].status`,priority=1 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at the changes done to the provider, I wonder what happens when a key expires on the device. Validation webhook won't be called. We essentially detect the drift and try to reinstall the keys (since the controller doesnt validate that either). The device rejects the keys and reenqueue indefinitely (updating the |
||
| // +kubebuilder:printcolumn:name="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 | ||
| // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" | ||
|
|
||
| // MacSec is the Schema for the macsecs API. | ||
| type MacSec struct { | ||
| metav1.TypeMeta `json:",inline"` | ||
| metav1.ObjectMeta `json:"metadata,omitempty"` | ||
|
|
||
| Spec MacSecSpec `json:"spec,omitempty"` | ||
| Status MacSecStatus `json:"status,omitempty"` | ||
| } | ||
|
|
||
| // +kubebuilder:object:root=true | ||
|
|
||
| // MacSecList contains a list of MacSec. | ||
| type MacSecList struct { | ||
| metav1.TypeMeta `json:",inline"` | ||
| metav1.ListMeta `json:"metadata,omitempty"` | ||
| Items []MacSec `json:"items"` | ||
| } | ||
|
|
||
| // GetConditions implements conditions.Getter. | ||
| func (m *MacSec) GetConditions() []metav1.Condition { | ||
| return m.Status.Conditions | ||
| } | ||
|
|
||
| // SetConditions implements conditions.Setter. | ||
| func (m *MacSec) SetConditions(conditions []metav1.Condition) { | ||
| m.Status.Conditions = conditions | ||
| } | ||
|
|
||
| func init() { | ||
| SchemeBuilder.Register(func(s *runtime.Scheme) error { | ||
| s.AddKnownTypes(GroupVersion, &MacSec{}, &MacSecList{}) | ||
| return nil | ||
| }) | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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 year from here