Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions claim-cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
claim-cli*
22 changes: 22 additions & 0 deletions claim-cli/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
PROJECT_NAME := "claim-cli"
PKG := "github.com/chainguard-demo/platform-examples/claim-cli"
PKG_LIST := $(shell go list ${PKG}/... | grep -v /vendor/)
GO_FILES := $(shell find . -name '*.go' | grep -v /vendor/ | grep -v _test.go)
VERSION := $(shell if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then git describe --exact-match --tags HEAD 2>/dev/null || echo "dev-$(shell git rev-parse --short HEAD)"; else echo "dev"; fi)
GOOS=$(shell go env GOOS)
GOARCH=$(shell go env GOARCH)

.PHONY: clean build help

build: ## Builds the binary on the current platform
go build -a -ldflags "-s -w" -o $(PROJECT_NAME)

clean: ## Reset everything
git clean -fd
git clean -fx
git reset --hard

help: ## Display this help screen
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'


45 changes: 45 additions & 0 deletions claim-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# claim-cli

Parse a Chainguard JWT or capabilities string into human readable tables.

## Usage

### Capabilities string from JWT

`claim-cli read AAAAAAAAAMH_8gDx4eD__vn--DBJ__tvlCHm8B_wHmMAAAAAAAAAAQ==`

```
┌─────────────────────────────────────────┬────────────────┐
│ ENTITY │ ACTION │
├─────────────────────────────────────────┼────────────────┤
│ groups │ create │
│ groups │ update │
│ groups │ list │
│ groups │ delete │
│ group_invites │ create │
│ group_invites │ list │
│ group_invites │ delete │
.....
```


### JWT Parsing

`chainctl auth token | claim-cli read --jwt -`

```
┌──────────────────────────────────────────┬─────────────────────────────────────────┬────────────────┐
│ ORG │ ENTITY │ ACTION │
├──────────────────────────────────────────┼─────────────────────────────────────────┼────────────────┤
│ asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf │ groups │ create │
│ asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf │ groups │ update │
│ asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf │ groups │ list │
│ asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf │ groups │ delete │
│ asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf │ group_invites │ create │
│ asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf │ group_invites │ list │
│ asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf │ group_invites │ delete │
│ asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf │ roles │ create │
│ asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf │ roles │ update │
│ asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf │ roles │ list │
.....
```
169 changes: 169 additions & 0 deletions claim-cli/cmd/read.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package cmd

import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"strings"

"chainguard.dev/sdk/proto/capabilities"
"github.com/go-jose/go-jose/v4/jwt"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var readCmd = &cobra.Command{
Use: "read",
Short: "Read a JWT or capabilities string",
Long: `Takes a Chainguard JWT or capabilities string and prints out the details.

Reads from a string or stdin. For stdin pass - as the argument.

Examples:

claim-cli read AAAAAAAAAMH_8gDx4eD__vn--DBJ__tvlCHm8B_wHmMAAAAAAAAAAQ==

chainctl auth token | claim-cli read --jwt - `,
RunE: read,
}

var caps capabilities.Set

type tableRow struct {
org string
noun string
verb string
}

type claims struct {
jwt.Claims `json:",inline"`

Capabilities map[string]capabilities.Set `json:"cap,omitempty"`
}

func init() {
rootCmd.AddCommand(readCmd)
readCmd.Flags().Bool("jwt", false, "Read a full JWT instead of just a capability claim")
viper.BindPFlag("jwt", readCmd.Flags().Lookup("jwt"))
}

func read(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("a JWT or capabilities string must be provided")
}
input := args[0]

if args[0] == "-" {
s := new(strings.Builder)
var r io.Reader = cmd.InOrStdin()
_, err := io.Copy(s, r)
if err != nil {
return err
}
input = s.String()
}

if viper.GetBool("jwt") {
return printJwtString(cmd.OutOrStdout(), input)
} else {
return printCapString(cmd.OutOrStdout(), input)
}

}

func printCapString(w io.Writer, c string) error {
out := []string{}

if err := json.Unmarshal(fmt.Appendf([]byte{}, `%q`, c), &caps); err != nil {
return err
}

for _, c := range caps {
cs := c.String()
if s, err := capabilities.Stringify(c); err == nil {
cs = s
}
out = append(out, cs)
}

var rows []tableRow

for _, v := range out {
var noun string
idx := strings.LastIndex(v, ".")
if idx == -1 {
noun = v
} else {
noun = v[:idx]
}
row := tableRow{
noun: noun,
verb: v[strings.LastIndex(v, ".")+1:],
}
rows = append(rows, row)
}

t := newTable(w, []string{"entity", "action"})
for _, v := range rows {
t.Append(v.noun, v.verb)
}
return t.Render()

}

func printJwtString(w io.Writer, jwt string) error {
s := strings.Split(string(jwt), ".")
if len(s) != 3 {
return errors.New("invalid token format")
}

d, err := base64.RawStdEncoding.DecodeString(s[1])
if err != nil {
return fmt.Errorf("failed to decode token claims: %w", err)
}

claims := new(claims)
if err := json.Unmarshal(d, claims); err != nil {
return fmt.Errorf("failed to unmarshal token claims: %w", err)
}

rows := []tableRow{}
for group, caps := range claims.Capabilities {
for _, c := range caps {
cs := c.String()
if s, err := capabilities.Stringify(c); err == nil {
cs = s
}
var noun string
idx := strings.LastIndex(cs, ".")
if idx == -1 {
noun = cs
} else {
noun = cs[:idx]
}
rows = append(rows, tableRow{
org: group,
noun: noun,
verb: cs[strings.LastIndex(cs, ".")+1:],
})
}
}

t := newTable(w, []string{"org", "entity", "action"})
for _, v := range rows {
t.Append(v.org, v.noun, v.verb)
}
return t.Render()
}

func newTable(w io.Writer, headers []string) *tablewriter.Table {
return tablewriter.NewTable(w,
tablewriter.WithConfig(tablewriter.NewConfigBuilder().Build()),
tablewriter.WithHeader(headers),
tablewriter.WithRenderer(renderer.NewBlueprint()),
)
}
21 changes: 21 additions & 0 deletions claim-cli/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package cmd

import (
"os"

"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
Use: "claim-cli",
Short: "Interact with Chainguard tokens",
}

func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}

func init() {}
43 changes: 43 additions & 0 deletions claim-cli/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module github.com/chaingaurd-demo/platform-examples/claim-cli

go 1.26.0

require (
chainguard.dev/sdk v0.1.164
github.com/go-jose/go-jose/v4 v4.1.4
github.com/olekukonko/tablewriter v1.1.4
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
)

require (
github.com/bits-and-blooms/bitset v1.24.5 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chainguard-dev/clog v1.8.1 // indirect
github.com/clipperhouse/displaywidth v0.10.0 // indirect
github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.2.0 // indirect
github.com/olekukonko/ll v0.1.6 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/text v0.38.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad // indirect
google.golang.org/grpc v1.82.1 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
)
Loading
Loading