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
16 changes: 16 additions & 0 deletions api/v1alpha1/dnszone_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,24 @@ type DNSZoneSpec struct {
// DNSZoneClassName references the DNSZoneClass used to provision this zone.
// +kubebuilder:validation:Required
DNSZoneClassName string `json:"dnsZoneClassName"`

// Visibility selects public publication or private resolver context serving.
// Existing zones default to Public. Changing visibility requires a new zone.
// +kubebuilder:default=Public
// +kubebuilder:validation:Enum=Public;Private
// +kubebuilder:validation:XValidation:rule="oldSelf == self",message="visibility is immutable"
// +optional
Visibility DNSZoneVisibility `json:"visibility,omitempty"`
}

// DNSZoneVisibility identifies the serving boundary of a zone.
type DNSZoneVisibility string

const (
DNSZoneVisibilityPublic DNSZoneVisibility = "Public"
DNSZoneVisibilityPrivate DNSZoneVisibility = "Private"
)

type DomainRefStatus struct {
Nameservers []networkingv1alpha.Nameserver `json:"nameservers,omitempty"`
}
Expand Down
12 changes: 12 additions & 0 deletions config/crd/bases/dns.networking.miloapis.com_dnszones.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ spec:
rule: oldSelf == '' || self == oldSelf
- message: Must have at least two segments separated by dots
rule: self.indexOf('.') != -1
visibility:
default: Public
description: |-
Visibility selects public publication or private resolver context serving.
Existing zones default to Public. Changing visibility requires a new zone.
enum:
- Public
- Private
type: string
x-kubernetes-validations:
- message: visibility is immutable
rule: oldSelf == self
required:
- dnsZoneClassName
- domainName
Expand Down
8 changes: 8 additions & 0 deletions docs/architecture/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ spec:

Namespaced. Models a single domain.

`spec.visibility` accepts `Public` or `Private` and defaults to `Public`.
Visibility is immutable. Create a new zone to change its serving boundary.
Private zones and their records are excluded from public replication, serving,
and discovery. This boundary prepares private zone support; it does not provision
a private resolver or connect a zone to a VPC.

