kedge/internal/config/config.go
Tyler King 6058e62348 Initial commit: Kedge network automation platform
Go-based network automation with YANG models, gRPC, Ansible,
Terraform, and Kubernetes integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 12:09:30 -05:00

154 lines
5 KiB
Go

package config
import (
"fmt"
"os"
"time"
)
// Config is the top-level configuration for the Kedge DaemonSet.
type Config struct {
NodeID string `json:"node_id"`
ClusterID string `json:"cluster_id"`
Mesh MeshConfig `json:"mesh"`
VLAN VLANConfig `json:"vlan"`
Shellstream ShellstreamConfig `json:"shellstream"`
Quartermaster QuartermasterConfig `json:"quartermaster"`
Underlay UnderlayConfig `json:"underlay"`
Health HealthConfig `json:"health"`
Headscale HeadscaleConfig `json:"headscale"`
}
// MeshConfig configures the WireGuard overlay mesh.
type MeshConfig struct {
Enabled bool `json:"enabled"`
InterfaceName string `json:"interface_name"`
ListenPort int `json:"listen_port"`
PrivateKeyPath string `json:"private_key_path"`
HealthInterval time.Duration `json:"health_interval"`
DeadPeerTimeout time.Duration `json:"dead_peer_timeout"`
InitialPeers []PeerEntry `json:"initial_peers"`
}
// PeerEntry defines a WireGuard peer from configuration.
type PeerEntry struct {
PublicKey string `json:"public_key"`
Endpoint string `json:"endpoint"`
AllowedIPs []string `json:"allowed_ips"`
}
// VLANConfig configures underlay VLAN interfaces.
type VLANConfig struct {
Enabled bool `json:"enabled"`
VLANs []VLANEntry `json:"vlans"`
}
// VLANEntry defines a VLAN to be managed on the node.
type VLANEntry struct {
ID int `json:"id"`
ParentInterface string `json:"parent_interface"`
Subnet string `json:"subnet"`
Description string `json:"description"`
}
// ShellstreamConfig configures the Shellstream handshake listener.
type ShellstreamConfig struct {
ListenAddr string `json:"listen_addr"`
ClusterID string `json:"cluster_id"`
TrustBundlePath string `json:"trust_bundle_path"`
AccordPolicyPath string `json:"accord_policy_path"`
}
// QuartermasterConfig configures the Quartermaster gRPC client.
type QuartermasterConfig struct {
Endpoint string `json:"endpoint"`
UseTLS bool `json:"use_tls"`
CertPath string `json:"cert_path,omitempty"`
KeyPath string `json:"key_path,omitempty"`
CAPath string `json:"ca_path,omitempty"`
}
// UnderlayConfig configures the YANG watcher and compilation trigger.
type UnderlayConfig struct {
Enabled bool `json:"enabled"`
Namespace string `json:"namespace"`
ConfigMapName string `json:"configmap_name"`
CompilerPath string `json:"compiler_path"`
}
// HealthConfig configures the health and metrics server.
type HealthConfig struct {
ListenAddr string `json:"listen_addr"`
}
// HeadscaleConfig configures Headscale peer discovery.
type HeadscaleConfig struct {
Enabled bool `json:"enabled"`
Endpoint string `json:"endpoint"`
APIKey string `json:"api_key"`
PollInterval time.Duration `json:"poll_interval"`
}
// Load reads the DaemonSet configuration from environment variables and
// the config file at /etc/kedge/config.json.
func Load() (*Config, error) {
cfg := &Config{
NodeID: getEnv("KEDGE_NODE_ID", ""),
ClusterID: getEnv("KEDGE_CLUSTER_ID", ""),
Mesh: MeshConfig{
Enabled: getEnv("KEDGE_OVERLAY_ENABLED", "true") == "true",
InterfaceName: getEnv("KEDGE_WG_INTERFACE", "wg0"),
ListenPort: 51820,
PrivateKeyPath: getEnv("KEDGE_WG_PRIVATE_KEY", "/etc/kedge/wg-private.key"),
HealthInterval: 30 * time.Second,
DeadPeerTimeout: 5 * time.Minute,
},
VLAN: VLANConfig{
Enabled: getEnv("KEDGE_UNDERLAY_ENABLED", "false") == "true",
},
Shellstream: ShellstreamConfig{
ListenAddr: getEnv("KEDGE_SHELLSTREAM_ADDR", ":8443"),
ClusterID: getEnv("KEDGE_CLUSTER_ID", ""),
TrustBundlePath: getEnv("KEDGE_TRUST_BUNDLE", "/run/spire/bundle/bundle.pem"),
AccordPolicyPath: getEnv("KEDGE_ACCORD_POLICY", "/etc/kedge/accord-policy.json"),
},
Quartermaster: QuartermasterConfig{
Endpoint: getEnv("KEDGE_QM_ENDPOINT", "quartermaster.guildhouse.svc:50051"),
},
Underlay: UnderlayConfig{
Enabled: getEnv("KEDGE_UNDERLAY_ENABLED", "false") == "true",
Namespace: getEnv("KEDGE_UNDERLAY_NAMESPACE", "kedge"),
ConfigMapName: getEnv("KEDGE_UNDERLAY_CONFIGMAP", "kedge-underlay"),
CompilerPath: getEnv("KEDGE_YANG_COMPILER", "/opt/kedge/yang/compiler/compile.py"),
},
Health: HealthConfig{
ListenAddr: getEnv("KEDGE_HEALTH_ADDR", ":9090"),
},
Headscale: HeadscaleConfig{
Enabled: getEnv("KEDGE_HEADSCALE_ENABLED", "false") == "true",
Endpoint: getEnv("KEDGE_HEADSCALE_ENDPOINT", ""),
PollInterval: 60 * time.Second,
},
}
if cfg.NodeID == "" {
hostname, err := os.Hostname()
if err != nil {
return nil, fmt.Errorf("KEDGE_NODE_ID not set and hostname unavailable: %w", err)
}
cfg.NodeID = hostname
}
if cfg.ClusterID == "" {
return nil, fmt.Errorf("KEDGE_CLUSTER_ID is required")
}
return cfg, nil
}
func getEnv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}