2020-03-18 22:35:32 +00:00
|
|
|
// Package accessctl implements functionality related to allowing or denying
|
|
|
|
// actions in a repo based on who is taking what actions.
|
2020-02-15 22:13:50 +00:00
|
|
|
package accessctl
|
|
|
|
|
|
|
|
import (
|
2020-03-18 22:35:32 +00:00
|
|
|
"errors"
|
2020-02-15 22:13:50 +00:00
|
|
|
"fmt"
|
|
|
|
|
2020-04-11 23:10:18 +00:00
|
|
|
"dehub.dev/src/dehub.git/sigcred"
|
|
|
|
|
2020-03-18 22:35:32 +00:00
|
|
|
yaml "gopkg.in/yaml.v2"
|
2020-02-15 22:13:50 +00:00
|
|
|
)
|
|
|
|
|
2020-03-18 22:35:32 +00:00
|
|
|
// DefaultAccessControlsStr is the encoded form of the default access control
|
|
|
|
// set which is applied to all CommitRequests if no user-supplied ones match.
|
|
|
|
//
|
|
|
|
// The effect of these AccessControls is to allow all commit types on any branch
|
|
|
|
// (with the exception of the main branch, which only allows change commits), as
|
|
|
|
// long as the commit has one signature from a configured account.
|
|
|
|
var DefaultAccessControlsStr = `
|
|
|
|
- action: allow
|
|
|
|
filters:
|
|
|
|
- type: not
|
|
|
|
filter:
|
|
|
|
type: branch
|
|
|
|
pattern: main
|
|
|
|
- type: signature
|
|
|
|
any_account: true
|
|
|
|
count: 1
|
|
|
|
|
2020-04-24 19:33:33 +00:00
|
|
|
- action: deny
|
|
|
|
filters:
|
|
|
|
- type: commit_attributes
|
|
|
|
non_fast_forward: true
|
|
|
|
|
2020-03-18 22:35:32 +00:00
|
|
|
- action: allow
|
|
|
|
filters:
|
|
|
|
- type: branch
|
|
|
|
pattern: main
|
2020-04-26 20:23:03 +00:00
|
|
|
- type: payload_type
|
|
|
|
payload_type: change
|
2020-03-18 22:35:32 +00:00
|
|
|
- type: signature
|
|
|
|
any_account: true
|
|
|
|
count: 1
|
|
|
|
|
|
|
|
- action: deny
|
|
|
|
`
|
|
|
|
|
|
|
|
// DefaultAccessControls is the decoded form of DefaultAccessControlsStr.
|
|
|
|
var DefaultAccessControls = func() []AccessControl {
|
|
|
|
var acl []AccessControl
|
|
|
|
if err := yaml.Unmarshal([]byte(DefaultAccessControlsStr), &acl); err != nil {
|
|
|
|
panic(err)
|
2020-03-14 22:14:18 +00:00
|
|
|
}
|
2020-03-18 22:35:32 +00:00
|
|
|
return acl
|
|
|
|
}()
|
2020-03-14 22:14:18 +00:00
|
|
|
|
2020-03-18 22:35:32 +00:00
|
|
|
// CommitRequest is used to describe a set of interactions which are being
|
|
|
|
// requested to be performed.
|
|
|
|
type CommitRequest struct {
|
|
|
|
// Type describes what type of commit is being requested. Possibilities are
|
|
|
|
// determined by the requester.
|
|
|
|
Type string
|
2020-03-14 22:14:18 +00:00
|
|
|
|
2020-02-29 20:02:25 +00:00
|
|
|
// Branch is the name of the branch the interactions are being attempted on.
|
|
|
|
// It is required.
|
|
|
|
Branch string
|
|
|
|
|
2020-04-26 20:23:03 +00:00
|
|
|
// Credentials are the credentials attached to the commit.
|
|
|
|
Credentials []sigcred.CredentialUnion
|
2020-03-14 22:14:18 +00:00
|
|
|
|
2020-03-18 22:35:32 +00:00
|
|
|
// FilesChanged is the set of file paths (relative to the repo root) which
|
|
|
|
// have been modified in some way.
|
|
|
|
FilesChanged []string
|
2020-04-24 19:33:33 +00:00
|
|
|
|
|
|
|
// NonFastForward should be set to true if the branch HEAD and this commit
|
|
|
|
// are not directly related (i.e. neither is a direct ancestor of the
|
|
|
|
// other).
|
|
|
|
NonFastForward bool
|
2020-02-15 22:13:50 +00:00
|
|
|
}
|
|
|
|
|
2020-03-18 22:35:32 +00:00
|
|
|
// Action describes what action an AccessControl should perform
|
|
|
|
// when given a CommitRequest.
|
|
|
|
type Action string
|
2020-02-29 20:02:25 +00:00
|
|
|
|
2020-03-18 22:35:32 +00:00
|
|
|
// Enumerates possible Action values
|
|
|
|
const (
|
|
|
|
ActionAllow Action = "allow"
|
|
|
|
ActionDeny Action = "deny"
|
2020-03-14 22:14:18 +00:00
|
|
|
|
2020-03-18 22:35:32 +00:00
|
|
|
// ActionNext is used internally when a request does not match an
|
|
|
|
// AccessControl's filters. It _could_ be used in the Config as well, but it
|
|
|
|
// would be pretty pointless to do so, so we don't talk about it.
|
|
|
|
ActionNext Action = "next"
|
|
|
|
)
|
2020-03-14 22:14:18 +00:00
|
|
|
|
2020-03-18 22:35:32 +00:00
|
|
|
// AccessControl describes a set of Filters, and the Actions which should be
|
|
|
|
// taken on a CommitRequest if those Filters all match on the CommitRequest.
|
|
|
|
type AccessControl struct {
|
2020-04-26 20:23:03 +00:00
|
|
|
Action Action `yaml:"action"`
|
|
|
|
Filters []FilterUnion `yaml:"filters"`
|
2020-02-15 22:13:50 +00:00
|
|
|
}
|
|
|
|
|
2020-03-18 22:35:32 +00:00
|
|
|
// ActionForCommit returns what Action this AccessControl says to take for a
|
|
|
|
// given CommitRequest. It may return ActionNext if the request is not matched
|
|
|
|
// by the AccessControl's Filters.
|
|
|
|
func (ac AccessControl) ActionForCommit(req CommitRequest) (Action, error) {
|
2020-04-26 20:23:03 +00:00
|
|
|
for _, filterUn := range ac.Filters {
|
|
|
|
if err := filterUn.Filter().MatchCommit(req); errors.As(err, new(ErrFilterNoMatch)) {
|
2020-03-18 22:35:32 +00:00
|
|
|
return ActionNext, nil
|
|
|
|
|
|
|
|
} else if err != nil {
|
2020-04-26 20:23:03 +00:00
|
|
|
return "", fmt.Errorf("matching commit using filter of type %q: %w", filterUn.Type(), err)
|
2020-02-15 22:13:50 +00:00
|
|
|
}
|
|
|
|
}
|
2020-03-18 22:35:32 +00:00
|
|
|
return ac.Action, nil
|
|
|
|
}
|
2020-02-15 22:13:50 +00:00
|
|
|
|
2020-03-18 22:35:32 +00:00
|
|
|
// ErrCommitRequestDenied is returned from AssertCanCommit when a particular
|
|
|
|
// AccessControl has explicitly disallowed the CommitRequest.
|
|
|
|
type ErrCommitRequestDenied struct {
|
|
|
|
By AccessControl
|
|
|
|
}
|
2020-02-29 20:02:25 +00:00
|
|
|
|
2020-03-18 22:35:32 +00:00
|
|
|
func (e ErrCommitRequestDenied) Error() string {
|
|
|
|
acB, err := yaml.Marshal(e.By)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
2020-02-15 22:13:50 +00:00
|
|
|
}
|
2020-03-18 22:35:32 +00:00
|
|
|
return fmt.Sprintf("commit matched and denied by this access control:\n%s", string(acB))
|
|
|
|
}
|
2020-02-29 20:02:25 +00:00
|
|
|
|
2020-03-18 22:35:32 +00:00
|
|
|
// AssertCanCommit asserts that the given CommitRequest is allowed by the given
|
|
|
|
// AccessControls.
|
|
|
|
func AssertCanCommit(acl []AccessControl, req CommitRequest) error {
|
|
|
|
acl = append(acl, DefaultAccessControls...)
|
|
|
|
for _, ac := range acl {
|
|
|
|
action, err := ac.ActionForCommit(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2020-03-14 22:14:18 +00:00
|
|
|
}
|
2020-03-18 22:35:32 +00:00
|
|
|
switch action {
|
|
|
|
case ActionNext:
|
|
|
|
continue
|
|
|
|
case ActionAllow:
|
|
|
|
return nil
|
|
|
|
case ActionDeny:
|
|
|
|
return ErrCommitRequestDenied{By: ac}
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("invalid action %q", action)
|
2020-03-14 22:14:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-18 22:35:32 +00:00
|
|
|
panic("should not be able to get here")
|
2020-02-15 22:13:50 +00:00
|
|
|
}
|