Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions charts/kite/templates/declarative-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{{- if .Values.kiteConfig.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "kite.fullname" . }}-declarative-config
namespace: {{ .Release.Namespace }}
labels:
{{- include "kite.labels" . | nindent 4 }}
data:
{{- if .Values.kiteConfig.oauth }}
oauth.yaml: |
oauth:
providers:
{{- range .Values.kiteConfig.oauth.providers }}
- name: {{ .name | quote }}
{{- if .issuerUrl }}
issuerUrl: {{ .issuerUrl | quote }}
{{- end }}
{{- if .clientId }}
clientId: {{ .clientId | quote }}
{{- end }}
{{- if .clientSecret }}
clientSecret: {{ .clientSecret | quote }}
{{- end }}
{{- if .authUrl }}
authUrl: {{ .authUrl | quote }}
{{- end }}
{{- if .tokenUrl }}
tokenUrl: {{ .tokenUrl | quote }}
{{- end }}
{{- if .userInfoUrl }}
userInfoUrl: {{ .userInfoUrl | quote }}
{{- end }}
{{- if .scopes }}
scopes: {{ .scopes | quote }}
{{- end }}
{{- if not (kindIs "invalid" .enabled) }}
enabled: {{ .enabled }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.kiteConfig.roles }}
roles.yaml: |
roles:
{{- range .Values.kiteConfig.roles }}
- name: {{ .name | quote }}
{{- if .description }}
description: {{ .description | quote }}
{{- end }}
{{- if not (kindIs "invalid" .clusters) }}
clusters:
{{- toYaml .clusters | nindent 10 }}
{{- end }}
{{- if not (kindIs "invalid" .namespaces) }}
namespaces:
{{- toYaml .namespaces | nindent 10 }}
{{- end }}
{{- if not (kindIs "invalid" .resources) }}
resources:
{{- toYaml .resources | nindent 10 }}
{{- end }}
{{- if not (kindIs "invalid" .verbs) }}
verbs:
{{- toYaml .verbs | nindent 10 }}
{{- end }}
{{- if not (kindIs "invalid" .assignments) }}
assignments:
{{- range .assignments }}
- subjectType: {{ .subjectType | quote }}
subject: {{ .subject | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.kiteConfig.generalSettings }}
settings.yaml: |
generalSettings:
{{- toYaml .Values.kiteConfig.generalSettings | nindent 6 }}
{{- end }}
{{- end }}
21 changes: 20 additions & 1 deletion charts/kite/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ spec:
{{- end }}
template:
metadata:
{{- with .Values.podAnnotations }}
{{- if or .Values.podAnnotations .Values.kiteConfig.enabled }}
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.kiteConfig.enabled }}
checksum/declarative-config: {{ include (print $.Template.BasePath "/declarative-config.yaml") . | sha256sum }}
{{- end }}
{{- end }}
labels:
{{- include "kite.labels" . | nindent 8 }}
Expand Down Expand Up @@ -73,6 +78,10 @@ spec:
- name: KITE_BASE
value: {{ .Values.basePath }}
{{- end }}
{{- if .Values.kiteConfig.enabled }}
- name: KITE_CONFIG_DIR
value: /etc/kite/config.d
{{- end }}
{{- with .Values.extraEnvs }}
{{- toYaml . | nindent 12 }}
{{- end }}
Expand All @@ -96,6 +105,11 @@ spec:
- name: {{ include "kite.fullname" . }}-storage
mountPath: {{ .Values.db.sqlite.persistence.mountPath }}
{{- end }}
{{- if .Values.kiteConfig.enabled }}
- name: declarative-config
mountPath: /etc/kite/config.d
readOnly: true
{{- end }}
volumes:
{{- if eq .Values.db.type "sqlite"}}
- name: {{ include "kite.fullname" . }}-storage
Expand All @@ -110,6 +124,11 @@ spec:
emptyDir: {}
{{- end }}
{{- end }}
{{- if .Values.kiteConfig.enabled }}
- name: declarative-config
configMap:
name: {{ include "kite.fullname" . }}-declarative-config
{{- end }}
{{- with .Values.volumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
Expand Down
78 changes: 78 additions & 0 deletions charts/kite/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -305,3 +305,81 @@ nodeSelector: {}
tolerations: []

affinity: {}

# ══════════════════════════════════════════════════════════════════════════════
# Declarative Configuration (GitOps-ready)
# ══════════════════════════════════════════════════════════════════════════════
# When enabled, the chart creates a ConfigMap mounted at /etc/kite/config.d/
# containing YAML config files. Kite watches this directory with fsnotify and
# reconciles OAuth, RBAC, and settings to the database automatically.
#
# Features:
# - Hot reload: changes apply within seconds (no pod restart needed)
# - conf.d pattern: multiple files are merged alphabetically
# - GitOps native: works with ArgoCD, Flux, or any Helm-based pipeline
# - No CRDs required: configuration is a simple ConfigMap
# - Managed resources are tracked: manually-created resources are never touched
# - Orphan cleanup: if you remove a provider/role from config, it gets deleted
#
# For OAuth client secrets, do NOT put them in values.yaml. Instead:
# - Use a Kubernetes Secret and reference it via extraEnvs, or
# - Use External Secrets Operator / Sealed Secrets to inject them
# - The clientSecret field supports env var interpolation at runtime
#
# Example with Azure Entra ID:
# kiteConfig:
# enabled: true
# oauth:
# providers:
# - name: "microsoft-entra-id"
# issuerUrl: "https://login.microsoftonline.com/YOUR_TENANT/v2.0"
# clientId: "YOUR_CLIENT_ID"
# clientSecret: "YOUR_CLIENT_SECRET"
# authUrl: "https://login.microsoftonline.com/YOUR_TENANT/oauth2/v2.0/authorize"
# tokenUrl: "https://login.microsoftonline.com/YOUR_TENANT/oauth2/v2.0/token"
# userInfoUrl: "https://graph.microsoft.com/oidc/userinfo"
# scopes: "openid profile email User.Read"
# roles:
# - name: admin
# assignments:
# - { subjectType: group, subject: "aad-group-id-for-admins" }
# - name: viewer
# assignments:
# - { subjectType: group, subject: "aad-group-id-for-viewers" }
# - name: project-alpha-dev
# description: "Developer access for Project Alpha"
# namespaces: ["alpha-dev", "alpha-pre"]
# verbs: ["get", "log", "terminal"]
# assignments:
# - { subjectType: group, subject: "aad-group-id-alpha-devs" }
# generalSettings:
# kubectlEnabled: true
# enableAnalytics: false
# ══════════════════════════════════════════════════════════════════════════════
kiteConfig:
# Set to true to create a ConfigMap with declarative config
enabled: false
# OAuth/OIDC providers
# oauth:
# providers:
# - name: "my-oidc-provider"
# issuerUrl: ""
# clientId: ""
# clientSecret: ""
# scopes: "openid profile email"
# RBAC roles with assignments
# roles:
# - name: admin
# assignments:
# - { subjectType: group, subject: "admin-group-id" }
# - name: custom-role
# description: "Custom scoped role"
# namespaces: ["ns1", "ns2"]
# verbs: ["get"]
# assignments:
# - { subjectType: group, subject: "group-id" }
# General settings
# generalSettings:
# kubectlEnabled: true
# enableAnalytics: false
# enableVersionCheck: true
54 changes: 54 additions & 0 deletions deploy/examples/declarative-config-confd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
## Example: Standalone declarative config files (conf.d pattern)
##
## These files can be placed in /etc/kite/config.d/ (or the path set in
## KITE_CONFIG_DIR) either via ConfigMap mount, hostPath, or any volume type.
##
## Files are read alphabetically and merged:
## - OAuth providers and roles are appended across files
## - General settings use last-write-wins per field
##
## This example shows how to split config across multiple files:
## /etc/kite/config.d/
## 01-oauth.yaml ← OAuth providers
## 02-roles-platform.yaml ← Platform RBAC
## 03-roles-team-alpha.yaml ← Team-specific RBAC
## 04-settings.yaml ← General settings
##
## Each file follows the same KiteConfig schema.

# ── 01-oauth.yaml ────────────────────────────────────────────────────────────
# oauth:
# providers:
# - name: "my-oidc-provider"
# issuerUrl: "https://accounts.google.com"
# clientId: "xxx.apps.googleusercontent.com"
# clientSecret: "GOCSPX-xxx"
# scopes: "openid profile email"
# enabled: true

# ── 02-roles-platform.yaml ──────────────────────────────────────────────────
# roles:
# - name: admin
# assignments:
# - { subjectType: group, subject: "platform-admins-group-id" }
# - name: viewer
# assignments:
# - { subjectType: group, subject: "platform-viewers-group-id" }

# ── 03-roles-team-alpha.yaml ────────────────────────────────────────────────
# roles:
# - name: alpha-developer
# description: "Alpha team developer access"
# namespaces: ["alpha-*"]
# verbs: ["get", "log", "terminal", "create", "update"]
# assignments:
# - { subjectType: group, subject: "alpha-devs-group-id" }

# ── 04-settings.yaml ────────────────────────────────────────────────────────
# generalSettings:
# kubectlEnabled: true
# enableAnalytics: false
# enableVersionCheck: true
# aiAgentEnabled: true
# aiProvider: "openai"
# aiModel: "gpt-4o"
61 changes: 61 additions & 0 deletions deploy/examples/kite-values-entra-id.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
## Example: Declarative config with Azure Entra ID OAuth + RBAC roles
##
## Usage with Helm:
## helm upgrade --install kite kite/kite -f kite-values-entra-id.yaml
##
## Usage without Helm (standalone ConfigMap):
## 1. Create the ConfigMap from this file:
## kubectl create configmap kite-declarative-config \
## --namespace kite \
## --from-file=oauth.yaml=deploy/examples/entra-id-oauth.yaml \
## --from-file=roles.yaml=deploy/examples/entra-id-roles.yaml
## 2. Mount it at /etc/kite/config.d/ in the Kite Deployment
## 3. Set env KITE_CONFIG_DIR=/etc/kite/config.d (optional, it's the default)
##
## For OAuth secrets, create a separate K8s Secret and inject via env vars:
## kubectl create secret generic kite-oauth-credentials \
## --namespace kite \
## --from-literal=CLIENT_ID=your-client-id \
## --from-literal=CLIENT_SECRET=your-client-secret
##
## Then reference in the Helm values extraEnvs or Deployment envFrom.

# ── Helm values.yaml example ─────────────────────────────────────────────────
kiteConfig:
enabled: true
oauth:
providers:
- name: "microsoft-entra-id"
issuerUrl: "https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0"
clientId: "YOUR_CLIENT_ID"
clientSecret: "YOUR_CLIENT_SECRET"
authUrl: "https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/authorize"
tokenUrl: "https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/token"
userInfoUrl: "https://graph.microsoft.com/oidc/userinfo"
scopes: "openid profile email User.Read"
enabled: true
roles:
# Assign Entra ID groups to the built-in admin role
- name: admin
assignments:
- subjectType: group
subject: "00000000-0000-0000-0000-000000000001" # Platform Admins group ID
# Assign Entra ID groups to the built-in viewer role
- name: viewer
assignments:
- subjectType: group
subject: "00000000-0000-0000-0000-000000000002" # Platform Viewers group ID
# Custom project-scoped role
- name: project-alpha-developer
description: "Developer access for Project Alpha namespaces"
clusters: ["*"]
namespaces: ["alpha-dev", "alpha-pre", "alpha-pro"]
resources: ["*"]
verbs: ["get", "log", "terminal"]
assignments:
- subjectType: group
subject: "00000000-0000-0000-0000-000000000003" # Alpha Dev Team group ID
generalSettings:
kubectlEnabled: true
enableAnalytics: false
enableVersionCheck: true
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/anthropics/anthropic-sdk-go v1.26.0
github.com/blang/semver/v4 v4.0.0
github.com/bytedance/mockey v1.4.5
github.com/fsnotify/fsnotify v1.9.0
github.com/gin-contrib/gzip v1.2.5
github.com/gin-gonic/gin v1.12.0
github.com/glebarez/sqlite v1.11.0
Expand Down Expand Up @@ -47,7 +48,6 @@ require (
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/fatih/camelcase v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
Expand Down
9 changes: 9 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/zxh326/kite/pkg/auth"
"github.com/zxh326/kite/pkg/cluster"
"github.com/zxh326/kite/pkg/common"
"github.com/zxh326/kite/pkg/config"
"github.com/zxh326/kite/pkg/handlers"
"github.com/zxh326/kite/pkg/handlers/resources"
"github.com/zxh326/kite/pkg/middleware"
Expand Down Expand Up @@ -254,6 +255,13 @@ func main() {
log.Fatalf("Failed to create ClusterManager: %v", err)
}

// Start declarative config watcher (reads /etc/kite/config.d/*.yaml)
configCtx, configCancel := context.WithCancel(context.Background())
defer configCancel()
if w := config.NewWatcher(); w != nil {
go w.Start(configCtx)
}

base := r.Group(common.Base)
// Setup router
setupAPIRouter(base, cm)
Expand All @@ -277,6 +285,7 @@ func main() {
<-quit

klog.Info("Shutting down server...")
configCancel() // Stop declarative config watcher
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
Expand Down
Loading