Skip to content

Commit

Permalink
attestations for auth'ed user
Browse files Browse the repository at this point in the history
  • Loading branch information
btoews committed Dec 4, 2023
1 parent afbe418 commit 08309ee
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 8 deletions.
65 changes: 60 additions & 5 deletions auth/caveats.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
package auth

import (
"errors"
"fmt"
"math"
"math/big"
"time"

"golang.org/x/exp/slices"

"github.com/superfly/macaroon"
"github.com/vmihailenco/msgpack/v5"
)

const (
CavConfineUser = macaroon.CavAuthConfineUser
CavConfineOrganization = macaroon.CavAuthConfineOrganization
CavConfineGoogleHD = macaroon.CavAuthConfineGoogleHD
CavConfineGitHubOrg = macaroon.CavAuthConfineGitHubOrg
CavMaxValidity = macaroon.CavAuthMaxValidity
CavConfineUser = macaroon.CavAuthConfineUser
CavConfineOrganization = macaroon.CavAuthConfineOrganization
CavConfineGoogleHD = macaroon.CavAuthConfineGoogleHD
CavConfineGitHubOrg = macaroon.CavAuthConfineGitHubOrg
CavMaxValidity = macaroon.CavAuthMaxValidity
AttestationFlyioUserID = macaroon.AttestationAuthFlyioUserID
AttestationGitHubUserID = macaroon.AttestationAuthGitHubUserID
AttestationGoogleUserID = macaroon.AttestationAuthGoogleUserID
)

// ConfineOrganization is a requirement placed on 3P caveats, requiring that the
Expand Down Expand Up @@ -190,3 +196,52 @@ func GetMaxValidity(cs *macaroon.CaveatSet) (time.Duration, bool) {

return max, max != time.Duration(math.MaxInt64)
}

type FlyioUserID uint64

func init() { macaroon.RegisterCaveatType(new(FlyioUserID)) }
func (c *FlyioUserID) CaveatType() macaroon.CaveatType { return AttestationFlyioUserID }
func (c *FlyioUserID) Name() string { return "FlyioUserID" }
func (c *FlyioUserID) Prohibits(a macaroon.Access) error { return macaroon.ErrBadCaveat }
func (c *FlyioUserID) IsAttestation() bool { return true }

type GitHubUserID uint64

func init() { macaroon.RegisterCaveatType(new(GitHubUserID)) }
func (c *GitHubUserID) CaveatType() macaroon.CaveatType { return AttestationGitHubUserID }
func (c *GitHubUserID) Name() string { return "GitHubUserID" }
func (c *GitHubUserID) Prohibits(a macaroon.Access) error { return macaroon.ErrBadCaveat }
func (c *GitHubUserID) IsAttestation() bool { return true }

type GoogleUserID big.Int

func init() { macaroon.RegisterCaveatType(new(GoogleUserID)) }
func (c *GoogleUserID) CaveatType() macaroon.CaveatType { return AttestationGoogleUserID }
func (c *GoogleUserID) Name() string { return "GoogleUserID" }
func (c *GoogleUserID) Prohibits(a macaroon.Access) error { return macaroon.ErrBadCaveat }
func (c *GoogleUserID) IsAttestation() bool { return true }

func (c *GoogleUserID) EncodeMsgpack(enc *msgpack.Encoder) error {
return enc.Encode((*big.Int)(c).Bytes())
}

func (c *GoogleUserID) DecodeMsgpack(dec *msgpack.Decoder) error {
b, err := dec.DecodeBytes()
if err != nil {
return err
}

(*big.Int)(c).SetBytes(b)
return nil
}

func (c *GoogleUserID) MarshalJSON() ([]byte, error) {
return []byte((*big.Int)(c).String()), nil
}

func (c *GoogleUserID) UnmarshalJSON(data []byte) error {
if _, ok := (*big.Int)(c).SetString(string(data), 10); !ok {
return errors.New("bad bigint")
}
return nil
}
12 changes: 12 additions & 0 deletions auth/caveats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package auth

import (
"encoding/json"
"math/big"
"testing"

"github.com/alecthomas/assert/v2"
Expand All @@ -14,6 +15,13 @@ func TestCaveatSerialization(t *testing.T) {
RequireOrganization(123),
RequireGoogleHD("123"),
RequireGitHubOrg(123),
ptr(FlyioUserID(123)),
ptr(GitHubUserID(123)),
(*GoogleUserID)(new(big.Int).SetBytes([]byte{
0xDE, 0xAD, 0xBE, 0xEF,
0xDE, 0xAD, 0xBE, 0xEF,
123,
})),
)

b, err := json.Marshal(cs)
Expand All @@ -30,3 +38,7 @@ func TestCaveatSerialization(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, cs, cs2)
}

