File
Blob: cmd/client/token.go
| 1 | package client |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "fmt" |
| 6 | "net" |
| 7 | "time" |
| 8 | |
| 9 | "go.miragespace.co/specter/tun/client" |
| 10 | "go.miragespace.co/specter/tun/client/dialer" |
| 11 | |
| 12 | "github.com/quic-go/quic-go" |
| 13 | "github.com/urfave/cli/v3" |
| 14 | "go.uber.org/zap" |
| 15 | ) |
| 16 | |
| 17 | func tokenCommand() *cli.Command { |
| 18 | config := func() cli.Flag { |
| 19 | return &cli.StringFlag{ |
| 20 | Name: "config", |
| 21 | Aliases: []string{"c"}, |
| 22 | Required: true, |
| 23 | Usage: "owner configuration file; stop the owner client before using these commands", |
| 24 | } |
| 25 | } |
| 26 | return &cli.Command{ |
| 27 | Name: "token", |
| 28 | Usage: "Manage domain tokens with a stopped owner client", |
| 29 | Commands: []*cli.Command{ |
| 30 | { |
| 31 | Name: "mint", |
| 32 | Usage: "Mint a bearer token for a registered hostname", |
| 33 | ArgsUsage: "<hostname>", |
| 34 | Flags: []cli.Flag{config(), &cli.StringFlag{ |
| 35 | Name: "expires-at", |
| 36 | Usage: "optional expiry in RFC3339 format (default: no expiry)", |
| 37 | }}, |
| 38 | Action: cmdToken, |
| 39 | }, |
| 40 | { |
| 41 | Name: "list", |
| 42 | Usage: "List grants, including incomplete grants", |
| 43 | ArgsUsage: " ", |
| 44 | Flags: []cli.Flag{config()}, |
| 45 | Action: cmdToken, |
| 46 | }, |
| 47 | { |
| 48 | Name: "revoke", |
| 49 | Usage: "Revoke a grant by its ID", |
| 50 | ArgsUsage: "<id>", |
| 51 | Flags: []cli.Flag{config()}, |
| 52 | Action: cmdToken, |
| 53 | }, |
| 54 | }, |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | func cmdToken(ctx context.Context, cmd *cli.Command) error { |
| 59 | expected := 1 |
| 60 | if cmd.Name == "list" { |
| 61 | expected = 0 |
| 62 | } |
| 63 | if cmd.Args().Len() != expected { |
| 64 | return fmt.Errorf("token %s expects %d argument(s)", cmd.Name, expected) |
| 65 | } |
| 66 | var expiry time.Time |
| 67 | if cmd.String("expires-at") != "" { |
| 68 | parsed, err := time.Parse(time.RFC3339, cmd.String("expires-at")) |
| 69 | if err != nil { |
| 70 | return fmt.Errorf("expires-at must be RFC3339: %w", err) |
| 71 | } |
| 72 | expiry = parsed |
| 73 | } |
| 74 | cfg, err := client.NewConfig(cmd.String("config")) |
| 75 | if err != nil { |
| 76 | return err |
| 77 | } |
| 78 | apex, err := dialer.ParseApex(cfg.Apex) |
| 79 | if err != nil { |
| 80 | return err |
| 81 | } |
| 82 | listener, err := net.ListenPacket("udp", ":0") |
| 83 | if err != nil { |
| 84 | return err |
| 85 | } |
| 86 | defer listener.Close() |
| 87 | quicTransport := &quic.Transport{Conn: listener} |
| 88 | defer quicTransport.Close() |
| 89 | logger := cmd.Root().Metadata["logger"].(*zap.Logger) |
| 90 | _, tp := createTransport(cmd, transportCfg{ |
| 91 | logger: logger, |
| 92 | quicTp: quicTransport, |
| 93 | apex: apex, |
| 94 | }) |
| 95 | defer tp.Stop() |
| 96 | c, err := client.NewClient(ctx, client.ClientConfig{ |
| 97 | Logger: logger, |
| 98 | Configuration: cfg, |
| 99 | ServerTransport: tp, |
| 100 | }) |
| 101 | if err != nil { |
| 102 | return err |
| 103 | } |
| 104 | defer c.Close() |
| 105 | if err := c.Register(ctx); err != nil { |
| 106 | return err |
| 107 | } |
| 108 | switch cmd.Name { |
| 109 | case "mint": |
| 110 | resp, err := c.MintDelegation(ctx, cmd.Args().First(), expiry) |
| 111 | if err != nil { |
| 112 | return err |
| 113 | } |
| 114 | return c.FormatMintedDelegation(resp, cmd.Root().Writer) |
| 115 | case "list": |
| 116 | resp, err := c.ListDelegations(ctx) |
| 117 | if err != nil { |
| 118 | return err |
| 119 | } |
| 120 | return c.FormatDelegations(resp, cmd.Root().Writer) |
| 121 | case "revoke": |
| 122 | resp, err := c.RevokeDelegation(ctx, cmd.Args().First()) |
| 123 | if err != nil { |
| 124 | return err |
| 125 | } |
| 126 | return c.FormatRevokedDelegation(resp, cmd.Root().Writer) |
| 127 | } |
| 128 | return nil |
| 129 | } |