| Field | Type | Description |
|-------|------|-------------|
| `spec.domainName` | string | Required FQDN (e.g. `example.com`). Immutable once set. |
Expand All @@ -62,6 +68,8 @@ metadata:
spec:
domainName: example.com
dnsZoneClassName: powerdns
# Optional. Omitted visibility preserves public DNS behavior.
visibility: Public
```

## DNSRecordSet
Expand Down
3 changes: 3 additions & 0 deletions internal/controller/dnsrecordset_downstream_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ func (r *DNSRecordSetReconciler) Reconcile(ctx context.Context, req ctrl.Request
return ctrl.Result{}, err
}

if zone.Spec.Visibility == dnsv1alpha1.DNSZoneVisibilityPrivate {
return ctrl.Result{}, nil
}
if zone.Spec.DNSZoneClassName == "" || zone.Spec.DNSZoneClassName != r.DNSHandler.Client.Name {
logger.V(1).Info("Resource belongs to a different class. Not Reconciling")
return ctrl.Result{}, nil
Expand Down
3 changes: 3 additions & 0 deletions internal/controller/dnsrecordset_powerdns_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ func (r *DNSRecordSetPowerDNSReconciler) Reconcile(
return reconcile.Result{}, err
}

if zone.Spec.Visibility == dnsv1alpha1.DNSZoneVisibilityPrivate {
return reconcile.Result{}, nil
}
if !zone.DeletionTimestamp.IsZero() {
logger.V(1).Info("zone deleting; skipping")
return reconcile.Result{}, nil
Expand Down
25 changes: 18 additions & 7 deletions internal/controller/dnsrecordset_replicator_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ func (r *DNSRecordSetReplicator) Reconcile(ctx context.Context, req mcreconcile.
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// Resolve visibility before installing public finalizers or creating events.
var zone dnsv1alpha1.DNSZone
zoneErr := upstreamCluster.GetClient().Get(ctx,
types.NamespacedName{Namespace: req.Namespace, Name: upstream.Spec.DNSZoneRef.Name}, &zone)
if zoneErr != nil && !apierrors.IsNotFound(zoneErr) {
return ctrl.Result{}, zoneErr
}
if zoneErr == nil && zone.Spec.Visibility == dnsv1alpha1.DNSZoneVisibilityPrivate {
return ctrl.Result{}, nil
}

// Build a typed clientset from the upstream cluster REST config so we can
// create events.k8s.io/v1 Event objects directly. Event emission is
// best-effort: if clientset construction fails we log and continue.
Expand All @@ -71,8 +82,9 @@ func (r *DNSRecordSetReplicator) Reconcile(ctx context.Context, req mcreconcile.

strategy := downstreamclient.NewMappedNamespaceResourceStrategy(req.ClusterName, upstreamCluster.GetClient(), r.DownstreamClient)

// Ensure upstream finalizer (non-deletion path; replaces webhook defaulter)
if upstream.DeletionTimestamp.IsZero() && !controllerutil.ContainsFinalizer(&upstream, rsFinalizer) {
// Claim the public deletion lifecycle only once zone visibility is known.
// A record can arrive before its private zone in a project control plane.
if zoneErr == nil && upstream.DeletionTimestamp.IsZero() && !controllerutil.ContainsFinalizer(&upstream, rsFinalizer) {
base := upstream.DeepCopy()
controllerutil.AddFinalizer(&upstream, rsFinalizer)
if err := upstreamCluster.GetClient().Patch(ctx, &upstream, client.MergeFrom(base)); err != nil {
Expand Down Expand Up @@ -101,10 +113,10 @@ func (r *DNSRecordSetReplicator) Reconcile(ctx context.Context, req mcreconcile.
// Gate on referenced DNSZone early and update status when missing.
// We fetch the zone before emitting the spec-update event so we can include
// the domain name annotation.
var zone dnsv1alpha1.DNSZone
if err := upstreamCluster.GetClient().Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: upstream.Spec.DNSZoneRef.Name}, &zone); err != nil {
if apierrors.IsNotFound(err) {
if zoneErr != nil {
if apierrors.IsNotFound(zoneErr) {
zoneMsg := fmt.Sprintf("DNSZone %q not found", upstream.Spec.DNSZoneRef.Name)
base := upstream.DeepCopy()
if apimeta.SetStatusCondition(&upstream.Status.Conditions, metav1.Condition{
Type: CondAccepted,
Status: metav1.ConditionFalse,
Expand All @@ -113,15 +125,14 @@ func (r *DNSRecordSetReplicator) Reconcile(ctx context.Context, req mcreconcile.
ObservedGeneration: upstream.Generation,
LastTransitionTime: metav1.NewTime(time.Now()),
}) {
base := upstream.DeepCopy()
if err := upstreamCluster.GetClient().Status().Patch(ctx, &upstream, client.MergeFrom(base)); err != nil {
return ctrl.Result{}, err
}
}
lg.Info("referenced DNSZone not found; marked Accepted=False and exiting early", "dnsZone", upstream.Spec.DNSZoneRef.Name)
return ctrl.Result{}, nil
}
return ctrl.Result{}, err
return ctrl.Result{}, zoneErr
}

// If the zone is being deleted, do not program downstream recordset
Expand Down
3 changes: 3 additions & 0 deletions internal/controller/dnszone_downstream_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ func (r *DNSZoneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
if err := r.Get(ctx, req.NamespacedName, &zone); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if zone.Spec.Visibility == dnsv1alpha1.DNSZoneVisibilityPrivate {
return ctrl.Result{}, nil
}

if zone.Spec.DNSZoneClassName == "" || zone.Spec.DNSZoneClassName != r.DNSHandler.Client.Name {
logger.V(1).Info("Resource belongs to different class. Not Reconciling")
Expand Down
5 changes: 5 additions & 0 deletions internal/controller/dnszone_replicator_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ func (r *DNSZoneReplicator) Reconcile(ctx context.Context, req mcreconcile.Reque
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// Private zones must not claim a public apex or enter public replication.
if upstream.Spec.Visibility == dnsv1alpha1.DNSZoneVisibilityPrivate {
return ctrl.Result{}, nil
}

// Build a typed clientset from the upstream cluster REST config so we can
// create events.k8s.io/v1 Event objects directly. Event emission is
// best-effort: if clientset construction fails we log and continue.
Expand Down
5 changes: 5 additions & 0 deletions internal/controller/dnszonediscovery_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ func (r *DNSZoneDiscoveryReplicator) Reconcile(ctx context.Context, req mcreconc
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// Private zone names must not be sent to public discovery resolvers.
if zone.Spec.Visibility == dnsv1alpha1.DNSZoneVisibilityPrivate {
return ctrl.Result{}, nil
}

// Ensure OwnerReference to referenced DNSZone early
if !metav1.IsControlledBy(&dzd, &zone) {
base := dzd.DeepCopy()
Expand Down
Loading
Loading