mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 12:46:57 +00:00
- Document WireGuard IPv6 disable, service auth, token security, process isolation - Introduce OramaOS architecture, enrollment flow, and management via Gateway API - Add troubleshooting for RQLite/Olric auth, OramaOS LUKS/enrollment issues
167 lines
5.1 KiB
Go
167 lines
5.1 KiB
Go
// Package unlock implements the genesis node unlock command.
|
|
//
|
|
// When the genesis OramaOS node reboots before enough peers exist for
|
|
// Shamir-based LUKS key reconstruction, the operator must manually provide
|
|
// the LUKS key. This command reads the encrypted genesis key from the
|
|
// node's rootfs, decrypts it with the rootwallet, and sends it to the agent.
|
|
package unlock
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Flags holds parsed command-line flags.
|
|
type Flags struct {
|
|
NodeIP string // WireGuard IP of the OramaOS node
|
|
Genesis bool // Must be set to confirm genesis unlock
|
|
KeyFile string // Path to the encrypted genesis key file (optional override)
|
|
}
|
|
|
|
// Handle processes the unlock command.
|
|
func Handle(args []string) {
|
|
flags, err := parseFlags(args)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if !flags.Genesis {
|
|
fmt.Fprintf(os.Stderr, "Error: --genesis flag is required to confirm genesis unlock\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Step 1: Read the encrypted genesis key from the node
|
|
fmt.Printf("Fetching encrypted genesis key from %s...\n", flags.NodeIP)
|
|
encKey, err := fetchGenesisKey(flags.NodeIP)
|
|
if err != nil && flags.KeyFile == "" {
|
|
fmt.Fprintf(os.Stderr, "Error: could not fetch genesis key from node: %v\n", err)
|
|
fmt.Fprintf(os.Stderr, "You can provide the key file directly with --key-file\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
if flags.KeyFile != "" {
|
|
data, readErr := os.ReadFile(flags.KeyFile)
|
|
if readErr != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: could not read key file: %v\n", readErr)
|
|
os.Exit(1)
|
|
}
|
|
encKey = strings.TrimSpace(string(data))
|
|
}
|
|
|
|
// Step 2: Decrypt with rootwallet
|
|
fmt.Println("Decrypting genesis key with rootwallet...")
|
|
luksKey, err := decryptGenesisKey(encKey)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: decryption failed: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Step 3: Send LUKS key to the agent over WireGuard
|
|
fmt.Printf("Sending LUKS key to agent at %s:9998...\n", flags.NodeIP)
|
|
if err := sendUnlockKey(flags.NodeIP, luksKey); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: unlock failed: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println("Genesis node unlocked successfully.")
|
|
fmt.Println("The node is decrypting and mounting its data partition.")
|
|
}
|
|
|
|
func parseFlags(args []string) (*Flags, error) {
|
|
fs := flag.NewFlagSet("unlock", flag.ContinueOnError)
|
|
fs.SetOutput(os.Stderr)
|
|
|
|
flags := &Flags{}
|
|
fs.StringVar(&flags.NodeIP, "node-ip", "", "WireGuard IP of the OramaOS node (required)")
|
|
fs.BoolVar(&flags.Genesis, "genesis", false, "Confirm genesis node unlock")
|
|
fs.StringVar(&flags.KeyFile, "key-file", "", "Path to encrypted genesis key file (optional)")
|
|
|
|
if err := fs.Parse(args); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if flags.NodeIP == "" {
|
|
return nil, fmt.Errorf("--node-ip is required")
|
|
}
|
|
|
|
return flags, nil
|
|
}
|
|
|
|
// fetchGenesisKey retrieves the encrypted genesis key from the node.
|
|
// The agent serves it at GET /v1/agent/genesis-key (only during genesis unlock mode).
|
|
func fetchGenesisKey(nodeIP string) (string, error) {
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Get(fmt.Sprintf("http://%s:9998/v1/agent/genesis-key", nodeIP))
|
|
if err != nil {
|
|
return "", fmt.Errorf("request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return "", fmt.Errorf("status %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var result struct {
|
|
EncryptedKey string `json:"encrypted_key"`
|
|
}
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return "", fmt.Errorf("invalid response: %w", err)
|
|
}
|
|
|
|
return result.EncryptedKey, nil
|
|
}
|
|
|
|
// decryptGenesisKey decrypts the AES-256-GCM encrypted LUKS key using rootwallet.
|
|
// The key was encrypted with: AES-256-GCM(luksKey, HKDF(rootwalletKey, "genesis-luks"))
|
|
// For now, we use `rw decrypt` if available, or a local HKDF+AES-GCM implementation.
|
|
func decryptGenesisKey(encryptedKey string) ([]byte, error) {
|
|
// Try rw decrypt first
|
|
cmd := exec.Command("rw", "decrypt", encryptedKey, "--purpose", "genesis-luks", "--chain", "evm")
|
|
output, err := cmd.Output()
|
|
if err == nil {
|
|
decoded, decErr := base64.StdEncoding.DecodeString(strings.TrimSpace(string(output)))
|
|
if decErr != nil {
|
|
return nil, fmt.Errorf("failed to decode decrypted key: %w", decErr)
|
|
}
|
|
return decoded, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("rw decrypt failed: %w (is rootwallet installed and initialized?)", err)
|
|
}
|
|
|
|
// sendUnlockKey sends the decrypted LUKS key to the agent's unlock endpoint.
|
|
func sendUnlockKey(nodeIP string, luksKey []byte) error {
|
|
body, _ := json.Marshal(map[string]string{
|
|
"key": base64.StdEncoding.EncodeToString(luksKey),
|
|
})
|
|
|
|
client := &http.Client{Timeout: 30 * time.Second}
|
|
resp, err := client.Post(
|
|
fmt.Sprintf("http://%s:9998/v1/agent/unlock", nodeIP),
|
|
"application/json",
|
|
bytes.NewReader(body),
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("status %d: %s", resp.StatusCode, string(respBody))
|
|
}
|
|
|
|
return nil
|
|
}
|