зеркало из https://github.com/mozilla/mig.git
Merge pull request #256 from ameihm0912/loaderexpect
[medium] add environment validation to loader authorization
This commit is contained in:
Коммит
9e7799c679
|
@ -595,6 +595,46 @@ func (cli Client) GetLoaderEntry(lid float64) (le mig.LoaderEntry, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// Change the expect fields of an existing loader entry
|
||||
func (cli Client) LoaderEntryExpect(le mig.LoaderEntry, eval string) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("LoaderEntryExpect() -> %v", e)
|
||||
}
|
||||
}()
|
||||
data := url.Values{"loaderid": {fmt.Sprintf("%.0f", le.ID)},
|
||||
"expectenv": {eval},
|
||||
}
|
||||
r, err := http.NewRequest("POST", cli.Conf.API.URL+"loader/expect/",
|
||||
strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err := cli.Do(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var resource *cljs.Resource
|
||||
if len(body) > 1 {
|
||||
err = json.Unmarshal(body, &resource)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf("error: HTTP %d. Expect update failed with error '%v' (code %s).",
|
||||
resp.StatusCode, resource.Collection.Error.Message, resource.Collection.Error.Code)
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Change the status of an existing loader entry
|
||||
func (cli Client) LoaderEntryStatus(le mig.LoaderEntry, status bool) (err error) {
|
||||
defer func() {
|
||||
|
|
|
@ -49,7 +49,8 @@ func loaderReader(input string, cli client.Client) (err error) {
|
|||
}
|
||||
fmt.Println("reloaded")
|
||||
}
|
||||
var symbols = []string{"disable", "enable", "exit", "help", "json", "key", "r"}
|
||||
var symbols = []string{"disable", "enable", "expectenv",
|
||||
"exit", "help", "json", "key", "r"}
|
||||
readline.Completer = func(query, ctx string) []string {
|
||||
var res []string
|
||||
for _, sym := range symbols {
|
||||
|
@ -84,21 +85,34 @@ func loaderReader(input string, cli client.Client) (err error) {
|
|||
}
|
||||
fmt.Println("Loader has been enabled")
|
||||
reloadfunc()
|
||||
case "expectenv":
|
||||
sv := ""
|
||||
if len(orders) >= 2 {
|
||||
sv = strings.Join(orders[1:], " ")
|
||||
}
|
||||
err = cli.LoaderEntryExpect(le, sv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Expected environment match set")
|
||||
reloadfunc()
|
||||
case "help":
|
||||
fmt.Printf(`The following orders are avialable:
|
||||
disable disable loader entry
|
||||
disable disable loader entry
|
||||
|
||||
enable enable loader entry
|
||||
enable enable loader entry
|
||||
|
||||
help show this help
|
||||
expectenv <val> set expected environment match, omit val to remove
|
||||
|
||||
exit exit this mode (also works with ctrl+d)
|
||||
help show this help
|
||||
|
||||
json show json of loader entry stored in database
|
||||
exit exit this mode (also works with ctrl+d)
|
||||
|
||||
key change loader key
|
||||
json show json of loader entry stored in database
|
||||
|
||||
r refresh the loader entry (get latest version from database)
|
||||
key change loader key
|
||||
|
||||
r refresh the loader entry (get latest version from database)
|
||||
`)
|
||||
case "exit":
|
||||
fmt.Printf("exit\n")
|
||||
|
@ -161,6 +175,11 @@ func loaderCreator(cli client.Client) (err error) {
|
|||
panic("input name too short")
|
||||
}
|
||||
fmt.Printf("Name: '%s'\n", newle.Name)
|
||||
fmt.Println("Provide expected environment target string, or enter for none")
|
||||
newle.ExpectEnv, err = readline.String("expectenv> ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Generating loader prefix...")
|
||||
newle.Prefix = mig.GenerateLoaderPrefix()
|
||||
fmt.Println("Generating loader key...")
|
||||
|
|
|
@ -79,6 +79,54 @@ func (db *DB) UpdateLoaderEntry(lid float64, agt mig.Agent) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// If any expected environment has been set on a loader entry, this function
|
||||
// validates the environment submitted by the loader matches that expected
|
||||
// query string; returns an error if not
|
||||
func (db *DB) CompareLoaderExpectEnv(lid float64) error {
|
||||
var (
|
||||
expectenv sql.NullString
|
||||
result bool
|
||||
)
|
||||
rerr := fmt.Errorf("loader environment verification failed")
|
||||
txn, err := db.c.Begin()
|
||||
if err != nil {
|
||||
txn.Rollback()
|
||||
return rerr
|
||||
}
|
||||
_, err = txn.Exec("SET LOCAL ROLE migreadonly")
|
||||
if err != nil {
|
||||
txn.Rollback()
|
||||
return rerr
|
||||
}
|
||||
err = txn.QueryRow(`SELECT expectenv FROM loaders WHERE
|
||||
id=$1`, lid).Scan(&expectenv)
|
||||
if err != nil {
|
||||
txn.Rollback()
|
||||
return rerr
|
||||
}
|
||||
// If no expected environment is set, we are done here
|
||||
if !expectenv.Valid || expectenv.String == "" {
|
||||
err = txn.Commit()
|
||||
if err != nil {
|
||||
txn.Rollback()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
qfmt := fmt.Sprintf("SELECT TRUE FROM loaders WHERE id=$1 AND %v", expectenv.String)
|
||||
err = txn.QueryRow(qfmt, lid).Scan(&result)
|
||||
if err != nil {
|
||||
txn.Rollback()
|
||||
return rerr
|
||||
}
|
||||
err = txn.Commit()
|
||||
if err != nil {
|
||||
txn.Rollback()
|
||||
return rerr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Given a loader ID, identify which manifest is applicable to return to this
|
||||
// loader in a manifest request
|
||||
func (db *DB) ManifestIDFromLoaderID(lid float64) (ret float64, err error) {
|
||||
|
@ -152,10 +200,12 @@ func (db *DB) AllLoadersFromManifestID(mid float64) (ret []mig.LoaderEntry, err
|
|||
|
||||
// Return a loader entry given an ID
|
||||
func (db *DB) GetLoaderFromID(lid float64) (ret mig.LoaderEntry, err error) {
|
||||
var name sql.NullString
|
||||
err = db.c.QueryRow(`SELECT id, loadername, keyprefix, name, lastseen, enabled
|
||||
var name, expectenv sql.NullString
|
||||
err = db.c.QueryRow(`SELECT id, loadername, keyprefix, name, lastseen, enabled,
|
||||
expectenv
|
||||
FROM loaders WHERE id=$1`, lid).Scan(&ret.ID, &ret.Name,
|
||||
&ret.Prefix, &name, &ret.LastSeen, &ret.Enabled)
|
||||
&ret.Prefix, &name, &ret.LastSeen, &ret.Enabled,
|
||||
&expectenv)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error while retrieving loader: '%v'", err)
|
||||
return
|
||||
|
@ -163,6 +213,9 @@ func (db *DB) GetLoaderFromID(lid float64) (ret mig.LoaderEntry, err error) {
|
|||
if name.Valid {
|
||||
ret.AgentName = name.String
|
||||
}
|
||||
if expectenv.Valid {
|
||||
ret.ExpectEnv = expectenv.String
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -176,6 +229,21 @@ func (db *DB) LoaderUpdateStatus(lid float64, status bool) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// Update loader expect fields
|
||||
func (db *DB) LoaderUpdateExpect(lid float64, eenv string) (err error) {
|
||||
var eset sql.NullString
|
||||
if eenv != "" {
|
||||
eset.String = eenv
|
||||
eset.Valid = true
|
||||
}
|
||||
_, err = db.c.Exec(`UPDATE loaders SET expectenv=$1 WHERE
|
||||
id=$2`, eset, lid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Change loader key, hashkey should be the hashed version of the key component
|
||||
func (db *DB) LoaderUpdateKey(lid float64, hashkey []byte, salt []byte) (err error) {
|
||||
_, err = db.c.Exec(`UPDATE loaders SET loaderkey=$1, salt=$2 WHERE
|
||||
|
@ -189,10 +257,16 @@ func (db *DB) LoaderUpdateKey(lid float64, hashkey []byte, salt []byte) (err err
|
|||
// Add a new loader entry to the database; the hashed loader key should
|
||||
// be provided as hashkey
|
||||
func (db *DB) LoaderAdd(le mig.LoaderEntry, hashkey []byte, salt []byte) (err error) {
|
||||
var eval sql.NullString
|
||||
if le.ExpectEnv != "" {
|
||||
eval.String = le.ExpectEnv
|
||||
eval.Valid = true
|
||||
}
|
||||
_, err = db.c.Exec(`INSERT INTO loaders
|
||||
(loadername, keyprefix, loaderkey, salt, lastseen, enabled)
|
||||
(loadername, keyprefix, loaderkey, salt, lastseen, enabled,
|
||||
expectenv)
|
||||
VALUES
|
||||
($1, $2, $3, $4, now(), FALSE)`, le.Name,
|
||||
le.Prefix, hashkey, salt)
|
||||
($1, $2, $3, $4, now(), FALSE, $5)`, le.Name,
|
||||
le.Prefix, hashkey, salt, eval)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -140,7 +140,8 @@ CREATE TABLE loaders (
|
|||
env json,
|
||||
tags json,
|
||||
lastseen timestamp with time zone NOT NULL,
|
||||
enabled boolean NOT NULL DEFAULT false
|
||||
enabled boolean NOT NULL DEFAULT false,
|
||||
expectenv character varying(2048)
|
||||
);
|
||||
ALTER TABLE ONLY loaders
|
||||
ADD CONSTRAINT loaders_pkey PRIMARY KEY (id);
|
||||
|
@ -207,7 +208,7 @@ GRANT INSERT ON actions, signatures, manifests, manifestsig, loaders TO migapi;
|
|||
GRANT DELETE ON manifestsig TO migapi;
|
||||
GRANT INSERT (name, pgpfingerprint, publickey, status, createdat, lastmodified, isadmin) ON investigators TO migapi;
|
||||
GRANT UPDATE (isadmin, status, lastmodified) ON investigators TO migapi;
|
||||
GRANT UPDATE (name, env, tags, loaderkey, salt, lastseen, enabled) ON loaders TO migapi;
|
||||
GRANT UPDATE (name, env, tags, loaderkey, salt, lastseen, enabled, expectenv) ON loaders TO migapi;
|
||||
GRANT UPDATE (status) ON manifests TO migapi;
|
||||
GRANT USAGE ON SEQUENCE investigators_id_seq TO migapi;
|
||||
GRANT USAGE ON SEQUENCE loaders_id_seq TO migapi;
|
||||
|
@ -217,6 +218,7 @@ GRANT USAGE ON SEQUENCE manifests_id_seq TO migapi;
|
|||
CREATE ROLE migreadonly;
|
||||
ALTER ROLE migreadonly WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN;
|
||||
GRANT SELECT ON actions, agents, agtmodreq, commands, invagtmodperm, modules, signatures TO migreadonly;
|
||||
GRANT SELECT (id, env, tags, expectenv) ON loaders TO migreadonly;
|
||||
GRANT SELECT (id, name, pgpfingerprint, publickey, status, createdat, lastmodified) ON investigators TO migreadonly;
|
||||
GRANT migreadonly TO migapi;
|
||||
GRANT migreadonly TO migscheduler;
|
||||
|
|
|
@ -315,6 +315,8 @@ to that system.
|
|||
Please provide the name of the new entry
|
||||
name> corbomite.internal
|
||||
Name: 'corbomite.internal'
|
||||
Provide expected environment target string, or enter for none
|
||||
expectenv> tags#>>'{operator}'='myorg'
|
||||
Generating loader prefix...
|
||||
Generating loader key...
|
||||
{
|
||||
|
@ -325,6 +327,7 @@ to that system.
|
|||
"agentname": "",
|
||||
"lastseen": "0001-01-01T00:00:00Z",
|
||||
"enabled": false
|
||||
"expectenv": "tags#\u003e\u003e'{operator}'='myorg'"
|
||||
}
|
||||
|
||||
Loader key including prefix to supply to client will be "qqLwjje7BNbZUenzBaucYKgK6ubkz0yqDZ7k4kNX"
|
||||
|
@ -334,7 +337,13 @@ to that system.
|
|||
|
||||
The name can be any value you want, but usually you will want something describing
|
||||
the system or in the case of a workstation something describing the user of the
|
||||
device. Here we just used the hostname. The key including prefix is the API key that
|
||||
device. Here we just used the hostname.
|
||||
|
||||
If desired, an expected environment value can be set on the instance. If set, this target string
|
||||
must match desired parts of the environment the loader is sending, if it does not the request will
|
||||
be rejected.
|
||||
|
||||
The key including prefix is the API key that
|
||||
will need to be configured in mig-loader on that system to allow it to authenticate as
|
||||
this loader instance.
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ type LoaderEntry struct {
|
|||
AgentName string `json:"agentname"` // Loader environment, agent name
|
||||
LastSeen time.Time `json:"lastseen"` // Last time loader was used
|
||||
Enabled bool `json:"enabled"` // Loader entry is active
|
||||
ExpectEnv string `json:"expectenv"` // Expected environment
|
||||
}
|
||||
|
||||
func (le *LoaderEntry) Validate() (err error) {
|
||||
|
|
|
@ -99,6 +99,8 @@ func main() {
|
|||
authenticate(getLoader, true)).Methods("GET")
|
||||
s.HandleFunc("/loader/status/",
|
||||
authenticate(statusLoader, true)).Methods("POST")
|
||||
s.HandleFunc("/loader/expect/",
|
||||
authenticate(expectLoader, true)).Methods("POST")
|
||||
s.HandleFunc("/loader/key/",
|
||||
authenticate(keyLoader, true)).Methods("POST")
|
||||
s.HandleFunc("/loader/new/",
|
||||
|
|
|
@ -28,6 +28,14 @@ func locateManifestFromLoader(loaderid float64, agt mig.Agent) (mr mig.ManifestR
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Confirm the submitted environment matches any expected environment set on the
|
||||
// loader entry. This check is intended to prevent a malicious loader process from
|
||||
// submitting a forged environment to obtain a different manifest.
|
||||
err = ctx.DB.CompareLoaderExpectEnv(loaderid)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// If the check was successful, determine which manifest to send
|
||||
manifestid, err := ctx.DB.ManifestIDFromLoaderID(loaderid)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -473,6 +481,40 @@ func getLoader(respWriter http.ResponseWriter, request *http.Request) {
|
|||
respond(http.StatusOK, resource, respWriter, request)
|
||||
}
|
||||
|
||||
// Update expect values on a loader entry
|
||||
func expectLoader(respWriter http.ResponseWriter, request *http.Request) {
|
||||
loc := fmt.Sprintf("%s%s", ctx.Server.Host, request.URL.String())
|
||||
opid := getOpID(request)
|
||||
resource := cljs.New(loc)
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("%v", e)}.Err()
|
||||
resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: fmt.Sprintf("%v", e)})
|
||||
respond(http.StatusInternalServerError, resource, respWriter, request)
|
||||
}
|
||||
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "leaving expectLoader()"}.Debug()
|
||||
}()
|
||||
|
||||
err := request.ParseForm()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("Received loader expect change request")}.Debug()
|
||||
|
||||
loaderid, err := strconv.ParseFloat(request.FormValue("loaderid"), 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
eval := request.FormValue("expectenv")
|
||||
err = ctx.DB.LoaderUpdateExpect(loaderid, eval)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
respond(http.StatusOK, resource, respWriter, request)
|
||||
}
|
||||
|
||||
// Enable or disable a loader entry
|
||||
func statusLoader(respWriter http.ResponseWriter, request *http.Request) {
|
||||
loc := fmt.Sprintf("%s%s", ctx.Server.Host, request.URL.String())
|
||||
|
|
Загрузка…
Ссылка в новой задаче