Files
kcrypt-challenger/cmd/discovery/main.go
Dimitris Karakasilis f943b01c90 Introduce a cli interface to interace with the challenger client
This will make debugging easier both while developing and in production.
No need to use it through the kcrypt binary anymore, because we might
not actually care about decrypting the disks but rather about getting
the passphrase from the KMS.

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
2025-09-18 13:47:10 +03:00

270 lines
8.8 KiB
Go

package main
import (
"flag"
"fmt"
"os"
"strings"
"github.com/jaypipes/ghw/pkg/block"
"github.com/kairos-io/kairos-challenger/cmd/discovery/client"
"github.com/kairos-io/kairos-sdk/kcrypt/bus"
"github.com/kairos-io/kairos-sdk/types"
"github.com/mudler/go-pluggable"
)
func main() {
// Check if we're being called as a plugin or CLI mode
if len(os.Args) > 1 && isEventDefined(os.Args[1]) {
// Plugin mode - use the go-pluggable interface
exitCode := RunPluginMode()
os.Exit(exitCode)
} else {
// CLI mode - use flags
exitCode := RunCLIMode(os.Args[1:])
os.Exit(exitCode)
}
}
// RunPluginMode implements the go-pluggable interface
// Returns exit code for testability
func RunPluginMode() int {
c, err := client.NewClient()
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating client: %v\n", err)
return 1
}
err = c.Start()
if err != nil {
fmt.Fprintf(os.Stderr, "Error starting plugin: %v\n", err)
return 1
}
return 0
}
// RunCLIMode implements the CLI interface with flags
// Takes args slice and returns exit code for testability
func RunCLIMode(args []string) int {
// Create a new FlagSet for testability
fs := flag.NewFlagSet("kcrypt-discovery-challenger", flag.ContinueOnError)
var (
partitionName = fs.String("partition-name", "", "Name of the partition (at least one identifier required)")
partitionUUID = fs.String("partition-uuid", "", "UUID of the partition (at least one identifier required)")
partitionLabel = fs.String("partition-label", "", "Filesystem label of the partition (at least one identifier required)")
attempts = fs.Int("attempts", 30, "Number of attempts to get the passphrase")
challengerServer = fs.String("challenger-server", "", "URL of the challenger server (overrides config)")
enableMDNS = fs.Bool("mdns", false, "Enable mDNS discovery (overrides config)")
serverCertificate = fs.String("certificate", "", "Server certificate for verification (overrides config)")
debug = fs.Bool("debug", false, "Enable debug logging to show configuration values")
showHelp = fs.Bool("help", false, "Show this help message")
showVersion = fs.Bool("version", false, "Show version information")
)
fs.Usage = func() {
usageText := fmt.Sprintf(`Usage: kcrypt-discovery-challenger [options]
kcrypt-challenger discovery client - Get decryption passphrases for encrypted partitions
This tool can work in two modes:
1. Plugin mode: kcrypt-discovery-challenger %s < partition_data.json
2. CLI mode: kcrypt-discovery-challenger [at least one of --partition-name, --partition-uuid, or --partition-label]
CLI Options:
`, bus.EventDiscoveryPassword)
fmt.Fprint(os.Stderr, usageText)
fs.PrintDefaults()
examplesText := fmt.Sprintf(`
Examples:
# Get passphrase using partition name only
kcrypt-discovery-challenger --partition-name=/dev/sda2
# Get passphrase using UUID only
kcrypt-discovery-challenger --partition-uuid=12345-abcde
# Get passphrase using filesystem label only
kcrypt-discovery-challenger --partition-label=encrypted-data
# Get passphrase with multiple identifiers (provides more options for matching)
kcrypt-discovery-challenger --partition-name=/dev/sda2 --partition-uuid=12345-abcde --partition-label=encrypted-data
# Get passphrase with custom server (override config)
kcrypt-discovery-challenger --partition-label=encrypted-data --challenger-server=https://my-server.com:8082
# Plugin mode (for integration with kcrypt)
echo '{"data": "{\"name\": \"/dev/sda2\", \"uuid\": \"12345-abcde\", \"label\": \"encrypted-data\"}"}' | kcrypt-discovery-challenger %s
Configuration:
The client reads configuration from Kairos configuration files in /oem, /sysroot/oem, or /tmp/oem
Key configuration options under kcrypt.challenger:
- challenger_server: URL of the challenger server
- mdns: Enable mDNS discovery
- certificate: Server certificate for verification
`, bus.EventDiscoveryPassword)
fmt.Fprint(os.Stderr, examplesText)
}
err := fs.Parse(args)
if err != nil {
return 1
}
// Create logger based on debug flag
var logger types.KairosLogger
if *debug {
logger = types.NewKairosLogger("kcrypt-discovery-challenger", "debug", false)
} else {
logger = types.NewKairosLogger("kcrypt-discovery-challenger", "error", false)
}
if *showHelp {
fs.Usage()
return 0
}
if *showVersion {
fmt.Println("kcrypt-challenger discovery client")
fmt.Println("Part of the Kairos project: https://github.com/kairos-io/kcrypt-challenger")
return 0
}
// Validate required flags - at least one identifier must be provided
if *partitionName == "" && *partitionUUID == "" && *partitionLabel == "" {
fmt.Fprintf(os.Stderr, "Error: At least one of partition-name, partition-uuid, or partition-label must be provided\n\n")
fs.Usage()
return 1
}
// Create client with potential CLI overrides
c, err := createClientWithOverrides(*challengerServer, *enableMDNS, *serverCertificate, logger, args)
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating client: %v\n", err)
return 1
}
// Create partition object
partition := &block.Partition{
Name: *partitionName,
UUID: *partitionUUID,
FilesystemLabel: *partitionLabel,
}
// Log partition information
logger.Debugf("Partition details:")
logger.Debugf(" Name: %s", partition.Name)
logger.Debugf(" UUID: %s", partition.UUID)
logger.Debugf(" Label: %s", partition.FilesystemLabel)
logger.Debugf(" Attempts: %d", *attempts)
// Get the passphrase using the same backend logic as the plugin
fmt.Fprintf(os.Stderr, "Requesting passphrase for partition %s (UUID: %s, Label: %s)...\n",
*partitionName, *partitionUUID, *partitionLabel)
passphrase, err := c.GetPassphrase(partition, *attempts)
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting passphrase: %v\n", err)
// Check if log file exists and show relevant information
if logContent, readErr := os.ReadFile(client.LOGFILE); readErr == nil {
fmt.Fprintf(os.Stderr, "\nDebug information from %s:\n%s\n", client.LOGFILE, string(logContent))
}
return 1
}
// Output the passphrase to stdout (this is what tools expect)
fmt.Print(passphrase)
fmt.Fprintf(os.Stderr, "\nPassphrase retrieved successfully\n")
return 0
}
// isEventDefined checks whether an event is defined in the bus.
// It accepts strings or EventType, returns a boolean indicating that
// the event was defined among the events emitted by the bus.
func isEventDefined(i interface{}) bool {
checkEvent := func(e pluggable.EventType) bool {
if e == bus.EventDiscoveryPassword {
return true
}
return false
}
switch f := i.(type) {
case string:
return checkEvent(pluggable.EventType(f))
case pluggable.EventType:
return checkEvent(f)
default:
return false
}
}
// createClientWithOverrides creates a client and applies CLI flag overrides to the config
func createClientWithOverrides(serverURL string, enableMDNS bool, certificate string, logger types.KairosLogger, args []string) (*client.Client, error) {
// Start with the default config from files
c, err := client.NewClient()
if err != nil {
return nil, err
}
// Log the original configuration values
logger.Debugf("Original configuration:")
logger.Debugf(" Server: %s", c.Config.Kcrypt.Challenger.Server)
logger.Debugf(" MDNS: %t", c.Config.Kcrypt.Challenger.MDNS)
logger.Debugf(" Certificate: %s", maskSensitiveString(c.Config.Kcrypt.Challenger.Certificate))
// Apply CLI overrides if provided
if serverURL != "" {
logger.Debugf("Overriding server URL: %s -> %s", c.Config.Kcrypt.Challenger.Server, serverURL)
c.Config.Kcrypt.Challenger.Server = serverURL
}
// For boolean flags, check if the flag was explicitly provided in the args
mdnsSet := false
for _, arg := range args {
if arg == "-mdns" || arg == "--mdns" ||
strings.HasPrefix(arg, "-mdns=") || strings.HasPrefix(arg, "--mdns=") {
mdnsSet = true
break
}
}
if mdnsSet {
logger.Debugf("Overriding MDNS setting: %t -> %t", c.Config.Kcrypt.Challenger.MDNS, enableMDNS)
c.Config.Kcrypt.Challenger.MDNS = enableMDNS
}
if certificate != "" {
logger.Debugf("Overriding certificate: %s -> %s",
maskSensitiveString(c.Config.Kcrypt.Challenger.Certificate),
maskSensitiveString(certificate))
c.Config.Kcrypt.Challenger.Certificate = certificate
}
// Log the final configuration values
logger.Debugf("Final configuration:")
logger.Debugf(" Server: %s", c.Config.Kcrypt.Challenger.Server)
logger.Debugf(" MDNS: %t", c.Config.Kcrypt.Challenger.MDNS)
logger.Debugf(" Certificate: %s", maskSensitiveString(c.Config.Kcrypt.Challenger.Certificate))
return c, nil
}
// maskSensitiveString masks certificate paths/content for logging
func maskSensitiveString(s string) string {
if s == "" {
return "<empty>"
}
if len(s) <= 10 {
return strings.Repeat("*", len(s))
}
// Show first 3 and last 3 characters with * in between
return s[:3] + strings.Repeat("*", len(s)-6) + s[len(s)-3:]
}