From a6665eed0d60e8e08255f1a2040905256e224e74 Mon Sep 17 00:00:00 2001 From: i521907 Date: Wed, 4 Feb 2026 11:42:12 -0500 Subject: [PATCH 1/5] Adding AAA TACACS Support --- .../nx/v1alpha1/zz_generated.deepcopy.go | 3 - api/core/v1alpha1/aaa_types.go | 280 ++++++++++ api/core/v1alpha1/zz_generated.deepcopy.go | 313 +++++++++++- cmd/main.go | 12 + .../networking.metal.ironcore.dev_aaas.yaml | 479 ++++++++++++++++++ config/rbac/role.yaml | 24 +- config/samples/networking_v1alpha1_aaa.yaml | 103 ++++ internal/controller/core/aaa_controller.go | 332 ++++++++++++ internal/provider/cisco/nxos/aaa.go | 186 +++++++ internal/provider/cisco/nxos/provider.go | 192 +++++++ internal/provider/provider.go | 23 + 11 files changed, 1920 insertions(+), 27 deletions(-) create mode 100644 api/core/v1alpha1/aaa_types.go create mode 100644 config/crd/bases/networking.metal.ironcore.dev_aaas.yaml create mode 100644 config/samples/networking_v1alpha1_aaa.yaml create mode 100644 internal/controller/core/aaa_controller.go create mode 100644 internal/provider/cisco/nxos/aaa.go diff --git a/api/cisco/nx/v1alpha1/zz_generated.deepcopy.go b/api/cisco/nx/v1alpha1/zz_generated.deepcopy.go index e293098e..f1f8050e 100644 --- a/api/cisco/nx/v1alpha1/zz_generated.deepcopy.go +++ b/api/cisco/nx/v1alpha1/zz_generated.deepcopy.go @@ -1,8 +1,5 @@ //go:build !ignore_autogenerated -// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors -// SPDX-License-Identifier: Apache-2.0 - // Code generated by controller-gen. DO NOT EDIT. package v1alpha1 diff --git a/api/core/v1alpha1/aaa_types.go b/api/core/v1alpha1/aaa_types.go new file mode 100644 index 00000000..6a46e444 --- /dev/null +++ b/api/core/v1alpha1/aaa_types.go @@ -0,0 +1,280 @@ +// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package v1alpha1 + +import ( + "sync" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// AAASpec defines the desired state of AAA +// +// It models the Authentication, Authorization, and Accounting (AAA) configuration on a network device, +// including TACACS+ server configuration and AAA group/method settings. +type AAASpec 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 of this AAA. + // This reference is used to link the AAA to its provider-specific configuration. + // +optional + ProviderConfigRef *TypedLocalObjectReference `json:"providerConfigRef,omitempty"` + + // TACACSServers is the list of TACACS+ servers to configure. + // +optional + // +listType=map + // +listMapKey=address + // +kubebuilder:validation:MaxItems=16 + TACACSServers []TACACSServer `json:"tacacsServers,omitempty"` + + // TACACSGroup is the TACACS+ server group configuration. + // +optional + TACACSGroup *TACACSGroup `json:"tacacsGroup,omitempty"` + + // Authentication defines the AAA authentication configuration. + // +optional + Authentication *AAAAuthentication `json:"authentication,omitempty"` + + // Authorization defines the AAA authorization configuration. + // +optional + Authorization *AAAAuthorization `json:"authorization,omitempty"` + + // Accounting defines the AAA accounting configuration. + // +optional + Accounting *AAAAccounting `json:"accounting,omitempty"` +} + +// TACACSServer represents a TACACS+ server configuration. +type TACACSServer struct { + // Address is the IP address or hostname of the TACACS+ server. + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + Address string `json:"address"` + + // Port is the TCP port of the TACACS+ server. + // Defaults to 49 if not specified. + // +optional + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + // +kubebuilder:default=49 + Port int32 `json:"port,omitempty"` + + // KeySecretRef is a reference to a secret containing the shared key for this TACACS+ server. + // The secret must contain a key specified in the SecretKeySelector. + // +required + KeySecretRef SecretKeySelector `json:"keySecretRef"` + + // KeyEncryption specifies the encryption type for the key. + // Type7 is the Cisco Type 7 encryption (reversible). + // Type6 is the AES encryption (more secure). + // Clear means the key is sent in cleartext (not recommended). + // +optional + // +kubebuilder:validation:Enum=Type6;Type7;Clear + // +kubebuilder:default=Type7 + KeyEncryption TACACSKeyEncryption `json:"keyEncryption,omitempty"` + + // Timeout is the timeout in seconds for this TACACS+ server. + // +optional + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=60 + Timeout *int32 `json:"timeout,omitempty"` +} + +// TACACSKeyEncryption defines the encryption type for TACACS+ server keys. +// +kubebuilder:validation:Enum=Type6;Type7;Clear +type TACACSKeyEncryption string + +const ( + // TACACSKeyEncryptionType6 uses AES encryption (more secure). + TACACSKeyEncryptionType6 TACACSKeyEncryption = "Type6" + // TACACSKeyEncryptionType7 uses Cisco Type 7 encryption (reversible). + TACACSKeyEncryptionType7 TACACSKeyEncryption = "Type7" + // TACACSKeyEncryptionClear sends the key in cleartext. + TACACSKeyEncryptionClear TACACSKeyEncryption = "Clear" +) + +// TACACSGroup represents a TACACS+ server group configuration. +type TACACSGroup struct { + // Name is the name of the TACACS+ server group. + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=63 + Name string `json:"name"` + + // Servers is the list of TACACS+ server addresses to include in this group. + // The addresses must match addresses defined in TACACSServers. + // +required + // +listType=set + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=16 + Servers []string `json:"servers"` + + // VRF is the VRF to use for communication with the TACACS+ servers. + // +optional + // +kubebuilder:validation:MaxLength=63 + VRF string `json:"vrf,omitempty"` + + // SourceInterface is the source interface to use for communication with the TACACS+ servers. + // +optional + // +kubebuilder:validation:MaxLength=63 + SourceInterface string `json:"sourceInterface,omitempty"` +} + +// AAAAuthentication defines the AAA authentication configuration. +type AAAAuthentication struct { + // Login defines authentication methods for login. + // +optional + Login *AAAAuthenticationLogin `json:"login,omitempty"` + + // LoginErrorEnable enables login error messages. + // +optional + LoginErrorEnable bool `json:"loginErrorEnable,omitempty"` +} + +// AAAAuthenticationLogin defines the login authentication methods. +type AAAAuthenticationLogin struct { + // Default defines the default authentication method list. + // +optional + Default *AAAMethodList `json:"default,omitempty"` + + // Console defines the console authentication method list. + // +optional + Console *AAAMethodList `json:"console,omitempty"` +} + +// AAAAuthorization defines the AAA authorization configuration. +type AAAAuthorization struct { + // ConfigCommands defines authorization for configuration commands. + // +optional + ConfigCommands *AAAAuthorizationConfigCommands `json:"configCommands,omitempty"` +} + +// AAAAuthorizationConfigCommands defines authorization for configuration commands. +type AAAAuthorizationConfigCommands struct { + // Default defines the default authorization method list. + // +optional + Default *AAAMethodList `json:"default,omitempty"` +} + +// AAAAccounting defines the AAA accounting configuration. +type AAAAccounting struct { + // Default defines the default accounting method list. + // +optional + Default *AAAMethodList `json:"default,omitempty"` +} + +// AAAMethodList defines a list of AAA methods to try in order. +type AAAMethodList struct { + // Methods is the ordered list of authentication/authorization/accounting methods. + // Methods are tried in order until one succeeds or all fail. + // +required + // +listType=atomic + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=4 + Methods []AAAMethod `json:"methods"` +} + +// AAAMethod represents an AAA method. +type AAAMethod struct { + // Type is the type of AAA method. + // +required + // +kubebuilder:validation:Enum=Group;Local;None + Type AAAMethodType `json:"type"` + + // GroupName is the name of the server group when Type is Group. + // +optional + // +kubebuilder:validation:MaxLength=63 + GroupName string `json:"groupName,omitempty"` +} + +// AAAMethodType defines the type of AAA method. +// +kubebuilder:validation:Enum=Group;Local;None +type AAAMethodType string + +const ( + // AAAMethodTypeGroup uses a server group (e.g., TACACS+ group). + AAAMethodTypeGroup AAAMethodType = "Group" + // AAAMethodTypeLocal uses the local user database. + AAAMethodTypeLocal AAAMethodType = "Local" + // AAAMethodTypeNone allows access without authentication. + AAAMethodTypeNone AAAMethodType = "None" +) + +// AAAStatus defines the observed state of AAA. +type AAAStatus struct { + // The conditions are a list of status objects that describe the state of the AAA. + // +listType=map + // +listMapKey=type + // +patchStrategy=merge + // +patchMergeKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:path=aaas +// +kubebuilder:resource:singular=aaa +// +kubebuilder:resource:shortName=aaa +// +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name` +// +kubebuilder:printcolumn:name="TACACS Group",type=string,JSONPath=`.spec.tacacsGroup.name` +// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status` +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" + +// AAA is the Schema for the aaas API +type AAA struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Specification of the desired state of the resource. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +required + Spec AAASpec `json:"spec"` + + // Status of the resource. This is set and updated automatically. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Status AAAStatus `json:"status,omitempty,omitzero"` +} + +// GetConditions implements conditions.Getter. +func (a *AAA) GetConditions() []metav1.Condition { + return a.Status.Conditions +} + +// SetConditions implements conditions.Setter. +func (a *AAA) SetConditions(conditions []metav1.Condition) { + a.Status.Conditions = conditions +} + +// +kubebuilder:object:root=true + +// AAAList contains a list of AAA +type AAAList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []AAA `json:"items"` +} + +var ( + AAADependencies []schema.GroupVersionKind + aaaDependenciesMu sync.Mutex +) + +func RegisterAAADependency(gvk schema.GroupVersionKind) { + aaaDependenciesMu.Lock() + defer aaaDependenciesMu.Unlock() + AAADependencies = append(AAADependencies, gvk) +} + +func init() { + SchemeBuilder.Register(&AAA{}, &AAAList{}) +} diff --git a/api/core/v1alpha1/zz_generated.deepcopy.go b/api/core/v1alpha1/zz_generated.deepcopy.go index 025027bd..17fd6de3 100644 --- a/api/core/v1alpha1/zz_generated.deepcopy.go +++ b/api/core/v1alpha1/zz_generated.deepcopy.go @@ -1,8 +1,5 @@ //go:build !ignore_autogenerated -// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors -// SPDX-License-Identifier: Apache-2.0 - // Code generated by controller-gen. DO NOT EDIT. package v1alpha1 @@ -12,6 +9,275 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAA) DeepCopyInto(out *AAA) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAA. +func (in *AAA) DeepCopy() *AAA { + if in == nil { + return nil + } + out := new(AAA) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AAA) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAAAccounting) DeepCopyInto(out *AAAAccounting) { + *out = *in + if in.Default != nil { + in, out := &in.Default, &out.Default + *out = new(AAAMethodList) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAAccounting. +func (in *AAAAccounting) DeepCopy() *AAAAccounting { + if in == nil { + return nil + } + out := new(AAAAccounting) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAAAuthentication) DeepCopyInto(out *AAAAuthentication) { + *out = *in + if in.Login != nil { + in, out := &in.Login, &out.Login + *out = new(AAAAuthenticationLogin) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAAuthentication. +func (in *AAAAuthentication) DeepCopy() *AAAAuthentication { + if in == nil { + return nil + } + out := new(AAAAuthentication) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAAAuthenticationLogin) DeepCopyInto(out *AAAAuthenticationLogin) { + *out = *in + if in.Default != nil { + in, out := &in.Default, &out.Default + *out = new(AAAMethodList) + (*in).DeepCopyInto(*out) + } + if in.Console != nil { + in, out := &in.Console, &out.Console + *out = new(AAAMethodList) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAAuthenticationLogin. +func (in *AAAAuthenticationLogin) DeepCopy() *AAAAuthenticationLogin { + if in == nil { + return nil + } + out := new(AAAAuthenticationLogin) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAAAuthorization) DeepCopyInto(out *AAAAuthorization) { + *out = *in + if in.ConfigCommands != nil { + in, out := &in.ConfigCommands, &out.ConfigCommands + *out = new(AAAAuthorizationConfigCommands) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAAuthorization. +func (in *AAAAuthorization) DeepCopy() *AAAAuthorization { + if in == nil { + return nil + } + out := new(AAAAuthorization) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAAAuthorizationConfigCommands) DeepCopyInto(out *AAAAuthorizationConfigCommands) { + *out = *in + if in.Default != nil { + in, out := &in.Default, &out.Default + *out = new(AAAMethodList) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAAuthorizationConfigCommands. +func (in *AAAAuthorizationConfigCommands) DeepCopy() *AAAAuthorizationConfigCommands { + if in == nil { + return nil + } + out := new(AAAAuthorizationConfigCommands) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAAList) DeepCopyInto(out *AAAList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]AAA, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAList. +func (in *AAAList) DeepCopy() *AAAList { + if in == nil { + return nil + } + out := new(AAAList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AAAList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAAMethod) DeepCopyInto(out *AAAMethod) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAMethod. +func (in *AAAMethod) DeepCopy() *AAAMethod { + if in == nil { + return nil + } + out := new(AAAMethod) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAAMethodList) DeepCopyInto(out *AAAMethodList) { + *out = *in + if in.Methods != nil { + in, out := &in.Methods, &out.Methods + *out = make([]AAAMethod, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAMethodList. +func (in *AAAMethodList) DeepCopy() *AAAMethodList { + if in == nil { + return nil + } + out := new(AAAMethodList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAASpec) DeepCopyInto(out *AAASpec) { + *out = *in + out.DeviceRef = in.DeviceRef + if in.ProviderConfigRef != nil { + in, out := &in.ProviderConfigRef, &out.ProviderConfigRef + *out = new(TypedLocalObjectReference) + **out = **in + } + if in.TACACSServers != nil { + in, out := &in.TACACSServers, &out.TACACSServers + *out = make([]TACACSServer, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.TACACSGroup != nil { + in, out := &in.TACACSGroup, &out.TACACSGroup + *out = new(TACACSGroup) + (*in).DeepCopyInto(*out) + } + if in.Authentication != nil { + in, out := &in.Authentication, &out.Authentication + *out = new(AAAAuthentication) + (*in).DeepCopyInto(*out) + } + if in.Authorization != nil { + in, out := &in.Authorization, &out.Authorization + *out = new(AAAAuthorization) + (*in).DeepCopyInto(*out) + } + if in.Accounting != nil { + in, out := &in.Accounting, &out.Accounting + *out = new(AAAAccounting) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAASpec. +func (in *AAASpec) DeepCopy() *AAASpec { + if in == nil { + return nil + } + out := new(AAASpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAAStatus) DeepCopyInto(out *AAAStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAStatus. +func (in *AAAStatus) DeepCopy() *AAAStatus { + if in == nil { + return nil + } + out := new(AAAStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACLEntry) DeepCopyInto(out *ACLEntry) { *out = *in @@ -3230,6 +3496,47 @@ func (in *SyslogStatus) DeepCopy() *SyslogStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TACACSGroup) DeepCopyInto(out *TACACSGroup) { + *out = *in + if in.Servers != nil { + in, out := &in.Servers, &out.Servers + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TACACSGroup. +func (in *TACACSGroup) DeepCopy() *TACACSGroup { + if in == nil { + return nil + } + out := new(TACACSGroup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TACACSServer) DeepCopyInto(out *TACACSServer) { + *out = *in + out.KeySecretRef = in.KeySecretRef + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(int32) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TACACSServer. +func (in *TACACSServer) DeepCopy() *TACACSServer { + if in == nil { + return nil + } + out := new(TACACSServer) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLS) DeepCopyInto(out *TLS) { *out = *in diff --git a/cmd/main.go b/cmd/main.go index 50eb0a7d..464bf3e0 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -538,6 +538,18 @@ func main() { os.Exit(1) } + if err := (&corecontroller.AAAReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("aaa-controller"), + WatchFilterValue: watchFilterValue, + Provider: prov, + Locker: locker, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "AAA") + os.Exit(1) + } + if err := (&corecontroller.PrefixSetReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), diff --git a/config/crd/bases/networking.metal.ironcore.dev_aaas.yaml b/config/crd/bases/networking.metal.ironcore.dev_aaas.yaml new file mode 100644 index 00000000..f2813fd6 --- /dev/null +++ b/config/crd/bases/networking.metal.ironcore.dev_aaas.yaml @@ -0,0 +1,479 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.0 + name: aaas.networking.metal.ironcore.dev +spec: + group: networking.metal.ironcore.dev + names: + kind: AAA + listKind: AAAList + plural: aaas + shortNames: + - aaa + singular: aaa + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.deviceRef.name + name: Device + type: string + - jsonPath: .spec.tacacsGroup.name + name: TACACS Group + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: AAA is the Schema for the aaas API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Specification of the desired state of the resource. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + properties: + accounting: + description: Accounting defines the AAA accounting configuration. + properties: + default: + description: Default defines the default accounting method list. + properties: + methods: + description: |- + Methods is the ordered list of authentication/authorization/accounting methods. + Methods are tried in order until one succeeds or all fail. + items: + description: AAAMethod represents an AAA method. + properties: + groupName: + description: GroupName is the name of the server group + when Type is Group. + maxLength: 63 + type: string + type: + allOf: + - enum: + - Group + - Local + - None + - enum: + - Group + - Local + - None + description: Type is the type of AAA method. + type: string + required: + - type + type: object + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - methods + type: object + type: object + authentication: + description: Authentication defines the AAA authentication configuration. + properties: + login: + description: Login defines authentication methods for login. + properties: + console: + description: Console defines the console authentication method + list. + properties: + methods: + description: |- + Methods is the ordered list of authentication/authorization/accounting methods. + Methods are tried in order until one succeeds or all fail. + items: + description: AAAMethod represents an AAA method. + properties: + groupName: + description: GroupName is the name of the server + group when Type is Group. + maxLength: 63 + type: string + type: + allOf: + - enum: + - Group + - Local + - None + - enum: + - Group + - Local + - None + description: Type is the type of AAA method. + type: string + required: + - type + type: object + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - methods + type: object + default: + description: Default defines the default authentication method + list. + properties: + methods: + description: |- + Methods is the ordered list of authentication/authorization/accounting methods. + Methods are tried in order until one succeeds or all fail. + items: + description: AAAMethod represents an AAA method. + properties: + groupName: + description: GroupName is the name of the server + group when Type is Group. + maxLength: 63 + type: string + type: + allOf: + - enum: + - Group + - Local + - None + - enum: + - Group + - Local + - None + description: Type is the type of AAA method. + type: string + required: + - type + type: object + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - methods + type: object + type: object + loginErrorEnable: + description: LoginErrorEnable enables login error messages. + type: boolean + type: object + authorization: + description: Authorization defines the AAA authorization configuration. + properties: + configCommands: + description: ConfigCommands defines authorization for configuration + commands. + properties: + default: + description: Default defines the default authorization method + list. + properties: + methods: + description: |- + Methods is the ordered list of authentication/authorization/accounting methods. + Methods are tried in order until one succeeds or all fail. + items: + description: AAAMethod represents an AAA method. + properties: + groupName: + description: GroupName is the name of the server + group when Type is Group. + maxLength: 63 + type: string + type: + allOf: + - enum: + - Group + - Local + - None + - enum: + - Group + - Local + - None + description: Type is the type of AAA method. + type: string + required: + - type + type: object + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - methods + type: object + type: object + type: object + deviceRef: + description: |- + DeviceName is the name of the Device this object belongs to. The Device object must exist in the same namespace. + Immutable. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + maxLength: 63 + minLength: 1 + type: string + required: + - name + type: object + x-kubernetes-map-type: atomic + x-kubernetes-validations: + - message: DeviceRef is immutable + rule: self == oldSelf + providerConfigRef: + description: |- + ProviderConfigRef is a reference to a resource holding the provider-specific configuration of this AAA. + This reference is used to link the AAA to its provider-specific configuration. + properties: + apiVersion: + description: APIVersion is the api group version of the resource + being referenced. + maxLength: 253 + minLength: 1 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/)?([a-z0-9]([-a-z0-9]*[a-z0-9])?)$ + type: string + kind: + description: |- + Kind of the resource being referenced. + Kind must consist of alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name of the resource being referenced. + Name must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - apiVersion + - kind + - name + type: object + x-kubernetes-map-type: atomic + tacacsGroup: + description: TACACSGroup is the TACACS+ server group configuration. + properties: + name: + description: Name is the name of the TACACS+ server group. + maxLength: 63 + minLength: 1 + type: string + servers: + description: |- + Servers is the list of TACACS+ server addresses to include in this group. + The addresses must match addresses defined in TACACSServers. + items: + type: string + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-type: set + sourceInterface: + description: SourceInterface is the source interface to use for + communication with the TACACS+ servers. + maxLength: 63 + type: string + vrf: + description: VRF is the VRF to use for communication with the + TACACS+ servers. + maxLength: 63 + type: string + required: + - name + - servers + type: object + tacacsServers: + description: TACACSServers is the list of TACACS+ servers to configure. + items: + description: TACACSServer represents a TACACS+ server configuration. + properties: + address: + description: Address is the IP address or hostname of the TACACS+ + server. + maxLength: 253 + minLength: 1 + type: string + keyEncryption: + allOf: + - enum: + - Type6 + - Type7 + - Clear + - enum: + - Type6 + - Type7 + - Clear + default: Type7 + description: |- + KeyEncryption specifies the encryption type for the key. + Type7 is the Cisco Type 7 encryption (reversible). + Type6 is the AES encryption (more secure). + Clear means the key is sent in cleartext (not recommended). + type: string + keySecretRef: + description: |- + KeySecretRef is a reference to a secret containing the shared key for this TACACS+ server. + The secret must contain a key specified in the SecretKeySelector. + properties: + key: + description: |- + Key is the of the entry in the secret resource's `data` or `stringData` + field to be used. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is unique within a namespace to reference + a secret resource. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace defines the space within which the secret name must be unique. + If omitted, the namespace of the object being reconciled will be used. + maxLength: 63 + minLength: 1 + type: string + required: + - key + - name + type: object + x-kubernetes-map-type: atomic + port: + default: 49 + description: |- + Port is the TCP port of the TACACS+ server. + Defaults to 49 if not specified. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + timeout: + description: Timeout is the timeout in seconds for this TACACS+ + server. + format: int32 + maximum: 60 + minimum: 1 + type: integer + required: + - address + - keySecretRef + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - address + x-kubernetes-list-type: map + required: + - deviceRef + type: object + status: + description: |- + Status of the resource. This is set and updated automatically. + Read-only. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + properties: + conditions: + description: The conditions are a list of status objects that describe + the state of the AAA. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 65b7217d..daf31d26 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -28,20 +28,10 @@ rules: - list - update - watch -- apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - create - - delete - - get - - list - - update - - watch - apiGroups: - networking.metal.ironcore.dev resources: + - aaas - accesscontrollists - banners - bgp @@ -75,6 +65,7 @@ rules: - apiGroups: - networking.metal.ironcore.dev resources: + - aaas/finalizers - accesscontrollists/finalizers - banners/finalizers - bgp/finalizers @@ -102,6 +93,7 @@ rules: - apiGroups: - networking.metal.ironcore.dev resources: + - aaas/status - accesscontrollists/status - banners/status - bgp/status @@ -160,13 +152,3 @@ rules: - get - patch - update -- apiGroups: - - nx.cisco.networking.metal.ironcore.dev - resources: - - interfaceconfigs - - managementaccessconfigs - - networkvirtualizationedgeconfigs - verbs: - - get - - list - - watch diff --git a/config/samples/networking_v1alpha1_aaa.yaml b/config/samples/networking_v1alpha1_aaa.yaml new file mode 100644 index 00000000..1d281c0c --- /dev/null +++ b/config/samples/networking_v1alpha1_aaa.yaml @@ -0,0 +1,103 @@ +# Example AAA configuration with TACACS+ servers +# This configures: +# - feature tacacs+ +# - tacacs-server hosts with Type 7 encrypted keys +# - AAA group server for TACACS +# - AAA authentication, authorization, and accounting +--- +apiVersion: v1 +kind: Secret +metadata: + name: tacacs-server-keys + namespace: default +type: Opaque +stringData: + # Replace with your actual TACACS+ server shared secret + server-key: "supersecretkey" +--- +apiVersion: networking.metal.ironcore.dev/v1alpha1 +kind: AAA +metadata: + name: aaa-tacacs + namespace: default +spec: + # Reference to the Device this AAA configuration belongs to + deviceRef: + name: my-switch + + # TACACS+ server configuration + # Equivalent to: + # tacacs-server host 10.16.8.142 key 7 $TYPE_7_SECRET + # tacacs-server host 10.16.8.32 key 7 $TYPE_7_SECRET + # tacacs-server host 169.145.33.51 key 7 $TYPE_7_SECRET + tacacsServers: + - address: "10.16.8.142" + keySecretRef: + name: tacacs-server-keys + key: server-key + keyEncryption: Type7 + - address: "10.16.8.32" + keySecretRef: + name: tacacs-server-keys + key: server-key + keyEncryption: Type7 + - address: "169.145.33.51" + keySecretRef: + name: tacacs-server-keys + key: server-key + keyEncryption: Type7 + + # TACACS+ server group configuration + # Equivalent to: + # aaa group server tacacs+ GR_TACACS + # server 10.16.8.32 + # server 10.16.8.142 + # server 169.145.33.51 + # use-vrf management + # source-interface mgmt0 + tacacsGroup: + name: GR_TACACS + servers: + - "10.16.8.32" + - "10.16.8.142" + - "169.145.33.51" + vrf: management + sourceInterface: mgmt0 + + # AAA authentication configuration + # Equivalent to: + # aaa authentication login default group GR_TACACS + # aaa authentication login console group GR_TACACS local + # aaa authentication login error-enable + authentication: + login: + default: + methods: + - type: Group + groupName: GR_TACACS + console: + methods: + - type: Group + groupName: GR_TACACS + - type: Local + loginErrorEnable: true + + # AAA authorization configuration + # Equivalent to: + # aaa authorization config-commands default group GR_TACACS local + authorization: + configCommands: + default: + methods: + - type: Group + groupName: GR_TACACS + - type: Local + + # AAA accounting configuration + # Equivalent to: + # aaa accounting default group GR_TACACS + accounting: + default: + methods: + - type: Group + groupName: GR_TACACS diff --git a/internal/controller/core/aaa_controller.go b/internal/controller/core/aaa_controller.go new file mode 100644 index 00000000..17733f43 --- /dev/null +++ b/internal/controller/core/aaa_controller.go @@ -0,0 +1,332 @@ +// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package core + +import ( + "context" + "errors" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + kerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/client-go/tools/record" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + "github.com/ironcore-dev/network-operator/api/core/v1alpha1" + "github.com/ironcore-dev/network-operator/internal/clientutil" + "github.com/ironcore-dev/network-operator/internal/conditions" + "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/provider" + "github.com/ironcore-dev/network-operator/internal/resourcelock" +) + +// AAAReconciler reconciles a AAA object +type AAAReconciler struct { + client.Client + Scheme *runtime.Scheme + + // WatchFilterValue is the label value used to filter events prior to reconciliation. + WatchFilterValue string + + // Recorder is used to record events for the controller. + // More info: https://book.kubebuilder.io/reference/raising-events + Recorder record.EventRecorder + + // Provider is the driver that will be used to create & delete the AAA configuration. + Provider provider.ProviderFunc + + // Locker is used to synchronize operations on resources targeting the same device. + Locker *resourcelock.ResourceLocker +} + +// +kubebuilder:rbac:groups=networking.metal.ironcore.dev,resources=aaas,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=networking.metal.ironcore.dev,resources=aaas/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=networking.metal.ironcore.dev,resources=aaas/finalizers,verbs=update +// +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch +// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.2/pkg/reconcile +// +// For more details about the method shape, read up here: +// - https://ahmet.im/blog/controller-pitfalls/#reconcile-method-shape +func (r *AAAReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { + log := ctrl.LoggerFrom(ctx) + log.Info("Reconciling resource") + + obj := new(v1alpha1.AAA) + if err := r.Get(ctx, req.NamespacedName, obj); err != nil { + if apierrors.IsNotFound(err) { + // If the custom resource is not found then it usually means that it was deleted or not created + // In this way, we will stop the reconciliation + log.Info("Resource not found. Ignoring since object must be deleted") + return ctrl.Result{}, nil + } + // Error reading the object - requeue the request. + log.Error(err, "Failed to get resource") + return ctrl.Result{}, err + } + + prov, ok := r.Provider().(provider.AAAProvider) + if !ok { + if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ + Type: v1alpha1.ReadyCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.NotImplementedReason, + Message: "Provider does not implement provider.AAAProvider", + }) { + return ctrl.Result{}, r.Status().Update(ctx, obj) + } + return ctrl.Result{}, nil + } + + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if err := r.Locker.AcquireLock(ctx, device.Name, "aaa-controller"); err != nil { + if errors.Is(err, resourcelock.ErrLockAlreadyHeld) { + log.Info("Device is already locked, requeuing reconciliation") + return ctrl.Result{RequeueAfter: time.Second * 5}, nil + } + log.Error(err, "Failed to acquire device lock") + return ctrl.Result{}, err + } + defer func() { + if err := r.Locker.ReleaseLock(ctx, device.Name, "aaa-controller"); err != nil { + log.Error(err, "Failed to release device lock") + reterr = kerrors.NewAggregate([]error{reterr, err}) + } + }() + + conn, err := deviceutil.GetDeviceConnection(ctx, r, device) + if err != nil { + return ctrl.Result{}, err + } + + var cfg *provider.ProviderConfig + if obj.Spec.ProviderConfigRef != nil { + cfg, err = provider.GetProviderConfig(ctx, r, obj.Namespace, obj.Spec.ProviderConfigRef) + if err != nil { + return ctrl.Result{}, err + } + } + + s := &aaaScope{ + Device: device, + AAA: obj, + Connection: conn, + ProviderConfig: cfg, + Provider: prov, + } + + if !obj.DeletionTimestamp.IsZero() { + if controllerutil.ContainsFinalizer(obj, v1alpha1.FinalizerName) { + if err := r.finalize(ctx, s); err != nil { + log.Error(err, "Failed to finalize resource") + return ctrl.Result{}, err + } + controllerutil.RemoveFinalizer(obj, v1alpha1.FinalizerName) + if err := r.Update(ctx, obj); err != nil { + log.Error(err, "Failed to remove finalizer from resource") + return ctrl.Result{}, err + } + } + log.Info("Resource is being deleted, skipping reconciliation") + return ctrl.Result{}, nil + } + + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers + if !controllerutil.ContainsFinalizer(obj, v1alpha1.FinalizerName) { + controllerutil.AddFinalizer(obj, v1alpha1.FinalizerName) + if err := r.Update(ctx, obj); err != nil { + log.Error(err, "Failed to add finalizer to resource") + return ctrl.Result{}, err + } + log.Info("Added finalizer to resource") + return ctrl.Result{}, nil + } + + orig := obj.DeepCopy() + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + log.Info("Initializing status conditions") + return ctrl.Result{}, r.Status().Update(ctx, obj) + } + + // Always attempt to update the metadata/status after reconciliation + defer func() { + if !equality.Semantic.DeepEqual(orig.ObjectMeta, obj.ObjectMeta) { + if err := r.Patch(ctx, obj, client.MergeFrom(orig)); err != nil { + log.Error(err, "Failed to update resource metadata") + reterr = kerrors.NewAggregate([]error{reterr, err}) + } + return + } + + if !equality.Semantic.DeepEqual(orig.Status, obj.Status) { + if err := r.Status().Patch(ctx, obj, client.MergeFrom(orig)); err != nil { + log.Error(err, "Failed to update status") + reterr = kerrors.NewAggregate([]error{reterr, err}) + } + } + }() + + if err := r.reconcile(ctx, s); err != nil { + log.Error(err, "Failed to reconcile resource") + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *AAAReconciler) SetupWithManager(mgr ctrl.Manager) error { + labelSelector := metav1.LabelSelector{} + if r.WatchFilterValue != "" { + labelSelector.MatchLabels = map[string]string{v1alpha1.WatchLabel: r.WatchFilterValue} + } + + filter, err := predicate.LabelSelectorPredicate(labelSelector) + if err != nil { + return fmt.Errorf("failed to create label selector predicate: %w", err) + } + + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.AAA{}). + Named("aaa"). + WithEventFilter(filter). + // Watches enqueues AAA for referenced Secret resources. + Watches( + &corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(r.secretToAAA), + builder.WithPredicates(predicate.GenerationChangedPredicate{}), + ). + Complete(r) +} + +// aaaScope holds the different objects that are read and used during the reconcile. +type aaaScope struct { + Device *v1alpha1.Device + AAA *v1alpha1.AAA + Connection *deviceutil.Connection + ProviderConfig *provider.ProviderConfig + Provider provider.AAAProvider +} + +func (r *AAAReconciler) reconcile(ctx context.Context, s *aaaScope) (reterr error) { + if s.AAA.Labels == nil { + s.AAA.Labels = make(map[string]string) + } + + s.AAA.Labels[v1alpha1.DeviceLabel] = s.Device.Name + + // Ensure the AAA is owned by the Device. + if !controllerutil.HasControllerReference(s.AAA) { + if err := controllerutil.SetOwnerReference(s.Device, s.AAA, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { + return err + } + } + + if err := s.Provider.Connect(ctx, s.Connection); err != nil { + return fmt.Errorf("failed to connect to provider: %w", err) + } + defer func() { + if err := s.Provider.Disconnect(ctx, s.Connection); err != nil { + reterr = kerrors.NewAggregate([]error{reterr, err}) + } + }() + + // Load TACACS+ server keys from secrets + c := clientutil.NewClient(r, s.AAA.Namespace) + tacacsKeys := make(map[string]string) + for _, server := range s.AAA.Spec.TACACSServers { + key, err := c.Secret(ctx, &server.KeySecretRef) + if err != nil { + return fmt.Errorf("failed to get TACACS+ key for server %s: %w", server.Address, err) + } + tacacsKeys[server.Address] = string(key) + } + + // Ensure the AAA is realized on the provider. + err := s.Provider.EnsureAAA(ctx, &provider.EnsureAAARequest{ + AAA: s.AAA, + ProviderConfig: s.ProviderConfig, + TACACSServerKeys: tacacsKeys, + }) + + cond := conditions.FromError(err) + // As this resource is configuration only, we use the Configured condition as top-level Ready condition. + cond.Type = v1alpha1.ReadyCondition + conditions.Set(s.AAA, cond) + + return err +} + +func (r *AAAReconciler) finalize(ctx context.Context, s *aaaScope) (reterr error) { + if err := s.Provider.Connect(ctx, s.Connection); err != nil { + return fmt.Errorf("failed to connect to provider: %w", err) + } + defer func() { + if err := s.Provider.Disconnect(ctx, s.Connection); err != nil { + reterr = kerrors.NewAggregate([]error{reterr, err}) + } + }() + + return s.Provider.DeleteAAA(ctx, &provider.DeleteAAARequest{ + AAA: s.AAA, + ProviderConfig: s.ProviderConfig, + }) +} + +// secretToAAA is a [handler.MapFunc] to be used to enqueue requests for reconciliation +// for an AAA to update when one of its referenced Secrets gets updated. +func (r *AAAReconciler) secretToAAA(ctx context.Context, obj client.Object) []ctrl.Request { + secret, ok := obj.(*corev1.Secret) + if !ok { + panic(fmt.Sprintf("Expected a Secret but got a %T", obj)) + } + + log := ctrl.LoggerFrom(ctx, "Secret", klog.KObj(secret)) + + aaas := new(v1alpha1.AAAList) + if err := r.List(ctx, aaas); err != nil { + log.Error(err, "Failed to list AAAs") + return nil + } + + requests := []ctrl.Request{} + for _, a := range aaas.Items { + // Check if any TACACS+ server references this secret + for _, server := range a.Spec.TACACSServers { + if server.KeySecretRef.Name == secret.Name && a.Namespace == secret.Namespace { + log.Info("Enqueuing AAA for reconciliation", "AAA", klog.KObj(&a)) + requests = append(requests, ctrl.Request{ + NamespacedName: client.ObjectKey{ + Name: a.Name, + Namespace: a.Namespace, + }, + }) + break // Only enqueue once per AAA + } + } + } + + return requests +} diff --git a/internal/provider/cisco/nxos/aaa.go b/internal/provider/cisco/nxos/aaa.go new file mode 100644 index 00000000..ff855525 --- /dev/null +++ b/internal/provider/cisco/nxos/aaa.go @@ -0,0 +1,186 @@ +// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package nxos + +import ( + "github.com/ironcore-dev/network-operator/api/core/v1alpha1" + "github.com/ironcore-dev/network-operator/internal/provider/cisco/gnmiext/v2" +) + +var ( + _ gnmiext.Configurable = (*TACACSFeature)(nil) + _ gnmiext.Configurable = (*TacacsPlusProvider)(nil) + _ gnmiext.Configurable = (*TacacsPlusProviderGroup)(nil) + _ gnmiext.Configurable = (*AAADefaultAuth)(nil) + _ gnmiext.Configurable = (*AAAConsoleAuth)(nil) + _ gnmiext.Configurable = (*AAADefaultAuthor)(nil) + _ gnmiext.Configurable = (*AAADefaultAcc)(nil) +) + +// TACACSFeature enables/disables the TACACS+ feature on NX-OS. +// Path: System/fm-items/tacacsplus-items/adminSt +type TACACSFeature string + +func (*TACACSFeature) XPath() string { + return "System/fm-items/tacacsplus-items/adminSt" +} + +const ( + TACACSFeatureEnabled TACACSFeature = "enabled" + TACACSFeatureDisabled TACACSFeature = "disabled" +) + +// TacacsPlusProvider represents a TACACS+ server host configuration. +// Path: System/userext-items/tacacsext-items/tacacsplusprovider-items/TacacsPlusProvider-list[name=
] +type TacacsPlusProvider struct { + Name string `json:"name"` + Port int32 `json:"port,omitempty"` + Key string `json:"key,omitempty"` + KeyEnc string `json:"keyEnc,omitempty"` + Timeout int32 `json:"timeout,omitempty"` + Retries int32 `json:"retries,omitempty"` + AuthProtocol string `json:"authProtocol,omitempty"` +} + +func (*TacacsPlusProvider) IsListItem() {} + +func (p *TacacsPlusProvider) XPath() string { + return "System/userext-items/tacacsext-items/tacacsplusprovider-items/TacacsPlusProvider-list[name=" + p.Name + "]" +} + +// TacacsPlusProviderGroup represents a TACACS+ server group configuration. +// Path: System/userext-items/tacacsext-items/tacacsplusprovidergroup-items/TacacsPlusProviderGroup-list[name=] +type TacacsPlusProviderGroup struct { + Name string `json:"name"` + Vrf string `json:"vrf,omitempty"` + SrcIf string `json:"srcIf,omitempty"` + Deadtime int32 `json:"deadtime,omitempty"` + ProviderRefItems TacacsPlusProviderGroupRefItems `json:"providerref-items,omitzero"` +} + +func (*TacacsPlusProviderGroup) IsListItem() {} + +func (g *TacacsPlusProviderGroup) XPath() string { + return "System/userext-items/tacacsext-items/tacacsplusprovidergroup-items/TacacsPlusProviderGroup-list[name=" + g.Name + "]" +} + +type TacacsPlusProviderGroupRefItems struct { + ProviderRefList gnmiext.List[string, *TacacsPlusProviderRef] `json:"ProviderRef-list,omitzero"` +} + +type TacacsPlusProviderRef struct { + Name string `json:"name"` +} + +func (r *TacacsPlusProviderRef) Key() string { return r.Name } + +// AAADefaultAuth represents AAA default authentication configuration. +// Path: System/userext-items/authrealm-items/defaultauth-items +type AAADefaultAuth struct { + Realm string `json:"realm,omitempty"` + ProviderGroup string `json:"providerGroup,omitempty"` + Fallback string `json:"fallback,omitempty"` + Local string `json:"local,omitempty"` + None string `json:"none,omitempty"` + ErrEn bool `json:"errEn,omitempty"` + AuthProtocol string `json:"authProtocol,omitempty"` +} + +func (*AAADefaultAuth) XPath() string { + return "System/userext-items/authrealm-items/defaultauth-items" +} + +// AAAConsoleAuth represents AAA console authentication configuration. +// Path: System/userext-items/authrealm-items/consoleauth-items +type AAAConsoleAuth struct { + Realm string `json:"realm,omitempty"` + ProviderGroup string `json:"providerGroup,omitempty"` + Fallback string `json:"fallback,omitempty"` + Local string `json:"local,omitempty"` + None string `json:"none,omitempty"` + ErrEn bool `json:"errEn,omitempty"` + AuthProtocol string `json:"authProtocol,omitempty"` +} + +func (*AAAConsoleAuth) XPath() string { + return "System/userext-items/authrealm-items/consoleauth-items" +} + +// AAADefaultAuthor represents AAA default authorization configuration for config commands. +// Path: System/userext-items/authrealm-items/defaultauthor-items/DefaultAuthor-list[cmdType=config] +type AAADefaultAuthor struct { + Name string `json:"name"` + CmdType string `json:"cmdType"` + Realm string `json:"realm,omitempty"` + ProviderGroup string `json:"providerGroup,omitempty"` + LocalRbac bool `json:"localRbac,omitempty"` + AuthorMethodNone bool `json:"authorMethodNone,omitempty"` +} + +func (*AAADefaultAuthor) IsListItem() {} + +func (a *AAADefaultAuthor) XPath() string { + return "System/userext-items/authrealm-items/defaultauthor-items/DefaultAuthor-list[cmdType=" + a.CmdType + "]" +} + +// AAADefaultAcc represents AAA default accounting configuration. +// Path: System/userext-items/authrealm-items/defaultacc-items +type AAADefaultAcc struct { + Name string `json:"name,omitempty"` + Realm string `json:"realm,omitempty"` + ProviderGroup string `json:"providerGroup,omitempty"` + LocalRbac bool `json:"localRbac,omitempty"` + AccMethodNone bool `json:"accMethodNone,omitempty"` +} + +func (*AAADefaultAcc) XPath() string { + return "System/userext-items/authrealm-items/defaultacc-items" +} + +// MapKeyEncryption maps the API key encryption type to NX-OS type. +func MapKeyEncryption(enc v1alpha1.TACACSKeyEncryption) string { + switch enc { + case v1alpha1.TACACSKeyEncryptionType6: + return "6" + case v1alpha1.TACACSKeyEncryptionType7: + return "7" + case v1alpha1.TACACSKeyEncryptionClear: + return "0" + default: + return "7" + } +} + +// MapRealmFromMethodType maps the API method type to NX-OS realm. +func MapRealmFromMethodType(method v1alpha1.AAAMethodType, groupName string) string { + switch method { + case v1alpha1.AAAMethodTypeGroup: + return "tacacs" + case v1alpha1.AAAMethodTypeLocal: + return "local" + case v1alpha1.AAAMethodTypeNone: + return "none" + default: + return "local" + } +} + +// MapLocalFromMethodList checks if local is in the method list. +func MapLocalFromMethodList(methods []v1alpha1.AAAMethod) string { + for _, m := range methods { + if m.Type == v1alpha1.AAAMethodTypeLocal { + return "yes" + } + } + return "no" +} + +// MapFallbackFromMethodList determines fallback setting from method list. +func MapFallbackFromMethodList(methods []v1alpha1.AAAMethod) string { + // If there's more than one method, enable fallback + if len(methods) > 1 { + return "yes" + } + return "no" +} diff --git a/internal/provider/cisco/nxos/provider.go b/internal/provider/cisco/nxos/provider.go index aad3b9c3..31134cd6 100644 --- a/internal/provider/cisco/nxos/provider.go +++ b/internal/provider/cisco/nxos/provider.go @@ -56,6 +56,7 @@ var ( _ provider.VLANProvider = (*Provider)(nil) _ provider.VRFProvider = (*Provider)(nil) _ provider.NVEProvider = (*Provider)(nil) + _ provider.AAAProvider = (*Provider)(nil) ) type Provider struct { @@ -2681,6 +2682,197 @@ func separateFeatureActivation(conf []gnmiext.Configurable) (features, others [] return fa, conf[:n:n] } +func (p *Provider) EnsureAAA(ctx context.Context, req *provider.EnsureAAARequest) error { + var conf []gnmiext.Configurable + + // Enable TACACS+ feature if there are TACACS servers configured + if len(req.AAA.Spec.TACACSServers) > 0 { + tacacsFeature := TACACSFeatureEnabled + conf = append(conf, &tacacsFeature) + } + + // Configure TACACS+ server hosts + for _, server := range req.AAA.Spec.TACACSServers { + provider := &TacacsPlusProvider{ + Name: server.Address, + Port: server.Port, + KeyEnc: MapKeyEncryption(server.KeyEncryption), + } + if key, ok := req.TACACSServerKeys[server.Address]; ok { + provider.Key = key + } + if server.Timeout != nil { + provider.Timeout = *server.Timeout + } + conf = append(conf, provider) + } + + // Configure TACACS+ server group + if req.AAA.Spec.TACACSGroup != nil { + group := &TacacsPlusProviderGroup{ + Name: req.AAA.Spec.TACACSGroup.Name, + Vrf: req.AAA.Spec.TACACSGroup.VRF, + SrcIf: req.AAA.Spec.TACACSGroup.SourceInterface, + } + for _, serverAddr := range req.AAA.Spec.TACACSGroup.Servers { + group.ProviderRefItems.ProviderRefList.Set(&TacacsPlusProviderRef{Name: serverAddr}) + } + conf = append(conf, group) + } + + // Configure AAA default authentication + if req.AAA.Spec.Authentication != nil && req.AAA.Spec.Authentication.Login != nil { + if req.AAA.Spec.Authentication.Login.Default != nil && len(req.AAA.Spec.Authentication.Login.Default.Methods) > 0 { + methods := req.AAA.Spec.Authentication.Login.Default.Methods + authen := &AAADefaultAuth{ + ErrEn: req.AAA.Spec.Authentication.LoginErrorEnable, + Fallback: MapFallbackFromMethodList(methods), + Local: MapLocalFromMethodList(methods), + } + // Set realm and provider group based on first method + if methods[0].Type == v1alpha1.AAAMethodTypeGroup { + authen.Realm = "tacacs" + authen.ProviderGroup = methods[0].GroupName + } else { + authen.Realm = MapRealmFromMethodType(methods[0].Type, "") + } + conf = append(conf, authen) + } + + // Configure AAA console authentication + if req.AAA.Spec.Authentication.Login.Console != nil && len(req.AAA.Spec.Authentication.Login.Console.Methods) > 0 { + methods := req.AAA.Spec.Authentication.Login.Console.Methods + consoleAuth := &AAAConsoleAuth{ + ErrEn: req.AAA.Spec.Authentication.LoginErrorEnable, + Fallback: MapFallbackFromMethodList(methods), + Local: MapLocalFromMethodList(methods), + } + if methods[0].Type == v1alpha1.AAAMethodTypeGroup { + consoleAuth.Realm = "tacacs" + consoleAuth.ProviderGroup = methods[0].GroupName + } else { + consoleAuth.Realm = MapRealmFromMethodType(methods[0].Type, "") + } + conf = append(conf, consoleAuth) + } + } + + // Configure AAA authorization for config commands + if req.AAA.Spec.Authorization != nil && req.AAA.Spec.Authorization.ConfigCommands != nil { + if req.AAA.Spec.Authorization.ConfigCommands.Default != nil && len(req.AAA.Spec.Authorization.ConfigCommands.Default.Methods) > 0 { + methods := req.AAA.Spec.Authorization.ConfigCommands.Default.Methods + author := &AAADefaultAuthor{ + Name: "Author", + CmdType: "config", + LocalRbac: MapLocalFromMethodList(methods) == "yes", + } + if methods[0].Type == v1alpha1.AAAMethodTypeGroup { + author.Realm = "tacacs" + author.ProviderGroup = methods[0].GroupName + } else { + author.Realm = MapRealmFromMethodType(methods[0].Type, "") + } + conf = append(conf, author) + } + } + + // Configure AAA accounting + if req.AAA.Spec.Accounting != nil && req.AAA.Spec.Accounting.Default != nil && len(req.AAA.Spec.Accounting.Default.Methods) > 0 { + methods := req.AAA.Spec.Accounting.Default.Methods + acct := &AAADefaultAcc{ + Name: "Accounting", + LocalRbac: MapLocalFromMethodList(methods) == "yes", + } + if methods[0].Type == v1alpha1.AAAMethodTypeGroup { + acct.Realm = "tacacs" + acct.ProviderGroup = methods[0].GroupName + } else { + acct.Realm = MapRealmFromMethodType(methods[0].Type, "") + } + conf = append(conf, acct) + } + + return p.Patch(ctx, conf...) +} + +func (p *Provider) DeleteAAA(ctx context.Context, req *provider.DeleteAAARequest) error { + // Reset AAA accounting to local + if req.AAA.Spec.Accounting != nil && req.AAA.Spec.Accounting.Default != nil { + acct := &AAADefaultAcc{ + Name: "Accounting", + Realm: "local", + LocalRbac: true, + } + if err := p.Patch(ctx, acct); err != nil { + return err + } + } + + // Reset AAA authorization to local + if req.AAA.Spec.Authorization != nil && req.AAA.Spec.Authorization.ConfigCommands != nil { + author := &AAADefaultAuthor{ + Name: "Author", + CmdType: "config", + Realm: "local", + LocalRbac: true, + } + if err := p.Patch(ctx, author); err != nil { + return err + } + } + + // Reset AAA authentication to local + if req.AAA.Spec.Authentication != nil { + authen := &AAADefaultAuth{ + Realm: "local", + Local: "yes", + Fallback: "yes", + ErrEn: false, + } + if err := p.Patch(ctx, authen); err != nil { + return err + } + + if req.AAA.Spec.Authentication.Login != nil && req.AAA.Spec.Authentication.Login.Console != nil { + consoleAuth := &AAAConsoleAuth{ + Realm: "local", + Local: "yes", + Fallback: "yes", + ErrEn: false, + } + if err := p.Patch(ctx, consoleAuth); err != nil { + return err + } + } + } + + // Delete TACACS+ server group + if req.AAA.Spec.TACACSGroup != nil { + group := &TacacsPlusProviderGroup{Name: req.AAA.Spec.TACACSGroup.Name} + if err := p.client.Delete(ctx, group); err != nil { + return err + } + } + + // Delete TACACS+ server hosts + for _, server := range req.AAA.Spec.TACACSServers { + provider := &TacacsPlusProvider{Name: server.Address} + if err := p.client.Delete(ctx, provider); err != nil { + return err + } + } + + // Disable TACACS+ feature if no servers remain + if len(req.AAA.Spec.TACACSServers) > 0 { + tacacsFeature := TACACSFeatureDisabled + if err := p.Patch(ctx, &tacacsFeature); err != nil { + return err + } + } + + return nil +} + func init() { provider.Register("cisco-nxos-gnmi", NewProvider) } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index a1ac2a02..b8da57af 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -564,6 +564,29 @@ type NVEProvider interface { GetNVEStatus(context.Context, *NVERequest) (NVEStatus, error) } +// AAAProvider is the interface for the realization of the AAA objects over different providers. +type AAAProvider interface { + Provider + + // EnsureAAA call is responsible for AAA realization on the provider. + EnsureAAA(context.Context, *EnsureAAARequest) error + // DeleteAAA call is responsible for AAA deletion on the provider. + DeleteAAA(context.Context, *DeleteAAARequest) error +} + +type EnsureAAARequest struct { + AAA *v1alpha1.AAA + ProviderConfig *ProviderConfig + // TACACSServerKeys contains the decrypted keys for each TACACS+ server, + // keyed by server address. + TACACSServerKeys map[string]string +} + +type DeleteAAARequest struct { + AAA *v1alpha1.AAA + ProviderConfig *ProviderConfig +} + type NVERequest struct { NVE *v1alpha1.NetworkVirtualizationEdge SourceInterface *v1alpha1.Interface From 202f24ada44206792ce31d699defb3ea368d8728 Mon Sep 17 00:00:00 2001 From: i521907 Date: Wed, 4 Feb 2026 21:35:08 -0500 Subject: [PATCH 2/5] Fix golangci-lint issues --- internal/provider/cisco/nxos/aaa.go | 35 +++++++++++++-------- internal/provider/cisco/nxos/provider.go | 40 ++++++++++++------------ 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/internal/provider/cisco/nxos/aaa.go b/internal/provider/cisco/nxos/aaa.go index ff855525..09474f70 100644 --- a/internal/provider/cisco/nxos/aaa.go +++ b/internal/provider/cisco/nxos/aaa.go @@ -31,6 +31,15 @@ const ( TACACSFeatureDisabled TACACSFeature = "disabled" ) +// AAA configuration constants +const ( + AAARealmTacacs = "tacacs" + AAARealmLocal = "local" + AAARealmNone = "none" + AAAValueYes = "yes" + AAAValueNo = "no" +) + // TacacsPlusProvider represents a TACACS+ server host configuration. // Path: System/userext-items/tacacsext-items/tacacsplusprovider-items/TacacsPlusProvider-list[name=
] type TacacsPlusProvider struct { @@ -52,11 +61,11 @@ func (p *TacacsPlusProvider) XPath() string { // TacacsPlusProviderGroup represents a TACACS+ server group configuration. // Path: System/userext-items/tacacsext-items/tacacsplusprovidergroup-items/TacacsPlusProviderGroup-list[name=] type TacacsPlusProviderGroup struct { - Name string `json:"name"` - Vrf string `json:"vrf,omitempty"` - SrcIf string `json:"srcIf,omitempty"` - Deadtime int32 `json:"deadtime,omitempty"` - ProviderRefItems TacacsPlusProviderGroupRefItems `json:"providerref-items,omitzero"` + Name string `json:"name"` + Vrf string `json:"vrf,omitempty"` + SrcIf string `json:"srcIf,omitempty"` + Deadtime int32 `json:"deadtime,omitempty"` + ProviderRefItems TacacsPlusProviderGroupRefItems `json:"providerref-items,omitzero"` } func (*TacacsPlusProviderGroup) IsListItem() {} @@ -156,13 +165,13 @@ func MapKeyEncryption(enc v1alpha1.TACACSKeyEncryption) string { func MapRealmFromMethodType(method v1alpha1.AAAMethodType, groupName string) string { switch method { case v1alpha1.AAAMethodTypeGroup: - return "tacacs" + return AAARealmTacacs case v1alpha1.AAAMethodTypeLocal: - return "local" + return AAARealmLocal case v1alpha1.AAAMethodTypeNone: - return "none" + return AAARealmNone default: - return "local" + return AAARealmLocal } } @@ -170,17 +179,17 @@ func MapRealmFromMethodType(method v1alpha1.AAAMethodType, groupName string) str func MapLocalFromMethodList(methods []v1alpha1.AAAMethod) string { for _, m := range methods { if m.Type == v1alpha1.AAAMethodTypeLocal { - return "yes" + return AAAValueYes } } - return "no" + return AAAValueNo } // MapFallbackFromMethodList determines fallback setting from method list. func MapFallbackFromMethodList(methods []v1alpha1.AAAMethod) string { // If there's more than one method, enable fallback if len(methods) > 1 { - return "yes" + return AAAValueYes } - return "no" + return AAAValueNo } diff --git a/internal/provider/cisco/nxos/provider.go b/internal/provider/cisco/nxos/provider.go index 31134cd6..2f0c8999 100644 --- a/internal/provider/cisco/nxos/provider.go +++ b/internal/provider/cisco/nxos/provider.go @@ -2693,18 +2693,18 @@ func (p *Provider) EnsureAAA(ctx context.Context, req *provider.EnsureAAARequest // Configure TACACS+ server hosts for _, server := range req.AAA.Spec.TACACSServers { - provider := &TacacsPlusProvider{ + srv := &TacacsPlusProvider{ Name: server.Address, Port: server.Port, KeyEnc: MapKeyEncryption(server.KeyEncryption), } if key, ok := req.TACACSServerKeys[server.Address]; ok { - provider.Key = key + srv.Key = key } if server.Timeout != nil { - provider.Timeout = *server.Timeout + srv.Timeout = *server.Timeout } - conf = append(conf, provider) + conf = append(conf, srv) } // Configure TACACS+ server group @@ -2731,7 +2731,7 @@ func (p *Provider) EnsureAAA(ctx context.Context, req *provider.EnsureAAARequest } // Set realm and provider group based on first method if methods[0].Type == v1alpha1.AAAMethodTypeGroup { - authen.Realm = "tacacs" + authen.Realm = AAARealmTacacs authen.ProviderGroup = methods[0].GroupName } else { authen.Realm = MapRealmFromMethodType(methods[0].Type, "") @@ -2748,7 +2748,7 @@ func (p *Provider) EnsureAAA(ctx context.Context, req *provider.EnsureAAARequest Local: MapLocalFromMethodList(methods), } if methods[0].Type == v1alpha1.AAAMethodTypeGroup { - consoleAuth.Realm = "tacacs" + consoleAuth.Realm = AAARealmTacacs consoleAuth.ProviderGroup = methods[0].GroupName } else { consoleAuth.Realm = MapRealmFromMethodType(methods[0].Type, "") @@ -2764,10 +2764,10 @@ func (p *Provider) EnsureAAA(ctx context.Context, req *provider.EnsureAAARequest author := &AAADefaultAuthor{ Name: "Author", CmdType: "config", - LocalRbac: MapLocalFromMethodList(methods) == "yes", + LocalRbac: MapLocalFromMethodList(methods) == AAAValueYes, } if methods[0].Type == v1alpha1.AAAMethodTypeGroup { - author.Realm = "tacacs" + author.Realm = AAARealmTacacs author.ProviderGroup = methods[0].GroupName } else { author.Realm = MapRealmFromMethodType(methods[0].Type, "") @@ -2781,10 +2781,10 @@ func (p *Provider) EnsureAAA(ctx context.Context, req *provider.EnsureAAARequest methods := req.AAA.Spec.Accounting.Default.Methods acct := &AAADefaultAcc{ Name: "Accounting", - LocalRbac: MapLocalFromMethodList(methods) == "yes", + LocalRbac: MapLocalFromMethodList(methods) == AAAValueYes, } if methods[0].Type == v1alpha1.AAAMethodTypeGroup { - acct.Realm = "tacacs" + acct.Realm = AAARealmTacacs acct.ProviderGroup = methods[0].GroupName } else { acct.Realm = MapRealmFromMethodType(methods[0].Type, "") @@ -2800,7 +2800,7 @@ func (p *Provider) DeleteAAA(ctx context.Context, req *provider.DeleteAAARequest if req.AAA.Spec.Accounting != nil && req.AAA.Spec.Accounting.Default != nil { acct := &AAADefaultAcc{ Name: "Accounting", - Realm: "local", + Realm: AAARealmLocal, LocalRbac: true, } if err := p.Patch(ctx, acct); err != nil { @@ -2813,7 +2813,7 @@ func (p *Provider) DeleteAAA(ctx context.Context, req *provider.DeleteAAARequest author := &AAADefaultAuthor{ Name: "Author", CmdType: "config", - Realm: "local", + Realm: AAARealmLocal, LocalRbac: true, } if err := p.Patch(ctx, author); err != nil { @@ -2824,9 +2824,9 @@ func (p *Provider) DeleteAAA(ctx context.Context, req *provider.DeleteAAARequest // Reset AAA authentication to local if req.AAA.Spec.Authentication != nil { authen := &AAADefaultAuth{ - Realm: "local", - Local: "yes", - Fallback: "yes", + Realm: AAARealmLocal, + Local: AAAValueYes, + Fallback: AAAValueYes, ErrEn: false, } if err := p.Patch(ctx, authen); err != nil { @@ -2835,9 +2835,9 @@ func (p *Provider) DeleteAAA(ctx context.Context, req *provider.DeleteAAARequest if req.AAA.Spec.Authentication.Login != nil && req.AAA.Spec.Authentication.Login.Console != nil { consoleAuth := &AAAConsoleAuth{ - Realm: "local", - Local: "yes", - Fallback: "yes", + Realm: AAARealmLocal, + Local: AAAValueYes, + Fallback: AAAValueYes, ErrEn: false, } if err := p.Patch(ctx, consoleAuth); err != nil { @@ -2856,8 +2856,8 @@ func (p *Provider) DeleteAAA(ctx context.Context, req *provider.DeleteAAARequest // Delete TACACS+ server hosts for _, server := range req.AAA.Spec.TACACSServers { - provider := &TacacsPlusProvider{Name: server.Address} - if err := p.client.Delete(ctx, provider); err != nil { + srv := &TacacsPlusProvider{Name: server.Address} + if err := p.client.Delete(ctx, srv); err != nil { return err } } From fa37d502fe18487858edce4882a1182fd007c211 Mon Sep 17 00:00:00 2001 From: i521907 Date: Tue, 17 Feb 2026 18:35:43 -0500 Subject: [PATCH 3/5] Restructure AAA API to OpenConfig system/aaa model --- api/cisco/nx/v1alpha1/aaaconfig_types.go | 101 ++++ .../nx/v1alpha1/zz_generated.deepcopy.go | 121 +++++ api/core/v1alpha1/aaa_types.go | 195 ++++--- api/core/v1alpha1/zz_generated.deepcopy.go | 183 +++---- .../networking.metal.ironcore.dev_aaas.yaml | 508 ++++++++---------- ...metal.ironcore.dev_accesscontrollists.yaml | 2 +- ...networking.metal.ironcore.dev_banners.yaml | 2 +- .../networking.metal.ironcore.dev_bgp.yaml | 2 +- ...etworking.metal.ironcore.dev_bgppeers.yaml | 2 +- ...rking.metal.ironcore.dev_certificates.yaml | 2 +- ...networking.metal.ironcore.dev_devices.yaml | 2 +- .../networking.metal.ironcore.dev_dns.yaml | 2 +- ...king.metal.ironcore.dev_evpninstances.yaml | 2 +- ...working.metal.ironcore.dev_interfaces.yaml | 2 +- .../networking.metal.ironcore.dev_isis.yaml | 2 +- ...metal.ironcore.dev_managementaccesses.yaml | 2 +- ...oncore.dev_networkvirtualizationedges.yaml | 2 +- .../networking.metal.ironcore.dev_ntp.yaml | 2 +- .../networking.metal.ironcore.dev_ospf.yaml | 2 +- .../networking.metal.ironcore.dev_pim.yaml | 2 +- ...working.metal.ironcore.dev_prefixsets.yaml | 2 +- ...ng.metal.ironcore.dev_routingpolicies.yaml | 2 +- .../networking.metal.ironcore.dev_snmp.yaml | 2 +- ...networking.metal.ironcore.dev_syslogs.yaml | 2 +- .../networking.metal.ironcore.dev_users.yaml | 2 +- .../networking.metal.ironcore.dev_vlans.yaml | 2 +- .../networking.metal.ironcore.dev_vrfs.yaml | 2 +- ...working.metal.ironcore.dev_aaaconfigs.yaml | 136 +++++ ...ing.metal.ironcore.dev_bordergateways.yaml | 2 +- ...g.metal.ironcore.dev_interfaceconfigs.yaml | 2 +- ....ironcore.dev_managementaccessconfigs.yaml | 2 +- ....dev_networkvirtualizationedgeconfigs.yaml | 2 +- ...networking.metal.ironcore.dev_systems.yaml | 2 +- ...working.metal.ironcore.dev_vpcdomains.yaml | 2 +- config/default/kustomization.yaml | 2 +- config/rbac/role.yaml | 22 + config/samples/networking_v1alpha1_aaa.yaml | 141 +++-- internal/controller/core/aaa_controller.go | 42 +- internal/provider/cisco/nxos/aaa.go | 57 +- internal/provider/cisco/nxos/provider.go | 266 ++++----- 40 files changed, 1085 insertions(+), 745 deletions(-) create mode 100644 api/cisco/nx/v1alpha1/aaaconfig_types.go create mode 100644 config/crd/bases/nx.cisco.networking.metal.ironcore.dev_aaaconfigs.yaml diff --git a/api/cisco/nx/v1alpha1/aaaconfig_types.go b/api/cisco/nx/v1alpha1/aaaconfig_types.go new file mode 100644 index 00000000..160da1e6 --- /dev/null +++ b/api/cisco/nx/v1alpha1/aaaconfig_types.go @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: 2025 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" + + "github.com/ironcore-dev/network-operator/api/core/v1alpha1" +) + +// +kubebuilder:rbac:groups=nx.cisco.networking.metal.ironcore.dev,resources=aaaconfigs,verbs=get;list;watch + +// AAAConfigSpec defines the desired state of AAAConfig +type AAAConfigSpec struct { + // LoginErrorEnable enables login error messages (NX-OS specific). + // Maps to: aaa authentication login error-enable + // +optional + LoginErrorEnable bool `json:"loginErrorEnable,omitempty"` + + // KeyEncryption specifies the default encryption type for TACACS+ keys. + // +kubebuilder:validation:Enum=Type6;Type7;Clear + // +kubebuilder:default=Type7 + KeyEncryption TACACSKeyEncryption `json:"keyEncryption,omitempty"` + + // ConsoleAuthentication defines NX-OS console-specific authentication methods. + // Maps to: aaa authentication login console + // +optional + ConsoleAuthentication *NXOSMethodList `json:"consoleAuthentication,omitempty"` + + // ConfigCommandsAuthorization defines NX-OS config-commands authorization methods. + // Maps to: aaa authorization config-commands default + // +optional + ConfigCommandsAuthorization *NXOSMethodList `json:"configCommandsAuthorization,omitempty"` +} + +// TACACSKeyEncryption defines the encryption type for TACACS+ server keys. +// +kubebuilder:validation:Enum=Type6;Type7;Clear +type TACACSKeyEncryption string + +const ( + // TACACSKeyEncryptionType6 uses AES encryption (more secure). + TACACSKeyEncryptionType6 TACACSKeyEncryption = "Type6" + // TACACSKeyEncryptionType7 uses Cisco Type 7 encryption (reversible). + TACACSKeyEncryptionType7 TACACSKeyEncryption = "Type7" + // TACACSKeyEncryptionClear sends the key in cleartext. + TACACSKeyEncryptionClear TACACSKeyEncryption = "Clear" +) + +// NXOSMethodList defines an ordered list of AAA methods for NX-OS specific contexts. +type NXOSMethodList struct { + // Methods is the ordered list of methods. + // +required + // +listType=atomic + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=4 + Methods []NXOSMethod `json:"methods"` +} + +// NXOSMethod represents a single AAA method in an NX-OS context. +type NXOSMethod struct { + // Type is the method type. + // +required + // +kubebuilder:validation:Enum=Group;Local;None + Type string `json:"type"` + + // GroupName is the server group name when Type is Group. + // +optional + // +kubebuilder:validation:MaxLength=63 + GroupName string `json:"groupName,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=aaaconfigs +// +kubebuilder:resource:singular=aaaconfig +// +kubebuilder:resource:shortName=nxaaa + +// AAAConfig is the Schema for the aaaconfigs API +type AAAConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Specification of the desired state of the resource. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +required + Spec AAAConfigSpec `json:"spec"` +} + +// +kubebuilder:object:root=true + +// AAAConfigList contains a list of AAAConfig +type AAAConfigList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []AAAConfig `json:"items"` +} + +func init() { + v1alpha1.RegisterAAADependency(GroupVersion.WithKind("AAAConfig")) + SchemeBuilder.Register(&AAAConfig{}, &AAAConfigList{}) +} diff --git a/api/cisco/nx/v1alpha1/zz_generated.deepcopy.go b/api/cisco/nx/v1alpha1/zz_generated.deepcopy.go index f1f8050e..291062c2 100644 --- a/api/cisco/nx/v1alpha1/zz_generated.deepcopy.go +++ b/api/cisco/nx/v1alpha1/zz_generated.deepcopy.go @@ -1,5 +1,8 @@ //go:build !ignore_autogenerated +// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + // Code generated by controller-gen. DO NOT EDIT. package v1alpha1 @@ -10,6 +13,89 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAAConfig) DeepCopyInto(out *AAAConfig) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAConfig. +func (in *AAAConfig) DeepCopy() *AAAConfig { + if in == nil { + return nil + } + out := new(AAAConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AAAConfig) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAAConfigList) DeepCopyInto(out *AAAConfigList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]AAAConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAConfigList. +func (in *AAAConfigList) DeepCopy() *AAAConfigList { + if in == nil { + return nil + } + out := new(AAAConfigList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AAAConfigList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAAConfigSpec) DeepCopyInto(out *AAAConfigSpec) { + *out = *in + if in.ConsoleAuthentication != nil { + in, out := &in.ConsoleAuthentication, &out.ConsoleAuthentication + *out = new(NXOSMethodList) + (*in).DeepCopyInto(*out) + } + if in.ConfigCommandsAuthorization != nil { + in, out := &in.ConfigCommandsAuthorization, &out.ConfigCommandsAuthorization + *out = new(NXOSMethodList) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAConfigSpec. +func (in *AAAConfigSpec) DeepCopy() *AAAConfigSpec { + if in == nil { + return nil + } + out := new(AAAConfigSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AutoRecovery) DeepCopyInto(out *AutoRecovery) { *out = *in @@ -395,6 +481,41 @@ func (in *ManagementAccessConfigSpec) DeepCopy() *ManagementAccessConfigSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NXOSMethod) DeepCopyInto(out *NXOSMethod) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NXOSMethod. +func (in *NXOSMethod) DeepCopy() *NXOSMethod { + if in == nil { + return nil + } + out := new(NXOSMethod) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NXOSMethodList) DeepCopyInto(out *NXOSMethodList) { + *out = *in + if in.Methods != nil { + in, out := &in.Methods, &out.Methods + *out = make([]NXOSMethod, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NXOSMethodList. +func (in *NXOSMethodList) DeepCopy() *NXOSMethodList { + if in == nil { + return nil + } + out := new(NXOSMethodList) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkVirtualizationEdgeConfig) DeepCopyInto(out *NetworkVirtualizationEdgeConfig) { *out = *in diff --git a/api/core/v1alpha1/aaa_types.go b/api/core/v1alpha1/aaa_types.go index 6a46e444..7fa0e50a 100644 --- a/api/core/v1alpha1/aaa_types.go +++ b/api/core/v1alpha1/aaa_types.go @@ -10,10 +10,11 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) -// AAASpec defines the desired state of AAA +// AAASpec defines the desired state of AAA. // // It models the Authentication, Authorization, and Accounting (AAA) configuration on a network device, -// including TACACS+ server configuration and AAA group/method settings. +// aligned with the OpenConfig system/aaa YANG model. +// +kubebuilder:validation:XValidation:rule="has(self.serverGroups) || has(self.authentication) || has(self.authorization) || has(self.accounting)",message="at least one of serverGroups, authentication, authorization, or accounting must be set" type AAASpec struct { // DeviceName is the name of the Device this object belongs to. The Device object must exist in the same namespace. // Immutable. @@ -26,153 +27,141 @@ type AAASpec struct { // +optional ProviderConfigRef *TypedLocalObjectReference `json:"providerConfigRef,omitempty"` - // TACACSServers is the list of TACACS+ servers to configure. + // ServerGroups is the list of AAA server groups. + // OpenConfig: /system/aaa/server-groups/server-group // +optional // +listType=map - // +listMapKey=address - // +kubebuilder:validation:MaxItems=16 - TACACSServers []TACACSServer `json:"tacacsServers,omitempty"` + // +listMapKey=name + // +kubebuilder:validation:MaxItems=8 + ServerGroups []AAAServerGroup `json:"serverGroups,omitempty"` - // TACACSGroup is the TACACS+ server group configuration. - // +optional - TACACSGroup *TACACSGroup `json:"tacacsGroup,omitempty"` - - // Authentication defines the AAA authentication configuration. + // Authentication defines the AAA authentication method list. + // OpenConfig: /system/aaa/authentication // +optional Authentication *AAAAuthentication `json:"authentication,omitempty"` - // Authorization defines the AAA authorization configuration. + // Authorization defines the AAA authorization method list. + // OpenConfig: /system/aaa/authorization // +optional Authorization *AAAAuthorization `json:"authorization,omitempty"` - // Accounting defines the AAA accounting configuration. + // Accounting defines the AAA accounting method list. + // OpenConfig: /system/aaa/accounting // +optional Accounting *AAAAccounting `json:"accounting,omitempty"` } -// TACACSServer represents a TACACS+ server configuration. -type TACACSServer struct { - // Address is the IP address or hostname of the TACACS+ server. - // +required - // +kubebuilder:validation:MinLength=1 - // +kubebuilder:validation:MaxLength=253 - Address string `json:"address"` - - // Port is the TCP port of the TACACS+ server. - // Defaults to 49 if not specified. - // +optional - // +kubebuilder:validation:Minimum=1 - // +kubebuilder:validation:Maximum=65535 - // +kubebuilder:default=49 - Port int32 `json:"port,omitempty"` - - // KeySecretRef is a reference to a secret containing the shared key for this TACACS+ server. - // The secret must contain a key specified in the SecretKeySelector. - // +required - KeySecretRef SecretKeySelector `json:"keySecretRef"` - - // KeyEncryption specifies the encryption type for the key. - // Type7 is the Cisco Type 7 encryption (reversible). - // Type6 is the AES encryption (more secure). - // Clear means the key is sent in cleartext (not recommended). - // +optional - // +kubebuilder:validation:Enum=Type6;Type7;Clear - // +kubebuilder:default=Type7 - KeyEncryption TACACSKeyEncryption `json:"keyEncryption,omitempty"` - - // Timeout is the timeout in seconds for this TACACS+ server. - // +optional - // +kubebuilder:validation:Minimum=1 - // +kubebuilder:validation:Maximum=60 - Timeout *int32 `json:"timeout,omitempty"` -} - -// TACACSKeyEncryption defines the encryption type for TACACS+ server keys. -// +kubebuilder:validation:Enum=Type6;Type7;Clear -type TACACSKeyEncryption string +// AAAServerGroupType defines the protocol type of an AAA server group. +// +kubebuilder:validation:Enum=TACACS;RADIUS +type AAAServerGroupType string const ( - // TACACSKeyEncryptionType6 uses AES encryption (more secure). - TACACSKeyEncryptionType6 TACACSKeyEncryption = "Type6" - // TACACSKeyEncryptionType7 uses Cisco Type 7 encryption (reversible). - TACACSKeyEncryptionType7 TACACSKeyEncryption = "Type7" - // TACACSKeyEncryptionClear sends the key in cleartext. - TACACSKeyEncryptionClear TACACSKeyEncryption = "Clear" + // AAAServerGroupTypeTACACS is a TACACS+ server group. + AAAServerGroupTypeTACACS AAAServerGroupType = "TACACS" + // AAAServerGroupTypeRADIUS is a RADIUS server group. + AAAServerGroupTypeRADIUS AAAServerGroupType = "RADIUS" ) -// TACACSGroup represents a TACACS+ server group configuration. -type TACACSGroup struct { - // Name is the name of the TACACS+ server group. +// AAAServerGroup represents a named group of AAA servers. +// OpenConfig: /system/aaa/server-groups/server-group[name] +// +kubebuilder:validation:XValidation:rule="self.type != 'TACACS' || self.servers.all(s, has(s.tacacs))",message="servers in a TACACS group must have tacacs config" +type AAAServerGroup struct { + // Name is the name of the server group. // +required // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=63 Name string `json:"name"` - // Servers is the list of TACACS+ server addresses to include in this group. - // The addresses must match addresses defined in TACACSServers. + // Type is the protocol type of this server group. // +required - // +listType=set + Type AAAServerGroupType `json:"type"` + + // Servers is the list of servers in this group. + // OpenConfig: /system/aaa/server-groups/server-group/servers/server + // +required + // +listType=map + // +listMapKey=address // +kubebuilder:validation:MinItems=1 // +kubebuilder:validation:MaxItems=16 - Servers []string `json:"servers"` + Servers []AAAServer `json:"servers"` - // VRF is the VRF to use for communication with the TACACS+ servers. + // VrfName is the VRF to use for communication with the servers in this group. // +optional // +kubebuilder:validation:MaxLength=63 - VRF string `json:"vrf,omitempty"` + VrfName string `json:"vrfName,omitempty"` - // SourceInterface is the source interface to use for communication with the TACACS+ servers. + // SourceInterfaceName is the source interface to use for communication with the servers. // +optional // +kubebuilder:validation:MaxLength=63 - SourceInterface string `json:"sourceInterface,omitempty"` + SourceInterfaceName string `json:"sourceInterfaceName,omitempty"` } -// AAAAuthentication defines the AAA authentication configuration. -type AAAAuthentication struct { - // Login defines authentication methods for login. +// AAAServer represents a single AAA server within a group. +// OpenConfig: /system/aaa/server-groups/server-group/servers/server[address] +type AAAServer struct { + // Address is the IP address or hostname of the server. + // +required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + Address string `json:"address"` + + // Timeout is the response timeout in seconds for this server. // +optional - Login *AAAAuthenticationLogin `json:"login,omitempty"` + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=60 + Timeout *int32 `json:"timeout,omitempty"` - // LoginErrorEnable enables login error messages. + // TACACS contains TACACS+ specific server configuration. + // Required when the parent server group type is TACACS. + // OpenConfig augmentation: /system/aaa/server-groups/server-group/servers/server/tacacs // +optional - LoginErrorEnable bool `json:"loginErrorEnable,omitempty"` + TACACS *AAAServerTACACS `json:"tacacs,omitempty"` } -// AAAAuthenticationLogin defines the login authentication methods. -type AAAAuthenticationLogin struct { - // Default defines the default authentication method list. +// AAAServerTACACS contains TACACS+ specific server configuration. +type AAAServerTACACS struct { + // Port is the TCP port of the TACACS+ server. + // Defaults to 49 if not specified. // +optional - Default *AAAMethodList `json:"default,omitempty"` + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + // +kubebuilder:default=49 + Port int32 `json:"port,omitempty"` - // Console defines the console authentication method list. - // +optional - Console *AAAMethodList `json:"console,omitempty"` + // KeySecretRef is a reference to a secret containing the shared key for this TACACS+ server. + // The secret must contain a key specified in the SecretKeySelector. + // +required + KeySecretRef SecretKeySelector `json:"keySecretRef"` } -// AAAAuthorization defines the AAA authorization configuration. -type AAAAuthorization struct { - // ConfigCommands defines authorization for configuration commands. - // +optional - ConfigCommands *AAAAuthorizationConfigCommands `json:"configCommands,omitempty"` +// AAAAuthentication defines the AAA authentication method list. +// OpenConfig: /system/aaa/authentication +type AAAAuthentication struct { + // Methods is the ordered list of authentication methods. + // Methods are tried in order until one succeeds or all fail. + // +required + // +listType=atomic + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=4 + Methods []AAAMethod `json:"methods"` } -// AAAAuthorizationConfigCommands defines authorization for configuration commands. -type AAAAuthorizationConfigCommands struct { - // Default defines the default authorization method list. - // +optional - Default *AAAMethodList `json:"default,omitempty"` +// AAAAuthorization defines the AAA authorization method list. +// OpenConfig: /system/aaa/authorization +type AAAAuthorization struct { + // Methods is the ordered list of authorization methods. + // Methods are tried in order until one succeeds or all fail. + // +required + // +listType=atomic + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=4 + Methods []AAAMethod `json:"methods"` } -// AAAAccounting defines the AAA accounting configuration. +// AAAAccounting defines the AAA accounting method list. +// OpenConfig: /system/aaa/accounting type AAAAccounting struct { - // Default defines the default accounting method list. - // +optional - Default *AAAMethodList `json:"default,omitempty"` -} - -// AAAMethodList defines a list of AAA methods to try in order. -type AAAMethodList struct { - // Methods is the ordered list of authentication/authorization/accounting methods. + // Methods is the ordered list of accounting methods. // Methods are tried in order until one succeeds or all fail. // +required // +listType=atomic @@ -182,6 +171,7 @@ type AAAMethodList struct { } // AAAMethod represents an AAA method. +// +kubebuilder:validation:XValidation:rule="self.type != 'Group' || self.groupName != \"\"",message="groupName is required when type is Group" type AAAMethod struct { // Type is the type of AAA method. // +required @@ -224,7 +214,6 @@ type AAAStatus struct { // +kubebuilder:resource:singular=aaa // +kubebuilder:resource:shortName=aaa // +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name` -// +kubebuilder:printcolumn:name="TACACS Group",type=string,JSONPath=`.spec.tacacsGroup.name` // +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status` // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" diff --git a/api/core/v1alpha1/zz_generated.deepcopy.go b/api/core/v1alpha1/zz_generated.deepcopy.go index 17fd6de3..eb7af4df 100644 --- a/api/core/v1alpha1/zz_generated.deepcopy.go +++ b/api/core/v1alpha1/zz_generated.deepcopy.go @@ -1,5 +1,8 @@ //go:build !ignore_autogenerated +// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + // Code generated by controller-gen. DO NOT EDIT. package v1alpha1 @@ -39,10 +42,10 @@ func (in *AAA) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AAAAccounting) DeepCopyInto(out *AAAAccounting) { *out = *in - if in.Default != nil { - in, out := &in.Default, &out.Default - *out = new(AAAMethodList) - (*in).DeepCopyInto(*out) + if in.Methods != nil { + in, out := &in.Methods, &out.Methods + *out = make([]AAAMethod, len(*in)) + copy(*out, *in) } } @@ -59,10 +62,10 @@ func (in *AAAAccounting) DeepCopy() *AAAAccounting { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AAAAuthentication) DeepCopyInto(out *AAAAuthentication) { *out = *in - if in.Login != nil { - in, out := &in.Login, &out.Login - *out = new(AAAAuthenticationLogin) - (*in).DeepCopyInto(*out) + if in.Methods != nil { + in, out := &in.Methods, &out.Methods + *out = make([]AAAMethod, len(*in)) + copy(*out, *in) } } @@ -76,38 +79,13 @@ func (in *AAAAuthentication) DeepCopy() *AAAAuthentication { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AAAAuthenticationLogin) DeepCopyInto(out *AAAAuthenticationLogin) { - *out = *in - if in.Default != nil { - in, out := &in.Default, &out.Default - *out = new(AAAMethodList) - (*in).DeepCopyInto(*out) - } - if in.Console != nil { - in, out := &in.Console, &out.Console - *out = new(AAAMethodList) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAAuthenticationLogin. -func (in *AAAAuthenticationLogin) DeepCopy() *AAAAuthenticationLogin { - if in == nil { - return nil - } - out := new(AAAAuthenticationLogin) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AAAAuthorization) DeepCopyInto(out *AAAAuthorization) { *out = *in - if in.ConfigCommands != nil { - in, out := &in.ConfigCommands, &out.ConfigCommands - *out = new(AAAAuthorizationConfigCommands) - (*in).DeepCopyInto(*out) + if in.Methods != nil { + in, out := &in.Methods, &out.Methods + *out = make([]AAAMethod, len(*in)) + copy(*out, *in) } } @@ -121,26 +99,6 @@ func (in *AAAAuthorization) DeepCopy() *AAAAuthorization { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AAAAuthorizationConfigCommands) DeepCopyInto(out *AAAAuthorizationConfigCommands) { - *out = *in - if in.Default != nil { - in, out := &in.Default, &out.Default - *out = new(AAAMethodList) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAAuthorizationConfigCommands. -func (in *AAAAuthorizationConfigCommands) DeepCopy() *AAAAuthorizationConfigCommands { - if in == nil { - return nil - } - out := new(AAAAuthorizationConfigCommands) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AAAList) DeepCopyInto(out *AAAList) { *out = *in @@ -189,21 +147,64 @@ func (in *AAAMethod) DeepCopy() *AAAMethod { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AAAMethodList) DeepCopyInto(out *AAAMethodList) { +func (in *AAAServer) DeepCopyInto(out *AAAServer) { *out = *in - if in.Methods != nil { - in, out := &in.Methods, &out.Methods - *out = make([]AAAMethod, len(*in)) - copy(*out, *in) + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(int32) + **out = **in + } + if in.TACACS != nil { + in, out := &in.TACACS, &out.TACACS + *out = new(AAAServerTACACS) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAServer. +func (in *AAAServer) DeepCopy() *AAAServer { + if in == nil { + return nil } + out := new(AAAServer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAAServerGroup) DeepCopyInto(out *AAAServerGroup) { + *out = *in + if in.Servers != nil { + in, out := &in.Servers, &out.Servers + *out = make([]AAAServer, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAServerGroup. +func (in *AAAServerGroup) DeepCopy() *AAAServerGroup { + if in == nil { + return nil + } + out := new(AAAServerGroup) + in.DeepCopyInto(out) + return out } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAMethodList. -func (in *AAAMethodList) DeepCopy() *AAAMethodList { +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AAAServerTACACS) DeepCopyInto(out *AAAServerTACACS) { + *out = *in + out.KeySecretRef = in.KeySecretRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AAAServerTACACS. +func (in *AAAServerTACACS) DeepCopy() *AAAServerTACACS { if in == nil { return nil } - out := new(AAAMethodList) + out := new(AAAServerTACACS) in.DeepCopyInto(out) return out } @@ -217,18 +218,13 @@ func (in *AAASpec) DeepCopyInto(out *AAASpec) { *out = new(TypedLocalObjectReference) **out = **in } - if in.TACACSServers != nil { - in, out := &in.TACACSServers, &out.TACACSServers - *out = make([]TACACSServer, len(*in)) + if in.ServerGroups != nil { + in, out := &in.ServerGroups, &out.ServerGroups + *out = make([]AAAServerGroup, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.TACACSGroup != nil { - in, out := &in.TACACSGroup, &out.TACACSGroup - *out = new(TACACSGroup) - (*in).DeepCopyInto(*out) - } if in.Authentication != nil { in, out := &in.Authentication, &out.Authentication *out = new(AAAAuthentication) @@ -3496,47 +3492,6 @@ func (in *SyslogStatus) DeepCopy() *SyslogStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TACACSGroup) DeepCopyInto(out *TACACSGroup) { - *out = *in - if in.Servers != nil { - in, out := &in.Servers, &out.Servers - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TACACSGroup. -func (in *TACACSGroup) DeepCopy() *TACACSGroup { - if in == nil { - return nil - } - out := new(TACACSGroup) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TACACSServer) DeepCopyInto(out *TACACSServer) { - *out = *in - out.KeySecretRef = in.KeySecretRef - if in.Timeout != nil { - in, out := &in.Timeout, &out.Timeout - *out = new(int32) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TACACSServer. -func (in *TACACSServer) DeepCopy() *TACACSServer { - if in == nil { - return nil - } - out := new(TACACSServer) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLS) DeepCopyInto(out *TLS) { *out = *in diff --git a/config/crd/bases/networking.metal.ironcore.dev_aaas.yaml b/config/crd/bases/networking.metal.ironcore.dev_aaas.yaml index f2813fd6..ced5f156 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_aaas.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_aaas.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: aaas.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev @@ -20,9 +20,6 @@ spec: - jsonPath: .spec.deviceRef.name name: Device type: string - - jsonPath: .spec.tacacsGroup.name - name: TACACS Group - type: string - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string @@ -57,179 +54,130 @@ spec: More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status properties: accounting: - description: Accounting defines the AAA accounting configuration. + description: |- + Accounting defines the AAA accounting method list. + OpenConfig: /system/aaa/accounting properties: - default: - description: Default defines the default accounting method list. - properties: - methods: - description: |- - Methods is the ordered list of authentication/authorization/accounting methods. - Methods are tried in order until one succeeds or all fail. - items: - description: AAAMethod represents an AAA method. - properties: - groupName: - description: GroupName is the name of the server group - when Type is Group. - maxLength: 63 - type: string - type: - allOf: - - enum: - - Group - - Local - - None - - enum: - - Group - - Local - - None - description: Type is the type of AAA method. - type: string - required: - - type - type: object - maxItems: 4 - minItems: 1 - type: array - x-kubernetes-list-type: atomic - required: - - methods - type: object + methods: + description: |- + Methods is the ordered list of accounting methods. + Methods are tried in order until one succeeds or all fail. + items: + description: AAAMethod represents an AAA method. + properties: + groupName: + description: GroupName is the name of the server group when + Type is Group. + maxLength: 63 + type: string + type: + allOf: + - enum: + - Group + - Local + - None + - enum: + - Group + - Local + - None + description: Type is the type of AAA method. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: groupName is required when type is Group + rule: self.type != 'Group' || self.groupName != "" + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - methods type: object authentication: - description: Authentication defines the AAA authentication configuration. + description: |- + Authentication defines the AAA authentication method list. + OpenConfig: /system/aaa/authentication properties: - login: - description: Login defines authentication methods for login. - properties: - console: - description: Console defines the console authentication method - list. - properties: - methods: - description: |- - Methods is the ordered list of authentication/authorization/accounting methods. - Methods are tried in order until one succeeds or all fail. - items: - description: AAAMethod represents an AAA method. - properties: - groupName: - description: GroupName is the name of the server - group when Type is Group. - maxLength: 63 - type: string - type: - allOf: - - enum: - - Group - - Local - - None - - enum: - - Group - - Local - - None - description: Type is the type of AAA method. - type: string - required: - - type - type: object - maxItems: 4 - minItems: 1 - type: array - x-kubernetes-list-type: atomic - required: - - methods - type: object - default: - description: Default defines the default authentication method - list. - properties: - methods: - description: |- - Methods is the ordered list of authentication/authorization/accounting methods. - Methods are tried in order until one succeeds or all fail. - items: - description: AAAMethod represents an AAA method. - properties: - groupName: - description: GroupName is the name of the server - group when Type is Group. - maxLength: 63 - type: string - type: - allOf: - - enum: - - Group - - Local - - None - - enum: - - Group - - Local - - None - description: Type is the type of AAA method. - type: string - required: - - type - type: object - maxItems: 4 - minItems: 1 - type: array - x-kubernetes-list-type: atomic - required: - - methods - type: object - type: object - loginErrorEnable: - description: LoginErrorEnable enables login error messages. - type: boolean + methods: + description: |- + Methods is the ordered list of authentication methods. + Methods are tried in order until one succeeds or all fail. + items: + description: AAAMethod represents an AAA method. + properties: + groupName: + description: GroupName is the name of the server group when + Type is Group. + maxLength: 63 + type: string + type: + allOf: + - enum: + - Group + - Local + - None + - enum: + - Group + - Local + - None + description: Type is the type of AAA method. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: groupName is required when type is Group + rule: self.type != 'Group' || self.groupName != "" + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - methods type: object authorization: - description: Authorization defines the AAA authorization configuration. + description: |- + Authorization defines the AAA authorization method list. + OpenConfig: /system/aaa/authorization properties: - configCommands: - description: ConfigCommands defines authorization for configuration - commands. - properties: - default: - description: Default defines the default authorization method - list. - properties: - methods: - description: |- - Methods is the ordered list of authentication/authorization/accounting methods. - Methods are tried in order until one succeeds or all fail. - items: - description: AAAMethod represents an AAA method. - properties: - groupName: - description: GroupName is the name of the server - group when Type is Group. - maxLength: 63 - type: string - type: - allOf: - - enum: - - Group - - Local - - None - - enum: - - Group - - Local - - None - description: Type is the type of AAA method. - type: string - required: - - type - type: object - maxItems: 4 - minItems: 1 - type: array - x-kubernetes-list-type: atomic - required: - - methods - type: object - type: object + methods: + description: |- + Methods is the ordered list of authorization methods. + Methods are tried in order until one succeeds or all fail. + items: + description: AAAMethod represents an AAA method. + properties: + groupName: + description: GroupName is the name of the server group when + Type is Group. + maxLength: 63 + type: string + type: + allOf: + - enum: + - Group + - Local + - None + - enum: + - Group + - Local + - None + description: Type is the type of AAA method. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: groupName is required when type is Group + rule: self.type != 'Group' || self.groupName != "" + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - methods type: object deviceRef: description: |- @@ -284,124 +232,136 @@ spec: - name type: object x-kubernetes-map-type: atomic - tacacsGroup: - description: TACACSGroup is the TACACS+ server group configuration. - properties: - name: - description: Name is the name of the TACACS+ server group. - maxLength: 63 - minLength: 1 - type: string - servers: - description: |- - Servers is the list of TACACS+ server addresses to include in this group. - The addresses must match addresses defined in TACACSServers. - items: - type: string - maxItems: 16 - minItems: 1 - type: array - x-kubernetes-list-type: set - sourceInterface: - description: SourceInterface is the source interface to use for - communication with the TACACS+ servers. - maxLength: 63 - type: string - vrf: - description: VRF is the VRF to use for communication with the - TACACS+ servers. - maxLength: 63 - type: string - required: - - name - - servers - type: object - tacacsServers: - description: TACACSServers is the list of TACACS+ servers to configure. + serverGroups: + description: |- + ServerGroups is the list of AAA server groups. + OpenConfig: /system/aaa/server-groups/server-group items: - description: TACACSServer represents a TACACS+ server configuration. + description: |- + AAAServerGroup represents a named group of AAA servers. + OpenConfig: /system/aaa/server-groups/server-group[name] properties: - address: - description: Address is the IP address or hostname of the TACACS+ - server. - maxLength: 253 + name: + description: Name is the name of the server group. + maxLength: 63 minLength: 1 type: string - keyEncryption: - allOf: - - enum: - - Type6 - - Type7 - - Clear - - enum: - - Type6 - - Type7 - - Clear - default: Type7 + servers: description: |- - KeyEncryption specifies the encryption type for the key. - Type7 is the Cisco Type 7 encryption (reversible). - Type6 is the AES encryption (more secure). - Clear means the key is sent in cleartext (not recommended). + Servers is the list of servers in this group. + OpenConfig: /system/aaa/server-groups/server-group/servers/server + items: + description: |- + AAAServer represents a single AAA server within a group. + OpenConfig: /system/aaa/server-groups/server-group/servers/server[address] + properties: + address: + description: Address is the IP address or hostname of + the server. + maxLength: 253 + minLength: 1 + type: string + tacacs: + description: |- + TACACS contains TACACS+ specific server configuration. + Required when the parent server group type is TACACS. + OpenConfig augmentation: /system/aaa/server-groups/server-group/servers/server/tacacs + properties: + keySecretRef: + description: |- + KeySecretRef is a reference to a secret containing the shared key for this TACACS+ server. + The secret must contain a key specified in the SecretKeySelector. + properties: + key: + description: |- + Key is the of the entry in the secret resource's `data` or `stringData` + field to be used. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is unique within a namespace + to reference a secret resource. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace defines the space within which the secret name must be unique. + If omitted, the namespace of the object being reconciled will be used. + maxLength: 63 + minLength: 1 + type: string + required: + - key + - name + type: object + x-kubernetes-map-type: atomic + port: + default: 49 + description: |- + Port is the TCP port of the TACACS+ server. + Defaults to 49 if not specified. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - keySecretRef + type: object + timeout: + description: Timeout is the response timeout in seconds + for this server. + format: int32 + maximum: 60 + minimum: 1 + type: integer + required: + - address + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - address + x-kubernetes-list-type: map + sourceInterfaceName: + description: SourceInterfaceName is the source interface to + use for communication with the servers. + maxLength: 63 + type: string + type: + description: Type is the protocol type of this server group. + enum: + - TACACS + - RADIUS + type: string + vrfName: + description: VrfName is the VRF to use for communication with + the servers in this group. + maxLength: 63 type: string - keySecretRef: - description: |- - KeySecretRef is a reference to a secret containing the shared key for this TACACS+ server. - The secret must contain a key specified in the SecretKeySelector. - properties: - key: - description: |- - Key is the of the entry in the secret resource's `data` or `stringData` - field to be used. - maxLength: 253 - minLength: 1 - type: string - name: - description: Name is unique within a namespace to reference - a secret resource. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: |- - Namespace defines the space within which the secret name must be unique. - If omitted, the namespace of the object being reconciled will be used. - maxLength: 63 - minLength: 1 - type: string - required: - - key - - name - type: object - x-kubernetes-map-type: atomic - port: - default: 49 - description: |- - Port is the TCP port of the TACACS+ server. - Defaults to 49 if not specified. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - timeout: - description: Timeout is the timeout in seconds for this TACACS+ - server. - format: int32 - maximum: 60 - minimum: 1 - type: integer required: - - address - - keySecretRef + - name + - servers + - type type: object - maxItems: 16 + x-kubernetes-validations: + - message: servers in a TACACS group must have tacacs config + rule: self.type != 'TACACS' || self.servers.all(s, has(s.tacacs)) + maxItems: 8 type: array x-kubernetes-list-map-keys: - - address + - name x-kubernetes-list-type: map required: - deviceRef type: object + x-kubernetes-validations: + - message: at least one of serverGroups, authentication, authorization, + or accounting must be set + rule: has(self.serverGroups) || has(self.authentication) || has(self.authorization) + || has(self.accounting) status: description: |- Status of the resource. This is set and updated automatically. diff --git a/config/crd/bases/networking.metal.ironcore.dev_accesscontrollists.yaml b/config/crd/bases/networking.metal.ironcore.dev_accesscontrollists.yaml index 05813432..bc2f2645 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_accesscontrollists.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_accesscontrollists.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: accesscontrollists.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_banners.yaml b/config/crd/bases/networking.metal.ironcore.dev_banners.yaml index 9f11a95f..eab0b691 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_banners.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_banners.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: banners.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_bgp.yaml b/config/crd/bases/networking.metal.ironcore.dev_bgp.yaml index ea3a25ed..69870194 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_bgp.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_bgp.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: bgp.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml b/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml index c3037146..6fc9be75 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: bgppeers.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_certificates.yaml b/config/crd/bases/networking.metal.ironcore.dev_certificates.yaml index 202bc909..d6614907 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_certificates.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_certificates.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: certificates.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_devices.yaml b/config/crd/bases/networking.metal.ironcore.dev_devices.yaml index d7930048..0d3d864d 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_devices.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_devices.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: devices.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_dns.yaml b/config/crd/bases/networking.metal.ironcore.dev_dns.yaml index 7da4119b..086b6d54 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_dns.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_dns.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: dns.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_evpninstances.yaml b/config/crd/bases/networking.metal.ironcore.dev_evpninstances.yaml index 72638132..4a89e2f0 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_evpninstances.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_evpninstances.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: evpninstances.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml b/config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml index ec6df376..cd8228c6 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: interfaces.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_isis.yaml b/config/crd/bases/networking.metal.ironcore.dev_isis.yaml index 4981fa00..025d1587 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_isis.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_isis.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: isis.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_managementaccesses.yaml b/config/crd/bases/networking.metal.ironcore.dev_managementaccesses.yaml index 217228b4..8c44f2cb 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_managementaccesses.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_managementaccesses.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: managementaccesses.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_networkvirtualizationedges.yaml b/config/crd/bases/networking.metal.ironcore.dev_networkvirtualizationedges.yaml index 82919309..8fa1d84e 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_networkvirtualizationedges.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_networkvirtualizationedges.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: networkvirtualizationedges.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_ntp.yaml b/config/crd/bases/networking.metal.ironcore.dev_ntp.yaml index 7c3e94ef..5c9d21e3 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_ntp.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_ntp.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: ntp.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_ospf.yaml b/config/crd/bases/networking.metal.ironcore.dev_ospf.yaml index 2062876e..fdb8cfe8 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_ospf.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_ospf.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: ospf.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_pim.yaml b/config/crd/bases/networking.metal.ironcore.dev_pim.yaml index de9d718b..2a04e539 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_pim.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_pim.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: pim.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_prefixsets.yaml b/config/crd/bases/networking.metal.ironcore.dev_prefixsets.yaml index 1d50bac5..d13cd77f 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_prefixsets.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_prefixsets.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: prefixsets.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_routingpolicies.yaml b/config/crd/bases/networking.metal.ironcore.dev_routingpolicies.yaml index 63fc35b7..608bc64c 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_routingpolicies.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_routingpolicies.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: routingpolicies.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_snmp.yaml b/config/crd/bases/networking.metal.ironcore.dev_snmp.yaml index 3c3f7411..c0adc2f2 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_snmp.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_snmp.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: snmp.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_syslogs.yaml b/config/crd/bases/networking.metal.ironcore.dev_syslogs.yaml index 6b48d3a9..7b00364b 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_syslogs.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_syslogs.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: syslogs.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_users.yaml b/config/crd/bases/networking.metal.ironcore.dev_users.yaml index 184191dc..837680c1 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_users.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_users.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: users.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_vlans.yaml b/config/crd/bases/networking.metal.ironcore.dev_vlans.yaml index 12c1e9df..acbfd095 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_vlans.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_vlans.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: vlans.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/networking.metal.ironcore.dev_vrfs.yaml b/config/crd/bases/networking.metal.ironcore.dev_vrfs.yaml index 881356eb..21bdcb5f 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_vrfs.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_vrfs.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: vrfs.networking.metal.ironcore.dev spec: group: networking.metal.ironcore.dev diff --git a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_aaaconfigs.yaml b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_aaaconfigs.yaml new file mode 100644 index 00000000..4b1aba88 --- /dev/null +++ b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_aaaconfigs.yaml @@ -0,0 +1,136 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: aaaconfigs.nx.cisco.networking.metal.ironcore.dev +spec: + group: nx.cisco.networking.metal.ironcore.dev + names: + kind: AAAConfig + listKind: AAAConfigList + plural: aaaconfigs + shortNames: + - nxaaa + singular: aaaconfig + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: AAAConfig is the Schema for the aaaconfigs API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Specification of the desired state of the resource. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + properties: + configCommandsAuthorization: + description: |- + ConfigCommandsAuthorization defines NX-OS config-commands authorization methods. + Maps to: aaa authorization config-commands default + properties: + methods: + description: Methods is the ordered list of methods. + items: + description: NXOSMethod represents a single AAA method in an + NX-OS context. + properties: + groupName: + description: GroupName is the server group name when Type + is Group. + maxLength: 63 + type: string + type: + description: Type is the method type. + enum: + - Group + - Local + - None + type: string + required: + - type + type: object + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - methods + type: object + consoleAuthentication: + description: |- + ConsoleAuthentication defines NX-OS console-specific authentication methods. + Maps to: aaa authentication login console + properties: + methods: + description: Methods is the ordered list of methods. + items: + description: NXOSMethod represents a single AAA method in an + NX-OS context. + properties: + groupName: + description: GroupName is the server group name when Type + is Group. + maxLength: 63 + type: string + type: + description: Type is the method type. + enum: + - Group + - Local + - None + type: string + required: + - type + type: object + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + required: + - methods + type: object + keyEncryption: + allOf: + - enum: + - Type6 + - Type7 + - Clear + - enum: + - Type6 + - Type7 + - Clear + default: Type7 + description: KeyEncryption specifies the default encryption type for + TACACS+ keys. + type: string + loginErrorEnable: + description: |- + LoginErrorEnable enables login error messages (NX-OS specific). + Maps to: aaa authentication login error-enable + type: boolean + type: object + required: + - spec + type: object + served: true + storage: true diff --git a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_bordergateways.yaml b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_bordergateways.yaml index 77009306..f03a5f2e 100644 --- a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_bordergateways.yaml +++ b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_bordergateways.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: bordergateways.nx.cisco.networking.metal.ironcore.dev spec: group: nx.cisco.networking.metal.ironcore.dev diff --git a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_interfaceconfigs.yaml b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_interfaceconfigs.yaml index 1a0483fe..bdbb3f36 100644 --- a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_interfaceconfigs.yaml +++ b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_interfaceconfigs.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: interfaceconfigs.nx.cisco.networking.metal.ironcore.dev spec: group: nx.cisco.networking.metal.ironcore.dev diff --git a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_managementaccessconfigs.yaml b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_managementaccessconfigs.yaml index 8baf5feb..f846b389 100644 --- a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_managementaccessconfigs.yaml +++ b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_managementaccessconfigs.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: managementaccessconfigs.nx.cisco.networking.metal.ironcore.dev spec: group: nx.cisco.networking.metal.ironcore.dev diff --git a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_networkvirtualizationedgeconfigs.yaml b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_networkvirtualizationedgeconfigs.yaml index cd52a8e7..b9b672d6 100644 --- a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_networkvirtualizationedgeconfigs.yaml +++ b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_networkvirtualizationedgeconfigs.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: networkvirtualizationedgeconfigs.nx.cisco.networking.metal.ironcore.dev spec: group: nx.cisco.networking.metal.ironcore.dev diff --git a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_systems.yaml b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_systems.yaml index 76eddf09..6afb22e2 100644 --- a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_systems.yaml +++ b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_systems.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: systems.nx.cisco.networking.metal.ironcore.dev spec: group: nx.cisco.networking.metal.ironcore.dev diff --git a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_vpcdomains.yaml b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_vpcdomains.yaml index 7fccf11d..ad3dd0d7 100644 --- a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_vpcdomains.yaml +++ b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_vpcdomains.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.0 + controller-gen.kubebuilder.io/version: v0.20.1 name: vpcdomains.nx.cisco.networking.metal.ironcore.dev spec: group: nx.cisco.networking.metal.ironcore.dev diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 3f7b4660..23b66b1b 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -24,7 +24,7 @@ resources: # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. - ../certmanager # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. -#- ../prometheus +- ../prometheus # [METRICS] Expose the controller manager metrics service. - metrics_service.yaml # [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy. diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index daf31d26..6efdfd61 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -28,6 +28,17 @@ rules: - list - update - watch +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - delete + - get + - list + - update + - watch - apiGroups: - networking.metal.ironcore.dev resources: @@ -120,6 +131,17 @@ rules: - get - patch - update +- apiGroups: + - nx.cisco.networking.metal.ironcore.dev + resources: + - aaaconfigs + - interfaceconfigs + - managementaccessconfigs + - networkvirtualizationedgeconfigs + verbs: + - get + - list + - watch - apiGroups: - nx.cisco.networking.metal.ironcore.dev resources: diff --git a/config/samples/networking_v1alpha1_aaa.yaml b/config/samples/networking_v1alpha1_aaa.yaml index 1d281c0c..94e2d6b0 100644 --- a/config/samples/networking_v1alpha1_aaa.yaml +++ b/config/samples/networking_v1alpha1_aaa.yaml @@ -1,8 +1,8 @@ # Example AAA configuration with TACACS+ servers # This configures: # - feature tacacs+ -# - tacacs-server hosts with Type 7 encrypted keys -# - AAA group server for TACACS +# - tacacs-server hosts with encrypted keys +# - AAA server group for TACACS+ # - AAA authentication, authorization, and accounting --- apiVersion: v1 @@ -15,6 +15,30 @@ stringData: # Replace with your actual TACACS+ server shared secret server-key: "supersecretkey" --- +# Cisco NX-OS specific AAA configuration +# This configures vendor-specific settings like key encryption type, +# login error messages, console authentication, and config-commands authorization. +apiVersion: nx.cisco.networking.metal.ironcore.dev/v1alpha1 +kind: AAAConfig +metadata: + name: aaa-tacacs-nxos + namespace: default +spec: + keyEncryption: Type7 + loginErrorEnable: true + # NX-OS: aaa authentication login console group GR_TACACS local + consoleAuthentication: + methods: + - type: Group + groupName: GR_TACACS + - type: Local + # NX-OS: aaa authorization config-commands default group GR_TACACS local + configCommandsAuthorization: + methods: + - type: Group + groupName: GR_TACACS + - type: Local +--- apiVersion: networking.metal.ironcore.dev/v1alpha1 kind: AAA metadata: @@ -25,79 +49,54 @@ spec: deviceRef: name: my-switch - # TACACS+ server configuration - # Equivalent to: - # tacacs-server host 10.16.8.142 key 7 $TYPE_7_SECRET - # tacacs-server host 10.16.8.32 key 7 $TYPE_7_SECRET - # tacacs-server host 169.145.33.51 key 7 $TYPE_7_SECRET - tacacsServers: - - address: "10.16.8.142" - keySecretRef: - name: tacacs-server-keys - key: server-key - keyEncryption: Type7 - - address: "10.16.8.32" - keySecretRef: - name: tacacs-server-keys - key: server-key - keyEncryption: Type7 - - address: "169.145.33.51" - keySecretRef: - name: tacacs-server-keys - key: server-key - keyEncryption: Type7 + # Reference to the Cisco NX-OS specific AAA configuration + providerConfigRef: + group: nx.cisco.networking.metal.ironcore.dev + kind: AAAConfig + name: aaa-tacacs-nxos - # TACACS+ server group configuration - # Equivalent to: - # aaa group server tacacs+ GR_TACACS - # server 10.16.8.32 - # server 10.16.8.142 - # server 169.145.33.51 - # use-vrf management - # source-interface mgmt0 - tacacsGroup: - name: GR_TACACS - servers: - - "10.16.8.32" - - "10.16.8.142" - - "169.145.33.51" - vrf: management - sourceInterface: mgmt0 + # TACACS+ server group with nested servers + # OpenConfig: /system/aaa/server-groups/server-group + serverGroups: + - name: GR_TACACS + type: TACACS + vrfName: management + sourceInterfaceName: mgmt0 + servers: + - address: "10.16.8.142" + tacacs: + keySecretRef: + name: tacacs-server-keys + key: server-key + - address: "10.16.8.32" + tacacs: + keySecretRef: + name: tacacs-server-keys + key: server-key + - address: "169.145.33.51" + tacacs: + keySecretRef: + name: tacacs-server-keys + key: server-key - # AAA authentication configuration - # Equivalent to: - # aaa authentication login default group GR_TACACS - # aaa authentication login console group GR_TACACS local - # aaa authentication login error-enable + # AAA authentication method list + # OpenConfig: /system/aaa/authentication authentication: - login: - default: - methods: - - type: Group - groupName: GR_TACACS - console: - methods: - - type: Group - groupName: GR_TACACS - - type: Local - loginErrorEnable: true + methods: + - type: Group + groupName: GR_TACACS - # AAA authorization configuration - # Equivalent to: - # aaa authorization config-commands default group GR_TACACS local + # AAA authorization method list + # OpenConfig: /system/aaa/authorization authorization: - configCommands: - default: - methods: - - type: Group - groupName: GR_TACACS - - type: Local + methods: + - type: Group + groupName: GR_TACACS + - type: Local - # AAA accounting configuration - # Equivalent to: - # aaa accounting default group GR_TACACS + # AAA accounting method list + # OpenConfig: /system/aaa/accounting accounting: - default: - methods: - - type: Group - groupName: GR_TACACS + methods: + - type: Group + groupName: GR_TACACS diff --git a/internal/controller/core/aaa_controller.go b/internal/controller/core/aaa_controller.go index 17733f43..0df76622 100644 --- a/internal/controller/core/aaa_controller.go +++ b/internal/controller/core/aaa_controller.go @@ -253,15 +253,19 @@ func (r *AAAReconciler) reconcile(ctx context.Context, s *aaaScope) (reterr erro } }() - // Load TACACS+ server keys from secrets + // Load server keys from secrets c := clientutil.NewClient(r, s.AAA.Namespace) tacacsKeys := make(map[string]string) - for _, server := range s.AAA.Spec.TACACSServers { - key, err := c.Secret(ctx, &server.KeySecretRef) - if err != nil { - return fmt.Errorf("failed to get TACACS+ key for server %s: %w", server.Address, err) + for _, group := range s.AAA.Spec.ServerGroups { + for _, server := range group.Servers { + if server.TACACS != nil { + key, err := c.Secret(ctx, &server.TACACS.KeySecretRef) + if err != nil { + return fmt.Errorf("failed to get key for server %s in group %s: %w", server.Address, group.Name, err) + } + tacacsKeys[server.Address] = string(key) + } } - tacacsKeys[server.Address] = string(key) } // Ensure the AAA is realized on the provider. @@ -313,16 +317,22 @@ func (r *AAAReconciler) secretToAAA(ctx context.Context, obj client.Object) []ct requests := []ctrl.Request{} for _, a := range aaas.Items { - // Check if any TACACS+ server references this secret - for _, server := range a.Spec.TACACSServers { - if server.KeySecretRef.Name == secret.Name && a.Namespace == secret.Namespace { - log.Info("Enqueuing AAA for reconciliation", "AAA", klog.KObj(&a)) - requests = append(requests, ctrl.Request{ - NamespacedName: client.ObjectKey{ - Name: a.Name, - Namespace: a.Namespace, - }, - }) + found := false + for _, group := range a.Spec.ServerGroups { + for _, server := range group.Servers { + if server.TACACS != nil && server.TACACS.KeySecretRef.Name == secret.Name && a.Namespace == secret.Namespace { + log.Info("Enqueuing AAA for reconciliation", "AAA", klog.KObj(&a)) + requests = append(requests, ctrl.Request{ + NamespacedName: client.ObjectKey{ + Name: a.Name, + Namespace: a.Namespace, + }, + }) + found = true + break + } + } + if found { break // Only enqueue once per AAA } } diff --git a/internal/provider/cisco/nxos/aaa.go b/internal/provider/cisco/nxos/aaa.go index 09474f70..100b54a6 100644 --- a/internal/provider/cisco/nxos/aaa.go +++ b/internal/provider/cisco/nxos/aaa.go @@ -4,8 +4,9 @@ package nxos import ( + nxv1alpha1 "github.com/ironcore-dev/network-operator/api/cisco/nx/v1alpha1" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/provider/cisco/gnmiext/v2" + gnmiext "github.com/ironcore-dev/network-operator/internal/provider/cisco/gnmiext/v2" ) var ( @@ -19,7 +20,6 @@ var ( ) // TACACSFeature enables/disables the TACACS+ feature on NX-OS. -// Path: System/fm-items/tacacsplus-items/adminSt type TACACSFeature string func (*TACACSFeature) XPath() string { @@ -41,7 +41,6 @@ const ( ) // TacacsPlusProvider represents a TACACS+ server host configuration. -// Path: System/userext-items/tacacsext-items/tacacsplusprovider-items/TacacsPlusProvider-list[name=
] type TacacsPlusProvider struct { Name string `json:"name"` Port int32 `json:"port,omitempty"` @@ -59,7 +58,6 @@ func (p *TacacsPlusProvider) XPath() string { } // TacacsPlusProviderGroup represents a TACACS+ server group configuration. -// Path: System/userext-items/tacacsext-items/tacacsplusprovidergroup-items/TacacsPlusProviderGroup-list[name=] type TacacsPlusProviderGroup struct { Name string `json:"name"` Vrf string `json:"vrf,omitempty"` @@ -85,7 +83,6 @@ type TacacsPlusProviderRef struct { func (r *TacacsPlusProviderRef) Key() string { return r.Name } // AAADefaultAuth represents AAA default authentication configuration. -// Path: System/userext-items/authrealm-items/defaultauth-items type AAADefaultAuth struct { Realm string `json:"realm,omitempty"` ProviderGroup string `json:"providerGroup,omitempty"` @@ -101,7 +98,6 @@ func (*AAADefaultAuth) XPath() string { } // AAAConsoleAuth represents AAA console authentication configuration. -// Path: System/userext-items/authrealm-items/consoleauth-items type AAAConsoleAuth struct { Realm string `json:"realm,omitempty"` ProviderGroup string `json:"providerGroup,omitempty"` @@ -117,11 +113,9 @@ func (*AAAConsoleAuth) XPath() string { } // AAADefaultAuthor represents AAA default authorization configuration for config commands. -// Path: System/userext-items/authrealm-items/defaultauthor-items/DefaultAuthor-list[cmdType=config] +// Note: "name" and "realm" are read-only operational fields on NX-OS and must not be sent. type AAADefaultAuthor struct { - Name string `json:"name"` CmdType string `json:"cmdType"` - Realm string `json:"realm,omitempty"` ProviderGroup string `json:"providerGroup,omitempty"` LocalRbac bool `json:"localRbac,omitempty"` AuthorMethodNone bool `json:"authorMethodNone,omitempty"` @@ -134,7 +128,6 @@ func (a *AAADefaultAuthor) XPath() string { } // AAADefaultAcc represents AAA default accounting configuration. -// Path: System/userext-items/authrealm-items/defaultacc-items type AAADefaultAcc struct { Name string `json:"name,omitempty"` Realm string `json:"realm,omitempty"` @@ -147,14 +140,14 @@ func (*AAADefaultAcc) XPath() string { return "System/userext-items/authrealm-items/defaultacc-items" } -// MapKeyEncryption maps the API key encryption type to NX-OS type. -func MapKeyEncryption(enc v1alpha1.TACACSKeyEncryption) string { +// MapKeyEncryption maps the Cisco-specific key encryption type to NX-OS type. +func MapKeyEncryption(enc nxv1alpha1.TACACSKeyEncryption) string { switch enc { - case v1alpha1.TACACSKeyEncryptionType6: + case nxv1alpha1.TACACSKeyEncryptionType6: return "6" - case v1alpha1.TACACSKeyEncryptionType7: + case nxv1alpha1.TACACSKeyEncryptionType7: return "7" - case v1alpha1.TACACSKeyEncryptionClear: + case nxv1alpha1.TACACSKeyEncryptionClear: return "0" default: return "7" @@ -162,7 +155,7 @@ func MapKeyEncryption(enc v1alpha1.TACACSKeyEncryption) string { } // MapRealmFromMethodType maps the API method type to NX-OS realm. -func MapRealmFromMethodType(method v1alpha1.AAAMethodType, groupName string) string { +func MapRealmFromMethodType(method v1alpha1.AAAMethodType) string { switch method { case v1alpha1.AAAMethodTypeGroup: return AAARealmTacacs @@ -193,3 +186,35 @@ func MapFallbackFromMethodList(methods []v1alpha1.AAAMethod) string { } return AAAValueNo } + +// MapNXOSRealm maps an NX-OS method type string to NX-OS realm. +func MapNXOSRealm(methodType string) string { + switch methodType { + case "Group": + return AAARealmTacacs + case "Local": + return AAARealmLocal + case "None": + return AAARealmNone + default: + return AAARealmLocal + } +} + +// MapNXOSLocal checks if local is in an NX-OS method list. +func MapNXOSLocal(methods []nxv1alpha1.NXOSMethod) string { + for _, m := range methods { + if m.Type == "Local" { + return AAAValueYes + } + } + return AAAValueNo +} + +// MapNXOSFallback determines fallback setting from an NX-OS method list. +func MapNXOSFallback(methods []nxv1alpha1.NXOSMethod) string { + if len(methods) > 1 { + return AAAValueYes + } + return AAAValueNo +} diff --git a/internal/provider/cisco/nxos/provider.go b/internal/provider/cisco/nxos/provider.go index 2f0c8999..9bbd410e 100644 --- a/internal/provider/cisco/nxos/provider.go +++ b/internal/provider/cisco/nxos/provider.go @@ -2685,100 +2685,117 @@ func separateFeatureActivation(conf []gnmiext.Configurable) (features, others [] func (p *Provider) EnsureAAA(ctx context.Context, req *provider.EnsureAAARequest) error { var conf []gnmiext.Configurable - // Enable TACACS+ feature if there are TACACS servers configured - if len(req.AAA.Spec.TACACSServers) > 0 { + // Read Cisco-specific config from ProviderConfig + var cfg nxv1alpha1.AAAConfig + if req.ProviderConfig != nil { + if err := req.ProviderConfig.Into(&cfg); err != nil { + return err + } + } + + // Process server groups + for _, group := range req.AAA.Spec.ServerGroups { + if group.Type != v1alpha1.AAAServerGroupTypeTACACS { + continue + } + + // Enable TACACS+ feature tacacsFeature := TACACSFeatureEnabled conf = append(conf, &tacacsFeature) - } - // Configure TACACS+ server hosts - for _, server := range req.AAA.Spec.TACACSServers { - srv := &TacacsPlusProvider{ - Name: server.Address, - Port: server.Port, - KeyEnc: MapKeyEncryption(server.KeyEncryption), + // Configure individual TACACS+ server hosts + for _, server := range group.Servers { + srv := &TacacsPlusProvider{ + Name: server.Address, + KeyEnc: MapKeyEncryption(cfg.Spec.KeyEncryption), + } + if server.TACACS != nil { + srv.Port = server.TACACS.Port + } + if key, ok := req.TACACSServerKeys[server.Address]; ok { + srv.Key = key + } + if server.Timeout != nil { + srv.Timeout = *server.Timeout + } + conf = append(conf, srv) } - if key, ok := req.TACACSServerKeys[server.Address]; ok { - srv.Key = key + + // Configure the server group + grp := &TacacsPlusProviderGroup{ + Name: group.Name, + Vrf: group.VrfName, + SrcIf: group.SourceInterfaceName, } - if server.Timeout != nil { - srv.Timeout = *server.Timeout + for _, server := range group.Servers { + grp.ProviderRefItems.ProviderRefList.Set(&TacacsPlusProviderRef{Name: server.Address}) } - conf = append(conf, srv) + conf = append(conf, grp) } - // Configure TACACS+ server group - if req.AAA.Spec.TACACSGroup != nil { - group := &TacacsPlusProviderGroup{ - Name: req.AAA.Spec.TACACSGroup.Name, - Vrf: req.AAA.Spec.TACACSGroup.VRF, - SrcIf: req.AAA.Spec.TACACSGroup.SourceInterface, + // Configure AAA default authentication (from core API flat method list) + if req.AAA.Spec.Authentication != nil && len(req.AAA.Spec.Authentication.Methods) > 0 { + methods := req.AAA.Spec.Authentication.Methods + authen := &AAADefaultAuth{ + ErrEn: cfg.Spec.LoginErrorEnable, + Fallback: MapFallbackFromMethodList(methods), + Local: MapLocalFromMethodList(methods), } - for _, serverAddr := range req.AAA.Spec.TACACSGroup.Servers { - group.ProviderRefItems.ProviderRefList.Set(&TacacsPlusProviderRef{Name: serverAddr}) + if methods[0].Type == v1alpha1.AAAMethodTypeGroup { + authen.Realm = AAARealmTacacs + authen.ProviderGroup = methods[0].GroupName + } else { + authen.Realm = MapRealmFromMethodType(methods[0].Type) } - conf = append(conf, group) + conf = append(conf, authen) } - // Configure AAA default authentication - if req.AAA.Spec.Authentication != nil && req.AAA.Spec.Authentication.Login != nil { - if req.AAA.Spec.Authentication.Login.Default != nil && len(req.AAA.Spec.Authentication.Login.Default.Methods) > 0 { - methods := req.AAA.Spec.Authentication.Login.Default.Methods - authen := &AAADefaultAuth{ - ErrEn: req.AAA.Spec.Authentication.LoginErrorEnable, - Fallback: MapFallbackFromMethodList(methods), - Local: MapLocalFromMethodList(methods), - } - // Set realm and provider group based on first method - if methods[0].Type == v1alpha1.AAAMethodTypeGroup { - authen.Realm = AAARealmTacacs - authen.ProviderGroup = methods[0].GroupName - } else { - authen.Realm = MapRealmFromMethodType(methods[0].Type, "") - } - conf = append(conf, authen) + // Configure AAA console authentication (from Cisco AAAConfig) + if cfg.Spec.ConsoleAuthentication != nil && len(cfg.Spec.ConsoleAuthentication.Methods) > 0 { + methods := cfg.Spec.ConsoleAuthentication.Methods + consoleAuth := &AAAConsoleAuth{ + ErrEn: cfg.Spec.LoginErrorEnable, + Fallback: MapNXOSFallback(methods), + Local: MapNXOSLocal(methods), } + if methods[0].Type == "Group" { + consoleAuth.Realm = AAARealmTacacs + consoleAuth.ProviderGroup = methods[0].GroupName + } else { + consoleAuth.Realm = MapNXOSRealm(methods[0].Type) + } + conf = append(conf, consoleAuth) + } - // Configure AAA console authentication - if req.AAA.Spec.Authentication.Login.Console != nil && len(req.AAA.Spec.Authentication.Login.Console.Methods) > 0 { - methods := req.AAA.Spec.Authentication.Login.Console.Methods - consoleAuth := &AAAConsoleAuth{ - ErrEn: req.AAA.Spec.Authentication.LoginErrorEnable, - Fallback: MapFallbackFromMethodList(methods), - Local: MapLocalFromMethodList(methods), - } - if methods[0].Type == v1alpha1.AAAMethodTypeGroup { - consoleAuth.Realm = AAARealmTacacs - consoleAuth.ProviderGroup = methods[0].GroupName - } else { - consoleAuth.Realm = MapRealmFromMethodType(methods[0].Type, "") - } - conf = append(conf, consoleAuth) + // Configure AAA authorization (from core API flat method list) + if req.AAA.Spec.Authorization != nil && len(req.AAA.Spec.Authorization.Methods) > 0 { + methods := req.AAA.Spec.Authorization.Methods + author := &AAADefaultAuthor{ + CmdType: "config", + LocalRbac: MapLocalFromMethodList(methods) == AAAValueYes, } + if methods[0].Type == v1alpha1.AAAMethodTypeGroup { + author.ProviderGroup = methods[0].GroupName + } + conf = append(conf, author) } - // Configure AAA authorization for config commands - if req.AAA.Spec.Authorization != nil && req.AAA.Spec.Authorization.ConfigCommands != nil { - if req.AAA.Spec.Authorization.ConfigCommands.Default != nil && len(req.AAA.Spec.Authorization.ConfigCommands.Default.Methods) > 0 { - methods := req.AAA.Spec.Authorization.ConfigCommands.Default.Methods - author := &AAADefaultAuthor{ - Name: "Author", - CmdType: "config", - LocalRbac: MapLocalFromMethodList(methods) == AAAValueYes, - } - if methods[0].Type == v1alpha1.AAAMethodTypeGroup { - author.Realm = AAARealmTacacs - author.ProviderGroup = methods[0].GroupName - } else { - author.Realm = MapRealmFromMethodType(methods[0].Type, "") - } - conf = append(conf, author) + // Configure AAA config-commands authorization (from Cisco AAAConfig) + if cfg.Spec.ConfigCommandsAuthorization != nil && len(cfg.Spec.ConfigCommandsAuthorization.Methods) > 0 { + methods := cfg.Spec.ConfigCommandsAuthorization.Methods + author := &AAADefaultAuthor{ + CmdType: "config", + LocalRbac: MapNXOSLocal(methods) == AAAValueYes, + } + if methods[0].Type == "Group" { + author.ProviderGroup = methods[0].GroupName } + conf = append(conf, author) } - // Configure AAA accounting - if req.AAA.Spec.Accounting != nil && req.AAA.Spec.Accounting.Default != nil && len(req.AAA.Spec.Accounting.Default.Methods) > 0 { - methods := req.AAA.Spec.Accounting.Default.Methods + // Configure AAA accounting (from core API flat method list) + if req.AAA.Spec.Accounting != nil && len(req.AAA.Spec.Accounting.Methods) > 0 { + methods := req.AAA.Spec.Accounting.Methods acct := &AAADefaultAcc{ Name: "Accounting", LocalRbac: MapLocalFromMethodList(methods) == AAAValueYes, @@ -2787,87 +2804,92 @@ func (p *Provider) EnsureAAA(ctx context.Context, req *provider.EnsureAAARequest acct.Realm = AAARealmTacacs acct.ProviderGroup = methods[0].GroupName } else { - acct.Realm = MapRealmFromMethodType(methods[0].Type, "") + acct.Realm = MapRealmFromMethodType(methods[0].Type) } conf = append(conf, acct) } - return p.Patch(ctx, conf...) + return p.Update(ctx, conf...) } func (p *Provider) DeleteAAA(ctx context.Context, req *provider.DeleteAAARequest) error { + var conf []gnmiext.Configurable + + // Read Cisco-specific config from ProviderConfig + var cfg nxv1alpha1.AAAConfig + if req.ProviderConfig != nil { + if err := req.ProviderConfig.Into(&cfg); err != nil { + return err + } + } + // Reset AAA accounting to local - if req.AAA.Spec.Accounting != nil && req.AAA.Spec.Accounting.Default != nil { - acct := &AAADefaultAcc{ + if req.AAA.Spec.Accounting != nil { + conf = append(conf, &AAADefaultAcc{ Name: "Accounting", Realm: AAARealmLocal, LocalRbac: true, - } - if err := p.Patch(ctx, acct); err != nil { - return err - } + }) } // Reset AAA authorization to local - if req.AAA.Spec.Authorization != nil && req.AAA.Spec.Authorization.ConfigCommands != nil { - author := &AAADefaultAuthor{ - Name: "Author", - CmdType: "config", - Realm: AAARealmLocal, - LocalRbac: true, - } - if err := p.Patch(ctx, author); err != nil { - return err - } + if req.AAA.Spec.Authorization != nil || cfg.Spec.ConfigCommandsAuthorization != nil { + conf = append(conf, &AAADefaultAuthor{ + CmdType: "config", + ProviderGroup: "", + LocalRbac: true, + }) } // Reset AAA authentication to local if req.AAA.Spec.Authentication != nil { - authen := &AAADefaultAuth{ + conf = append(conf, &AAADefaultAuth{ Realm: AAARealmLocal, Local: AAAValueYes, Fallback: AAAValueYes, ErrEn: false, + }) + } + + // Reset console authentication to local + if cfg.Spec.ConsoleAuthentication != nil { + conf = append(conf, &AAAConsoleAuth{ + Realm: AAARealmLocal, + Local: AAAValueYes, + Fallback: AAAValueYes, + ErrEn: false, + }) + } + + // Delete TACACS+ server groups and hosts + hasTACACS := false + for _, group := range req.AAA.Spec.ServerGroups { + if group.Type != v1alpha1.AAAServerGroupTypeTACACS { + continue } - if err := p.Patch(ctx, authen); err != nil { + hasTACACS = true + + grp := &TacacsPlusProviderGroup{Name: group.Name} + if err := p.client.Delete(ctx, grp); err != nil { return err } - if req.AAA.Spec.Authentication.Login != nil && req.AAA.Spec.Authentication.Login.Console != nil { - consoleAuth := &AAAConsoleAuth{ - Realm: AAARealmLocal, - Local: AAAValueYes, - Fallback: AAAValueYes, - ErrEn: false, - } - if err := p.Patch(ctx, consoleAuth); err != nil { + for _, server := range group.Servers { + srv := &TacacsPlusProvider{Name: server.Address} + if err := p.client.Delete(ctx, srv); err != nil { return err } } } - // Delete TACACS+ server group - if req.AAA.Spec.TACACSGroup != nil { - group := &TacacsPlusProviderGroup{Name: req.AAA.Spec.TACACSGroup.Name} - if err := p.client.Delete(ctx, group); err != nil { - return err - } - } - - // Delete TACACS+ server hosts - for _, server := range req.AAA.Spec.TACACSServers { - srv := &TacacsPlusProvider{Name: server.Address} - if err := p.client.Delete(ctx, srv); err != nil { - return err - } + // Disable TACACS+ feature + if hasTACACS { + tacacsFeature := TACACSFeatureDisabled + conf = append(conf, &tacacsFeature) } - // Disable TACACS+ feature if no servers remain - if len(req.AAA.Spec.TACACSServers) > 0 { - tacacsFeature := TACACSFeatureDisabled - if err := p.Patch(ctx, &tacacsFeature); err != nil { - return err - } + if len(conf) > 0 { + return p.Update(ctx, conf...) } return nil From 9b582901d76a4787896fedd4661550292b6743eb Mon Sep 17 00:00:00 2001 From: i521907 Date: Wed, 18 Feb 2026 10:52:56 -0500 Subject: [PATCH 4/5] [NX-OS] Add RADIUS support to AAA --- api/cisco/nx/v1alpha1/aaaconfig_types.go | 18 +++ api/core/v1alpha1/aaa_types.go | 31 +++++ hack/provider/main.go | 6 +- internal/controller/core/aaa_controller.go | 12 +- internal/deviceutil/deviceutil.go | 4 +- internal/provider/cisco/nxos/aaa.go | 81 +++++++++++++ internal/provider/cisco/nxos/ascii.go | 3 +- internal/provider/cisco/nxos/provider.go | 131 ++++++++++++++------- internal/provider/cisco/nxos/user.go | 2 +- internal/provider/provider.go | 5 +- internal/provisioning/http.go | 2 +- test/e2e/util.go | 20 ++-- test/lab/main_test.go | 2 +- 13 files changed, 250 insertions(+), 67 deletions(-) diff --git a/api/cisco/nx/v1alpha1/aaaconfig_types.go b/api/cisco/nx/v1alpha1/aaaconfig_types.go index 160da1e6..46f3ad92 100644 --- a/api/cisco/nx/v1alpha1/aaaconfig_types.go +++ b/api/cisco/nx/v1alpha1/aaaconfig_types.go @@ -23,6 +23,11 @@ type AAAConfigSpec struct { // +kubebuilder:default=Type7 KeyEncryption TACACSKeyEncryption `json:"keyEncryption,omitempty"` + // RADIUSKeyEncryption specifies the default encryption type for RADIUS server keys. + // +kubebuilder:validation:Enum=Type6;Type7;Clear + // +kubebuilder:default=Type7 + RADIUSKeyEncryption RADIUSKeyEncryption `json:"radiusKeyEncryption,omitempty"` + // ConsoleAuthentication defines NX-OS console-specific authentication methods. // Maps to: aaa authentication login console // +optional @@ -47,6 +52,19 @@ const ( TACACSKeyEncryptionClear TACACSKeyEncryption = "Clear" ) +// RADIUSKeyEncryption defines the encryption type for RADIUS server keys. +// +kubebuilder:validation:Enum=Type6;Type7;Clear +type RADIUSKeyEncryption string + +const ( + // RADIUSKeyEncryptionType6 uses AES encryption (more secure). + RADIUSKeyEncryptionType6 RADIUSKeyEncryption = "Type6" + // RADIUSKeyEncryptionType7 uses Cisco Type 7 encryption (reversible). + RADIUSKeyEncryptionType7 RADIUSKeyEncryption = "Type7" + // RADIUSKeyEncryptionClear sends the key in cleartext. + RADIUSKeyEncryptionClear RADIUSKeyEncryption = "Clear" +) + // NXOSMethodList defines an ordered list of AAA methods for NX-OS specific contexts. type NXOSMethodList struct { // Methods is the ordered list of methods. diff --git a/api/core/v1alpha1/aaa_types.go b/api/core/v1alpha1/aaa_types.go index 7fa0e50a..098b7d9a 100644 --- a/api/core/v1alpha1/aaa_types.go +++ b/api/core/v1alpha1/aaa_types.go @@ -65,6 +65,7 @@ const ( // AAAServerGroup represents a named group of AAA servers. // OpenConfig: /system/aaa/server-groups/server-group[name] // +kubebuilder:validation:XValidation:rule="self.type != 'TACACS' || self.servers.all(s, has(s.tacacs))",message="servers in a TACACS group must have tacacs config" +// +kubebuilder:validation:XValidation:rule="self.type != 'RADIUS' || self.servers.all(s, has(s.radius))",message="servers in a RADIUS group must have radius config" type AAAServerGroup struct { // Name is the name of the server group. // +required @@ -116,6 +117,12 @@ type AAAServer struct { // OpenConfig augmentation: /system/aaa/server-groups/server-group/servers/server/tacacs // +optional TACACS *AAAServerTACACS `json:"tacacs,omitempty"` + + // RADIUS contains RADIUS specific server configuration. + // Required when the parent server group type is RADIUS. + // OpenConfig augmentation: /system/aaa/server-groups/server-group/servers/server/radius + // +optional + RADIUS *AAAServerRADIUS `json:"radius,omitempty"` } // AAAServerTACACS contains TACACS+ specific server configuration. @@ -134,6 +141,30 @@ type AAAServerTACACS struct { KeySecretRef SecretKeySelector `json:"keySecretRef"` } +// AAAServerRADIUS contains RADIUS specific server configuration. +type AAAServerRADIUS struct { + // AuthPort is the UDP port for RADIUS authentication requests. + // Defaults to 1812 if not specified. + // +optional + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + // +kubebuilder:default=1812 + AuthPort int32 `json:"authPort,omitempty"` + + // AcctPort is the UDP port for RADIUS accounting requests. + // Defaults to 1813 if not specified. + // +optional + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + // +kubebuilder:default=1813 + AcctPort int32 `json:"acctPort,omitempty"` + + // KeySecretRef is a reference to a secret containing the shared key for this RADIUS server. + // The secret must contain a key specified in the SecretKeySelector. + // +required + KeySecretRef SecretKeySelector `json:"keySecretRef"` +} + // AAAAuthentication defines the AAA authentication method list. // OpenConfig: /system/aaa/authentication type AAAAuthentication struct { diff --git a/hack/provider/main.go b/hack/provider/main.go index 82779ef6..d8bbab11 100644 --- a/hack/provider/main.go +++ b/hack/provider/main.go @@ -11,6 +11,7 @@ import ( "net/netip" "os" "os/signal" + "path/filepath" "reflect" "strings" "syscall" @@ -84,7 +85,8 @@ func (r *refStoreReader) List(ctx context.Context, list client.ObjectList, opts } func usage() { - fmt.Fprintf(os.Stderr, "Usage: %s [flags] \n\n", os.Args[0]) // #nosec G705 + base := filepath.Base(os.Args[0]) + fmt.Fprintf(os.Stderr, "Usage: %s [flags] \n\n", base) //nolint:gosec fmt.Fprintf(os.Stderr, "A debug tool for testing provider implementations.\n\n") fmt.Fprintf(os.Stderr, "This tool allows you to directly test provider implementations by creating or\n") fmt.Fprintf(os.Stderr, "deleting resources on network devices.\n\n") @@ -93,7 +95,7 @@ func usage() { fmt.Fprintf(os.Stderr, "Flags:\n") flag.PrintDefaults() fmt.Fprintf(os.Stderr, "\nExample:\n") - fmt.Fprintf(os.Stderr, " %s -address=192.168.1.1:9339 -username=admin -password=secret -file=config/samples/v1alpha1_interface.yaml create\n", os.Args[0]) // #nosec G705 + fmt.Fprintf(os.Stderr, " %s -address=192.168.1.1:9339 -username=admin -password=secret -file=config/samples/v1alpha1_interface.yaml create\n", base) //nolint:gosec } func validateFlags() error { diff --git a/internal/controller/core/aaa_controller.go b/internal/controller/core/aaa_controller.go index 0df76622..1370d88c 100644 --- a/internal/controller/core/aaa_controller.go +++ b/internal/controller/core/aaa_controller.go @@ -256,6 +256,7 @@ func (r *AAAReconciler) reconcile(ctx context.Context, s *aaaScope) (reterr erro // Load server keys from secrets c := clientutil.NewClient(r, s.AAA.Namespace) tacacsKeys := make(map[string]string) + radiusKeys := make(map[string]string) for _, group := range s.AAA.Spec.ServerGroups { for _, server := range group.Servers { if server.TACACS != nil { @@ -265,6 +266,13 @@ func (r *AAAReconciler) reconcile(ctx context.Context, s *aaaScope) (reterr erro } tacacsKeys[server.Address] = string(key) } + if server.RADIUS != nil { + key, err := c.Secret(ctx, &server.RADIUS.KeySecretRef) + if err != nil { + return fmt.Errorf("failed to get key for server %s in group %s: %w", server.Address, group.Name, err) + } + radiusKeys[server.Address] = string(key) + } } } @@ -273,6 +281,7 @@ func (r *AAAReconciler) reconcile(ctx context.Context, s *aaaScope) (reterr erro AAA: s.AAA, ProviderConfig: s.ProviderConfig, TACACSServerKeys: tacacsKeys, + RADIUSServerKeys: radiusKeys, }) cond := conditions.FromError(err) @@ -320,7 +329,8 @@ func (r *AAAReconciler) secretToAAA(ctx context.Context, obj client.Object) []ct found := false for _, group := range a.Spec.ServerGroups { for _, server := range group.Servers { - if server.TACACS != nil && server.TACACS.KeySecretRef.Name == secret.Name && a.Namespace == secret.Namespace { + if (server.TACACS != nil && server.TACACS.KeySecretRef.Name == secret.Name && a.Namespace == secret.Namespace) || + (server.RADIUS != nil && server.RADIUS.KeySecretRef.Name == secret.Name && a.Namespace == secret.Namespace) { log.Info("Enqueuing AAA for reconciliation", "AAA", klog.KObj(&a)) requests = append(requests, ctrl.Request{ NamespacedName: client.ObjectKey{ diff --git a/internal/deviceutil/deviceutil.go b/internal/deviceutil/deviceutil.go index abde29f1..a82a2e47 100644 --- a/internal/deviceutil/deviceutil.go +++ b/internal/deviceutil/deviceutil.go @@ -90,7 +90,7 @@ type Connection struct { // Username for basic authentication. Might be empty if the device does not require authentication. Username string // Password for basic authentication. Might be empty if the device does not require authentication. - Password string // #nosec G117 + Password string `json:"-"` // TLS configuration for the connection. TLS *tls.Config } @@ -184,7 +184,7 @@ func WithDefaultTimeout(timeout time.Duration) Option { type auth struct { Username string - Password string // #nosec G117 + Password string `json:"-"` SecureTransportCreds bool } diff --git a/internal/provider/cisco/nxos/aaa.go b/internal/provider/cisco/nxos/aaa.go index 100b54a6..2c899587 100644 --- a/internal/provider/cisco/nxos/aaa.go +++ b/internal/provider/cisco/nxos/aaa.go @@ -13,6 +13,8 @@ var ( _ gnmiext.Configurable = (*TACACSFeature)(nil) _ gnmiext.Configurable = (*TacacsPlusProvider)(nil) _ gnmiext.Configurable = (*TacacsPlusProviderGroup)(nil) + _ gnmiext.Configurable = (*RadiusProvider)(nil) + _ gnmiext.Configurable = (*RadiusProviderGroup)(nil) _ gnmiext.Configurable = (*AAADefaultAuth)(nil) _ gnmiext.Configurable = (*AAAConsoleAuth)(nil) _ gnmiext.Configurable = (*AAADefaultAuthor)(nil) @@ -34,6 +36,7 @@ const ( // AAA configuration constants const ( AAARealmTacacs = "tacacs" + AAARealmRadius = "radius" AAARealmLocal = "local" AAARealmNone = "none" AAAValueYes = "yes" @@ -82,6 +85,48 @@ type TacacsPlusProviderRef struct { func (r *TacacsPlusProviderRef) Key() string { return r.Name } +// RadiusProvider represents a RADIUS server host configuration. +type RadiusProvider struct { + Name string `json:"name"` + AuthPort int32 `json:"authPort,omitempty"` + AcctPort int32 `json:"acctPort,omitempty"` + Key string `json:"key,omitempty"` + KeyEnc string `json:"keyEnc,omitempty"` + Timeout int32 `json:"timeout,omitempty"` + Retries int32 `json:"retries,omitempty"` +} + +func (*RadiusProvider) IsListItem() {} + +func (p *RadiusProvider) XPath() string { + return "System/userext-items/radiusext-items/radiusprovider-items/RadiusProvider-list[name=" + p.Name + "]" +} + +// RadiusProviderGroup represents a RADIUS server group configuration. +type RadiusProviderGroup struct { + Name string `json:"name"` + Vrf string `json:"vrf,omitempty"` + SrcIf string `json:"srcIf,omitempty"` + Deadtime int32 `json:"deadtime,omitempty"` + ProviderRefItems RadiusProviderGroupRefItems `json:"providerref-items,omitzero"` +} + +func (*RadiusProviderGroup) IsListItem() {} + +func (g *RadiusProviderGroup) XPath() string { + return "System/userext-items/radiusext-items/radiusprovidergroup-items/RadiusProviderGroup-list[name=" + g.Name + "]" +} + +type RadiusProviderGroupRefItems struct { + ProviderRefList gnmiext.List[string, *RadiusProviderRef] `json:"ProviderRef-list,omitzero"` +} + +type RadiusProviderRef struct { + Name string `json:"name"` +} + +func (r *RadiusProviderRef) Key() string { return r.Name } + // AAADefaultAuth represents AAA default authentication configuration. type AAADefaultAuth struct { Realm string `json:"realm,omitempty"` @@ -154,6 +199,42 @@ func MapKeyEncryption(enc nxv1alpha1.TACACSKeyEncryption) string { } } +// MapRADIUSKeyEncryption maps the Cisco-specific RADIUS key encryption type to NX-OS type. +func MapRADIUSKeyEncryption(enc nxv1alpha1.RADIUSKeyEncryption) string { + switch enc { + case nxv1alpha1.RADIUSKeyEncryptionType6: + return "6" + case nxv1alpha1.RADIUSKeyEncryptionType7: + return "7" + case nxv1alpha1.RADIUSKeyEncryptionClear: + return "0" + default: + return "7" + } +} + +// groupTypeByName returns the AAAServerGroupType for the given group name, +// defaulting to TACACS if not found. +func groupTypeByName(name string, groups []v1alpha1.AAAServerGroup) v1alpha1.AAAServerGroupType { + for _, g := range groups { + if g.Name == name { + return g.Type + } + } + return v1alpha1.AAAServerGroupTypeTACACS +} + +// MapRealmFromGroup returns the NX-OS realm string for the given group name, +// resolving TACACS vs RADIUS from the server group list. +func MapRealmFromGroup(groupName string, groups []v1alpha1.AAAServerGroup) string { + switch groupTypeByName(groupName, groups) { + case v1alpha1.AAAServerGroupTypeRADIUS: + return AAARealmRadius + default: + return AAARealmTacacs + } +} + // MapRealmFromMethodType maps the API method type to NX-OS realm. func MapRealmFromMethodType(method v1alpha1.AAAMethodType) string { switch method { diff --git a/internal/provider/cisco/nxos/ascii.go b/internal/provider/cisco/nxos/ascii.go index 47197f95..564b3089 100644 --- a/internal/provider/cisco/nxos/ascii.go +++ b/internal/provider/cisco/nxos/ascii.go @@ -18,8 +18,7 @@ func (s ASCIIStr) String() string { if v == "0" { break } - if num, err := strconv.Atoi(v); err == nil { - // #nosec G115 + if num, err := strconv.Atoi(v); err == nil && num >= 0 && num <= 127 { runes = append(runes, rune(num)) } } diff --git a/internal/provider/cisco/nxos/provider.go b/internal/provider/cisco/nxos/provider.go index 9bbd410e..8c4dd2ad 100644 --- a/internal/provider/cisco/nxos/provider.go +++ b/internal/provider/cisco/nxos/provider.go @@ -2695,42 +2695,72 @@ func (p *Provider) EnsureAAA(ctx context.Context, req *provider.EnsureAAARequest // Process server groups for _, group := range req.AAA.Spec.ServerGroups { - if group.Type != v1alpha1.AAAServerGroupTypeTACACS { - continue - } - - // Enable TACACS+ feature - tacacsFeature := TACACSFeatureEnabled - conf = append(conf, &tacacsFeature) - - // Configure individual TACACS+ server hosts - for _, server := range group.Servers { - srv := &TacacsPlusProvider{ - Name: server.Address, - KeyEnc: MapKeyEncryption(cfg.Spec.KeyEncryption), + switch group.Type { + case v1alpha1.AAAServerGroupTypeTACACS: + // Enable TACACS+ feature + tacacsFeature := TACACSFeatureEnabled + conf = append(conf, &tacacsFeature) + + // Configure individual TACACS+ server hosts + for _, server := range group.Servers { + srv := &TacacsPlusProvider{ + Name: server.Address, + KeyEnc: MapKeyEncryption(cfg.Spec.KeyEncryption), + } + if server.TACACS != nil { + srv.Port = server.TACACS.Port + } + if key, ok := req.TACACSServerKeys[server.Address]; ok { + srv.Key = key + } + if server.Timeout != nil { + srv.Timeout = *server.Timeout + } + conf = append(conf, srv) } - if server.TACACS != nil { - srv.Port = server.TACACS.Port + + // Configure the TACACS+ server group + grp := &TacacsPlusProviderGroup{ + Name: group.Name, + Vrf: group.VrfName, + SrcIf: group.SourceInterfaceName, } - if key, ok := req.TACACSServerKeys[server.Address]; ok { - srv.Key = key + for _, server := range group.Servers { + grp.ProviderRefItems.ProviderRefList.Set(&TacacsPlusProviderRef{Name: server.Address}) } - if server.Timeout != nil { - srv.Timeout = *server.Timeout + conf = append(conf, grp) + + case v1alpha1.AAAServerGroupTypeRADIUS: + // Configure individual RADIUS server hosts + for _, server := range group.Servers { + srv := &RadiusProvider{ + Name: server.Address, + KeyEnc: MapRADIUSKeyEncryption(cfg.Spec.RADIUSKeyEncryption), + } + if server.RADIUS != nil { + srv.AuthPort = server.RADIUS.AuthPort + srv.AcctPort = server.RADIUS.AcctPort + } + if key, ok := req.RADIUSServerKeys[server.Address]; ok { + srv.Key = key + } + if server.Timeout != nil { + srv.Timeout = *server.Timeout + } + conf = append(conf, srv) } - conf = append(conf, srv) - } - // Configure the server group - grp := &TacacsPlusProviderGroup{ - Name: group.Name, - Vrf: group.VrfName, - SrcIf: group.SourceInterfaceName, - } - for _, server := range group.Servers { - grp.ProviderRefItems.ProviderRefList.Set(&TacacsPlusProviderRef{Name: server.Address}) + // Configure the RADIUS server group + grp := &RadiusProviderGroup{ + Name: group.Name, + Vrf: group.VrfName, + SrcIf: group.SourceInterfaceName, + } + for _, server := range group.Servers { + grp.ProviderRefItems.ProviderRefList.Set(&RadiusProviderRef{Name: server.Address}) + } + conf = append(conf, grp) } - conf = append(conf, grp) } // Configure AAA default authentication (from core API flat method list) @@ -2742,7 +2772,7 @@ func (p *Provider) EnsureAAA(ctx context.Context, req *provider.EnsureAAARequest Local: MapLocalFromMethodList(methods), } if methods[0].Type == v1alpha1.AAAMethodTypeGroup { - authen.Realm = AAARealmTacacs + authen.Realm = MapRealmFromGroup(methods[0].GroupName, req.AAA.Spec.ServerGroups) authen.ProviderGroup = methods[0].GroupName } else { authen.Realm = MapRealmFromMethodType(methods[0].Type) @@ -2759,7 +2789,7 @@ func (p *Provider) EnsureAAA(ctx context.Context, req *provider.EnsureAAARequest Local: MapNXOSLocal(methods), } if methods[0].Type == "Group" { - consoleAuth.Realm = AAARealmTacacs + consoleAuth.Realm = MapRealmFromGroup(methods[0].GroupName, req.AAA.Spec.ServerGroups) consoleAuth.ProviderGroup = methods[0].GroupName } else { consoleAuth.Realm = MapNXOSRealm(methods[0].Type) @@ -2801,7 +2831,7 @@ func (p *Provider) EnsureAAA(ctx context.Context, req *provider.EnsureAAARequest LocalRbac: MapLocalFromMethodList(methods) == AAAValueYes, } if methods[0].Type == v1alpha1.AAAMethodTypeGroup { - acct.Realm = AAARealmTacacs + acct.Realm = MapRealmFromGroup(methods[0].GroupName, req.AAA.Spec.ServerGroups) acct.ProviderGroup = methods[0].GroupName } else { acct.Realm = MapRealmFromMethodType(methods[0].Type) @@ -2861,24 +2891,35 @@ func (p *Provider) DeleteAAA(ctx context.Context, req *provider.DeleteAAARequest }) } - // Delete TACACS+ server groups and hosts + // Delete server groups and hosts hasTACACS := false for _, group := range req.AAA.Spec.ServerGroups { - if group.Type != v1alpha1.AAAServerGroupTypeTACACS { - continue - } - hasTACACS = true + switch group.Type { + case v1alpha1.AAAServerGroupTypeTACACS: + hasTACACS = true - grp := &TacacsPlusProviderGroup{Name: group.Name} - if err := p.client.Delete(ctx, grp); err != nil { - return err - } + grp := &TacacsPlusProviderGroup{Name: group.Name} + if err := p.client.Delete(ctx, grp); err != nil { + return err + } + for _, server := range group.Servers { + srv := &TacacsPlusProvider{Name: server.Address} + if err := p.client.Delete(ctx, srv); err != nil { + return err + } + } - for _, server := range group.Servers { - srv := &TacacsPlusProvider{Name: server.Address} - if err := p.client.Delete(ctx, srv); err != nil { + case v1alpha1.AAAServerGroupTypeRADIUS: + grp := &RadiusProviderGroup{Name: group.Name} + if err := p.client.Delete(ctx, grp); err != nil { return err } + for _, server := range group.Servers { + srv := &RadiusProvider{Name: server.Address} + if err := p.client.Delete(ctx, srv); err != nil { + return err + } + } } } diff --git a/internal/provider/cisco/nxos/user.go b/internal/provider/cisco/nxos/user.go index 1bb4a99e..447a57d7 100644 --- a/internal/provider/cisco/nxos/user.go +++ b/internal/provider/cisco/nxos/user.go @@ -25,7 +25,7 @@ type User struct { AllowExpired string `json:"allowExpired"` Expiration string `json:"expiration"` Name string `json:"name"` - Pwd string `json:"pwd,omitempty"` // #nosec G117 + Pwd string `json:"pwd,omitempty"` //nolint:gosec PwdHash PwdHashType `json:"passwordHash,omitempty"` PwdEncryptType PwdEncryptType `json:"pwdEncryptType,omitempty"` SshauthItems struct { diff --git a/internal/provider/provider.go b/internal/provider/provider.go index b8da57af..7760d8a2 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -162,7 +162,7 @@ type UserProvider interface { type EnsureUserRequest struct { Username string - Password string // #nosec G117 + Password string `json:"-"` SSHKey string Roles []string ProviderConfig *ProviderConfig @@ -580,6 +580,9 @@ type EnsureAAARequest struct { // TACACSServerKeys contains the decrypted keys for each TACACS+ server, // keyed by server address. TACACSServerKeys map[string]string + // RADIUSServerKeys contains the decrypted shared secrets for each RADIUS server, + // keyed by server address. + RADIUSServerKeys map[string]string } type DeleteAAARequest struct { diff --git a/internal/provisioning/http.go b/internal/provisioning/http.go index 2876f3f8..40c363c8 100644 --- a/internal/provisioning/http.go +++ b/internal/provisioning/http.go @@ -391,7 +391,7 @@ func (s *HTTPServer) GetMTLSClientCA(w http.ResponseWriter, r *http.Request) { type DeviceCertificateResponse struct { Certificate []byte `json:"certificate"` - PrivateKey []byte `json:"privateKey"` // #nosec G117 + PrivateKey []byte `json:"privateKey"` //nolint:gosec CACertificate []byte `json:"caCertificate"` } diff --git a/test/e2e/util.go b/test/e2e/util.go index 3eabc631..f378b279 100644 --- a/test/e2e/util.go +++ b/test/e2e/util.go @@ -10,6 +10,7 @@ import ( "fmt" "os" "os/exec" + "path/filepath" "strings" . "github.com/onsi/ginkgo/v2" //nolint:revive @@ -37,8 +38,7 @@ func Run(cmd *exec.Cmd) (string, error) { } command := strings.Join(cmd.Args, " ") - // #nosec G705 - _, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command) + _, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command) //nolint:gosec output, err := cmd.CombinedOutput() if err != nil { @@ -55,16 +55,15 @@ func Apply(resource string) error { if err != nil { return fmt.Errorf("failed to create temp file: %w", err) } - // #nosec G703 - defer func() { _ = os.Remove(file.Name()) }() + tmpPath := filepath.Clean(file.Name()) + defer func() { _ = os.Remove(tmpPath) }() if _, err = file.Write([]byte(resource)); err != nil { return fmt.Errorf("failed to write to temp file: %w", err) } if err = file.Close(); err != nil { return fmt.Errorf("failed to close temp file: %w", err) } - // #nosec G204 G702 - cmd := exec.Command("kubectl", "apply", "-f", file.Name()) + cmd := exec.Command("kubectl", "apply", "-f", tmpPath) //nolint:gosec if _, err = Run(cmd); err != nil { return fmt.Errorf("failed to apply resource: %w", err) } @@ -207,18 +206,17 @@ func LoadImageToKindClusterWithName(name string) error { return fmt.Errorf("failed to create temp file: %w", err) } _ = file.Close() - // #nosec G703 - defer func() { _ = os.Remove(file.Name()) }() + imgPath := filepath.Clean(file.Name()) + defer func() { _ = os.Remove(imgPath) }() // https://github.com/containerd/nerdctl/blob/main/docs/command-reference.md#whale-nerdctl-save // https://docs.podman.io/en/v5.3.0/markdown/podman-save.1.html - // #nosec G702 - cmd := exec.Command(prov, "save", name, "--output", file.Name()) + cmd := exec.Command(prov, "save", name, "--output", imgPath) //nolint:gosec if _, err = Run(cmd); err != nil { return fmt.Errorf("failed to save image: %w", err) } - cmd = exec.Command("kind", "load", "image-archive", file.Name(), "--name", cluster) //nolint:gosec + cmd = exec.Command("kind", "load", "image-archive", imgPath, "--name", cluster) //nolint:gosec _, err = Run(cmd) return err } diff --git a/test/lab/main_test.go b/test/lab/main_test.go index 6f7e4780..b56e5340 100644 --- a/test/lab/main_test.go +++ b/test/lab/main_test.go @@ -158,7 +158,7 @@ func Apply() script.Cmd { var Endpoint = struct { Addr string User string - Pass string // #nosec G117 + Pass string `json:"-"` }{} // ReadEnv reads required environment variables and populates the global Endpoint struct. From 98588a4be78761d145a00c13a311817c3d1cef1c Mon Sep 17 00:00:00 2001 From: i521907 Date: Thu, 12 Mar 2026 10:12:28 -0400 Subject: [PATCH 5/5] Fix unused nolint:gosec directives --- hack/provider/main.go | 4 ++-- internal/provider/cisco/nxos/user.go | 2 +- test/e2e/util.go | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/hack/provider/main.go b/hack/provider/main.go index d7b9f77c..a29b277d 100644 --- a/hack/provider/main.go +++ b/hack/provider/main.go @@ -86,7 +86,7 @@ func (r *refStoreReader) List(ctx context.Context, list client.ObjectList, opts func usage() { base := filepath.Base(os.Args[0]) - fmt.Fprintf(os.Stderr, "Usage: %s [flags] \n\n", base) //nolint:gosec + fmt.Fprintf(os.Stderr, "Usage: %s [flags] \n\n", base) fmt.Fprintf(os.Stderr, "A debug tool for testing provider implementations.\n\n") fmt.Fprintf(os.Stderr, "This tool allows you to directly test provider implementations by creating or\n") fmt.Fprintf(os.Stderr, "deleting resources on network devices.\n\n") @@ -95,7 +95,7 @@ func usage() { fmt.Fprintf(os.Stderr, "Flags:\n") flag.PrintDefaults() fmt.Fprintf(os.Stderr, "\nExample:\n") - fmt.Fprintf(os.Stderr, " %s -address=192.168.1.1:9339 -username=admin -password=secret -file=config/samples/v1alpha1_interface.yaml create\n", base) //nolint:gosec + fmt.Fprintf(os.Stderr, " %s -address=192.168.1.1:9339 -username=admin -password=secret -file=config/samples/v1alpha1_interface.yaml create\n", base) } func validateFlags() error { diff --git a/internal/provider/cisco/nxos/user.go b/internal/provider/cisco/nxos/user.go index 447a57d7..16e1ddc9 100644 --- a/internal/provider/cisco/nxos/user.go +++ b/internal/provider/cisco/nxos/user.go @@ -25,7 +25,7 @@ type User struct { AllowExpired string `json:"allowExpired"` Expiration string `json:"expiration"` Name string `json:"name"` - Pwd string `json:"pwd,omitempty"` //nolint:gosec + Pwd string `json:"pwd,omitempty"` PwdHash PwdHashType `json:"passwordHash,omitempty"` PwdEncryptType PwdEncryptType `json:"pwdEncryptType,omitempty"` SshauthItems struct { diff --git a/test/e2e/util.go b/test/e2e/util.go index f378b279..474e6d74 100644 --- a/test/e2e/util.go +++ b/test/e2e/util.go @@ -38,7 +38,7 @@ func Run(cmd *exec.Cmd) (string, error) { } command := strings.Join(cmd.Args, " ") - _, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command) //nolint:gosec + _, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command) output, err := cmd.CombinedOutput() if err != nil { @@ -63,7 +63,7 @@ func Apply(resource string) error { if err = file.Close(); err != nil { return fmt.Errorf("failed to close temp file: %w", err) } - cmd := exec.Command("kubectl", "apply", "-f", tmpPath) //nolint:gosec + cmd := exec.Command("kubectl", "apply", "-f", tmpPath) if _, err = Run(cmd); err != nil { return fmt.Errorf("failed to apply resource: %w", err) } @@ -211,12 +211,12 @@ func LoadImageToKindClusterWithName(name string) error { // https://github.com/containerd/nerdctl/blob/main/docs/command-reference.md#whale-nerdctl-save // https://docs.podman.io/en/v5.3.0/markdown/podman-save.1.html - cmd := exec.Command(prov, "save", name, "--output", imgPath) //nolint:gosec + cmd := exec.Command(prov, "save", name, "--output", imgPath) if _, err = Run(cmd); err != nil { return fmt.Errorf("failed to save image: %w", err) } - cmd = exec.Command("kind", "load", "image-archive", imgPath, "--name", cluster) //nolint:gosec + cmd = exec.Command("kind", "load", "image-archive", imgPath, "--name", cluster) _, err = Run(cmd) return err }