Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -484,4 +484,16 @@ resources:
kind: CommunitySet
path: github.com/ironcore-dev/network-operator/api/core/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: networking.metal.ironcore.dev
group: core
kind: MacSec
path: github.com/ironcore-dev/network-operator/api/core/v1alpha1
version: v1alpha1
webhooks:
validation: true
webhookVersion: v1
version: "3"
4 changes: 4 additions & 0 deletions Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ k8s_resource(new_name='acl', objects=['acl:accesscontrollist'], trigger_mode=TRI
k8s_yaml('./config/samples/v1alpha1_certificate.yaml')
k8s_resource(new_name='trustpoint', objects=['network-operator:issuer', 'network-operator-ca:certificate', 'trustpoint:certificate'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False, labels=['samples'])

k8s_yaml('./config/samples/v1alpha1_macsec.yaml')
k8s_resource(new_name='macsec-eth1-1', objects=['secure-layer2:macsec'], resource_deps=['leaf1', 'eth1-1'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
k8s_resource(new_name='macsec-secrets', objects=['secret1:secret', 'secret2-rollover:secret'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)

k8s_yaml('./config/samples/v1alpha1_snmp.yaml')
k8s_resource(new_name='snmp', objects=['snmp:snmp'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False, labels=['samples'])

Expand Down
4 changes: 4 additions & 0 deletions api/core/v1alpha1/groupversion_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ const DeviceKind = "Device"
// the name of the aggregate interface they belong to.
const AggregateLabel = "networking.metal.ironcore.dev/aggregate-name"

// MacSecLabel is a label applied to the interface to indicate
// the name of the MacSec policy associated with the interface.
const MacSecLabel = "networking.metal.ironcore.dev/macsec-name"

// RoutedVLANLabel is a label applied to VLANs to indicate
// the name of the RoutedVLAN interface that provides Layer 3 routing for the VLAN.
const RoutedVLANLabel = "networking.metal.ironcore.dev/routed-vlan-name"
Expand Down
181 changes: 181 additions & 0 deletions api/core/v1alpha1/macsec_types.go
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

Copy link
Copy Markdown
Contributor

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

// 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"
Comment thread
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"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add this:

// +kubebuilder:validation:XValidation:rule="self.endTime > self.startTime",message="endTime must be after startTime"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the lifetime validation to a webhoook

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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"
Comment thread
sven-rosenzweig marked this conversation as resolved.
Resolved
// +kubebuilder:printcolumn:name="Configured",type=string,JSONPath=`.status.conditions[?(@.type=="Configured")].status`,priority=1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 ready condition in the meantime). I am doing several assumptions and might be wrong here. Do you have any thoughts on this regard?

// +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
})
}
148 changes: 148 additions & 0 deletions api/core/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading