package secretsencrypt import ( "bytes" "encoding/json" "fmt" "os" "path/filepath" "strings" "text/tabwriter" "time" "github.com/erikdubbelboer/gspt" "github.com/k3s-io/k3s/pkg/cli/cmds" "github.com/k3s-io/k3s/pkg/clientaccess" "github.com/k3s-io/k3s/pkg/secretsencrypt" "github.com/k3s-io/k3s/pkg/server" "github.com/k3s-io/k3s/pkg/version" "github.com/pkg/errors" "github.com/urfave/cli" "k8s.io/utils/pointer" ) func commandPrep(cfg *cmds.Server) (*clientaccess.Info, error) { // hide process arguments from ps output, since they may contain // database credentials or other secrets. gspt.SetProcTitle(os.Args[0] + " secrets-encrypt") dataDir, err := server.ResolveDataDir(cfg.DataDir) if err != nil { return nil, err } if cfg.Token == "" { fp := filepath.Join(dataDir, "token") tokenByte, err := os.ReadFile(fp) if err != nil { return nil, err } cfg.Token = string(bytes.TrimRight(tokenByte, "\n")) } return clientaccess.ParseAndValidateToken(cmds.ServerConfig.ServerURL, cfg.Token, clientaccess.WithUser("server")) } func wrapServerError(err error) error { return errors.Wrap(err, "see server log for details") } func Enable(app *cli.Context) error { if err := cmds.InitLogging(); err != nil { return err } info, err := commandPrep(&cmds.ServerConfig) if err != nil { return err } b, err := json.Marshal(server.EncryptionRequest{Enable: pointer.Bool(true)}) if err != nil { return err } if err = info.Put("/v1-"+version.Program+"/encrypt/config", b); err != nil { return wrapServerError(err) } fmt.Println("secrets-encryption enabled") return nil } func Disable(app *cli.Context) error { if err := cmds.InitLogging(); err != nil { return err } info, err := commandPrep(&cmds.ServerConfig) if err != nil { return err } b, err := json.Marshal(server.EncryptionRequest{Enable: pointer.Bool(false)}) if err != nil { return err } if err = info.Put("/v1-"+version.Program+"/encrypt/config", b); err != nil { return wrapServerError(err) } fmt.Println("secrets-encryption disabled") return nil } func Status(app *cli.Context) error { if err := cmds.InitLogging(); err != nil { return err } info, err := commandPrep(&cmds.ServerConfig) if err != nil { return err } data, err := info.Get("/v1-" + version.Program + "/encrypt/status") if err != nil { return wrapServerError(err) } status := server.EncryptionState{} if err := json.Unmarshal(data, &status); err != nil { return err } if strings.ToLower(cmds.ServerConfig.EncryptOutput) == "json" { json, err := json.MarshalIndent(status, "", "\t") if err != nil { return err } fmt.Println(string(json)) return nil } if status.Enable == nil { fmt.Println("Encryption Status: Disabled, no configuration file found") return nil } var statusOutput string if *status.Enable { statusOutput += "Encryption Status: Enabled\n" } else { statusOutput += "Encryption Status: Disabled\n" } statusOutput += fmt.Sprintln("Current Rotation Stage:", status.Stage) if status.HashMatch { statusOutput += fmt.Sprintln("Server Encryption Hashes: All hashes match") } else { statusOutput += fmt.Sprintf("Server Encryption Hashes: %s\n", status.HashError) } var tabBuffer bytes.Buffer w := tabwriter.NewWriter(&tabBuffer, 0, 0, 2, ' ', 0) fmt.Fprintf(w, "\n") fmt.Fprintf(w, "Active\tKey Type\tName\n") fmt.Fprintf(w, "------\t--------\t----\n") if status.ActiveKey != "" { fmt.Fprintf(w, " *\t%s\t%s\n", "AES-CBC", status.ActiveKey) } for _, k := range status.InactiveKeys { fmt.Fprintf(w, "\t%s\t%s\n", "AES-CBC", k) } w.Flush() fmt.Println(statusOutput + tabBuffer.String()) return nil } func Prepare(app *cli.Context) error { if err := cmds.InitLogging(); err != nil { return err } info, err := commandPrep(&cmds.ServerConfig) if err != nil { return err } b, err := json.Marshal(server.EncryptionRequest{ Stage: pointer.String(secretsencrypt.EncryptionPrepare), Force: cmds.ServerConfig.EncryptForce, }) if err != nil { return err } if err = info.Put("/v1-"+version.Program+"/encrypt/config", b); err != nil { return wrapServerError(err) } fmt.Println("prepare completed successfully") return nil } func Rotate(app *cli.Context) error { if err := cmds.InitLogging(); err != nil { return err } info, err := commandPrep(&cmds.ServerConfig) if err != nil { return err } b, err := json.Marshal(server.EncryptionRequest{ Stage: pointer.String(secretsencrypt.EncryptionRotate), Force: cmds.ServerConfig.EncryptForce, }) if err != nil { return err } if err = info.Put("/v1-"+version.Program+"/encrypt/config", b); err != nil { return wrapServerError(err) } fmt.Println("rotate completed successfully") return nil } func Reencrypt(app *cli.Context) error { if err := cmds.InitLogging(); err != nil { return err } info, err := commandPrep(&cmds.ServerConfig) if err != nil { return err } b, err := json.Marshal(server.EncryptionRequest{ Stage: pointer.String(secretsencrypt.EncryptionReencryptActive), Force: cmds.ServerConfig.EncryptForce, Skip: cmds.ServerConfig.EncryptSkip, }) if err != nil { return err } if err = info.Put("/v1-"+version.Program+"/encrypt/config", b); err != nil { return wrapServerError(err) } fmt.Println("reencryption started") return nil } func RotateKeys(app *cli.Context) error { if err := cmds.InitLogging(); err != nil { return err } info, err := commandPrep(&cmds.ServerConfig) if err != nil { return err } b, err := json.Marshal(server.EncryptionRequest{ Stage: pointer.String(secretsencrypt.EncryptionRotateKeys), }) if err != nil { return err } timeout := 70 * time.Second if err = info.Put("/v1-"+version.Program+"/encrypt/config", b, clientaccess.WithTimeout(timeout)); err != nil { return wrapServerError(err) } fmt.Println("keys rotated, reencryption started") return nil }