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,104 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// 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.
|
||||
type BindTSIGAPISpec struct {
|
||||
// Image is the companion API container image.
|
||||
// +kubebuilder:default="git.unkin.net/unkin/bind-tsig-api:latest"
|
||||
// +optional
|
||||
Image string `json:"image,omitempty"`
|
||||
|
||||
// ImagePullPolicy for the API container.
|
||||
// +optional
|
||||
ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"`
|
||||
|
||||
// Replicas of the API. Defaults to 1.
|
||||
// +kubebuilder:default=1
|
||||
// +optional
|
||||
Replicas int32 `json:"replicas,omitempty"`
|
||||
|
||||
// Port the API listens on. Defaults to 8443.
|
||||
// +kubebuilder:default=8443
|
||||
// +optional
|
||||
Port int32 `json:"port,omitempty"`
|
||||
|
||||
// TargetNamespace is where the API creates BindTSIGKey resources. Defaults
|
||||
// to the API's own namespace.
|
||||
// +optional
|
||||
TargetNamespace string `json:"targetNamespace,omitempty"`
|
||||
|
||||
// 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".
|
||||
// +optional
|
||||
TokenSecretName string `json:"tokenSecretName,omitempty"`
|
||||
|
||||
// Env are extra environment variables rendered into the API ConfigMap.
|
||||
// +optional
|
||||
Env map[string]string `json:"env,omitempty"`
|
||||
|
||||
// Service controls how the API is exposed (defaults to ClusterIP).
|
||||
// +optional
|
||||
Service ClusterServiceSpec `json:"service,omitempty"`
|
||||
|
||||
// Resources for the API container.
|
||||
// +optional
|
||||
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
|
||||
}
|
||||
|
||||
// BindTSIGAPIStatus reports observed API state.
|
||||
type BindTSIGAPIStatus struct {
|
||||
// +optional
|
||||
Phase string `json:"phase,omitempty"`
|
||||
// +optional
|
||||
ReadyReplicas int32 `json:"readyReplicas,omitempty"`
|
||||
// Endpoint is the in-cluster URL clients (Vault) use to reach the API.
|
||||
// +optional
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
// TokenSecret is the Secret holding the master access token.
|
||||
// +optional
|
||||
TokenSecret string `json:"tokenSecret,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=btapi
|
||||
// +kubebuilder:printcolumn:name="Endpoint",type=string,JSONPath=`.status.endpoint`
|
||||
// +kubebuilder:printcolumn:name="Ready",type=integer,JSONPath=`.status.readyReplicas`
|
||||
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
|
||||
|
||||
// BindTSIGAPI deploys the companion TSIG API. Creating one makes the operator
|
||||
// reconcile a Deployment, Service, ConfigMap, token Secret and RBAC for it.
|
||||
type BindTSIGAPI struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec BindTSIGAPISpec `json:"spec,omitempty"`
|
||||
Status BindTSIGAPIStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// BindTSIGAPIList contains a list of BindTSIGAPI.
|
||||
type BindTSIGAPIList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []BindTSIGAPI `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&BindTSIGAPI{}, &BindTSIGAPIList{})
|
||||
}
|
||||
@@ -581,6 +581,111 @@ func (in *BindPolicyStatus) DeepCopy() *BindPolicyStatus {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BindTSIGAPI) DeepCopyInto(out *BindTSIGAPI) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BindTSIGAPI.
|
||||
func (in *BindTSIGAPI) DeepCopy() *BindTSIGAPI {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BindTSIGAPI)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *BindTSIGAPI) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BindTSIGAPIList) DeepCopyInto(out *BindTSIGAPIList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]BindTSIGAPI, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BindTSIGAPIList.
|
||||
func (in *BindTSIGAPIList) DeepCopy() *BindTSIGAPIList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BindTSIGAPIList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *BindTSIGAPIList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BindTSIGAPISpec) DeepCopyInto(out *BindTSIGAPISpec) {
|
||||
*out = *in
|
||||
if in.Env != nil {
|
||||
in, out := &in.Env, &out.Env
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
in.Service.DeepCopyInto(&out.Service)
|
||||
in.Resources.DeepCopyInto(&out.Resources)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BindTSIGAPISpec.
|
||||
func (in *BindTSIGAPISpec) DeepCopy() *BindTSIGAPISpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BindTSIGAPISpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BindTSIGAPIStatus) DeepCopyInto(out *BindTSIGAPIStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BindTSIGAPIStatus.
|
||||
func (in *BindTSIGAPIStatus) DeepCopy() *BindTSIGAPIStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BindTSIGAPIStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BindTSIGKey) DeepCopyInto(out *BindTSIGKey) {
|
||||
*out = *in
|
||||
|
||||
Reference in New Issue
Block a user