cmd/k8s-operator/deploy/crds,k8s-operator/apis/v1alpha1: allow to define an exit node via Connector CR

Make it possible to define an exit node to be deployed to a Kubernetes cluster
via Connector Custom resource.

Also changes to Connector API so that one Connector corresponds
to one Tailnet node that can be either a subnet router or an exit
node or both.

Updates tailscale/tailscale#10708

Signed-off-by: Irbe Krumina <irbe@tailscale.com>
irbekrm/containerbootdeclarativeconf
Irbe Krumina 5 months ago
parent 3a9450bc06
commit 72f5312dba

@ -16,11 +16,15 @@ spec:
scope: Cluster
versions:
- additionalPrinterColumns:
- description: Cluster CIDR ranges exposed to tailnet via subnet router
jsonPath: .status.subnetRouter.routes
- description: CIDR ranges exposed to tailnet by a subnet router defined via this Connector instance.
jsonPath: .status.subnetRoutes
name: SubnetRoutes
type: string
- description: Status of the components deployed by the connector
- description: Whether this Connector instance defines an exit node.
jsonPath: .status.isExitNode
name: IsExitNode
type: string
- description: Status of the deployed Connector resources.
jsonPath: .status.conditions[?(@.type == "ConnectorReady")].reason
name: Status
type: string
@ -40,38 +44,39 @@ spec:
metadata:
type: object
spec:
description: Desired state of the Connector resource.
description: ConnectorSpec describes the desired Tailscale component.
type: object
required:
- subnetRouter
properties:
exitNode:
description: ExitNode defines whether the Connector node should act as a Tailscale exit node. Defaults to false. https://tailscale.com/kb/1103/exit-nodes
type: boolean
hostname:
description: Hostname is the tailnet hostname that should be assigned to the Connector node. If unset, hostname is defaulted to <connector name>-connector. Hostname can contain lower case letters, numbers and dashes, it must not start or end with a dash and must be between 2 and 63 characters long.
type: string
pattern: ^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$
subnetRouter:
description: SubnetRouter configures a Tailscale subnet router to be deployed in the cluster. If unset no subnet router will be deployed. https://tailscale.com/kb/1019/subnets/
description: SubnetRouter defines subnet routes that the Connector node should expose to tailnet. If unset, none are exposed. https://tailscale.com/kb/1019/subnets/
type: object
required:
- routes
properties:
hostname:
description: Hostname is the tailnet hostname that should be assigned to the subnet router node. If unset hostname is defaulted to <connector name>-subnetrouter. Hostname can contain lower case letters, numbers and dashes, it must not start or end with a dash and must be between 2 and 63 characters long.
type: string
pattern: ^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$
routes:
description: Routes refer to in-cluster CIDRs that the subnet router should make available. Route values must be strings that represent a valid IPv4 or IPv6 CIDR range. Values can be Tailscale 4via6 subnet routes. https://tailscale.com/kb/1201/4via6-subnets/
type: array
items:
type: string
format: cidr
tags:
description: Tags that the Tailscale node will be tagged with. If you want the subnet router to be autoapproved, you can configure Tailscale ACLs to autoapprove the subnetrouter's CIDRs for these tags. See https://tailscale.com/kb/1018/acls/#auto-approvers-for-routes-and-exit-nodes Defaults to tag:k8s. If you specify custom tags here, you must also make tag:k8s-operator owner of the custom tag. See https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator. Tags cannot be changed once a Connector has been created. Tag values must be in form ^tag:[a-zA-Z][a-zA-Z0-9-]*$.
type: array
items:
type: string
pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$
x-kubernetes-validations:
- rule: has(self.tags) == has(oldSelf.tags)
message: Subnetrouter tags cannot be changed. Delete and redeploy the Connector if you need to change it.
tags:
description: Tags that the Tailscale node will be tagged with. Defaults to [tag:k8s]. To autoapprove the subnet routes or exit node defined by a Connector, you can configure Tailscale ACLs to give these tags the necessary permissions. See https://tailscale.com/kb/1018/acls/#auto-approvers-for-routes-and-exit-nodes If you specify custom tags here, you must also make the operator an owner of these tags. See https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator. Tags cannot be changed once a Connector node has been created. Tag values must be in form ^tag:[a-zA-Z][a-zA-Z0-9-]*$.
type: array
items:
type: string
pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$
x-kubernetes-validations:
- rule: has(self.subnetRouter) || self.exitNode == true
message: A Connector needs to be either an exit node or a subnet router, or both.
status:
description: Status of the Connector. This is set and managed by the Tailscale operator.
description: ConnectorStatus describes the status of the Connector. This is set and managed by the Tailscale operator.
type: object
properties:
conditions:
@ -107,27 +112,12 @@ spec:
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
subnetRouter:
description: SubnetRouter status is the current status of a subnet router
type: object
required:
- message
- ready
- reason
- routes
properties:
message:
description: Message is a more verbose reason for the current subnet router status
type: string
ready:
description: Ready is the ready status of the subnet router
type: string
reason:
description: Reason is the reason for the subnet router status
type: string
routes:
description: Routes are the CIDRs currently exposed via subnet router
type: string
isExitNode:
description: IsExitNode is set to true if the Connector acts as an exit node.
type: boolean
subnetRoutes:
description: SubnetRoutes are the routes currently exposed to tailnet via this Connector instance.
type: string
served: true
storage: true
subresources:

