2022-10-09 22:32:56 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
2025-09-18 13:47:10 +03:00
|
|
|
"strings"
|
2022-10-09 22:32:56 +00:00
|
|
|
|
2025-09-18 13:47:10 +03:00
|
|
|
"github.com/jaypipes/ghw/pkg/block"
|
2023-01-02 15:56:10 +02:00
|
|
|
"github.com/kairos-io/kairos-challenger/cmd/discovery/client"
|
2025-09-24 12:57:32 +03:00
|
|
|
"github.com/kairos-io/kairos-challenger/pkg/constants"
|
2025-05-06 11:18:50 +02:00
|
|
|
"github.com/kairos-io/kairos-sdk/kcrypt/bus"
|
2025-09-18 13:47:10 +03:00
|
|
|
"github.com/kairos-io/kairos-sdk/types"
|
2025-09-24 12:57:32 +03:00
|
|
|
"github.com/kairos-io/tpm-helpers"
|
|
|
|
"github.com/spf13/cobra"
|
2022-10-09 22:32:56 +00:00
|
|
|
)
|
|
|
|
|
2025-09-24 13:04:13 +03:00
|
|
|
// GetFlags holds all flags specific to the get command
|
|
|
|
type GetFlags struct {
|
|
|
|
PartitionName string
|
|
|
|
PartitionUUID string
|
|
|
|
PartitionLabel string
|
|
|
|
Attempts int
|
|
|
|
ChallengerServer string
|
|
|
|
EnableMDNS bool
|
|
|
|
ServerCertificate string
|
|
|
|
}
|
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
var (
|
2025-09-24 13:04:13 +03:00
|
|
|
// Global/persistent flags
|
|
|
|
debug bool
|
2025-09-24 12:57:32 +03:00
|
|
|
)
|
2025-09-18 13:47:10 +03:00
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
// rootCmd represents the base command (TPM hash generation)
|
|
|
|
var rootCmd = &cobra.Command{
|
|
|
|
Use: "kcrypt-discovery-challenger",
|
|
|
|
Short: "kcrypt-challenger discovery client",
|
|
|
|
Long: `kcrypt-challenger discovery client
|
2025-09-18 13:47:10 +03:00
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
This tool provides TPM-based operations for encrypted partition management.
|
|
|
|
By default, it outputs the TPM hash for this device.
|
2025-09-18 13:47:10 +03:00
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
Configuration:
|
|
|
|
The client reads configuration from Kairos configuration files in the following directories:
|
|
|
|
- /oem (during installation from ISO)
|
|
|
|
- /sysroot/oem (on installed systems during initramfs)
|
|
|
|
- /tmp/oem (when running in hooks)
|
|
|
|
|
|
|
|
Configuration format (YAML):
|
|
|
|
kcrypt:
|
|
|
|
challenger:
|
|
|
|
challenger_server: "https://my-server.com:8082" # Server URL
|
|
|
|
mdns: true # Enable mDNS discovery
|
|
|
|
certificate: "/path/to/server-cert.pem" # Server certificate
|
|
|
|
nv_index: "0x1500000" # TPM NV index (offline mode)
|
|
|
|
c_index: "0x1500001" # TPM certificate index
|
|
|
|
tpm_device: "/dev/tpmrm0" # TPM device path`,
|
|
|
|
Example: ` # Get TPM hash for this device (default)
|
|
|
|
kcrypt-discovery-challenger
|
|
|
|
|
|
|
|
# Get passphrase for encrypted partition
|
|
|
|
kcrypt-discovery-challenger get --partition-name=/dev/sda2
|
|
|
|
|
|
|
|
# Run plugin event
|
|
|
|
kcrypt-discovery-challenger discovery.password`,
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
return runTPMHash()
|
|
|
|
},
|
|
|
|
}
|
2025-09-18 13:47:10 +03:00
|
|
|
|
2025-09-24 13:04:13 +03:00
|
|
|
// newGetCmd creates the get command with its flags
|
|
|
|
func newGetCmd() *cobra.Command {
|
|
|
|
flags := &GetFlags{}
|
|
|
|
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
Use: "get",
|
|
|
|
Short: "Get passphrase for encrypted partition",
|
|
|
|
Long: `Get passphrase for encrypted partition using TPM attestation.
|
2025-09-24 12:57:32 +03:00
|
|
|
|
|
|
|
This command retrieves passphrases for encrypted partitions by communicating
|
|
|
|
with a challenger server using TPM-based attestation. At least one partition
|
|
|
|
identifier (name, UUID, or label) must be provided.
|
|
|
|
|
|
|
|
The command uses configuration from the root command's config files, but flags
|
|
|
|
can override specific settings:
|
|
|
|
--challenger-server Override kcrypt.challenger.challenger_server
|
|
|
|
--mdns Override kcrypt.challenger.mdns
|
|
|
|
--certificate Override kcrypt.challenger.certificate`,
|
2025-09-24 13:04:13 +03:00
|
|
|
Example: ` # Get passphrase using partition name
|
2025-09-24 12:57:32 +03:00
|
|
|
kcrypt-discovery-challenger get --partition-name=/dev/sda2
|
|
|
|
|
|
|
|
# Get passphrase using UUID
|
|
|
|
kcrypt-discovery-challenger get --partition-uuid=12345-abcde
|
|
|
|
|
|
|
|
# Get passphrase using filesystem label
|
|
|
|
kcrypt-discovery-challenger get --partition-label=encrypted-data
|
|
|
|
|
|
|
|
# Get passphrase with multiple identifiers
|
|
|
|
kcrypt-discovery-challenger get --partition-name=/dev/sda2 --partition-uuid=12345-abcde --partition-label=encrypted-data
|
|
|
|
|
|
|
|
# Get passphrase with custom server
|
|
|
|
kcrypt-discovery-challenger get --partition-label=encrypted-data --challenger-server=https://my-server.com:8082`,
|
2025-09-24 13:04:13 +03:00
|
|
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
// Validate that at least one partition identifier is provided
|
|
|
|
if flags.PartitionName == "" && flags.PartitionUUID == "" && flags.PartitionLabel == "" {
|
|
|
|
return fmt.Errorf("at least one of --partition-name, --partition-uuid, or --partition-label must be provided")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
return runGetPassphrase(flags)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register flags
|
|
|
|
cmd.Flags().StringVar(&flags.PartitionName, "partition-name", "", "Name of the partition (at least one identifier required)")
|
|
|
|
cmd.Flags().StringVar(&flags.PartitionUUID, "partition-uuid", "", "UUID of the partition (at least one identifier required)")
|
|
|
|
cmd.Flags().StringVar(&flags.PartitionLabel, "partition-label", "", "Filesystem label of the partition (at least one identifier required)")
|
|
|
|
cmd.Flags().IntVar(&flags.Attempts, "attempts", 30, "Number of attempts to get the passphrase")
|
|
|
|
cmd.Flags().StringVar(&flags.ChallengerServer, "challenger-server", "", "URL of the challenger server (overrides config)")
|
|
|
|
cmd.Flags().BoolVar(&flags.EnableMDNS, "mdns", false, "Enable mDNS discovery (overrides config)")
|
|
|
|
cmd.Flags().StringVar(&flags.ServerCertificate, "certificate", "", "Server certificate for verification (overrides config)")
|
|
|
|
|
|
|
|
return cmd
|
2025-09-24 12:57:32 +03:00
|
|
|
}
|
2025-09-18 13:47:10 +03:00
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
// pluginCmd represents the plugin event commands
|
|
|
|
var pluginCmd = &cobra.Command{
|
|
|
|
Use: string(bus.EventDiscoveryPassword),
|
|
|
|
Short: fmt.Sprintf("Run %s plugin event", bus.EventDiscoveryPassword),
|
|
|
|
Long: fmt.Sprintf(`Run the %s plugin event.
|
|
|
|
|
|
|
|
This command runs in plugin mode, reading JSON partition data from stdin
|
|
|
|
and outputting the passphrase to stdout. This is used for integration
|
|
|
|
with kcrypt and other tools.`, bus.EventDiscoveryPassword),
|
|
|
|
Example: fmt.Sprintf(` # Plugin mode (for integration with kcrypt)
|
|
|
|
echo '{"data": "{\"name\": \"/dev/sda2\", \"uuid\": \"12345-abcde\", \"label\": \"encrypted-data\"}"}' | kcrypt-discovery-challenger %s`, bus.EventDiscoveryPassword),
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
return runPluginMode()
|
|
|
|
},
|
|
|
|
}
|
2025-09-18 13:47:10 +03:00
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
func init() {
|
2025-09-24 13:04:13 +03:00
|
|
|
// Global/persistent flags (available to all commands)
|
2025-09-24 12:57:32 +03:00
|
|
|
rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug logging")
|
|
|
|
|
|
|
|
// Add subcommands
|
2025-09-24 13:04:13 +03:00
|
|
|
rootCmd.AddCommand(newGetCmd())
|
2025-09-24 12:57:32 +03:00
|
|
|
rootCmd.AddCommand(pluginCmd)
|
|
|
|
}
|
2025-09-18 13:47:10 +03:00
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
func main() {
|
|
|
|
if err := rootCmd.Execute(); err != nil {
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
2025-09-18 13:47:10 +03:00
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
// runTPMHash handles the root command - TPM hash generation
|
|
|
|
func runTPMHash() error {
|
|
|
|
// Create logger based on debug flag
|
|
|
|
var logger types.KairosLogger
|
|
|
|
if debug {
|
|
|
|
logger = types.NewKairosLogger("kcrypt-discovery-challenger", "debug", false)
|
|
|
|
logger.Debugf("Debug mode enabled for TPM hash generation")
|
|
|
|
} else {
|
|
|
|
logger = types.NewKairosLogger("kcrypt-discovery-challenger", "error", false)
|
|
|
|
}
|
2025-09-18 13:47:10 +03:00
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
// Initialize AK Manager with the standard handle file
|
|
|
|
logger.Debugf("Initializing AK Manager with handle file: %s", constants.AKBlobFile)
|
|
|
|
akManager, err := tpm.NewAKManager(tpm.WithAKHandleFile(constants.AKBlobFile))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("creating AK manager: %w", err)
|
|
|
|
}
|
|
|
|
logger.Debugf("AK Manager initialized successfully")
|
2025-09-18 13:47:10 +03:00
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
// Ensure AK exists (create if necessary)
|
|
|
|
logger.Debugf("Getting or creating AK")
|
|
|
|
_, err = akManager.GetOrCreateAK()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("getting/creating AK: %w", err)
|
|
|
|
}
|
|
|
|
logger.Debugf("AK obtained/created successfully")
|
2025-09-18 13:47:10 +03:00
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
// Get attestation data (includes EK)
|
|
|
|
logger.Debugf("Getting attestation data")
|
|
|
|
ek, _, err := akManager.GetAttestationData()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("getting attestation data: %w", err)
|
2025-09-18 13:47:10 +03:00
|
|
|
}
|
2025-09-24 12:57:32 +03:00
|
|
|
logger.Debugf("Attestation data retrieved successfully")
|
2025-09-18 13:47:10 +03:00
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
// Compute TPM hash from EK
|
|
|
|
logger.Debugf("Computing TPM hash from EK")
|
|
|
|
tpmHash, err := tpm.DecodePubHash(ek)
|
2025-09-18 13:47:10 +03:00
|
|
|
if err != nil {
|
2025-09-24 12:57:32 +03:00
|
|
|
return fmt.Errorf("computing TPM hash: %w", err)
|
2025-09-18 13:47:10 +03:00
|
|
|
}
|
2025-09-24 12:57:32 +03:00
|
|
|
logger.Debugf("TPM hash computed successfully: %s", tpmHash)
|
|
|
|
|
|
|
|
// Output the TPM hash to stdout
|
|
|
|
fmt.Print(tpmHash)
|
|
|
|
return nil
|
|
|
|
}
|
2025-09-18 13:47:10 +03:00
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
// runGetPassphrase handles the get subcommand - passphrase retrieval
|
2025-09-24 13:04:13 +03:00
|
|
|
func runGetPassphrase(flags *GetFlags) error {
|
2025-09-18 13:47:10 +03:00
|
|
|
// Create logger based on debug flag
|
|
|
|
var logger types.KairosLogger
|
2025-09-24 12:57:32 +03:00
|
|
|
if debug {
|
2025-09-18 13:47:10 +03:00
|
|
|
logger = types.NewKairosLogger("kcrypt-discovery-challenger", "debug", false)
|
|
|
|
} else {
|
|
|
|
logger = types.NewKairosLogger("kcrypt-discovery-challenger", "error", false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create client with potential CLI overrides
|
2025-09-24 13:04:13 +03:00
|
|
|
c, err := createClientWithOverrides(flags.ChallengerServer, flags.EnableMDNS, flags.ServerCertificate, logger)
|
2022-10-09 22:32:56 +00:00
|
|
|
if err != nil {
|
2025-09-24 12:57:32 +03:00
|
|
|
return fmt.Errorf("creating client: %w", err)
|
2022-10-09 22:32:56 +00:00
|
|
|
}
|
2025-09-18 13:47:10 +03:00
|
|
|
|
|
|
|
// Create partition object
|
|
|
|
partition := &block.Partition{
|
2025-09-24 13:04:13 +03:00
|
|
|
Name: flags.PartitionName,
|
|
|
|
UUID: flags.PartitionUUID,
|
|
|
|
FilesystemLabel: flags.PartitionLabel,
|
2025-09-18 13:47:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
2025-09-24 13:04:13 +03:00
|
|
|
logger.Debugf(" Attempts: %d", flags.Attempts)
|
2025-09-18 13:47:10 +03:00
|
|
|
|
|
|
|
// 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",
|
2025-09-24 13:04:13 +03:00
|
|
|
flags.PartitionName, flags.PartitionUUID, flags.PartitionLabel)
|
2025-09-18 13:47:10 +03:00
|
|
|
|
2025-09-24 13:04:13 +03:00
|
|
|
passphrase, err := c.GetPassphrase(partition, flags.Attempts)
|
2025-09-18 13:47:10 +03:00
|
|
|
if err != nil {
|
2025-09-24 12:57:32 +03:00
|
|
|
return fmt.Errorf("getting passphrase: %w", err)
|
2025-09-18 13:47:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Output the passphrase to stdout (this is what tools expect)
|
|
|
|
fmt.Print(passphrase)
|
|
|
|
fmt.Fprintf(os.Stderr, "\nPassphrase retrieved successfully\n")
|
2025-09-24 12:57:32 +03:00
|
|
|
return nil
|
2022-10-09 22:32:56 +00:00
|
|
|
}
|
2025-05-06 11:18:50 +02:00
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
// runPluginMode handles plugin event commands
|
|
|
|
func runPluginMode() error {
|
|
|
|
// In plugin mode, use quiet=true to log to file instead of console
|
|
|
|
// Log level depends on debug flag, write logs to /var/log/kairos/kcrypt-discovery-challenger.log
|
|
|
|
var logLevel string
|
|
|
|
if debug {
|
|
|
|
logLevel = "debug"
|
|
|
|
} else {
|
|
|
|
logLevel = "error"
|
|
|
|
}
|
2025-05-06 11:18:50 +02:00
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
logger := types.NewKairosLogger("kcrypt-discovery-challenger", logLevel, true)
|
|
|
|
c, err := client.NewClientWithLogger(logger)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("creating client: %w", err)
|
2025-05-06 11:18:50 +02:00
|
|
|
}
|
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
err = c.Start()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("starting plugin: %w", err)
|
2025-05-06 11:18:50 +02:00
|
|
|
}
|
2025-09-24 12:57:32 +03:00
|
|
|
return nil
|
2025-05-06 11:18:50 +02:00
|
|
|
}
|
2025-09-18 13:47:10 +03:00
|
|
|
|
|
|
|
// createClientWithOverrides creates a client and applies CLI flag overrides to the config
|
2025-09-24 12:57:32 +03:00
|
|
|
func createClientWithOverrides(serverURL string, enableMDNS bool, certificate string, logger types.KairosLogger) (*client.Client, error) {
|
2025-09-18 14:29:48 +03:00
|
|
|
// Start with the default config from files and pass the logger
|
|
|
|
c, err := client.NewClientWithLogger(logger)
|
2025-09-18 13:47:10 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2025-09-24 12:57:32 +03:00
|
|
|
// For boolean flags, we can directly use the value since Cobra handles it properly
|
|
|
|
if enableMDNS {
|
2025-09-18 13:47:10 +03:00
|
|
|
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:]
|
|
|
|
}
|