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:
Aaron Meihm 2017-10-17 17:29:42 -05:00
Родитель d39d7435f8
Коммит 8a1194722d
4 изменённых файлов: 280 добавлений и 38 удалений

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
}

245
acl_test.go Normal file
Просмотреть файл

@ -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")
}
}

Просмотреть файл

@ -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
}