diff --git a/src/mig/agent/acl.go b/src/mig/agent/acl.go new file mode 100644 index 00000000..ac9ece36 --- /dev/null +++ b/src/mig/agent/acl.go @@ -0,0 +1,86 @@ +/* Mozilla InvestiGator Agent + +Version: MPL 1.1/GPL 2.0/LGPL 2.1 + +The contents of this file are subject to the Mozilla Public License Version +1.1 (the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at +http://www.mozilla.org/MPL/ + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +for the specific language governing rights and limitations under the +License. + +The Initial Developer of the Original Code is +Mozilla Corporation +Portions created by the Initial Developer are Copyright (C) 2014 +the Initial Developer. All Rights Reserved. + +Contributor(s): +Julien Vehent jvehent@mozilla.com [:ulfr] +Guillaume Destuynder + +Alternatively, the contents of this file may be used under the terms of +either the GNU General Public License Version 2 or later (the "GPL"), or +the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +in which case the provisions of the GPL or the LGPL are applicable instead +of those above. If you wish to allow use of your version of this file only +under the terms of either the GPL or the LGPL, and not to allow others to +use your version of this file under the terms of the MPL, indicate your +decision by deleting the provisions above and replace them with the notice +and other provisions required by the GPL or the LGPL. If you do not delete +the provisions above, a recipient may use your version of this file under +the terms of any one of the MPL, the GPL or the LGPL. +*/ + +package main + +import ( + "fmt" + "mig" + "mig/pgp" + "time" +) + +// checkActionAuthorization verifies the PGP signatures of a given action +// against the Access Control List of the agent. +func checkActionAuthorization(a mig.Action, ctx Context) (err error) { + defer func() { + if e := recover(); e != nil { + err = fmt.Errorf("checkActionAuthorization() -> %v", e) + } + ctx.Channels.Log <- mig.Log{ActionID: a.ID, Desc: "leaving checkActionAuthorization()"}.Debug() + }() + // get an io.Reader from the public pgp key + keyring, keycount, err := pgp.ArmoredPubKeysToKeyring(PUBLICPGPKEYS[0:]) + if err != nil { + panic(err) + } + ctx.Channels.Log <- mig.Log{ActionID: a.ID, Desc: fmt.Sprintf("loaded %d keys", keycount)}.Debug() + + // Check the action syntax and signature + err = a.Validate() + if err != nil { + desc := fmt.Sprintf("action validation failed: %v", err) + ctx.Channels.Log <- mig.Log{ActionID: a.ID, Desc: desc}.Err() + panic(desc) + } + // Validate() checks that the action hasn't expired, but we need to + // check the start time ourselves + if time.Now().Before(a.ValidFrom) { + ctx.Channels.Log <- mig.Log{ActionID: a.ID, Desc: "action is scheduled for later"}.Err() + panic("Action ValidFrom date is in the future") + } + + // check ACLs, includes verifying signatures + err = a.VerifyACL(ctx.ACL, keyring) + if err != nil { + desc := fmt.Sprintf("action ACL verification failed: %v", err) + ctx.Channels.Log <- mig.Log{ActionID: a.ID, Desc: desc}.Err() + panic(desc) + } + + ctx.Channels.Log <- mig.Log{ActionID: a.ID, Desc: "ACL verification succeeded."}.Debug() + return +} diff --git a/src/mig/agent/agent.go b/src/mig/agent/agent.go index 9cb56569..f9902e5e 100644 --- a/src/mig/agent/agent.go +++ b/src/mig/agent/agent.go @@ -47,7 +47,6 @@ import ( "mig" "mig/modules/connected" "mig/modules/filechecker" - "mig/pgp" "os" "os/exec" "strings" @@ -245,33 +244,12 @@ func parseCommands(ctx Context, msg []byte) (err error) { panic(err) } - // get an io.Reader from the public pgp key - keyring, keycount, err := pgp.ArmoredPubKeysToKeyring(PUBLICPGPKEYS[0:]) + // verify the PGP signature of the action, and verify that + // the signer is authorized to perform this action + err = checkActionAuthorization(cmd.Action, ctx) if err != nil { panic(err) } - ctx.Channels.Log <- mig.Log{CommandID: cmd.ID, ActionID: cmd.Action.ID, Desc: fmt.Sprintf("loaded %d keys", keycount)}.Debug() - - // Check the action syntax and signature - err = cmd.Action.Validate() - if err != nil { - desc := fmt.Sprintf("action validation failed: %v", err) - ctx.Channels.Log <- mig.Log{CommandID: cmd.ID, ActionID: cmd.Action.ID, Desc: desc}.Err() - panic(desc) - } - err = cmd.Action.VerifySignature(keyring) - if err != nil { - desc := fmt.Sprintf("action signature verification failed: %v", err) - ctx.Channels.Log <- mig.Log{CommandID: cmd.ID, ActionID: cmd.Action.ID, Desc: desc}.Err() - panic(desc) - } - - // Expiration is verified by the Validate() call above, but we need - // to verify the ScheduledDate ourselves - if time.Now().Before(cmd.Action.ValidFrom) { - ctx.Channels.Log <- mig.Log{CommandID: cmd.ID, ActionID: cmd.Action.ID, Desc: "action is scheduled for later"}.Err() - panic("ScheduledDateInFuture") - } // Each operation is ran separately by a module, a channel is created to receive the results from each module // a goroutine is created to read from the result channel, and when all modules are done, build the response diff --git a/src/mig/agent/context.go b/src/mig/agent/context.go index b5d24e87..0144af6f 100644 --- a/src/mig/agent/context.go +++ b/src/mig/agent/context.go @@ -39,6 +39,7 @@ import ( "crypto/rand" "crypto/tls" "crypto/x509" + "encoding/json" "fmt" "github.com/VividCortex/godaemon" "github.com/streadway/amqp" @@ -55,7 +56,7 @@ import ( // logs and channels // Context is intended as a single structure that can be passed around easily. type Context struct { - OpID uint64 // ID of the current operation, used for tracking + ACL mig.ACL Agent struct { Hostname, OS, QueueLoc, UID, BinPath string } @@ -77,6 +78,7 @@ type Context struct { Chan *amqp.Channel Bind mig.Binding } + OpID uint64 // ID of the current operation, used for tracking Sleeper time.Duration // timer used when the agent has to sleep for a while Stats struct { } @@ -146,6 +148,12 @@ func Init(foreground bool) (ctx Context, err error) { panic(err) } + // parse the ACLs + ctx, err = initACL(ctx) + if err != nil { + panic(err) + } + // connect to the message broker ctx, err = initMQ(ctx) if err != nil { @@ -277,6 +285,30 @@ func createIDFile(loc string) (id []byte, err error) { return } +// parse the permissions from the configuration into an ACL structure +func initACL(orig_ctx Context) (ctx Context, err error) { + ctx = orig_ctx + defer func() { + if e := recover(); e != nil { + err = fmt.Errorf("initACL() -> %v", e) + } + ctx.Channels.Log <- mig.Log{Desc: "leaving initACL()"}.Debug() + }() + for _, jsonPermission := range AGENTACL { + var parsedPermission mig.Permission + err = json.Unmarshal([]byte(jsonPermission), &parsedPermission) + if err != nil { + panic(err) + } + for permName, _ := range parsedPermission { + desc := fmt.Sprintf("Loading permission named '%s'", permName) + ctx.Channels.Log <- mig.Log{Desc: desc}.Debug() + } + ctx.ACL = append(ctx.ACL, parsedPermission) + } + return +} + func initMQ(orig_ctx Context) (ctx Context, err error) { ctx = orig_ctx defer func() {