Initial bind-operator: 9 CRDs + controllers
Implements a Kubernetes operator that manages fleets of BIND9 servers declaratively, using controller-runtime (matching forgebot conventions). - add BindCluster reconciler: StatefulSet (pod-0 primary, secondaries), headless + client Services, rendered named.conf ConfigMap, TSIG keys Secret and rndc control Secret; watches dependent CRs to re-render - add BindTSIGKey reconciler that generates key material into a Secret - add BindZone/DNSRecord reconcilers using fully-dynamic delivery (rndc addzone + TSIG nsupdate against the primary pod) - add BindCatalogZone reconciler so secondaries auto-provision zones - add BindPolicy (RPZ), BindDNSSECPolicy, BindView, BindACL reconcilers - render primary/secondary named.conf variants selected by pod ordinal - generate CRDs, deepcopy and RBAC; add samples mapping the three Puppet roles (authoritative/resolver/external-dns) to three BindClusters - add Makefile, Dockerfile.operator, Woodpecker CI and kind manifests
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// BindACLSpec defines a reusable named address_match_list.
|
||||
type BindACLSpec struct {
|
||||
// ClusterRef names the BindCluster whose named.conf this ACL is rendered
|
||||
// into. When empty the ACL is available to every cluster in the namespace.
|
||||
// +optional
|
||||
ClusterRef string `json:"clusterRef,omitempty"`
|
||||
|
||||
// Entries are raw BIND address-match-list elements, e.g. "10.0.0.0/8",
|
||||
// "!192.168.1.5", "key transfer-key", "localhost", "any", or the name of
|
||||
// another ACL.
|
||||
// +kubebuilder:validation:MinItems=1
|
||||
Entries []string `json:"entries"`
|
||||
}
|
||||
|
||||
// BindACLStatus reports observed ACL state.
|
||||
type BindACLStatus struct {
|
||||
// +optional
|
||||
Ready bool `json:"ready,omitempty"`
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// +optional
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:shortName=bacl
|
||||
// +kubebuilder:printcolumn:name="Cluster",type=string,JSONPath=`.spec.clusterRef`
|
||||
// +kubebuilder:printcolumn:name="Entries",type=integer,JSONPath=`.spec.entries[*]`
|
||||
// +kubebuilder:printcolumn:name="Ready",type=boolean,JSONPath=`.status.ready`
|
||||
|
||||
// BindACL is a named address-match-list referenced by views, zones and
|
||||
// policies for match-clients / allow-query / allow-transfer / allow-update.
|
||||
type BindACL struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec BindACLSpec `json:"spec,omitempty"`
|
||||
Status BindACLStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// BindACLList contains a list of BindACL.
|
||||
type BindACLList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []BindACL `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&BindACL{}, &BindACLList{})
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// BindCatalogZoneSpec defines a BIND9 catalog zone. The primary publishes it
|
||||
// and secondaries consume it, so member zones are provisioned onto every
|
||||
// secondary automatically without per-zone reconfiguration.
|
||||
type BindCatalogZoneSpec struct {
|
||||
// ClusterRef names the owning BindCluster.
|
||||
ClusterRef string `json:"clusterRef"`
|
||||
|
||||
// ZoneName is the catalog zone's own origin, e.g. "catalog.internal".
|
||||
ZoneName string `json:"zoneName"`
|
||||
|
||||
// DefaultPrimaries are the addresses member zones point at on secondaries.
|
||||
// Defaults to the cluster primary Service.
|
||||
// +optional
|
||||
DefaultPrimaries []string `json:"defaultPrimaries,omitempty"`
|
||||
|
||||
// TransferKeyRef names the BindTSIGKey authenticating catalog + member zone
|
||||
// transfers to secondaries.
|
||||
// +optional
|
||||
TransferKeyRef string `json:"transferKeyRef,omitempty"`
|
||||
}
|
||||
|
||||
// BindCatalogZoneStatus reports observed catalog state.
|
||||
type BindCatalogZoneStatus struct {
|
||||
// +optional
|
||||
Ready bool `json:"ready,omitempty"`
|
||||
// MemberCount is the number of member zones registered in the catalog.
|
||||
// +optional
|
||||
MemberCount int32 `json:"memberCount,omitempty"`
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// +optional
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:shortName=bcz
|
||||
// +kubebuilder:printcolumn:name="Cluster",type=string,JSONPath=`.spec.clusterRef`
|
||||
// +kubebuilder:printcolumn:name="Zone",type=string,JSONPath=`.spec.zoneName`
|
||||
// +kubebuilder:printcolumn:name="Members",type=integer,JSONPath=`.status.memberCount`
|
||||
// +kubebuilder:printcolumn:name="Ready",type=boolean,JSONPath=`.status.ready`
|
||||
|
||||
// BindCatalogZone auto-provisions member zones onto cluster secondaries.
|
||||
type BindCatalogZone struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec BindCatalogZoneSpec `json:"spec,omitempty"`
|
||||
Status BindCatalogZoneStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// BindCatalogZoneList contains a list of BindCatalogZone.
|
||||
type BindCatalogZoneList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []BindCatalogZone `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&BindCatalogZone{}, &BindCatalogZoneList{})
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// BindMode selects the behaviour of a BindCluster and maps onto a classic
|
||||
// BIND deployment role.
|
||||
// - authoritative: serves signed/unsigned authoritative zones. Ordinal-0 is
|
||||
// the primary that holds zone data; the remaining pods are secondaries that
|
||||
// replicate via AXFR/IXFR + NOTIFY (optionally driven by a catalog zone).
|
||||
// - resolver: N identical recursive resolvers, no zone replication.
|
||||
// - dynamic: like authoritative, but the primary accepts RFC2136 TSIG updates
|
||||
// (the external-dns pattern); secondaries replicate the result.
|
||||
//
|
||||
// +kubebuilder:validation:Enum=authoritative;resolver;dynamic
|
||||
type BindMode string
|
||||
|
||||
const (
|
||||
ModeAuthoritative BindMode = "authoritative"
|
||||
ModeResolver BindMode = "resolver"
|
||||
ModeDynamic BindMode = "dynamic"
|
||||
)
|
||||
|
||||
// ClusterServiceSpec controls how the cluster is exposed to clients.
|
||||
type ClusterServiceSpec struct {
|
||||
// Type of the client-facing Service. Defaults to ClusterIP.
|
||||
// +kubebuilder:validation:Enum=ClusterIP;LoadBalancer;NodePort
|
||||
// +optional
|
||||
Type corev1.ServiceType `json:"type,omitempty"`
|
||||
|
||||
// LoadBalancerIP requests a specific address when Type is LoadBalancer.
|
||||
// +optional
|
||||
LoadBalancerIP string `json:"loadBalancerIP,omitempty"`
|
||||
|
||||
// Annotations added to the client-facing Service (e.g. PureLB/MetalLB hints).
|
||||
// +optional
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// BindClusterSpec defines the desired state of a BIND cluster.
|
||||
type BindClusterSpec struct {
|
||||
// Mode selects the cluster role.
|
||||
// +kubebuilder:default=authoritative
|
||||
Mode BindMode `json:"mode"`
|
||||
|
||||
// Replicas is the number of BIND pods. Ordinal-0 is the primary for the
|
||||
// authoritative and dynamic modes.
|
||||
// +kubebuilder:default=3
|
||||
// +kubebuilder:validation:Minimum=1
|
||||
// +optional
|
||||
Replicas int32 `json:"replicas,omitempty"`
|
||||
|
||||
// Image is the BIND9 container image.
|
||||
// +kubebuilder:default="git.unkin.net/unkin/bind9:latest"
|
||||
// +optional
|
||||
Image string `json:"image,omitempty"`
|
||||
|
||||
// ImagePullPolicy for the BIND container.
|
||||
// +optional
|
||||
ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"`
|
||||
|
||||
// Recursion overrides the default per-mode recursion setting. When nil,
|
||||
// resolver mode enables recursion and the other modes disable it.
|
||||
// +optional
|
||||
Recursion *bool `json:"recursion,omitempty"`
|
||||
|
||||
// Forwarders is a list of upstream resolvers used by resolver mode (and any
|
||||
// forward zones that do not specify their own).
|
||||
// +optional
|
||||
Forwarders []string `json:"forwarders,omitempty"`
|
||||
|
||||
// AllowNewZones enables the rndc addzone/delzone control path required for
|
||||
// dynamic zone provisioning. Defaults to true.
|
||||
// +kubebuilder:default=true
|
||||
// +optional
|
||||
AllowNewZones *bool `json:"allowNewZones,omitempty"`
|
||||
|
||||
// CatalogZoneRef names a BindCatalogZone that secondaries consume so member
|
||||
// zones are auto-provisioned without per-zone reconfiguration.
|
||||
// +optional
|
||||
CatalogZoneRef string `json:"catalogZoneRef,omitempty"`
|
||||
|
||||
// ExtraOptions are raw named.conf `options { ... }` lines appended verbatim.
|
||||
// +optional
|
||||
ExtraOptions []string `json:"extraOptions,omitempty"`
|
||||
|
||||
// StorageClassName for the per-pod PVC that holds zone data and journals.
|
||||
// +optional
|
||||
StorageClassName *string `json:"storageClassName,omitempty"`
|
||||
|
||||
// StorageSize for the per-pod PVC. Defaults to 1Gi.
|
||||
// +kubebuilder:default="1Gi"
|
||||
// +optional
|
||||
StorageSize string `json:"storageSize,omitempty"`
|
||||
|
||||
// Resources for the BIND container.
|
||||
// +optional
|
||||
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
|
||||
|
||||
// Service controls how the cluster is exposed.
|
||||
// +optional
|
||||
Service ClusterServiceSpec `json:"service,omitempty"`
|
||||
|
||||
// NodeSelector for the BIND pods.
|
||||
// +optional
|
||||
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
|
||||
|
||||
// Tolerations for the BIND pods.
|
||||
// +optional
|
||||
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
|
||||
|
||||
// Affinity for the BIND pods.
|
||||
// +optional
|
||||
Affinity *corev1.Affinity `json:"affinity,omitempty"`
|
||||
}
|
||||
|
||||
// BindClusterStatus reports observed cluster state.
|
||||
type BindClusterStatus struct {
|
||||
// Phase is a coarse lifecycle summary.
|
||||
// +optional
|
||||
Phase string `json:"phase,omitempty"`
|
||||
|
||||
// Replicas is the number of BIND pods requested.
|
||||
// +optional
|
||||
Replicas int32 `json:"replicas,omitempty"`
|
||||
|
||||
// ReadyReplicas is the number of BIND pods currently ready.
|
||||
// +optional
|
||||
ReadyReplicas int32 `json:"readyReplicas,omitempty"`
|
||||
|
||||
// PrimaryPod is the pod that holds authoritative zone data (ordinal-0).
|
||||
// +optional
|
||||
PrimaryPod string `json:"primaryPod,omitempty"`
|
||||
|
||||
// PrimaryService is the in-cluster DNS name secondaries transfer from.
|
||||
// +optional
|
||||
PrimaryService string `json:"primaryService,omitempty"`
|
||||
|
||||
// ObservedGeneration is the last reconciled generation.
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
|
||||
// Conditions represent the latest available observations.
|
||||
// +optional
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:shortName=bc
|
||||
// +kubebuilder:printcolumn:name="Mode",type=string,JSONPath=`.spec.mode`
|
||||
// +kubebuilder:printcolumn:name="Desired",type=integer,JSONPath=`.spec.replicas`
|
||||
// +kubebuilder:printcolumn:name="Ready",type=integer,JSONPath=`.status.readyReplicas`
|
||||
// +kubebuilder:printcolumn:name="Primary",type=string,JSONPath=`.status.primaryPod`
|
||||
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
|
||||
|
||||
// BindCluster is a managed set of BIND9 servers.
|
||||
type BindCluster struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec BindClusterSpec `json:"spec,omitempty"`
|
||||
Status BindClusterStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// BindClusterList contains a list of BindCluster.
|
||||
type BindClusterList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []BindCluster `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&BindCluster{}, &BindClusterList{})
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// DNSSECKey describes a key in a signing policy.
|
||||
type DNSSECKey struct {
|
||||
// Lifetime is how long the key is used before rollover, e.g. "P30D" or
|
||||
// "unlimited". Empty means unlimited.
|
||||
// +optional
|
||||
Lifetime string `json:"lifetime,omitempty"`
|
||||
|
||||
// Algorithm overrides the policy algorithm for this key.
|
||||
// +optional
|
||||
Algorithm string `json:"algorithm,omitempty"`
|
||||
|
||||
// KeySize in bits for RSA algorithms (ignored for ECDSA/EdDSA).
|
||||
// +optional
|
||||
KeySize int32 `json:"keySize,omitempty"`
|
||||
}
|
||||
|
||||
// BindDNSSECPolicySpec mirrors a BIND9 dnssec-policy. Zones referencing it are
|
||||
// signed with inline-signing and automated key management.
|
||||
type BindDNSSECPolicySpec struct {
|
||||
// ClusterRef names the owning BindCluster.
|
||||
ClusterRef string `json:"clusterRef"`
|
||||
|
||||
// PolicyName is the dnssec-policy name in named.conf. Defaults to the object
|
||||
// name.
|
||||
// +optional
|
||||
PolicyName string `json:"policyName,omitempty"`
|
||||
|
||||
// Algorithm for signing. Defaults to ecdsap256sha256.
|
||||
// +kubebuilder:default="ecdsap256sha256"
|
||||
// +optional
|
||||
Algorithm string `json:"algorithm,omitempty"`
|
||||
|
||||
// CSK, when set, uses a Combined Signing Key instead of split KSK/ZSK.
|
||||
// +optional
|
||||
CSK *DNSSECKey `json:"csk,omitempty"`
|
||||
|
||||
// KSK is the Key Signing Key configuration (ignored when CSK is set).
|
||||
// +optional
|
||||
KSK *DNSSECKey `json:"ksk,omitempty"`
|
||||
|
||||
// ZSK is the Zone Signing Key configuration (ignored when CSK is set).
|
||||
// +optional
|
||||
ZSK *DNSSECKey `json:"zsk,omitempty"`
|
||||
|
||||
// NSEC3 enables NSEC3 hashing instead of NSEC.
|
||||
// +optional
|
||||
NSEC3 bool `json:"nsec3,omitempty"`
|
||||
|
||||
// MaxZoneTTL, e.g. "P1D".
|
||||
// +optional
|
||||
MaxZoneTTL string `json:"maxZoneTTL,omitempty"`
|
||||
|
||||
// SignaturesValidity, e.g. "P14D".
|
||||
// +optional
|
||||
SignaturesValidity string `json:"signaturesValidity,omitempty"`
|
||||
|
||||
// ExtraOptions are raw named.conf lines appended inside the policy block.
|
||||
// +optional
|
||||
ExtraOptions []string `json:"extraOptions,omitempty"`
|
||||
}
|
||||
|
||||
// BindDNSSECPolicyStatus reports observed policy state.
|
||||
type BindDNSSECPolicyStatus struct {
|
||||
// +optional
|
||||
Ready bool `json:"ready,omitempty"`
|
||||
// ZoneCount is the number of zones signed with this policy.
|
||||
// +optional
|
||||
ZoneCount int32 `json:"zoneCount,omitempty"`
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// +optional
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:shortName=bdp
|
||||
// +kubebuilder:printcolumn:name="Cluster",type=string,JSONPath=`.spec.clusterRef`
|
||||
// +kubebuilder:printcolumn:name="Algorithm",type=string,JSONPath=`.spec.algorithm`
|
||||
// +kubebuilder:printcolumn:name="Zones",type=integer,JSONPath=`.status.zoneCount`
|
||||
// +kubebuilder:printcolumn:name="Ready",type=boolean,JSONPath=`.status.ready`
|
||||
|
||||
// BindDNSSECPolicy is a reusable DNSSEC signing policy.
|
||||
type BindDNSSECPolicy struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec BindDNSSECPolicySpec `json:"spec,omitempty"`
|
||||
Status BindDNSSECPolicyStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// BindDNSSECPolicyList contains a list of BindDNSSECPolicy.
|
||||
type BindDNSSECPolicyList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []BindDNSSECPolicy `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&BindDNSSECPolicy{}, &BindDNSSECPolicyList{})
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// RPZTrigger is the match domain of a response-policy rule.
|
||||
// +kubebuilder:validation:Enum=qname;client-ip;ip;nsdname;nsip
|
||||
type RPZTrigger string
|
||||
|
||||
// RPZAction is the policy action taken on a match.
|
||||
// +kubebuilder:validation:Enum=nxdomain;nodata;passthru;drop;tcp-only;cname
|
||||
type RPZAction string
|
||||
|
||||
// RPZRule is a single response-policy rule.
|
||||
type RPZRule struct {
|
||||
// Trigger selects what the Match is compared against.
|
||||
// +kubebuilder:default=qname
|
||||
// +optional
|
||||
Trigger RPZTrigger `json:"trigger,omitempty"`
|
||||
|
||||
// Match is the trigger value, e.g. a domain "bad.example." or CIDR.
|
||||
Match string `json:"match"`
|
||||
|
||||
// Action taken when the rule matches.
|
||||
// +kubebuilder:default=nxdomain
|
||||
// +optional
|
||||
Action RPZAction `json:"action,omitempty"`
|
||||
|
||||
// Target is the rewrite target when Action is cname.
|
||||
// +optional
|
||||
Target string `json:"target,omitempty"`
|
||||
}
|
||||
|
||||
// BindPolicySpec defines a Response Policy Zone (RPZ) — a DNS firewall applied
|
||||
// to a resolver cluster.
|
||||
type BindPolicySpec struct {
|
||||
// ClusterRef names the owning BindCluster (typically a resolver).
|
||||
ClusterRef string `json:"clusterRef"`
|
||||
|
||||
// ViewRef optionally scopes the policy to a single view.
|
||||
// +optional
|
||||
ViewRef string `json:"viewRef,omitempty"`
|
||||
|
||||
// ZoneName is the RPZ zone origin, e.g. "rpz.internal".
|
||||
ZoneName string `json:"zoneName"`
|
||||
|
||||
// Order controls this policy's position in the response-policy clause.
|
||||
// +kubebuilder:default=100
|
||||
// +optional
|
||||
Order int32 `json:"order,omitempty"`
|
||||
|
||||
// Rules are the inline policy triggers.
|
||||
// +optional
|
||||
Rules []RPZRule `json:"rules,omitempty"`
|
||||
|
||||
// Primaries lets the RPZ zone be transferred from an external feed instead
|
||||
// of being locally populated.
|
||||
// +optional
|
||||
Primaries []string `json:"primaries,omitempty"`
|
||||
|
||||
// TransferKeyRef names the BindTSIGKey used to pull from Primaries.
|
||||
// +optional
|
||||
TransferKeyRef string `json:"transferKeyRef,omitempty"`
|
||||
}
|
||||
|
||||
// BindPolicyStatus reports observed policy state.
|
||||
type BindPolicyStatus struct {
|
||||
// +optional
|
||||
Ready bool `json:"ready,omitempty"`
|
||||
// RuleCount is the number of active rules.
|
||||
// +optional
|
||||
RuleCount int32 `json:"ruleCount,omitempty"`
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// +optional
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:shortName=bp
|
||||
// +kubebuilder:printcolumn:name="Cluster",type=string,JSONPath=`.spec.clusterRef`
|
||||
// +kubebuilder:printcolumn:name="Zone",type=string,JSONPath=`.spec.zoneName`
|
||||
// +kubebuilder:printcolumn:name="Rules",type=integer,JSONPath=`.status.ruleCount`
|
||||
// +kubebuilder:printcolumn:name="Ready",type=boolean,JSONPath=`.status.ready`
|
||||
|
||||
// BindPolicy is a Response Policy Zone (RPZ) applied to a cluster.
|
||||
type BindPolicy struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec BindPolicySpec `json:"spec,omitempty"`
|
||||
Status BindPolicyStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// BindPolicyList contains a list of BindPolicy.
|
||||
type BindPolicyList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []BindPolicy `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&BindPolicy{}, &BindPolicyList{})
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// TSIGAlgorithm is a supported TSIG HMAC algorithm.
|
||||
// +kubebuilder:validation:Enum=hmac-sha256;hmac-sha512;hmac-sha384;hmac-sha224;hmac-sha1;hmac-md5
|
||||
type TSIGAlgorithm string
|
||||
|
||||
const (
|
||||
TSIGHMACSHA256 TSIGAlgorithm = "hmac-sha256"
|
||||
TSIGHMACSHA512 TSIGAlgorithm = "hmac-sha512"
|
||||
)
|
||||
|
||||
// BindTSIGKeySpec defines a TSIG key. If no existing key material is imported,
|
||||
// the operator generates a random key and stores it in a Secret.
|
||||
type BindTSIGKeySpec struct {
|
||||
// Algorithm is the HMAC algorithm. Defaults to hmac-sha256.
|
||||
// +kubebuilder:default="hmac-sha256"
|
||||
// +optional
|
||||
Algorithm TSIGAlgorithm `json:"algorithm,omitempty"`
|
||||
|
||||
// KeyName is the TSIG key name emitted into named.conf. Defaults to the
|
||||
// object name.
|
||||
// +optional
|
||||
KeyName string `json:"keyName,omitempty"`
|
||||
|
||||
// SecretName is the Secret the key material is written to (or read from when
|
||||
// ImportExisting is set). Defaults to "<name>-tsig".
|
||||
// +optional
|
||||
SecretName string `json:"secretName,omitempty"`
|
||||
|
||||
// ImportExisting, when true, means the referenced Secret already contains a
|
||||
// `secret` key and the operator will not generate new material.
|
||||
// +optional
|
||||
ImportExisting bool `json:"importExisting,omitempty"`
|
||||
}
|
||||
|
||||
// BindTSIGKeyStatus reports observed TSIG key state.
|
||||
type BindTSIGKeyStatus struct {
|
||||
// SecretName holds the generated/managed key material.
|
||||
// +optional
|
||||
SecretName string `json:"secretName,omitempty"`
|
||||
|
||||
// KeyName as used in named.conf.
|
||||
// +optional
|
||||
KeyName string `json:"keyName,omitempty"`
|
||||
|
||||
// Ready is true once the key Secret exists.
|
||||
// +optional
|
||||
Ready bool `json:"ready,omitempty"`
|
||||
|
||||
// ObservedGeneration is the last reconciled generation.
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
|
||||
// +optional
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:shortName=btk
|
||||
// +kubebuilder:printcolumn:name="Algorithm",type=string,JSONPath=`.spec.algorithm`
|
||||
// +kubebuilder:printcolumn:name="Secret",type=string,JSONPath=`.status.secretName`
|
||||
// +kubebuilder:printcolumn:name="Ready",type=boolean,JSONPath=`.status.ready`
|
||||
|
||||
// BindTSIGKey is a TSIG key backing zone transfers, dynamic updates and view
|
||||
// matching. The key material lives in a Kubernetes Secret, never in the CR.
|
||||
type BindTSIGKey struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec BindTSIGKeySpec `json:"spec,omitempty"`
|
||||
Status BindTSIGKeyStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// BindTSIGKeyList contains a list of BindTSIGKey.
|
||||
type BindTSIGKeyList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []BindTSIGKey `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&BindTSIGKey{}, &BindTSIGKeyList{})
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// BindViewSpec defines a split-horizon view. View ordering is significant in
|
||||
// BIND; use Order to control the sequence in named.conf.
|
||||
type BindViewSpec struct {
|
||||
// ClusterRef names the owning BindCluster.
|
||||
ClusterRef string `json:"clusterRef"`
|
||||
|
||||
// Order controls the position of this view in named.conf (ascending). The
|
||||
// first view whose match-clients matches a query wins.
|
||||
// +kubebuilder:default=100
|
||||
// +optional
|
||||
Order int32 `json:"order,omitempty"`
|
||||
|
||||
// MatchClients is an address-match-list (inline entries and/or ACL names)
|
||||
// selecting which clients this view answers. Defaults to "any".
|
||||
// +optional
|
||||
MatchClients []string `json:"matchClients,omitempty"`
|
||||
|
||||
// MatchDestinations is an optional destination address-match-list.
|
||||
// +optional
|
||||
MatchDestinations []string `json:"matchDestinations,omitempty"`
|
||||
|
||||
// Recursion overrides the cluster recursion setting for this view.
|
||||
// +optional
|
||||
Recursion *bool `json:"recursion,omitempty"`
|
||||
|
||||
// AllowQuery is an address-match-list restricting queries into this view.
|
||||
// +optional
|
||||
AllowQuery []string `json:"allowQuery,omitempty"`
|
||||
|
||||
// ExtraOptions are raw named.conf lines appended inside the view block.
|
||||
// +optional
|
||||
ExtraOptions []string `json:"extraOptions,omitempty"`
|
||||
}
|
||||
|
||||
// BindViewStatus reports observed view state.
|
||||
type BindViewStatus struct {
|
||||
// +optional
|
||||
Ready bool `json:"ready,omitempty"`
|
||||
// ZoneCount is the number of zones currently bound to this view.
|
||||
// +optional
|
||||
ZoneCount int32 `json:"zoneCount,omitempty"`
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// +optional
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:shortName=bv
|
||||
// +kubebuilder:printcolumn:name="Cluster",type=string,JSONPath=`.spec.clusterRef`
|
||||
// +kubebuilder:printcolumn:name="Order",type=integer,JSONPath=`.spec.order`
|
||||
// +kubebuilder:printcolumn:name="Zones",type=integer,JSONPath=`.status.zoneCount`
|
||||
// +kubebuilder:printcolumn:name="Ready",type=boolean,JSONPath=`.status.ready`
|
||||
|
||||
// BindView is a split-horizon view on a BindCluster.
|
||||
type BindView struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec BindViewSpec `json:"spec,omitempty"`
|
||||
Status BindViewStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// BindViewList contains a list of BindView.
|
||||
type BindViewList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []BindView `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&BindView{}, &BindViewList{})
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ZoneType is the BIND zone type.
|
||||
// +kubebuilder:validation:Enum=primary;secondary;forward;stub
|
||||
type ZoneType string
|
||||
|
||||
const (
|
||||
ZonePrimary ZoneType = "primary"
|
||||
ZoneSecondary ZoneType = "secondary"
|
||||
ZoneForward ZoneType = "forward"
|
||||
ZoneStub ZoneType = "stub"
|
||||
)
|
||||
|
||||
// Record is a single resource record set seeded into a primary zone via
|
||||
// dynamic update (nsupdate). Ongoing changes may also arrive from DNSRecord
|
||||
// objects or external RFC2136 clients.
|
||||
type Record struct {
|
||||
// Name is the owner name, relative to the zone apex or fully qualified.
|
||||
// Use "@" for the apex.
|
||||
// +kubebuilder:default="@"
|
||||
// +optional
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// Type is the RR type, e.g. A, AAAA, CNAME, MX, TXT, SRV, NS, PTR, CAA.
|
||||
Type string `json:"type"`
|
||||
|
||||
// TTL for the record set in seconds. Falls back to the zone default TTL.
|
||||
// +optional
|
||||
TTL *int32 `json:"ttl,omitempty"`
|
||||
|
||||
// Values are the RDATA entries, e.g. ["10 mail.example.com."] for an MX or
|
||||
// ["192.0.2.1","192.0.2.2"] for an A round-robin.
|
||||
// +kubebuilder:validation:MinItems=1
|
||||
Values []string `json:"values"`
|
||||
}
|
||||
|
||||
// BindZoneSpec defines a DNS zone managed on a BindCluster's primary.
|
||||
type BindZoneSpec struct {
|
||||
// ClusterRef names the owning BindCluster.
|
||||
ClusterRef string `json:"clusterRef"`
|
||||
|
||||
// ViewRef optionally binds this zone to a BindView.
|
||||
// +optional
|
||||
ViewRef string `json:"viewRef,omitempty"`
|
||||
|
||||
// ZoneName is the DNS origin, e.g. "example.com" or "2.0.192.in-addr.arpa".
|
||||
ZoneName string `json:"zoneName"`
|
||||
|
||||
// Type is the zone type. Defaults to primary.
|
||||
// +kubebuilder:default=primary
|
||||
// +optional
|
||||
Type ZoneType `json:"type,omitempty"`
|
||||
|
||||
// DefaultTTL for records that do not set their own TTL. Defaults to 3600.
|
||||
// +kubebuilder:default=3600
|
||||
// +optional
|
||||
DefaultTTL int32 `json:"defaultTTL,omitempty"`
|
||||
|
||||
// Records are static record sets seeded into a primary zone.
|
||||
// +optional
|
||||
Records []Record `json:"records,omitempty"`
|
||||
|
||||
// DynamicUpdate enables RFC2136 updates for this zone (external-dns style).
|
||||
// When true, UpdateKeyRef must reference a BindTSIGKey.
|
||||
// +optional
|
||||
DynamicUpdate bool `json:"dynamicUpdate,omitempty"`
|
||||
|
||||
// UpdateKeyRef names the BindTSIGKey permitted to send dynamic updates.
|
||||
// +optional
|
||||
UpdateKeyRef string `json:"updateKeyRef,omitempty"`
|
||||
|
||||
// AllowTransfer is an address-match-list (inline entries and/or ACL/key
|
||||
// names) permitted to AXFR/IXFR this zone.
|
||||
// +optional
|
||||
AllowTransfer []string `json:"allowTransfer,omitempty"`
|
||||
|
||||
// Catalog, when true, registers this zone as a member of the cluster's
|
||||
// catalog zone so secondaries auto-provision it.
|
||||
// +kubebuilder:default=true
|
||||
// +optional
|
||||
Catalog *bool `json:"catalog,omitempty"`
|
||||
|
||||
// DNSSECPolicyRef names a BindDNSSECPolicy to sign this zone with.
|
||||
// +optional
|
||||
DNSSECPolicyRef string `json:"dnssecPolicyRef,omitempty"`
|
||||
|
||||
// Forwarders lists upstreams for a forward-type zone.
|
||||
// +optional
|
||||
Forwarders []string `json:"forwarders,omitempty"`
|
||||
|
||||
// Primaries lists source servers for a secondary/stub-type zone.
|
||||
// +optional
|
||||
Primaries []string `json:"primaries,omitempty"`
|
||||
|
||||
// TransferKeyRef names the BindTSIGKey used to authenticate transfers from
|
||||
// Primaries for a secondary zone.
|
||||
// +optional
|
||||
TransferKeyRef string `json:"transferKeyRef,omitempty"`
|
||||
}
|
||||
|
||||
// BindZoneStatus reports observed zone state.
|
||||
type BindZoneStatus struct {
|
||||
// Phase is a coarse lifecycle summary (Pending/Ready/Error).
|
||||
// +optional
|
||||
Phase string `json:"phase,omitempty"`
|
||||
// Serial is the last observed SOA serial on the primary.
|
||||
// +optional
|
||||
Serial int64 `json:"serial,omitempty"`
|
||||
// RecordCount is the number of managed record sets applied.
|
||||
// +optional
|
||||
RecordCount int32 `json:"recordCount,omitempty"`
|
||||
// Signed reports whether DNSSEC signing is active.
|
||||
// +optional
|
||||
Signed bool `json:"signed,omitempty"`
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// +optional
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:shortName=bz
|
||||
// +kubebuilder:printcolumn:name="Zone",type=string,JSONPath=`.spec.zoneName`
|
||||
// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type`
|
||||
// +kubebuilder:printcolumn:name="Cluster",type=string,JSONPath=`.spec.clusterRef`
|
||||
// +kubebuilder:printcolumn:name="Serial",type=integer,JSONPath=`.status.serial`
|
||||
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
|
||||
|
||||
// BindZone is a forward or reverse DNS zone.
|
||||
type BindZone struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec BindZoneSpec `json:"spec,omitempty"`
|
||||
Status BindZoneStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// BindZoneList contains a list of BindZone.
|
||||
type BindZoneList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []BindZone `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&BindZone{}, &BindZoneList{})
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// DNSRecordSpec defines a single record set applied to a zone via TSIG dynamic
|
||||
// update (nsupdate) — the external-dns write path expressed as a CRD.
|
||||
type DNSRecordSpec struct {
|
||||
// ZoneRef names the BindZone this record belongs to. The cluster, view and
|
||||
// update key are derived from the referenced zone.
|
||||
ZoneRef string `json:"zoneRef"`
|
||||
|
||||
// Name is the owner name, relative to the zone apex or fully qualified.
|
||||
// +kubebuilder:default="@"
|
||||
// +optional
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// Type is the RR type, e.g. A, AAAA, CNAME, TXT, SRV, MX.
|
||||
Type string `json:"type"`
|
||||
|
||||
// TTL for the record set in seconds. Falls back to the zone default TTL.
|
||||
// +optional
|
||||
TTL *int32 `json:"ttl,omitempty"`
|
||||
|
||||
// Values are the RDATA entries.
|
||||
// +kubebuilder:validation:MinItems=1
|
||||
Values []string `json:"values"`
|
||||
}
|
||||
|
||||
// DNSRecordStatus reports observed record state.
|
||||
type DNSRecordStatus struct {
|
||||
// Phase is a coarse lifecycle summary (Pending/Applied/Error).
|
||||
// +optional
|
||||
Phase string `json:"phase,omitempty"`
|
||||
// FQDN is the fully-qualified owner name that was applied.
|
||||
// +optional
|
||||
FQDN string `json:"fqdn,omitempty"`
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// +optional
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:shortName=dnsr
|
||||
// +kubebuilder:printcolumn:name="Zone",type=string,JSONPath=`.spec.zoneRef`
|
||||
// +kubebuilder:printcolumn:name="Name",type=string,JSONPath=`.spec.name`
|
||||
// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type`
|
||||
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
|
||||
|
||||
// DNSRecord is an individually-managed record set applied to a BindZone.
|
||||
type DNSRecord struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec DNSRecordSpec `json:"spec,omitempty"`
|
||||
Status DNSRecordStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// DNSRecordList contains a list of DNSRecord.
|
||||
type DNSRecordList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []DNSRecord `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&DNSRecord{}, &DNSRecordList{})
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=bind.unkin.net
|
||||
package v1alpha1
|
||||
@@ -0,0 +1,17 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersion is the group/version used to register these objects.
|
||||
GroupVersion = schema.GroupVersion{Group: "bind.unkin.net", Version: "v1alpha1"}
|
||||
|
||||
// SchemeBuilder registers the API types with a runtime scheme.
|
||||
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
||||
|
||||
// AddToScheme adds the types in this group-version to the given scheme.
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user