Skip to content

Commit 70ccbab

Browse files
authored
Merge pull request #5 from mNi-Cloud/add_ionscale
Add ionscale
2 parents 42d1c63 + 88b4b58 commit 70ccbab

44 files changed

Lines changed: 6136 additions & 208 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ jobs:
2020
- name: Run linter
2121
uses: golangci/golangci-lint-action@v6
2222
with:
23-
version: v1.63.4
23+
version: v1.64.2

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Build the manager binary
2-
FROM docker.io/golang:1.23 AS builder
2+
FROM docker.io/golang:1.24 AS builder
33
ARG TARGETOS
44
ARG TARGETARCH
55

PROJECT

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,31 @@ resources:
1717
kind: Connector
1818
path: github.com/mNi-Cloud/kodiak/api/v1alpha1
1919
version: v1alpha1
20+
- api:
21+
crdVersion: v1
22+
namespaced: true
23+
controller: true
24+
domain: mnicloud.jp
25+
group: kodiak
26+
kind: ControlServer
27+
path: github.com/mNi-Cloud/kodiak/api/v1alpha1
28+
version: v1alpha1
29+
- api:
30+
crdVersion: v1
31+
namespaced: true
32+
controller: true
33+
domain: mnicloud.jp
34+
group: kodiak
35+
kind: Tailnet
36+
path: github.com/mNi-Cloud/kodiak/api/v1alpha1
37+
version: v1alpha1
38+
- api:
39+
crdVersion: v1
40+
namespaced: true
41+
controller: true
42+
domain: mnicloud.jp
43+
group: kodiak
44+
kind: AuthKey
45+
path: github.com/mNi-Cloud/kodiak/api/v1alpha1
46+
version: v1alpha1
2047
version: "3"

README.md

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,35 @@
11
# kodiak
2-
// TODO(user): Add simple overview of use/purpose
2+
3+
Kodiak is a Kubernetes Operator that provisions and manages a full self-hosted
4+
Tailscale environment powered by [ionscale](https://github.com/jsiebens/ionscale).
5+
It automates creation of the ionscale control plane, tailnets, authentication
6+
keys, and the connector workloads that join Kubernetes networks to Tailscale.
37

48
## Description
5-
// TODO(user): An in-depth paragraph about your project and overview of use
9+
10+
Kodiak exposes four custom resources (`ControlServer`, `Tailnet`, `AuthKey`, and
11+
`Connector`) that map directly to the lifecycle of an ionscale deployment:
12+
13+
- **ControlServer** provisions the ionscale API/control plane, including TLS
14+
configuration, embedded DERP options, persistent storage, and optional OIDC
15+
authentication. The controller renders ionscale configuration, wires any
16+
referenced secrets (e.g. the system admin key), and maintains a Deployment,
17+
Service, and PersistentVolumeClaim to run the server. Status reflects the
18+
service endpoint and readiness.
19+
- **Tailnet** configures a tailnet on the control server. The controller calls
20+
ionscale's API to create or update tailnet state (policies, DNS, feature
21+
flags) and reports readiness, the assigned tailnet ID, and machine counts.
22+
- **AuthKey** manages short-lived authentication keys for joining nodes to a
23+
tailnet. Kodiak requests keys from ionscale, stores the resulting value in a
24+
Kubernetes secret, keeps status in sync with ionscale, and rotates keys when
25+
specs change.
26+
- **Connector** (existing implementation) runs a Tailscale client pod that
27+
connects Kubernetes networks to the tailnet, handling auth key injection,
28+
connector pod lifecycle, and status reporting based on `tailscale status`.
29+
30+
The controllers reconcile towards declarative state, use Kubernetes
31+
finalizers to clean up remote resources (e.g. deleting tailnets or auth keys),
32+
and surface detailed conditions in resource status.
633

734
## Getting Started
835

@@ -111,7 +138,27 @@ previously added to 'dist/chart/values.yaml' or 'dist/chart/manager/manager.yaml
111138
is manually re-applied afterwards.
112139

