From 0093287a815f0646e0be82a6525a6e56e0b542c2 Mon Sep 17 00:00:00 2001 From: Gor <37933026+pogossian@users.noreply.github.com> Date: Mon, 21 Mar 2022 20:44:30 +0400 Subject: [PATCH] Release 1.3.0 (#62) New Features: - Added Nat CRD Bug Fixes: - Memory leak caused by the EventBroadcaster --- PROJECT | 18 ++ api/v1alpha1/nat_types.go | 112 ++++++++ api/v1alpha1/natmeta_types.go | 80 ++++++ api/v1alpha1/zz_generated.deepcopy.go | 178 +++++++++++++ config/crd/bases/k8s.netris.ai_natmeta.yaml | 103 ++++++++ config/crd/bases/k8s.netris.ai_nats.yaml | 160 ++++++++++++ config/crd/kustomization.yaml | 6 + .../crd/patches/cainjection_in_natmeta.yaml | 7 + config/crd/patches/cainjection_in_nats.yaml | 7 + config/crd/patches/webhook_in_natmeta.yaml | 16 ++ config/crd/patches/webhook_in_nats.yaml | 16 ++ config/rbac/nat_editor_role.yaml | 24 ++ config/rbac/nat_viewer_role.yaml | 20 ++ config/rbac/natmeta_editor_role.yaml | 24 ++ config/rbac/natmeta_viewer_role.yaml | 20 ++ config/rbac/role.yaml | 52 ++++ controllers/controller.go | 15 ++ controllers/nat_controller.go | 233 +++++++++++++++++ controllers/nat_translations.go | 232 +++++++++++++++++ controllers/natmeta_controller.go | 244 ++++++++++++++++++ deploy/charts/netris-operator/Chart.yaml | 4 +- .../crds/k8s.netris.ai_natmeta.yaml | 103 ++++++++ .../crds/k8s.netris.ai_nats.yaml | 160 ++++++++++++ .../netris-operator/templates/rbac.yaml | 52 ++++ go.sum | 2 - main.go | 21 ++ netrisstorage/nat.go | 103 ++++++++ netrisstorage/storage.go | 6 + samples/README.md | 47 +++- samples/allocation.yaml | 8 + samples/kustomization.yaml | 1 + samples/l4lb.yaml | 8 +- samples/nat.yaml | 44 ++++ samples/subnet.yaml | 11 + samples/vnet.yaml | 2 +- 35 files changed, 2126 insertions(+), 13 deletions(-) create mode 100644 api/v1alpha1/nat_types.go create mode 100644 api/v1alpha1/natmeta_types.go create mode 100644 config/crd/bases/k8s.netris.ai_natmeta.yaml create mode 100644 config/crd/bases/k8s.netris.ai_nats.yaml create mode 100644 config/crd/patches/cainjection_in_natmeta.yaml create mode 100644 config/crd/patches/cainjection_in_nats.yaml create mode 100644 config/crd/patches/webhook_in_natmeta.yaml create mode 100644 config/crd/patches/webhook_in_nats.yaml create mode 100644 config/rbac/nat_editor_role.yaml create mode 100644 config/rbac/nat_viewer_role.yaml create mode 100644 config/rbac/natmeta_editor_role.yaml create mode 100644 config/rbac/natmeta_viewer_role.yaml create mode 100644 controllers/nat_controller.go create mode 100644 controllers/nat_translations.go create mode 100644 controllers/natmeta_controller.go create mode 100644 deploy/charts/netris-operator/crds/k8s.netris.ai_natmeta.yaml create mode 100644 deploy/charts/netris-operator/crds/k8s.netris.ai_nats.yaml create mode 100644 netrisstorage/nat.go create mode 100644 samples/nat.yaml diff --git a/PROJECT b/PROJECT index 0cf55f4..c40c9f8 100644 --- a/PROJECT +++ b/PROJECT @@ -187,4 +187,22 @@ resources: kind: LinkMeta path: github.com/netrisai/netris-operator/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: netris.ai + group: k8s + kind: Nat + path: github.com/netrisai/netris-operator/api/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: netris.ai + group: k8s + kind: NatMeta + path: github.com/netrisai/netris-operator/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/api/v1alpha1/nat_types.go b/api/v1alpha1/nat_types.go new file mode 100644 index 0000000..9ff38a4 --- /dev/null +++ b/api/v1alpha1/nat_types.go @@ -0,0 +1,112 @@ +/* +Copyright 2021. Netris, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// NatSpec defines the desired state of Nat +type NatSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + Comment string `json:"comment,omitempty"` + + // +kubebuilder:validation:Enum=enabled;disabled + State string `json:"state,omitempty"` + Site string `json:"site"` + + // +kubebuilder:validation:Enum=dnat;snat;accept_snat;masquerade + Action string `json:"action"` + + // +kubebuilder:validation:Enum=all;tcp;udp;icmp + Protocol string `json:"protocol"` + + // +kubebuilder:validation:Pattern=`(^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12]\d|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/([1-9]|[1-5][0-9]|6[0-4]))?$)` + SrcAddress string `json:"srcAddress"` + + SrcPort string `json:"srcPort,omitempty"` + + // +kubebuilder:validation:Pattern=`(^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12]\d|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/([1-9]|[1-5][0-9]|6[0-4]))?$)` + DstAddress string `json:"dstAddress"` + + DstPort string `json:"dstPort,omitempty"` + + // +kubebuilder:validation:Pattern=`^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$` + SnatToIP string `json:"snatToIp,omitempty"` + + // +kubebuilder:validation:Pattern=`(^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12]\d|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/([1-9]|[1-5][0-9]|6[0-4]))?$)` + SnatToPool string `json:"snatToPool,omitempty"` + + // +kubebuilder:validation:Pattern=`(^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12]\d|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/([1-9]|[1-5][0-9]|6[0-4]))?$)` + DnatToIP string `json:"dnatToIp,omitempty"` + + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + DnatToPort int `json:"dnatToPort,omitempty"` +} + +// NatStatus defines the observed state of Nat +type NatStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + Status string `json:"status,omitempty"` + Message string `json:"message,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="State",type=string,JSONPath=`.spec.state` +// +kubebuilder:printcolumn:name="Site",type=string,JSONPath=`.spec.site` +// +kubebuilder:printcolumn:name="Action",type=string,JSONPath=`.spec.action` +// +kubebuilder:printcolumn:name="Protocol",type=string,JSONPath=`.spec.protocol` +// +kubebuilder:printcolumn:name="SrcAddress",type=string,JSONPath=`.spec.srcAddress` +// +kubebuilder:printcolumn:name="SrcPort",type=integer,JSONPath=`.spec.srcPort` +// +kubebuilder:printcolumn:name="DstAddress",type=string,JSONPath=`.spec.dstAddress` +// +kubebuilder:printcolumn:name="DstPort",type=integer,JSONPath=`.spec.dstPort` +// +kubebuilder:printcolumn:name="SNATToIP",type=string,JSONPath=`.spec.snatToIp`,priority=1 +// +kubebuilder:printcolumn:name="SNATToPool",type=string,JSONPath=`.spec.snatToPool`,priority=1 +// +kubebuilder:printcolumn:name="DNATToIP",type=string,JSONPath=`.spec.dnatToIp`,priority=1 +// +kubebuilder:printcolumn:name="DNATToPort",type=integer,JSONPath=`.spec.dnatToPort`,priority=1 +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status` +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` + +// Nat is the Schema for the nats API +type Nat struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NatSpec `json:"spec,omitempty"` + Status NatStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// NatList contains a list of Nat +type NatList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Nat `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Nat{}, &NatList{}) +} diff --git a/api/v1alpha1/natmeta_types.go b/api/v1alpha1/natmeta_types.go new file mode 100644 index 0000000..e0b6f26 --- /dev/null +++ b/api/v1alpha1/natmeta_types.go @@ -0,0 +1,80 @@ +/* +Copyright 2021. Netris, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// NatMetaSpec defines the desired state of NatMeta +type NatMetaSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + Imported bool `json:"imported"` + Reclaim bool `json:"reclaimPolicy"` + NatCRGeneration int64 `json:"natGeneration"` + ID int `json:"id"` + NatName string `json:"natName"` + + Comment string `json:"comment,omitempty"` + State string `json:"state,omitempty"` + SiteID int `json:"siteID"` + Action string `json:"action"` + Protocol string `json:"protocol"` + SrcAddress string `json:"srcAddress"` + SrcPort string `json:"srcPort,omitempty"` + DstAddress string `json:"dstAddress"` + DstPort string `json:"dstPort,omitempty"` + SnatToIP string `json:"snatToIp,omitempty"` + SnatToPool string `json:"snatToPool,omitempty"` + DnatToIP string `json:"dnatToIp,omitempty"` + DnatToPort int `json:"dnatToPort,omitempty"` +} + +// NatMetaStatus defines the observed state of NatMeta +type NatMetaStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// NatMeta is the Schema for the natmeta API +type NatMeta struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NatMetaSpec `json:"spec,omitempty"` + Status NatMetaStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// NatMetaList contains a list of NatMeta +type NatMetaList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NatMeta `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NatMeta{}, &NatMetaList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 6d68db8..0cf0340 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1085,6 +1085,184 @@ func (in *LinkStatus) DeepCopy() *LinkStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Nat) DeepCopyInto(out *Nat) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Nat. +func (in *Nat) DeepCopy() *Nat { + if in == nil { + return nil + } + out := new(Nat) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Nat) 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 *NatList) DeepCopyInto(out *NatList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Nat, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NatList. +func (in *NatList) DeepCopy() *NatList { + if in == nil { + return nil + } + out := new(NatList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NatList) 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 *NatMeta) DeepCopyInto(out *NatMeta) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NatMeta. +func (in *NatMeta) DeepCopy() *NatMeta { + if in == nil { + return nil + } + out := new(NatMeta) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NatMeta) 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 *NatMetaList) DeepCopyInto(out *NatMetaList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NatMeta, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NatMetaList. +func (in *NatMetaList) DeepCopy() *NatMetaList { + if in == nil { + return nil + } + out := new(NatMetaList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NatMetaList) 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 *NatMetaSpec) DeepCopyInto(out *NatMetaSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NatMetaSpec. +func (in *NatMetaSpec) DeepCopy() *NatMetaSpec { + if in == nil { + return nil + } + out := new(NatMetaSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NatMetaStatus) DeepCopyInto(out *NatMetaStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NatMetaStatus. +func (in *NatMetaStatus) DeepCopy() *NatMetaStatus { + if in == nil { + return nil + } + out := new(NatMetaStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NatSpec) DeepCopyInto(out *NatSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NatSpec. +func (in *NatSpec) DeepCopy() *NatSpec { + if in == nil { + return nil + } + out := new(NatSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NatStatus) DeepCopyInto(out *NatStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NatStatus. +func (in *NatStatus) DeepCopy() *NatStatus { + if in == nil { + return nil + } + out := new(NatStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Site) DeepCopyInto(out *Site) { *out = *in diff --git a/config/crd/bases/k8s.netris.ai_natmeta.yaml b/config/crd/bases/k8s.netris.ai_natmeta.yaml new file mode 100644 index 0000000..53688ef --- /dev/null +++ b/config/crd/bases/k8s.netris.ai_natmeta.yaml @@ -0,0 +1,103 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + creationTimestamp: null + name: natmeta.k8s.netris.ai +spec: + group: k8s.netris.ai + names: + kind: NatMeta + listKind: NatMetaList + plural: natmeta + singular: natmeta + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: NatMeta is the Schema for the natmeta 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: NatMetaSpec defines the desired state of NatMeta + properties: + action: + type: string + comment: + type: string + dnatToIp: + type: string + dnatToPort: + type: integer + dstAddress: + type: string + dstPort: + type: string + id: + type: integer + imported: + description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "make" to regenerate code after modifying this file' + type: boolean + natGeneration: + format: int64 + type: integer + natName: + type: string + protocol: + type: string + reclaimPolicy: + type: boolean + siteID: + type: integer + snatToIp: + type: string + snatToPool: + type: string + srcAddress: + type: string + srcPort: + type: string + state: + type: string + required: + - action + - dstAddress + - id + - imported + - natGeneration + - natName + - protocol + - reclaimPolicy + - siteID + - srcAddress + type: object + status: + description: NatMetaStatus defines the observed state of NatMeta + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/bases/k8s.netris.ai_nats.yaml b/config/crd/bases/k8s.netris.ai_nats.yaml new file mode 100644 index 0000000..cd61776 --- /dev/null +++ b/config/crd/bases/k8s.netris.ai_nats.yaml @@ -0,0 +1,160 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + creationTimestamp: null + name: nats.k8s.netris.ai +spec: + group: k8s.netris.ai + names: + kind: Nat + listKind: NatList + plural: nats + singular: nat + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.state + name: State + type: string + - jsonPath: .spec.site + name: Site + type: string + - jsonPath: .spec.action + name: Action + type: string + - jsonPath: .spec.protocol + name: Protocol + type: string + - jsonPath: .spec.srcAddress + name: SrcAddress + type: string + - jsonPath: .spec.srcPort + name: SrcPort + type: integer + - jsonPath: .spec.dstAddress + name: DstAddress + type: string + - jsonPath: .spec.dstPort + name: DstPort + type: integer + - jsonPath: .spec.snatToIp + name: SNATToIP + priority: 1 + type: string + - jsonPath: .spec.snatToPool + name: SNATToPool + priority: 1 + type: string + - jsonPath: .spec.dnatToIp + name: DNATToIP + priority: 1 + type: string + - jsonPath: .spec.dnatToPort + name: DNATToPort + priority: 1 + type: integer + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: Nat is the Schema for the nats 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: NatSpec defines the desired state of Nat + properties: + action: + enum: + - dnat + - snat + - accept_snat + - masquerade + type: string + comment: + type: string + dnatToIp: + pattern: (^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12]\d|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/([1-9]|[1-5][0-9]|6[0-4]))?$) + type: string + dnatToPort: + maximum: 65535 + minimum: 1 + type: integer + dstAddress: + pattern: (^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12]\d|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/([1-9]|[1-5][0-9]|6[0-4]))?$) + type: string + dstPort: + type: string + protocol: + enum: + - all + - tcp + - udp + - icmp + type: string + site: + type: string + snatToIp: + pattern: ^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ + type: string + snatToPool: + pattern: (^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12]\d|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/([1-9]|[1-5][0-9]|6[0-4]))?$) + type: string + srcAddress: + pattern: (^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12]\d|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/([1-9]|[1-5][0-9]|6[0-4]))?$) + type: string + srcPort: + type: string + state: + enum: + - enabled + - disabled + type: string + required: + - action + - dstAddress + - protocol + - site + - srcAddress + type: object + status: + description: NatStatus defines the observed state of Nat + properties: + message: + type: string + status: + description: 'INSERT ADDITIONAL STATUS FIELD - define observed state + of cluster Important: Run "make" to regenerate code after modifying + this file' + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 8c5417e..271300b 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -22,6 +22,8 @@ resources: - bases/k8s.netris.ai_controllermeta.yaml - bases/k8s.netris.ai_links.yaml - bases/k8s.netris.ai_linkmeta.yaml +- bases/k8s.netris.ai_nats.yaml +- bases/k8s.netris.ai_natmeta.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -47,6 +49,8 @@ patchesStrategicMerge: #- patches/webhook_in_controllermeta.yaml #- patches/webhook_in_links.yaml #- patches/webhook_in_linkmeta.yaml +#- patches/webhook_in_nats.yaml +#- patches/webhook_in_natmeta.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. @@ -71,6 +75,8 @@ patchesStrategicMerge: #- patches/cainjection_in_controllermeta.yaml #- patches/cainjection_in_links.yaml #- patches/cainjection_in_linkmeta.yaml +#- patches/cainjection_in_nats.yaml +#- patches/cainjection_in_natmeta.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_natmeta.yaml b/config/crd/patches/cainjection_in_natmeta.yaml new file mode 100644 index 0000000..7f0395c --- /dev/null +++ b/config/crd/patches/cainjection_in_natmeta.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: natmeta.k8s.netris.ai diff --git a/config/crd/patches/cainjection_in_nats.yaml b/config/crd/patches/cainjection_in_nats.yaml new file mode 100644 index 0000000..4bfac17 --- /dev/null +++ b/config/crd/patches/cainjection_in_nats.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: nats.k8s.netris.ai diff --git a/config/crd/patches/webhook_in_natmeta.yaml b/config/crd/patches/webhook_in_natmeta.yaml new file mode 100644 index 0000000..6fc6a25 --- /dev/null +++ b/config/crd/patches/webhook_in_natmeta.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: natmeta.k8s.netris.ai +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_nats.yaml b/config/crd/patches/webhook_in_nats.yaml new file mode 100644 index 0000000..0d0eda8 --- /dev/null +++ b/config/crd/patches/webhook_in_nats.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: nats.k8s.netris.ai +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/nat_editor_role.yaml b/config/rbac/nat_editor_role.yaml new file mode 100644 index 0000000..5b720ba --- /dev/null +++ b/config/rbac/nat_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit nats. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: nat-editor-role +rules: +- apiGroups: + - k8s.netris.ai + resources: + - nats + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - k8s.netris.ai + resources: + - nats/status + verbs: + - get diff --git a/config/rbac/nat_viewer_role.yaml b/config/rbac/nat_viewer_role.yaml new file mode 100644 index 0000000..6884856 --- /dev/null +++ b/config/rbac/nat_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view nats. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: nat-viewer-role +rules: +- apiGroups: + - k8s.netris.ai + resources: + - nats + verbs: + - get + - list + - watch +- apiGroups: + - k8s.netris.ai + resources: + - nats/status + verbs: + - get diff --git a/config/rbac/natmeta_editor_role.yaml b/config/rbac/natmeta_editor_role.yaml new file mode 100644 index 0000000..a03f334 --- /dev/null +++ b/config/rbac/natmeta_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit natmeta. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: natmeta-editor-role +rules: +- apiGroups: + - k8s.netris.ai + resources: + - natmeta + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - k8s.netris.ai + resources: + - natmeta/status + verbs: + - get diff --git a/config/rbac/natmeta_viewer_role.yaml b/config/rbac/natmeta_viewer_role.yaml new file mode 100644 index 0000000..eabcd0c --- /dev/null +++ b/config/rbac/natmeta_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view natmeta. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: natmeta-viewer-role +rules: +- apiGroups: + - k8s.netris.ai + resources: + - natmeta + verbs: + - get + - list + - watch +- apiGroups: + - k8s.netris.ai + resources: + - natmeta/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 45e41b3..6e31254 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -337,6 +337,58 @@ rules: - get - patch - update +- apiGroups: + - k8s.netris.ai + resources: + - natmeta + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - k8s.netris.ai + resources: + - natmeta/finalizers + verbs: + - update +- apiGroups: + - k8s.netris.ai + resources: + - natmeta/status + verbs: + - get + - patch + - update +- apiGroups: + - k8s.netris.ai + resources: + - nats + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - k8s.netris.ai + resources: + - nats/finalizers + verbs: + - update +- apiGroups: + - k8s.netris.ai + resources: + - nats/status + verbs: + - get + - patch + - update - apiGroups: - k8s.netris.ai resources: diff --git a/controllers/controller.go b/controllers/controller.go index 203aded..f916352 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -208,6 +208,21 @@ func (u *uniReconciler) patchControllerStatus(controller *k8sv1alpha1.Controller return ctrl.Result{RequeueAfter: requeueInterval}, nil } +func (u *uniReconciler) patchNatStatus(nat *k8sv1alpha1.Nat, status, message string) (ctrl.Result, error) { + u.DebugLogger.Info("Patching Status", "status", status, "message", message) + + nat.Status.Status = status + nat.Status.Message = message + + ctx, cancel := context.WithTimeout(cntxt, contextTimeout) + defer cancel() + err := u.Status().Patch(ctx, nat.DeepCopyObject(), client.Merge, &client.PatchOptions{}) + if err != nil { + u.DebugLogger.Info("{r.Status().Patch}", "error", err, "action", "status update") + } + return ctrl.Result{RequeueAfter: requeueInterval}, nil +} + func (u *uniReconciler) patchLinkStatus(link *k8sv1alpha1.Link, status, message string) (ctrl.Result, error) { u.DebugLogger.Info("Patching Status", "status", status, "message", message) diff --git a/controllers/nat_controller.go b/controllers/nat_controller.go new file mode 100644 index 0000000..6bdaf76 --- /dev/null +++ b/controllers/nat_controller.go @@ -0,0 +1,233 @@ +/* +Copyright 2021. Netris, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "fmt" + + "go.uber.org/zap/zapcore" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/go-logr/logr" + k8sv1alpha1 "github.com/netrisai/netris-operator/api/v1alpha1" + "github.com/netrisai/netris-operator/netrisstorage" + "github.com/netrisai/netriswebapi/http" + api "github.com/netrisai/netriswebapi/v2" +) + +// NatReconciler reconciles a Nat object +type NatReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + Cred *api.Clientset + NStorage *netrisstorage.Storage +} + +//+kubebuilder:rbac:groups=k8s.netris.ai,resources=nats,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=k8s.netris.ai,resources=nats/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=k8s.netris.ai,resources=nats/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Nat object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.2/pkg/reconcile +func (r *NatReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + logger := r.Log.WithValues("name", req.NamespacedName) + debugLogger := logger.V(int(zapcore.WarnLevel)) + nat := &k8sv1alpha1.Nat{} + + u := uniReconciler{ + Client: r.Client, + Logger: logger, + DebugLogger: debugLogger, + Cred: r.Cred, + NStorage: r.NStorage, + } + + natCtx, natCancel := context.WithTimeout(cntxt, contextTimeout) + defer natCancel() + if err := r.Get(natCtx, req.NamespacedName, nat); err != nil { + if errors.IsNotFound(err) { + debugLogger.Info(err.Error()) + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + natMetaNamespaced := req.NamespacedName + natMetaNamespaced.Name = string(nat.GetUID()) + natMeta := &k8sv1alpha1.NatMeta{} + metaFound := true + + natMetaCtx, natMetaCancel := context.WithTimeout(cntxt, contextTimeout) + defer natMetaCancel() + if err := r.Get(natMetaCtx, natMetaNamespaced, natMeta); err != nil { + if errors.IsNotFound(err) { + debugLogger.Info(err.Error()) + metaFound = false + natMeta = nil + } else { + return ctrl.Result{}, err + } + } + + if nat.DeletionTimestamp != nil { + logger.Info("Go to delete") + _, err := r.deleteNat(nat, natMeta) + if err != nil { + logger.Error(fmt.Errorf("{deleteNat} %s", err), "") + return u.patchNatStatus(nat, "Failure", err.Error()) + } + logger.Info("Nat deleted") + return ctrl.Result{}, nil + } + + if natMustUpdateAnnotations(nat) { + debugLogger.Info("Setting default annotations") + natUpdateDefaultAnnotations(nat) + natPatchCtx, natPatchCancel := context.WithTimeout(cntxt, contextTimeout) + defer natPatchCancel() + err := r.Patch(natPatchCtx, nat.DeepCopyObject(), client.Merge, &client.PatchOptions{}) + if err != nil { + logger.Error(fmt.Errorf("{Patch Nat default annotations} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + return ctrl.Result{}, nil + } + + if metaFound { + debugLogger.Info("Meta found") + if natCompareFieldsForNewMeta(nat, natMeta) { + debugLogger.Info("Generating New Meta") + natID := natMeta.Spec.ID + newVnetMeta, err := r.NatToNatMeta(nat) + if err != nil { + logger.Error(fmt.Errorf("{NatToNatMeta} %s", err), "") + return u.patchNatStatus(nat, "Failure", err.Error()) + } + natMeta.Spec = newVnetMeta.DeepCopy().Spec + natMeta.Spec.ID = natID + natMeta.Spec.NatCRGeneration = nat.GetGeneration() + + natMetaUpdateCtx, natMetaUpdateCancel := context.WithTimeout(cntxt, contextTimeout) + defer natMetaUpdateCancel() + err = r.Update(natMetaUpdateCtx, natMeta.DeepCopyObject(), &client.UpdateOptions{}) + if err != nil { + logger.Error(fmt.Errorf("{natMeta Update} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + } + } else { + debugLogger.Info("Meta not found") + if nat.GetFinalizers() == nil { + nat.SetFinalizers([]string{"resource.k8s.netris.ai/delete"}) + + natPatchCtx, natPatchCancel := context.WithTimeout(cntxt, contextTimeout) + defer natPatchCancel() + err := r.Patch(natPatchCtx, nat.DeepCopyObject(), client.Merge, &client.PatchOptions{}) + if err != nil { + logger.Error(fmt.Errorf("{Patch Nat Finalizer} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + return ctrl.Result{}, nil + } + + natMeta, err := r.NatToNatMeta(nat) + if err != nil { + logger.Error(fmt.Errorf("{NatToNatMeta} %s", err), "") + return u.patchNatStatus(nat, "Failure", err.Error()) + } + + natMeta.Spec.NatCRGeneration = nat.GetGeneration() + + natMetaCreateCtx, natMetaCreateCancel := context.WithTimeout(cntxt, contextTimeout) + defer natMetaCreateCancel() + if err := r.Create(natMetaCreateCtx, natMeta.DeepCopyObject(), &client.CreateOptions{}); err != nil { + logger.Error(fmt.Errorf("{natMeta Create} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + } + + return ctrl.Result{RequeueAfter: requeueInterval}, nil +} + +func (r *NatReconciler) deleteNat(nat *k8sv1alpha1.Nat, natMeta *k8sv1alpha1.NatMeta) (ctrl.Result, error) { + if natMeta != nil && natMeta.Spec.ID > 0 && !natMeta.Spec.Reclaim { + reply, err := r.Cred.NAT().Delete(natMeta.Spec.ID) + if err != nil { + return ctrl.Result{}, fmt.Errorf("{deleteNat} %s", err) + } + resp, err := http.ParseAPIResponse(reply.Data) + if err != nil { + return ctrl.Result{}, err + } + if !resp.IsSuccess { + return ctrl.Result{}, fmt.Errorf("{deleteNat} %s", fmt.Errorf(resp.Message)) + } + } + return r.deleteCRs(nat, natMeta) +} + +func (r *NatReconciler) deleteCRs(nat *k8sv1alpha1.Nat, natMeta *k8sv1alpha1.NatMeta) (ctrl.Result, error) { + if natMeta != nil { + _, err := r.deleteNatMetaCR(natMeta) + if err != nil { + return ctrl.Result{}, fmt.Errorf("{deleteCRs} %s", err) + } + } + + return r.deleteNatCR(nat) +} + +func (r *NatReconciler) deleteNatCR(nat *k8sv1alpha1.Nat) (ctrl.Result, error) { + nat.ObjectMeta.SetFinalizers(nil) + nat.SetFinalizers(nil) + ctx, cancel := context.WithTimeout(cntxt, contextTimeout) + defer cancel() + if err := r.Update(ctx, nat.DeepCopyObject(), &client.UpdateOptions{}); err != nil { + return ctrl.Result{}, fmt.Errorf("{deleteNatCR} %s", err) + } + + return ctrl.Result{}, nil +} + +func (r *NatReconciler) deleteNatMetaCR(natMeta *k8sv1alpha1.NatMeta) (ctrl.Result, error) { + ctx, cancel := context.WithTimeout(cntxt, contextTimeout) + defer cancel() + if err := r.Delete(ctx, natMeta.DeepCopyObject(), &client.DeleteOptions{}); err != nil { + return ctrl.Result{}, fmt.Errorf("{deleteNatMetaCR} %s", err) + } + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *NatReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&k8sv1alpha1.Nat{}). + Complete(r) +} diff --git a/controllers/nat_translations.go b/controllers/nat_translations.go new file mode 100644 index 0000000..625ebf8 --- /dev/null +++ b/controllers/nat_translations.go @@ -0,0 +1,232 @@ +/* +Copyright 2021. Netris, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "fmt" + "strconv" + "strings" + + k8sv1alpha1 "github.com/netrisai/netris-operator/api/v1alpha1" + "github.com/netrisai/netriswebapi/v2/types/nat" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// NatToNatMeta converts the Nat resource to NatMeta type and used for add the Nat for Netris API. +func (r *NatReconciler) NatToNatMeta(nat *k8sv1alpha1.Nat) (*k8sv1alpha1.NatMeta, error) { + var ( + imported = false + reclaim = false + ) + + if i, ok := nat.GetAnnotations()["resource.k8s.netris.ai/import"]; ok && i == "true" { + imported = true + } + if i, ok := nat.GetAnnotations()["resource.k8s.netris.ai/reclaimPolicy"]; ok && i == "retain" { + reclaim = true + } + + siteID := 0 + if site, ok := r.NStorage.SitesStorage.FindByName(nat.Spec.Site); ok { + siteID = site.ID + } else { + return nil, fmt.Errorf("Invalid site '%s'", nat.Spec.Site) + } + + state := nat.Spec.State + if nat.Spec.State == "" { + state = "enabled" + } + + natMeta := &k8sv1alpha1.NatMeta{ + ObjectMeta: metav1.ObjectMeta{ + Name: string(nat.GetUID()), + Namespace: nat.GetNamespace(), + }, + TypeMeta: metav1.TypeMeta{}, + Spec: k8sv1alpha1.NatMetaSpec{ + Imported: imported, + Reclaim: reclaim, + NatName: nat.Name, + Comment: nat.Spec.Comment, + State: state, + SiteID: siteID, + Action: strings.ToUpper(nat.Spec.Action), + Protocol: nat.Spec.Protocol, + SrcAddress: nat.Spec.SrcAddress, + SrcPort: nat.Spec.SrcPort, + DstAddress: nat.Spec.DstAddress, + DstPort: nat.Spec.DstPort, + SnatToIP: nat.Spec.SnatToIP, + SnatToPool: nat.Spec.SnatToPool, + DnatToIP: nat.Spec.DnatToIP, + DnatToPort: nat.Spec.DnatToPort, + }, + } + + return natMeta, nil +} + +func natCompareFieldsForNewMeta(nat *k8sv1alpha1.Nat, natMeta *k8sv1alpha1.NatMeta) bool { + imported := false + reclaim := false + if i, ok := nat.GetAnnotations()["resource.k8s.netris.ai/import"]; ok && i == "true" { + imported = true + } + if i, ok := nat.GetAnnotations()["resource.k8s.netris.ai/reclaimPolicy"]; ok && i == "retain" { + reclaim = true + } + return nat.GetGeneration() != natMeta.Spec.NatCRGeneration || imported != natMeta.Spec.Imported || reclaim != natMeta.Spec.Reclaim +} + +func natMustUpdateAnnotations(nat *k8sv1alpha1.Nat) bool { + update := false + if i, ok := nat.GetAnnotations()["resource.k8s.netris.ai/import"]; !(ok && (i == "true" || i == "false")) { + update = true + } + if i, ok := nat.GetAnnotations()["resource.k8s.netris.ai/reclaimPolicy"]; !(ok && (i == "retain" || i == "delete")) { + update = true + } + return update +} + +func natUpdateDefaultAnnotations(nat *k8sv1alpha1.Nat) { + imported := "false" + reclaim := "delete" + if i, ok := nat.GetAnnotations()["resource.k8s.netris.ai/import"]; ok && i == "true" { + imported = "true" + } + if i, ok := nat.GetAnnotations()["resource.k8s.netris.ai/reclaimPolicy"]; ok && i == "retain" { + reclaim = "retain" + } + annotations := nat.GetAnnotations() + annotations["resource.k8s.netris.ai/import"] = imported + annotations["resource.k8s.netris.ai/reclaimPolicy"] = reclaim + nat.SetAnnotations(annotations) +} + +// NatMetaToNetris converts the k8s Nat resource to Netris type and used for add the Nat for Netris API. +func NatMetaToNetris(natMeta *k8sv1alpha1.NatMeta) (*nat.NATw, error) { + natAdd := &nat.NATw{ + Name: natMeta.Spec.NatName, + Comment: natMeta.Spec.Comment, + State: natMeta.Spec.State, + Site: nat.IDName{ID: natMeta.Spec.SiteID}, + Action: natMeta.Spec.Action, + Protocol: natMeta.Spec.Protocol, + SourceAddress: natMeta.Spec.SrcAddress, + SourcePort: natMeta.Spec.SrcPort, + DestinationAddress: natMeta.Spec.DstAddress, + DestinationPort: natMeta.Spec.SrcPort, + SnatToIP: natMeta.Spec.SnatToIP, + SnatToPool: natMeta.Spec.SnatToPool, + DnatToIP: natMeta.Spec.DnatToIP, + DnatToPort: strconv.Itoa(natMeta.Spec.DnatToPort), + } + + return natAdd, nil +} + +// NatMetaToNetrisUpdate converts the k8s Nat resource to Netris type and used for update the Nat for Netris API. +func NatMetaToNetrisUpdate(natMeta *k8sv1alpha1.NatMeta) (*nat.NATw, error) { + natAdd := &nat.NATw{ + Name: natMeta.Spec.NatName, + Comment: natMeta.Spec.Comment, + State: natMeta.Spec.State, + Site: nat.IDName{ID: natMeta.Spec.SiteID}, + Action: natMeta.Spec.Action, + Protocol: natMeta.Spec.Protocol, + SourceAddress: natMeta.Spec.SrcAddress, + SourcePort: natMeta.Spec.SrcPort, + DestinationAddress: natMeta.Spec.DstAddress, + DestinationPort: natMeta.Spec.DstPort, + SnatToIP: natMeta.Spec.SnatToIP, + SnatToPool: natMeta.Spec.SnatToPool, + DnatToIP: natMeta.Spec.DnatToIP, + DnatToPort: strconv.Itoa(natMeta.Spec.DnatToPort), + } + + return natAdd, nil +} + +func compareNatMetaAPIENat(natMeta *k8sv1alpha1.NatMeta, apiNat *nat.NAT, u uniReconciler) bool { + if apiNat.Name != natMeta.Spec.NatName { + u.DebugLogger.Info("Name changed", "netrisValue", apiNat.Name, "k8sValue", natMeta.Spec.NatName) + return false + } + if apiNat.Comment != natMeta.Spec.Comment { + u.DebugLogger.Info("Comment changed", "netrisValue", apiNat.Comment, "k8sValue", natMeta.Spec.Comment) + return false + } + if apiNat.State.Value != natMeta.Spec.State { + u.DebugLogger.Info("State changed", "netrisValue", apiNat.State.Value, "k8sValue", natMeta.Spec.State) + return false + } + if apiNat.Site.ID != natMeta.Spec.SiteID { + u.DebugLogger.Info("Sote changed", "netrisValue", apiNat.Site.ID, "k8sValue", natMeta.Spec.SiteID) + return false + } + apiAction := apiNat.Action.Label + if apiAction == "ACCEPT" { + apiAction = "ACCEPT_SNAT" + } + if apiAction != natMeta.Spec.Action { + u.DebugLogger.Info("Action changed", "netrisValue", apiNat.Action.Label, "k8sValue", natMeta.Spec.Action) + return false + } + if apiNat.Protocol.Value != natMeta.Spec.Protocol { + u.DebugLogger.Info("Protocol changed", "netrisValue", apiNat.Protocol.Value, "k8sValue", natMeta.Spec.Protocol) + return false + } + if apiNat.SourceAddress != natMeta.Spec.SrcAddress { + u.DebugLogger.Info("SourceAddress changed", "netrisValue", apiNat.SourceAddress, "k8sValue", natMeta.Spec.SrcAddress) + return false + } + if (apiNat.Protocol.Value == "tcp" || apiNat.Protocol.Value == "udp") && apiNat.SourcePort != natMeta.Spec.SrcPort { + u.DebugLogger.Info("SourcePort changed", "netrisValue", apiNat.SourcePort, "k8sValue", natMeta.Spec.SrcPort) + return false + } + if apiNat.DestinationAddress != natMeta.Spec.DstAddress { + natMetaDst := strings.Split(natMeta.Spec.DstAddress, "/")[0] + if apiNat.DestinationAddress != natMetaDst { + u.DebugLogger.Info("DestinationAddress changed", "netrisValue", apiNat.DestinationAddress, "k8sValue", natMeta.Spec.DstAddress) + return false + } + } + if (apiNat.Protocol.Value == "tcp" || apiNat.Protocol.Value == "udp") && apiNat.DestinationPort != natMeta.Spec.DstPort { + u.DebugLogger.Info("DestinationPort changed", "netrisValue", apiNat.DestinationPort, "k8sValue", natMeta.Spec.DstPort) + return false + } + if apiNat.SnatToIP != natMeta.Spec.SnatToIP { + u.DebugLogger.Info("SnatToIP changed", "netrisValue", apiNat.SnatToIP, "k8sValue", natMeta.Spec.SnatToIP) + return false + } + if apiNat.SnatToPool != natMeta.Spec.SnatToPool { + u.DebugLogger.Info("SnatToPool changed", "netrisValue", apiNat.SnatToPool, "k8sValue", natMeta.Spec.SnatToPool) + return false + } + if apiNat.DnatToIP != natMeta.Spec.DnatToIP { + u.DebugLogger.Info("DnatToIP changed", "netrisValue", apiNat.DnatToIP, "k8sValue", natMeta.Spec.DnatToIP) + return false + } + if apiNat.DnatToPort != natMeta.Spec.DnatToPort { + u.DebugLogger.Info("DnatToPort changed", "netrisValue", apiNat.DnatToPort, "k8sValue", natMeta.Spec.DnatToPort) + return false + } + + return true +} diff --git a/controllers/natmeta_controller.go b/controllers/natmeta_controller.go new file mode 100644 index 0000000..b4ccbe4 --- /dev/null +++ b/controllers/natmeta_controller.go @@ -0,0 +1,244 @@ +/* +Copyright 2021. Netris, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "encoding/json" + "fmt" + + "go.uber.org/zap/zapcore" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/go-logr/logr" + k8sv1alpha1 "github.com/netrisai/netris-operator/api/v1alpha1" + "github.com/netrisai/netris-operator/netrisstorage" + "github.com/netrisai/netriswebapi/http" + api "github.com/netrisai/netriswebapi/v2" + "github.com/netrisai/netriswebapi/v2/types/nat" +) + +// NatMetaReconciler reconciles a NatMeta object +type NatMetaReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + Cred *api.Clientset + NStorage *netrisstorage.Storage +} + +//+kubebuilder:rbac:groups=k8s.netris.ai,resources=natmeta,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=k8s.netris.ai,resources=natmeta/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=k8s.netris.ai,resources=natmeta/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the NatMeta object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.2/pkg/reconcile +func (r *NatMetaReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + debugLogger := r.Log.WithValues("name", req.NamespacedName).V(int(zapcore.WarnLevel)) + + natMeta := &k8sv1alpha1.NatMeta{} + natCR := &k8sv1alpha1.Nat{} + natMetaCtx, natMetaCancel := context.WithTimeout(cntxt, contextTimeout) + defer natMetaCancel() + if err := r.Get(natMetaCtx, req.NamespacedName, natMeta); err != nil { + if errors.IsNotFound(err) { + debugLogger.Info(err.Error()) + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + logger := r.Log.WithValues("name", fmt.Sprintf("%s/%s", req.NamespacedName.Namespace, natMeta.Spec.NatName)) + debugLogger = logger.V(int(zapcore.WarnLevel)) + + u := uniReconciler{ + Client: r.Client, + Logger: logger, + DebugLogger: debugLogger, + Cred: r.Cred, + NStorage: r.NStorage, + } + + provisionState := "OK" + + natNN := req.NamespacedName + natNN.Name = natMeta.Spec.NatName + natNNCtx, natNNCancel := context.WithTimeout(cntxt, contextTimeout) + defer natNNCancel() + if err := r.Get(natNNCtx, natNN, natCR); err != nil { + if errors.IsNotFound(err) { + debugLogger.Info(err.Error()) + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + if natMeta.DeletionTimestamp != nil { + return ctrl.Result{}, nil + } + + if natMeta.Spec.ID == 0 { + debugLogger.Info("ID Not found in meta") + if natMeta.Spec.Imported { + logger.Info("Importing nat") + debugLogger.Info("Imported yaml mode. Finding Nat by name") + if nat, ok := r.NStorage.NATStorage.FindByName(natMeta.Spec.NatName); ok { + debugLogger.Info("Imported yaml mode. Nat found") + natMeta.Spec.ID = nat.ID + + natMetaPatchCtx, natMetaPatchCancel := context.WithTimeout(cntxt, contextTimeout) + defer natMetaPatchCancel() + err := r.Patch(natMetaPatchCtx, natMeta.DeepCopyObject(), client.Merge, &client.PatchOptions{}) + if err != nil { + logger.Error(fmt.Errorf("{patch natmeta.Spec.ID} %s", err), "") + return u.patchNatStatus(natCR, "Failure", err.Error()) + } + debugLogger.Info("Imported yaml mode. ID patched") + logger.Info("Nat imported") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + logger.Info("Nat not found for import") + debugLogger.Info("Imported yaml mode. Nat not found") + } + + logger.Info("Creating Nat") + if _, err, errMsg := r.createNat(natMeta); err != nil { + logger.Error(fmt.Errorf("{createNat} %s", err), "") + return u.patchNatStatus(natCR, "Failure", errMsg.Error()) + } + logger.Info("Nat Created") + } else { + if apiNat, ok := r.NStorage.NATStorage.FindByID(natMeta.Spec.ID); ok { + + debugLogger.Info("Comparing NatMeta with Netris Nat") + if ok := compareNatMetaAPIENat(natMeta, apiNat, u); ok { + debugLogger.Info("Nothing Changed") + } else { + debugLogger.Info("Go to update Nat in Netris") + logger.Info("Updating Nat") + natUpdate, err := NatMetaToNetrisUpdate(natMeta) + if err != nil { + logger.Error(fmt.Errorf("{NatMetaToNetrisUpdate} %s", err), "") + return u.patchNatStatus(natCR, "Failure", err.Error()) + } + + js, _ := json.Marshal(natUpdate) + debugLogger.Info("natUpdate", "payload", string(js)) + + _, err, errMsg := updateNat(natMeta.Spec.ID, natUpdate, r.Cred) + if err != nil { + logger.Error(fmt.Errorf("{updateNat} %s", err), "") + return u.patchNatStatus(natCR, "Failure", errMsg.Error()) + } + logger.Info("Nat Updated") + } + } else { + debugLogger.Info("Nat not found in Netris") + debugLogger.Info("Going to create Nat") + logger.Info("Creating Nat") + if _, err, errMsg := r.createNat(natMeta); err != nil { + logger.Error(fmt.Errorf("{createNat} %s", err), "") + return u.patchNatStatus(natCR, "Failure", errMsg.Error()) + } + logger.Info("Nat Created") + } + } + return u.patchNatStatus(natCR, provisionState, "Success") +} + +func (r *NatMetaReconciler) createNat(natMeta *k8sv1alpha1.NatMeta) (ctrl.Result, error, error) { + debugLogger := r.Log.WithValues( + "name", fmt.Sprintf("%s/%s", natMeta.Namespace, natMeta.Spec.NatName), + "natName", natMeta.Spec.NatCRGeneration, + ).V(int(zapcore.WarnLevel)) + + natAdd, err := NatMetaToNetris(natMeta) + if err != nil { + return ctrl.Result{}, err, err + } + + js, _ := json.Marshal(natAdd) + debugLogger.Info("natToAdd", "payload", string(js)) + + reply, err := r.Cred.NAT().Add(natAdd) + if err != nil { + return ctrl.Result{}, err, err + } + resp, err := http.ParseAPIResponse(reply.Data) + if err != nil { + return ctrl.Result{}, err, err + } + if !resp.IsSuccess { + return ctrl.Result{}, fmt.Errorf(resp.Message), fmt.Errorf(resp.Message) + } + + idStruct := struct { + ID int `json:"id"` + }{} + debugLogger.Info("response Data", "payload", resp.Data) + err = http.Decode(resp.Data, &idStruct) + if err != nil { + return ctrl.Result{}, err, err + } + + debugLogger.Info("Nat Created", "id", idStruct.ID) + + natMeta.Spec.ID = idStruct.ID + + ctx, cancel := context.WithTimeout(cntxt, contextTimeout) + defer cancel() + err = r.Patch(ctx, natMeta.DeepCopyObject(), client.Merge, &client.PatchOptions{}) // requeue + if err != nil { + return ctrl.Result{}, err, err + } + + debugLogger.Info("ID patched to meta", "id", idStruct.ID) + return ctrl.Result{}, nil, nil +} + +func updateNat(id int, nat *nat.NATw, cred *api.Clientset) (ctrl.Result, error, error) { + reply, err := cred.NAT().Update(id, nat) + if err != nil { + return ctrl.Result{}, fmt.Errorf("{updateNat} %s", err), err + } + resp, err := http.ParseAPIResponse(reply.Data) + if err != nil { + return ctrl.Result{}, err, err + } + if !resp.IsSuccess { + return ctrl.Result{}, fmt.Errorf("{updateNat} %s", fmt.Errorf(resp.Message)), fmt.Errorf(resp.Message) + } + + return ctrl.Result{}, nil, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *NatMetaReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&k8sv1alpha1.NatMeta{}). + Complete(r) +} diff --git a/deploy/charts/netris-operator/Chart.yaml b/deploy/charts/netris-operator/Chart.yaml index e2dfb37..0ae87e5 100644 --- a/deploy/charts/netris-operator/Chart.yaml +++ b/deploy/charts/netris-operator/Chart.yaml @@ -15,12 +15,12 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.6.0 +version: 0.7.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: v1.2.0 +appVersion: v1.3.0 home: https://github.com/netrisai/netris-operator icon: https://www.netris.ai/wp-content/uploads/2021/01/logo-300.png # [todo] Change url to permalink keywords: diff --git a/deploy/charts/netris-operator/crds/k8s.netris.ai_natmeta.yaml b/deploy/charts/netris-operator/crds/k8s.netris.ai_natmeta.yaml new file mode 100644 index 0000000..53688ef --- /dev/null +++ b/deploy/charts/netris-operator/crds/k8s.netris.ai_natmeta.yaml @@ -0,0 +1,103 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + creationTimestamp: null + name: natmeta.k8s.netris.ai +spec: + group: k8s.netris.ai + names: + kind: NatMeta + listKind: NatMetaList + plural: natmeta + singular: natmeta + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: NatMeta is the Schema for the natmeta 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: NatMetaSpec defines the desired state of NatMeta + properties: + action: + type: string + comment: + type: string + dnatToIp: + type: string + dnatToPort: + type: integer + dstAddress: + type: string + dstPort: + type: string + id: + type: integer + imported: + description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "make" to regenerate code after modifying this file' + type: boolean + natGeneration: + format: int64 + type: integer + natName: + type: string + protocol: + type: string + reclaimPolicy: + type: boolean + siteID: + type: integer + snatToIp: + type: string + snatToPool: + type: string + srcAddress: + type: string + srcPort: + type: string + state: + type: string + required: + - action + - dstAddress + - id + - imported + - natGeneration + - natName + - protocol + - reclaimPolicy + - siteID + - srcAddress + type: object + status: + description: NatMetaStatus defines the observed state of NatMeta + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/deploy/charts/netris-operator/crds/k8s.netris.ai_nats.yaml b/deploy/charts/netris-operator/crds/k8s.netris.ai_nats.yaml new file mode 100644 index 0000000..cd61776 --- /dev/null +++ b/deploy/charts/netris-operator/crds/k8s.netris.ai_nats.yaml @@ -0,0 +1,160 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + creationTimestamp: null + name: nats.k8s.netris.ai +spec: + group: k8s.netris.ai + names: + kind: Nat + listKind: NatList + plural: nats + singular: nat + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.state + name: State + type: string + - jsonPath: .spec.site + name: Site + type: string + - jsonPath: .spec.action + name: Action + type: string + - jsonPath: .spec.protocol + name: Protocol + type: string + - jsonPath: .spec.srcAddress + name: SrcAddress + type: string + - jsonPath: .spec.srcPort + name: SrcPort + type: integer + - jsonPath: .spec.dstAddress + name: DstAddress + type: string + - jsonPath: .spec.dstPort + name: DstPort + type: integer + - jsonPath: .spec.snatToIp + name: SNATToIP + priority: 1 + type: string + - jsonPath: .spec.snatToPool + name: SNATToPool + priority: 1 + type: string + - jsonPath: .spec.dnatToIp + name: DNATToIP + priority: 1 + type: string + - jsonPath: .spec.dnatToPort + name: DNATToPort + priority: 1 + type: integer + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: Nat is the Schema for the nats 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: NatSpec defines the desired state of Nat + properties: + action: + enum: + - dnat + - snat + - accept_snat + - masquerade + type: string + comment: + type: string + dnatToIp: + pattern: (^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12]\d|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/([1-9]|[1-5][0-9]|6[0-4]))?$) + type: string + dnatToPort: + maximum: 65535 + minimum: 1 + type: integer + dstAddress: + pattern: (^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12]\d|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/([1-9]|[1-5][0-9]|6[0-4]))?$) + type: string + dstPort: + type: string + protocol: + enum: + - all + - tcp + - udp + - icmp + type: string + site: + type: string + snatToIp: + pattern: ^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ + type: string + snatToPool: + pattern: (^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12]\d|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/([1-9]|[1-5][0-9]|6[0-4]))?$) + type: string + srcAddress: + pattern: (^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12]\d|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/([1-9]|[1-5][0-9]|6[0-4]))?$) + type: string + srcPort: + type: string + state: + enum: + - enabled + - disabled + type: string + required: + - action + - dstAddress + - protocol + - site + - srcAddress + type: object + status: + description: NatStatus defines the observed state of Nat + properties: + message: + type: string + status: + description: 'INSERT ADDITIONAL STATUS FIELD - define observed state + of cluster Important: Run "make" to regenerate code after modifying + this file' + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/deploy/charts/netris-operator/templates/rbac.yaml b/deploy/charts/netris-operator/templates/rbac.yaml index 9c9908d..9591af8 100644 --- a/deploy/charts/netris-operator/templates/rbac.yaml +++ b/deploy/charts/netris-operator/templates/rbac.yaml @@ -336,6 +336,58 @@ rules: - get - patch - update + - apiGroups: + - k8s.netris.ai + resources: + - natmeta + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - k8s.netris.ai + resources: + - natmeta/finalizers + verbs: + - update + - apiGroups: + - k8s.netris.ai + resources: + - natmeta/status + verbs: + - get + - patch + - update + - apiGroups: + - k8s.netris.ai + resources: + - nats + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - k8s.netris.ai + resources: + - nats/finalizers + verbs: + - update + - apiGroups: + - k8s.netris.ai + resources: + - nats/status + verbs: + - get + - patch + - update - apiGroups: - k8s.netris.ai resources: diff --git a/go.sum b/go.sum index f450dde..a279612 100644 --- a/go.sum +++ b/go.sum @@ -229,8 +229,6 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/netrisai/netriswebapi v0.0.0-20220125103148-df073aedbe0f h1:L20UUY0zsLahHQbg4DJb4lwp+W7Z9Pc884r70xJv2DI= -github.com/netrisai/netriswebapi v0.0.0-20220125103148-df073aedbe0f/go.mod h1:GLLz33Jc07/hIPwEYZDWEtNtHjX/QZjVzf9xLnfSiqs= github.com/netrisai/netriswebapi v0.0.0-20220207194714-42a1095aa823 h1:AkliiG0Hsa49rcUNOoAwWAueV30MP+UInuzcp/TS0cE= github.com/netrisai/netriswebapi v0.0.0-20220207194714-42a1095aa823/go.mod h1:GLLz33Jc07/hIPwEYZDWEtNtHjX/QZjVzf9xLnfSiqs= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= diff --git a/main.go b/main.go index d87d9c3..11e7f48 100644 --- a/main.go +++ b/main.go @@ -305,6 +305,27 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "LinkMeta") os.Exit(1) } + if err = (&controllers.NatReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("Nat"), + Scheme: mgr.GetScheme(), + Cred: cred, + NStorage: nStorage, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Nat") + os.Exit(1) + } + if err = (&controllers.NatMetaReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("NatMeta"), + Scheme: mgr.GetScheme(), + Cred: cred, + NStorage: nStorage, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "NatMeta") + os.Exit(1) + } + // +kubebuilder:scaffold:builder watcherLogLevel := "info" diff --git a/netrisstorage/nat.go b/netrisstorage/nat.go new file mode 100644 index 0000000..3e7bc88 --- /dev/null +++ b/netrisstorage/nat.go @@ -0,0 +1,103 @@ +/* +Copyright 2021. Netris, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package netrisstorage + +import ( + "sync" + + "github.com/netrisai/netriswebapi/v2/types/nat" +) + +// NATStorage . +type NATStorage struct { + sync.Mutex + NAT []*nat.NAT +} + +// NewNATStorage . +func NewNATStorage() *NATStorage { + return &NATStorage{} +} + +// GetAll . +func (p *NATStorage) GetAll() []*nat.NAT { + p.Lock() + defer p.Unlock() + return p.getAll() +} + +func (p *NATStorage) getAll() []*nat.NAT { + return p.NAT +} + +func (p *NATStorage) storeAll(items []*nat.NAT) { + p.NAT = items +} + +// FindByName . +func (p *NATStorage) FindByName(name string) (*nat.NAT, bool) { + p.Lock() + defer p.Unlock() + return p.findByName(name) +} + +func (p *NATStorage) findByName(name string) (*nat.NAT, bool) { + for _, nat := range p.NAT { + if nat.Name == name { + return nat, true + } + } + return nil, false +} + +// FindByID . +func (p *NATStorage) FindByID(id int) (*nat.NAT, bool) { + p.Lock() + defer p.Unlock() + item, ok := p.findByID(id) + if !ok { + _ = p.download() + return p.findByID(id) + } + return item, ok +} + +func (p *NATStorage) findByID(id int) (*nat.NAT, bool) { + for _, nat := range p.NAT { + if nat.ID == id { + return nat, true + } + } + return nil, false +} + +// Download . +func (p *NATStorage) download() error { + items, err := Cred.NAT().Get() + if err != nil { + return err + } + p.storeAll(items) + return nil +} + +// Download . +func (p *NATStorage) Download() error { + p.Lock() + defer p.Unlock() + return p.download() +} diff --git a/netrisstorage/storage.go b/netrisstorage/storage.go index 4687abe..c079745 100644 --- a/netrisstorage/storage.go +++ b/netrisstorage/storage.go @@ -43,6 +43,7 @@ type Storage struct { *SubnetsStorage *HWsStorage *LinksStorage + *NATStorage } // NewStorage . @@ -58,6 +59,7 @@ func NewStorage(cred *api.Clientset) *Storage { SubnetsStorage: NewSubnetsStorage(), HWsStorage: NewHWsStorage(), LinksStorage: NewLinksStorage(), + NATStorage: NewNATStorage(), } } @@ -101,6 +103,10 @@ func (s *Storage) Download() error { fmt.Println("LinksStorage", err) return err } + if err := s.NATStorage.Download(); err != nil { + fmt.Println("NATStorage", err) + return err + } return nil } diff --git a/samples/README.md b/samples/README.md index a76c1f9..aa12fe4 100644 --- a/samples/README.md +++ b/samples/README.md @@ -284,15 +284,15 @@ metadata: spec: ownerTenant: Admin # [1] site: santa-clara # [2] - state: active # [3] optional - protocol: tcp # [4] optional + state: active # [3] optional + protocol: tcp # [4] optional frontend: port: 31434 # [5] - ip: 203.0.113.7 # [6] optional + ip: 203.0.113.7 # [6] optional backend: # [7] - 192.0.2.100:443 - 192.0.2.101:443 - check: # [8] optional. Ignoring when protocol == udp + check: # [8] optional. Ignoring when protocol == udp type: http # [9] optional timeout: 3000 # [10] optional requestPath: / # [11] optional. Ignoring when check.type == tcp @@ -313,6 +313,45 @@ Ref | Attribute | Default | Descript [11]| check.requestPath | / | Http probe path. Ignoring when check.type == tcp +### Nat Attributes +``` +apiVersion: k8s.netris.ai/v1alpha1 +kind: Nat +metadata: + name: my-dnat +spec: + comment: MY DNAT # [1] optional + # state: enabled # [2] optional + site: santa-clara # [3] + action: dnat # [4] + protocol: tcp # [5] + srcAddress: 0.0.0.0/0 # [6] + srcPort: 1-65535 # [7] + dstAddress: 203.0.113.193/32 # [8] + dstPort: "8080" # [9] + dnatToIp: 172.28.51.150/32 # [10] + dnatToPort: 80 # [11] + # snatToIp: 203.0.113.192 # [12] + # snatToPool: 203.0.113.192/26 # [13] +``` + +Ref | Attribute | Default | Description +-----| -------------------------------------- | ------------- | ---------------- +[1] | comment | "" | Users of this tenant will be permitted to edit this unit. +[2] | state | enabled | Optional. Controller description. +[3] | site | "" | The site where this ACL belongs. +[4] | action | "" | Possible values: `dnat`, `snat`, `accept_snat`, `masquerade`. +[5] | protocol | "" | Possible values: `all`, `tcp`, `udp`, `icmp`. +[6] | srcAddress | "" | Match traffic sourced from this subnet. +[7] | srcPort | "" | Match traffic sourced from this port. Ignoring when protocol == `all` or `icmp` +[8] | dstAddress | "" | Match traffic destined to this subnet. +[9] | dstPort | "" | Match traffic destined to this port. Ignoring when protocol == `all` or `icmp` +[10] | dnatToIp | "" | The internal IP address to which external hosts will gain access as a result of a DNAT translation. Only when action == `dnat` +[11] | dnatToPort | nil | The internal port to which external port will gain access as a result of a DNAT translation. Only when action == `dnat` +[12] | snatToIp | "" | Replace the original address with the specified one. Only when action == `snat` +[13] | snatToPool | "" | Replace the original address with the pool of ip addresses. Only when action == `snat` + + # Annotations > Annotation keys and values can only be strings. Other types, such as boolean or numeric values must be quoted, i.e. "true", "false", "100". diff --git a/samples/allocation.yaml b/samples/allocation.yaml index f314139..33230ad 100644 --- a/samples/allocation.yaml +++ b/samples/allocation.yaml @@ -24,6 +24,14 @@ spec: --- apiVersion: k8s.netris.ai/v1alpha1 kind: Allocation +metadata: + name: my-allocation-vnet +spec: + prefix: 172.28.51.0/24 + tenant: Admin +--- +apiVersion: k8s.netris.ai/v1alpha1 +kind: Allocation metadata: name: my-allocation-common-v6 spec: diff --git a/samples/kustomization.yaml b/samples/kustomization.yaml index 4d4d359..14c798d 100644 --- a/samples/kustomization.yaml +++ b/samples/kustomization.yaml @@ -11,3 +11,4 @@ resources: - l4lb.yaml - bgp.yaml - link.yaml + - nat.yaml diff --git a/samples/l4lb.yaml b/samples/l4lb.yaml index 1aef63a..075caf4 100644 --- a/samples/l4lb.yaml +++ b/samples/l4lb.yaml @@ -8,11 +8,11 @@ spec: state: active protocol: tcp frontend: - port: 31434 - # ip: 203.0.113.150 + port: 8443 + ip: 203.0.113.150 backend: - - 203.0.113.50:443 - - 203.0.113.51:443 + - 172.28.51.100:443 + - 172.28.51.101:443 check: type: http timeout: 3000 diff --git a/samples/nat.yaml b/samples/nat.yaml new file mode 100644 index 0000000..16e6745 --- /dev/null +++ b/samples/nat.yaml @@ -0,0 +1,44 @@ +apiVersion: k8s.netris.ai/v1alpha1 +kind: Nat +metadata: + name: my-snat +spec: + comment: MY SNAT + # state: enabled + site: santa-clara + action: snat + protocol: all + srcAddress: 172.28.51.0/24 + dstAddress: 0.0.0.0/0 + snatToIp: 203.0.113.192 + # snatToPool: 203.0.113.192/26 +--- +apiVersion: k8s.netris.ai/v1alpha1 +kind: Nat +metadata: + name: my-dnat +spec: + comment: MY DNAT + # state: enabled + site: santa-clara + action: dnat + protocol: tcp + srcAddress: 0.0.0.0/0 + srcPort: 1-65535 + dstAddress: 203.0.113.193/32 + dstPort: "8080" + dnatToIp: 172.28.51.150/32 + dnatToPort: 80 +--- +apiVersion: k8s.netris.ai/v1alpha1 +kind: Nat +metadata: + name: my-snat-accept +spec: + comment: MY SNAT ACCEPT + # state: enabled + site: santa-clara + action: accept_snat + protocol: all + srcAddress: 172.28.51.0/24 + dstAddress: 10.10.0.0/16 diff --git a/samples/subnet.yaml b/samples/subnet.yaml index bae5d9e..7916763 100644 --- a/samples/subnet.yaml +++ b/samples/subnet.yaml @@ -56,6 +56,17 @@ spec: --- apiVersion: k8s.netris.ai/v1alpha1 kind: Subnet +metadata: + name: my-subnet-vnet +spec: + prefix: 172.28.51.0/24 + tenant: Admin + purpose: common + sites: + - santa-clara +--- +apiVersion: k8s.netris.ai/v1alpha1 +kind: Subnet metadata: name: my-subnet-common-v6 spec: diff --git a/samples/vnet.yaml b/samples/vnet.yaml index b1d7a1f..77dc1f0 100644 --- a/samples/vnet.yaml +++ b/samples/vnet.yaml @@ -8,7 +8,7 @@ spec: sites: - name: santa-clara gateways: - - 203.0.113.1/25 + - 172.28.51.1/24 - 2001:db8:acad::fffe/64 switchPorts: - name: swp5@my-sw01