@ -6,6 +6,9 @@
package v1alpha1
import (
"fmt"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -17,17 +20,19 @@ var ConnectorKind = "Connector"
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster,shortName=cn
// +kubebuilder:printcolumn:name="SubnetRoutes",type="string",JSONPath=`.status.subnetRouter.routes`,description="Cluster CIDR ranges exposed to tailnet via subnet router"
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=`.status.conditions[?(@.type == "ConnectorReady")].reason`,description="Status of the components deployed by the connector"
// +kubebuilder:printcolumn:name="SubnetRoutes",type="string",JSONPath=`.status.subnetRoutes`,description="CIDR ranges exposed to tailnet by a subnet router defined via this Connector instance."
// +kubebuilder:printcolumn:name="IsExitNode",type="string",JSONPath=`.status.isExitNode`,description="Whether this Connector instance defines an exit node."
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=`.status.conditions[?(@.type == "ConnectorReady")].reason`,description="Status of the deployed Connector resources."
type Connector struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Desired state of the Connector resource.
// ConnectorSpec describes the desired Tailscale component.
Spec ConnectorSpec `json:"spec"`
// Status of the Connector. This is set and managed by the Tailscale operator.
// ConnectorStatus describes the status of the Connector. This is set
// and managed by the Tailscale operator.
// +optional
Status ConnectorStatus `json:"status"`
}
@ -41,40 +46,69 @@ type ConnectorList struct {
Items []Connector `json:"items"`
}
// ConnectorSpec defines the desired state of a ConnectorSpec.
// ConnectorSpec describes a Tailscale node to be deployed in the cluster.
// +kubebuilder:validation:XValidation:rule="has(self.subnetRouter) || self.exitNode == true",message="A Connector needs to be either an exit node or a subnet router, or both."
type ConnectorSpec struct {
// SubnetRouter configures a Tailscale subnet router to be deployed in
// the cluster. If unset no subnet router will be deployed.
// Tags that the Tailscale node will be tagged with.
// Defaults to [tag:k8s].
// To autoapprove the subnet routes or exit node defined by a Connector,
// you can configure Tailscale ACLs to give these tags the necessary
// permissions.
// See https://tailscale.com/kb/1018/acls/#auto-approvers-for-routes-and-exit-nodes
// If you specify custom tags here, you must also make the operator an owner of these tags.
// See https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator.
// Tags cannot be changed once a Connector node has been created.
// Tag values must be in form ^tag:[a-zA-Z][a-zA-Z0-9-]*$.
// +optional
Tags Tags `json:"tags,omitempty"`
// Hostname is the tailnet hostname that should be assigned to the
// Connector node. If unset, hostname is defaulted to <connector
// name>-connector. Hostname can contain lower case letters, numbers and
// dashes, it must not start or end with a dash and must be between 2
// and 63 characters long.
// +optional
Hostname Hostname `json:"hostname,omitempty"`
// SubnetRouter defines subnet routes that the Connector node should
// expose to tailnet. If unset, none are exposed.
// https://tailscale.com/kb/1019/subnets/
// +optional
SubnetRouter *SubnetRouter `json:"subnetRouter"`
// ExitNode defines whether the Connector node should act as a
// Tailscale exit node. Defaults to false.
// https://tailscale.com/kb/1103/exit-nodes
// +optional
ExitNode bool `json:"exitNode"`
}
// SubnetRouter describes a subnet router.
// +kubebuilder:validation:XValidation:rule="has(self.tags) == has(oldSelf.tags)",message="Subnetrouter tags cannot be changed. Delete and redeploy the Connector if you need to change it."
// SubnetRouter defines subnet routes that should be exposed to tailnet via a
// Connector node.
type SubnetRouter struct {
// Routes refer to in-cluster CIDRs that the subnet router should make
// available. Route values must be strings that represent a valid IPv4
// or IPv6 CIDR range. Values can be Tailscale 4via6 subnet routes.
// https://tailscale.com/kb/1201/4via6-subnets/
Routes []Route `json:"routes"`
// Tags that the Tailscale node will be tagged with. If you want the
// subnet router to be autoapproved, you can configure Tailscale ACLs to
// autoapprove the subnetrouter's CIDRs for these tags.
// See https://tailscale.com/kb/1018/acls/#auto-approvers-for-routes-and-exit-nodes
// Defaults to tag:k8s.
// If you specify custom tags here, you must also make tag:k8s-operator owner of the custom tag.
// See https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator.
// Tags cannot be changed once a Connector has been created.
// Tag values must be in form ^tag:[a-zA-Z][a-zA-Z0-9-]*$.
// +optional
Tags []Tag `json:"tags,omitempty"`
// Hostname is the tailnet hostname that should be assigned to the
// subnet router node. If unset hostname is defaulted to <connector
// name>-subnetrouter. Hostname can contain lower case letters, numbers
// and dashes, it must not start or end with a dash and must be between
// 2 and 63 characters long.
// +optional
Hostname Hostname `json:"hostname,omitempty"`
Routes Routes `json:"routes"`
}
type Tags []Tag
func (tags Tags) Stringify() []string {
stringTags := make([]string, len(tags))
for i, t := range tags {
stringTags[i] = string(t)
}
return stringTags
}
type Routes []Route
func (routes Routes) Stringify() string {
var sb strings.Builder
sb.WriteString(string(routes[0]))
for _, r := range routes[1:] {
sb.WriteString(fmt.Sprintf(",%s", r))
}
return sb.String()
}
// +kubebuilder:validation:Type=string
@ -91,28 +125,19 @@ type Hostname string
// ConnectorStatus defines the observed state of the Connector.
type ConnectorStatus struct {
// List of status conditions to indicate the status of the Connector.
// Known condition types are `ConnectorReady`.
// +listType=map
// +listMapKey=type
// +optional
Conditions []ConnectorCondition `json:"conditions"`
// SubnetRouter status is the current status of a subnet router
// SubnetRoutes are the routes currently exposed to tailnet via this
// Connector instance.
// +optional
SubnetRouter *SubnetRouterStatus `json:"subnetRouter"`
}
// SubnetRouter status is the current status of a subnet router if deployed
type SubnetRouterStatus struct {
// Routes are the CIDRs currently exposed via subnet router
Routes string `json:"routes"`
// Ready is the ready status of the subnet router
Ready metav1.ConditionStatus `json:"ready"`
// Reason is the reason for the subnet router status
Reason string `json:"reason"`
// Message is a more verbose reason for the current subnet router status
Message string `json:"message"`
SubnetRoutes string `json:"subnetRoutes"`
// IsExitNode is set to true if the Connector acts as an exit node.
// +optional
IsExitNode bool `json:"isExitNode"`
}
// ConnectorCondition contains condition information for a Connector.
@ -147,7 +172,7 @@ type ConnectorCondition struct {
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}
// ConnectorConditionType represents a Connector condition type
// ConnectorConditionType represents a Connector condition type.
type ConnectorConditionType string
const (

@ -92,6 +92,11 @@ func (in *ConnectorList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConnectorSpec) DeepCopyInto(out *ConnectorSpec) {
*out = *in
if in.Tags != nil {
in, out := &in.Tags, &out.Tags
*out = make(Tags, len(*in))
copy(*out, *in)
}
if in.SubnetRouter != nil {
in, out := &in.SubnetRouter, &out.SubnetRouter
*out = new(SubnetRouter)
@ -119,11 +124,6 @@ func (in *ConnectorStatus) DeepCopyInto(out *ConnectorStatus) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.SubnetRouter != nil {
in, out := &in.SubnetRouter, &out.SubnetRouter
*out = new(SubnetRouterStatus)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectorStatus.
@ -136,17 +136,31 @@ func (in *ConnectorStatus) DeepCopy() *ConnectorStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in Routes) DeepCopyInto(out *Routes) {
{
in := &in
*out = make(Routes, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Routes.
func (in Routes) DeepCopy() Routes {
if in == nil {
return nil
}
out := new(Routes)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SubnetRouter) DeepCopyInto(out *SubnetRouter) {
*out = *in
if in.Routes != nil {
in, out := &in.Routes, &out.Routes
*out = make([]Route, len(*in))
copy(*out, *in)
}
if in.Tags != nil {
in, out := &in.Tags, &out.Tags
*out = make([]Tag, len(*in))
*out = make(Routes, len(*in))
copy(*out, *in)
}
}
@ -162,16 +176,20 @@ func (in *SubnetRouter) DeepCopy() *SubnetRouter {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SubnetRouterStatus) DeepCopyInto(out *SubnetRouterStatus) {
*out = *in
func (in Tags) DeepCopyInto(out *Tags) {
{
in := &in
*out = make(Tags, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetRouterStatus.
func (in *SubnetRouterStatus) DeepCopy() *SubnetRouterStatus {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Tags.
func (in Tags) DeepCopy() Tags {
if in == nil {
return nil
}
out := new(SubnetRouterStatus)
out := new(Tags)
in.DeepCopyInto(out)
return out
return *out
}

Loading…
Cancel
Save