113140
## Contributing
114-
// TODO(user): Add detailed information on how you would like others to contribute to this project
141+
### Required Secrets
142+
143+
Control server access requires a system admin key. By default Kodiak expects a
144+
secret named `<controlserver-name>-admin` in the same namespace with a
145+
`systemAdminKey` entry. You can override the secret name using the annotation
146+
`kodiak.mnicloud.jp/system-admin-key-secret` on the `ControlServer` resource.
147+
148+
Auth keys are stored in Kubernetes secrets. If `spec.secretName` is omitted the
149+
controller creates a secret named `authkey-<resource-name>` containing a
150+
`TS_AUTH_KEY` entry.
151+
152+
### Development Notes
153+
154+
- Controller unit tests use `envtest`. Install the Kubernetes control-plane
155+
binaries (etcd, kube-apiserver, kubectl) via `make envtest` before running
156+
`go test ./...`.
157+
- E2E tests rely on Docker and Kind. Expect failures if these dependencies are
158+
unavailable in the current environment.
159+
160+
Please open issues or pull requests for bugs, enhancements, or documentation
161+
updates.
115162

116163
**NOTE:** Run `make help` for more information on all potential `make` targets
117164

@@ -132,4 +179,3 @@ distributed under the License is distributed on an "AS IS" BASIS,
132179
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
133180
See the License for the specific language governing permissions and
134181
limitations under the License.
135-

api/v1alpha1/authkey_types.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
Copyright 2025.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha1
18+
19+
import (
20+
corev1 "k8s.io/api/core/v1"
21+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
)
23+
24+
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
25+
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
26+
27+
// AuthKeySpec defines the desired state of AuthKey
28+
type AuthKeySpec struct {
29+
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
30+
// Important: Run "make" to regenerate code after modifying this file
31+
32+
// +kubebuilder:validation:Required
33+
// TailnetRef references the Tailnet this auth key belongs to
34+
TailnetRef corev1.LocalObjectReference `json:"tailnetRef"`
35+
36+
// +optional
37+
// +kubebuilder:default=false
38+
// Ephemeral determines if machines authenticated with this key are ephemeral
39+
Ephemeral bool `json:"ephemeral,omitempty"`
40+
41+
// +optional
42+
// Expiry duration for the auth key (e.g., "24h", "7d")
43+
Expiry string `json:"expiry,omitempty"`
44+
45+
// +optional
46+
// Tags to apply to machines authenticated with this key
47+
Tags []string `json:"tags,omitempty"`
48+
49+
// +optional
50+
// +kubebuilder:default=false
51+
// PreAuthorized determines if machines are automatically authorized
52+
PreAuthorized bool `json:"preAuthorized,omitempty"`
53+
54+
// +optional
55+
// SecretName to store the generated auth key (defaults to authkey-<name>)
56+
SecretName string `json:"secretName,omitempty"`
57+
}
58+
59+
// AuthKeyStatus defines the observed state of AuthKey.
60+
type AuthKeyStatus struct {
61+
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
62+
// Important: Run "make" to regenerate code after modifying this file
63+
64+
// +optional
65+
// KeyID is the ID assigned by the control server
66+
KeyID uint64 `json:"keyId,omitempty"`
67+
68+
// +optional
69+
// SecretRef references the secret containing the auth key
70+
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"`
71+
72+
// +optional
73+
// CreatedAt is when the key was created
74+
CreatedAt *metav1.Time `json:"createdAt,omitempty"`
75+
76+
// +optional
77+
// ExpiresAt is when the key expires
78+
ExpiresAt *metav1.Time `json:"expiresAt,omitempty"`
79+
80+
// +optional
81+
// Ready indicates if the auth key is ready for use
82+
Ready bool `json:"ready,omitempty"`
83+
84+
// +optional
85+
// Phase represents the current phase of the auth key
86+
Phase string `json:"phase,omitempty"`
87+
88+
// conditions represent the current state of the AuthKey resource.
89+
// +listType=map
90+
// +listMapKey=type
91+
// +optional
92+
Conditions []metav1.Condition `json:"conditions,omitempty"`
93+
}
94+
95+
// +kubebuilder:object:root=true
96+
// +kubebuilder:subresource:status
97+
// +kubebuilder:printcolumn:name="READY",type="boolean",JSONPath=".status.ready",description="Auth key ready status"
98+
// +kubebuilder:printcolumn:name="TAILNET",type="string",JSONPath=".spec.tailnetRef.name",description="Tailnet reference"
99+
// +kubebuilder:printcolumn:name="EPHEMERAL",type="boolean",JSONPath=".spec.ephemeral",description="Ephemeral key"
100+
// +kubebuilder:printcolumn:name="PREAUTH",type="boolean",JSONPath=".spec.preAuthorized",description="Pre-authorized"
101+
// +kubebuilder:printcolumn:name="EXPIRES",type="date",JSONPath=".status.expiresAt",description="Expiration time"
102+
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
103+
104+
// AuthKey is the Schema for the authkeys API
105+
type AuthKey struct {
106+
metav1.TypeMeta `json:",inline"`
107+
108+
// metadata is a standard object metadata
109+
// +optional
110+
metav1.ObjectMeta `json:"metadata,omitempty,omitzero"`
111+
112+
// spec defines the desired state of AuthKey
113+
// +required
114+
Spec AuthKeySpec `json:"spec"`
115+
116+
// status defines the observed state of AuthKey
117+
// +optional
118+
Status AuthKeyStatus `json:"status,omitempty,omitzero"`
119+
}
120+
121+
// +kubebuilder:object:root=true
122+
123+
// AuthKeyList contains a list of AuthKey
124+
type AuthKeyList struct {
125+
metav1.TypeMeta `json:",inline"`
126+
metav1.ListMeta `json:"metadata,omitempty"`
127+
Items []AuthKey `json:"items"`
128+
}
129+
130+
func init() {
131+
SchemeBuilder.Register(&AuthKey{}, &AuthKeyList{})
132+
}

