Go-based network automation with YANG models, gRPC, Ansible, Terraform, and Kubernetes integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
154 lines
5 KiB
Go
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
|
|
}
|