Merge pull request #256 from ameihm0912/loaderexpect

[medium] add environment validation to loader authorization
This commit is contained in:
Aaron Meihm 2016-08-15 15:15:00 -05:00 коммит произвёл GitHub
Родитель de3ff01f04 f0e40ea3f8
Коммит 9e7799c679
8 изменённых файлов: 206 добавлений и 17 удалений

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

@ -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())