зеркало из https://github.com/mozilla/mig.git
simplify ACL type and remove Permission, fix authorization bypass
This change removes the Permission type, and modifies ACL to simply be a map of a given module name and the authorization specification. It was previously stored as a list of ACL types, with each type being a map which was not required. This also fixes a bug in authorization, where an action would be authorized if the first operation validated against the ACL. This means, it was possible if an action contained more than one operation, an investigator could be authorized for execution if the first module matched the ACL. Note that for this to happen, the first operation checked would need to be authorized and in general deployments this does not have a significant impact. Resolves #394
This commit is contained in:
Родитель
d39d7435f8
Коммит
8a1194722d
44
acl.go
44
acl.go
|
@ -11,9 +11,14 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
type ACL []Permission
|
||||
|
||||
type Permission map[string]struct {
|
||||
// ACL defines an access control list used by the agent to determine what investigators
|
||||
// can call a given module. The key in this map is the module name, and can be "default" in
|
||||
// which case this element will be used if no key for a given module exists.
|
||||
//
|
||||
// The value includes a minimum weight to authorize the request, and a map of investigators
|
||||
// with the key of the map being the name of the investigator, and the value storing the PGP
|
||||
// fingerprint of the investigators key and the weight that investigator has.
|
||||
type ACL map[string]struct {
|
||||
MinimumWeight int
|
||||
Investigators map[string]struct {
|
||||
Fingerprint string
|
||||
|
@ -21,12 +26,21 @@ type Permission map[string]struct {
|
|||
}
|
||||
}
|
||||
|
||||
// verifyPermission controls that the PGP keys, identified by their fingerprints, that
|
||||
// signed an operation are sufficient to allow this operation to run
|
||||
func verifyPermission(operation Operation, permName string, perm Permission, fingerprints []string) (err error) {
|
||||
if perm[permName].MinimumWeight < 1 {
|
||||
return fmt.Errorf("Invalid permission '%s'. Must require at least 1 signature, has %d",
|
||||
permName, perm[permName].MinimumWeight)
|
||||
// verifyPermission validates that the fingerprints are permitted to execute an operation by
|
||||
// comparing the fingerprints to the fingerprints and weight specifications in ACL acl.
|
||||
func verifyPermission(moduleName string, acl ACL, fingerprints []string) error {
|
||||
aclname := moduleName
|
||||
aclent, ok := acl[moduleName]
|
||||
if !ok {
|
||||
// No ACL entry found for this module name, see if we can find a default
|
||||
aclent, ok = acl["default"]
|
||||
if !ok {
|
||||
return fmt.Errorf("No ACL entry found for %v, and no default present", moduleName)
|
||||
}
|
||||
aclname = "default"
|
||||
}
|
||||
if aclent.MinimumWeight < 1 {
|
||||
return fmt.Errorf("Invalid ACL %v, weight must be > 0", aclname)
|
||||
}
|
||||
var seenFp []string
|
||||
signaturesWeight := 0
|
||||
|
@ -34,19 +48,19 @@ func verifyPermission(operation Operation, permName string, perm Permission, fin
|
|||
// if the same key is used to sign multiple times, return an error
|
||||
for _, seen := range seenFp {
|
||||
if seen == fp {
|
||||
return fmt.Errorf("Permission violation: key id '%s' used to sign multiple times.", fp)
|
||||
return fmt.Errorf("Permission violation: key %v used to sign multiple times", fp)
|
||||
}
|
||||
}
|
||||
for _, signer := range perm[permName].Investigators {
|
||||
for _, signer := range aclent.Investigators {
|
||||
if strings.ToUpper(fp) == strings.ToUpper(signer.Fingerprint) {
|
||||
signaturesWeight += signer.Weight
|
||||
}
|
||||
}
|
||||
seenFp = append(seenFp, fp)
|
||||
}
|
||||
if signaturesWeight < perm[permName].MinimumWeight {
|
||||
return fmt.Errorf("Permission denied for operation '%s'. Insufficient signatures weight. Need %d, got %d",
|
||||
operation.Module, perm[permName].MinimumWeight, signaturesWeight)
|
||||
if signaturesWeight < aclent.MinimumWeight {
|
||||
return fmt.Errorf("Permission denied for operation %v, insufficient signatures weight"+
|
||||
" (need %v, got %v)", moduleName, aclent.MinimumWeight, signaturesWeight)
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,245 @@
|
|||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Contributor: Aaron Meihm ameihm@mozilla.com [:alm]
|
||||
|
||||
package mig /* import "mig.ninja/mig" */
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidACL(t *testing.T) {
|
||||
aclstr := `{
|
||||
"file": {
|
||||
"minimumweight": 1,
|
||||
"investigators": {
|
||||
"test": {
|
||||
"fingerprint": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"weight": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
var acl ACL
|
||||
err := json.Unmarshal([]byte(aclstr), &acl)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Unmarshal: %v", err)
|
||||
}
|
||||
err = verifyPermission("file", acl, []string{"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"})
|
||||
if err != nil {
|
||||
t.Fatalf("verifyPermission should have returned no error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidACLDefault(t *testing.T) {
|
||||
aclstr := `{
|
||||
"default": {
|
||||
"minimumweight": 1,
|
||||
"investigators": {
|
||||
"test": {
|
||||
"fingerprint": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"weight": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
var acl ACL
|
||||
err := json.Unmarshal([]byte(aclstr), &acl)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Unmarshal: %v", err)
|
||||
}
|
||||
err = verifyPermission("file", acl, []string{"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"})
|
||||
if err != nil {
|
||||
t.Fatalf("verifyPermission should have returned no error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidACLMultiple(t *testing.T) {
|
||||
aclstr := `{
|
||||
"default": {
|
||||
"minimumweight": 1,
|
||||
"investigators": {
|
||||
"test3": {
|
||||
"fingerprint": "CCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"weight": 1
|
||||
},
|
||||
"test2": {
|
||||
"fingerprint": "BBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"weight": 1
|
||||
},
|
||||
"test": {
|
||||
"fingerprint": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"weight": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
var acl ACL
|
||||
err := json.Unmarshal([]byte(aclstr), &acl)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Unmarshal: %v", err)
|
||||
}
|
||||
err = verifyPermission("file", acl, []string{"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"})
|
||||
if err != nil {
|
||||
t.Fatalf("verifyPermission should have returned no error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidACLMultipleSigners(t *testing.T) {
|
||||
aclstr := `{
|
||||
"default": {
|
||||
"minimumweight": 2,
|
||||
"investigators": {
|
||||
"test3": {
|
||||
"fingerprint": "CCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"weight": 1
|
||||
},
|
||||
"test2": {
|
||||
"fingerprint": "BBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"weight": 1
|
||||
},
|
||||
"test": {
|
||||
"fingerprint": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"weight": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
var acl ACL
|
||||
err := json.Unmarshal([]byte(aclstr), &acl)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Unmarshal: %v", err)
|
||||
}
|
||||
err = verifyPermission("file", acl, []string{"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"BBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"})
|
||||
if err != nil {
|
||||
t.Fatalf("verifyPermission should have returned no error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidACLMultipleSignersOneValid(t *testing.T) {
|
||||
aclstr := `{
|
||||
"default": {
|
||||
"minimumweight": 2,
|
||||
"investigators": {
|
||||
"test3": {
|
||||
"fingerprint": "CCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"weight": 1
|
||||
},
|
||||
"test2": {
|
||||
"fingerprint": "BBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"weight": 1
|
||||
},
|
||||
"test": {
|
||||
"fingerprint": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"weight": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
var acl ACL
|
||||
err := json.Unmarshal([]byte(aclstr), &acl)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Unmarshal: %v", err)
|
||||
}
|
||||
err = verifyPermission("file", acl, []string{"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"DDDDDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"})
|
||||
if err == nil {
|
||||
t.Fatalf("verifyPermission should have failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidACLNoFingerprint(t *testing.T) {
|
||||
aclstr := `{
|
||||
"file": {
|
||||
"minimumweight": 1,
|
||||
"investigators": {
|
||||
"test": {
|
||||
"fingerprint": "BBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"weight": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
var acl ACL
|
||||
err := json.Unmarshal([]byte(aclstr), &acl)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Unmarshal: %v", err)
|
||||
}
|
||||
err = verifyPermission("file", acl, []string{"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"})
|
||||
if err == nil {
|
||||
t.Fatalf("verifyPermission should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidACLNoModuleEntry(t *testing.T) {
|
||||
aclstr := `{
|
||||
"somemodule": {
|
||||
"minimumweight": 1,
|
||||
"investigators": {
|
||||
"test": {
|
||||
"fingerprint": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"weight": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
var acl ACL
|
||||
err := json.Unmarshal([]byte(aclstr), &acl)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Unmarshal: %v", err)
|
||||
}
|
||||
err = verifyPermission("file", acl, []string{"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"})
|
||||
if err == nil {
|
||||
t.Fatalf("verifyPermission should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidACLBadWeight(t *testing.T) {
|
||||
aclstr := `{
|
||||
"file": {
|
||||
"minimumweight": 2,
|
||||
"investigators": {
|
||||
"test": {
|
||||
"fingerprint": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"weight": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
var acl ACL
|
||||
err := json.Unmarshal([]byte(aclstr), &acl)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Unmarshal: %v", err)
|
||||
}
|
||||
err = verifyPermission("file", acl, []string{"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"})
|
||||
if err == nil {
|
||||
t.Fatalf("verifyPermission should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidACLZeroFingerprints(t *testing.T) {
|
||||
aclstr := `{
|
||||
"file": {
|
||||
"minimumweight": 2,
|
||||
"investigators": {
|
||||
"test": {
|
||||
"fingerprint": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"weight": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
var acl ACL
|
||||
err := json.Unmarshal([]byte(aclstr), &acl)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Unmarshal: %v", err)
|
||||
}
|
||||
err = verifyPermission("file", acl, []string{})
|
||||
if err == nil {
|
||||
t.Fatalf("verifyPermission should have failed")
|
||||
}
|
||||
}
|
25
action.go
25
action.go
|
@ -380,28 +380,13 @@ func (a Action) VerifyACL(acl ACL, keyring io.Reader, onlyVerifyPubKey bool) (er
|
|||
return
|
||||
}
|
||||
|
||||
// Then, for each operation contained in the action, look for
|
||||
// a permission that apply to it, by comparing the operation name
|
||||
// with permission name. If none is found, use the default permission.
|
||||
// Authorize access to the operation by verifying the fingerprints present against our
|
||||
// ACLs
|
||||
for _, operation := range a.Operations {
|
||||
for _, permission := range acl {
|
||||
for permName, _ := range permission {
|
||||
if permName == operation.Module {
|
||||
return verifyPermission(operation, permName, permission, fingerprints)
|
||||
}
|
||||
}
|
||||
err = verifyPermission(operation.Module, acl, fingerprints)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// no specific permission found, apply the default permission
|
||||
var defaultPerm Permission
|
||||
for _, permission := range acl {
|
||||
for permName, _ := range permission {
|
||||
if permName == "default" {
|
||||
defaultPerm = permission
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return verifyPermission(operation, "default", defaultPerm, fingerprints)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -318,12 +318,10 @@ func initACL(orig_ctx Context) (ctx Context, err error) {
|
|||
ctx.Channels.Log <- mig.Log{Desc: fmt.Sprintf("loading acls from %v", aclpath)}.Info()
|
||||
}
|
||||
|
||||
var perms mig.Permission
|
||||
err = json.Unmarshal([]byte(AGENTACL), &perms)
|
||||
err = json.Unmarshal([]byte(AGENTACL), &ctx.ACL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ctx.ACL = append(ctx.ACL, perms)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче