Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ ifndef TAG
TAG ?= $(shell git rev-parse --short=7 HEAD)
endif
HUB_NET_CONTROLLER_MANAGER_IMAGE_VERSION ?= $(TAG)
HUB_AFD_CONTROLLER_MANAGER_IMAGE_VERSION ?= $(TAG)
MEMBER_NET_CONTROLLER_MANAGER_IMAGE_VERSION ?= $(TAG)
MCS_CONTROLLER_MANAGER_IMAGE_VERSION ?= $(TAG)
NET_CRD_INSTALLER_IMAGE_VERSION ?= $(TAG)

HUB_NET_CONTROLLER_MANAGER_IMAGE_NAME ?= hub-net-controller-manager
# hub-afd-controller-manager is a sibling of hub-net-controller-manager.
# The image name is intentionally distinct: images are baked into distinct
# Deployments (Proposal 001 §7 identity split — see
# cmd/hub-afd-controller-manager/main.go), and mirroring the naming makes
# CVE-scan tooling and release automation treat both binaries symmetrically.
HUB_AFD_CONTROLLER_MANAGER_IMAGE_NAME ?= hub-afd-controller-manager
MEMBER_NET_CONTROLLER_MANAGER_IMAGE_NAME ?= member-net-controller-manager
MCS_CONTROLLER_MANAGER_IMAGE_NAME ?= mcs-controller-manager
NET_CRD_INSTALLER_IMAGE_NAME ?= net-crd-installer
Expand Down Expand Up @@ -183,13 +190,24 @@ generate: $(CONTROLLER_GEN)
.PHONY: build
build: generate fmt vet ## Build binaries.
go build -o bin/hub-net-controller-manager cmd/hub-net-controller-manager/main.go
go build -o bin/hub-afd-controller-manager cmd/hub-afd-controller-manager/main.go
go build -o bin/member-net-controller-manager cmd/member-net-controller-manager/main.go
go build -o bin/mcs-controller-manager cmd/mcs-controller-manager/main.go

.PHONY: run-hub-net-controller-manager
run-hub-net-controller-manager: manifests generate fmt vet ## Run a controllers from your host.
go run ./cmd/hub-net-controller-manager/main.go

# The AFD controller-manager is a separate binary from hub-net-controller-manager
# because Proposal 001 §7 (SFI-NS253) requires the AFD Workload-Identity subject
# to be distinct from the ATM subject. See cmd/hub-afd-controller-manager/main.go
# for the full rationale. This target expects AZURE_TENANT_ID / AZURE_CLIENT_ID /
# AZURE_FEDERATED_TOKEN_FILE / AZURE_SUBSCRIPTION_ID to be set in the environment
# (the binary loads them via pkg/common/azurefrontdoor.LoadConfigFromEnv).
.PHONY: run-hub-afd-controller-manager
run-hub-afd-controller-manager: manifests generate fmt vet ## Run the AFD controller from your host.
go run ./cmd/hub-afd-controller-manager/main.go

.PHONY: run-member-net-controller-manager
run-member-net-controller-manager: manifests generate fmt vet ## Run a controllers from your host.
go run ./cmd/member-net-controller-manager/main.go
Expand All @@ -213,11 +231,11 @@ tidy:

.PHONY: image
image:
$(MAKE) OUTPUT_TYPE="type=docker" docker-build-hub-net-controller-manager docker-build-member-net-controller-manager docker-build-mcs-controller-manager docker-build-net-crd-installer
$(MAKE) OUTPUT_TYPE="type=docker" docker-build-hub-net-controller-manager docker-build-hub-afd-controller-manager docker-build-member-net-controller-manager docker-build-mcs-controller-manager docker-build-net-crd-installer

.PHONY: push
push:
$(MAKE) OUTPUT_TYPE="type=registry" docker-build-hub-net-controller-manager docker-build-member-net-controller-manager docker-build-mcs-controller-manager docker-build-net-crd-installer
$(MAKE) OUTPUT_TYPE="type=registry" docker-build-hub-net-controller-manager docker-build-hub-afd-controller-manager docker-build-member-net-controller-manager docker-build-mcs-controller-manager docker-build-net-crd-installer

# By default, docker buildx create will pull image moby/buildkit:buildx-stable-1 and hit the too many requests error.
.PHONY: docker-buildx-builder
Expand Down Expand Up @@ -253,6 +271,23 @@ docker-build-hub-net-controller-manager: docker-buildx-builder tidy
--build-arg GOARCH=$(TARGET_ARCH) \
--build-arg GOOS=$(TARGET_OS) .