api/v1alpha1/connector_types.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package v1alpha1
1818

1919
import (
20+
corev1 "k8s.io/api/core/v1"
2021
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2122
)
2223

@@ -74,9 +75,17 @@ type ConnectorSpecSpec struct {
7475
}
7576

7677
type TailscaleConfig struct {
77-
// +kubebuilder:validation:Required
78-
// Auth key used for Tailscale authentication
79-
AuthKey string `json:"authKey"`
78+
// +optional
79+
// Auth key used for Tailscale authentication (mutually exclusive with AuthKeyRef)
80+
AuthKey string `json:"authKey,omitempty"`
81+
82+
// +optional
83+
// AuthKeySecretRef references an existing secret containing TS_AUTH_KEY (mutually exclusive with AuthKey and AuthKeyRef)
84+
AuthKeySecretRef *corev1.SecretKeySelector `json:"authKeySecretRef,omitempty"`
85+
86+
// +optional
87+
// AuthKeyRef references an AuthKey resource to use for authentication
88+
AuthKeyRef *corev1.LocalObjectReference `json:"authKeyRef,omitempty"`
8089

8190
// +kubebuilder:default="stable"
8291
// +optional
@@ -102,8 +111,12 @@ type TailscaleConfig struct {
102111
AcceptDNS bool `json:"acceptDns,omitempty"`
103112

104113
// +optional
105-
// URL for custom Tailscale control server (e.g., Headscale)
114+
// URL for custom Tailscale control server (mutually exclusive with ControlServerRef)
106115
ControlServerUrl string `json:"controlServerUrl,omitempty"`
116+
117+
// +optional
118+
// ControlServerRef references a ControlServer resource to use
119+
ControlServerRef *corev1.LocalObjectReference `json:"controlServerRef,omitempty"`
107120
}
108121

109122
// ConnectorStatus defines the observed state of Connector.

0 commit comments

Comments
 (0)