зеркало из https://github.com/mozilla/mig.git
[medium] Integrate ACL verification into Agent action validation code
This commit is contained in:
Родитель
8b9e360879
Коммит
9ef088aa17
|
@ -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 <kang@mozilla.com>
|
||||
|
||||
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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
Загрузка…
Ссылка в новой задаче