Compare commits

..

29 Commits

Author SHA1 Message Date
0233e71b7e More making annotations handle namespaces 2025-04-21 01:40:27 +02:00
5f7b39b76e Some internal documentation, some minor improvements 2025-04-21 01:26:03 +02:00
5468aac185 Rwname firewallrule to firewallpolicy 2025-04-20 21:17:46 +02:00
92ee1eeade Merge pull request #31 from vegardengen/28-add-status-information-to-firewall-rule-resources
28 add status information to firewall rule resources
2025-04-20 13:07:01 +02:00
52afa7365d Fix reconciler logic. 2025-04-20 13:06:26 +02:00
6b85bf78c0 Add status fields and finalizer 2025-04-20 10:30:21 +02:00
a018b3e258 Fix log statement that created panic 2025-04-19 20:35:00 +02:00
70a2987230 Merge pull request #30 from vegardengen/29-create-finalizer-for-cleaning-up-firewallgroup-resources
Finalizer to clean up firewall group objects when deleting a firewall group resource
2025-04-19 15:52:18 +02:00
4fd0a7dc14 Finalizer to clean up firewall group objects when deleting a firewall group resources 2025-04-19 15:51:17 +02:00
54d94a90d5 Merge pull request #27 from vegardengen/12-create-firewall-rule-api
12 create firewall rule api
2025-04-19 00:00:14 +02:00
c63d52bb46 Firewall rule API, including needed changes in other APIs 2025-04-18 23:59:19 +02:00
89a811bef9 Preliminary scaffolding 2025-04-15 15:25:30 +02:00
4c873ea723 Merge pull request #26 from vegardengen/24-create-network-api
24 create network api
2025-04-15 10:36:24 +02:00
7e93d04c4a Add logic to reduce full resync of firewall zonea 2025-04-15 10:34:46 +02:00
625e8e0423 Add tracking only network APi 2025-04-15 10:03:07 +02:00
d63566b794 Merge pull request #25 from vegardengen/chore/formatting
Chore: Formatting
2025-04-14 19:36:58 +02:00
8ec57323fe go fmt 2025-04-14 19:35:35 +02:00
664e56def8 Merge pull request #23 from vegardengen/22-use-reauthentication-method-in-more-reconcilers
Use reauthentication
2025-04-14 17:48:02 +02:00
3f59db13f0 Use reauthentication 2025-04-14 17:47:26 +02:00
a023204d6c Merge pull request #21 from vegardengen/19-create-configmap-with-default-namespace
Fix configmap role
2025-04-14 15:14:21 +02:00
46d5e2ce2a Fix configmap role 2025-04-14 15:13:09 +02:00
72c13517b0 Merge pull request #20 from vegardengen/19-create-configmap-with-default-namespace
19 create configmap with default namespace
2025-04-14 15:08:32 +02:00
7b2acb168a Add namespace 2025-04-14 15:07:49 +02:00
46a0832aea Use configmap in firewallzone controller 2025-04-14 14:05:48 +02:00
c61a555d8a Add OperatorConfig to all controllers 2025-04-14 13:58:49 +02:00
b4b3888bc9 Add configmap support 2025-04-14 13:47:53 +02:00
6636314010 Merge remote-tracking branch 'refs/remotes/github/master' 2025-04-14 13:36:32 +02:00
f31d386bd0 Merge pull request #15 from vegardengen/14-create-object-for-ports-on-firewall-groups
Handle ports in specification and in load balancer services
2025-04-12 19:53:12 +02:00
2aeb2dd25a Handle ports in specification and in load balancer services 2025-04-12 19:52:33 +02:00
31 changed files with 2479 additions and 389 deletions

View File

@@ -32,7 +32,7 @@ resources:
controller: true controller: true
domain: engen.priv.no domain: engen.priv.no
group: unifi group: unifi
kind: FirewallRule kind: FirewallPolicy
path: github.com/vegardengen/unifi-network-operator/api/v1beta1 path: github.com/vegardengen/unifi-network-operator/api/v1beta1
version: v1beta1 version: v1beta1
version: "3" version: "3"

View File

@@ -0,0 +1,40 @@
package v1beta1
// 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.
type NamedUnifiResource struct {
Name string `json:"name,omitempty"`
ID string `json:"id,omitempty"`
}
type ServiceEntry struct {
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
}
type FirewallGroupEntry struct {
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
}
type FirewallZoneEntry struct {
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
}
type FirewallPolicyEntry struct {
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
}
type NetworkEntry struct {
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
}
type FirewallSource struct {
FirewallZones []FirewallZoneEntry `json:"from_zones,omitempty"`
Networks []NetworkEntry `json:"from_networks,omitempty"`
}
type FirewallDestination struct {
FirewallGroups []FirewallGroupEntry `json:"firewall_groups,omitempty"`
Services []ServiceEntry `json:"services,omitempty"`
}

View File

@@ -24,27 +24,28 @@ import (
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// FirewallGroupSpec defines the desired state of FirewallGroup. // FirewallGroupSpec defines the desired state of FirewallGroup.
type FirewallGroupSpec struct { type FirewallGroupSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file // Important: Run "make" to regenerate code after modifying this file
// Foo is an example field of FirewallGroup. Edit firewallgroup_types.go to remove/update // Foo is an example field of FirewallGroup. Edit firewallgroup_types.go to remove/update
// Description is a human-readable explanation for the object // Description is a human-readable explanation for the object
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
MatchServicesInAllNamespaces bool `json:"matchServicesInAllNamespaces,omitempty"` MatchServicesInAllNamespaces bool `json:"matchServicesInAllNamespaces,omitempty"`
// ManualAddresses is a list of manual IPs or CIDRs (IPv4 or IPv6) // ManualAddresses is a list of manual IPs or CIDRs (IPv4 or IPv6)
// +optional // +optional
ManualAddresses []string `json:"manualAddresses,omitempty"` ManualAddresses []string `json:"manualAddresses,omitempty"`
ManualPorts []string `json:"manualPorts,omitempty"`
ManualServices []ServiceEntry `json:"manual_services,omitempty"`
AutoCreatedFrom FirewallPolicyEntry `json:"auto_created_from,omitempty"`
// AutoIncludeSelector defines which services to extract addresses from // AutoIncludeSelector defines which services to extract addresses from
// +optional // +optional
AutoIncludeSelector *metav1.LabelSelector `json:"autoIncludeSelector,omitempty"` AutoIncludeSelector *metav1.LabelSelector `json:"autoIncludeSelector,omitempty"`
// AddressType can be "ip", "cidr", or "both"
// +kubebuilder:validation:Enum=ip;cidr;both
// +optional
AddressType string `json:"addressType,omitempty"`
} }
// FirewallGroupStatus defines the observed state of FirewallGroup. // FirewallGroupStatus defines the observed state of FirewallGroup.
@@ -52,17 +53,29 @@ type FirewallGroupStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file // Important: Run "make" to regenerate code after modifying this file
ResolvedAddresses []string `json:"resolvedAddresses,omitempty"` ResolvedIPV4Addresses []string `json:"resolvedIPV4Addresses,omitempty"`
ResolvedIPV6Addresses []string `json:"resolvedIPV6Addresses,omitempty"`
ResolvedTCPPorts []string `json:"resolvedTCPorts,omitempty"`
ResolvedUDPPorts []string `json:"resolvedUDPorts,omitempty"`
// SyncedWithUnifi indicates whether the addresses are successfully pushed // SyncedWithUnifi indicates whether the addresses are successfully pushed
// +optional // +optional
SyncedWithUnifi bool `json:"syncedWithUnifi,omitempty"` SyncedWithUnifi bool `json:"syncedWithUnifi,omitempty"`
ResourcesManaged *FirewallGroupResourcesManaged `json:"resources_managed,omitempty"`
// LastSyncTime is the last time the object was synced // LastSyncTime is the last time the object was synced
// +optional // +optional
LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"`
} }
type FirewallGroupResourcesManaged struct {
IPV4Object *NamedUnifiResource `json:"ipv4_object,omitempty"`
IPV6Object *NamedUnifiResource `json:"ipv6_object,omitempty"`
TCPPortsObject *NamedUnifiResource `json:"tcp_ports_object,omitempty"`
UDPPortsObject *NamedUnifiResource `json:"udp_ports_object,omitempty"`
}
// +kubebuilder:object:root=true // +kubebuilder:object:root=true
// +kubebuilder:subresource:status // +kubebuilder:subresource:status

View File

