Skip to content

Commit

Permalink
Refactor ACL
Browse files Browse the repository at this point in the history
  • Loading branch information
kiootic committed Jun 27, 2023
1 parent 40f4ccc commit 0679e39
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 130 deletions.
35 changes: 17 additions & 18 deletions cmd/controller/app/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"github.com/oursky/pageship/internal/handler/site"
"github.com/oursky/pageship/internal/handler/site/middleware"
"github.com/oursky/pageship/internal/httputil"
"github.com/oursky/pageship/internal/models"
sitedb "github.com/oursky/pageship/internal/site/db"
"github.com/oursky/pageship/internal/storage"
"github.com/oursky/pageship/internal/watch"
Expand Down Expand Up @@ -54,7 +53,7 @@ func init() {
startCmd.PersistentFlags().String("host-pattern", config.DefaultHostPattern, "host match pattern")
startCmd.PersistentFlags().String("host-id-scheme", string(config.HostIDSchemeDefault), "host ID scheme")
startCmd.PersistentFlags().StringSlice("reserved-apps", []string{defaultControllerHostID}, "reserved app IDs")
startCmd.PersistentFlags().String("user-credentials-allowlist", "", "user credentials allowlist file")
startCmd.PersistentFlags().String("api-acl", "", "API ACL file")

startCmd.PersistentFlags().String("token-authority", "pageship", "auth token authority")
startCmd.PersistentFlags().String("token-signing-key", "", "auth token signing key")
Expand Down Expand Up @@ -95,12 +94,12 @@ type StartSitesConfig struct {
}

type StartControllerConfig struct {
MaxDeploymentSize string `mapstructure:"max-deployment-size" validate:"size"`
StorageKeyPrefix string `mapstructure:"storage-key-prefix"`
TokenSigningKey string `mapstructure:"token-signing-key"`
TokenAuthority string `mapstructure:"token-authority"`
ReservedApps []string `mapstructure:"reserved-apps"`
UserCredentialsAllowlist string `mapstructure:"user-credentials-allowlist" validate:"omitempty,filepath"`
MaxDeploymentSize string `mapstructure:"max-deployment-size" validate:"size"`
StorageKeyPrefix string `mapstructure:"storage-key-prefix"`
TokenSigningKey string `mapstructure:"token-signing-key"`
TokenAuthority string `mapstructure:"token-authority"`
ReservedApps []string `mapstructure:"reserved-apps"`
APIACLFile string `mapstructure:"api-acl" validate:"omitempty,filepath"`
}

type StartCronConfig struct {
Expand Down Expand Up @@ -175,35 +174,35 @@ func (s *setup) controller(domain string, conf StartControllerConfig, sitesConf
TokenAuthority: conf.TokenAuthority,
}

if conf.UserCredentialsAllowlist != "" {
allowlistLog := logger.Named("allowlist")
list, err := watch.NewFile(
allowlistLog,
conf.UserCredentialsAllowlist,
func(path string) (config.Allowlist[models.CredentialID], error) {
if conf.APIACLFile != "" {
aclLog := logger.Named("api-acl")
acl, err := watch.NewFile(
aclLog,
conf.APIACLFile,
func(path string) (config.ACL, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()

list, err := config.LoadAllowlist[models.CredentialID](f)
list, err := config.LoadACL(f)
if err != nil {
return nil, err
}

allowlistLog.Info("loaded allowlist", zap.Int("count", len(list)))
aclLog.Info("loaded ACL", zap.Int("count", len(list)))
return list, nil
},
)
if err != nil {
return err
}

controllerConf.UserCredentialsAllowlist = list
controllerConf.ACL = acl
s.works = append(s.works, func(ctx context.Context) error {
<-ctx.Done()
list.Close()
acl.Close()
return nil
})
}
Expand Down
52 changes: 52 additions & 0 deletions internal/config/acl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package config

import (
"fmt"
"io"

"github.com/mitchellh/mapstructure"
"github.com/pelletier/go-toml/v2"
)

type ACL []ACLSubjectRule

func LoadACL(r io.Reader) (ACL, error) {
var m map[string]any
if err := toml.NewDecoder(r).Decode(&m); err != nil {
return nil, err
}

var aclFile struct {
Access ACL `json:"access"`
}
if err := mapstructure.Decode(m, &aclFile); err != nil {
return nil, err
}

if err := validate.Struct(aclFile); err != nil {
return nil, err
}

return aclFile.Access, nil
}

type ACLSubjectRule struct {
PageshipUser string `json:"pageshipUser,omitempty" pageship:"max=100"`
GitHubUser string `json:"githubUser,omitempty" pageship:"max=100"`
GitHubRepositoryActions string `json:"gitHubRepositoryActions,omitempty" pageship:"max=100"`
IpRange string `json:"ipRange,omitempty" pageship:"omitempty,max=100,cidr"`
}

func (c *ACLSubjectRule) String() string {
switch {
case c.PageshipUser != "":
return fmt.Sprintf("pageshipUser:%s", c.PageshipUser)
case c.GitHubUser != "":
return fmt.Sprintf("githubUser:%s", c.GitHubUser)
case c.GitHubRepositoryActions != "":
return fmt.Sprintf("gitHubRepositoryActions:%s", c.GitHubRepositoryActions)
case c.IpRange != "":
return fmt.Sprintf("ipRange:%s", c.IpRange)
}
return "<unknown>"
}
39 changes: 0 additions & 39 deletions internal/config/allowlist.go

This file was deleted.

29 changes: 2 additions & 27 deletions internal/config/app_access.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package config

import (
"fmt"
)

type AccessLevel string

const (
Expand Down Expand Up @@ -37,33 +33,12 @@ func (l AccessLevel) CanAccess(a AccessLevel) bool {
}

type AccessRule struct {
CredentialMatcher `mapstructure:",squash"`
Access AccessLevel `json:"access" pageship:"omitempty,accessLevel"`
ACLSubjectRule `mapstructure:",squash"`
Access AccessLevel `json:"access" pageship:"omitempty,accessLevel"`
}

func (r *AccessRule) SetDefaults() {
if r.Access == "" {
r.Access = AccessLevelDefault
}
}

type CredentialMatcher struct {
PageshipUser string `json:"pageshipUser,omitempty" pageship:"max=100"`
GitHubUser string `json:"githubUser,omitempty" pageship:"max=100"`
GitHubRepositoryActions string `json:"gitHubRepositoryActions,omitempty" pageship:"max=100"`
IpRange string `json:"ipRange,omitempty" pageship:"omitempty,max=100,cidr"`
}

func (c *CredentialMatcher) String() string {
switch {
case c.PageshipUser != "":
return fmt.Sprintf("pageshipUser:%s", c.PageshipUser)
case c.GitHubUser != "":
return fmt.Sprintf("githubUser:%s", c.GitHubUser)
case c.GitHubRepositoryActions != "":
return fmt.Sprintf("gitHubRepositoryActions:%s", c.GitHubRepositoryActions)
case c.IpRange != "":
return fmt.Sprintf("ipRange:%s", c.IpRange)
}
return "<unknown>"
}
4 changes: 2 additions & 2 deletions internal/config/app_deployment.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package config

type AppDeploymentsConfig struct {
Access []CredentialMatcher `json:"access" pageship:"omitempty"`
TTL string `json:"ttl" pageship:"omitempty,duration"`
Access ACL `json:"access" pageship:"omitempty"`
TTL string `json:"ttl" pageship:"omitempty,duration"`
}

func (c *AppDeploymentsConfig) SetDefaults() {
Expand Down
4 changes: 2 additions & 2 deletions internal/config/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ const SiteConfigName = "pageship"
const DefaultSite = "main"

type SiteConfig struct {
Public string `json:"public" pageship:"required"`
Access []CredentialMatcher `json:"access" pageship:"omitempty"`
Public string `json:"public" pageship:"required"`
Access ACL `json:"access" pageship:"omitempty"`
}

func DefaultSiteConfig() SiteConfig {
Expand Down
6 changes: 3 additions & 3 deletions internal/handler/controller/auth_github_ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ func (c *Controller) handleAuthGithubSSHConn(conn *websocket.Conn) {
return nil, fmt.Errorf("unknown public key for %q", meta.User())
}

if c.Config.UserCredentialsAllowlist != nil {
list, err := c.Config.UserCredentialsAllowlist.Get(conn.Request().Context())
if c.Config.ACL != nil {
acl, err := c.Config.ACL.Get(conn.Request().Context())
if err != nil {
return nil, fmt.Errorf("access denied")
}

creds := []models.CredentialID{models.CredentialGitHubUser(meta.User())}
creds = appendRequestCredentials(conn.Request(), creds)
if !list.IsAllowed(creds...) {
if _, err := models.CheckACLAuthz(acl, creds); err != nil {
log(conn.Request()).Info(
"user rejected",
zap.String("github_user", meta.User()),
Expand Down
17 changes: 8 additions & 9 deletions internal/handler/controller/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ package controller

import (
"github.com/oursky/pageship/internal/config"
"github.com/oursky/pageship/internal/models"
"github.com/oursky/pageship/internal/watch"
)

type Config struct {
MaxDeploymentSize int64
StorageKeyPrefix string
HostIDScheme config.HostIDScheme
HostPattern *config.HostPattern
ReservedApps map[string]struct{}
TokenAuthority string
TokenSigningKey []byte
UserCredentialsAllowlist *watch.File[config.Allowlist[models.CredentialID]]
MaxDeploymentSize int64
StorageKeyPrefix string
HostIDScheme config.HostIDScheme
HostPattern *config.HostPattern
ReservedApps map[string]struct{}
TokenAuthority string
TokenSigningKey []byte
ACL *watch.File[config.ACL]
}
2 changes: 1 addition & 1 deletion internal/handler/site/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (h *Handler) checkAuthz(r *http.Request, handler *SiteHandler) error {
credentials = append(credentials, models.CredentialIP(ip))
}

_, err = models.CheckDeploymentAuthz(access, credentials)
_, err = models.CheckACLAuthz(access, credentials)
return err
}

Expand Down
8 changes: 4 additions & 4 deletions internal/models/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ func NewApp(now time.Time, id string, ownerUserID string) *App {
func (a *App) CredentialIndexKeys() []CredentialIndexKey {
m := make(map[CredentialIndexKey]struct{})

collectIndexKeys(m, &config.CredentialMatcher{PageshipUser: a.OwnerUserID})
collectIndexKeys(m, &config.ACLSubjectRule{PageshipUser: a.OwnerUserID})
for _, r := range a.Config.Team {
collectIndexKeys(m, &r.CredentialMatcher)
collectIndexKeys(m, &r.ACLSubjectRule)
}

var keys []CredentialIndexKey
Expand All @@ -47,8 +47,8 @@ func (a *App) CredentialIndexKeys() []CredentialIndexKey {
return keys
}

func collectIndexKeys(keys map[CredentialIndexKey]struct{}, m *config.CredentialMatcher) {
for _, k := range MakeCredentialMatcherIndexKeys(m) {
func collectIndexKeys(keys map[CredentialIndexKey]struct{}, r *config.ACLSubjectRule) {
for _, k := range MakeCredentialRuleIndexKeys(r) {
keys[k] = struct{}{}
}
}
20 changes: 10 additions & 10 deletions internal/models/app_authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,30 @@ import "github.com/oursky/pageship/internal/config"

type AppAuthzResult struct {
CredentialID CredentialID
Matcher *config.CredentialMatcher // nil => is owner
Rule *config.ACLSubjectRule // nil => is owner
}

func (i *AppAuthzResult) MatchedRule() string {
if i.Matcher == nil {
if i.Rule == nil {
return "<owner>"
}
return i.Matcher.String()
return i.Rule.String()
}

func (a *App) CheckAuthz(level config.AccessLevel, userID string, credentials []CredentialID) (*AppAuthzResult, error) {
if userID != "" && a.OwnerUserID == userID {
return &AppAuthzResult{
CredentialID: CredentialUserID(a.OwnerUserID),
Matcher: nil,
Rule: nil,
}, nil
}

for _, r := range a.Config.Team {
for _, id := range credentials {
if id.Matches(&r.CredentialMatcher) && r.Access.CanAccess(level) {
if id.Matches(&r.ACLSubjectRule) && r.Access.CanAccess(level) {
return &AppAuthzResult{
CredentialID: id,
Matcher: &r.CredentialMatcher,
Rule: &r.ACLSubjectRule,
}, nil
}
}
Expand All @@ -36,13 +36,13 @@ func (a *App) CheckAuthz(level config.AccessLevel, userID string, credentials []
return nil, ErrAccessDenied
}

func CheckDeploymentAuthz(access []config.CredentialMatcher, credentials []CredentialID) (*AppAuthzResult, error) {
for _, m := range access {
func CheckACLAuthz(access config.ACL, credentials []CredentialID) (*AppAuthzResult, error) {
for _, r := range access {
for _, id := range credentials {
if id.Matches(&m) {
if id.Matches(&r) {
return &AppAuthzResult{
CredentialID: id,
Matcher: &m,
Rule: &r,
}, nil
}
}
Expand Down
Loading

0 comments on commit 0679e39

Please sign in to comment.