Skip to content

Instantly share code, notes, and snippets.

@btoews
Created January 24, 2025 18:02
Show Gist options
  • Save btoews/a3999342ab7d377e9f0818e5d14d0a4d to your computer and use it in GitHub Desktop.
Save btoews/a3999342ab7d377e9f0818e5d14d0a4d to your computer and use it in GitHub Desktop.
Kurt token stuff
package main
import (
"fmt"
"os"
"strings"
"github.com/superfly/macaroon"
"github.com/superfly/macaroon/flyio"
)
var (
// this secret should be loaded from an environment variable. it just needs
// to be 32 random bytes.
ThirdPartyKey = macaroon.NewEncryptionKey()
// the value of this isn't super important, but it identifies the app behind
// your third party caveat.
ThirdPartyLocation = "https://kurt.app"
)
func main() {
tok := strings.Join(os.Args[1:], " ")
if tok == "" {
fmt.Println("usage: attenuate-token $(fly tokens deploy -x1h -a myapp)")
os.Exit(1)
}
tok, err := AttenuateToken(tok)
if err != nil {
fmt.Println("error attenuating token:", err)
os.Exit(1)
}
fmt.Printf("attenuated:\n%s\n\n", tok)
tok, err = DischargeToken(tok)
if err != nil {
fmt.Println("error discharging token:", err)
os.Exit(1)
}
fmt.Printf("discharged:\n%s\n\n", tok)
fmt.Printf(`
Try running:
APP=myapp
MACHINE=mymachine
TOKEN="%s"
curl -H "Authorization: $TOKEN" -H 'Content-Type: application/json' --data '{"command": ["whoami"]}' https://api.machines.dev/v1/apps/$APP/machines/$MACHINE/exec
`, tok)
}
// attenuateToken takes a token returned by `flyctl tokens deploy` and
// attenuates it so it can only be used for exec'ing commands on machines and so
// that it can't be used by anyone without first passing it through
// DischargeToken. The returned value is what you would give to users.
func AttenuateToken(token string) (string, error) {
bun, err := flyio.ParseBundle(token)
if err != nil {
return "", fmt.Errorf("error parsing token: %v", err)
}
// A third party caveat. This will need to be discharged before the token is
// used against any other service.
tpCav, err := macaroon.NewCaveat3P(ThirdPartyKey, ThirdPartyLocation)
if err != nil {
return "", fmt.Errorf("error creating third party caveat: %v", err)
}
// This restricts the token to only be allowed for exec'ing commands on
// machines. You can tweak this to allow only certain commands or command
// prefixes.
comCav := &flyio.Commands{flyio.Command{}}
if err := bun.Attenuate(tpCav, comCav); err != nil {
return "", fmt.Errorf("error attenuating token: %v", err)
}
return bun.Header(), nil
}
// DischargeToken takes a token that was attenuated by AttenuateToken and
// discharges it so we can use it against the machines API to exec commands.
func DischargeToken(token string) (string, error) {
bun, err := flyio.ParseBundle(token)
if err != nil {
return "", fmt.Errorf("error parsing token: %v", err)
}
if err := bun.Discharge(ThirdPartyLocation, ThirdPartyKey, rejectCaveatsIn3P); err != nil {
return "", fmt.Errorf("error discharging token: %v", err)
}
return bun.Header(), nil
}
// You can include caveats in a 3p caveat that tell the third party the
// conditions under which they should discharge. We aren't adding any of those
// restrictions, so this callback just rejects any caveats found inside the 3p
// caveat.
func rejectCaveatsIn3P(cavs []macaroon.Caveat) ([]macaroon.Caveat, error) {
if len(cavs) != 0 {
return nil, fmt.Errorf("unexpected caveats in 3p caveat: %v", cavs)
}
return nil, nil
}
module kurtauth
go 1.22.1
require github.com/superfly/macaroon v0.2.14-0.20250124173236-5d85b206bb04
require (
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect
golang.org/x/sys v0.11.0 // indirect
)
github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0=
github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/superfly/macaroon v0.2.14-0.20250124173236-5d85b206bb04 h1:mGeQ5kRsAmhUnenGN3fNplCu+EuU9FF7GXbrmAPP27E=
github.com/superfly/macaroon v0.2.14-0.20250124173236-5d85b206bb04/go.mod h1:ZAmlRD/Hmp/ddTxE8IonZ7NdTny2DcOffRvZhapQwJw=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment