Skip to content

Commit

Permalink
simple macaroon implementation
Browse files Browse the repository at this point in the history
authorize secret usage with macaroons

use our newly opensourced macaroons implementation instead of a new one

more caveats

command for generating macaroon
  • Loading branch information
btoews committed Sep 26, 2023
1 parent ef2652d commit eb4c033
Show file tree
Hide file tree
Showing 12 changed files with 709 additions and 256 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.vscode
*.pem
/bin/*
/.envrc
Expand Down
97 changes: 87 additions & 10 deletions authorizer.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package tokenizer

import (
"bytes"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"encoding/hex"
"fmt"
"net/http"
"strings"

"github.com/sirupsen/logrus"
"github.com/superfly/macaroon"
tkmac "github.com/superfly/tokenizer/macaroon"
)

const headerProxyAuthorization = "Proxy-Authorization"
Expand All @@ -29,8 +33,87 @@ func NewBearerAuthConfig(token string) *BearerAuthConfig {
var _ AuthConfig = new(BearerAuthConfig)

func (c *BearerAuthConfig) AuthRequest(req *http.Request) error {
var found bool
for _, tok := range proxyAuthorizationTokens(req) {
hdrDigest := sha256.Sum256([]byte(tok))
if subtle.ConstantTimeCompare(c.Digest, hdrDigest[:]) == 1 {
return nil
}
}

return fmt.Errorf("%w: bad or missing proxy auth", ErrNotAuthorized)
}

type MacaroonAuthConfig struct {
Key []byte `json:"key"`
}

func NewMacaroonAuthConfig(key []byte) *MacaroonAuthConfig {
return &MacaroonAuthConfig{Key: key}
}

var _ AuthConfig = new(MacaroonAuthConfig)

func (c *MacaroonAuthConfig) AuthRequest(req *http.Request) error {
var (
expectedKID = tkmac.KeyFingerprint(c.Key)
log = logrus.WithField("expected-kid", hex.EncodeToString(expectedKID))
)

for _, tok := range proxyAuthorizationTokens(req) {
permission, discharges, err := macaroon.ParsePermissionAndDischargeTokens(tok, tkmac.Location)
if err != nil {
log.WithError(err).Warn("bad macaroon encoding")
continue
}

m, err := macaroon.Decode(permission)
if err != nil {
log.WithError(err).Warn("bad macaroon format")
continue
}
log = log.WithFields(logrus.Fields{"uuid": m.Nonce.UUID()})

if !bytes.Equal(m.Nonce.KID, expectedKID) {
log.WithField("kid", hex.EncodeToString(m.Nonce.KID)).Warn("wrong macaroon key")
continue
}

cavs, err := m.Verify(c.Key, discharges, nil)
if err != nil {
log.WithError(err).Warn("bad macaroon signature")
continue
}

if err = cavs.Validate(&tkmac.Access{Request: req}); err != nil {
log.WithError(err).Warn("bad macaroon authz")
continue
}

return nil
}

return fmt.Errorf("%w: bad or missing proxy auth", ErrNotAuthorized)
}

func (c *MacaroonAuthConfig) Macaroon(caveats ...macaroon.Caveat) (string, error) {
m, err := macaroon.New(tkmac.KeyFingerprint(c.Key), tkmac.Location, c.Key)
if err != nil {
return "", err
}

if err := m.Add(caveats...); err != nil {
return "", err
}

mb, err := m.Encode()
if err != nil {
return "", err
}

return macaroon.ToAuthorizationHeader(mb), nil
}

func proxyAuthorizationTokens(req *http.Request) (ret []string) {
hdrLoop:
for _, hdr := range req.Header.Values(headerProxyAuthorization) {
scheme, rest, ok := strings.Cut(hdr, " ")
Expand All @@ -40,7 +123,7 @@ hdrLoop:
}

switch scheme {
case "Bearer":
case "Bearer", "FlyV1":
hdr = rest
case "Basic":
raw, err := base64.StdEncoding.DecodeString(rest)
Expand All @@ -59,14 +142,8 @@ hdrLoop:
continue hdrLoop
}

hdrDigest := sha256.Sum256([]byte(hdr))
if subtle.ConstantTimeCompare(c.Digest, hdrDigest[:]) == 1 {
found = true
}
}
if !found {
return fmt.Errorf("%w: bad or missing proxy auth", ErrNotAuthorized)
ret = append(ret, hdr)
}

return nil
return
}
6 changes: 5 additions & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,15 @@ func WithSecret(sealedSecret string, params map[string]string) ClientOption {
}

func WithAuth(auth string) ClientOption {
if len(auth) < 6 || auth[:6] != "FlyV1 " {
auth = fmt.Sprintf("Bearer %s", auth)
}

return func(co *clientOptions) {
if co.headers == nil {
co.headers = make(http.Header)
}
co.headers.Add(headerProxyAuthorization, fmt.Sprintf("Bearer %s", auth))
co.headers.Add(headerProxyAuthorization, auth)
}
}

Expand Down
80 changes: 70 additions & 10 deletions cmd/curl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,30 @@ import (
"net/url"
"os"

"github.com/superfly/macaroon"
"github.com/superfly/tokenizer"
tkmac "github.com/superfly/tokenizer/macaroon"
)

func main() {
if len(os.Args) < 2 {
usage()
}

if os.Args[1] == "gen" {
switch os.Args[1] {
case "gen":
if len(os.Args) != 3 {
usage()
}
doGenerateSecret(os.Args[2])
case "gen-macaroon":
doGenerateMacararoon()
default:
if _, err := url.Parse(os.Args[1]); err != nil {
usage()
}
doCurl(os.Args[1])
}

if _, err := url.Parse(os.Args[1]); err != nil {
usage()
}
doCurl(os.Args[1])
}

func doCurl(url string) {
Expand Down Expand Up @@ -57,10 +62,44 @@ func doCurl(url string) {

func doGenerateSecret(secretToken string) {
var (
sealKey = mustGetEnv("SEAL_KEY")
authToken = randHex(8)
secret = &tokenizer.Secret{
AuthConfig: tokenizer.NewBearerAuthConfig(authToken),
sealKey = mustGetEnv("SEAL_KEY")
authToken = os.Getenv("AUTH_TOKEN")
hexMacaroonKey = os.Getenv("MACAROON_KEY")
)

if authToken != "" && hexMacaroonKey != "" {
fatalln("cannot specify AUTH_TOKEN and MACAROON_KEY")
}

var authConfig tokenizer.AuthConfig
if hexMacaroonKey != "" {
key, err := hex.DecodeString(hexMacaroonKey)
if err != nil {
fatalf("MACAROON_KEY must be hex encoded: %s", err)
}

mac, err := macaroon.New(tkmac.KeyFingerprint(key), tkmac.Location, key)
if err != nil {
fatalf("failed to generate macaroon: %s", err)
}

tok, err := mac.Encode()
if err != nil {
fatalf("failed to encode macaroon: %s", err)
}

authToken = macaroon.ToAuthorizationHeader(tok)
authConfig = tokenizer.NewMacaroonAuthConfig(key)
} else {
if authToken == "" {
authToken = randHex(8)
}
authConfig = tokenizer.NewBearerAuthConfig(authToken)
}

var (
secret = &tokenizer.Secret{
AuthConfig: authConfig,
ProcessorConfig: &tokenizer.InjectProcessorConfig{Token: secretToken},
}
)
Expand All @@ -74,6 +113,27 @@ func doGenerateSecret(secretToken string) {
os.Exit(0)
}

func doGenerateMacararoon() {
hexMacaroonKey := mustGetEnv("MACAROON_KEY")
macaroonKey, err := hex.DecodeString(hexMacaroonKey)
if err != nil {
fatalf("bad MACAROON_KEY: %s", err)
}

m, err := macaroon.New(tkmac.KeyFingerprint(macaroonKey), tkmac.Location, macaroonKey)
if err != nil {
fatalf("failed to generate macaroon: %s", err)
}

tok, err := m.Encode()
if err != nil {
fatalf("failed to generate macaroon: %s", err)
}

fmt.Printf("export AUTH_TOKEN=\"%s\"\n", macaroon.ToAuthorizationHeader(tok))
os.Exit(0)
}

func mustGetEnv(key string) string {
val := os.Getenv(key)
if val == "" {
Expand Down
10 changes: 7 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ require (
github.com/alecthomas/assert/v2 v2.2.2
github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027
github.com/sirupsen/logrus v1.9.3
golang.org/x/crypto v0.11.0
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df
github.com/superfly/macaroon v0.0.5
golang.org/x/crypto v0.12.0
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
)

require (
github.com/alecthomas/repr v0.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
golang.org/x/sys v0.10.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/sys v0.11.0 // indirect
)
25 changes: 17 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 h1:1L0aalTpPz7YlMx
github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
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/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=
Expand All @@ -17,15 +19,22 @@ github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYe
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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
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=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/superfly/macaroon v0.0.5 h1:Rw48kdYc2k0PHccGnNWO0Byc5TQoJBjbQtzGZJPFKcU=
github.com/superfly/macaroon v0.0.5/go.mod h1:5DZuLe1e3EiEDs9R7snKQJVslVjgBhlJ9jbnOmKasRg=
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.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
25 changes: 25 additions & 0 deletions macaroon/access.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package macaroon

import (
"net/http"
"time"

"github.com/superfly/macaroon"
)

type Access struct {
Request *http.Request
}

var _ macaroon.Access = (*Access)(nil)

func (a *Access) Now() time.Time {
return time.Now()
}

func (a *Access) Validate() error {
if a.Request == nil {
return macaroon.ErrInvalidAccess
}
return nil
}
Loading

0 comments on commit eb4c033

Please sign in to comment.