Add companion TSIG API and BindTSIGAPI CRD
The vault-plugin-secrets-bind-tsig plugin needs an HTTP endpoint that
creates, reads, rotates and deletes TSIG keys on its behalf, decoupling
Vault from direct Kubernetes API access. This adds that companion API and
lets the operator deploy it declaratively.
- Add BindTSIGAPI CRD: creating one makes the operator reconcile a
Deployment, Service, ConfigMap (env vars), token Secret and namespaced
RBAC for the companion API. Spec covers image, replicas, port,
targetNamespace, tokenSecretName, extra env, service exposure and
resources.
- Generate the master access token Secret only when absent, so a
VaultStaticSecret may pre-seed/overwrite it; the operator does not own it.
- Add the companion API server (internal/tsigapi): bearer-auth HTTP
contract POST /v1/keys, GET/DELETE /v1/keys/{name}, POST
/v1/keys/{name}/rotate, backed by BindTSIGKey custom resources the
operator reconciles into key material.
- Add cmd/tsigapi entrypoint and Dockerfile.tsigapi (distroless).
- Wire the reconciler into setup, regenerate CRDs/RBAC/deepcopy, and add
Woodpecker build (PR dry-run) and release (tag push) steps for the
bind-tsig-api image.
- Cover the API server with auth and key-lifecycle unit tests.
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.17.3
|
||||
name: bindtsigapis.bind.unkin.net
|
||||
spec:
|
||||
group: bind.unkin.net
|
||||
names:
|
||||
kind: BindTSIGAPI
|
||||
listKind: BindTSIGAPIList
|
||||
plural: bindtsigapis
|
||||
shortNames:
|
||||
- btapi
|
||||
singular: bindtsigapi
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .status.endpoint
|
||||
name: Endpoint
|
||||
type: string
|
||||
- jsonPath: .status.readyReplicas
|
||||
name: Ready
|
||||
type: integer
|
||||
- jsonPath: .status.phase
|
||||
name: Phase
|
||||
type: string
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: |-
|
||||
BindTSIGAPI deploys the companion TSIG API. Creating one makes the operator
|
||||
reconcile a Deployment, Service, ConfigMap, token Secret and RBAC for it.
|
||||
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: |-
|
||||
BindTSIGAPISpec configures the companion TSIG API that the operator deploys.
|
||||
The API exposes an HTTP contract (used by vault-plugin-secrets-bind-tsig) for
|
||||
creating, rotating and deleting TSIG keys; it does so by managing BindTSIGKey
|
||||
custom resources, which the operator then reconciles into key material.
|
||||
properties:
|
||||
env:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Env are extra environment variables rendered into the
|
||||
API ConfigMap.
|
||||
type: object
|
||||
image:
|
||||
default: git.unkin.net/unkin/bind-tsig-api:latest
|
||||
description: Image is the companion API container image.
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
description: ImagePullPolicy for the API container.
|
||||
type: string
|
||||
port:
|
||||
default: 8443
|
||||
description: Port the API listens on. Defaults to 8443.
|
||||
format: int32
|
||||
type: integer
|
||||
replicas:
|
||||
default: 1
|
||||
description: Replicas of the API. Defaults to 1.
|
||||
format: int32
|
||||
type: integer
|
||||
resources:
|
||||
description: Resources for the API container.
|
||||
properties:
|
||||
claims:
|
||||
description: |-
|
||||
Claims lists the names of resources, defined in spec.resourceClaims,
|
||||
that are used by this container.
|
||||
|
||||
This field depends on the
|
||||
DynamicResourceAllocation feature gate.
|
||||
|
||||
This field is immutable. It can only be set for containers.
|
||||
items:
|
||||
description: ResourceClaim references one entry in PodSpec.ResourceClaims.
|
||||
properties:
|
||||
name:
|
||||
description: |-
|
||||
Name must match the name of one entry in pod.spec.resourceClaims of
|
||||
the Pod where this field is used. It makes that resource available
|
||||
inside a container.
|
||||
type: string
|
||||
request:
|
||||
description: |-
|
||||
Request is the name chosen for a request in the referenced claim.
|
||||
If empty, everything from the claim is made available, otherwise
|
||||
only the result of this request.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- name
|
||||
x-kubernetes-list-type: map
|
||||
limits:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: |-
|
||||
Limits describes the maximum amount of compute resources allowed.
|
||||
More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
|
||||
type: object
|
||||
requests:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: |-
|
||||
Requests describes the minimum amount of compute resources required.
|
||||
If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
|
||||
otherwise to an implementation-defined value. Requests cannot exceed Limits.
|
||||
More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
|
||||
type: object
|
||||
type: object
|
||||
service:
|
||||
description: Service controls how the API is exposed (defaults to
|
||||
ClusterIP).
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Annotations added to the client-facing Service (e.g.
|
||||
PureLB/MetalLB hints).
|
||||
type: object
|
||||
externalTrafficPolicy:
|
||||
description: |-
|
||||
ExternalTrafficPolicy for a LoadBalancer/NodePort Service. Local preserves
|
||||
client source IPs (required for source-IP ACLs on the DNS servers) but
|
||||
only routes to nodes running a pod. Defaults to Cluster.
|
||||
enum:
|
||||
- Cluster
|
||||
- Local
|
||||
type: string
|
||||
loadBalancerIP:
|
||||
description: LoadBalancerIP requests a specific address when Type
|
||||
is LoadBalancer.
|
||||
type: string
|
||||
type:
|
||||
description: Type of the client-facing Service. Defaults to ClusterIP.
|
||||
enum:
|
||||
- ClusterIP
|
||||
- LoadBalancer
|
||||
- NodePort
|
||||
type: string
|
||||
type: object
|
||||
targetNamespace:
|
||||
description: |-
|
||||
TargetNamespace is where the API creates BindTSIGKey resources. Defaults
|
||||
to the API's own namespace.
|
||||
type: string
|
||||
tokenSecretName:
|
||||
description: |-
|
||||
TokenSecretName holds the master access token clients present to the API.
|
||||
The operator generates a token if the Secret does not exist, so a
|
||||
VaultStaticSecret may pre-seed it instead. Defaults to "<name>-token".
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: BindTSIGAPIStatus reports observed API state.
|
||||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: |-
|
||||
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: |-
|
||||
message is a human readable message indicating details about the transition.
|
||||
This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: |-
|
||||
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||
with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: |-
|
||||
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected values and meanings for this field,
|
||||
and whether the values are considered a guaranteed API.
|
||||
The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
endpoint:
|
||||
description: Endpoint is the in-cluster URL clients (Vault) use to
|
||||
reach the API.
|
||||
type: string
|
||||
observedGeneration:
|
||||
format: int64
|
||||
type: integer
|
||||
phase:
|
||||
type: string
|
||||
readyReplicas:
|
||||
format: int32
|
||||
type: integer
|
||||
tokenSecret:
|
||||
description: TokenSecret is the Secret holding the master access token.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -2023,6 +2023,273 @@ spec:
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.17.3
|
||||
name: bindtsigapis.bind.unkin.net
|
||||
spec:
|
||||
group: bind.unkin.net
|
||||
names:
|
||||
kind: BindTSIGAPI
|
||||
listKind: BindTSIGAPIList
|
||||
plural: bindtsigapis
|
||||
shortNames:
|
||||
- btapi
|
||||
singular: bindtsigapi
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .status.endpoint
|
||||
name: Endpoint
|
||||
type: string
|
||||
- jsonPath: .status.readyReplicas
|
||||
name: Ready
|
||||
type: integer
|
||||
- jsonPath: .status.phase
|
||||
name: Phase
|
||||
type: string
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: |-
|
||||
BindTSIGAPI deploys the companion TSIG API. Creating one makes the operator
|
||||
reconcile a Deployment, Service, ConfigMap, token Secret and RBAC for it.
|
||||
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: |-
|
||||
BindTSIGAPISpec configures the companion TSIG API that the operator deploys.
|
||||
The API exposes an HTTP contract (used by vault-plugin-secrets-bind-tsig) for
|
||||
creating, rotating and deleting TSIG keys; it does so by managing BindTSIGKey
|
||||
custom resources, which the operator then reconciles into key material.
|
||||
properties:
|
||||
env:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Env are extra environment variables rendered into the
|
||||
API ConfigMap.
|
||||
type: object
|
||||
image:
|
||||
default: git.unkin.net/unkin/bind-tsig-api:latest
|
||||
description: Image is the companion API container image.
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
description: ImagePullPolicy for the API container.
|
||||
type: string
|
||||
port:
|
||||
default: 8443
|
||||
description: Port the API listens on. Defaults to 8443.
|
||||
format: int32
|
||||
type: integer
|
||||
replicas:
|
||||
default: 1
|
||||
description: Replicas of the API. Defaults to 1.
|
||||
format: int32
|
||||
type: integer
|
||||
resources:
|
||||
description: Resources for the API container.
|
||||
properties:
|
||||
claims:
|
||||
description: |-
|
||||
Claims lists the names of resources, defined in spec.resourceClaims,
|
||||
that are used by this container.
|
||||
|
||||
This field depends on the
|
||||
DynamicResourceAllocation feature gate.
|
||||
|
||||
This field is immutable. It can only be set for containers.
|
||||
items:
|
||||
description: ResourceClaim references one entry in PodSpec.ResourceClaims.
|
||||
properties:
|
||||
name:
|
||||
description: |-
|
||||
Name must match the name of one entry in pod.spec.resourceClaims of
|
||||
the Pod where this field is used. It makes that resource available
|
||||
inside a container.
|
||||
type: string
|
||||
request:
|
||||
description: |-
|
||||
Request is the name chosen for a request in the referenced claim.
|
||||
If empty, everything from the claim is made available, otherwise
|
||||
only the result of this request.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- name
|
||||
x-kubernetes-list-type: map
|
||||
limits:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: |-
|
||||
Limits describes the maximum amount of compute resources allowed.
|
||||
More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
|
||||
type: object
|
||||
requests:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: |-
|
||||
Requests describes the minimum amount of compute resources required.
|
||||
If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
|
||||
otherwise to an implementation-defined value. Requests cannot exceed Limits.
|
||||
More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
|
||||
type: object
|
||||
type: object
|
||||
service:
|
||||
description: Service controls how the API is exposed (defaults to
|
||||
ClusterIP).
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Annotations added to the client-facing Service (e.g.
|
||||
PureLB/MetalLB hints).
|
||||
type: object
|
||||
externalTrafficPolicy:
|
||||
description: |-
|
||||
ExternalTrafficPolicy for a LoadBalancer/NodePort Service. Local preserves
|
||||
client source IPs (required for source-IP ACLs on the DNS servers) but
|
||||
only routes to nodes running a pod. Defaults to Cluster.
|
||||
enum:
|
||||
- Cluster
|
||||
- Local
|
||||
type: string
|
||||
loadBalancerIP:
|
||||
description: LoadBalancerIP requests a specific address when Type
|
||||
is LoadBalancer.
|
||||
type: string
|
||||
type:
|
||||
description: Type of the client-facing Service. Defaults to ClusterIP.
|
||||
enum:
|
||||
- ClusterIP
|
||||
- LoadBalancer
|
||||
- NodePort
|
||||
type: string
|
||||
type: object
|
||||
targetNamespace:
|
||||
description: |-
|
||||
TargetNamespace is where the API creates BindTSIGKey resources. Defaults
|
||||
to the API's own namespace.
|
||||
type: string
|
||||
tokenSecretName:
|
||||
description: |-
|
||||
TokenSecretName holds the master access token clients present to the API.
|
||||
The operator generates a token if the Secret does not exist, so a
|
||||
VaultStaticSecret may pre-seed it instead. Defaults to "<name>-token".
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: BindTSIGAPIStatus reports observed API state.
|
||||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: |-
|
||||
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: |-
|
||||
message is a human readable message indicating details about the transition.
|
||||
This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: |-
|
||||
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||
with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: |-
|
||||
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected values and meanings for this field,
|
||||
and whether the values are considered a guaranteed API.
|
||||
The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
endpoint:
|
||||
description: Endpoint is the in-cluster URL clients (Vault) use to
|
||||
reach the API.
|
||||
type: string
|
||||
observedGeneration:
|
||||
format: int64
|
||||
type: integer
|
||||
phase:
|
||||
type: string
|
||||
readyReplicas:
|
||||
format: int32
|
||||
type: integer
|
||||
tokenSecret:
|
||||
description: TokenSecret is the Secret holding the master access token.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.17.3
|
||||
|
||||
@@ -9,6 +9,7 @@ rules:
|
||||
resources:
|
||||
- configmaps
|
||||
- secrets
|
||||
- serviceaccounts
|
||||
- services
|
||||
verbs:
|
||||
- create
|
||||
@@ -36,6 +37,7 @@ rules:
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- deployments
|
||||
- statefulsets
|
||||
verbs:
|
||||
- create
|
||||
@@ -53,6 +55,7 @@ rules:
|
||||
- bindclusters
|
||||
- binddnssecpolicies
|
||||
- bindpolicies
|
||||
- bindtsigapis
|
||||
- bindtsigkeys
|
||||
- bindviews
|
||||
- bindzones
|
||||
@@ -73,6 +76,7 @@ rules:
|
||||
- bindclusters/status
|
||||
- binddnssecpolicies/status
|
||||
- bindpolicies/status
|
||||
- bindtsigapis/status
|
||||
- bindtsigkeys/status
|
||||
- bindviews/status
|
||||
- bindzones/status
|
||||
@@ -81,3 +85,16 @@ rules:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- rbac.authorization.k8s.io
|
||||
resources:
|
||||
- rolebindings
|
||||
- roles
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
|
||||
Reference in New Issue
Block a user