Compare commits

...

16 Commits

Author SHA1 Message Date
ea68bed9c2 Add helm
All checks were successful
Build project / build (push) Successful in 4m43s
2025-10-21 07:23:07 +02:00
32a595d160 Merge pull request 'Handle services in same namespace if namespace is not specified' (#34) from feature/properly-handle-namespaced-services into main
All checks were successful
Publish / build (push) Successful in 2m0s
Reviewed-on: #34
2025-06-28 23:14:01 +00:00
834f52c015 Handle services in same namespace if namespace is not specified
All checks were successful
Build project / build (push) Successful in 1m42s
2025-06-29 01:12:26 +02:00
9d43b517e6 Merge pull request 'Properly check for firewallGroup in same namespace' (#33) from feature/properly-handle-firewallgroups-in-same-namespace into main
All checks were successful
Publish / build (push) Successful in 2m27s
Reviewed-on: #33
2025-06-28 23:07:05 +00:00
2b38f08a1a Merge pull request 'feature/cleanly-delete-empty-resources' (#32) from feature/cleanly-delete-empty-resources into main
Some checks failed
Publish / build (push) Has been cancelled
Reviewed-on: #32
2025-06-28 22:59:42 +00:00
3a63034e26 Properly check for firewallGroup in same namespace
All checks were successful
Build project / build (push) Successful in 1m54s
Publish / build (push) Successful in 2m0s
2025-06-29 00:58:57 +02:00
37d8060995 Check for nil ResourcesManaged
All checks were successful
Build project / build (push) Successful in 2m0s
Publish / build (push) Successful in 2m4s
2025-06-29 00:44:49 +02:00
8623d6cbc0 debug
All checks were successful
Build project / build (push) Successful in 1m52s
Publish / build (push) Successful in 2m2s
2025-06-29 00:24:00 +02:00
61606e8a7e debug 2025-06-29 00:23:26 +02:00
44d89a5a50 Check for nil instead of length
Some checks failed
Build project / build (push) Failing after 1m7s
2025-06-29 00:17:44 +02:00
19f24add0a Check for Status field before checking for managed resources
Some checks failed
Build project / build (push) Failing after 1m9s
Publish / build (push) Failing after 1m20s
2025-06-29 00:11:49 +02:00
2e95d29373 Merge pull request 'Check for firewallGroup in same namespace if namespace is not defined' (#31) from feature/default-namespace-same-as-firewallpolicy-namespace-when-referring-firewallgroup into main
All checks were successful
Publish / build (push) Successful in 2m16s
Reviewed-on: #31
2025-06-28 21:56:08 +00:00
990140ee1c Check for firewallGroup in same namespace if namespace is not defined
All checks were successful
Build project / build (push) Successful in 1m51s
2025-06-28 23:53:51 +02:00
24c88264c9 Merge pull request 'Fix namespace matching for firewallgroups in firewallpolicies' (#30) from feature/default-namespace-same-as-firewallpolicy-namespace-when-referring-firewallgroup into main
All checks were successful
Publish / build (push) Successful in 2m1s
Reviewed-on: #30
2025-06-28 16:38:11 +00:00
938d53fa8f Fix namespace matching for firewallgroups in firewallpolicies
All checks were successful
Build project / build (push) Successful in 1m55s
Publish / build (push) Successful in 2m5s
2025-06-28 18:33:54 +02:00
768c61020e Merge pull request 'feature/add-namespace-to-firewallgroup-and-firewallpolicy-resources' (#29) from feature/add-namespace-to-firewallgroup-and-firewallpolicy-resources into main
All checks were successful
Publish / build (push) Successful in 1m58s
Reviewed-on: #29
2025-06-27 15:48:14 +00:00
26 changed files with 2186 additions and 58 deletions

View File

@@ -229,3 +229,57 @@ mv $(1) $(1)-$(3) ;\
} ;\
ln -sf $(1)-$(3) $(1)
endef
##@ Helm
HELM_CHART_DIR ?= helm/unifi-network-operator
HELM_RELEASE_NAME ?= unifi-network-operator
HELM_NAMESPACE ?= unifi-network-operator-system
.PHONY: helm-lint
helm-lint: ## Lint the Helm chart
helm lint $(HELM_CHART_DIR) --set unifi.url="https://test.local" --set unifi.password="test"
.PHONY: helm-template
helm-template: ## Render Helm templates for inspection
helm template $(HELM_RELEASE_NAME) $(HELM_CHART_DIR) \
--namespace $(HELM_NAMESPACE) \
--set unifi.url="https://test.local" \
--set unifi.password="test" \
--debug
.PHONY: helm-install
helm-install: ## Install the Helm chart (requires UNIFI_URL and UNIFI_PASSWORD env vars)
@if [ -z "$(UNIFI_URL)" ]; then echo "Error: UNIFI_URL is not set"; exit 1; fi
@if [ -z "$(UNIFI_PASSWORD)" ]; then echo "Error: UNIFI_PASSWORD is not set"; exit 1; fi
helm install $(HELM_RELEASE_NAME) $(HELM_CHART_DIR) \
--namespace $(HELM_NAMESPACE) \
--create-namespace \
--set unifi.url="$(UNIFI_URL)" \
--set unifi.password="$(UNIFI_PASSWORD)" \
--set unifi.site="$(UNIFI_SITE)" \
--set unifi.username="$(UNIFI_USERNAME)"
.PHONY: helm-upgrade
helm-upgrade: ## Upgrade the Helm release
helm upgrade $(HELM_RELEASE_NAME) $(HELM_CHART_DIR) \
--namespace $(HELM_NAMESPACE)
.PHONY: helm-uninstall
helm-uninstall: ## Uninstall the Helm release
helm uninstall $(HELM_RELEASE_NAME) --namespace $(HELM_NAMESPACE)
.PHONY: helm-package
helm-package: ## Package the Helm chart
helm package $(HELM_CHART_DIR) -d dist/
.PHONY: helm-dry-run
helm-dry-run: ## Dry run Helm installation
@if [ -z "$(UNIFI_URL)" ]; then echo "Error: UNIFI_URL is not set"; exit 1; fi
@if [ -z "$(UNIFI_PASSWORD)" ]; then echo "Error: UNIFI_PASSWORD is not set"; exit 1; fi
helm install $(HELM_RELEASE_NAME) $(HELM_CHART_DIR) \
--namespace $(HELM_NAMESPACE) \
--create-namespace \
--set unifi.url="$(UNIFI_URL)" \
--set unifi.password="$(UNIFI_PASSWORD)" \
--dry-run --debug

298
helm/INSTALL.md Normal file
View File

@@ -0,0 +1,298 @@
# UniFi Network Operator - Helm Installation Guide
## Quick Start
### 1. Install the Helm Chart
The simplest way to install the operator:
```bash
helm install unifi-network-operator ./helm/unifi-network-operator \
--namespace unifi-network-operator-system \
--create-namespace \
--set unifi.url="https://your-unifi-controller:8443" \
--set unifi.password="your-password"
```
### 2. Verify Installation
```bash
# Check if the operator is running
kubectl get pods -n unifi-network-operator-system
# Check the operator logs
kubectl logs -n unifi-network-operator-system -l app.kubernetes.io/name=unifi-network-operator -f
# Verify CRDs are installed
kubectl get crds | grep unifi.engen.priv.no
```
### 3. Create Your First Resource
Create a FirewallZone:
```bash
cat <<EOF | kubectl apply -f -
apiVersion: unifi.engen.priv.no/v1beta1
kind: FirewallZone
metadata:
name: test-zone
namespace: default
spec:
zoneName: "test-zone"
EOF
```
## Production Installation
For production deployments, create a `values.yaml` file:
```yaml
# production-values.yaml
replicaCount: 1
image:
repository: gitea.engen.priv.no/klauvsteinen/unifi-network-operator-controller
tag: "latest"
pullPolicy: IfNotPresent
unifi:
url: "https://unifi.example.com:8443"
site: "default"
username: "operator-user"
# Use existingSecret in production!
existingSecret: "unifi-credentials"
config:
defaultNamespace: "default"
fullSyncZone: "gateway"
fullSyncNetwork: "core"
kubernetesUnifiZone: "kubernetes"
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 50m
memory: 128Mi
metrics:
serviceMonitor:
enabled: true
additionalLabels:
prometheus: kube-prometheus
leaderElection:
enabled: true
nodeSelector:
kubernetes.io/os: linux
tolerations: []
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- unifi-network-operator
topologyKey: kubernetes.io/hostname
```
Create the secret first:
```bash
kubectl create namespace unifi-network-operator-system
kubectl create secret generic unifi-credentials \
--from-literal=UNIFI_URL="https://unifi.example.com:8443" \
--from-literal=UNIFI_SITE="default" \
--from-literal=UNIFI_USERNAME="operator-user" \
--from-literal=UNIFI_PASSWORD="your-secure-password" \
-n unifi-network-operator-system
```
Then install with the values file:
```bash
helm install unifi-network-operator ./helm/unifi-network-operator \
-n unifi-network-operator-system \
-f production-values.yaml
```
## Upgrading
```bash
helm upgrade unifi-network-operator ./helm/unifi-network-operator \
-n unifi-network-operator-system \
-f production-values.yaml
```
## Uninstalling
```bash
# Remove the operator (keeps CRDs and CRs by default)
helm uninstall unifi-network-operator -n unifi-network-operator-system
# To also remove CRDs (this will delete all custom resources!)
kubectl delete crds -l app.kubernetes.io/name=unifi-network-operator
```
## Testing Locally
You can test the chart rendering without installing:
```bash
# Render templates
helm template unifi-network-operator ./helm/unifi-network-operator \
--set unifi.url="https://test.local" \
--set unifi.password="test" \
--debug
# Lint the chart
helm lint ./helm/unifi-network-operator \
--set unifi.url="https://test.local" \
--set unifi.password="test"
# Dry run installation
helm install unifi-network-operator ./helm/unifi-network-operator \
-n unifi-network-operator-system \
--create-namespace \
--set unifi.url="https://test.local" \
--set unifi.password="test" \
--dry-run --debug
```
## Packaging for Distribution
To package the chart for distribution:
```bash
# Package the chart
helm package helm/unifi-network-operator
# This creates: unifi-network-operator-0.1.0.tgz
# Generate index (if hosting a chart repository)
helm repo index .
```
## Common Configuration Scenarios
### Scenario 1: Development Environment
```bash
helm install unifi-network-operator ./helm/unifi-network-operator \
-n unifi-network-operator-system \
--create-namespace \
--set unifi.url="https://192.168.1.1:8443" \
--set unifi.password="admin" \
--set resources.limits.memory="128Mi" \
--set resources.requests.memory="64Mi"
```
### Scenario 2: Multiple Sites
For managing multiple UniFi sites, deploy separate instances:
```bash
# Site 1
helm install unifi-operator-site1 ./helm/unifi-network-operator \
-n unifi-site1 \
--create-namespace \
--set unifi.url="https://unifi-site1.example.com:8443" \
--set unifi.site="site1" \
--set unifi.password="password1"
# Site 2
helm install unifi-operator-site2 ./helm/unifi-network-operator \
-n unifi-site2 \
--create-namespace \
--set unifi.url="https://unifi-site2.example.com:8443" \
--set unifi.site="site2" \
--set unifi.password="password2"
```
### Scenario 3: Using with ArgoCD
Create an ArgoCD Application:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: unifi-network-operator
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/yourusername/unifi-network-operator
targetRevision: main
path: helm/unifi-network-operator
helm:
values: |
unifi:
existingSecret: unifi-credentials
config:
fullSyncZone: "gateway"
fullSyncNetwork: "core"
metrics:
serviceMonitor:
enabled: true
destination:
server: https://kubernetes.default.svc
namespace: unifi-network-operator-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
```
## Troubleshooting
### Operator Won't Start
Check the logs:
```bash
kubectl logs -n unifi-network-operator-system \
-l app.kubernetes.io/name=unifi-network-operator
```
### Connection Issues to UniFi Controller
Verify the secret:
```bash
kubectl get secret -n unifi-network-operator-system
kubectl describe secret unifi-network-operator-unifi \
-n unifi-network-operator-system
```
### CRDs Not Installing
Manually install CRDs:
```bash
kubectl apply -f helm/unifi-network-operator/crds/
```
### Resources Not Syncing
Check operator configuration:
```bash
kubectl get configmap -n unifi-network-operator-system
kubectl describe configmap unifi-network-operator-config \
-n unifi-network-operator-system
```
## Additional Resources
- [Helm Chart README](./unifi-network-operator/README.md)
- [Values Reference](./unifi-network-operator/values.yaml)
- [Custom Resource Examples](../config/samples/)

234
helm/README.md Normal file
View File

@@ -0,0 +1,234 @@
# UniFi Network Operator - Helm Chart
This directory contains the Helm chart for deploying the UniFi Network Operator to Kubernetes.
## Quick Links
- **[Installation Guide](./INSTALL.md)** - Detailed installation instructions and examples
- **[Chart Documentation](./unifi-network-operator/README.md)** - Full configuration reference
- **[Values Reference](./unifi-network-operator/values.yaml)** - All configurable values
## Quick Start
```bash
# Install with minimal configuration
helm install unifi-network-operator ./helm/unifi-network-operator \
--namespace unifi-network-operator-system \
--create-namespace \
--set unifi.url="https://your-unifi-controller:8443" \
--set unifi.password="your-password"
```
## Chart Structure
```
helm/unifi-network-operator/
├── Chart.yaml # Chart metadata
├── values.yaml # Default configuration values
├── README.md # Detailed chart documentation
├── .helmignore # Files to ignore when packaging
├── crds/ # Custom Resource Definitions
│ ├── unifi.engen.priv.no_firewallgroups.yaml
│ ├── unifi.engen.priv.no_firewallpolicies.yaml
│ ├── unifi.engen.priv.no_firewallzones.yaml
│ ├── unifi.engen.priv.no_networkconfigurations.yaml
│ └── unifi.engen.priv.no_portforwards.yaml
└── templates/ # Kubernetes resource templates
├── NOTES.txt # Post-installation notes
├── _helpers.tpl # Template helpers
├── deployment.yaml # Operator deployment
├── serviceaccount.yaml # Service account
├── clusterrole.yaml # Cluster-level permissions
├── clusterrolebinding.yaml
├── role.yaml # Namespace-level permissions
├── rolebinding.yaml
├── configmap.yaml # Operator configuration
├── secret.yaml # UniFi credentials
├── service.yaml # Metrics service
└── servicemonitor.yaml # Prometheus integration
```
## Features
- **Secure by Default**: Runs with restricted security context and non-root user
- **Flexible Configuration**: Extensive values for customization
- **Production Ready**: Leader election, resource limits, health checks
- **Monitoring**: Built-in Prometheus ServiceMonitor support
- **GitOps Friendly**: Works with ArgoCD, Flux, and other GitOps tools
- **Credential Management**: Support for external secrets
## Key Configuration Options
### Required Settings
- `unifi.url` - UniFi controller URL (e.g., `https://unifi.example.com:8443`)
- `unifi.password` - UniFi password (or use `unifi.existingSecret`)
### Common Optional Settings
- `unifi.site` - UniFi site ID (default: `default`)
- `unifi.username` - UniFi username (default: `admin`)
- `config.fullSyncZone` - Zone name for bidirectional sync
- `config.fullSyncNetwork` - Network name for bidirectional sync
- `metrics.serviceMonitor.enabled` - Enable Prometheus monitoring
- `resources.*` - Resource limits and requests
## Using Make Targets
The project Makefile includes helpful Helm targets:
```bash
# Lint the chart
make helm-lint
# Render templates (for debugging)
make helm-template
# Install (requires env vars)
export UNIFI_URL="https://unifi.example.com:8443"
export UNIFI_PASSWORD="your-password"
make helm-install
# Upgrade
make helm-upgrade
# Uninstall
make helm-uninstall
# Package the chart
make helm-package
# Dry run
make helm-dry-run
```
## Examples
### Development Installation
```bash
helm install unifi-network-operator ./helm/unifi-network-operator \
-n unifi-network-operator-system \
--create-namespace \
--set unifi.url="https://192.168.1.1:8443" \
--set unifi.password="admin"
```
### Production with Existing Secret
```bash
# Create secret
kubectl create secret generic unifi-creds \
--from-literal=UNIFI_URL="https://unifi.example.com:8443" \
--from-literal=UNIFI_SITE="default" \
--from-literal=UNIFI_USERNAME="operator" \
--from-literal=UNIFI_PASSWORD="secure-password" \
-n unifi-network-operator-system
# Install with secret reference
helm install unifi-network-operator ./helm/unifi-network-operator \
-n unifi-network-operator-system \
--set unifi.existingSecret="unifi-creds"
```
### With Full Sync and Monitoring
```bash
helm install unifi-network-operator ./helm/unifi-network-operator \
-n unifi-network-operator-system \
--create-namespace \
--set unifi.url="https://unifi.example.com:8443" \
--set unifi.password="password" \
--set config.fullSyncZone="gateway" \
--set config.fullSyncNetwork="core" \
--set metrics.serviceMonitor.enabled=true
```
## Upgrading
To upgrade the operator:
```bash
helm upgrade unifi-network-operator ./helm/unifi-network-operator \
-n unifi-network-operator-system
```
## Uninstalling
```bash
# Remove the operator (CRDs remain)
helm uninstall unifi-network-operator -n unifi-network-operator-system
# Also remove CRDs (WARNING: deletes all custom resources)
kubectl delete crds \
firewallgroups.unifi.engen.priv.no \
firewallpolicies.unifi.engen.priv.no \
firewallzones.unifi.engen.priv.no \
networkconfigurations.unifi.engen.priv.no \
portforwards.unifi.engen.priv.no
```
## Customization
Create a `custom-values.yaml` file:
```yaml
image:
tag: "v1.0.0"
replicaCount: 1
unifi:
existingSecret: "my-unifi-secret"
config:
fullSyncZone: "gateway"
fullSyncNetwork: "core"
kubernetesUnifiZone: "k8s"
resources:
limits:
memory: 256Mi
requests:
memory: 128Mi
metrics:
serviceMonitor:
enabled: true
additionalLabels:
prometheus: kube-prometheus
nodeSelector:
kubernetes.io/os: linux
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
```
Install with:
```bash
helm install unifi-network-operator ./helm/unifi-network-operator \
-n unifi-network-operator-system \
--create-namespace \
-f custom-values.yaml
```
## Documentation
- **[INSTALL.md](./INSTALL.md)** - Complete installation guide with examples
- **[Chart README](./unifi-network-operator/README.md)** - Full configuration reference
- **[values.yaml](./unifi-network-operator/values.yaml)** - Commented default values
## Support
For issues and questions:
- Check the [Installation Guide](./INSTALL.md)
- Review the [Chart Documentation](./unifi-network-operator/README.md)
- Check operator logs: `kubectl logs -n unifi-network-operator-system -l app.kubernetes.io/name=unifi-network-operator`
## License
This Helm chart is provided under the same license as the UniFi Network Operator project.

View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@@ -0,0 +1,16 @@
apiVersion: v2
name: unifi-network-operator
description: A Kubernetes operator for managing UniFi network configurations
type: application
version: 0.1.0
appVersion: "latest"
home: https://github.com/yourusername/unifi-network-operator
maintainers:
- name: Vegar Dengen
keywords:
- unifi
- network
- operator
- firewall
sources:
- https://github.com/yourusername/unifi-network-operator

View File

@@ -0,0 +1,335 @@
# UniFi Network Operator Helm Chart
A Kubernetes operator for managing UniFi network configurations declaratively through Kubernetes Custom Resources.
## Introduction
This Helm chart deploys the UniFi Network Operator on a Kubernetes cluster. The operator enables you to manage UniFi network infrastructure (firewall zones, groups, policies, networks, and port forwards) using Kubernetes resources.
## Prerequisites
- Kubernetes 1.19+
- Helm 3.0+
- Access to a UniFi Network Controller
- UniFi controller credentials (URL, username, password)
## Installing the Chart
To install the chart with the release name `unifi-network-operator`:
```bash
helm install unifi-network-operator ./helm/unifi-network-operator \
--namespace unifi-network-operator-system \
--create-namespace \
--set unifi.url="https://unifi.example.com:8443" \
--set unifi.username="admin" \
--set unifi.password="your-password" \
--set unifi.site="default"
```
## Uninstalling the Chart
To uninstall/delete the `unifi-network-operator` deployment:
```bash
helm uninstall unifi-network-operator -n unifi-network-operator-system
```
This command removes all the Kubernetes components associated with the chart. Note that CRDs are not deleted by default to prevent data loss.
## Configuration
The following table lists the configurable parameters of the UniFi Network Operator chart and their default values.
### General Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `replicaCount` | Number of operator replicas | `1` |
| `image.repository` | Operator image repository | `gitea.engen.priv.no/klauvsteinen/unifi-network-operator-controller` |
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `image.tag` | Image tag (overrides appVersion) | `latest` |
| `imagePullSecrets` | Image pull secrets | `[]` |
| `nameOverride` | Override chart name | `""` |
| `fullnameOverride` | Override full chart name | `""` |
### Service Account Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `serviceAccount.create` | Create service account | `true` |
| `serviceAccount.automount` | Auto-mount service account token | `true` |
| `serviceAccount.annotations` | Service account annotations | `{}` |
| `serviceAccount.name` | Service account name | `""` |
### Security Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `podSecurityContext.runAsNonRoot` | Run as non-root user | `true` |
| `podSecurityContext.seccompProfile.type` | Seccomp profile type | `RuntimeDefault` |
| `securityContext.allowPrivilegeEscalation` | Allow privilege escalation | `false` |
| `securityContext.capabilities.drop` | Dropped capabilities | `["ALL"]` |
### Resource Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `resources.limits.cpu` | CPU limit | `500m` |
| `resources.limits.memory` | Memory limit | `128Mi` |
| `resources.requests.cpu` | CPU request | `10m` |
| `resources.requests.memory` | Memory request | `64Mi` |
### UniFi Controller Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `unifi.url` | UniFi controller URL | `""` (required) |
| `unifi.site` | UniFi site ID | `"default"` |
| `unifi.username` | UniFi username | `"admin"` |
| `unifi.password` | UniFi password | `""` (required) |
| `unifi.existingSecret` | Use existing secret for credentials | `""` |
| `unifi.existingSecretKeys.url` | Key for URL in existing secret | `UNIFI_URL` |
| `unifi.existingSecretKeys.site` | Key for site in existing secret | `UNIFI_SITE` |
| `unifi.existingSecretKeys.username` | Key for username in existing secret | `UNIFI_USERNAME` |
| `unifi.existingSecretKeys.password` | Key for password in existing secret | `UNIFI_PASSWORD` |
### Operator Configuration Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `config.create` | Create ConfigMap for operator config | `true` |
| `config.defaultNamespace` | Default namespace for resources | `"default"` |
| `config.fullSyncZone` | Full sync zone name | `""` |
| `config.fullSyncNetwork` | Full sync network name | `""` |
| `config.kubernetesUnifiZone` | Kubernetes UniFi zone name | `""` |
| `config.existingConfigMap` | Use existing ConfigMap | `""` |
### RBAC Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `rbac.create` | Create RBAC resources | `true` |
### CRD Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `crds.install` | Install CRDs | `true` |
| `crds.keep` | Keep CRDs on uninstall | `true` |
### Service Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `service.enabled` | Enable metrics service | `true` |
| `service.type` | Service type | `ClusterIP` |
| `service.port` | Service port | `8443` |
| `service.annotations` | Service annotations | `{}` |
### Metrics Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `metrics.serviceMonitor.enabled` | Enable Prometheus ServiceMonitor | `false` |
| `metrics.serviceMonitor.additionalLabels` | Additional labels for ServiceMonitor | `{}` |
| `metrics.serviceMonitor.interval` | Scrape interval | `30s` |
| `metrics.serviceMonitor.scrapeTimeout` | Scrape timeout | `10s` |
### Other Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `leaderElection.enabled` | Enable leader election | `true` |
| `nodeSelector` | Node selector | `{}` |
| `tolerations` | Tolerations | `[]` |
| `affinity` | Affinity rules | `{}` |
| `podAnnotations` | Pod annotations | `{"kubectl.kubernetes.io/default-container": "manager"}` |
| `podLabels` | Pod labels | `{"control-plane": "controller-manager"}` |
## Using an Existing Secret
If you prefer to manage the UniFi credentials separately, you can create a secret manually and reference it:
```bash
kubectl create secret generic my-unifi-secret \
--from-literal=UNIFI_URL="https://unifi.example.com:8443" \
--from-literal=UNIFI_SITE="default" \
--from-literal=UNIFI_USERNAME="admin" \
--from-literal=UNIFI_PASSWORD="your-password" \
-n unifi-network-operator-system
```
Then install the chart with:
```bash
helm install unifi-network-operator ./helm/unifi-network-operator \
--namespace unifi-network-operator-system \
--create-namespace \
--set unifi.existingSecret="my-unifi-secret"
```
## Examples
### Basic Installation
```bash
helm install unifi-network-operator ./helm/unifi-network-operator \
-n unifi-network-operator-system \
--create-namespace \
--set unifi.url="https://192.168.1.1:8443" \
--set unifi.password="mypassword"
```
### Installation with Custom Configuration
```bash
helm install unifi-network-operator ./helm/unifi-network-operator \
-n unifi-network-operator-system \
--create-namespace \
--set unifi.url="https://unifi.example.com:8443" \
--set unifi.username="operator" \
--set unifi.password="secure-password" \
--set unifi.site="main" \
--set config.defaultNamespace="production" \
--set config.fullSyncZone="gateway" \
--set config.fullSyncNetwork="core" \
--set resources.limits.memory="256Mi" \
--set metrics.serviceMonitor.enabled=true
```
### Using a Values File
Create a `my-values.yaml` file:
```yaml
unifi:
url: "https://unifi.example.com:8443"
username: "operator"
password: "my-secure-password"
site: "default"
config:
defaultNamespace: "default"
fullSyncZone: "gateway"
fullSyncNetwork: "core"
resources:
limits:
memory: 256Mi
requests:
memory: 128Mi
metrics:
serviceMonitor:
enabled: true
additionalLabels:
prometheus: kube-prometheus
```
Install with:
```bash
helm install unifi-network-operator ./helm/unifi-network-operator \
-n unifi-network-operator-system \
--create-namespace \
-f my-values.yaml
```
## Custom Resources
After installing the operator, you can create the following custom resources:
### FirewallZone
```yaml
apiVersion: unifi.engen.priv.no/v1beta1
kind: FirewallZone
metadata:
name: my-zone
spec:
zoneName: "my-zone"
```
### FirewallGroup
```yaml
apiVersion: unifi.engen.priv.no/v1beta1
kind: FirewallGroup
metadata:
name: web-servers
spec:
addresses:
- "10.0.1.100/32"
- "10.0.1.101/32"
ports:
- "80/tcp"
- "443/tcp"
```
### FirewallPolicy
```yaml
apiVersion: unifi.engen.priv.no/v1beta1
kind: FirewallPolicy
metadata:
name: allow-web
spec:
sourceZone: "wan"
destinationGroup: "web-servers"
```
### Networkconfiguration
```yaml
apiVersion: unifi.engen.priv.no/v1beta1
kind: Networkconfiguration
metadata:
name: vlan10
spec:
networkName: "VLAN10"
```
## Upgrading
To upgrade the operator to a new version:
```bash
helm upgrade unifi-network-operator ./helm/unifi-network-operator \
-n unifi-network-operator-system
```
## Troubleshooting
### Check Operator Logs
```bash
kubectl logs -n unifi-network-operator-system -l app.kubernetes.io/name=unifi-network-operator -f
```
### Check Operator Status
```bash
kubectl get deployment -n unifi-network-operator-system
kubectl get pods -n unifi-network-operator-system
```
### Verify CRDs are Installed
```bash
kubectl get crds | grep unifi.engen.priv.no
```
### Common Issues
1. **Authentication Failures**: Verify your UniFi credentials and URL are correct
2. **CRD Not Found**: Ensure CRDs are installed with `crds.install=true`
3. **Operator Not Starting**: Check resource limits and image pull secrets
## License
This chart is provided as-is under the same license as the UniFi Network Operator project.
## Support
For issues and questions, please refer to the project repository.

View File

@@ -0,0 +1,187 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.2
name: firewallgroups.unifi.engen.priv.no
spec:
group: unifi.engen.priv.no
names:
kind: FirewallGroup
listKind: FirewallGroupList
plural: firewallgroups
singular: firewallgroup
scope: Namespaced
versions:
- name: v1beta1
schema:
openAPIV3Schema:
description: FirewallGroup is the Schema for the firewallgroups 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:
auto_created_from:
properties:
name:
type: string
namespace:
type: string
type: object
autoIncludeSelector:
description: AutoIncludeSelector defines which services to extract
addresses from
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements.
The requirements are ANDed.
items:
description: |-
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector applies
to.
type: string
operator:
description: |-
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: |-
values is an array of string values. If the operator is In or NotIn,
the values array must be non-empty. If the operator is Exists or DoesNotExist,
the values array must be empty. This array is replaced during a strategic
merge patch.
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
description: |-
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions, whose key field is "key", the
operator is "In", and the values array contains only "value". The requirements are ANDed.
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:
type: string
type: object
status:
description: FirewallGroupStatus defines the observed state of FirewallGroup.
properties:
lastSyncTime:
description: LastSyncTime is the last time the object was synced
format: date-time
type: string
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
type: boolean
type: object
type: object
served: true
storage: true
subresources:
status: {}

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

@@ -0,0 +1,75 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.2
name: firewallzones.unifi.engen.priv.no
spec:
group: unifi.engen.priv.no
names:
kind: FirewallZone
listKind: FirewallZoneList
plural: firewallzones
singular: firewallzone
scope: Namespaced
versions:
- name: v1beta1
schema:
openAPIV3Schema:
description: FirewallZone is the Schema for the firewallzones API.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: FirewallZoneSpec defines the desired state of FirewallZone.
properties:
_id:
type: string
default_zone:
type: boolean
name:
type: string
network_ids:
items:
type: string
type: array
zone_key:
type: string
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
storage: true
subresources:
status: {}

View File

@@ -0,0 +1,117 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.2
name: networkconfigurations.unifi.engen.priv.no
spec:
group: unifi.engen.priv.no
names:
kind: Networkconfiguration
listKind: NetworkconfigurationList
plural: networkconfigurations
singular: networkconfiguration
scope: Namespaced
versions:
- name: v1beta1
schema:
openAPIV3Schema:
description: Networkconfiguration is the Schema for the networkconfigurations
API.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: NetworkconfigurationSpec defines the desired state of Networkconfiguration.
properties:
_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
gateway_type:
type: string
ip_subnet:
type: string
ipv6_interface_type:
type: string
ipv6_pd_auto_prefixid_enabled:
type: boolean
ipv6_ra_enabled:
type: boolean
ipv6_setting_preference:
type: string
ipv6_subnet:
type: string
name:
type: string
networkgroup:
type: string
purpose:
type: string
setting_preference:
type: string
vlan:
format: int64
type: integer
vlan_enabled:
type: boolean
required:
- name
type: object
status:
description: NetworkconfigurationStatus defines the observed state of
Networkconfiguration.
properties:
firewall_zone_id:
description: |-
INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
Important: Run "make" to regenerate code after modifying this file
type: string
ipv6_subnet_status:
type: string
lastSyncTime:
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
type: boolean
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -0,0 +1,49 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.2
name: portforwards.unifi.engen.priv.no
spec:
group: unifi.engen.priv.no
names:
kind: PortForward
listKind: PortForwardList
plural: portforwards
singular: portforward
scope: Namespaced
versions:
- name: v1beta1
schema:
openAPIV3Schema:
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: |-
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:
type: object
status:
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -0,0 +1,49 @@
Thank you for installing {{ .Chart.Name }}!
Your release is named {{ .Release.Name }}.
The UniFi Network Operator has been deployed to namespace: {{ .Release.Namespace }}
To learn more about the release, try:
$ helm status {{ .Release.Name }} -n {{ .Release.Namespace }}
$ helm get all {{ .Release.Name }} -n {{ .Release.Namespace }}
{{- if not .Values.unifi.existingSecret }}
IMPORTANT: Make sure to configure your UniFi controller credentials properly.
The operator requires the following environment variables to be set:
- UNIFI_URL: {{ .Values.unifi.url }}
- UNIFI_SITE: {{ .Values.unifi.site }}
- UNIFI_USER: {{ .Values.unifi.username }}
- UNIFI_PASSWORD: [CONFIGURED]
{{- end }}
{{- if .Values.config.create }}
Operator configuration has been created with:
{{- if .Values.config.defaultNamespace }}
- Default Namespace: {{ .Values.config.defaultNamespace }}
{{- end }}
{{- if .Values.config.fullSyncZone }}
- Full Sync Zone: {{ .Values.config.fullSyncZone }}
{{- end }}
{{- if .Values.config.fullSyncNetwork }}
- Full Sync Network: {{ .Values.config.fullSyncNetwork }}
{{- end }}
{{- if .Values.config.kubernetesUnifiZone }}
- Kubernetes UniFi Zone: {{ .Values.config.kubernetesUnifiZone }}
{{- end }}
{{- end }}
To get the operator logs:
$ kubectl logs -n {{ .Release.Namespace }} -l {{ include "unifi-network-operator.selectorLabels" . | replace "\n" "," }} -f
Next steps:
1. Create FirewallZone resources to manage UniFi firewall zones
2. Create FirewallGroup resources to group IP addresses and ports
3. Create FirewallPolicy resources to define firewall rules
4. Create Networkconfiguration resources to manage network settings
5. Annotate Services for port forwarding
For more information, visit: {{ .Chart.Home }}

View File

@@ -0,0 +1,83 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "unifi-network-operator.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "unifi-network-operator.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "unifi-network-operator.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "unifi-network-operator.labels" -}}
helm.sh/chart: {{ include "unifi-network-operator.chart" . }}
{{ include "unifi-network-operator.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "unifi-network-operator.selectorLabels" -}}
app.kubernetes.io/name: {{ include "unifi-network-operator.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
control-plane: controller-manager
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "unifi-network-operator.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "unifi-network-operator.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Create the name of the secret to use
*/}}
{{- define "unifi-network-operator.secretName" -}}
{{- if .Values.unifi.existingSecret }}
{{- .Values.unifi.existingSecret }}
{{- else }}
{{- include "unifi-network-operator.fullname" . }}-unifi
{{- end }}
{{- end }}
{{/*
Create the name of the configmap to use
*/}}
{{- define "unifi-network-operator.configMapName" -}}
{{- if .Values.config.existingConfigMap }}
{{- .Values.config.existingConfigMap }}
{{- else }}
{{- include "unifi-network-operator.fullname" . }}-config
{{- end }}
{{- end }}

View File

@@ -0,0 +1,56 @@
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "unifi-network-operator.fullname" . }}-manager-role
labels:
{{- include "unifi-network-operator.labels" . | nindent 4 }}
rules:
- apiGroups:
- ""
resources:
- configmaps
- services
verbs:
- get
- list
- watch
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallgroups
- firewallpolicies
- firewallzones
- networkconfigurations
- portforwards
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallgroups/finalizers
- firewallpolicies/finalizers
- firewallzones/finalizers
- networkconfigurations/finalizers
- portforwards/finalizers
verbs:
- update
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallgroups/status
- firewallpolicies/status
- firewallzones/status
- networkconfigurations/status
- portforwards/status
verbs:
- get
- patch
- update
{{- end }}

View File

@@ -0,0 +1,16 @@
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "unifi-network-operator.fullname" . }}-manager-rolebinding
labels:
{{- include "unifi-network-operator.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "unifi-network-operator.fullname" . }}-manager-role
subjects:
- kind: ServiceAccount
name: {{ include "unifi-network-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}

View File

@@ -0,0 +1,22 @@
{{- if .Values.config.create -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "unifi-network-operator.configMapName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "unifi-network-operator.labels" . | nindent 4 }}
data:
{{- if .Values.config.defaultNamespace }}
defaultNamespace: {{ .Values.config.defaultNamespace | quote }}
{{- end }}
{{- if .Values.config.fullSyncZone }}
fullSyncZone: {{ .Values.config.fullSyncZone | quote }}
{{- end }}
{{- if .Values.config.fullSyncNetwork }}
fullSyncNetwork: {{ .Values.config.fullSyncNetwork | quote }}
{{- end }}
{{- if .Values.config.kubernetesUnifiZone }}
kubernetesUnifiZone: {{ .Values.config.kubernetesUnifiZone | quote }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,82 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "unifi-network-operator.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "unifi-network-operator.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "unifi-network-operator.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "unifi-network-operator.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "unifi-network-operator.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: manager
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
{{- if .Values.leaderElection.enabled }}
- --leader-elect
{{- end }}
- --health-probe-bind-address=:8081
env:
- name: UNIFI_URL
valueFrom:
secretKeyRef:
name: {{ include "unifi-network-operator.secretName" . }}
key: {{ .Values.unifi.existingSecretKeys.url }}
- name: UNIFI_SITE
valueFrom:
secretKeyRef:
name: {{ include "unifi-network-operator.secretName" . }}
key: {{ .Values.unifi.existingSecretKeys.site }}
- name: UNIFI_USER
valueFrom:
secretKeyRef:
name: {{ include "unifi-network-operator.secretName" . }}
key: {{ .Values.unifi.existingSecretKeys.username }}
- name: UNIFI_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "unifi-network-operator.secretName" . }}
key: {{ .Values.unifi.existingSecretKeys.password }}
securityContext:
{{- toYaml .Values.securityContext | nindent 10 }}
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 10 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 10 }}
resources:
{{- toYaml .Values.resources | nindent 10 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
terminationGracePeriodSeconds: 10

View File

@@ -0,0 +1,41 @@
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "unifi-network-operator.fullname" . }}-leader-election-role
namespace: {{ .Release.Namespace }}
labels:
{{- include "unifi-network-operator.labels" . | nindent 4 }}
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
{{- end }}

View File

@@ -0,0 +1,17 @@
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "unifi-network-operator.fullname" . }}-leader-election-rolebinding
namespace: {{ .Release.Namespace }}
labels:
{{- include "unifi-network-operator.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "unifi-network-operator.fullname" . }}-leader-election-role
subjects:
- kind: ServiceAccount
name: {{ include "unifi-network-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}

View File

@@ -0,0 +1,15 @@
{{- if not .Values.unifi.existingSecret -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "unifi-network-operator.fullname" . }}-unifi
namespace: {{ .Release.Namespace }}
labels:
{{- include "unifi-network-operator.labels" . | nindent 4 }}
type: Opaque
stringData:
{{ .Values.unifi.existingSecretKeys.url }}: {{ .Values.unifi.url | required "unifi.url is required when not using an existing secret" | quote }}
{{ .Values.unifi.existingSecretKeys.site }}: {{ .Values.unifi.site | quote }}
{{ .Values.unifi.existingSecretKeys.username }}: {{ .Values.unifi.username | quote }}
{{ .Values.unifi.existingSecretKeys.password }}: {{ .Values.unifi.password | required "unifi.password is required when not using an existing secret" | quote }}
{{- end }}

View File

@@ -0,0 +1,22 @@
{{- if .Values.service.enabled -}}
apiVersion: v1
kind: Service
metadata:
name: {{ include "unifi-network-operator.fullname" . }}-metrics
namespace: {{ .Release.Namespace }}
labels:
{{- include "unifi-network-operator.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
ports:
- name: https
port: {{ .Values.service.port }}
targetPort: 8443
protocol: TCP
selector:
{{- include "unifi-network-operator.selectorLabels" . | nindent 4 }}
{{- end }}

View File

@@ -0,0 +1,14 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "unifi-network-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "unifi-network-operator.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
{{- end }}

View File

@@ -0,0 +1,24 @@
{{- if .Values.metrics.serviceMonitor.enabled -}}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "unifi-network-operator.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "unifi-network-operator.labels" . | nindent 4 }}
{{- with .Values.metrics.serviceMonitor.additionalLabels }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
endpoints:
- interval: {{ .Values.metrics.serviceMonitor.interval }}
path: /metrics
port: https
scheme: https
scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }}
tlsConfig:
insecureSkipVerify: true
selector:
matchLabels:
{{- include "unifi-network-operator.selectorLabels" . | nindent 6 }}
{{- end }}

View File

@@ -0,0 +1,159 @@
# Default values for unifi-network-operator
# -- Number of replicas for the operator deployment
replicaCount: 1
image:
# -- Container image repository
repository: gitea.engen.priv.no/klauvsteinen/unifi-network-operator-controller
# -- Image pull policy
pullPolicy: IfNotPresent
# -- Overrides the image tag whose default is the chart appVersion
tag: "latest"
# -- Image pull secrets for private registries
imagePullSecrets: []
# -- Override the name of the chart
nameOverride: ""
# -- Override the full name of the chart
fullnameOverride: ""
serviceAccount:
# -- Specifies whether a service account should be created
create: true
# -- Automatically mount a ServiceAccount's API credentials
automount: true
# -- Annotations to add to the service account
annotations: {}
# -- The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
# -- Annotations to add to the pod
podAnnotations:
kubectl.kubernetes.io/default-container: manager
# -- Labels to add to the pod
podLabels:
control-plane: controller-manager
podSecurityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
service:
# -- Enable metrics service
enabled: true
# -- Service type
type: ClusterIP
# -- Metrics port
port: 8443
# -- Annotations to add to the service
annotations: {}
resources:
limits:
# -- CPU limit
cpu: 500m
# -- Memory limit
memory: 128Mi
requests:
# -- CPU request
cpu: 10m
# -- Memory request
memory: 64Mi
livenessProbe:
httpGet:
path: /healthz
port: 8081
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /readyz
port: 8081
initialDelaySeconds: 5
periodSeconds: 10
# -- Node selector for pod assignment
nodeSelector: {}
# -- Tolerations for pod assignment
tolerations: []
# -- Affinity for pod assignment
affinity: {}
# Leader election configuration
leaderElection:
# -- Enable leader election for high availability
enabled: true
# UniFi controller configuration
unifi:
# -- UniFi controller URL (e.g., https://unifi.example.com:8443)
url: ""
# -- UniFi site ID (e.g., default)
site: "default"
# -- UniFi username
username: "admin"
# -- UniFi password (leave empty to use existing secret)
password: ""
# -- Use existing secret for UniFi credentials
# If set, the chart will not create a secret
existingSecret: ""
# -- Keys in the existing secret for UniFi credentials
existingSecretKeys:
url: UNIFI_URL
site: UNIFI_SITE
username: UNIFI_USERNAME
password: UNIFI_PASSWORD
# Operator configuration
config:
# -- Create a ConfigMap for operator configuration
create: true
# -- Default namespace for resources
defaultNamespace: "default"
# -- Full sync zone name (zone for bidirectional sync)
fullSyncZone: ""
# -- Full sync network name (network for bidirectional sync)
fullSyncNetwork: ""
# -- Kubernetes UniFi zone name
kubernetesUnifiZone: ""
# -- Use existing ConfigMap for operator configuration
existingConfigMap: ""
# CRD configuration
crds:
# -- Install CRDs as part of the Helm chart
install: true
# -- Keep CRDs on chart uninstall
keep: true
# RBAC configuration
rbac:
# -- Create RBAC resources
create: true
# Metrics configuration
metrics:
# -- Enable Prometheus ServiceMonitor
serviceMonitor:
enabled: false
# -- Additional labels for the ServiceMonitor
additionalLabels: {}
# -- Scrape interval
interval: 30s
# -- Scrape timeout
scrapeTimeout: 10s

View File

@@ -377,7 +377,7 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") || strings.Contains(msg,"invalid character") {
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"
@@ -417,7 +417,7 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") || strings.Contains(msg,"invalid character") {
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"
@@ -457,7 +457,7 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") || strings.Contains(msg,"invalid character") {
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"
@@ -497,7 +497,7 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.R
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") || strings.Contains(msg,"invalid character") {
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"

View File

@@ -125,70 +125,72 @@ func (r *FirewallPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Reque
}
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 firewallPolicy.Status.ResourcesManaged != nil {
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 {
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 {
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 {
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
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
}
}
}
}
@@ -285,14 +287,14 @@ func (r *FirewallPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Reque
// 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
namespace := firewallPolicy.Namespace
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
namespace := firewallPolicy.Namespace
if len(dest_service.Namespace) > 0 {
namespace = dest_service.Namespace
}
@@ -310,7 +312,7 @@ func (r *FirewallPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Reque
// 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)) {
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 == firewallGroup.Namespace)) {
myFirewallGroups = append(myFirewallGroups, firewallGroup)
} else if _, found := destination_groups[firewallGroup.Namespace+"/"+firewallGroup.Name]; found {
myFirewallGroups = append(myFirewallGroups, firewallGroup)
@@ -340,7 +342,7 @@ func (r *FirewallPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Reque
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 {
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 == service.Namespace)) && !skipService {
myServices = append(myServices, service)
} else if _, found := destination_services[service.Namespace+"/"+service.Name]; found && !skipService {
myServices = append(myServices, service)