Compare commits

...

69 Commits

Author SHA1 Message Date
401f4a7cf7 build on non-main
All checks were successful
Build project / build (push) Successful in 1m30s
2025-06-24 10:10:56 +02:00
68e8782da4 Publish
All checks were successful
Publish / build (push) Successful in 2m20s
2025-06-24 10:03:25 +02:00
e95a4c13e6 Publish
Some checks failed
Publish / build (push) Failing after 1m7s
2025-06-24 10:00:49 +02:00
d82d7d2902 Build only on branch push 2025-06-24 09:59:45 +02:00
3091c89fa8 Build only on branch push 2025-06-24 09:57:32 +02:00
853fca1635 Build only on branch push 2025-06-24 09:57:01 +02:00
07208979e3 Build only on branch push 2025-06-24 09:56:05 +02:00
c86196aa88 ko
Some checks failed
Build project / build (push) Successful in 1m26s
Publish / build (push) Failing after 1m9s
2025-06-24 09:48:43 +02:00
a35bc7220d pipelines
All checks were successful
Build project / build (push) Successful in 1m29s
2025-06-24 09:44:19 +02:00
446fb89d00 Change workflow 2025-06-24 09:40:44 +02:00
4bf3adaf87 test
All checks were successful
Build & Push Linode Webhook / build (push) Successful in 1m30s
2025-06-24 01:48:34 +02:00
d7f2d8031e fix
All checks were successful
Build & Push Linode Webhook / build (push) Successful in 1m31s
2025-06-24 01:47:34 +02:00
604cc20505 fix
All checks were successful
Build & Push Linode Webhook / build (push) Successful in 1m32s
2025-06-24 01:45:10 +02:00
572521747f fix
Some checks failed
Build & Push Linode Webhook / build (push) Failing after 7s
2025-06-24 01:43:34 +02:00
acea7fd46e fix 2025-06-24 01:42:50 +02:00
634021854d fix
Some checks failed
Build & Push Linode Webhook / build (push) Failing after 33s
2025-06-24 01:34:02 +02:00
1e0a51f83b fix
Some checks failed
Build & Push Linode Webhook / build (push) Failing after 6s
2025-06-24 01:33:19 +02:00
cab7bc2f5a fix
Some checks failed
Build & Push Linode Webhook / build (push) Failing after 2s
2025-06-24 01:31:49 +02:00
cef43b9c3e fix
Some checks failed
Build & Push Linode Webhook / build (push) Failing after 25s
2025-06-24 01:29:31 +02:00
019a33a69a fix
Some checks failed
Build & Push Linode Webhook / build (push) Failing after 2s
2025-06-24 01:27:30 +02:00
c2ff504510 fix
Some checks failed
Build & Push Linode Webhook / build (push) Failing after 3s
2025-06-24 01:26:43 +02:00
55ef068855 fix 2025-06-24 01:26:08 +02:00
babbf2ed97 Workflow 2025-06-24 00:14:08 +02:00
8765a94893 Rename
Some checks failed
Lint / Run on Ubuntu (push) Failing after 7s
E2E Tests / Run on Ubuntu (push) Failing after 3s
Tests / Run on Ubuntu (push) Failing after 3s
2025-06-24 00:12:16 +02:00
54ade7cc49 rename
Some checks failed
Lint / Run on Ubuntu (push) Failing after 6s
E2E Tests / Run on Ubuntu (push) Failing after 2s
Tests / Run on Ubuntu (push) Failing after 3s
2025-06-24 00:07:51 +02:00
bf666f0a89 rename
Some checks failed
Lint / Run on Ubuntu (push) Failing after 7s
E2E Tests / Run on Ubuntu (push) Failing after 3s
Tests / Run on Ubuntu (push) Failing after 3s
2025-06-24 00:06:39 +02:00
b59fc563f3 rename
Some checks failed
Lint / Run on Ubuntu (push) Failing after 6s
E2E Tests / Run on Ubuntu (push) Failing after 3s
Tests / Run on Ubuntu (push) Failing after 4s
2025-06-23 23:50:53 +02:00
b444690400 Merge pull request #40 from vegardengen/39-port-forwards-should-not-log-per-default
Turn off logging for newly created portforward rules
2025-06-11 10:14:56 +02:00
bcf73d64bf Turn off logging for newly created portforward rules 2025-06-11 10:13:48 +02:00
d372e4c7a7 Merge pull request #38 from vegardengen/37-optimize-api-usage
37 optimize api usage
2025-06-04 22:12:51 +02:00
c80473d9e8 workaround for bug? 2025-06-04 22:02:48 +02:00
bcffdfede7 Change leader election 2025-06-04 20:56:52 +02:00
d7a444c8d7 Change an ID in Delete firewallgroup 2025-06-04 20:56:37 +02:00
df9926e3da Small fix on updating firewall group/deleting component of it in Unifi 2025-06-04 19:10:03 +02:00
c2ffce2d4d Do a few less updates 2025-06-04 18:29:26 +02:00
fc0bda1e7b Merge pull request #36 from vegardengen/35-fix-portforward-logic
Check if portfoward already exists, and update if needed
2025-04-22 07:34:48 +02:00
dd4df6ee07 Check if portfoward already exists, and update if needed 2025-04-22 07:33:05 +02:00
86b58cb5a9 Merge pull request #34 from vegardengen/11-create-port-forward-api
Portforward API
2025-04-21 10:37:11 +02:00
6aed3728cc Portforward API 2025-04-21 10:35:39 +02:00
3167397a81 Merge pull request #33 from vegardengen/32-refactor-unifi-firewall-rule-api
32 refactor unifi firewall rule api
2025-04-21 01:42:56 +02:00
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
47 changed files with 3034 additions and 2157 deletions

View File

@@ -0,0 +1,24 @@
name: Build project
on:
push:
branches-ignore:
- main
jobs:
build:
runs-on: ubuntu-latest
container: golang:1.24
steps:
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan gitea-ssh.engen.priv.no >> ~/.ssh/known_hosts
- name: Install node and go
run: apt update && apt -y install nodejs
- name: Check out repository code
uses: actions/checkout@v4
- name: ssh repo
run: git config --global url.git@gitea-ssh.engen.priv.no:.insteadOf https://gitea.engen.priv.no/
- name: Build
run: go build cmd/main.go

View File

@@ -0,0 +1,26 @@
name: Publish
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
build:
runs-on: ubuntu-latest
container: golang:1.24
steps:
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan gitea-ssh.engen.priv.no >> ~/.ssh/known_hosts
- name: Install node and go
run: apt update && apt -y install nodejs
- name: Check out repository code
uses: actions/checkout@v4
- name: ssh repo
run: git config --global url.git@gitea-ssh.engen.priv.no:.insteadOf https://gitea.engen.priv.no/
- name: Install ko
run: go install github.com/google/ko@latest
- name: Build
run: KO_DOCKER_REPO=registry.engen.priv.no/unifi-network-operator-controller PATH=~/go/bin:$PATH ko publish ./cmd --tags latest --bare