# docker-build-hub-afd-controller-manager mirrors the hub-net target above.
# The two binaries share a base image, distroless runtime, and non-root UID
# so the same CVE / supply-chain policy applies uniformly. Kept as a separate
# target (rather than a matrix over an image list) so operators can iterate
# on just the AFD image during POC without rebuilding the ATM one.
.PHONY: docker-build-hub-afd-controller-manager
docker-build-hub-afd-controller-manager: docker-buildx-builder tidy
docker buildx build \
--file docker/$(HUB_AFD_CONTROLLER_MANAGER_IMAGE_NAME).Dockerfile \
--output=$(OUTPUT_TYPE) \
--platform=$(TARGET_OS)/$(TARGET_ARCH) \
--pull \
--tag $(REGISTRY)/$(HUB_AFD_CONTROLLER_MANAGER_IMAGE_NAME):$(HUB_AFD_CONTROLLER_MANAGER_IMAGE_VERSION) \
--progress=$(BUILDKIT_PROGRESS_TYPE) \
--build-arg GOARCH=$(TARGET_ARCH) \
--build-arg GOOS=$(TARGET_OS) .

.PHONY: docker-build-member-net-controller-manager
docker-build-member-net-controller-manager: docker-buildx-builder tidy
docker buildx build \
Expand Down
23 changes: 23 additions & 0 deletions charts/hub-afd-controller-manager/.helmignore
Original file line number Diff line number Diff line change
@@ -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/
22 changes: 22 additions & 0 deletions charts/hub-afd-controller-manager/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: v2
name: hub-afd-controller-manager
description: |
Helm chart for the fleet-networking Azure Front Door controller-manager on the hub cluster.
This chart is a sibling of the hub-net-controller-manager chart. The two are packaged
separately (rather than as one chart with a feature toggle) because Proposal 001 §7
(SFI-NS253) requires the AFD Workload-Identity federated subject to be distinct from
the ATM subject, which is only enforceable at the Pod boundary. See
docs/first-party/001-afd-global-load-balancing.md §7 and
docs/first-party/003-pre-implementation-checklist.md §2.4.

# Application chart, not a library — this ships templates that render Kubernetes objects.
type: application

# This is the chart version. Bump on any chart or template change.
# Follows Semantic Versioning (https://semver.org/).
version: 0.1.0

# The application version tracks the AFD controller binary version. It intentionally
# matches hub-net-controller-manager's appVersion so release automation can promote
# both binaries together.
appVersion: "v0.1.0"
86 changes: 86 additions & 0 deletions charts/hub-afd-controller-manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# hub-afd-controller-manager

A Helm chart for the fleet-networking **Azure Front Door** controller-manager
on the hub cluster. This chart is a **sibling** of `hub-net-controller-manager`
and must be installed alongside it, not in place of it.

## Why a separate chart?

Proposal 001 §7 (SFI-NS253) requires the AFD controller to run under a
Workload-Identity federated subject that is **distinct** from the
ATM/MCS controller's subject, so ATM-only tenants do not inherit AFD
write permissions on the shared Azure subscription. A Kubernetes pod
projects exactly one WI token, so the identity split is only
enforceable at the Pod boundary — one binary per Deployment, one
ServiceAccount per binary, one AAD federated identity per
ServiceAccount. See:

- `docs/first-party/001-afd-global-load-balancing.md` §7
- `docs/first-party/003-pre-implementation-checklist.md` §2.4
- `cmd/hub-afd-controller-manager/main.go` (package comment)

## Prerequisites

1. **CRDs installed.** This chart does **not** install CRDs. The
`net-crd-installer` job attached to the `hub-net-controller-manager`
chart (or an equivalent) must have applied the following CRDs first:
- `frontdoorprofiles.networking.fleet.azure.com`
- `frontdoorcustomdomains.networking.fleet.azure.com`

The controller startup fails fast if either is missing (see
`cmd/hub-afd-controller-manager/main.go`).

2. **Workload Identity webhook.** The AKS cluster hosting the hub must
have the `azure-workload-identity` mutating webhook enabled (on AKS,
enable the `WorkloadIdentity` feature; on OSS clusters, install the
webhook chart from `Azure/azure-workload-identity`).

3. **Federated AAD identity provisioned.** Before `helm install`, an
operator must:
- Create an AAD app registration (or User-Assigned MI) for AFD.
- Federate it to the hub cluster's OIDC issuer with subject
`system:serviceaccount:<fleetSystemNamespace>:<release>-hub-afd-controller-manager-sa`.
- Grant the identity `Contributor` (or the equivalent least-privilege
`Front Door Domain Contributor` + `Front Door Endpoint Contributor`)
scoped to the resource groups referenced by `FrontDoorProfile.Spec.ResourceGroup`.

