Skip to content

Commit

Permalink
Merge pull request #26 from superfly/app-features
Browse files Browse the repository at this point in the history
flyio: add AppFeatureSet caveat
  • Loading branch information
btoews authored Jul 2, 2024
2 parents ed68b73 + 67e0e26 commit b8ac52a
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 21 deletions.
1 change: 1 addition & 0 deletions caveat.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const (
AttestationAuthGoogleUserID
CavAction
CavFlyioCommands
CavFlyioAppFeatureSet

// allocate internal blocks of size 255 here
block255Min CaveatType = 1 << 16
Expand Down
51 changes: 41 additions & 10 deletions flyio/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package flyio

import (
"fmt"
"strings"
"time"

"github.com/superfly/macaroon"
Expand All @@ -12,6 +13,7 @@ type Access struct {
Action resset.Action `json:"action,omitempty"`
OrgID *uint64 `json:"orgid,omitempty"`
AppID *uint64 `json:"appid,omitempty"`
AppFeature *string `json:"app_feature,omitempty"`
Feature *string `json:"feature,omitempty"`
Volume *string `json:"volume,omitempty"`
Machine *string `json:"machine,omitempty"`
Expand Down Expand Up @@ -52,15 +54,22 @@ func (f *Access) Validate() error {
return fmt.Errorf("%w: app, org-feature", resset.ErrResourcesMutuallyExclusive)
}

// app-level resources = machines, volumes
if f.Machine != nil || f.Volume != nil {
if f.AppID == nil {
return fmt.Errorf("%w app if app-owned resource is specified", resset.ErrResourceUnspecified)
}

if f.Machine != nil && f.Volume != nil {
return fmt.Errorf("%w: volume, machine", resset.ErrResourcesMutuallyExclusive)
}
// app-level resources = machines, volumes, app-features
var appResources []string
if f.Machine != nil {
appResources = append(appResources, "machine")
}
if f.Volume != nil {
appResources = append(appResources, "volume")
}
if f.AppFeature != nil {
appResources = append(appResources, *f.AppFeature)
}
if len(appResources) != 0 && f.AppID == nil {
return fmt.Errorf("%w app if app-owned resource is specified", resset.ErrResourceUnspecified)
}
if len(appResources) > 1 {
return fmt.Errorf("%w: %s", resset.ErrResourcesMutuallyExclusive, strings.Join(appResources, ", "))
}

// lfsc feature-level resource = clusters
Expand All @@ -75,9 +84,19 @@ func (f *Access) Validate() error {
}

// machine feature requires machine
if f.MachineFeature != nil && f.Machine == nil {
var machineResources []string
if f.Command != nil {
machineResources = append(machineResources, "command-execution")
}
if f.MachineFeature != nil {
machineResources = append(machineResources, *f.MachineFeature)
}
if len(machineResources) != 0 && f.Machine == nil {
return fmt.Errorf("%w machine ", resset.ErrResourceUnspecified)
}
if len(machineResources) > 1 {
return fmt.Errorf("%w: %s", resset.ErrResourcesMutuallyExclusive, strings.Join(machineResources, ", "))
}

return nil
}
Expand Down Expand Up @@ -106,6 +125,18 @@ var _ AppIDGetter = (*Access)(nil)
// GetAppID implements AppIDGetter.
func (a *Access) GetAppID() *uint64 { return a.AppID }

// AppFeatureGetter is an interface allowing other packages to implement
// Accesses that work with Caveats defined in this package.
type AppFeatureGetter interface {
resset.Access
GetAppFeature() *string
}

var _ AppFeatureGetter = (*Access)(nil)

// GetAppFeature implements AppFeatureGetter.
func (a *Access) GetAppFeature() *string { return a.AppFeature }

// FeatureGetter is an interface allowing other packages to implement Accesses
// that work with Caveats defined in this package.
type FeatureGetter interface {
Expand Down
17 changes: 17 additions & 0 deletions flyio/caveats.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const (
CavClusters = macaroon.CavFlyioClusters
CavNoAdminFeatures = macaroon.CavNoAdminFeatures
CavCommands = macaroon.CavFlyioCommands
CavAppFeatureSet = macaroon.CavFlyioAppFeatureSet
)

type FromMachine struct {
Expand Down Expand Up @@ -366,3 +367,19 @@ func (c *Commands) Prohibits(a macaroon.Access) error {

return nil
}

type AppFeatureSet struct {
Features resset.ResourceSet[string] `json:"features"`
}

func init() { macaroon.RegisterCaveatType(&AppFeatureSet{}) }
func (c *AppFeatureSet) CaveatType() macaroon.CaveatType { return CavAppFeatureSet }
func (c *AppFeatureSet) Name() string { return "AppFeatureSet" }

func (c *AppFeatureSet) Prohibits(a macaroon.Access) error {
f, isFlyioAccess := a.(AppFeatureGetter)
if !isFlyioAccess {
return fmt.Errorf("%w: access isnt AppFeatureGetter", macaroon.ErrInvalidAccess)
}
return c.Features.Prohibits(f.GetAppFeature(), f.GetAction())
}
47 changes: 36 additions & 11 deletions flyio/caveats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,63 +117,82 @@ func TestCommands(t *testing.T) {

yes(cs, &Access{
OrgID: uptr(1),
Action: resset.ActionAll,
AppID: uptr(1),
Machine: ptr("machine"),
Command: []string{"cmd1", "arg1"},
})

yes(cs, &Access{
OrgID: uptr(1),
Action: resset.ActionAll,
AppID: uptr(1),
Machine: ptr("machine"),
Command: []string{"cmd1", "arg1", "arg2"},
})

yes(cs, &Access{
OrgID: uptr(1),
AppID: uptr(1),
Machine: ptr("machine"),
Action: resset.ActionAll,
Command: []string{"cmd2", "arg1"},
})

no(cs, &Access{
OrgID: uptr(1),
AppID: uptr(1),
Machine: ptr("machine"),
Action: resset.ActionAll,
Command: []string{"cmd2", "arg1", "arg2"},
}, resset.ErrUnauthorizedForResource)

no(cs, &Access{
OrgID: uptr(1),
AppID: uptr(1),
Machine: ptr("machine"),
Action: resset.ActionAll,
Command: []string{"cmd3", "arg1"},
}, resset.ErrUnauthorizedForResource)

no(cs, &Access{
OrgID: uptr(1),
Action: resset.ActionAll,
OrgID: uptr(1),
AppID: uptr(1),
Machine: ptr("machine"),
Action: resset.ActionAll,
}, resset.ErrResourceUnspecified)

csNone := macaroon.NewCaveatSet(&Commands{})

no(csNone, &Access{
OrgID: uptr(1),
AppID: uptr(1),
Machine: ptr("machine"),
Action: resset.ActionAll,
Command: []string{"cmd2", "arg1", "arg2", "arg3"},
}, resset.ErrUnauthorizedForResource)

no(csNone, &Access{
OrgID: uptr(1),
Action: resset.ActionAll,
OrgID: uptr(1),
AppID: uptr(1),
Machine: ptr("machine"),
Action: resset.ActionAll,
}, resset.ErrResourceUnspecified)

csAny := macaroon.NewCaveatSet(&Commands{Command{}})

yes(csAny, &Access{
OrgID: uptr(1),
AppID: uptr(1),
Machine: ptr("machine"),
Action: resset.ActionAll,
Command: []string{"cmd2", "arg1", "arg2", "arg3"},
})

no(csAny, &Access{
OrgID: uptr(1),
Action: resset.ActionAll,
OrgID: uptr(1),
AppID: uptr(1),
Machine: ptr("machine"),
Action: resset.ActionAll,
}, resset.ErrResourceUnspecified)

csIf := macaroon.NewCaveatSet(
Expand All @@ -185,17 +204,23 @@ func TestCommands(t *testing.T) {

yes(csIf, &Access{
OrgID: uptr(1),
AppID: uptr(1),
Machine: ptr("machine"),
Action: resset.ActionNone,
Command: []string{"uname", "arg"},
})

yes(csIf, &Access{
OrgID: uptr(1),
Action: resset.ActionDelete,
OrgID: uptr(1),
AppID: uptr(1),
Machine: ptr("machine"),
Action: resset.ActionDelete,
})

no(csIf, &Access{
OrgID: uptr(1),
Action: resset.ActionWrite,
OrgID: uptr(1),
AppID: uptr(1),
Machine: ptr("machine"),
Action: resset.ActionWrite,
}, resset.ErrUnauthorizedForAction)
}

0 comments on commit b8ac52a

Please sign in to comment.