11
PROJECT
View File

@@ -32,7 +32,16 @@ resources:
controller: true
domain: engen.priv.no
group: unifi
kind: FirewallRule
kind: FirewallPolicy
path: github.com/vegardengen/unifi-network-operator/api/v1beta1
version: v1beta1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: engen.priv.no
group: unifi
kind: PortForward
path: github.com/vegardengen/unifi-network-operator/api/v1beta1
version: v1beta1
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.
// FirewallGroupSpec defines the desired state of FirewallGroup.
type FirewallGroupSpec 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 FirewallGroup. Edit firewallgroup_types.go to remove/update
// Description is a human-readable explanation for the object
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
MatchServicesInAllNamespaces bool `json:"matchServicesInAllNamespaces,omitempty"`
// ManualAddresses is a list of manual IPs or CIDRs (IPv4 or IPv6)
// +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
// +optional
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.
@@ -52,17 +53,29 @@ type FirewallGroupStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// 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
// +optional
SyncedWithUnifi bool `json:"syncedWithUnifi,omitempty"`
ResourcesManaged *FirewallGroupResourcesManaged `json:"resources_managed,omitempty"`
// LastSyncTime is the last time the object was synced
// +optional
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: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
// Important: Run "make" to regenerate code after modifying this file
Name string `json:"name,omitempty"`
ID string `json:"_id,omitempty"`
DefaultZone bool `json:"default_zone,omitempty"`
ZoneKey string `json:"zone_key,omitempty"`
NetworkIDs []string `json:"network_ids,omitempty"`
Name string `json:"name,omitempty"`
ID string `json:"_id,omitempty"`
DefaultZone bool `json:"default_zone,omitempty"`
ZoneKey string `json:"zone_key,omitempty"`
NetworkIDs []string `json:"network_ids,omitempty"`
}
// FirewallZoneStatus defines the observed state of FirewallZone.
type FirewallZoneStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// 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

View File

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

View File

@@ -0,0 +1,48 @@
/*
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"
)
// PortForward is a placeholder type to allow future CRD support if needed.
// Right now, port forwards are managed entirely through annotations on Services.
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
type PortForward struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec PortForwardSpec `json:"spec,omitempty"`
Status PortForwardStatus `json:"status,omitempty"`
}
type PortForwardSpec struct {
// Reserved for future CRD-based management
}
type PortForwardStatus struct {
// Reserved for future CRD-based status
}
// PortForwardList contains a list of PortForward
// Currently unused, defined for completeness
type PortForwardList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []PortForward `json:"items"`
}

View File

@@ -25,6 +25,31 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *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.
func (in *FirewallGroup) DeepCopyInto(out *FirewallGroup) {
*out = *in
@@ -52,6 +77,21 @@ func (in *FirewallGroup) DeepCopyObject() runtime.Object {
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.
func (in *FirewallGroupList) DeepCopyInto(out *FirewallGroupList) {
*out = *in
@@ -84,6 +124,41 @@ func (in *FirewallGroupList) DeepCopyObject() runtime.Object {
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.
func (in *FirewallGroupSpec) DeepCopyInto(out *FirewallGroupSpec) {
*out = *in
@@ -92,6 +167,17 @@ func (in *FirewallGroupSpec) DeepCopyInto(out *FirewallGroupSpec) {
*out = make([]string, len(*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 {
in, out := &in.AutoIncludeSelector, &out.AutoIncludeSelector
*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.
func (in *FirewallGroupStatus) DeepCopyInto(out *FirewallGroupStatus) {
*out = *in
if in.ResolvedAddresses != nil {
in, out := &in.ResolvedAddresses, &out.ResolvedAddresses
if in.ResolvedIPV4Addresses != nil {
in, out := &in.ResolvedIPV4Addresses, &out.ResolvedIPV4Addresses
*out = make([]string, len(*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 {
in, out := &in.LastSyncTime, &out.LastSyncTime
*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.
func (in *FirewallRule) DeepCopyInto(out *FirewallRule) {
func (in *FirewallPolicy) DeepCopyInto(out *FirewallPolicy) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
out.Status = in.Status
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallRule.
func (in *FirewallRule) DeepCopy() *FirewallRule {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallPolicy.
func (in *FirewallPolicy) DeepCopy() *FirewallPolicy {
if in == nil {
return nil
}
out := new(FirewallRule)
out := new(FirewallPolicy)
in.DeepCopyInto(out)
return out
}
// 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 {
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.
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.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]FirewallRule, len(*in))
*out = make([]FirewallPolicy, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallRuleList.
func (in *FirewallRuleList) DeepCopy() *FirewallRuleList {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallPolicyList.
func (in *FirewallPolicyList) DeepCopy() *FirewallPolicyList {
if in == nil {
return nil
}
out := new(FirewallRuleList)
out := new(FirewallPolicyList)
in.DeepCopyInto(out)
return out
}
// 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 {
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.
func (in *FirewallRuleSpec) DeepCopyInto(out *FirewallRuleSpec) {
func (in *FirewallPolicyResourcesManaged) DeepCopyInto(out *FirewallPolicyResourcesManaged) {
*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.
func (in *FirewallRuleSpec) DeepCopy() *FirewallRuleSpec {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallPolicyResourcesManaged.
func (in *FirewallPolicyResourcesManaged) DeepCopy() *FirewallPolicyResourcesManaged {
if in == nil {
return nil
}
out := new(FirewallRuleSpec)
out := new(FirewallPolicyResourcesManaged)
in.DeepCopyInto(out)
return out
}
// 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
in.Source.DeepCopyInto(&out.Source)
in.Destination.DeepCopyInto(&out.Destination)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallRuleStatus.
func (in *FirewallRuleStatus) DeepCopy() *FirewallRuleStatus {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallPolicySpec.
func (in *FirewallPolicySpec) DeepCopy() *FirewallPolicySpec {
if in == 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)
return out
}
@@ -228,7 +406,7 @@ func (in *FirewallZone) DeepCopyInto(out *FirewallZone) {
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
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.
@@ -249,6 +427,21 @@ func (in *FirewallZone) DeepCopyObject() runtime.Object {
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.
func (in *FirewallZoneList) DeepCopyInto(out *FirewallZoneList) {
*out = *in
@@ -281,6 +474,26 @@ func (in *FirewallZoneList) DeepCopyObject() runtime.Object {
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.
func (in *FirewallZoneSpec) DeepCopyInto(out *FirewallZoneSpec) {
*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.
func (in *FirewallZoneStatus) DeepCopyInto(out *FirewallZoneStatus) {
*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.
@@ -316,6 +534,36 @@ func (in *FirewallZoneStatus) DeepCopy() *FirewallZoneStatus {
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.
func (in *Networkconfiguration) DeepCopyInto(out *Networkconfiguration) {
*out = *in
@@ -375,6 +623,26 @@ func (in *NetworkconfigurationList) DeepCopyObject() runtime.Object {
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.
func (in *NetworkconfigurationSpec) DeepCopyInto(out *NetworkconfigurationSpec) {
*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.
func (in *NetworkconfigurationStatus) DeepCopyInto(out *NetworkconfigurationStatus) {
*out = *in
if in.ResourcesManaged != nil {
in, out := &in.ResourcesManaged, &out.ResourcesManaged
*out = new(NetworkconfigurationResourcesManaged)
(*in).DeepCopyInto(*out)
}
if in.LastSyncTime != nil {
in, out := &in.LastSyncTime, &out.LastSyncTime
*out = (*in).DeepCopy()
@@ -408,3 +681,114 @@ func (in *NetworkconfigurationStatus) DeepCopy() *NetworkconfigurationStatus {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PortForward) DeepCopyInto(out *PortForward) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
out.Status = in.Status
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortForward.
func (in *PortForward) DeepCopy() *PortForward {
if in == nil {
return nil
}
out := new(PortForward)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PortForward) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PortForwardList) DeepCopyInto(out *PortForwardList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]PortForward, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortForwardList.
func (in *PortForwardList) DeepCopy() *PortForwardList {
if in == nil {
return nil
}
out := new(PortForwardList)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PortForwardSpec) DeepCopyInto(out *PortForwardSpec) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortForwardSpec.
func (in *PortForwardSpec) DeepCopy() *PortForwardSpec {
if in == nil {
return nil
}
out := new(PortForwardSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PortForwardStatus) DeepCopyInto(out *PortForwardStatus) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortForwardStatus.
func (in *PortForwardStatus) DeepCopy() *PortForwardStatus {
if in == nil {
return nil
}
out := new(PortForwardStatus)
in.DeepCopyInto(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

@@ -21,11 +21,13 @@ import (
"flag"
"os"
"path/filepath"
"time"
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/utils/pointer"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
@@ -37,9 +39,10 @@ import (
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
"github.com/vegardengen/unifi-network-operator/internal/controller"
"github.com/vegardengen/unifi-network-operator/internal/unifi"
unifiv1beta1 "gitea.engen.priv.no/klauvsteinen/unifi-network-operator/api/v1beta1"
"gitea.engen.priv.no/klauvsteinen/unifi-network-operator/internal/config"
"gitea.engen.priv.no/klauvsteinen/unifi-network-operator/internal/controller"
"gitea.engen.priv.no/klauvsteinen/unifi-network-operator/internal/unifi"
// +kubebuilder:scaffold:imports
)
@@ -186,6 +189,10 @@ func main() {
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "f05533b6.engen.priv.no",
LeaseDuration: pointer.Duration(30 * time.Second),
RenewDeadline: pointer.Duration(20 * time.Second),
RetryPeriod: pointer.Duration(5 * time.Second),
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
// when the Manager ends. This requires the binary to immediately end when the
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
@@ -203,6 +210,8 @@ func main() {
os.Exit(1)
}
configLoader := config.NewConfigLoader(mgr.GetClient())
// Unifi client
setupLog.Info("Setting up UniFi client")
unifiClient, err := unifi.CreateUnifiClient()
@@ -213,34 +222,48 @@ func main() {
setupLog.Info("Finished Setting up UniFi client")
if err = (&controller.NetworkconfigurationReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
UnifiClient: unifiClient,
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
UnifiClient: unifiClient,
ConfigLoader: configLoader,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Networkconfiguration")
os.Exit(1)
}
if err = (&controller.FirewallZoneReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
UnifiClient: unifiClient,
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
UnifiClient: unifiClient,
ConfigLoader: configLoader,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "FirewallZone")
os.Exit(1)
}
if err = (&controller.FirewallRuleReconciler{
if err = (&controller.FirewallPolicyReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
UnifiClient: unifiClient,
ConfigLoader: configLoader,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "FirewallPolicy")
os.Exit(1)
}
if err = (&controller.PortForwardReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
UnifiClient: unifiClient,
ConfigLoader: configLoader,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "FirewallRule")
setupLog.Error(err, "unable to create controller", "controller", "PortForward")
os.Exit(1)
}
// +kubebuilder:scaffold:builder
if err = (&controller.FirewallGroupReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
UnifiClient: unifiClient,
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
UnifiClient: unifiClient,
ConfigLoader: configLoader,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "FirewallGroup")
os.Exit(1)

View File

@@ -37,15 +37,14 @@ spec:
metadata:
type: object
spec:
description: FirewallGroupSpec defines the desired state of FirewallGroup.
properties:
addressType:
description: AddressType can be "ip", "cidr", or "both"
enum:
- ip
- cidr
- both
type: string
auto_created_from:
properties:
name:
type: string
namespace:
type: string
type: object
autoIncludeSelector:
description: AutoIncludeSelector defines which services to extract
addresses from
@@ -93,18 +92,33 @@ spec:
type: object
type: object
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:
description: ManualAddresses is a list of manual IPs or CIDRs (IPv4
or IPv6)
items:
type: string
type: array
manualPorts:
items:
type: string
type: array
matchServicesInAllNamespaces:
type: boolean
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: object
status:
@@ -114,10 +128,53 @@ spec:
description: LastSyncTime is the last time the object was synced
format: date-time
type: string
resolvedAddresses:
resolvedIPV4Addresses:
items:
type: string
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:
description: SyncedWithUnifi indicates whether the addresses are successfully
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

@@ -54,6 +54,19 @@ spec:
type: object
status:
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
served: true

View File

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

View File

@@ -4,20 +4,22 @@ kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.2
name: firewallrules.unifi.engen.priv.no
name: portforwards.unifi.engen.priv.no
spec:
group: unifi.engen.priv.no
names:
kind: FirewallRule
listKind: FirewallRuleList
plural: firewallrules
singular: firewallrule
kind: PortForward
listKind: PortForwardList
plural: portforwards
singular: portforward
scope: Namespaced
versions:
- name: v1beta1
schema:
openAPIV3Schema:
description: FirewallRule is the Schema for the firewallrules API.
description: |-
PortForward is a placeholder type to allow future CRD support if needed.
Right now, port forwards are managed entirely through annotations on Services.
properties:
apiVersion:
description: |-
@@ -37,15 +39,8 @@ spec:
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

View File

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

View File

@@ -0,0 +1,27 @@
# This rule is not used by the project unifi-network-operator itself.
# It is provided to allow the cluster admin to help manage permissions for users.
#
# Grants full permissions ('*') over unifi.engen.priv.no.
# This role is intended for users authorized to modify roles and bindings within the cluster,
# enabling them to delegate specific permissions to other users or groups as needed.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize
name: firewallpolicy-admin-role
rules:
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallpolicies
verbs:
- '*'
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallpolicies/status
verbs:
- get

View File

@@ -0,0 +1,33 @@
# This rule is not used by the project unifi-network-operator itself.
# It is provided to allow the cluster admin to help manage permissions for users.
#
# Grants permissions to create, update, and delete resources within the unifi.engen.priv.no.
# This role is intended for users who need to manage these resources
# but should not control RBAC or manage permissions for others.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize
name: firewallpolicy-editor-role
rules:
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallpolicies
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallpolicies/status
verbs:
- get

View File

@@ -0,0 +1,29 @@
# This rule is not used by the project unifi-network-operator itself.
# It is provided to allow the cluster admin to help manage permissions for users.
#
# Grants read-only access to unifi.engen.priv.no resources.
# This role is intended for users who need visibility into these resources
# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize
name: firewallpolicy-viewer-role
rules:
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallpolicies
verbs:
- get
- list
- watch
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallpolicies/status
verbs:
- get

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ rules:
- apiGroups:
- ""
resources:
- configmaps
- services
verbs:
- get
@@ -16,9 +17,10 @@ rules:
- unifi.engen.priv.no
resources:
- firewallgroups
- firewallrules
- firewallpolicies
- firewallzones
- networkconfigurations
- portforwards
verbs:
- create
- delete
@@ -31,18 +33,20 @@ rules:
- unifi.engen.priv.no
resources:
- firewallgroups/finalizers
- firewallrules/finalizers
- firewallpolicies/finalizers
- firewallzones/finalizers
- networkconfigurations/finalizers
- portforwards/finalizers
verbs:
- update
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallgroups/status
- firewallrules/status
- firewallpolicies/status
- firewallzones/status
- networkconfigurations/status
- portforwards/status
verbs:
- get
- patch

View File

@@ -3,4 +3,5 @@ resources:
- unifi_v1beta1_networkconfiguration.yaml
- unifi_v1beta1_firewallzone.yaml
- unifi_v1beta1_firewallrule.yaml
- unifi_v1beta1_portforward.yaml
# +kubebuilder:scaffold:manifestskustomizesamples

View File

@@ -0,0 +1,8 @@
apiVersion: unifi.engen.priv.no/v1beta1
kind: FirewallPolicy
metadata:
labels:
app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize
name: firewallpolicy-sample
spec:

View File

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

347
go.mod
View File

@@ -1,15 +1,14 @@
module github.com/vegardengen/unifi-network-operator
module gitea.engen.priv.no/klauvsteinen/unifi-network-operator
go 1.24.0
toolchain go1.24.1
godebug default=go1.23
require (
github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.37.0
github.com/vegardengen/go-unifi v0.0.1-alpha9
gitea.engen.priv.no/klauvsteinen/go-unifi v0.0.1-alpha26
k8s.io/api v0.32.1
k8s.io/apimachinery v0.32.1
k8s.io/client-go v0.32.1
@@ -17,376 +16,54 @@ require (
)
require (
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
4d63.com/gochecknoglobals v0.2.1 // indirect
cel.dev/expr v0.23.1 // indirect
cloud.google.com/go v0.112.0 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/iam v1.1.5 // indirect
cloud.google.com/go/kms v1.15.5 // indirect
cloud.google.com/go/storage v1.36.0 // indirect
code.gitea.io/sdk/gitea v0.16.0 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/4meepo/tagalign v1.3.3 // indirect
github.com/Abirdcfly/dupword v0.0.13 // indirect
github.com/AlekSi/pointer v1.2.0 // indirect
github.com/Antonboom/errname v0.1.12 // indirect
github.com/Antonboom/nilnil v0.1.7 // indirect
github.com/Antonboom/testifylint v0.2.3 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.4.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/OpenPeeDeeP/depguard/v2 v2.1.0 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/alecthomas/go-check-sumtype v0.1.3 // indirect
github.com/alessio/shellescape v1.4.1 // indirect
github.com/alexkohler/nakedret/v2 v2.0.2 // indirect
github.com/alexkohler/prealloc v1.0.0 // indirect
github.com/alingse/asasalint v0.0.11 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/ashanbrown/forbidigo v1.6.0 // indirect
github.com/ashanbrown/makezero v1.1.1 // indirect
github.com/atc0005/go-teams-notify/v2 v2.8.0 // indirect
github.com/aws/aws-sdk-go v1.47.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.21.2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 // indirect
github.com/aws/aws-sdk-go-v2/config v1.19.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.43 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.76 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.20.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4 // indirect
github.com/aws/aws-sdk-go-v2/service/kms v1.24.7 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.15.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 // indirect
github.com/aws/smithy-go v1.15.0 // indirect
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bkielbasa/cyclop v1.2.1 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/blizzy78/varnamelen v0.8.0 // indirect
github.com/bombsimon/wsl/v3 v3.4.0 // indirect
github.com/breml/bidichk v0.2.7 // indirect
github.com/breml/errchkjson v0.3.6 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/butuzov/ireturn v0.2.2 // indirect
github.com/butuzov/mirror v1.1.0 // indirect
github.com/caarlos0/ctrlc v1.2.0 // indirect
github.com/caarlos0/env/v9 v9.0.0 // indirect
github.com/caarlos0/go-reddit/v3 v3.0.1 // indirect
github.com/caarlos0/go-shellwords v1.0.12 // indirect
github.com/caarlos0/go-version v0.1.1 // indirect
github.com/caarlos0/log v0.4.2 // indirect
github.com/catenacyber/perfsprint v0.2.0 // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/ccojocar/zxcvbn-go v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charithe/durationcheck v0.0.10 // indirect
github.com/charmbracelet/lipgloss v0.8.0 // indirect
github.com/chavacava/garif v0.1.0 // indirect
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect
github.com/cloudflare/circl v1.3.5 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/curioswitch/go-reassign v0.2.0 // indirect
github.com/daixiang0/gci v0.11.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/denis-tingaikin/go-header v0.4.3 // indirect
github.com/dghubble/go-twitter v0.0.0-20211115160449-93a8679adecb // indirect
github.com/dghubble/oauth1 v0.7.2 // indirect
github.com/dghubble/sling v1.4.0 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/disgoorg/disgo v0.16.9 // indirect
github.com/disgoorg/json v1.1.0 // indirect
github.com/disgoorg/log v1.2.1 // indirect
github.com/disgoorg/snowflake/v2 v2.0.1 // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/cli v24.0.7+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v24.0.7+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/esimonov/ifshort v1.0.4 // indirect
github.com/ettle/strcase v0.1.1 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/firefart/nonamedreturns v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
github.com/ghostiam/protogetter v0.2.3 // indirect
github.com/go-critic/go-critic v0.9.0 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.4.1 // indirect
github.com/go-git/go-git/v5 v5.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/errors v0.20.4 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/loads v0.21.2 // indirect
github.com/go-openapi/runtime v0.26.0 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/strfmt v0.21.7 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-openapi/validate v0.22.1 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect
github.com/go-toolsmith/astcopy v1.1.0 // indirect
github.com/go-toolsmith/astequal v1.1.0 // indirect
github.com/go-toolsmith/astfmt v1.1.0 // indirect
github.com/go-toolsmith/astp v1.1.0 // indirect
github.com/go-toolsmith/strparse v1.1.0 // indirect
github.com/go-toolsmith/typep v1.1.0 // indirect
github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect
github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect
github.com/golangci/golangci-lint v1.55.2 // indirect
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect
github.com/golangci/misspell v0.4.1 // indirect
github.com/golangci/revgrep v0.5.2 // indirect
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/cel-go v0.22.0 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-containerregistry v0.16.1 // indirect
github.com/google/go-github/v55 v55.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/ko v0.14.1 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/rpmpack v0.5.0 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/google/wire v0.5.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 // indirect
github.com/goreleaser/chglog v0.5.0 // indirect
github.com/goreleaser/fileglob v1.3.0 // indirect
github.com/goreleaser/goreleaser v1.21.2 // indirect
github.com/goreleaser/nfpm/v2 v2.33.1 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.4.2 // indirect
github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/jsonschema v0.9.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jgautheron/goconst v1.6.0 // indirect
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/julz/importas v0.1.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kisielk/errcheck v1.6.3 // indirect
github.com/kisielk/gotool v1.0.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.4 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/kulti/thelper v0.6.3 // indirect
github.com/kunwardeep/paralleltest v1.0.8 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/kyoh86/exportloopref v0.1.11 // indirect
github.com/ldez/gomoddirectives v0.2.3 // indirect
github.com/ldez/tagliatelle v0.5.0 // indirect
github.com/leonklingele/grouper v1.1.1 // indirect
github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lufeee/execinquery v1.2.1 // indirect
github.com/macabu/inamedparam v0.1.2 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/maratori/testableexamples v1.0.0 // indirect
github.com/maratori/testpackage v1.1.1 // indirect
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-mastodon v0.0.6 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
github.com/mgechev/revive v1.3.4 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/moricho/tparallel v0.3.1 // indirect
github.com/muesli/mango v0.1.0 // indirect
github.com/muesli/mango-cobra v1.2.0 // indirect
github.com/muesli/mango-pflag v0.1.0 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/roff v0.1.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nakabonne/nestif v0.3.1 // indirect
github.com/nishanths/exhaustive v0.11.0 // indirect
github.com/nishanths/predeclared v0.2.2 // indirect
github.com/nunnatsa/ginkgolinter v0.14.1 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/polyfloyd/go-errorlint v1.4.5 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.63.0 // indirect
github.com/prometheus/procfs v0.16.0 // indirect
github.com/quasilyte/go-ruleguard v0.4.0 // indirect
github.com/quasilyte/gogrep v0.5.0 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
github.com/rivo/uniseg v0.4.2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryancurrah/gomodguard v1.3.0 // indirect
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
github.com/sashamelentyev/usestdlibvars v1.24.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect
github.com/securego/gosec/v2 v2.18.2 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sigstore/cosign/v2 v2.2.1 // indirect
github.com/sigstore/rekor v1.3.3 // indirect
github.com/sigstore/sigstore v1.7.5 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sivchari/containedctx v1.0.3 // indirect
github.com/sivchari/nosnakecase v1.7.0 // indirect
github.com/sivchari/tenv v1.7.1 // indirect
github.com/skeema/knownhosts v1.1.1 // indirect
github.com/slack-go/slack v0.12.3 // indirect
github.com/sonatard/noctx v0.0.2 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/sourcegraph/go-diff v0.7.0 // indirect
github.com/spf13/afero v1.10.0 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/spf13/viper v1.17.0 // indirect
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect
github.com/tdakkota/asciicheck v0.2.0 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/tetafro/godot v1.4.15 // indirect
github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect
github.com/timonwong/loggercheck v0.9.4 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/ultraware/funlen v0.1.0 // indirect
github.com/ultraware/whitespace v0.0.5 // indirect
github.com/uudashr/gocognit v1.1.2 // indirect
github.com/vbatts/tar-split v0.11.5 // indirect
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/go-gitlab v0.93.2 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xen0n/gosmopolitan v1.2.2 // indirect
github.com/yagipy/maintidx v1.0.0 // indirect
github.com/yeya24/promlinter v0.2.0 // indirect
github.com/ykadowak/zerologlint v0.1.3 // indirect
gitlab.com/bosi/decorder v0.4.1 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
go-simpler.org/sloglint v0.1.2 // indirect
go.mongodb.org/mongo-driver v1.12.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
@@ -395,15 +72,10 @@ require (
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.tmz.dev/musttag v0.7.2 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
gocloud.dev v0.34.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/sync v0.13.0 // indirect
@@ -412,37 +84,22 @@ require (
golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.32.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/api v0.155.0 // indirect
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a // indirect
google.golang.org/grpc v1.71.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/mail.v2 v2.3.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/tools v0.4.6 // indirect
k8s.io/apiextensions-apiserver v0.32.1 // indirect
k8s.io/apiserver v0.32.1 // indirect
k8s.io/component-base v0.32.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect
mvdan.cc/gofumpt v0.5.0 // indirect
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.32.0 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/kind v0.20.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect

1451
go.sum

File diff suppressed because it is too large Load Diff

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"
"net"
"reflect"
"regexp"
"slices"
"strconv"
"strings"
corev1 "k8s.io/api/core/v1"
@@ -29,27 +32,33 @@ import (
"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"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
// "sigs.k8s.io/controller-runtime/pkg/source"
goUnifi "github.com/vegardengen/go-unifi/unifi"
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
"github.com/vegardengen/unifi-network-operator/internal/unifi"
goUnifi "gitea.engen.priv.no/klauvsteinen/go-unifi/unifi"
unifiv1beta1 "gitea.engen.priv.no/klauvsteinen/unifi-network-operator/api/v1beta1"
"gitea.engen.priv.no/klauvsteinen/unifi-network-operator/internal/config"
"gitea.engen.priv.no/klauvsteinen/unifi-network-operator/internal/unifi"
)
const firewallGroupFinalizer = "finalizer.unifi.engen.priv.no/firewallgroup"
// FirewallGroupReconciler reconciles a FirewallGroup object
type FirewallGroupReconciler struct {
client.Client
Scheme *runtime.Scheme
UnifiClient *unifi.UnifiClient
Scheme *runtime.Scheme
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/status,verbs=get;update;patch
// +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=configmaps,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.
@@ -63,14 +72,159 @@ type FirewallGroupReconciler struct {
func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
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)
}
log.Info(nwObj.Spec.Name)
var ipv4, ipv6 []string
log.Info(firewallGroup.Spec.Name)
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)
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
if nwObj.Spec.MatchServicesInAllNamespaces {
if firewallGroup.Spec.MatchServicesInAllNamespaces {
if err := r.List(ctx, &services); err != nil {
log.Error(err, "unable to list services")
return reconcile.Result{}, err
@@ -111,8 +285,18 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
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 {
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 {
if 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
nwObj.Status.ResolvedAddresses = append(nwObj.Status.ResolvedAddresses, ipv6...)
firewallGroup.Status.ResolvedIPV4Addresses = ipv4
firewallGroup.Status.ResolvedIPV6Addresses = ipv6
firewallGroup.Status.ResolvedTCPPorts = tcpports
firewallGroup.Status.ResolvedUDPPorts = udpports
currentTime := metav1.Now()
nwObj.Status.LastSyncTime = &currentTime
nwObj.Status.SyncedWithUnifi = true
err := r.UnifiClient.Reauthenticate()
firewallGroup.Status.LastSyncTime = &currentTime
firewallGroup.Status.SyncedWithUnifi = true
if firewallGroup.Status.ResourcesManaged == nil {
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 {
return reconcile.Result{}, err
}
@@ -140,19 +361,23 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
log.Error(err, "Could not list network objects")
return reconcile.Result{}, err
}
ipv4_name := "k8s-" + nwObj.Spec.Name + "-ipv4"
ipv6_name := "k8s-" + nwObj.Spec.Name + "-ipv6"
ipv4_name := "k8s-" + firewallGroup.Spec.Name + "-ipv4"
ipv6_name := "k8s-" + firewallGroup.Spec.Name + "-ipv6"
tcpports_name := "k8s-" + firewallGroup.Spec.Name + "-tcpports"
udpports_name := "k8s-" + firewallGroup.Spec.Name + "-udpports"
ipv4_done := false
ipv6_done := false
tcpports_done := false
udpports_done := false
for _, firewall_group := range firewall_groups {
if firewall_group.Name == ipv4_name {
if len(ipv4) == 0 {
log.Info(fmt.Sprintf("Delete %s", ipv4_name))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group.ID)
log.Info(fmt.Sprintf("Delete %s: %s", ipv4_name, 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") {
if strings.Contains(msg, "api.err.objectreferredby") || strings.Contains(msg,"invalid character") {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"127.0.0.1"}
firewall_group.Name = firewall_group.Name + "-deleted"
@@ -161,10 +386,15 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
firewallGroup.Status.ResourcesManaged.IPV4Object.Name = ""
firewallGroup.Status.ResourcesManaged.IPV4Object.ID = ""
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
} else {
firewallGroup.Status.ResourcesManaged.IPV4Object.Name = ""
firewallGroup.Status.ResourcesManaged.IPV4Object.ID = ""
}
ipv4_done = true
} else {
@@ -183,11 +413,11 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
if firewall_group.Name == ipv6_name {
if len(ipv6) == 0 {
log.Info(fmt.Sprintf("Delete %s", ipv6_name))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group.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") {
if strings.Contains(msg, "api.err.objectreferredby") || strings.Contains(msg,"invalid character") {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"::1"}
firewall_group.Name = firewall_group.Name + "-deleted"
@@ -196,10 +426,15 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
firewallGroup.Status.ResourcesManaged.IPV6Object.Name = ""
firewallGroup.Status.ResourcesManaged.IPV6Object.ID = ""
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
} else {
firewallGroup.Status.ResourcesManaged.IPV6Object.Name = ""
firewallGroup.Status.ResourcesManaged.IPV6Object.ID = ""
}
ipv6_done = true
} else {
@@ -215,6 +450,86 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
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, firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") || strings.Contains(msg,"invalid character") {
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, firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") || strings.Contains(msg,"invalid character") {
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 {
firewall_group.Name = ipv4_name
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")
return reconcile.Result{}, err
}
firewallGroup.Status.ResourcesManaged.IPV4Object.Name = firewall_group.Name
firewallGroup.Status.ResourcesManaged.IPV4Object.ID = firewall_group.ID
ipv4_done = true
}
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")
return reconcile.Result{}, err
}
firewallGroup.Status.ResourcesManaged.IPV6Object.Name = firewall_group.Name
firewallGroup.Status.ResourcesManaged.IPV6Object.ID = firewall_group.ID
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 {
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.GroupMembers = ipv4
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 {
log.Error(err, "Could not create firewall group")
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 {
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.GroupMembers = ipv6
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 {
log.Error(err, "Could not create firewall group")
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")
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
}
@@ -289,15 +686,15 @@ func (r *FirewallGroupReconciler) mapServiceToFirewallGroups(ctx context.Context
return nil
}
for _, fwg := range allFirewallGroups.Items {
if fwg.Spec.MatchServicesInAllNamespaces || fwg.Namespace == service.Namespace {
for _, firewallGroup := range allFirewallGroups.Items {
if firewallGroup.Spec.MatchServicesInAllNamespaces || firewallGroup.Namespace == service.Namespace {
annotationKey := "unifi.engen.priv.no/firewall-group"
annotationVal := fwg.Name
annotationVal := firewallGroup.Name
if val, ok := service.Annotations[annotationKey]; ok && (annotationVal == "" || val == annotationVal) {
requests = append(requests, ctrl.Request{
NamespacedName: types.NamespacedName{
Name: fwg.Name,
Namespace: fwg.Namespace,
Name: firewallGroup.Name,
Namespace: firewallGroup.Namespace,
},
})
}

View File

@@ -27,7 +27,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
unifiv1beta1 "gitea.engen.priv.no/klauvsteinen/unifi-network-operator/api/v1beta1"
)
var _ = Describe("FirewallGroup Controller", func() {

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 "gitea.engen.priv.no/klauvsteinen/go-unifi/unifi"
unifiv1beta1 "gitea.engen.priv.no/klauvsteinen/unifi-network-operator/api/v1beta1"
"gitea.engen.priv.no/klauvsteinen/unifi-network-operator/internal/config"
"gitea.engen.priv.no/klauvsteinen/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

@@ -0,0 +1,84 @@
/*
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"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
unifiv1beta1 "gitea.engen.priv.no/klauvsteinen/unifi-network-operator/api/v1beta1"
)
var _ = Describe("FirewallPolicy Controller", func() {
Context("When reconciling a resource", func() {
const resourceName = "test-resource"
ctx := context.Background()
typeNamespacedName := types.NamespacedName{
Name: resourceName,
Namespace: "default", // TODO(user):Modify as needed
}
firewallpolicy := &unifiv1beta1.FirewallPolicy{}
BeforeEach(func() {
By("creating the custom resource for the Kind FirewallPolicy")
err := k8sClient.Get(ctx, typeNamespacedName, firewallpolicy)
if err != nil && errors.IsNotFound(err) {
resource := &unifiv1beta1.FirewallPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: resourceName,
Namespace: "default",
},
// TODO(user): Specify other spec details if needed.
}
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
}
})
AfterEach(func() {
// TODO(user): Cleanup logic after each test, like removing the resource instance.
resource := &unifiv1beta1.FirewallPolicy{}
err := k8sClient.Get(ctx, typeNamespacedName, resource)
Expect(err).NotTo(HaveOccurred())
By("Cleanup the specific resource instance FirewallPolicy")
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
})
It("should successfully reconcile the resource", func() {
By("Reconciling the created resource")
controllerReconciler := &FirewallPolicyReconciler{
Client: k8sClient,
Scheme: k8sClient.Scheme(),
}
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.
// Example: If you expect a certain status condition after reconciliation, verify it here.
})
})
})

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 (
"context"
"fmt"
"strings"
"regexp"
"strings"
"time"
"k8s.io/apimachinery/pkg/runtime"
@@ -28,42 +28,44 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
"github.com/vegardengen/unifi-network-operator/internal/unifi"
unifiv1beta1 "gitea.engen.priv.no/klauvsteinen/unifi-network-operator/api/v1beta1"
"gitea.engen.priv.no/klauvsteinen/unifi-network-operator/internal/config"
"gitea.engen.priv.no/klauvsteinen/unifi-network-operator/internal/unifi"
)
// FirewallZoneReconciler reconciles a FirewallZone object
type FirewallZoneReconciler struct {
client.Client
Scheme *runtime.Scheme
UnifiClient *unifi.UnifiClient
Scheme *runtime.Scheme
UnifiClient *unifi.UnifiClient
ConfigLoader *config.ConfigLoaderType
}
func toKubeName(input string) string {
// Lowercase the input
name := strings.ToLower(input)
// Lowercase the input
name := strings.ToLower(input)
// Replace any non-alphanumeric characters with dashes
re := regexp.MustCompile(`[^a-z0-9\-\.]+`)
name = re.ReplaceAllString(name, "-")
// 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, "-.")
// 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]
}
// 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
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/status,verbs=get;update;patch
// +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
// 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) {
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
_ = r.List(ctx, &fwzCRDs)
_ = r.List(ctx, &fwzCRDs, client.InNamespace(defaultNs))
firewall_zones, err := r.UnifiClient.Client.ListFirewallZones(context.Background(), r.UnifiClient.SiteID)
if err != nil {
@@ -108,17 +177,17 @@ func (r *FirewallZoneReconciler) Reconcile(ctx context.Context, req ctrl.Request
for _, unifizone := range firewall_zones {
log.Info(fmt.Sprintf("%+v\n", unifizone))
if _, found := firewallZoneNamesCRDs[unifizone.Name]; !found {
zoneCRD := &unifiv1beta1.FirewallZone {
ObjectMeta : ctrl.ObjectMeta {
Name: toKubeName(unifizone.Name),
Namespace: "default",
},
Spec: unifiv1beta1.FirewallZoneSpec {
Name : unifizone.Name,
ID : unifizone.ID,
zoneCRD := &unifiv1beta1.FirewallZone{
ObjectMeta: ctrl.ObjectMeta{
Name: toKubeName(unifizone.Name),
Namespace: defaultNs,
},
Spec: unifiv1beta1.FirewallZoneSpec{
Name: unifizone.Name,
ID: unifizone.ID,
DefaultZone: unifizone.DefaultZone,
ZoneKey : unifizone.ZoneKey,
NetworkIDs : unifizone.NetworkIDs,
ZoneKey: unifizone.ZoneKey,
NetworkIDs: unifizone.NetworkIDs,
},
}
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
}
} else {
for _, zoneCRD := range fwzCRDs.Items {
if zoneCRD.Spec.Name == unifizone.Name {
zoneCRD.Spec = unifiv1beta1.FirewallZoneSpec {
Name : unifizone.Name,
ID : unifizone.ID,
DefaultZone: unifizone.DefaultZone,
ZoneKey : unifizone.ZoneKey,
NetworkIDs : unifizone.NetworkIDs,
}
err := r.Update(ctx, &zoneCRD)
if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
}
for _, zoneCRD := range fwzCRDs.Items {
if zoneCRD.Spec.Name == unifizone.Name {
zoneCRD.Spec = unifiv1beta1.FirewallZoneSpec{
Name: unifizone.Name,
ID: unifizone.ID,
DefaultZone: unifizone.DefaultZone,
ZoneKey: unifizone.ZoneKey,
NetworkIDs: unifizone.NetworkIDs,
}
err := r.Update(ctx, &zoneCRD)
if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
}
}
return ctrl.Result{RequeueAfter: 10 * time.Minute}, nil

View File

@@ -27,7 +27,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
unifiv1beta1 "gitea.engen.priv.no/klauvsteinen/unifi-network-operator/api/v1beta1"
)
var _ = Describe("FirewallZone Controller", func() {

View File

@@ -19,26 +19,32 @@ package controller
import (
"context"
"fmt"
"reflect"
"time"
"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/log"
unifiv1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
"github.com/vegardengen/unifi-network-operator/internal/unifi"
unifiv1 "gitea.engen.priv.no/klauvsteinen/unifi-network-operator/api/v1beta1"
"gitea.engen.priv.no/klauvsteinen/unifi-network-operator/internal/config"
"gitea.engen.priv.no/klauvsteinen/unifi-network-operator/internal/unifi"
)
// NetworkconfigurationReconciler reconciles a Networkconfiguration object
type NetworkconfigurationReconciler struct {
client.Client
Scheme *runtime.Scheme
UnifiClient *unifi.UnifiClient
Scheme *runtime.Scheme
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/status,verbs=get;update;patch
// +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
// move the current state of the cluster closer to the desired state.
@@ -51,54 +57,182 @@ type NetworkconfigurationReconciler struct {
// - 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) {
log := log.FromContext(ctx)
var networkCRDs unifiv1.NetworkconfigurationList
if err := r.List(ctx, &networkCRDs); err != nil {
cfg, err := r.ConfigLoader.GetConfig(ctx, "unifi-operator-config")
if err != nil {
return ctrl.Result{}, err
}
k8sNetworks := make(map[string]*unifiv1.Networkconfiguration)
for i := range networkCRDs.Items {
log.Info(fmt.Sprintf("Inserting network %s\n", networkCRDs.Items[i].Spec.NetworkID))
k8sNetworks[networkCRDs.Items[i].Spec.NetworkID] = &networkCRDs.Items[i]
defaultNs := cfg.Data["defaultNamespace"]
log.Info(defaultNs)
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,
}
if !reflect.DeepEqual(networkObj.Spec, networkSpec) {
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)
if err != nil {
log.Error(err, "Failed to list Unifi Networks")
return ctrl.Result{}, err
log.Error(err, "Could not list netwrorks")
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 {
networkID := network.ID
seenNetworks[networkID] = true
log.Info(fmt.Sprintf("Searching for %s\n", networkID))
if existing, found := k8sNetworks[networkID]; found {
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))
var networkObject unifiv1.Networkconfiguration
networkObject.Name = network.Name
networkObject.Spec.Name = network.Name
networkObject.Spec.NetworkID = network.ID
networkObject.Spec.IPSubnet = network.IPSubnet
networkObject.Spec.Ipv6InterfaceType = network.IPV6InterfaceType
networkObject.Spec.Ipv6PdAutoPrefixidEnabled = network.IPV6PDAutoPrefixidEnabled
networkObject.Spec.Ipv6RaEnabled = network.IPV6RaEnabled
networkObject.Spec.Ipv6SettingPreference = network.IPV6SettingPreference
networkObject.Spec.Ipv6Subnet = network.IPV6Subnet
networkObject.Spec.Purpose = network.Purpose
networkObject.Spec.Networkgroup = network.NetworkGroup
networkObject.Spec.SettingPreference = network.SettingPreference
networkObject.Spec.VlanEnabled = network.VLANEnabled
// Step 2: Collect zones in fwzCRDs that are NOT in firewall_zones
for _, network := range networkCRDs.Items {
if _, found := networkNamesUnifi[network.Spec.Name]; !found {
err := r.Delete(ctx, &network)
if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
networkNamesCRDs := make(map[string]struct{})
for _, networkCRD := range networkCRDs.Items {
networkNamesCRDs[networkCRD.Spec.Name] = struct{}{}
}
for _, unifinetwork := range networks {
if unifinetwork.Purpose == "corporate" {
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,
}
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 {
if !reflect.DeepEqual(networkCRD.Spec, networkSpec) {
networkCRD.Spec = networkSpec
err := r.Update(ctx, &networkCRD)
if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
if !reflect.DeepEqual(networkCRD.Status, networkStatus) {
networkCRD.Status = networkStatus
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.

View File

@@ -27,7 +27,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
unifiv1beta1 "gitea.engen.priv.no/klauvsteinen/unifi-network-operator/api/v1beta1"
)
var _ = Describe("Networkconfiguration Controller", func() {

View File

@@ -0,0 +1,152 @@
package controller
import (
"context"
"fmt"
"strconv"
"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"
// "sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
// "sigs.k8s.io/controller-runtime/pkg/reconcile"
// "sigs.k8s.io/controller-runtime/pkg/source"
goUnifi "gitea.engen.priv.no/klauvsteinen/go-unifi/unifi"
// unifiv1beta1 "gitea.engen.priv.no/klauvsteinen/unifi-network-operator/api/v1beta1"
"gitea.engen.priv.no/klauvsteinen/unifi-network-operator/internal/config"
"gitea.engen.priv.no/klauvsteinen/unifi-network-operator/internal/unifi"
)
type PortForwardReconciler struct {
client.Client
Scheme *runtime.Scheme
UnifiClient *unifi.UnifiClient
ConfigLoader *config.ConfigLoaderType
}
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=portforwards,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=portforwards/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=portforwards/finalizers,verbs=update
// +kubebuilder:rbac:groups="",resources=services,verbs=list;get;watch
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=list;get;watch
func (r *PortForwardReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
var svc corev1.Service
if err := r.Get(ctx, req.NamespacedName, &svc); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
annotation := svc.Annotations["portforward.unifi.engen.priv.no/forward"]
err := r.UnifiClient.Reauthenticate()
if err != nil {
log.Error(err, "Failed to authenticate to Unifi")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
portforwards, err := r.UnifiClient.Client.ListPortForward(context.Background(), r.UnifiClient.SiteID)
if err != nil {
log.Error(err, "Failed to list PortForfards")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
portforwardnames := make(map[string]int)
for i, portforward := range portforwards {
portforwardnames[portforward.Name] = i
}
if annotation == "" {
for _, port := range svc.Spec.Ports {
portforwardname := "k8s-forward-" + svc.Name + "-" + port.Name
if i, found := portforwardnames[portforwardname]; found {
log.Info(fmt.Sprintf("Cleaning up old portfoward for service %s/%s", svc.Namespace, svc.Name))
if err := r.UnifiClient.Client.DeletePortForward(context.Background(), r.UnifiClient.SiteID, portforwards[i].ID); err != nil {
log.Error(err, "Could not delete portforward")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
return ctrl.Result{}, nil
}
portMap := make(map[string]int)
entries := strings.Split(annotation, ";")
for _, entry := range entries {
entry = strings.TrimSpace(entry)
if entry == "" {
continue
}
parts := strings.Split(entry, ":")
for _, port := range svc.Spec.Ports {
if parts[0] == port.Name {
if len(parts) == 1 {
portMap[parts[0]] = int(port.Port)
} else if len(parts) == 2 {
extPort, err := strconv.Atoi(parts[1])
if err != nil {
log.Error(err, "Invalid external port", "entry", entry)
continue
}
portMap[parts[0]] = extPort
}
}
}
}
if len(svc.Status.LoadBalancer.Ingress) == 0 {
log.Info("No LoadBalancer IP for Service", "service", svc.Name)
return ctrl.Result{}, nil
}
ip := svc.Status.LoadBalancer.Ingress[0].IP
for _, port := range svc.Spec.Ports {
extPort, found := portMap[port.Name]
if found {
log.Info("Setting up port forward",
"externalPort", extPort,
"internalPort", port.Port,
"ip", ip,
"protocol", port.Protocol)
}
portforwardname := "k8s-forward-" + svc.Name + "-" + port.Name
log.Info(fmt.Sprintf("Should handle %s", portforwardname))
if portforwardindex, found := portforwardnames[portforwardname]; found {
if portforwards[portforwardindex].DstPort == fmt.Sprintf("%d", portMap[port.Name]) && portforwards[portforwardindex].Fwd == ip && portforwards[portforwardindex].FwdPort == fmt.Sprintf("%d", port.Port) {
log.Info("Portforward already exists and is correct")
} else {
log.Info("Exists, but need to update")
portforwards[portforwardindex].DstPort = fmt.Sprintf("%d", portMap[port.Name])
portforwards[portforwardindex].FwdPort = fmt.Sprintf("%d", port.Port)
portforwards[portforwardindex].Fwd = ip
if _, err := r.UnifiClient.Client.UpdatePortForward(context.Background(), r.UnifiClient.SiteID, &portforwards[portforwardindex]); err != nil {
log.Error(err, fmt.Sprintf("Failed to update portforward %s", portforwardname))
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
} else {
_, err := r.UnifiClient.Client.CreatePortForward(context.Background(), r.UnifiClient.SiteID, &goUnifi.PortForward{Name: portforwardname, PfwdInterface: "wan", Src: "any", Log: false, DestinationIPs: []goUnifi.PortForwardDestinationIPs{}, Enabled: true, Fwd: ip, DestinationIP: "any", Proto: "tcp", DstPort: fmt.Sprintf("%d", portMap[port.Name]), SiteID: r.UnifiClient.SiteID, FwdPort: fmt.Sprintf("%d", port.Port)})
if err != nil {
log.Error(err, "Portforward could not be created")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
return ctrl.Result{}, nil
}
func (r *PortForwardReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&corev1.Service{}).
WithOptions(controller.Options{MaxConcurrentReconciles: 1}).
Complete(r)
}

View File

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

View File

@@ -32,7 +32,7 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
unifiv1beta1 "gitea.engen.priv.no/klauvsteinen/unifi-network-operator/api/v1beta1"
// +kubebuilder:scaffold:imports
)

View File

@@ -13,7 +13,7 @@ import (
"strings"
"sync"
"github.com/vegardengen/go-unifi/unifi"
"gitea.engen.priv.no/klauvsteinen/go-unifi/unifi"
)
type UnifiClient struct {

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
}

1
riktig Normal file
View File

@@ -0,0 +1 @@
Riktig fil