From b4fab9f2c531cd3a50cafabc96c99f65b536ebbf Mon Sep 17 00:00:00 2001 From: btoews Date: Tue, 2 Jul 2024 12:12:27 -0600 Subject: [PATCH 1/2] flyio: add AppFeatureSet caveat --- caveat.go | 1 + flyio/access.go | 13 +++++++++++++ flyio/caveats.go | 17 +++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/caveat.go b/caveat.go index 641ac54..b04ac46 100644 --- a/caveat.go +++ b/caveat.go @@ -41,6 +41,7 @@ const ( AttestationAuthGoogleUserID CavAction CavFlyioCommands + CavFlyioAppFeatureSet // allocate internal blocks of size 255 here block255Min CaveatType = 1 << 16 diff --git a/flyio/access.go b/flyio/access.go index adaaec1..de6d9c6 100644 --- a/flyio/access.go +++ b/flyio/access.go @@ -12,6 +12,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"` @@ -106,6 +107,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 { diff --git a/flyio/caveats.go b/flyio/caveats.go index c06ad0a..32bdefc 100644 --- a/flyio/caveats.go +++ b/flyio/caveats.go @@ -22,6 +22,7 @@ const ( CavClusters = macaroon.CavFlyioClusters CavNoAdminFeatures = macaroon.CavNoAdminFeatures CavCommands = macaroon.CavFlyioCommands + CavAppFeatureSet = macaroon.CavFlyioAppFeatureSet ) type FromMachine struct { @@ -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()) +} From 67e0e2663d7ae425472d0ce561b7fe5164beac22 Mon Sep 17 00:00:00 2001 From: btoews Date: Tue, 2 Jul 2024 12:33:25 -0600 Subject: [PATCH 2/2] flyio: add missing validations on Access fields --- flyio/access.go | 38 +++++++++++++++++++++++++--------- flyio/caveats_test.go | 47 +++++++++++++++++++++++++++++++++---------- 2 files changed, 64 insertions(+), 21 deletions(-) diff --git a/flyio/access.go b/flyio/access.go index de6d9c6..2601902 100644 --- a/flyio/access.go +++ b/flyio/access.go @@ -2,6 +2,7 @@ package flyio import ( "fmt" + "strings" "time" "github.com/superfly/macaroon" @@ -53,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 @@ -76,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 } diff --git a/flyio/caveats_test.go b/flyio/caveats_test.go index 0b682ef..0e33574 100644 --- a/flyio/caveats_test.go +++ b/flyio/caveats_test.go @@ -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( @@ -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) }