@@ -0,0 +1,92 @@
/*
Copyright 2025 Vegard Engen.
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 v1beta1
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.
// FirewallPolicySpec defines the desired state of FirewallPolicy.
// type ServiceSpec struct {
// Namespace string `json:"namespace,omitempty"`
// Name string `json:"name,omitempty"`
// }
// type FirewallSource struct {
// Zones []string `json:"from_zones,omitempty"`
// Networks []string `json:"from_networks,omitempty"`
//}
//type FirewallDestination struct {
// FirewallGroups []string `json:"firewall_group,omitempty"`
// Services []ServiceSpec `json:"service,omitempty"`
//}
type FirewallPolicySpec struct {
Name string `json:"name"`
Source FirewallSource `json:"source"`
Destination FirewallDestination `json:"destination"`
MatchFirewallGroupsInAllNamespaces bool `json:"match_firewall_groups_in_all_namespaces,omitempty"`
MatchServicesInAllNamespaces bool `json:"match_services_in_all_namespaces,omitempty"`
}
// FirewallPolicyStatus defines the observed state of FirewallPolicy.
type FirewallPolicyStatus struct {
ResourcesManaged *FirewallPolicyResourcesManaged `json:"resources_managed,omitempty"`
}
type FirewallPolicyResourcesManaged struct {
UnifiFirewallPolicies []UnifiFirewallPolicyEntry `json:"firewall_policies_managed,omitempty"`
FirewallGroups []FirewallGroupEntry `json:"firewall_groups_managed,omitempty"`
}
type UnifiFirewallPolicyEntry struct {
From string `json:"from"`
To string `json:"to"`
TcpIpv4ID string `json:"tcpipv4_id"`
UdpIpv4ID string `json:"udpipv4_id"`
TcpIpv6ID string `json:"tcpipv6_id"`
UdpIpv6ID string `json:"udpipv6_id"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// FirewallPolicy is the Schema for the firewallpolicies API.
type FirewallPolicy struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec FirewallPolicySpec `json:"spec,omitempty"`
Status FirewallPolicyStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// FirewallPolicyList contains a list of FirewallPolicy.
type FirewallPolicyList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []FirewallPolicy `json:"items"`
}
func init() {
SchemeBuilder.Register(&FirewallPolicy{}, &FirewallPolicyList{})
}

View File

@@ -1,64 +0,0 @@
/*
Copyright 2025 Vegard Engen.
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 v1beta1
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.
// FirewallRuleSpec defines the desired state of FirewallRule.
type FirewallRuleSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Foo is an example field of FirewallRule. Edit firewallrule_types.go to remove/update
Foo string `json:"foo,omitempty"`
}
// FirewallRuleStatus defines the observed state of FirewallRule.
type FirewallRuleStatus 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
// FirewallRule is the Schema for the firewallrules API.
type FirewallRule struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec FirewallRuleSpec `json:"spec,omitempty"`
Status FirewallRuleStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// FirewallRuleList contains a list of FirewallRule.
type FirewallRuleList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []FirewallRule `json:"items"`
}
func init() {
SchemeBuilder.Register(&FirewallRule{}, &FirewallRuleList{})
}

View File

@@ -28,17 +28,23 @@ type FirewallZoneSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file // Important: Run "make" to regenerate code after modifying this file
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
ID string `json:"_id,omitempty"` ID string `json:"_id,omitempty"`
DefaultZone bool `json:"default_zone,omitempty"` DefaultZone bool `json:"default_zone,omitempty"`
ZoneKey string `json:"zone_key,omitempty"` ZoneKey string `json:"zone_key,omitempty"`
NetworkIDs []string `json:"network_ids,omitempty"` NetworkIDs []string `json:"network_ids,omitempty"`
} }
// FirewallZoneStatus defines the observed state of FirewallZone. // FirewallZoneStatus defines the observed state of FirewallZone.
type FirewallZoneStatus struct { type FirewallZoneStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file // Important: Run "make" to regenerate code after modifying this file
ResourcesManaged *FirewallZoneResourcesManaged `json:"resources_managed,omitempty"`
}
type FirewallZoneResourcesManaged struct {
UnifiFirewallZones []NamedUnifiResource `json:"firewall_zones_managed,omitempty"`
} }
// +kubebuilder:object:root=true // +kubebuilder:object:root=true

View File

@@ -29,6 +29,7 @@ type NetworkconfigurationSpec struct {
// Important: Run "make" to regenerate code after modifying this file // Important: Run "make" to regenerate code after modifying this file
// Foo is an example field of Networkconfiguration. Edit networkconfiguration_types.go to remove/update // Foo is an example field of Networkconfiguration. Edit networkconfiguration_types.go to remove/update
ID string `json:"_id,omitempty"`
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
FirewallZone string `json:"firewall_zone,omitempty"` FirewallZone string `json:"firewall_zone,omitempty"`
GatewayType string `json:"gateway_type,omitempty"` GatewayType string `json:"gateway_type,omitempty"`
@@ -39,7 +40,6 @@ type NetworkconfigurationSpec struct {
Ipv6SettingPreference string `json:"ipv6_setting_preference,omitempty"` Ipv6SettingPreference string `json:"ipv6_setting_preference,omitempty"`
Ipv6Subnet string `json:"ipv6_subnet,omitempty"` Ipv6Subnet string `json:"ipv6_subnet,omitempty"`
Name string `json:"name"` Name string `json:"name"`
NetworkID string `json:"network_id,omitempty"`
Networkgroup string `json:"networkgroup,omitempty"` Networkgroup string `json:"networkgroup,omitempty"`
Purpose string `json:"purpose,omitempty"` Purpose string `json:"purpose,omitempty"`
SettingPreference string `json:"setting_preference,omitempty"` SettingPreference string `json:"setting_preference,omitempty"`
@@ -51,16 +51,21 @@ type NetworkconfigurationSpec struct {
type NetworkconfigurationStatus struct { type NetworkconfigurationStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file // Important: Run "make" to regenerate code after modifying this file
FirewallZoneID string `json:"firewall_zone_id,omitempty"` FirewallZoneID string `json:"firewall_zone_id,omitempty"`
Ipv6SubnetStatus string `json:"ipv6_subnet_status,omitempty"` Ipv6SubnetStatus string `json:"ipv6_subnet_status,omitempty"`
// SyncedWithUnifi indicates whether the addresses are successfully pushed // SyncedWithUnifi indicates whether the addresses are successfully pushed
// +optional // +optional
SyncedWithUnifi bool `json:"syncedWithUnifi,omitempty"` SyncedWithUnifi bool `json:"syncedWithUnifi,omitempty"`
ResourcesManaged *NetworkconfigurationResourcesManaged `json:"resources_managed,omitempty"`
// LastSyncTime is the last time the object was synced // LastSyncTime is the last time the object was synced
// +optional // +optional
LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"`
}
type NetworkconfigurationResourcesManaged struct {
UnifiNetworks []NamedUnifiResource `json:"networks_managed,omitempty"`
} }
// +kubebuilder:object:root=true // +kubebuilder:object:root=true

View File

@@ -25,6 +25,31 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime" 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 *FirewallDestination) DeepCopyInto(out *FirewallDestination) {
*out = *in
if in.FirewallGroups != nil {
in, out := &in.FirewallGroups, &out.FirewallGroups
*out = make([]FirewallGroupEntry, len(*in))
copy(*out, *in)
}
if in.Services != nil {
in, out := &in.Services, &out.Services
*out = make([]ServiceEntry, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallDestination.
func (in *FirewallDestination) DeepCopy() *FirewallDestination {
if in == nil {
return nil
}
out := new(FirewallDestination)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallGroup) DeepCopyInto(out *FirewallGroup) { func (in *FirewallGroup) DeepCopyInto(out *FirewallGroup) {
*out = *in *out = *in
@@ -52,6 +77,21 @@ func (in *FirewallGroup) DeepCopyObject() runtime.Object {
return nil return nil
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallGroupEntry) DeepCopyInto(out *FirewallGroupEntry) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallGroupEntry.
func (in *FirewallGroupEntry) DeepCopy() *FirewallGroupEntry {
if in == nil {
return nil
}
out := new(FirewallGroupEntry)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallGroupList) DeepCopyInto(out *FirewallGroupList) { func (in *FirewallGroupList) DeepCopyInto(out *FirewallGroupList) {
*out = *in *out = *in
@@ -84,6 +124,41 @@ func (in *FirewallGroupList) DeepCopyObject() runtime.Object {
return nil return nil
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallGroupResourcesManaged) DeepCopyInto(out *FirewallGroupResourcesManaged) {
*out = *in
if in.IPV4Object != nil {
in, out := &in.IPV4Object, &out.IPV4Object
*out = new(NamedUnifiResource)
**out = **in
}
if in.IPV6Object != nil {
in, out := &in.IPV6Object, &out.IPV6Object
*out = new(NamedUnifiResource)
**out = **in
}
if in.TCPPortsObject != nil {
in, out := &in.TCPPortsObject, &out.TCPPortsObject
*out = new(NamedUnifiResource)
**out = **in
}
if in.UDPPortsObject != nil {
in, out := &in.UDPPortsObject, &out.UDPPortsObject
*out = new(NamedUnifiResource)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallGroupResourcesManaged.
func (in *FirewallGroupResourcesManaged) DeepCopy() *FirewallGroupResourcesManaged {
if in == nil {
return nil
}
out := new(FirewallGroupResourcesManaged)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallGroupSpec) DeepCopyInto(out *FirewallGroupSpec) { func (in *FirewallGroupSpec) DeepCopyInto(out *FirewallGroupSpec) {
*out = *in *out = *in
@@ -92,6 +167,17 @@ func (in *FirewallGroupSpec) DeepCopyInto(out *FirewallGroupSpec) {
*out = make([]string, len(*in)) *out = make([]string, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.ManualPorts != nil {
in, out := &in.ManualPorts, &out.ManualPorts
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ManualServices != nil {
in, out := &in.ManualServices, &out.ManualServices
*out = make([]ServiceEntry, len(*in))
copy(*out, *in)
}
out.AutoCreatedFrom = in.AutoCreatedFrom
if in.AutoIncludeSelector != nil { if in.AutoIncludeSelector != nil {
in, out := &in.AutoIncludeSelector, &out.AutoIncludeSelector in, out := &in.AutoIncludeSelector, &out.AutoIncludeSelector
*out = new(v1.LabelSelector) *out = new(v1.LabelSelector)
@@ -112,11 +198,31 @@ func (in *FirewallGroupSpec) DeepCopy() *FirewallGroupSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallGroupStatus) DeepCopyInto(out *FirewallGroupStatus) { func (in *FirewallGroupStatus) DeepCopyInto(out *FirewallGroupStatus) {
*out = *in *out = *in
if in.ResolvedAddresses != nil { if in.ResolvedIPV4Addresses != nil {
in, out := &in.ResolvedAddresses, &out.ResolvedAddresses in, out := &in.ResolvedIPV4Addresses, &out.ResolvedIPV4Addresses
*out = make([]string, len(*in)) *out = make([]string, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.ResolvedIPV6Addresses != nil {
in, out := &in.ResolvedIPV6Addresses, &out.ResolvedIPV6Addresses
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ResolvedTCPPorts != nil {
in, out := &in.ResolvedTCPPorts, &out.ResolvedTCPPorts
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ResolvedUDPPorts != nil {
in, out := &in.ResolvedUDPPorts, &out.ResolvedUDPPorts
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ResourcesManaged != nil {
in, out := &in.ResourcesManaged, &out.ResourcesManaged
*out = new(FirewallGroupResourcesManaged)
(*in).DeepCopyInto(*out)
}
if in.LastSyncTime != nil { if in.LastSyncTime != nil {
in, out := &in.LastSyncTime, &out.LastSyncTime in, out := &in.LastSyncTime, &out.LastSyncTime
*out = (*in).DeepCopy() *out = (*in).DeepCopy()
@@ -134,26 +240,26 @@ func (in *FirewallGroupStatus) DeepCopy() *FirewallGroupStatus {
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallRule) DeepCopyInto(out *FirewallRule) { func (in *FirewallPolicy) DeepCopyInto(out *FirewallPolicy) {
*out = *in *out = *in
out.TypeMeta = in.TypeMeta out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status in.Status.DeepCopyInto(&out.Status)
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallRule. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallPolicy.
func (in *FirewallRule) DeepCopy() *FirewallRule { func (in *FirewallPolicy) DeepCopy() *FirewallPolicy {
if in == nil { if in == nil {
return nil return nil
} }
out := new(FirewallRule) out := new(FirewallPolicy)
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *FirewallRule) DeepCopyObject() runtime.Object { func (in *FirewallPolicy) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil { if c := in.DeepCopy(); c != nil {
return c return c
} }
@@ -161,31 +267,46 @@ func (in *FirewallRule) DeepCopyObject() runtime.Object {
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallRuleList) DeepCopyInto(out *FirewallRuleList) { func (in *FirewallPolicyEntry) DeepCopyInto(out *FirewallPolicyEntry) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallPolicyEntry.
func (in *FirewallPolicyEntry) DeepCopy() *FirewallPolicyEntry {
if in == nil {
return nil
}
out := new(FirewallPolicyEntry)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallPolicyList) DeepCopyInto(out *FirewallPolicyList) {
*out = *in *out = *in
out.TypeMeta = in.TypeMeta out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta) in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil { if in.Items != nil {
in, out := &in.Items, &out.Items in, out := &in.Items, &out.Items
*out = make([]FirewallRule, len(*in)) *out = make([]FirewallPolicy, len(*in))
for i := range *in { for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
} }
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallRuleList. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallPolicyList.
func (in *FirewallRuleList) DeepCopy() *FirewallRuleList { func (in *FirewallPolicyList) DeepCopy() *FirewallPolicyList {
if in == nil { if in == nil {
return nil return nil
} }
out := new(FirewallRuleList) out := new(FirewallPolicyList)
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *FirewallRuleList) DeepCopyObject() runtime.Object { func (in *FirewallPolicyList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil { if c := in.DeepCopy(); c != nil {
return c return c
} }
@@ -193,31 +314,88 @@ func (in *FirewallRuleList) DeepCopyObject() runtime.Object {
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallRuleSpec) DeepCopyInto(out *FirewallRuleSpec) { func (in *FirewallPolicyResourcesManaged) DeepCopyInto(out *FirewallPolicyResourcesManaged) {
*out = *in *out = *in
if in.UnifiFirewallPolicies != nil {
in, out := &in.UnifiFirewallPolicies, &out.UnifiFirewallPolicies
*out = make([]UnifiFirewallPolicyEntry, len(*in))
copy(*out, *in)
}
if in.FirewallGroups != nil {
in, out := &in.FirewallGroups, &out.FirewallGroups
*out = make([]FirewallGroupEntry, len(*in))
copy(*out, *in)
}
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallRuleSpec. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallPolicyResourcesManaged.
func (in *FirewallRuleSpec) DeepCopy() *FirewallRuleSpec { func (in *FirewallPolicyResourcesManaged) DeepCopy() *FirewallPolicyResourcesManaged {
if in == nil { if in == nil {
return nil return nil
} }
out := new(FirewallRuleSpec) out := new(FirewallPolicyResourcesManaged)
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallRuleStatus) DeepCopyInto(out *FirewallRuleStatus) { func (in *FirewallPolicySpec) DeepCopyInto(out *FirewallPolicySpec) {
*out = *in *out = *in
in.Source.DeepCopyInto(&out.Source)
in.Destination.DeepCopyInto(&out.Destination)
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallRuleStatus. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallPolicySpec.
func (in *FirewallRuleStatus) DeepCopy() *FirewallRuleStatus { func (in *FirewallPolicySpec) DeepCopy() *FirewallPolicySpec {
if in == nil { if in == nil {
return nil return nil
} }
out := new(FirewallRuleStatus) out := new(FirewallPolicySpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallPolicyStatus) DeepCopyInto(out *FirewallPolicyStatus) {
*out = *in
if in.ResourcesManaged != nil {
in, out := &in.ResourcesManaged, &out.ResourcesManaged
*out = new(FirewallPolicyResourcesManaged)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallPolicyStatus.
func (in *FirewallPolicyStatus) DeepCopy() *FirewallPolicyStatus {
if in == nil {
return nil
}
out := new(FirewallPolicyStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallSource) DeepCopyInto(out *FirewallSource) {
*out = *in
if in.FirewallZones != nil {
in, out := &in.FirewallZones, &out.FirewallZones
*out = make([]FirewallZoneEntry, len(*in))
copy(*out, *in)
}
if in.Networks != nil {
in, out := &in.Networks, &out.Networks
*out = make([]NetworkEntry, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallSource.
func (in *FirewallSource) DeepCopy() *FirewallSource {
if in == nil {
return nil
}
out := new(FirewallSource)
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
@@ -228,7 +406,7 @@ func (in *FirewallZone) DeepCopyInto(out *FirewallZone) {
out.TypeMeta = in.TypeMeta out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec) in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status in.Status.DeepCopyInto(&out.Status)
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallZone. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallZone.
@@ -249,6 +427,21 @@ func (in *FirewallZone) DeepCopyObject() runtime.Object {
return nil return nil
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallZoneEntry) DeepCopyInto(out *FirewallZoneEntry) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallZoneEntry.
func (in *FirewallZoneEntry) DeepCopy() *FirewallZoneEntry {
if in == nil {
return nil
}
out := new(FirewallZoneEntry)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallZoneList) DeepCopyInto(out *FirewallZoneList) { func (in *FirewallZoneList) DeepCopyInto(out *FirewallZoneList) {
*out = *in *out = *in
@@ -281,6 +474,26 @@ func (in *FirewallZoneList) DeepCopyObject() runtime.Object {
return nil return nil
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallZoneResourcesManaged) DeepCopyInto(out *FirewallZoneResourcesManaged) {
*out = *in
if in.UnifiFirewallZones != nil {
in, out := &in.UnifiFirewallZones, &out.UnifiFirewallZones
*out = make([]NamedUnifiResource, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallZoneResourcesManaged.
func (in *FirewallZoneResourcesManaged) DeepCopy() *FirewallZoneResourcesManaged {
if in == nil {
return nil
}
out := new(FirewallZoneResourcesManaged)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallZoneSpec) DeepCopyInto(out *FirewallZoneSpec) { func (in *FirewallZoneSpec) DeepCopyInto(out *FirewallZoneSpec) {
*out = *in *out = *in
@@ -304,6 +517,11 @@ func (in *FirewallZoneSpec) DeepCopy() *FirewallZoneSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallZoneStatus) DeepCopyInto(out *FirewallZoneStatus) { func (in *FirewallZoneStatus) DeepCopyInto(out *FirewallZoneStatus) {
*out = *in *out = *in
if in.ResourcesManaged != nil {
in, out := &in.ResourcesManaged, &out.ResourcesManaged
*out = new(FirewallZoneResourcesManaged)
(*in).DeepCopyInto(*out)
}
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallZoneStatus. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallZoneStatus.
@@ -316,6 +534,36 @@ func (in *FirewallZoneStatus) DeepCopy() *FirewallZoneStatus {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NamedUnifiResource) DeepCopyInto(out *NamedUnifiResource) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamedUnifiResource.
func (in *NamedUnifiResource) DeepCopy() *NamedUnifiResource {
if in == nil {
return nil
}
out := new(NamedUnifiResource)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NetworkEntry) DeepCopyInto(out *NetworkEntry) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkEntry.
func (in *NetworkEntry) DeepCopy() *NetworkEntry {
if in == nil {
return nil
}
out := new(NetworkEntry)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Networkconfiguration) DeepCopyInto(out *Networkconfiguration) { func (in *Networkconfiguration) DeepCopyInto(out *Networkconfiguration) {
*out = *in *out = *in
@@ -375,6 +623,26 @@ func (in *NetworkconfigurationList) DeepCopyObject() runtime.Object {
return nil return nil
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NetworkconfigurationResourcesManaged) DeepCopyInto(out *NetworkconfigurationResourcesManaged) {
*out = *in
if in.UnifiNetworks != nil {
in, out := &in.UnifiNetworks, &out.UnifiNetworks
*out = make([]NamedUnifiResource, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkconfigurationResourcesManaged.
func (in *NetworkconfigurationResourcesManaged) DeepCopy() *NetworkconfigurationResourcesManaged {
if in == nil {
return nil
}
out := new(NetworkconfigurationResourcesManaged)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NetworkconfigurationSpec) DeepCopyInto(out *NetworkconfigurationSpec) { func (in *NetworkconfigurationSpec) DeepCopyInto(out *NetworkconfigurationSpec) {
*out = *in *out = *in
@@ -393,6 +661,11 @@ func (in *NetworkconfigurationSpec) DeepCopy() *NetworkconfigurationSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NetworkconfigurationStatus) DeepCopyInto(out *NetworkconfigurationStatus) { func (in *NetworkconfigurationStatus) DeepCopyInto(out *NetworkconfigurationStatus) {
*out = *in *out = *in
if in.ResourcesManaged != nil {
in, out := &in.ResourcesManaged, &out.ResourcesManaged
*out = new(NetworkconfigurationResourcesManaged)
(*in).DeepCopyInto(*out)
}
if in.LastSyncTime != nil { if in.LastSyncTime != nil {
in, out := &in.LastSyncTime, &out.LastSyncTime in, out := &in.LastSyncTime, &out.LastSyncTime
*out = (*in).DeepCopy() *out = (*in).DeepCopy()
@@ -408,3 +681,33 @@ func (in *NetworkconfigurationStatus) DeepCopy() *NetworkconfigurationStatus {
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceEntry) DeepCopyInto(out *ServiceEntry) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceEntry.
func (in *ServiceEntry) DeepCopy() *ServiceEntry {
if in == nil {
return nil
}
out := new(ServiceEntry)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UnifiFirewallPolicyEntry) DeepCopyInto(out *UnifiFirewallPolicyEntry) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UnifiFirewallPolicyEntry.
func (in *UnifiFirewallPolicyEntry) DeepCopy() *UnifiFirewallPolicyEntry {
if in == nil {
return nil
}
out := new(UnifiFirewallPolicyEntry)
in.DeepCopyInto(out)
return out
}

View File

@@ -38,6 +38,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook"
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1" unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
"github.com/vegardengen/unifi-network-operator/internal/config"
"github.com/vegardengen/unifi-network-operator/internal/controller" "github.com/vegardengen/unifi-network-operator/internal/controller"
"github.com/vegardengen/unifi-network-operator/internal/unifi" "github.com/vegardengen/unifi-network-operator/internal/unifi"
// +kubebuilder:scaffold:imports // +kubebuilder:scaffold:imports
@@ -203,6 +204,8 @@ func main() {
os.Exit(1) os.Exit(1)
} }
configLoader := config.NewConfigLoader(mgr.GetClient())
// Unifi client // Unifi client
setupLog.Info("Setting up UniFi client") setupLog.Info("Setting up UniFi client")
unifiClient, err := unifi.CreateUnifiClient() unifiClient, err := unifi.CreateUnifiClient()
@@ -213,34 +216,39 @@ func main() {
setupLog.Info("Finished Setting up UniFi client") setupLog.Info("Finished Setting up UniFi client")
if err = (&controller.NetworkconfigurationReconciler{ if err = (&controller.NetworkconfigurationReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
UnifiClient: unifiClient, UnifiClient: unifiClient,
ConfigLoader: configLoader,
}).SetupWithManager(mgr); err != nil { }).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Networkconfiguration") setupLog.Error(err, "unable to create controller", "controller", "Networkconfiguration")
os.Exit(1) os.Exit(1)
} }
if err = (&controller.FirewallZoneReconciler{ if err = (&controller.FirewallZoneReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
UnifiClient: unifiClient, UnifiClient: unifiClient,
ConfigLoader: configLoader,
}).SetupWithManager(mgr); err != nil { }).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "FirewallZone") setupLog.Error(err, "unable to create controller", "controller", "FirewallZone")
os.Exit(1) os.Exit(1)
} }
if err = (&controller.FirewallRuleReconciler{ if err = (&controller.FirewallPolicyReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
UnifiClient: unifiClient,
ConfigLoader: configLoader,
}).SetupWithManager(mgr); err != nil { }).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "FirewallRule") setupLog.Error(err, "unable to create controller", "controller", "FirewallPolicy")
os.Exit(1) os.Exit(1)
} }
// +kubebuilder:scaffold:builder // +kubebuilder:scaffold:builder
if err = (&controller.FirewallGroupReconciler{ if err = (&controller.FirewallGroupReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
UnifiClient: unifiClient, UnifiClient: unifiClient,
ConfigLoader: configLoader,
}).SetupWithManager(mgr); err != nil { }).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "FirewallGroup") setupLog.Error(err, "unable to create controller", "controller", "FirewallGroup")
os.Exit(1) os.Exit(1)

View File

@@ -37,15 +37,14 @@ spec:
metadata: metadata:
type: object type: object
spec: spec:
description: FirewallGroupSpec defines the desired state of FirewallGroup.
properties: properties:
addressType: auto_created_from:
description: AddressType can be "ip", "cidr", or "both" properties:
enum: name:
- ip type: string
- cidr namespace:
- both type: string
type: string type: object
autoIncludeSelector: autoIncludeSelector:
description: AutoIncludeSelector defines which services to extract description: AutoIncludeSelector defines which services to extract
addresses from addresses from
@@ -93,18 +92,33 @@ spec:
type: object type: object
type: object type: object
x-kubernetes-map-type: atomic x-kubernetes-map-type: atomic
id:
description: |-
Foo is an example field of FirewallGroup. Edit firewallgroup_types.go to remove/update
Description is a human-readable explanation for the object
type: string
manual_services:
items:
properties:
name:
type: string
namespace:
type: string
type: object
type: array
manualAddresses: manualAddresses:
description: ManualAddresses is a list of manual IPs or CIDRs (IPv4 description: ManualAddresses is a list of manual IPs or CIDRs (IPv4
or IPv6) or IPv6)
items: items:
type: string type: string
type: array type: array
manualPorts:
items:
type: string
type: array
matchServicesInAllNamespaces: matchServicesInAllNamespaces:
type: boolean type: boolean
name: name:
description: |-
Foo is an example field of FirewallGroup. Edit firewallgroup_types.go to remove/update
Description is a human-readable explanation for the object
type: string type: string
type: object type: object
status: status:
@@ -114,10 +128,53 @@ spec:
description: LastSyncTime is the last time the object was synced description: LastSyncTime is the last time the object was synced
format: date-time format: date-time
type: string type: string
resolvedAddresses: resolvedIPV4Addresses:
items: items:
type: string type: string
type: array type: array
resolvedIPV6Addresses:
items:
type: string
type: array
resolvedTCPorts:
items:
type: string
type: array
resolvedUDPorts:
items:
type: string
type: array
resources_managed:
properties:
ipv4_object:
properties:
id:
type: string
name:
type: string
type: object
ipv6_object:
properties:
id:
type: string
name:
type: string
type: object
tcp_ports_object:
properties:
id:
type: string
name:
type: string
type: object
udp_ports_object:
properties:
id:
type: string
name:
type: string
type: object
type: object
syncedWithUnifi: syncedWithUnifi:
description: SyncedWithUnifi indicates whether the addresses are successfully description: SyncedWithUnifi indicates whether the addresses are successfully
pushed pushed

View File

@@ -0,0 +1,138 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.2
name: firewallpolicies.unifi.engen.priv.no
spec:
group: unifi.engen.priv.no
names:
kind: FirewallPolicy
listKind: FirewallPolicyList
plural: firewallpolicies
singular: firewallpolicy
scope: Namespaced
versions:
- name: v1beta1
schema:
openAPIV3Schema:
description: FirewallPolicy is the Schema for the firewallpolicies 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:
properties:
destination:
properties:
firewall_groups:
items:
properties:
name:
type: string
namespace:
type: string
type: object
type: array
services:
items:
properties:
name:
type: string
namespace:
type: string
type: object
type: array
type: object
match_firewall_groups_in_all_namespaces:
type: boolean
match_services_in_all_namespaces:
type: boolean
name:
type: string
source:
properties:
from_networks:
items:
properties:
name:
type: string
namespace:
type: string
type: object
type: array
from_zones:
items:
properties:
name:
type: string
namespace:
type: string
type: object
type: array
type: object
required:
- destination
- name
- source
type: object
status:
description: FirewallPolicyStatus defines the observed state of FirewallPolicy.
properties:
resources_managed:
properties:
firewall_groups_managed:
items:
properties:
name:
type: string
namespace:
type: string
type: object
type: array
firewall_policies_managed:
items:
properties:
from:
type: string
tcpipv4_id:
type: string
tcpipv6_id:
type: string
to:
type: string
udpipv4_id:
type: string
udpipv6_id:
type: string
required:
- from
- tcpipv4_id
- tcpipv6_id
- to
- udpipv4_id
- udpipv6_id
type: object
type: array
type: object
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -1,54 +0,0 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.2
name: firewallrules.unifi.engen.priv.no
spec:
group: unifi.engen.priv.no
names:
kind: FirewallRule
listKind: FirewallRuleList
plural: firewallrules
singular: firewallrule
scope: Namespaced
versions:
- name: v1beta1
schema:
openAPIV3Schema:
description: FirewallRule is the Schema for the firewallrules 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: FirewallRuleSpec defines the desired state of FirewallRule.
properties:
foo:
description: Foo is an example field of FirewallRule. Edit firewallrule_types.go
to remove/update
type: string
type: object
status:
description: FirewallRuleStatus defines the observed state of FirewallRule.
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -54,6 +54,19 @@ spec:
type: object type: object
status: status:
description: FirewallZoneStatus defines the observed state of FirewallZone. description: FirewallZoneStatus defines the observed state of FirewallZone.
properties:
resources_managed:
properties:
firewall_zones_managed:
items:
properties:
id:
type: string
name:
type: string
type: object
type: array
type: object
type: object type: object
type: object type: object
served: true served: true

View File

@@ -40,9 +40,11 @@ spec:
spec: spec:
description: NetworkconfigurationSpec defines the desired state of Networkconfiguration. description: NetworkconfigurationSpec defines the desired state of Networkconfiguration.
properties: properties:
enabled: _id:
description: Foo is an example field of Networkconfiguration. Edit description: Foo is an example field of Networkconfiguration. Edit
networkconfiguration_types.go to remove/update networkconfiguration_types.go to remove/update
type: string
enabled:
type: boolean type: boolean
firewall_zone: firewall_zone:
type: string type: string
@@ -62,8 +64,6 @@ spec:
type: string type: string
name: name:
type: string type: string
network_id:
type: string
networkgroup: networkgroup:
type: string type: string
purpose: purpose:
@@ -93,6 +93,18 @@ spec:
description: LastSyncTime is the last time the object was synced description: LastSyncTime is the last time the object was synced
format: date-time format: date-time
type: string type: string
resources_managed:
properties:
networks_managed:
items:
properties:
id:
type: string
name:
type: string
type: object
type: array
type: object
syncedWithUnifi: syncedWithUnifi:
description: SyncedWithUnifi indicates whether the addresses are successfully description: SyncedWithUnifi indicates whether the addresses are successfully
pushed pushed

View File

@@ -4,7 +4,7 @@
resources: resources:
- bases/unifi.engen.priv.no_networkconfigurations.yaml - bases/unifi.engen.priv.no_networkconfigurations.yaml
- bases/unifi.engen.priv.no_firewallzones.yaml - bases/unifi.engen.priv.no_firewallzones.yaml
- bases/unifi.engen.priv.no_firewallrules.yaml - bases/unifi.engen.priv.no_firewallpolicies.yaml
# +kubebuilder:scaffold:crdkustomizeresource # +kubebuilder:scaffold:crdkustomizeresource
patches: patches:

View File

@@ -11,17 +11,17 @@ metadata:
labels: labels:
app.kubernetes.io/name: unifi-network-operator app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize app.kubernetes.io/managed-by: kustomize
name: firewallrule-admin-role name: firewallpolicy-admin-role
rules: rules:
- apiGroups: - apiGroups:
- unifi.engen.priv.no - unifi.engen.priv.no
resources: resources:
- firewallrules - firewallpolicies
verbs: verbs:
- '*' - '*'
- apiGroups: - apiGroups:
- unifi.engen.priv.no - unifi.engen.priv.no
resources: resources:
- firewallrules/status - firewallpolicies/status
verbs: verbs:
- get - get

View File

@@ -11,12 +11,12 @@ metadata:
labels: labels:
app.kubernetes.io/name: unifi-network-operator app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize app.kubernetes.io/managed-by: kustomize
name: firewallrule-editor-role name: firewallpolicy-editor-role
rules: rules:
- apiGroups: - apiGroups:
- unifi.engen.priv.no - unifi.engen.priv.no
resources: resources:
- firewallrules - firewallpolicies
verbs: verbs:
- create - create
- delete - delete
@@ -28,6 +28,6 @@ rules:
- apiGroups: - apiGroups:
- unifi.engen.priv.no - unifi.engen.priv.no
resources: resources:
- firewallrules/status - firewallpolicies/status
verbs: verbs:
- get - get

View File

@@ -11,12 +11,12 @@ metadata:
labels: labels:
app.kubernetes.io/name: unifi-network-operator app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize app.kubernetes.io/managed-by: kustomize
name: firewallrule-viewer-role name: firewallpolicy-viewer-role
rules: rules:
- apiGroups: - apiGroups:
- unifi.engen.priv.no - unifi.engen.priv.no
resources: resources:
- firewallrules - firewallpolicies
verbs: verbs:
- get - get
- list - list
@@ -24,6 +24,6 @@ rules:
- apiGroups: - apiGroups:
- unifi.engen.priv.no - unifi.engen.priv.no
resources: resources:
- firewallrules/status - firewallpolicies/status
verbs: verbs:
- get - get

View File

@@ -22,9 +22,9 @@ resources:
# default, aiding admins in cluster management. Those roles are # default, aiding admins in cluster management. Those roles are
# not used by the {{ .ProjectName }} itself. You can comment the following lines # not used by the {{ .ProjectName }} itself. You can comment the following lines
# if you do not want those helpers be installed with your Project. # if you do not want those helpers be installed with your Project.
- firewallrule_admin_role.yaml - firewallpolicy_admin_role.yaml
- firewallrule_editor_role.yaml - firewallpolicy_editor_role.yaml
- firewallrule_viewer_role.yaml - firewallpolicy_viewer_role.yaml
- firewallzone_admin_role.yaml - firewallzone_admin_role.yaml
- firewallzone_editor_role.yaml - firewallzone_editor_role.yaml
- firewallzone_viewer_role.yaml - firewallzone_viewer_role.yaml

View File

@@ -7,6 +7,7 @@ rules:
- apiGroups: - apiGroups:
- "" - ""
resources: resources:
- configmaps
- services - services
verbs: verbs:
- get - get
@@ -16,7 +17,7 @@ rules:
- unifi.engen.priv.no - unifi.engen.priv.no
resources: resources:
- firewallgroups - firewallgroups
- firewallrules - firewallpolicies
- firewallzones - firewallzones
- networkconfigurations - networkconfigurations
verbs: verbs:
@@ -31,7 +32,7 @@ rules:
- unifi.engen.priv.no - unifi.engen.priv.no
resources: resources:
- firewallgroups/finalizers - firewallgroups/finalizers
- firewallrules/finalizers - firewallpolicies/finalizers
- firewallzones/finalizers - firewallzones/finalizers
- networkconfigurations/finalizers - networkconfigurations/finalizers
verbs: verbs:
@@ -40,7 +41,7 @@ rules:
- unifi.engen.priv.no - unifi.engen.priv.no
resources: resources:
- firewallgroups/status - firewallgroups/status
- firewallrules/status - firewallpolicies/status
- firewallzones/status - firewallzones/status
- networkconfigurations/status - networkconfigurations/status
verbs: verbs:

View File

@@ -1,9 +1,8 @@
apiVersion: unifi.engen.priv.no/v1beta1 apiVersion: unifi.engen.priv.no/v1beta1
kind: FirewallRule kind: FirewallPolicy
metadata: metadata:
labels: labels:
app.kubernetes.io/name: unifi-network-operator app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize app.kubernetes.io/managed-by: kustomize
name: firewallrule-sample name: firewallpolicy-sample
spec: spec:
# TODO(user): Add fields here

2
go.mod
View File

@@ -9,7 +9,7 @@ godebug default=go1.23
require ( require (
github.com/onsi/ginkgo/v2 v2.23.4 github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.37.0 github.com/onsi/gomega v1.37.0
github.com/vegardengen/go-unifi v0.0.1-alpha9 github.com/vegardengen/go-unifi v0.0.1-alpha25
k8s.io/api v0.32.1 k8s.io/api v0.32.1
k8s.io/apimachinery v0.32.1 k8s.io/apimachinery v0.32.1
k8s.io/client-go v0.32.1 k8s.io/client-go v0.32.1

30
go.sum
View File

@@ -1080,8 +1080,38 @@ github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinC
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
github.com/vegardengen/go-unifi v0.0.1-alpha1 h1:PF1Y4NH/bfk6A7PLGxiqAhQkrfPau0B1vSayfmqlbRM= github.com/vegardengen/go-unifi v0.0.1-alpha1 h1:PF1Y4NH/bfk6A7PLGxiqAhQkrfPau0B1vSayfmqlbRM=
github.com/vegardengen/go-unifi v0.0.1-alpha1/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc= github.com/vegardengen/go-unifi v0.0.1-alpha1/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha11 h1:+2eOU2jkJSji6YYVHUfQht/tjfGeLZCQlNGD2Oh7ndE=
github.com/vegardengen/go-unifi v0.0.1-alpha11/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha12 h1:1g0JGmB1FivG0nU9EvZVyZkX5i+HR3qRGto0jk4k8hU=
github.com/vegardengen/go-unifi v0.0.1-alpha12/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha13 h1:MEFYSlYGBmbSkxTAJ4i89iV2ji5wrvDsRgx4LmuCz3Y=
github.com/vegardengen/go-unifi v0.0.1-alpha13/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha14 h1:O5vrSemL/XbX+9aNmovYNE0xAtdurVa3TawN2wBaUbs=
github.com/vegardengen/go-unifi v0.0.1-alpha14/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha15 h1:qjmfOmxDBu8/pSKiWP7YQzh8qZEUUJ9S4vs93GjfeqM=
github.com/vegardengen/go-unifi v0.0.1-alpha15/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha16 h1:OVVaOjYO8dh32fF1BQxSrRoV878ZBLtB/KQx9jIe9Ak=
github.com/vegardengen/go-unifi v0.0.1-alpha16/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha17 h1:SoSStxP7CV+lxlKg2mqfadpXCQ2xhwRwM78sdxtPmKU=
github.com/vegardengen/go-unifi v0.0.1-alpha17/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha18 h1:vUaALQmoRzP1z764GCzfQLt1IjnlG/ODJvNxx0B84D4=
github.com/vegardengen/go-unifi v0.0.1-alpha18/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha19 h1:B1U2hXNT/6HHsw92oHElhwpuYvwIUCvCdQ5D/tOik4o=
github.com/vegardengen/go-unifi v0.0.1-alpha19/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha2 h1:2aVWpzi+i1X4rVFLOphAp7ZoTvER/rySzvpdB5cul4I= github.com/vegardengen/go-unifi v0.0.1-alpha2 h1:2aVWpzi+i1X4rVFLOphAp7ZoTvER/rySzvpdB5cul4I=
github.com/vegardengen/go-unifi v0.0.1-alpha2/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc= github.com/vegardengen/go-unifi v0.0.1-alpha2/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha20 h1:kQ+QQ1IYGwvjLkoXYBEU4gSD2yQj+9FwHtMf7+GQlok=
github.com/vegardengen/go-unifi v0.0.1-alpha20/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha21 h1:a+yPTu97ETRdkhmKltMrJTo28qA0GPWctPFyICLCUZw=
github.com/vegardengen/go-unifi v0.0.1-alpha21/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha22 h1:2axPhv/S6bawsh7n3/ik4oBcRHNFjrTykOzgTRd+LnU=
github.com/vegardengen/go-unifi v0.0.1-alpha22/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha23 h1:PFKiAKy9u8s04a8cjSRIJa6N3BfPYQW8NmpEtXi+KJM=
github.com/vegardengen/go-unifi v0.0.1-alpha23/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha24 h1:0L5aRrQEdXBQAzO/AK/OaA/Gt2ymA8pzA8MoAk12aOc=
github.com/vegardengen/go-unifi v0.0.1-alpha24/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha25 h1:GAwtNpMslE6/0IfM80cWFaMqwqsn+NXlUVsvauN7v68=
github.com/vegardengen/go-unifi v0.0.1-alpha25/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha3 h1:ppllnTPduJIRZWSVLh9oGNXjo4L2hVzpVzCxvRhLSpM= github.com/vegardengen/go-unifi v0.0.1-alpha3 h1:ppllnTPduJIRZWSVLh9oGNXjo4L2hVzpVzCxvRhLSpM=
github.com/vegardengen/go-unifi v0.0.1-alpha3/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc= github.com/vegardengen/go-unifi v0.0.1-alpha3/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha4 h1:ZqdkYf/DRBwc1O+TwNsEAuMiXEv4j82XWbhbcMqUDnU= github.com/vegardengen/go-unifi v0.0.1-alpha4 h1:ZqdkYf/DRBwc1O+TwNsEAuMiXEv4j82XWbhbcMqUDnU=

44
internal/config/config.go Normal file
View File

@@ -0,0 +1,44 @@
package config
import (
"context"
"sync"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type ConfigLoaderType struct {
Client client.Client
mu sync.Mutex
loaded bool
config *corev1.ConfigMap
err error
}
func NewConfigLoader(k8sClient client.Client) *ConfigLoaderType {
return &ConfigLoaderType{Client: k8sClient}
}
func (c *ConfigLoaderType) GetConfig(ctx context.Context, name string) (*corev1.ConfigMap, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.loaded {
return c.config, c.err
}
cm := &corev1.ConfigMap{}
err := c.Client.Get(ctx, types.NamespacedName{
Name: name,
Namespace: "unifi-network-operator-system",
}, cm)
c.loaded = true
c.config = cm
c.err = err
return cm, err
}

View File

@@ -21,6 +21,9 @@ import (
"fmt" "fmt"
"net" "net"
"reflect" "reflect"
"regexp"
"slices"
"strconv"
"strings" "strings"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@@ -29,6 +32,7 @@ import (
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client" "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/handler"
"sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -36,20 +40,25 @@ import (
goUnifi "github.com/vegardengen/go-unifi/unifi" goUnifi "github.com/vegardengen/go-unifi/unifi"
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1" unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
"github.com/vegardengen/unifi-network-operator/internal/config"
"github.com/vegardengen/unifi-network-operator/internal/unifi" "github.com/vegardengen/unifi-network-operator/internal/unifi"
) )
const firewallGroupFinalizer = "finalizer.unifi.engen.priv.no/firewallgroup"
// FirewallGroupReconciler reconciles a FirewallGroup object // FirewallGroupReconciler reconciles a FirewallGroup object
type FirewallGroupReconciler struct { type FirewallGroupReconciler struct {
client.Client client.Client
Scheme *runtime.Scheme Scheme *runtime.Scheme
UnifiClient *unifi.UnifiClient UnifiClient *unifi.UnifiClient
ConfigLoader *config.ConfigLoaderType
} }
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallgroups,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallgroups,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallgroups/status,verbs=get;update;patch // +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallgroups/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallgroups/finalizers,verbs=update // +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallgroups/finalizers,verbs=update
// +kubebuilder:rbac:groups="",resources=services,verbs=list;get;watch // +kubebuilder:rbac:groups="",resources=services,verbs=list;get;watch
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=list;get;watch
// Reconcile is part of the main kubernetes reconciliation loop which aims to // Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state. // move the current state of the cluster closer to the desired state.
@@ -63,14 +72,159 @@ type FirewallGroupReconciler struct {
func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
log := log.FromContext(ctx) log := log.FromContext(ctx)
var nwObj unifiv1beta1.FirewallGroup
if err := r.Get(ctx, req.NamespacedName, &nwObj); err != nil { cfg, err := r.ConfigLoader.GetConfig(ctx, "unifi-operator-config")
if err != nil {
return ctrl.Result{}, err
}
defaultNs := cfg.Data["defaultNamespace"]
log.Info(defaultNs)
var firewallGroup unifiv1beta1.FirewallGroup
if err := r.Get(ctx, req.NamespacedName, &firewallGroup); err != nil {
return reconcile.Result{}, client.IgnoreNotFound(err) return reconcile.Result{}, client.IgnoreNotFound(err)
} }
log.Info(nwObj.Spec.Name) log.Info(firewallGroup.Spec.Name)
var ipv4, ipv6 []string
for _, addressEntry := range nwObj.Spec.ManualAddresses { // Check if the object is being deleted
if firewallGroup.DeletionTimestamp != nil {
if controllerutil.ContainsFinalizer(&firewallGroup, firewallGroupFinalizer) {
err := r.UnifiClient.Reauthenticate()
if err != nil {
return reconcile.Result{}, err
}
log.Info("Running finalizer logic for FirewallGroup", "name", firewallGroup.Name)
if len(firewallGroup.Status.ResourcesManaged.IPV4Object.ID) > 0 {
log.Info(fmt.Sprintf("Trying to delete ipv4 object %s", firewallGroup.Status.ResourcesManaged.IPV4Object.ID))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.IPV4Object.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") {
firewall_group, err := r.UnifiClient.Client.GetFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.IPV4Object.ID)
if err != nil {
log.Error(err, "Could not get object for renaming.")
return reconcile.Result{}, err
} else {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"127.0.0.1"}
firewall_group.Name = firewall_group.Name + "-deleted"
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
}
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
}
}
if len(firewallGroup.Status.ResourcesManaged.IPV6Object.ID) > 0 {
log.Info(fmt.Sprintf("Trying to delete ipv6 object %s", firewallGroup.Status.ResourcesManaged.IPV6Object.ID))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.IPV6Object.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") {
firewall_group, err := r.UnifiClient.Client.GetFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.IPV6Object.ID)
if err != nil {
log.Error(err, "Could not get object for renaming.")
return reconcile.Result{}, err
} else {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"::1"}
firewall_group.Name = firewall_group.Name + "-deleted"
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
}
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
}
}
if len(firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID) > 0 {
log.Info(fmt.Sprintf("Trying to delete tcp object %s", firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") {
firewall_group, err := r.UnifiClient.Client.GetFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID)
if err != nil {
log.Error(err, "Could not get object for renaming.")
return reconcile.Result{}, err
} else {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"0"}
firewall_group.Name = firewall_group.Name + "-deleted"
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
}
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
}
}
if len(firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID) > 0 {
log.Info(fmt.Sprintf("Trying to delete udp object %s", firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") {
firewall_group, err := r.UnifiClient.Client.GetFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID)
if err != nil {
log.Error(err, "Could not get object for renaming.")
return reconcile.Result{}, err
} else {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"0"}
firewall_group.Name = firewall_group.Name + "-deleted"
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
}
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
}
}
controllerutil.RemoveFinalizer(&firewallGroup, firewallGroupFinalizer)
if err := r.Update(ctx, &firewallGroup); err != nil {
return ctrl.Result{}, err
}
log.Info("Successfully finalized FirewallGroup")
}
return ctrl.Result{}, nil
}
if !controllerutil.ContainsFinalizer(&firewallGroup, firewallGroupFinalizer) {
controllerutil.AddFinalizer(&firewallGroup, firewallGroupFinalizer)
if err := r.Update(ctx, &firewallGroup); err != nil {
return ctrl.Result{}, err
}
}
var ipv4, ipv6, tcpports, udpports []string
for _, addressEntry := range firewallGroup.Spec.ManualAddresses {
ip := net.ParseIP(addressEntry) ip := net.ParseIP(addressEntry)
if ip != nil { if ip != nil {
@@ -98,8 +252,28 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
} }
} }
} }
for _, portEntry := range firewallGroup.Spec.ManualPorts {
port_type := "tcp"
port := portEntry
if match, _ := regexp.MatchString("(?:tcp|udp)\\/?)\\d+", string(portEntry)); match {
fields := strings.Split("/", portEntry)
port_type = fields[0]
port = fields[1]
}
if port_type == "tcp" {
if !slices.Contains(tcpports, port) {
tcpports = append(tcpports, port)
}
}
if port_type == "udp" {
if !slices.Contains(udpports, port) {
tcpports = append(udpports, port)
}
}
}
var services corev1.ServiceList var services corev1.ServiceList
if nwObj.Spec.MatchServicesInAllNamespaces { if firewallGroup.Spec.MatchServicesInAllNamespaces {
if err := r.List(ctx, &services); err != nil { if err := r.List(ctx, &services); err != nil {
log.Error(err, "unable to list services") log.Error(err, "unable to list services")
return reconcile.Result{}, err return reconcile.Result{}, err
@@ -111,8 +285,18 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
return reconcile.Result{}, err return reconcile.Result{}, err
} }
} }
serviceNamespaceNames := make(map[string]struct{})
for _, serviceEntry := range firewallGroup.Spec.ManualServices {
serviceNamespaceNames[serviceEntry.Namespace+"/"+serviceEntry.Name] = struct{}{}
}
log.Info(fmt.Sprintf("Manually specified: %+v", serviceNamespaceNames))
for _, service := range services.Items { for _, service := range services.Items {
if val, found := service.Annotations["unifi.engen.priv.no/firewall-group"]; found && val == nwObj.Name && service.Status.LoadBalancer.Ingress != nil { _, manually_specified := serviceNamespaceNames[service.Namespace+"/"+service.Name]
val, found := service.Annotations["unifi.engen.priv.no/firewall-group"]
log.Info(fmt.Sprintf("%s %sv %+v %+v", service.Name, val, manually_specified, found))
// if val, found := service.Annotations["unifi.engen.priv.no/firewall-group"]; (manually_specified || (found && val == firewallGroup.Name)) && service.Status.LoadBalancer.Ingress != nil {
if (manually_specified || (found && val == firewallGroup.Name)) && service.Status.LoadBalancer.Ingress != nil {
for _, ingress := range service.Status.LoadBalancer.Ingress { for _, ingress := range service.Status.LoadBalancer.Ingress {
if ingress.IP != "" { if ingress.IP != "" {
ip := ingress.IP ip := ingress.IP
@@ -123,15 +307,52 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
} }
} }
} }
if service.Spec.Ports != nil {
for _, portSpec := range service.Spec.Ports {
log.Info(fmt.Sprintf("portSpec: %+v", portSpec))
log.Info(fmt.Sprintf("Port: %s %d", strconv.Itoa(int(portSpec.Port)), portSpec.Port))
if portSpec.Protocol == "TCP" {
if !slices.Contains(tcpports, strconv.Itoa(int(portSpec.Port))) {
tcpports = append(tcpports, strconv.Itoa(int(portSpec.Port)))
}
}
if portSpec.Protocol == "UDP" {
if !slices.Contains(udpports, strconv.Itoa(int(portSpec.Port))) {
udpports = append(udpports, strconv.Itoa(int(portSpec.Port)))
}
}
}
}
} }
} }
nwObj.Status.ResolvedAddresses = ipv4 firewallGroup.Status.ResolvedIPV4Addresses = ipv4
nwObj.Status.ResolvedAddresses = append(nwObj.Status.ResolvedAddresses, ipv6...) firewallGroup.Status.ResolvedIPV6Addresses = ipv6
firewallGroup.Status.ResolvedTCPPorts = tcpports
firewallGroup.Status.ResolvedUDPPorts = udpports
currentTime := metav1.Now() currentTime := metav1.Now()
nwObj.Status.LastSyncTime = &currentTime firewallGroup.Status.LastSyncTime = &currentTime
nwObj.Status.SyncedWithUnifi = true firewallGroup.Status.SyncedWithUnifi = true
if firewallGroup.Status.ResourcesManaged == nil {
err := r.UnifiClient.Reauthenticate() firewallGroup.Status.ResourcesManaged = &unifiv1beta1.FirewallGroupResourcesManaged{
IPV4Object: &unifiv1beta1.NamedUnifiResource{
ID: "",
Name: "",
},
IPV6Object: &unifiv1beta1.NamedUnifiResource{
ID: "",
Name: "",
},
TCPPortsObject: &unifiv1beta1.NamedUnifiResource{
ID: "",
Name: "",
},
UDPPortsObject: &unifiv1beta1.NamedUnifiResource{
ID: "",
Name: "",
},
}
}
err = r.UnifiClient.Reauthenticate()
if err != nil { if err != nil {
return reconcile.Result{}, err return reconcile.Result{}, err
} }
@@ -140,10 +361,14 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
log.Error(err, "Could not list network objects") log.Error(err, "Could not list network objects")
return reconcile.Result{}, err return reconcile.Result{}, err
} }
ipv4_name := "k8s-" + nwObj.Spec.Name + "-ipv4" ipv4_name := "k8s-" + firewallGroup.Spec.Name + "-ipv4"
ipv6_name := "k8s-" + nwObj.Spec.Name + "-ipv6" ipv6_name := "k8s-" + firewallGroup.Spec.Name + "-ipv6"
tcpports_name := "k8s-" + firewallGroup.Spec.Name + "-tcpports"
udpports_name := "k8s-" + firewallGroup.Spec.Name + "-udpports"
ipv4_done := false ipv4_done := false
ipv6_done := false ipv6_done := false
tcpports_done := false
udpports_done := false
for _, firewall_group := range firewall_groups { for _, firewall_group := range firewall_groups {
if firewall_group.Name == ipv4_name { if firewall_group.Name == ipv4_name {
if len(ipv4) == 0 { if len(ipv4) == 0 {
@@ -161,10 +386,15 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
log.Error(updateerr, "Could neither delete or rename firewall group") log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr return reconcile.Result{}, updateerr
} }
firewallGroup.Status.ResourcesManaged.IPV4Object.Name = ""
firewallGroup.Status.ResourcesManaged.IPV4Object.ID = ""
} else { } else {
log.Error(err, "Could not delete firewall group") log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err return reconcile.Result{}, err
} }
} else {
firewallGroup.Status.ResourcesManaged.IPV4Object.Name = ""
firewallGroup.Status.ResourcesManaged.IPV4Object.ID = ""
} }
ipv4_done = true ipv4_done = true
} else { } else {
@@ -196,10 +426,15 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
log.Error(updateerr, "Could neither delete or rename firewall group") log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr return reconcile.Result{}, updateerr
} }
firewallGroup.Status.ResourcesManaged.IPV6Object.Name = ""
firewallGroup.Status.ResourcesManaged.IPV6Object.ID = ""
} else { } else {
log.Error(err, "Could not delete firewall group") log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err return reconcile.Result{}, err
} }
} else {
firewallGroup.Status.ResourcesManaged.IPV6Object.Name = ""
firewallGroup.Status.ResourcesManaged.IPV6Object.ID = ""
} }
ipv6_done = true ipv6_done = true
} else { } else {
@@ -215,6 +450,86 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
ipv6_done = true ipv6_done = true
} }
} }
if firewall_group.Name == tcpports_name {
if len(tcpports) == 0 {
log.Info(fmt.Sprintf("Delete %s", tcpports_name))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"0"}
firewall_group.Name = firewall_group.Name + "-deleted"
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
firewallGroup.Status.ResourcesManaged.TCPPortsObject.Name = ""
firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID = ""
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
} else {
firewallGroup.Status.ResourcesManaged.TCPPortsObject.Name = ""
firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID = ""
}
tcpports_done = true
} else {
if !reflect.DeepEqual(firewall_group.GroupMembers, tcpports) {
firewall_group.GroupMembers = tcpports
log.Info(fmt.Sprintf("Updating %s", tcpports_name))
_, err := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if err != nil {
log.Error(err, "Could not update firewall group")
return reconcile.Result{}, err
}
}
tcpports_done = true
}
}
if firewall_group.Name == udpports_name {
if len(udpports) == 0 {
log.Info(fmt.Sprintf("Delete %s", udpports_name))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"127.0.0.1"}
firewall_group.Name = firewall_group.Name + "-deleted"
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
firewallGroup.Status.ResourcesManaged.UDPPortsObject.Name = ""
firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID = ""
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
} else {
firewallGroup.Status.ResourcesManaged.UDPPortsObject.Name = ""
firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID = ""
}
udpports_done = true
} else {
if !reflect.DeepEqual(firewall_group.GroupMembers, udpports) {
firewall_group.GroupMembers = udpports
log.Info(fmt.Sprintf("Updating %s", udpports_name))
_, err := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if err != nil {
log.Error(err, "Could not update firewall group")
return reconcile.Result{}, err
}
}
udpports_done = true
}
}
if firewall_group.Name == ipv4_name+"-deleted" && len(ipv4) > 0 { if firewall_group.Name == ipv4_name+"-deleted" && len(ipv4) > 0 {
firewall_group.Name = ipv4_name firewall_group.Name = ipv4_name
firewall_group.GroupMembers = ipv4 firewall_group.GroupMembers = ipv4
@@ -224,6 +539,8 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
log.Error(err, "Could not update firewall group") log.Error(err, "Could not update firewall group")
return reconcile.Result{}, err return reconcile.Result{}, err
} }
firewallGroup.Status.ResourcesManaged.IPV4Object.Name = firewall_group.Name
firewallGroup.Status.ResourcesManaged.IPV4Object.ID = firewall_group.ID
ipv4_done = true ipv4_done = true
} }
if firewall_group.Name == ipv6_name+"-deleted" && len(ipv6) > 0 { if firewall_group.Name == ipv6_name+"-deleted" && len(ipv6) > 0 {
@@ -235,8 +552,36 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
log.Error(err, "Could not update firewall group") log.Error(err, "Could not update firewall group")
return reconcile.Result{}, err return reconcile.Result{}, err
} }
firewallGroup.Status.ResourcesManaged.IPV6Object.Name = firewall_group.Name
firewallGroup.Status.ResourcesManaged.IPV6Object.ID = firewall_group.ID
ipv6_done = true ipv6_done = true
} }
if firewall_group.Name == tcpports_name+"-deleted" && len(tcpports) > 0 {
firewall_group.Name = tcpports_name
firewall_group.GroupMembers = tcpports
log.Info(fmt.Sprintf("Creating %s (from previously deleted)", tcpports_name))
_, err := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if err != nil {
log.Error(err, "Could not update firewall group")
return reconcile.Result{}, err
}
firewallGroup.Status.ResourcesManaged.TCPPortsObject.Name = firewall_group.Name
firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID = firewall_group.ID
tcpports_done = true
}
if firewall_group.Name == udpports_name+"-deleted" && len(udpports) > 0 {
firewall_group.Name = udpports_name
firewall_group.GroupMembers = udpports
log.Info(fmt.Sprintf("Creating %s (from previously deleted)", udpports_name))
_, err := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if err != nil {
log.Error(err, "Could not update firewall group")
return reconcile.Result{}, err
}
firewallGroup.Status.ResourcesManaged.UDPPortsObject.Name = firewall_group.Name
firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID = firewall_group.ID
udpports_done = true
}
} }
if len(ipv4) > 0 && !ipv4_done { if len(ipv4) > 0 && !ipv4_done {
log.Info(fmt.Sprintf("Creating %s", ipv4_name)) log.Info(fmt.Sprintf("Creating %s", ipv4_name))
@@ -245,11 +590,19 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
firewall_group.SiteID = r.UnifiClient.SiteID firewall_group.SiteID = r.UnifiClient.SiteID
firewall_group.GroupMembers = ipv4 firewall_group.GroupMembers = ipv4
firewall_group.GroupType = "address-group" firewall_group.GroupType = "address-group"
_, err := r.UnifiClient.Client.CreateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group) firewall_group_result, err := r.UnifiClient.Client.CreateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
log.Info(fmt.Sprintf("%+v", firewall_group_result))
if err != nil { if err != nil {
log.Error(err, "Could not create firewall group") log.Error(err, "Could not create firewall group")
return reconcile.Result{}, err return reconcile.Result{}, err
} else {
firewall_group = *firewall_group_result
} }
log.Info(fmt.Sprintf("ID and name: %s %s", firewall_group.ID, firewall_group.Name))
log.Info(fmt.Sprintf("%+v", firewall_group))
firewallGroup.Status.ResourcesManaged.IPV4Object.ID = firewall_group.ID
firewallGroup.Status.ResourcesManaged.IPV4Object.Name = firewall_group.Name
} }
if len(ipv6) > 0 && !ipv6_done { if len(ipv6) > 0 && !ipv6_done {
log.Info(fmt.Sprintf("Creating %s", ipv6_name)) log.Info(fmt.Sprintf("Creating %s", ipv6_name))
@@ -258,18 +611,62 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
firewall_group.SiteID = r.UnifiClient.SiteID firewall_group.SiteID = r.UnifiClient.SiteID
firewall_group.GroupMembers = ipv6 firewall_group.GroupMembers = ipv6
firewall_group.GroupType = "ipv6-address-group" firewall_group.GroupType = "ipv6-address-group"
_, err := r.UnifiClient.Client.CreateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group) firewall_group_result, err := r.UnifiClient.Client.CreateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
log.Info(fmt.Sprintf("%+v", firewall_group_result))
if err != nil { if err != nil {
log.Error(err, "Could not create firewall group") log.Error(err, "Could not create firewall group")
return reconcile.Result{}, err return reconcile.Result{}, err
} else {
firewall_group = *firewall_group_result
} }
firewallGroup.Status.ResourcesManaged.IPV6Object.ID = firewall_group.ID
firewallGroup.Status.ResourcesManaged.IPV6Object.Name = firewall_group.Name
} }
if err := r.Status().Update(ctx, &nwObj); err != nil { if len(tcpports) > 0 && !tcpports_done {
log.Info(fmt.Sprintf("Creating %s", tcpports_name))
var firewall_group goUnifi.FirewallGroup
firewall_group.Name = tcpports_name
firewall_group.SiteID = r.UnifiClient.SiteID
firewall_group.GroupMembers = tcpports
firewall_group.GroupType = "port-group"
log.Info(fmt.Sprintf("Trying to apply: %+v", firewall_group))
firewall_group_result, err := r.UnifiClient.Client.CreateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if err != nil {
log.Error(err, "Could not create firewall group")
return reconcile.Result{}, err
} else {
firewall_group = *firewall_group_result
}
firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID = firewall_group.ID
firewallGroup.Status.ResourcesManaged.TCPPortsObject.Name = firewall_group.Name
}
if len(udpports) > 0 && !udpports_done {
log.Info(fmt.Sprintf("Creating %s", udpports_name))
var firewall_group goUnifi.FirewallGroup
firewall_group.Name = udpports_name
firewall_group.SiteID = r.UnifiClient.SiteID
firewall_group.GroupMembers = udpports
firewall_group.GroupType = "port-group"
log.Info(fmt.Sprintf("Trying to apply: %+v", firewall_group))
firewall_group_result, err := r.UnifiClient.Client.CreateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
log.Info(fmt.Sprintf("%+v", firewall_group_result))
if err != nil {
log.Error(err, "Could not create firewall group")
return reconcile.Result{}, err
} else {
firewall_group = *firewall_group_result
}
firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID = firewall_group.ID
firewallGroup.Status.ResourcesManaged.UDPPortsObject.Name = firewall_group.Name
}
log.Info(fmt.Sprintf("Updating status for %s: %+v", firewallGroup.Name, firewallGroup.Status))
if err := r.Status().Update(ctx, &firewallGroup); err != nil {
log.Error(err, "unable to update FirewallGroup status") log.Error(err, "unable to update FirewallGroup status")
return reconcile.Result{}, err return reconcile.Result{}, err
} }
log.Info("Successfully updated FirewallGroup status with collected IP addresses") log.Info("Successfully updated FirewallGroup status with collected IP addresses and ports")
return reconcile.Result{}, nil return reconcile.Result{}, nil
} }
@@ -289,15 +686,15 @@ func (r *FirewallGroupReconciler) mapServiceToFirewallGroups(ctx context.Context
return nil return nil
} }
for _, fwg := range allFirewallGroups.Items { for _, firewallGroup := range allFirewallGroups.Items {
if fwg.Spec.MatchServicesInAllNamespaces || fwg.Namespace == service.Namespace { if firewallGroup.Spec.MatchServicesInAllNamespaces || firewallGroup.Namespace == service.Namespace {
annotationKey := "unifi.engen.priv.no/firewall-group" annotationKey := "unifi.engen.priv.no/firewall-group"
annotationVal := fwg.Name annotationVal := firewallGroup.Name
if val, ok := service.Annotations[annotationKey]; ok && (annotationVal == "" || val == annotationVal) { if val, ok := service.Annotations[annotationKey]; ok && (annotationVal == "" || val == annotationVal) {
requests = append(requests, ctrl.Request{ requests = append(requests, ctrl.Request{
NamespacedName: types.NamespacedName{ NamespacedName: types.NamespacedName{
Name: fwg.Name, Name: firewallGroup.Name,
Namespace: fwg.Namespace, Namespace: firewallGroup.Namespace,
}, },
}) })
} }

View File

@@ -0,0 +1,885 @@
/*
Copyright 2025 Vegard Engen.
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 controller
import (
"context"
"fmt"
// "strings"
"encoding/json"
"strings"
"time"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"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/log"
goUnifi "github.com/vegardengen/go-unifi/unifi"
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
"github.com/vegardengen/unifi-network-operator/internal/config"
"github.com/vegardengen/unifi-network-operator/internal/unifi"
)
// FirewallPolicyReconciler reconciles a FirewallPolicy object
type FirewallPolicyReconciler struct {
client.Client
Scheme *runtime.Scheme
UnifiClient *unifi.UnifiClient
ConfigLoader *config.ConfigLoaderType
}
const firewallPolicyFinalizer = "finalizer.unifi.engen.priv.no/firewallpolicy"
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallpolicies,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallpolicies/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallpolicies/finalizers,verbs=update
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=list;get;watch
// +kubebuilder:rbac:groups="",resources=services,verbs=list;get;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.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the FirewallPolicy 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.20.2/pkg/reconcile
func fillDefaultPolicy() goUnifi.FirewallPolicy {
var firewallPolicy goUnifi.FirewallPolicy
firewallPolicy.Action = "ALLOW"
firewallPolicy.CreateAllowRespond = true
firewallPolicy.ConnectionStateType = "ALL"
firewallPolicy.ConnectionStates = []string{}
firewallPolicy.Destination = goUnifi.FirewallDestination{
MatchOppositePorts: false,
MatchingTarget: "IP",
MatchingTargetType: "OBJECT",
}
firewallPolicy.Enabled = true
firewallPolicy.ICMPTypename = "ANY"
firewallPolicy.ICMPV6Typename = "ANY"
firewallPolicy.MatchIPSec = false
firewallPolicy.MatchOppositeProtocol = false
firewallPolicy.Predefined = false
firewallPolicy.Schedule = goUnifi.FirewallSchedule{
Mode: "ALWAYS",
RepeatOnDays: []string{},
TimeAllDay: false,
}
firewallPolicy.Source = goUnifi.FirewallSource{
MatchMac: false,
MatchOppositePorts: false,
MatchOppositeNetworks: false,
}
return firewallPolicy
}
func (r *FirewallPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
cfg, err := r.ConfigLoader.GetConfig(ctx, "unifi-operator-config")
if err != nil {
return ctrl.Result{}, err
}
defaultNs := cfg.Data["defaultNamespace"]
kubernetesZone := cfg.Data["kubernetesUnifiZone"]
var kubernetesZoneID string
log.Info(defaultNs)
log.Info(kubernetesZone)
var firewallPolicy unifiv1beta1.FirewallPolicy
if err := r.Get(ctx, req.NamespacedName, &firewallPolicy); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
log.Info(firewallPolicy.Spec.Name)
if firewallPolicy.DeletionTimestamp != nil {
if controllerutil.ContainsFinalizer(&firewallPolicy, firewallPolicyFinalizer) {
err := r.UnifiClient.Reauthenticate()
if err != nil {
return ctrl.Result{}, err
}
log.Info("Running finalizer logic for FirewallPolicy", "name", firewallPolicy.Name)
if len(firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies) > 0 {
for i, UnifiFirewallPolicy := range firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies {
log.Info(fmt.Sprintf("From: %s to: %s TcpIpv4: %s UdpIpv4: %s TcpIpv6: %s UdpIpv6: %s", UnifiFirewallPolicy.From, UnifiFirewallPolicy.To, UnifiFirewallPolicy.TcpIpv4ID, UnifiFirewallPolicy.UdpIpv4ID, UnifiFirewallPolicy.TcpIpv6ID, UnifiFirewallPolicy.UdpIpv6ID))
if len(UnifiFirewallPolicy.TcpIpv4ID) > 0 {
err := r.UnifiClient.Client.DeleteFirewallPolicy(context.Background(), r.UnifiClient.SiteID, UnifiFirewallPolicy.TcpIpv4ID)
if err != nil && !strings.Contains(err.Error(), "not found") {
} else {
firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies[i].TcpIpv4ID = ""
if err := r.Status().Update(ctx, &firewallPolicy); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
if len(UnifiFirewallPolicy.UdpIpv4ID) > 0 {
err := r.UnifiClient.Client.DeleteFirewallPolicy(context.Background(), r.UnifiClient.SiteID, UnifiFirewallPolicy.UdpIpv4ID)
if err != nil && !strings.Contains(err.Error(), "not found") {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
} else {
firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies[i].UdpIpv4ID = ""
if err := r.Status().Update(ctx, &firewallPolicy); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
if len(UnifiFirewallPolicy.TcpIpv6ID) > 0 {
err := r.UnifiClient.Client.DeleteFirewallPolicy(context.Background(), r.UnifiClient.SiteID, UnifiFirewallPolicy.TcpIpv6ID)
if err != nil && !strings.Contains(err.Error(), "not found") {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
} else {
firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies[i].TcpIpv6ID = ""
if err := r.Status().Update(ctx, &firewallPolicy); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
if len(UnifiFirewallPolicy.UdpIpv6ID) > 0 {
err := r.UnifiClient.Client.DeleteFirewallPolicy(context.Background(), r.UnifiClient.SiteID, UnifiFirewallPolicy.UdpIpv6ID)
if err != nil && !strings.Contains(err.Error(), "not found") {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
} else {
firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies[i].UdpIpv6ID = ""
if err := r.Status().Update(ctx, &firewallPolicy); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
}
}
if len(firewallPolicy.Status.ResourcesManaged.FirewallGroups) > 0 {
for i, firewallGroup := range firewallPolicy.Status.ResourcesManaged.FirewallGroups {
var firewallGroupCRD unifiv1beta1.FirewallGroup
if firewallGroup.Name != "" {
if err := r.Get(ctx, types.NamespacedName{Name: firewallGroup.Name, Namespace: firewallGroup.Namespace}, &firewallGroupCRD); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
if err := r.Delete(ctx, &firewallGroupCRD); err != nil {
log.Error(err, "Could not delete firewall group")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
firewallPolicy.Status.ResourcesManaged.FirewallGroups[i].Name = ""
firewallPolicy.Status.ResourcesManaged.FirewallGroups[i].Namespace = ""
if err := r.Status().Update(ctx, &firewallPolicy); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
}
controllerutil.RemoveFinalizer(&firewallPolicy, firewallPolicyFinalizer)
if err := r.Update(ctx, &firewallPolicy); err != nil {
return ctrl.Result{}, err
}
log.Info("Successfully finalized FirewallGroup")
}
return ctrl.Result{}, nil
}
if !controllerutil.ContainsFinalizer(&firewallPolicy, firewallPolicyFinalizer) {
controllerutil.AddFinalizer(&firewallPolicy, firewallPolicyFinalizer)
if err := r.Update(ctx, &firewallPolicy); err != nil {
return ctrl.Result{}, err
}
}
// Create an index of already managed firewall policy entries, with source and destination as the key and placement in status field array as value. If no managed firewall policy
// entries, this is a new policy and we will create an empty index and set empty lists in the status field of the firewallPolicy resource.
firewallpolicyindex := make(map[string]int)
nextFirewallPolicyIndex := 0
if firewallPolicy.Status.ResourcesManaged == nil {
firewallGroupsManaged := []unifiv1beta1.FirewallGroupEntry{}
unifiFirewallPolicies := []unifiv1beta1.UnifiFirewallPolicyEntry{}
firewallPolicy.Status.ResourcesManaged = &unifiv1beta1.FirewallPolicyResourcesManaged{
UnifiFirewallPolicies: unifiFirewallPolicies,
FirewallGroups: firewallGroupsManaged,
}
} else {
for index, firewallPolicyEntry := range firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies {
firewallpolicyindex[firewallPolicyEntry.From+"/"+firewallPolicyEntry.To] = index
nextFirewallPolicyIndex = nextFirewallPolicyIndex + 1
}
}
err = r.UnifiClient.Reauthenticate()
if err != nil {
return ctrl.Result{}, err
}
var zoneCRDs unifiv1beta1.FirewallZoneList
var networkCRDs unifiv1beta1.NetworkconfigurationList
err = r.List(ctx, &zoneCRDs)
if err != nil {
log.Error(err, "Could not list firewall zones")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
// Create an index of zones, with namespace/name as key and placement in zoneCRDs as value. This enables getting the zone properties from specified namespace/name in
// the policy entries. If Namespace is not specified, default will be taken from the configmap.
zoneCRDNames := make(map[string]int)
for i, zoneCRD := range zoneCRDs.Items {
namespace := defaultNs
if len(zoneCRD.Namespace) > 0 {
namespace = zoneCRD.Namespace
}
if kubernetesZone == zoneCRD.Name {
kubernetesZoneID = zoneCRD.Spec.ID
log.Info(fmt.Sprintf("Zone for kubernetes resources: %s with ID %s", kubernetesZone, kubernetesZoneID))
}
zoneCRDNames[namespace+"/"+zoneCRD.Name] = i
}
err = r.List(ctx, &networkCRDs)
if err != nil {
log.Error(err, "Could not list networks")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
// Create an index of networks, with namespace/name as key and placement in networkCRDs as value. This enables getting the network properties from specified namespace/name in
// the policy entries.
networkCRDNames := make(map[string]int)
for i, networkCRD := range networkCRDs.Items {
namespace := defaultNs
if len(networkCRD.Namespace) > 0 {
namespace = networkCRD.Namespace
}
networkCRDNames[namespace+"/"+networkCRD.Name] = i
}
destination_services := make(map[string]struct{})
destination_groups := make(map[string]struct{})
// Run through the list of specified firewall groups destinations and service desitnations and create an index with namespace/name as key.
// This will be used when running through all firewall groups and servics known, to see if a rule should be added.
for _, dest_group := range firewallPolicy.Spec.Destination.FirewallGroups {
namespace := defaultNs
if len(dest_group.Namespace) > 0 {
namespace = dest_group.Namespace
}
destination_groups[namespace+"/"+dest_group.Name] = struct{}{}
}
for _, dest_service := range firewallPolicy.Spec.Destination.Services {
namespace := defaultNs
if len(dest_service.Namespace) > 0 {
namespace = dest_service.Namespace
}
destination_services[namespace+"/"+dest_service.Name] = struct{}{}
}
var firewallGroupCRDs unifiv1beta1.FirewallGroupList
var myFirewallGroups []unifiv1beta1.FirewallGroup
if err = r.List(ctx, &firewallGroupCRDs); err != nil {
log.Error(err, "Failed to list firewall groups")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
// Run through all firewall groups. Add them to the myFirewallGroups list if they either have an annotations or is specified in the resource.
for _, firewallGroup := range firewallGroupCRDs.Items {
if val, found := firewallGroup.Annotations["unifi.engen.priv.no/firewall-policy"]; found && ((strings.Contains(val, "/") && val == firewallPolicy.Namespace+"/"+firewallPolicy.Name) || (val == firewallPolicy.Name && firewallPolicy.Namespace == defaultNs)) {
myFirewallGroups = append(myFirewallGroups, firewallGroup)
} else if _, found := destination_groups[firewallGroup.Namespace+"/"+firewallGroup.Name]; found {
myFirewallGroups = append(myFirewallGroups, firewallGroup)
}
}
// Create an index with namespace/name as value,
myFirewallGroupNames := make(map[string]struct{})
for _, firewallGroup := range myFirewallGroups {
myFirewallGroupNames[firewallGroup.Namespace+"/"+firewallGroup.Name] = struct{}{}
}
var serviceCRDs corev1.ServiceList
var myServices []corev1.Service
if err = r.List(ctx, &serviceCRDs); err != nil {
log.Error(err, "Failed to list services")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
// Run through all services. Check if they are part of the manually specified services or have an annotation, and add it to the myServices list if found
for _, service := range serviceCRDs.Items {
skipService := false
if val, found := service.Annotations["unifi.engen.priv.no/firewall-group"]; found {
if _, found := myFirewallGroupNames[val]; found {
skipService = true
}
}
if val, found := service.Annotations["unifi.engen.priv.no/firewall-policy"]; found && ((strings.Contains(val, "/") && val == firewallPolicy.Namespace+"/"+firewallPolicy.Name) || (val == firewallPolicy.Name && firewallPolicy.Namespace == defaultNs)) && !skipService {
myServices = append(myServices, service)
} else if _, found := destination_services[service.Namespace+"/"+service.Name]; found && !skipService {
myServices = append(myServices, service)
}
}
// Run through all services we should manage. Create a firewallgroup object for it, if it's not already created.
// Add it to the list of managed unifiresources if it's created. Make sure to not add it twice.
for _, service := range myServices {
log.Info(fmt.Sprintf("Should handle service %s", service.Name))
var firewallGroupCRD unifiv1beta1.FirewallGroup
// Check if firewallgroup already exists. Add it to myFirewallGroups if it exists, create it if not.
if err := r.Get(ctx, types.NamespacedName{
Name: toKubeName("k8s-auto" + "_" + service.Namespace + "/" + service.Name),
Namespace: firewallPolicy.Namespace,
}, &firewallGroupCRD); err == nil {
myFirewallGroups = append(myFirewallGroups, firewallGroupCRD)
} else {
log.Info("Going to create firewall group")
var manualServices []unifiv1beta1.ServiceEntry
manualServices = append(manualServices, unifiv1beta1.ServiceEntry{
Name: service.Name,
Namespace: service.Namespace,
})
createdFirewallGroupCRD := &unifiv1beta1.FirewallGroup{
ObjectMeta: ctrl.ObjectMeta{
Name: toKubeName("k8s-auto" + "_" + service.Namespace + "/" + service.Name),
Namespace: firewallPolicy.Namespace,
},
Spec: unifiv1beta1.FirewallGroupSpec{
Name: "auto-" + service.Namespace + "/" + service.Name,
AutoCreatedFrom: unifiv1beta1.FirewallPolicyEntry{
Name: firewallPolicy.Name,
Namespace: firewallPolicy.Namespace,
},
ManualServices: manualServices,
MatchServicesInAllNamespaces: true,
},
}
if err := r.Create(ctx, createdFirewallGroupCRD); err != nil {
log.Error(err, fmt.Sprintf("Failed to create %s", createdFirewallGroupCRD.Name))
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
} else {
// Give it time to be fully created. It doesn't need to be handled and expanded at this point, but it should be before using it later.
time.Sleep(10 * time.Second)
_ = r.Get(ctx, types.NamespacedName{Name: createdFirewallGroupCRD.Name, Namespace: createdFirewallGroupCRD.Namespace}, &firewallGroupCRD)
}
log.Info(fmt.Sprintf("Adding %+v", firewallGroupCRD))
myFirewallGroups = append(myFirewallGroups, firewallGroupCRD)
// Run through list of already managed Unifi firewallgroups to check if it's already on the list, to avoid having it in list twice.
found := false
for _, managedFirewallGroup := range firewallPolicy.Status.ResourcesManaged.FirewallGroups {
if managedFirewallGroup.Name == firewallGroupCRD.Name && managedFirewallGroup.Namespace == firewallGroupCRD.Namespace {
found = true
}
}
// Add it to resource status field.
if !found {
firewallPolicy.Status.ResourcesManaged.FirewallGroups = append(firewallPolicy.Status.ResourcesManaged.FirewallGroups, unifiv1beta1.FirewallGroupEntry{Name: firewallGroupCRD.Name, Namespace: firewallGroupCRD.Namespace})
if err := r.Status().Update(ctx, &firewallPolicy); err != nil {
log.Error(err, "Failed to update status with added firewallgroup")
}
}
}
}
// Finished listing destinations. Starting to handle source specifications.
// Source can be either zones or networks managed by/known to the operator. Specified by namespace and name. Use default from configmap if namespace is not specified.
unifi_firewall_policies, err := r.UnifiClient.Client.ListFirewallPolicy(context.Background(), r.UnifiClient.SiteID)
if err != nil {
log.Error(err, "Could not list firewall policies")
return ctrl.Result{}, err
}
// Create an index of Unifi firewall policy names.
unifiFirewallpolicyNames := make(map[string]struct{})
for _, unifi_firewall_policy := range unifi_firewall_policies {
unifiFirewallpolicyNames[unifi_firewall_policy.Name] = struct{}{}
}
log.Info(fmt.Sprintf("Number of firewall policies: %d", len(unifi_firewall_policies)))
// Run through specified source zones and check if we should handle them.
for _, zoneEntry := range firewallPolicy.Spec.Source.FirewallZones {
namespace := defaultNs
if len(zoneEntry.Namespace) > 0 {
namespace = zoneEntry.Namespace
}
if zoneIndex, found := zoneCRDNames[namespace+"/"+zoneEntry.Name]; found {
// Should handle, so we create firewall policies.
log.Info(fmt.Sprintf("Creating firewallpolicies for %s", zoneCRDs.Items[zoneIndex].Name))
// Run through destination firewall groups and enumerate and create polices if they don't already exist.
for _, firewallGroup := range myFirewallGroups {
found := false
index, found := firewallpolicyindex["zone:"+zoneCRDs.Items[zoneIndex].Name+"/"+firewallGroup.Name]
// Not found? We add an empty entry in status field.
if !found {
firewallPolicyEntry := unifiv1beta1.UnifiFirewallPolicyEntry{
From: "zone:" + zoneCRDs.Items[zoneIndex].Name,
To: firewallGroup.Name,
TcpIpv4ID: "",
UdpIpv4ID: "",
TcpIpv6ID: "",
UdpIpv6ID: "",
}
firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies = append(firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies, firewallPolicyEntry)
index = nextFirewallPolicyIndex
nextFirewallPolicyIndex = nextFirewallPolicyIndex + 1
}
// Create policies for all permutations of Ipversion and protocol.
if len(firewallGroup.Status.ResolvedIPV4Addresses) > 0 {
if len(firewallGroup.Status.ResolvedTCPPorts) > 0 {
policyname := "k8s-fw-" + firewallPolicy.Name + "-" + "zone:" + zoneCRDs.Items[zoneIndex].Name + "-" + firewallGroup.Name + "-ipv4-tcp"
if _, found := unifiFirewallpolicyNames[policyname]; !found {
log.Info(fmt.Sprintf("Creating ipv4 tcp firewallpolicy for %s to %s: %s", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name, policyname))
unifiFirewallPolicy := fillDefaultPolicy()
unifiFirewallPolicy.Name = policyname
unifiFirewallPolicy.Source.PortMatchingType = "ANY"
unifiFirewallPolicy.Source.ZoneID = zoneCRDs.Items[zoneIndex].Spec.ID
unifiFirewallPolicy.Source.MatchingTarget = "ANY"
unifiFirewallPolicy.Protocol = "tcp"
unifiFirewallPolicy.IPVersion = "IPV4"
unifiFirewallPolicy.Description = fmt.Sprintf("Allow tcp IPV4 from %s to %s", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name)
unifiFirewallPolicy.Destination.MatchingTargetType = "OBJECT"
unifiFirewallPolicy.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV4Object.ID
unifiFirewallPolicy.Destination.MatchingTarget = "IP"
unifiFirewallPolicy.Destination.PortMatchingType = "OBJECT"
unifiFirewallPolicy.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID
unifiFirewallPolicy.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall policy from zone %s to %s: %+v", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name, unifiFirewallPolicy))
pretty, _ := json.MarshalIndent(unifiFirewallPolicy, "", " ")
log.Info(string(pretty))
updatedPolicy, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallPolicy)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies[index].TcpIpv4ID = updatedPolicy.ID
if err = r.Status().Update(ctx, &firewallPolicy); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall policy for ipv4 tcp %s to %s already exists", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name))
}
}
if len(firewallGroup.Status.ResolvedUDPPorts) > 0 {
policyname := "k8s-fw-" + firewallPolicy.Name + "-" + "zone:" + zoneCRDs.Items[zoneIndex].Name + "-" + firewallGroup.Name + "-ipv4-udp"
if _, found := unifiFirewallpolicyNames[policyname]; !found {
log.Info(fmt.Sprintf("Creating ipv4 udp firewallpolicy for %s to %s: %s", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name, policyname))
unifiFirewallPolicy := fillDefaultPolicy()
unifiFirewallPolicy.Name = policyname
unifiFirewallPolicy.Source.PortMatchingType = "ANY"
unifiFirewallPolicy.Source.ZoneID = zoneCRDs.Items[zoneIndex].Spec.ID
unifiFirewallPolicy.Source.MatchingTarget = "ANY"
unifiFirewallPolicy.Protocol = "udp"
unifiFirewallPolicy.IPVersion = "IPV4"
unifiFirewallPolicy.Description = fmt.Sprintf("Allow udp IPV4 from %s to %s", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name)
unifiFirewallPolicy.Destination.MatchingTargetType = "OBJECT"
unifiFirewallPolicy.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV4Object.ID
unifiFirewallPolicy.Destination.MatchingTarget = "IP"
unifiFirewallPolicy.Destination.PortMatchingType = "OBJECT"
unifiFirewallPolicy.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID
unifiFirewallPolicy.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall policy from zone %s to %s: %+v", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name, unifiFirewallPolicy))
pretty, _ := json.MarshalIndent(unifiFirewallPolicy, "", " ")
log.Info(string(pretty))
updatedPolicy, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallPolicy)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies[index].UdpIpv4ID = updatedPolicy.ID
if err := r.Status().Update(ctx, &firewallPolicy); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall policy for ipv4 udp %s to %s already exists", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name))
}
}
}
if len(firewallGroup.Status.ResolvedIPV6Addresses) > 0 {
if len(firewallGroup.Status.ResolvedTCPPorts) > 0 {
policyname := "k8s-fw-" + firewallPolicy.Name + "-" + "zone:" + zoneCRDs.Items[zoneIndex].Name + "-" + firewallGroup.Name + "-ipv6-tcp"
if _, found := unifiFirewallpolicyNames[policyname]; !found {
log.Info(fmt.Sprintf("Creating ipv6 tcp firewallpolicy for %s to %s: %s", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name, policyname))
unifiFirewallPolicy := fillDefaultPolicy()
unifiFirewallPolicy.Name = policyname
unifiFirewallPolicy.Source.PortMatchingType = "ANY"
unifiFirewallPolicy.Source.ZoneID = zoneCRDs.Items[zoneIndex].Spec.ID
unifiFirewallPolicy.Source.MatchingTarget = "ANY"
unifiFirewallPolicy.Protocol = "tcp"
unifiFirewallPolicy.IPVersion = "IPV6"
unifiFirewallPolicy.Description = fmt.Sprintf("Allow tcp IPV6 from %s to %s", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name)
unifiFirewallPolicy.Destination.MatchingTargetType = "OBJECT"
unifiFirewallPolicy.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV6Object.ID
unifiFirewallPolicy.Destination.MatchingTarget = "IP"
unifiFirewallPolicy.Destination.PortMatchingType = "OBJECT"
unifiFirewallPolicy.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID
unifiFirewallPolicy.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall policy from zone %s to %s: %+v", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name, unifiFirewallPolicy))
pretty, _ := json.MarshalIndent(unifiFirewallPolicy, "", " ")
log.Info(string(pretty))
updatedPolicy, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallPolicy)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies[index].TcpIpv6ID = updatedPolicy.ID
if err := r.Status().Update(ctx, &firewallPolicy); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall policy for ipv6 tcp %s to %s already exists", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name))
}
}
if len(firewallGroup.Status.ResolvedUDPPorts) > 0 {
policyname := "k8s-fw-" + firewallPolicy.Name + "-" + "zone:" + zoneCRDs.Items[zoneIndex].Name + "-" + firewallGroup.Name + "-ipv6-udp"
if _, found := unifiFirewallpolicyNames[policyname]; !found {
log.Info(fmt.Sprintf("Creating ipv6 udp firewallpolicy for %s to %s: %s", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name, policyname))
unifiFirewallPolicy := fillDefaultPolicy()
unifiFirewallPolicy.Name = policyname
unifiFirewallPolicy.Source.PortMatchingType = "ANY"
unifiFirewallPolicy.Source.ZoneID = zoneCRDs.Items[zoneIndex].Spec.ID
unifiFirewallPolicy.Source.MatchingTarget = "ANY"
unifiFirewallPolicy.Protocol = "udp"
unifiFirewallPolicy.IPVersion = "IPV6"
unifiFirewallPolicy.Description = fmt.Sprintf("Allow udp IPV6 from %s to %s", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name)
unifiFirewallPolicy.Destination.MatchingTargetType = "OBJECT"
unifiFirewallPolicy.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV6Object.ID
unifiFirewallPolicy.Destination.MatchingTarget = "IP"
unifiFirewallPolicy.Destination.PortMatchingType = "OBJECT"
unifiFirewallPolicy.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID
unifiFirewallPolicy.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall policy from zone %s to %s: %+v", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name, unifiFirewallPolicy))
pretty, _ := json.MarshalIndent(unifiFirewallPolicy, "", " ")
log.Info(string(pretty))
updatedPolicy, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallPolicy)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies[index].UdpIpv6ID = updatedPolicy.ID
if err := r.Status().Update(ctx, &firewallPolicy); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall policy for ipv6 udp %s to %s already exists", zoneCRDs.Items[zoneIndex].Name, firewallGroup.Name))
}
}
}
}
}
}
for _, networkEntry := range firewallPolicy.Spec.Source.Networks {
namespace := defaultNs
if len(networkEntry.Namespace) > 0 {
namespace = networkEntry.Namespace
}
if networkIndex, found := networkCRDNames[namespace+"/"+networkEntry.Name]; found {
log.Info(fmt.Sprintf("Creating firewallpolicies for %s", networkCRDs.Items[networkIndex].Name))
for _, firewallGroup := range myFirewallGroups {
index, found := firewallpolicyindex["network:"+networkCRDs.Items[networkIndex].Name+"/"+firewallGroup.Name]
if !found {
firewallPolicyEntry := unifiv1beta1.UnifiFirewallPolicyEntry{
From: "zone:" + networkCRDs.Items[networkIndex].Name,
To: firewallGroup.Name,
TcpIpv4ID: "",
UdpIpv4ID: "",
TcpIpv6ID: "",
UdpIpv6ID: "",
}
firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies = append(firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies, firewallPolicyEntry)
index = nextFirewallPolicyIndex
nextFirewallPolicyIndex = nextFirewallPolicyIndex + 1
}
if len(firewallGroup.Status.ResolvedIPV4Addresses) > 0 {
if len(firewallGroup.Status.ResolvedTCPPorts) > 0 {
policyname := "k8s-fw-" + firewallPolicy.Name + "-" + "network:" + networkCRDs.Items[networkIndex].Name + "-" + firewallGroup.Name + "-ipv4-tcp"
if _, found := unifiFirewallpolicyNames[policyname]; !found {
log.Info(fmt.Sprintf("Creating ipv4 tcp firewallpolicy for %s to %s: %s", networkCRDs.Items[networkIndex].Name, firewallGroup.Name, policyname))
unifiFirewallPolicy := fillDefaultPolicy()
unifiFirewallPolicy.Name = policyname
unifiFirewallPolicy.Source.NetworkIDs = []string{networkCRDs.Items[networkIndex].Spec.ID}
unifiFirewallPolicy.Source.PortMatchingType = "ANY"
unifiFirewallPolicy.Source.ZoneID = networkCRDs.Items[networkIndex].Status.FirewallZoneID
unifiFirewallPolicy.Source.MatchingTarget = "NETWORK"
unifiFirewallPolicy.Protocol = "tcp"
unifiFirewallPolicy.IPVersion = "IPV4"
unifiFirewallPolicy.Description = fmt.Sprintf("Allow tcp IPV4 from %s to %s", networkCRDs.Items[networkIndex].Name, firewallGroup.Name)
unifiFirewallPolicy.Destination.MatchingTargetType = "OBJECT"
unifiFirewallPolicy.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV4Object.ID
unifiFirewallPolicy.Destination.MatchingTarget = "IP"
unifiFirewallPolicy.Destination.PortMatchingType = "OBJECT"
unifiFirewallPolicy.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID
unifiFirewallPolicy.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall policy from network %s to %s: %+v", networkCRDs.Items[networkIndex].Name, firewallGroup.Name, unifiFirewallPolicy))
pretty, _ := json.MarshalIndent(unifiFirewallPolicy, "", " ")
log.Info(string(pretty))
updatedPolicy, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallPolicy)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies[index].TcpIpv4ID = updatedPolicy.ID
if err := r.Status().Update(ctx, &firewallPolicy); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall policy for ipv4 tcp %s to %s already exists", networkCRDs.Items[networkIndex].Name, firewallGroup.Name))
}
}
if len(firewallGroup.Status.ResolvedUDPPorts) > 0 {
policyname := "k8s-fw-" + firewallPolicy.Name + "-" + "network:" + networkCRDs.Items[networkIndex].Name + "-" + firewallGroup.Name + "-ipv4-udp"
if _, found := unifiFirewallpolicyNames[policyname]; !found {
log.Info(fmt.Sprintf("Creating ipv4 udp firewallpolicy for %s to %s: %s", networkCRDs.Items[networkIndex].Name, firewallGroup.Name, policyname))
unifiFirewallPolicy := fillDefaultPolicy()
unifiFirewallPolicy.Name = policyname
unifiFirewallPolicy.Source.NetworkIDs = []string{networkCRDs.Items[networkIndex].Spec.ID}
unifiFirewallPolicy.Source.PortMatchingType = "ANY"
unifiFirewallPolicy.Source.ZoneID = networkCRDs.Items[networkIndex].Status.FirewallZoneID
unifiFirewallPolicy.Source.MatchingTarget = "NETWORK"
unifiFirewallPolicy.Protocol = "udp"
unifiFirewallPolicy.IPVersion = "IPV4"
unifiFirewallPolicy.Description = fmt.Sprintf("Allow udp IPV4 from %s to %s", networkCRDs.Items[networkIndex].Name, firewallGroup.Name)
unifiFirewallPolicy.Destination.MatchingTargetType = "OBJECT"
unifiFirewallPolicy.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV4Object.ID
unifiFirewallPolicy.Destination.MatchingTarget = "IP"
unifiFirewallPolicy.Destination.PortMatchingType = "OBJECT"
unifiFirewallPolicy.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID
unifiFirewallPolicy.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall policy from network %s to %s: %+v", networkCRDs.Items[networkIndex].Name, firewallGroup.Name, unifiFirewallPolicy))
pretty, _ := json.MarshalIndent(unifiFirewallPolicy, "", " ")
log.Info(string(pretty))
updatedPolicy, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallPolicy)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies[index].UdpIpv4ID = updatedPolicy.ID
if err := r.Status().Update(ctx, &firewallPolicy); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall policy for ipv4 udp %s to %s already exists", networkCRDs.Items[networkIndex].Name, firewallGroup.Name))
}
}
}
if len(firewallGroup.Status.ResolvedIPV6Addresses) > 0 {
if len(firewallGroup.Status.ResolvedTCPPorts) > 0 {
policyname := "k8s-fw-" + firewallPolicy.Name + "-" + "network:" + networkCRDs.Items[networkIndex].Name + "-" + firewallGroup.Name + "-ipv6-tcp"
if _, found := unifiFirewallpolicyNames[policyname]; !found {
log.Info(fmt.Sprintf("Creating ipv6 tcp firewallpolicy for %s to %s: %s", networkCRDs.Items[networkIndex].Name, firewallGroup.Name, policyname))
unifiFirewallPolicy := fillDefaultPolicy()
unifiFirewallPolicy.Name = policyname
unifiFirewallPolicy.Source.NetworkIDs = []string{networkCRDs.Items[networkIndex].Spec.ID}
unifiFirewallPolicy.Source.PortMatchingType = "ANY"
unifiFirewallPolicy.Source.ZoneID = networkCRDs.Items[networkIndex].Status.FirewallZoneID
unifiFirewallPolicy.Source.MatchingTarget = "NETWORK"
unifiFirewallPolicy.Protocol = "tcp"
unifiFirewallPolicy.IPVersion = "IPV6"
unifiFirewallPolicy.Description = fmt.Sprintf("Allow tcp IPV6 from %s to %s", networkCRDs.Items[networkIndex].Name, firewallGroup.Name)
unifiFirewallPolicy.Destination.MatchingTargetType = "OBJECT"
unifiFirewallPolicy.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV6Object.ID
unifiFirewallPolicy.Destination.MatchingTarget = "IP"
unifiFirewallPolicy.Destination.PortMatchingType = "OBJECT"
unifiFirewallPolicy.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID
unifiFirewallPolicy.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall policy from network %s to %s: %+v", networkCRDs.Items[networkIndex].Name, firewallGroup.Name, unifiFirewallPolicy))
pretty, _ := json.MarshalIndent(unifiFirewallPolicy, "", " ")
log.Info(string(pretty))
updatedPolicy, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallPolicy)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies[index].TcpIpv6ID = updatedPolicy.ID
if err := r.Status().Update(ctx, &firewallPolicy); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall policy for ipv6 tcp %s to %s already exists", networkCRDs.Items[networkIndex].Name, firewallGroup.Name))
}
}
if len(firewallGroup.Status.ResolvedUDPPorts) > 0 {
policyname := "k8s-fw-" + firewallPolicy.Name + "-" + "network:" + networkCRDs.Items[networkIndex].Name + "-" + firewallGroup.Name + "-ipv6-udp"
if _, found := unifiFirewallpolicyNames[policyname]; !found {
log.Info(fmt.Sprintf("Creating ipv6 udp firewallpolicy for %s to %s: %s", networkCRDs.Items[networkIndex].Name, firewallGroup.Name, policyname))
unifiFirewallPolicy := fillDefaultPolicy()
unifiFirewallPolicy.Name = policyname
unifiFirewallPolicy.Source.NetworkIDs = []string{networkCRDs.Items[networkIndex].Spec.ID}
unifiFirewallPolicy.Source.PortMatchingType = "ANY"
unifiFirewallPolicy.Source.ZoneID = networkCRDs.Items[networkIndex].Status.FirewallZoneID
unifiFirewallPolicy.Source.MatchingTarget = "NETWORK"
unifiFirewallPolicy.Protocol = "udp"
unifiFirewallPolicy.IPVersion = "IPV6"
unifiFirewallPolicy.Description = fmt.Sprintf("Allow udp IPV6 from %s to %s", networkCRDs.Items[networkIndex].Name, firewallGroup.Name)
unifiFirewallPolicy.Destination.MatchingTargetType = "OBJECT"
unifiFirewallPolicy.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV6Object.ID
unifiFirewallPolicy.Destination.MatchingTarget = "IP"
unifiFirewallPolicy.Destination.PortMatchingType = "OBJECT"
unifiFirewallPolicy.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID
unifiFirewallPolicy.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall policy from network %s to %s: %+v", networkCRDs.Items[networkIndex].Name, firewallGroup.Name, unifiFirewallPolicy))
pretty, _ := json.MarshalIndent(unifiFirewallPolicy, "", " ")
log.Info(string(pretty))
updatedPolicy, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallPolicy)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallPolicy.Status.ResourcesManaged.UnifiFirewallPolicies[index].UdpIpv6ID = updatedPolicy.ID
if err := r.Status().Update(ctx, &firewallPolicy); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall policy for ipv6 udp %s to %s already exists", networkCRDs.Items[networkIndex].Name, firewallGroup.Name))
}
}
}
}
}
}
return ctrl.Result{}, nil
}
func (r *FirewallPolicyReconciler) mapFirewallGroupToFirewallPolicies(ctx context.Context, obj client.Object) []ctrl.Request {
var requests []ctrl.Request
firewallGroup, ok := obj.(*unifiv1beta1.FirewallGroup)
if !ok {
return requests
}
cfg, err := r.ConfigLoader.GetConfig(ctx, "unifi-operator-config")
if err != nil {
return requests
}
defaultNs := cfg.Data["defaultNamespace"]
var allFirewallPolicies unifiv1beta1.FirewallPolicyList
if err := r.List(ctx, &allFirewallPolicies); err != nil {
return nil
}
for _, policy := range allFirewallPolicies.Items {
if policy.Spec.MatchFirewallGroupsInAllNamespaces || policy.Namespace == firewallGroup.Namespace {
if val, found := firewallGroup.Annotations["unifi.engen.priv.no/firewall-policy"]; found && ((strings.Contains(val, "/") && val == policy.Namespace+"/"+policy.Name) || (val == policy.Name && policy.Namespace == defaultNs)) {
requests = append(requests, ctrl.Request{
NamespacedName: types.NamespacedName{
Name: policy.Name,
Namespace: policy.Namespace,
},
})
}
}
}
return requests
}
func (r *FirewallPolicyReconciler) mapServiceToFirewallPolicies(ctx context.Context, obj client.Object) []ctrl.Request {
var requests []ctrl.Request
service, ok := obj.(*corev1.Service)
if !ok {
return requests
}
cfg, err := r.ConfigLoader.GetConfig(ctx, "unifi-operator-config")
if err != nil {
return requests
}
defaultNs := cfg.Data["defaultNamespace"]
var allFirewallPolicies unifiv1beta1.FirewallPolicyList
if err := r.List(ctx, &allFirewallPolicies); err != nil {
return nil
}
for _, policy := range allFirewallPolicies.Items {
if policy.Spec.MatchServicesInAllNamespaces || policy.Namespace == service.Namespace {
if val, found := service.Annotations["unifi.engen.priv.no/firewall-policy"]; found && ((strings.Contains(val, "/") && val == policy.Namespace+"/"+policy.Name) || (val == policy.Name && policy.Namespace == defaultNs)) {
requests = append(requests, ctrl.Request{
NamespacedName: types.NamespacedName{
Name: policy.Name,
Namespace: policy.Namespace,
},
})
}
}
}
return requests
}
// SetupWithManager sets up the controller with the Manager.
func (r *FirewallPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&unifiv1beta1.FirewallPolicy{}).
Named("firewallpolicy").
Watches(
&corev1.Service{},
handler.EnqueueRequestsFromMapFunc(r.mapServiceToFirewallPolicies),
).
Watches(
&unifiv1beta1.FirewallGroup{},
handler.EnqueueRequestsFromMapFunc(r.mapFirewallGroupToFirewallPolicies),
).
Complete(r)
}

View File

@@ -30,7 +30,7 @@ import (
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1" unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
) )
var _ = Describe("FirewallRule Controller", func() { var _ = Describe("FirewallPolicy Controller", func() {
Context("When reconciling a resource", func() { Context("When reconciling a resource", func() {
const resourceName = "test-resource" const resourceName = "test-resource"
@@ -40,13 +40,13 @@ var _ = Describe("FirewallRule Controller", func() {
Name: resourceName, Name: resourceName,
Namespace: "default", // TODO(user):Modify as needed Namespace: "default", // TODO(user):Modify as needed
} }
firewallrule := &unifiv1beta1.FirewallRule{} firewallpolicy := &unifiv1beta1.FirewallPolicy{}
BeforeEach(func() { BeforeEach(func() {
By("creating the custom resource for the Kind FirewallRule") By("creating the custom resource for the Kind FirewallPolicy")
err := k8sClient.Get(ctx, typeNamespacedName, firewallrule) err := k8sClient.Get(ctx, typeNamespacedName, firewallpolicy)
if err != nil && errors.IsNotFound(err) { if err != nil && errors.IsNotFound(err) {
resource := &unifiv1beta1.FirewallRule{ resource := &unifiv1beta1.FirewallPolicy{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: resourceName, Name: resourceName,
Namespace: "default", Namespace: "default",
@@ -59,16 +59,16 @@ var _ = Describe("FirewallRule Controller", func() {
AfterEach(func() { AfterEach(func() {
// TODO(user): Cleanup logic after each test, like removing the resource instance. // TODO(user): Cleanup logic after each test, like removing the resource instance.
resource := &unifiv1beta1.FirewallRule{} resource := &unifiv1beta1.FirewallPolicy{}
err := k8sClient.Get(ctx, typeNamespacedName, resource) err := k8sClient.Get(ctx, typeNamespacedName, resource)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
By("Cleanup the specific resource instance FirewallRule") By("Cleanup the specific resource instance FirewallPolicy")
Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
}) })
It("should successfully reconcile the resource", func() { It("should successfully reconcile the resource", func() {
By("Reconciling the created resource") By("Reconciling the created resource")
controllerReconciler := &FirewallRuleReconciler{ controllerReconciler := &FirewallPolicyReconciler{
Client: k8sClient, Client: k8sClient,
Scheme: k8sClient.Scheme(), Scheme: k8sClient.Scheme(),
} }

View File

@@ -1,63 +0,0 @@
/*
Copyright 2025 Vegard Engen.
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 controller
import (
"context"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
)
// FirewallRuleReconciler reconciles a FirewallRule object
type FirewallRuleReconciler struct {
client.Client
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallrules,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallrules/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallrules/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 FirewallRule 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.20.2/pkg/reconcile
func (r *FirewallRuleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = log.FromContext(ctx)
// TODO(user): your logic here
return ctrl.Result{}, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *FirewallRuleReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&unifiv1beta1.FirewallRule{}).
Named("firewallrule").
Complete(r)
}

View File

@@ -19,8 +19,8 @@ package controller
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"regexp" "regexp"
"strings"
"time" "time"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@@ -29,41 +29,43 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log"
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1" unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
"github.com/vegardengen/unifi-network-operator/internal/config"
"github.com/vegardengen/unifi-network-operator/internal/unifi" "github.com/vegardengen/unifi-network-operator/internal/unifi"
) )
// FirewallZoneReconciler reconciles a FirewallZone object // FirewallZoneReconciler reconciles a FirewallZone object
type FirewallZoneReconciler struct { type FirewallZoneReconciler struct {
client.Client client.Client
Scheme *runtime.Scheme Scheme *runtime.Scheme
UnifiClient *unifi.UnifiClient UnifiClient *unifi.UnifiClient
ConfigLoader *config.ConfigLoaderType
} }
func toKubeName(input string) string { func toKubeName(input string) string {
// Lowercase the input // Lowercase the input
name := strings.ToLower(input) name := strings.ToLower(input)
// Replace any non-alphanumeric characters with dashes // Replace any non-alphanumeric characters with dashes
re := regexp.MustCompile(`[^a-z0-9\-\.]+`) re := regexp.MustCompile(`[^a-z0-9\-\.]+`)
name = re.ReplaceAllString(name, "-") name = re.ReplaceAllString(name, "-")
// Trim leading and trailing non-alphanumerics // Trim leading and trailing non-alphanumerics
name = strings.Trim(name, "-.") name = strings.Trim(name, "-.")
// Ensure it's not empty and doesn't exceed 253 characters // Ensure it's not empty and doesn't exceed 253 characters
if len(name) == 0 { if len(name) == 0 {
name = "default" name = "default"
} else if len(name) > 253 { } else if len(name) > 253 {
name = name[:253] name = name[:253]
} }
return name return name
} }
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallzones,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallzones,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallzones/status,verbs=get;update;patch // +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallzones/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallzones/finalizers,verbs=update // +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallzones/finalizers,verbs=update
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=list;get;watch
// Reconcile is part of the main kubernetes reconciliation loop which aims to // Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state. // move the current state of the cluster closer to the desired state.
@@ -77,8 +79,75 @@ func toKubeName(input string) string {
func (r *FirewallZoneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { func (r *FirewallZoneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx) log := log.FromContext(ctx)
cfg, err := r.ConfigLoader.GetConfig(ctx, "unifi-operator-config")
if err != nil {
return ctrl.Result{}, err
}
defaultNs := cfg.Data["defaultNamespace"]
err = r.UnifiClient.Reauthenticate()
if err != nil {
return ctrl.Result{}, err
}
fullSyncZone := "gateway"
if cfg.Data["fullSyncZone"] != "" {
fullSyncZone = cfg.Data["fullSyncZone"]
}
fullSync := false
var zoneObj unifiv1beta1.FirewallZone
if err := r.Get(ctx, req.NamespacedName, &zoneObj); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
log.Info(fmt.Sprintf("fullSyncZone: %s Zone: %s", fullSyncZone, zoneObj.Name))
if zoneObj.Name == fullSyncZone {
fullSync = true
log.Info("Going into fullsync mode")
}
err = r.UnifiClient.Reauthenticate()
if err != nil {
return ctrl.Result{}, err
}
if !fullSync {
firewallzones, err := r.UnifiClient.Client.ListFirewallZones(context.Background(), r.UnifiClient.SiteID)
if err != nil {
log.Error(err, "Could not list firewall zones")
return ctrl.Result{}, err
}
found := false
for _, unifizone := range firewallzones {
if unifizone.Name == zoneObj.Spec.Name {
found = true
zoneSpec := unifiv1beta1.FirewallZoneSpec{
Name: unifizone.Name,
ID: unifizone.ID,
DefaultZone: unifizone.DefaultZone,
NetworkIDs: unifizone.NetworkIDs,
}
zoneObj.Spec = zoneSpec
err := r.Update(ctx, &zoneObj)
if err != nil {
return ctrl.Result{}, err
}
}
}
if !found {
err := r.Delete(ctx, &zoneObj)
if err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
}
log.Info("Starting fullsync mode")
var fwzCRDs unifiv1beta1.FirewallZoneList var fwzCRDs unifiv1beta1.FirewallZoneList
_ = r.List(ctx, &fwzCRDs) _ = r.List(ctx, &fwzCRDs, client.InNamespace(defaultNs))
firewall_zones, err := r.UnifiClient.Client.ListFirewallZones(context.Background(), r.UnifiClient.SiteID) firewall_zones, err := r.UnifiClient.Client.ListFirewallZones(context.Background(), r.UnifiClient.SiteID)
if err != nil { if err != nil {
@@ -108,17 +177,17 @@ func (r *FirewallZoneReconciler) Reconcile(ctx context.Context, req ctrl.Request
for _, unifizone := range firewall_zones { for _, unifizone := range firewall_zones {
log.Info(fmt.Sprintf("%+v\n", unifizone)) log.Info(fmt.Sprintf("%+v\n", unifizone))
if _, found := firewallZoneNamesCRDs[unifizone.Name]; !found { if _, found := firewallZoneNamesCRDs[unifizone.Name]; !found {
zoneCRD := &unifiv1beta1.FirewallZone { zoneCRD := &unifiv1beta1.FirewallZone{
ObjectMeta : ctrl.ObjectMeta { ObjectMeta: ctrl.ObjectMeta{
Name: toKubeName(unifizone.Name), Name: toKubeName(unifizone.Name),
Namespace: "default", Namespace: defaultNs,
}, },
Spec: unifiv1beta1.FirewallZoneSpec { Spec: unifiv1beta1.FirewallZoneSpec{
Name : unifizone.Name, Name: unifizone.Name,
ID : unifizone.ID, ID: unifizone.ID,
DefaultZone: unifizone.DefaultZone, DefaultZone: unifizone.DefaultZone,
ZoneKey : unifizone.ZoneKey, ZoneKey: unifizone.ZoneKey,
NetworkIDs : unifizone.NetworkIDs, NetworkIDs: unifizone.NetworkIDs,
}, },
} }
err := r.Create(ctx, zoneCRD) err := r.Create(ctx, zoneCRD)
@@ -126,22 +195,22 @@ func (r *FirewallZoneReconciler) Reconcile(ctx context.Context, req ctrl.Request
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
} }
} else { } else {
for _, zoneCRD := range fwzCRDs.Items { for _, zoneCRD := range fwzCRDs.Items {
if zoneCRD.Spec.Name == unifizone.Name { if zoneCRD.Spec.Name == unifizone.Name {
zoneCRD.Spec = unifiv1beta1.FirewallZoneSpec { zoneCRD.Spec = unifiv1beta1.FirewallZoneSpec{
Name : unifizone.Name, Name: unifizone.Name,
ID : unifizone.ID, ID: unifizone.ID,
DefaultZone: unifizone.DefaultZone, DefaultZone: unifizone.DefaultZone,
ZoneKey : unifizone.ZoneKey, ZoneKey: unifizone.ZoneKey,
NetworkIDs : unifizone.NetworkIDs, NetworkIDs: unifizone.NetworkIDs,
} }
err := r.Update(ctx, &zoneCRD) err := r.Update(ctx, &zoneCRD)
if err != nil { if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
} }
} }
} }
} }
} }
return ctrl.Result{RequeueAfter: 10 * time.Minute}, nil return ctrl.Result{RequeueAfter: 10 * time.Minute}, nil

View File

@@ -19,26 +19,31 @@ package controller
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log"
unifiv1 "github.com/vegardengen/unifi-network-operator/api/v1beta1" unifiv1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
"github.com/vegardengen/unifi-network-operator/internal/config"
"github.com/vegardengen/unifi-network-operator/internal/unifi" "github.com/vegardengen/unifi-network-operator/internal/unifi"
) )
// NetworkconfigurationReconciler reconciles a Networkconfiguration object // NetworkconfigurationReconciler reconciles a Networkconfiguration object
type NetworkconfigurationReconciler struct { type NetworkconfigurationReconciler struct {
client.Client client.Client
Scheme *runtime.Scheme Scheme *runtime.Scheme
UnifiClient *unifi.UnifiClient UnifiClient *unifi.UnifiClient
ConfigLoader *config.ConfigLoaderType
} }
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=networkconfigurations,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=networkconfigurations,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=networkconfigurations/status,verbs=get;update;patch // +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=networkconfigurations/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=networkconfigurations/finalizers,verbs=update // +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=networkconfigurations/finalizers,verbs=update
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=list;get;watch
// Reconcile is part of the main kubernetes reconciliation loop which aims to // Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state. // move the current state of the cluster closer to the desired state.
@@ -51,54 +56,175 @@ type NetworkconfigurationReconciler struct {
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.2/pkg/reconcile // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.2/pkg/reconcile
func (r *NetworkconfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { func (r *NetworkconfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx) log := log.FromContext(ctx)
cfg, err := r.ConfigLoader.GetConfig(ctx, "unifi-operator-config")
var networkCRDs unifiv1.NetworkconfigurationList if err != nil {
if err := r.List(ctx, &networkCRDs); err != nil {
return ctrl.Result{}, err return ctrl.Result{}, err
} }
k8sNetworks := make(map[string]*unifiv1.Networkconfiguration)
for i := range networkCRDs.Items { defaultNs := cfg.Data["defaultNamespace"]
log.Info(fmt.Sprintf("Inserting network %s\n", networkCRDs.Items[i].Spec.NetworkID)) log.Info(defaultNs)
k8sNetworks[networkCRDs.Items[i].Spec.NetworkID] = &networkCRDs.Items[i]
fullSyncNetwork := "core"
if cfg.Data["fullSyncNetwork"] != "" {
fullSyncNetwork = cfg.Data["fullSyncNetwork"]
} }
fullSync := false
var networkObj unifiv1.Networkconfiguration
if err := r.Get(ctx, req.NamespacedName, &networkObj); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
log.Info(fmt.Sprintf("fullSyncNetwork: %s Network: %s", fullSyncNetwork, networkObj.Name))
if networkObj.Name == fullSyncNetwork {
fullSync = true
log.Info("Going into fullsync mode")
}
err = r.UnifiClient.Reauthenticate()
if err != nil {
return ctrl.Result{}, err
}
if !fullSync {
networks, err := r.UnifiClient.Client.ListNetwork(context.Background(), r.UnifiClient.SiteID)
if err != nil {
log.Error(err, "Could not list netwrorks")
return ctrl.Result{}, err
}
found := false
for _, unifinetwork := range networks {
if unifinetwork.Name == networkObj.Spec.Name {
found = true
networkSpec := unifiv1.NetworkconfigurationSpec{
Name: unifinetwork.Name,
ID: unifinetwork.ID,
IPSubnet: unifinetwork.IPSubnet,
Ipv6InterfaceType: unifinetwork.IPV6InterfaceType,
Ipv6PdAutoPrefixidEnabled: unifinetwork.IPV6PDAutoPrefixidEnabled,
Ipv6RaEnabled: unifinetwork.IPV6RaEnabled,
Ipv6SettingPreference: unifinetwork.IPV6SettingPreference,
Ipv6Subnet: unifinetwork.IPV6Subnet,
Purpose: unifinetwork.Purpose,
Networkgroup: unifinetwork.NetworkGroup,
SettingPreference: unifinetwork.SettingPreference,
Vlan: int64(unifinetwork.VLAN),
VlanEnabled: unifinetwork.VLANEnabled,
}
networkObj.Spec = networkSpec
err := r.Update(ctx, &networkObj)
if err != nil {
return ctrl.Result{}, err
}
}
}
if !found {
err := r.Delete(ctx, &networkObj)
if err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
}
log.Info("Starting fullsync mode")
var networkCRDs unifiv1.NetworkconfigurationList
_ = r.List(ctx, &networkCRDs, client.InNamespace(defaultNs))
networks, err := r.UnifiClient.Client.ListNetwork(context.Background(), r.UnifiClient.SiteID) networks, err := r.UnifiClient.Client.ListNetwork(context.Background(), r.UnifiClient.SiteID)
if err != nil { if err != nil {
log.Error(err, "Failed to list Unifi Networks") log.Error(err, "Could not list netwrorks")
return ctrl.Result{}, err return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
log.Info(fmt.Sprintf("Number of resources: %d Number of networks in Unifi: %d", len(networkCRDs.Items), len(networks)))
var firewallZoneCRDs unifiv1.FirewallZoneList
err = r.List(ctx, &firewallZoneCRDs, client.InNamespace(defaultNs))
if err != nil {
log.Error(err, "Could not list firewall zones")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
} }
seenNetworks := map[string]bool{} networkNamesUnifi := make(map[string]struct{})
for _, unifinetwork := range networks {
networkNamesUnifi[unifinetwork.Name] = struct{}{}
}
for _, network := range networks { // Step 2: Collect zones in fwzCRDs that are NOT in firewall_zones
networkID := network.ID for _, network := range networkCRDs.Items {
seenNetworks[networkID] = true if _, found := networkNamesUnifi[network.Spec.Name]; !found {
log.Info(fmt.Sprintf("Searching for %s\n", networkID)) err := r.Delete(ctx, &network)
if err != nil {
if existing, found := k8sNetworks[networkID]; found { return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
log.Info(fmt.Sprintf("Found network match: %s/%s", existing.Spec.NetworkID, networkID)) }
} else { }
if network.Purpose == "corporate" { }
log.Info(fmt.Sprintf("New network: %s with ID %s", network.Name, network.ID)) networkNamesCRDs := make(map[string]struct{})
var networkObject unifiv1.Networkconfiguration for _, networkCRD := range networkCRDs.Items {
networkObject.Name = network.Name networkNamesCRDs[networkCRD.Spec.Name] = struct{}{}
networkObject.Spec.Name = network.Name }
networkObject.Spec.NetworkID = network.ID for _, unifinetwork := range networks {
networkObject.Spec.IPSubnet = network.IPSubnet if unifinetwork.Purpose == "corporate" {
networkObject.Spec.Ipv6InterfaceType = network.IPV6InterfaceType networkSpec := unifiv1.NetworkconfigurationSpec{
networkObject.Spec.Ipv6PdAutoPrefixidEnabled = network.IPV6PDAutoPrefixidEnabled Name: unifinetwork.Name,
networkObject.Spec.Ipv6RaEnabled = network.IPV6RaEnabled ID: unifinetwork.ID,
networkObject.Spec.Ipv6SettingPreference = network.IPV6SettingPreference IPSubnet: unifinetwork.IPSubnet,
networkObject.Spec.Ipv6Subnet = network.IPV6Subnet Ipv6InterfaceType: unifinetwork.IPV6InterfaceType,
networkObject.Spec.Purpose = network.Purpose Ipv6PdAutoPrefixidEnabled: unifinetwork.IPV6PDAutoPrefixidEnabled,
networkObject.Spec.Networkgroup = network.NetworkGroup Ipv6RaEnabled: unifinetwork.IPV6RaEnabled,
networkObject.Spec.SettingPreference = network.SettingPreference Ipv6SettingPreference: unifinetwork.IPV6SettingPreference,
networkObject.Spec.VlanEnabled = network.VLANEnabled Ipv6Subnet: unifinetwork.IPV6Subnet,
Purpose: unifinetwork.Purpose,
Networkgroup: unifinetwork.NetworkGroup,
SettingPreference: unifinetwork.SettingPreference,
Vlan: int64(unifinetwork.VLAN),
VlanEnabled: unifinetwork.VLANEnabled,
}
networkStatus := unifiv1.NetworkconfigurationStatus{
FirewallZoneID: unifinetwork.FirewallZoneID,
}
log.Info(fmt.Sprintf("Network status %s: %+v", networkSpec.Name, networkStatus))
if _, found := networkNamesCRDs[unifinetwork.Name]; !found {
firewallZoneNamesCRDs := make(map[string]struct{})
firewallZoneIdsCRDs := make(map[string]struct{})
for _, firewallZoneCRD := range firewallZoneCRDs.Items {
firewallZoneNamesCRDs[firewallZoneCRD.Spec.Name] = struct{}{}
firewallZoneIdsCRDs[firewallZoneCRD.Spec.ID] = struct{}{}
}
networkCRD := &unifiv1.Networkconfiguration{
ObjectMeta: ctrl.ObjectMeta{
Name: toKubeName(unifinetwork.Name),
Namespace: defaultNs,
},
Spec: networkSpec,
Status: networkStatus,
}
err = r.Create(ctx, networkCRD)
if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
err = r.Get(ctx, types.NamespacedName{Name: networkCRD.Name, Namespace: networkCRD.Namespace}, networkCRD)
networkCRD.Status = networkStatus
if err = r.Status().Update(ctx, networkCRD); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
} else {
for _, networkCRD := range networkCRDs.Items {
if networkCRD.Spec.Name == unifinetwork.Name {
networkCRD.Spec = networkSpec
}
err := r.Update(ctx, &networkCRD)
if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
if err = r.Status().Update(ctx, &networkCRD); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
} }
} }
} }
return ctrl.Result{}, nil return ctrl.Result{RequeueAfter: 10 * time.Minute}, nil
} }
// SetupWithManager sets up the controller with the Manager. // SetupWithManager sets up the controller with the Manager.

View File

@@ -0,0 +1,33 @@
/* https://github.com/clbx/kube-port-forward-controller */
package unifi_network_operator_utils
import (
"regexp"
"strings"
)
func isIPv6(ip string) bool {
return strings.Contains(ip, ":")
}
func toKubeName(input string) string {
// Lowercase the input
name := strings.ToLower(input)
// Replace any non-alphanumeric characters with dashes
re := regexp.MustCompile(`[^a-z0-9\-\.]+`)
name = re.ReplaceAllString(name, "-")
// Trim leading and trailing non-alphanumerics
name = strings.Trim(name, "-.")
// Ensure it's not empty and doesn't exceed 253 characters
if len(name) == 0 {
name = "default"
} else if len(name) > 253 {
name = name[:253]
}
return name
}