func ptr[T any](t T) *T {
return &t
}
3 changes: 3 additions & 0 deletions caveat.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const (
CavAuthConfineGitHubOrg
CavAuthMaxValidity
CavNoAdminFeatures
AttestationAuthFlyioUserID
AttestationAuthGitHubUserID
AttestationAuthGoogleUserID

// Globally-recognized user-registerable caveat types may be requested via
// pull requests to this repository. Add a meaningful name of the caveat
Expand Down
2 changes: 1 addition & 1 deletion caveats.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (c *BindToParentToken) CaveatType() CaveatType { return CavBindToParentToke
func (c *BindToParentToken) Name() string { return "BindToParentToken" }

func (c *BindToParentToken) Prohibits(f Access) error {
// IsUser are part of token verification and have no role in
// BindToParentToken are part of token verification and have no role in
// access validation.
return fmt.Errorf("%w (bind-to-parent)", ErrBadCaveat)
}
Expand Down
33 changes: 33 additions & 0 deletions flyio/caveat_set.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package flyio

import (
"errors"
"fmt"

"golang.org/x/exp/slices"

"github.com/superfly/macaroon"
"github.com/superfly/macaroon/auth"
"github.com/superfly/macaroon/resset"
"golang.org/x/exp/constraints"
"golang.org/x/exp/maps"
Expand Down Expand Up @@ -170,6 +172,37 @@ func AppsAllowing(cs *macaroon.CaveatSet, action resset.Action) (uint64, []uint6
return orgScope, ret, nil
}

// DangerousUserID iterates over the caveats to determine the associated user
// ID. This identity should only be used for logging and auditing. It should
// not be used for making authorization decisions.
func DangerousUserID(cs *macaroon.CaveatSet) (uint64, error) {
var uid *uint64

for _, cav := range macaroon.GetCaveats[*IsUser](cs) {
switch {
case uid == nil:
uid = &cav.ID
case *uid != cav.ID:
return 0, errors.New("multiple user IDs specified")
}
}

for _, cav := range macaroon.GetCaveats[*auth.FlyioUserID](cs) {
switch cavID := *(*uint64)(cav); {
case uid == nil:
uid = &cavID
case *uid != cavID:
return 0, errors.New("multiple user IDs specified")
}
}

if uid == nil {
return 0, errors.New("user ID unspecified")
}

return *uid, nil
}

func ptr[T any](v T) *T {
return &v
}
Expand Down
42 changes: 42 additions & 0 deletions flyio/caveat_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/alecthomas/assert/v2"
"github.com/superfly/macaroon"
"github.com/superfly/macaroon/auth"
"github.com/superfly/macaroon/resset"
)

Expand Down Expand Up @@ -273,3 +274,44 @@ func TestAppsAllowing(t *testing.T) {
assert.Equal(t, 987, orgID)
assert.Equal(t, []uint64{123, 234}, appIDs)
}

func TestDangerousUserID(t *testing.T) {
_, err := DangerousUserID(macaroon.NewCaveatSet())
assert.Error(t, err)

id, err := DangerousUserID(macaroon.NewCaveatSet(&IsUser{ID: 123}))
assert.NoError(t, err)
assert.Equal(t, 123, id)

id, err = DangerousUserID(macaroon.NewCaveatSet(&IsUser{ID: 123}, &IsUser{ID: 123}))
assert.NoError(t, err)
assert.Equal(t, 123, id)

id, err = DangerousUserID(macaroon.NewCaveatSet(ptr(auth.FlyioUserID(123))))
assert.NoError(t, err)
assert.Equal(t, 123, id)

id, err = DangerousUserID(macaroon.NewCaveatSet(ptr(auth.FlyioUserID(123)), ptr(auth.FlyioUserID(123))))
assert.NoError(t, err)
assert.Equal(t, 123, id)

id, err = DangerousUserID(macaroon.NewCaveatSet(&IsUser{ID: 123}, ptr(auth.FlyioUserID(123))))
assert.NoError(t, err)
assert.Equal(t, 123, id)

id, err = DangerousUserID(macaroon.NewCaveatSet(ptr(auth.FlyioUserID(123)), &IsUser{ID: 123}))
assert.NoError(t, err)
assert.Equal(t, 123, id)

_, err = DangerousUserID(macaroon.NewCaveatSet(&IsUser{ID: 123}, &IsUser{ID: 234}))
assert.Error(t, err)

_, err = DangerousUserID(macaroon.NewCaveatSet(ptr(auth.FlyioUserID(123)), ptr(auth.FlyioUserID(234))))
assert.Error(t, err)

_, err = DangerousUserID(macaroon.NewCaveatSet(&IsUser{ID: 123}, ptr(auth.FlyioUserID(234))))
assert.Error(t, err)

_, err = DangerousUserID(macaroon.NewCaveatSet(ptr(auth.FlyioUserID(234)), &IsUser{ID: 123}))
assert.Error(t, err)
}
2 changes: 1 addition & 1 deletion flyio/caveats.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ func (c *Mutations) Prohibits(a macaroon.Access) error {
return nil
}

// TODO: deprecate this and replace with an attestation
// deprecated in favor of auth.FlyioUserID
type IsUser struct {
ID uint64 `json:"uint64"`
}
Expand Down
9 changes: 8 additions & 1 deletion internal/test-vectors/test_vectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/hex"
"encoding/json"
"math"
"math/big"
"os"

"golang.org/x/exp/slices"
Expand Down Expand Up @@ -116,6 +117,13 @@ var caveats = macaroon.NewCaveatSet(
auth.RequireOrganization(123),
auth.RequireGoogleHD("123"),
auth.RequireGitHubOrg(123),
ptr(auth.FlyioUserID(123)),
ptr(auth.GitHubUserID(123)),
(*auth.GoogleUserID)(new(big.Int).SetBytes([]byte{
0xDE, 0xAD, 0xBE, 0xEF,
0xDE, 0xAD, 0xBE, 0xEF,
123,
})),
&flyio.NoAdminFeatures{},
)

Expand Down Expand Up @@ -167,7 +175,6 @@ func init() { macaroon.RegisterCaveat
func (c *mapCaveat) CaveatType() macaroon.CaveatType { return cavMap }
func (c *mapCaveat) Name() string { return "Map" }
func (c *mapCaveat) Prohibits(f macaroon.Access) error { return nil }
func (c *mapCaveat) IsAttestation() bool { return false }

var _ msgpack.CustomEncoder = mapCaveat{}

Expand Down

0 comments on commit 08309ee

Please sign in to comment.