feat: initial theta-agent repository structure and security model
This commit is contained in:
@@ -0,0 +1,44 @@
|
|||||||
|
# Theta Agent
|
||||||
|
|
||||||
|
Theta Agent is a unified endpoint management daemon for the theta42 stack. It replaces legacy bash installation scripts (like `ldap-client`) and one-way metric scripts (`telemetry-agent`) with a powerful, 2-way Command & Control (C2) Go daemon.
|
||||||
|
|
||||||
|
The agent dials out to the central SSO Manager via a persistent WebSocket connection, enabling:
|
||||||
|
- **Continuous Telemetry:** Streams CPU/RAM/ZFS/GPU health to the central inventory.
|
||||||
|
- **Dynamic Discovery:** Automatically updates host IP and metadata on changes.
|
||||||
|
- **Remote Operations:** Allows SSO Manager administrators to remotely configure LDAP, restart systemd services, or execute maintenance scripts.
|
||||||
|
|
||||||
|
## The Security Model (Blast Radius & Zero-Trust)
|
||||||
|
|
||||||
|
Because Theta Agent runs as `root` (required to configure `/etc/sssd/sssd.conf`, restart services, and read hardware sensors), it represents a high-value target. If the central SSO Manager were compromised, a naive agent would allow an attacker to gain root shell execution on every server in the fleet.
|
||||||
|
|
||||||
|
To prevent lateral movement and contain the blast radius, **Theta Agent operates on a strict, local-first capability matrix.**
|
||||||
|
|
||||||
|
### 1. Local Configuration Wins
|
||||||
|
The agent will **only** execute commands that are explicitly enabled in its local configuration file (`/etc/theta/agent.yml`).
|
||||||
|
- By default, the agent is locked down to read-only telemetry and basic LDAP configuration.
|
||||||
|
- The central SSO Manager cannot override these settings. An administrator must physically (or via local config management) edit the local `agent.yml` file to grant the agent more permissions.
|
||||||
|
|
||||||
|
### 2. The Capability Matrix
|
||||||
|
Capabilities are segmented into modules. You only enable what a specific server needs:
|
||||||
|
|
||||||
|
| Capability | Risk Level | Description |
|
||||||
|
|------------|------------|-------------|
|
||||||
|
| `telemetry` | Safe | Read-only. Pushes system metrics back to the SSO Manager. |
|
||||||
|
| `configure_ldap` | Moderate | Allows the SSO manager to push down an updated SSSD configuration file. |
|
||||||
|
| `reboot` | High | Allows the SSO Manager to trigger a system reboot. |
|
||||||
|
| `service_control` | High | Allows starting/stopping/restarting systemd services. **Must be scoped** to specific services (e.g., `['gitea', 'nginx']`). |
|
||||||
|
| `arbitrary_bash` | CRITICAL | Allows the execution of raw bash scripts sent from the SSO Manager. Useful for GitOps deployments on worker nodes, but highly dangerous. |
|
||||||
|
|
||||||
|
### 3. Outbound-Only Communication
|
||||||
|
The agent does not open any listening ports on the host firewall. It uses a long-lived outbound WebSocket connection to the SSO Manager.
|
||||||
|
|
||||||
|
### 4. Cryptographic Authentication
|
||||||
|
Every agent is issued a unique, long-lived host token during installation. The SSO Manager verifies this token to ensure commands are only routed to the intended host, and telemetry is properly attributed.
|
||||||
|
|
||||||
|
## Example Configuration
|
||||||
|
|
||||||
|
See `agent.yml.example` for a secure baseline configuration.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
*(Coming soon: Build instructions and `theta-agent install` guide)*
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# theta-agent configuration file
|
||||||
|
# Default location: /etc/theta/agent.yml
|
||||||
|
|
||||||
|
server_url: "https://sso.example.com"
|
||||||
|
auth_token: "REPLACE_WITH_AGENT_TOKEN"
|
||||||
|
location: "default" # Location identifier (e.g., site, datacenter) for naming
|
||||||
|
|
||||||
|
capabilities:
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Basic Capabilities (Safe, read-only or infrastructure management)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
# Push CPU, RAM, GPU, and ZFS metrics to the SSO Manager
|
||||||
|
telemetry: true
|
||||||
|
|
||||||
|
# Allow the SSO Manager to push down SSSD and SSH keys configuration
|
||||||
|
configure_ldap: true
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Advanced Capabilities (High risk, remote operations)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
# Allow remote system reboots via the SSO Manager
|
||||||
|
reboot: false
|
||||||
|
|
||||||
|
# Allow restarting, starting, or stopping specific systemd services.
|
||||||
|
# Must be an explicit list of allowed service names.
|
||||||
|
# Example: ["gitea", "nginx", "docker"]
|
||||||
|
# Setting to true or [] denies all.
|
||||||
|
service_control: []
|
||||||
|
|
||||||
|
# CRITICAL: Allow the execution of raw bash scripts sent from the SSO Manager.
|
||||||
|
# Useful for GitOps deployments, but allows remote code execution.
|
||||||
|
arbitrary_bash: false
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Capabilities struct {
|
||||||
|
Telemetry bool `yaml:"telemetry"`
|
||||||
|
ConfigureLDAP bool `yaml:"configure_ldap"`
|
||||||
|
Reboot bool `yaml:"reboot"`
|
||||||
|
ServiceControl []string `yaml:"service_control"`
|
||||||
|
ArbitraryBash bool `yaml:"arbitrary_bash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
ServerURL string `yaml:"server_url"`
|
||||||
|
AuthToken string `yaml:"auth_token"`
|
||||||
|
Location string `yaml:"location"`
|
||||||
|
Capabilities Capabilities `yaml:"capabilities"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfig(path string) (*Config, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open config file: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var cfg Config
|
||||||
|
decoder := yaml.NewDecoder(file)
|
||||||
|
if err := decoder.Decode(&cfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode YAML config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanManageService checks if a specific service is permitted to be restarted/stopped
|
||||||
|
func (c *Capabilities) CanManageService(serviceName string) bool {
|
||||||
|
for _, allowed := range c.ServiceControl {
|
||||||
|
if allowed == serviceName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
module github.com/theta42/theta-agent
|
||||||
|
|
||||||
|
go 1.22.2
|
||||||
|
|
||||||
|
require gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.Println("Starting Theta Agent...")
|
||||||
|
|
||||||
|
// Attempt to load configuration
|
||||||
|
configPath := "/etc/theta/agent.yml"
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
configPath = os.Args[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error loading configuration from %s: %v", configPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Connecting to SSO Manager at %s", cfg.ServerURL)
|
||||||
|
log.Printf("Loaded capabilities: Telemetry=%v, LDAP=%v, Reboot=%v, Bash=%v",
|
||||||
|
cfg.Capabilities.Telemetry,
|
||||||
|
cfg.Capabilities.ConfigureLDAP,
|
||||||
|
cfg.Capabilities.Reboot,
|
||||||
|
cfg.Capabilities.ArbitraryBash,
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Initialize WebSocket connection to SSO Manager
|
||||||
|
// TODO: Start telemetry background loop if capabilities.Telemetry == true
|
||||||
|
|
||||||
|
// Block until signal is received
|
||||||
|
sigs := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-sigs
|
||||||
|
|
||||||
|
fmt.Println("Shutting down Theta Agent...")
|
||||||
|
}
|
||||||
Executable
BIN
Binary file not shown.
Reference in New Issue
Block a user