> **SFI invariant.** This AAD identity **must not** be the same as
> the identity backing the `hub-net-controller-manager` chart. The
> chart cannot cross-check this — enforcement lives in operator
> tooling (Terraform / `az` CLI).

## Required values

| Value | Description |
| --- | --- |
| `azure.tenantId` | AAD tenant hosting the federated identity. |
| `azure.clientId` | Client ID of the federated AAD app / MI. **Distinct from ATM.** |
| `azure.subscriptionID` | Subscription that owns the AFD profiles. |

`helm install` fails loudly if any of these is empty.

## Coexistence with hub-net-controller-manager

Both charts can (and typically will) be installed into the same
namespace on the same hub. They are designed for peaceful coexistence:

- **Distinct leader-election lease names** (`afd.hub.networking.fleet.azure.com`
vs `2bf2b407.hub.networking.fleet.azure.com`) — no lease contention.
- **Distinct container ports** (metrics `:8082` vs `:8080`, probe `:8083`
vs `:8081`).
- **Distinct ServiceAccounts** with distinct WI annotations — no
identity crossover.
- **Non-overlapping RBAC** — the AFD ClusterRole grants zero verbs on
ATM/MCS CRDs, and vice versa.

## Uninstall

```bash
helm uninstall <release> -n <fleet-system-namespace>
```

Uninstall leaves the CRDs in place (they are shared with the
hub-net-controller-manager install). Front Door profiles that are still
present when the controller pod is removed will keep their finalizers
until either the controller is re-installed or the finalizers are
manually cleared — see `pkg/controllers/hub/frontdoorprofile/controller.go`.
51 changes: 51 additions & 0 deletions charts/hub-afd-controller-manager/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "hub-afd-controller-manager.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "hub-afd-controller-manager.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 "hub-afd-controller-manager.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "hub-afd-controller-manager.labels" -}}
helm.sh/chart: {{ include "hub-afd-controller-manager.chart" . }}
{{ include "hub-afd-controller-manager.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "hub-afd-controller-manager.selectorLabels" -}}
app.kubernetes.io/name: {{ include "hub-afd-controller-manager.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
74 changes: 74 additions & 0 deletions charts/hub-afd-controller-manager/templates/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "hub-afd-controller-manager.fullname" . }}
namespace: {{ .Values.fleetSystemNamespace }}
labels:
{{- include "hub-afd-controller-manager.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "hub-afd-controller-manager.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "hub-afd-controller-manager.selectorLabels" . | nindent 8 }}
# Pod-level label required by the azure-workload-identity webhook —
# without it, no federated token is projected even if the
# ServiceAccount is annotated. Belt-and-braces with the
# ServiceAccount label; both are required in current AKS versions.
azure.workload.identity/use: "true"
spec:
serviceAccountName: {{ include "hub-afd-controller-manager.fullname" . }}-sa
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
- --leader-election-namespace={{ .Values.leaderElectionNamespace }}
- --v={{ .Values.logVerbosity }}
- --add_dir_header
env:
# AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_FEDERATED_TOKEN_FILE
# are auto-injected by the azure-workload-identity mutating webhook
# from the ServiceAccount annotations. AZURE_SUBSCRIPTION_ID is
# NOT part of Workload Identity and must be supplied explicitly.
- name: AZURE_SUBSCRIPTION_ID
value: {{ required "azure.subscriptionID is required" .Values.azure.subscriptionID | quote }}
ports:
# Distinct from hub-net-controller-manager (:8080 / :8081) so
# both binaries can run on the same node without collision when
# hostNetwork is enabled or ports are exposed on a Service.
- name: metrics
containerPort: 8082
protocol: TCP
- name: healthz
containerPort: 8083
protocol: TCP
livenessProbe:
httpGet:
path: /healthz
port: healthz
readinessProbe:
httpGet:
path: /readyz
port: healthz
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{{- if .Values.podDisruptionBudget.enabled }}
{{/*
PodDisruptionBudget so voluntary disruptions (upgrades, node drains,
AKS Automatic NAP-driven scaling) cannot take both replicas down at
once. With replicaCount=2 and minAvailable=1 the deployment tolerates
one voluntary eviction per drain cycle.

Disable via podDisruptionBudget.enabled=false on single-replica installs
(otherwise the PDB blocks all voluntary evictions and drains hang).
*/}}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "hub-afd-controller-manager.fullname" . }}-pdb
namespace: {{ .Values.fleetSystemNamespace }}
labels:
{{- include "hub-afd-controller-manager.labels" . | nindent 4 }}
spec:
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
selector:
matchLabels:
{{- include "hub-afd-controller-manager.selectorLabels" . | nindent 6 }}
{{- end }}
Loading
Loading