// Copyright (C) 2023 Marius Schellenberger package cli import ( "bufio" "context" "encoding/json" "errors" "flag" "fmt" "io" "os" "strings" "text/tabwriter" apiclient "git.giftfish.de/ston1th/keyctl/pkg/api/client" clientv1 "git.giftfish.de/ston1th/keyctl/pkg/api/v1/client" "git.giftfish.de/ston1th/keyctl/pkg/api/v1/schema" "git.giftfish.de/ston1th/keyctl/pkg/core" ) var fs = flag.NewFlagSet("", flag.ExitOnError) func exit(err error) { fmt.Fprintf(os.Stderr, "%s\n", err) os.Exit(1) } func Run(arg string) { ctx := context.Background() switch arg { case "new": name, existing, encoding, t, size, err := newKeyFlags() if err != nil { exit(err) } newKey := schema.NewKey{ Name: name, Existing: existing, Size: size, Encoding: core.EncodingFromString(encoding), Type: core.TypeFromString(t), } c := client() k, err := c.CreateKey(ctx, newKey) if err != nil { exit(err) } fmt.Println("Key ID:", k.ID) if k.Type == core.Shamir || k.Type == core.Xor { fmt.Println("Key Type:", k.Type) fmt.Println("Key Share:", k.Key) } case "del": id, force, err := delFlags("key id") if err != nil { exit(err) } if !force && !confirm(os.Stdin) { exit(errors.New("confirmation failed: secret not deleted")) } c := client() err = c.DeleteKey(ctx, id) if err != nil { exit(err) } case "approve": id, err := idFlags("approval id") if err != nil { exit(err) } c := client() err = c.AcceptApproval(ctx, id) if err != nil { exit(err) } case "reject": id, err := idFlags("approval id") if err != nil { exit(err) } c := client() err = c.RejectApproval(ctx, id) if err != nil { exit(err) } case "get": id, share, loop, json, newline, err := getFlags() if err != nil { exit(err) } c := client() var key schema.Key if loop { key, err = c.GetKeyLoop(ctx, id) if err != nil { exit(err) } } else { key, err = c.GetKey(ctx, id) if err != nil { exit(err) } } out, err := key.Print(share) if err != nil { exit(err) } if json { key.Key = out jsonOutput(key) // does not return } if newline { fmt.Println(out) } else { fmt.Print(out) } case "ls": json := outputFlags() c := client() keys, err := c.GetKeys(ctx) if err != nil { exit(err) } if json { jsonOutput(keys) // does not return } print(keys) case "req": json := outputFlags() c := client() approvals, err := c.GetApprovals(ctx) if err != nil { exit(err) } if json { jsonOutput(approvals) // does not return } print(approvals) } os.Exit(0) } func client() (c *clientv1.Client) { opts, err := apiclient.ClientOptionsFromEnv() if err != nil { exit(err) } apic := apiclient.NewClient(opts...) c, err = clientv1.NewClient(apic) if err != nil { exit(err) } return } func jsonOutput(v any) { b, err := json.Marshal(v) if err != nil { exit(err) } fmt.Println(string(b)) os.Exit(0) } func idFlags(desc string) (id string, err error) { fs.StringVar(&id, "id", "", desc) fs.Parse(os.Args[2:]) if id == "" { err = errors.New("missing flag: id") } return } func delFlags(desc string) (id string, force bool, err error) { fs.StringVar(&id, "id", "", desc) fs.BoolVar(&force, "force", false, "do not ask for confirmation") fs.Parse(os.Args[2:]) if id == "" { err = errors.New("missing flag: id") } return } func getFlags() (id, share string, loop, json, newline bool, err error) { fs.StringVar(&id, "id", "", "key id") fs.StringVar(&share, "share", "", "shamir key share") fs.BoolVar(&loop, "loop", false, "ignore network errors and loop until key is received") fs.BoolVar(&json, "json", false, "json output") fs.BoolVar(&newline, "newline", true, "output newline after secret") fs.Parse(os.Args[2:]) if id == "" { err = errors.New("missing flag: id") } if share == "" { share = os.Getenv("KEYCTL_SHARE") } return } func outputFlags() (json bool) { fs.BoolVar(&json, "json", false, "json output") fs.Parse(os.Args[2:]) return } func newKeyFlags() (name, existing, encoding, t string, size int, err error) { fs.StringVar(&name, "name", "", "key name") fs.StringVar(&existing, "key", "", "use existing secret key (optional; input-format: hex; types: plain, shamir)") fs.StringVar(&encoding, "enc", "hex", "key encoding scheme [hex|base32|base64|base64url]") fs.StringVar(&t, "type", "plain", "key type [plain|shamir|xor]") fs.IntVar(&size, "size", 16, "key size in bytes") fs.Parse(os.Args[2:]) if name == "" { err = errors.New("missing flag: name") } return } func print(tw core.TableWriter) { if tw.Len() == 0 { return } w := tabwriter.NewWriter(os.Stdout, 1, 0, 1, ' ', tabwriter.Debug) fmt.Fprintln(w, tw.Fields()) for _, v := range tw.Values() { fmt.Fprintln(w, v) } w.Flush() } func confirm(r io.Reader) (ok bool) { br := bufio.NewReader(r) fmt.Printf("confirm delete [y]: ") res, err := br.ReadString('\n') if err != nil { return } if len(res) < 2 { return } return strings.TrimSpace(res)[0] == 'y' }