|
- Fleet Documentation
+ Fleet Documentation
|
diff --git a/server/mock/datastore.go b/server/mock/datastore.go
index 8bf9612e6..3ca0cc33b 100644
--- a/server/mock/datastore.go
+++ b/server/mock/datastore.go
@@ -34,6 +34,7 @@ type Store struct {
UserStore
QueryStore
QueryResultStore
+ CarveStore
}
func (m *Store) Drop() error {
diff --git a/server/mock/datastore_carves.go b/server/mock/datastore_carves.go
new file mode 100644
index 000000000..8c3ddb97a
--- /dev/null
+++ b/server/mock/datastore_carves.go
@@ -0,0 +1,94 @@
+// Automatically generated by mockimpl. DO NOT EDIT!
+
+package mock
+
+import(
+ "time"
+
+ "github.com/kolide/fleet/server/kolide"
+)
+
+var _ kolide.CarveStore = (*CarveStore)(nil)
+
+type NewCarveFunc func(c *kolide.CarveMetadata) (*kolide.CarveMetadata, error)
+
+type CarveFunc func(carveId int64) (*kolide.CarveMetadata, error)
+
+type ListCarvesFunc func(opt kolide.CarveListOptions) ([]*kolide.CarveMetadata, error)
+
+type CarveBySessionIdFunc func(sessionId string) (*kolide.CarveMetadata, error)
+
+type CarveByNameFunc func(name string) (*kolide.CarveMetadata, error)
+
+type NewBlockFunc func(metadataId int64, blockId int64, data []byte) error
+
+type GetBlockFunc func(metadataId int64, blockId int64) ([]byte, error)
+
+type CleanupCarvesFunc func(now time.Time) (expired int, err error)
+
+type CarveStore struct {
+ NewCarveFunc NewCarveFunc
+ NewCarveFuncInvoked bool
+
+ CarveFunc CarveFunc
+ CarveFuncInvoked bool
+
+ ListCarvesFunc ListCarvesFunc
+ ListCarvesFuncInvoked bool
+
+ CarveBySessionIdFunc CarveBySessionIdFunc
+ CarveBySessionIdFuncInvoked bool
+
+ CarveByNameFunc CarveByNameFunc
+ CarveByNameFuncInvoked bool
+
+ NewBlockFunc NewBlockFunc
+ NewBlockFuncInvoked bool
+
+ GetBlockFunc GetBlockFunc
+ GetBlockFuncInvoked bool
+
+ CleanupCarvesFunc CleanupCarvesFunc
+ CleanupCarvesFuncInvoked bool
+}
+
+func (s *CarveStore) NewCarve(c *kolide.CarveMetadata) (*kolide.CarveMetadata, error) {
+ s.NewCarveFuncInvoked = true
+ return s.NewCarveFunc(c)
+}
+
+func (s *CarveStore) Carve(carveId int64) (*kolide.CarveMetadata, error){
+ s.CarveFuncInvoked = true
+ return s.CarveFunc(carveId)
+}
+
+func (s *CarveStore) ListCarves(opt kolide.CarveListOptions) ([]*kolide.CarveMetadata, error) {
+ s.ListCarvesFuncInvoked = true
+ return s.ListCarvesFunc(opt)
+}
+
+func (s *CarveStore) CarveBySessionId(sessionId string) (*kolide.CarveMetadata, error) {
+ s.CarveBySessionIdFuncInvoked = true
+ return s.CarveBySessionIdFunc(sessionId)
+}
+
+func (s *CarveStore) CarveByName(name string) (*kolide.CarveMetadata, error) {
+ s.CarveByNameFuncInvoked = true
+ return s.CarveByNameFunc(name)
+}
+
+
+func (s *CarveStore) NewBlock(metadataId int64, blockId int64, data []byte) error {
+ s.NewBlockFuncInvoked = true
+ return s.NewBlockFunc(metadataId, blockId, data)
+}
+
+func (s *CarveStore) GetBlock(metadataId int64, blockId int64) ([]byte, error) {
+ s.GetBlockFuncInvoked = true
+ return s.GetBlockFunc(metadataId, blockId)
+}
+
+func (s *CarveStore) CleanupCarves(now time.Time) (expired int, err error) {
+ s.CleanupCarvesFuncInvoked = true
+ return s.CleanupCarvesFunc(now)
+}
diff --git a/server/service/client.go b/server/service/client.go
index b82b0e9a7..8b91142b9 100644
--- a/server/service/client.go
+++ b/server/service/client.go
@@ -14,12 +14,17 @@ import (
"github.com/pkg/errors"
)
+// httpClient interface allows the HTTP methods to be mocked.
+type httpClient interface {
+ Do(req *http.Request) (*http.Response, error)
+}
+
type Client struct {
addr string
baseURL *url.URL
urlPrefix string
token string
- http *http.Client
+ http httpClient
insecureSkipVerify bool
}
@@ -72,7 +77,7 @@ func NewClient(addr string, insecureSkipVerify bool, rootCA, urlPrefix string) (
}, nil
}
-func (c *Client) doWithHeaders(verb, path string, params interface{}, headers map[string]string) (*http.Response, error) {
+func (c *Client) doWithHeaders(verb, path, rawQuery string, params interface{}, headers map[string]string) (*http.Response, error) {
var bodyBytes []byte
var err error
if params != nil {
@@ -84,7 +89,7 @@ func (c *Client) doWithHeaders(verb, path string, params interface{}, headers ma
request, err := http.NewRequest(
verb,
- c.url(path).String(),
+ c.url(path, rawQuery).String(),
bytes.NewBuffer(bodyBytes),
)
if err != nil {
@@ -97,16 +102,16 @@ func (c *Client) doWithHeaders(verb, path string, params interface{}, headers ma
return c.http.Do(request)
}
-func (c *Client) Do(verb, path string, params interface{}) (*http.Response, error) {
+func (c *Client) Do(verb, path, rawQuery string, params interface{}) (*http.Response, error) {
headers := map[string]string{
"Content-type": "application/json",
"Accept": "application/json",
}
- return c.doWithHeaders(verb, path, params, headers)
+ return c.doWithHeaders(verb, path, rawQuery, params, headers)
}
-func (c *Client) AuthenticatedDo(verb, path string, params interface{}) (*http.Response, error) {
+func (c *Client) AuthenticatedDo(verb, path, rawQuery string, params interface{}) (*http.Response, error) {
if c.token == "" {
return nil, errors.New("authentication token is empty")
}
@@ -117,15 +122,16 @@ func (c *Client) AuthenticatedDo(verb, path string, params interface{}) (*http.R
"Authorization": fmt.Sprintf("Bearer %s", c.token),
}
- return c.doWithHeaders(verb, path, params, headers)
+ return c.doWithHeaders(verb, path, rawQuery, params, headers)
}
func (c *Client) SetToken(t string) {
c.token = t
}
-func (c *Client) url(path string) *url.URL {
+func (c *Client) url(path, rawQuery string) *url.URL {
u := *c.baseURL
u.Path = c.urlPrefix + path
+ u.RawQuery = rawQuery
return &u
}
diff --git a/server/service/client_appconfig.go b/server/service/client_appconfig.go
index 858910c45..1365849b3 100644
--- a/server/service/client_appconfig.go
+++ b/server/service/client_appconfig.go
@@ -10,7 +10,7 @@ import (
// ApplyAppConfig sends the application config to be applied to the Fleet instance.
func (c *Client) ApplyAppConfig(payload *kolide.AppConfigPayload) error {
- response, err := c.AuthenticatedDo("PATCH", "/api/v1/kolide/config", payload)
+ response, err := c.AuthenticatedDo("PATCH", "/api/v1/kolide/config", "", payload)
if err != nil {
return errors.Wrap(err, "PATCH /api/v1/kolide/config")
}
@@ -38,7 +38,7 @@ func (c *Client) ApplyAppConfig(payload *kolide.AppConfigPayload) error {
// GetAppConfig fetches the application config from the server API
func (c *Client) GetAppConfig() (*kolide.AppConfigPayload, error) {
- response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/config", nil)
+ response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/config", "", nil)
if err != nil {
return nil, errors.Wrap(err, "GET /api/v1/kolide/config")
}
@@ -72,7 +72,7 @@ func (c *Client) GetServerSettings() (*kolide.ServerSettings, error) {
// GetEnrollSecretSpec fetches the enroll secrets stored on the server
func (c *Client) GetEnrollSecretSpec() (*kolide.EnrollSecretSpec, error) {
- response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/spec/enroll_secret", nil)
+ response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/spec/enroll_secret", "", nil)
if err != nil {
return nil, errors.Wrap(err, "GET /api/v1/kolide/spec/enroll_secret")
}
@@ -102,7 +102,7 @@ func (c *Client) GetEnrollSecretSpec() (*kolide.EnrollSecretSpec, error) {
// ApplyEnrollSecretSpec applies the enroll secrets.
func (c *Client) ApplyEnrollSecretSpec(spec *kolide.EnrollSecretSpec) error {
req := applyEnrollSecretSpecRequest{Spec: spec}
- response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/spec/enroll_secret", req)
+ response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/spec/enroll_secret", "", req)
if err != nil {
return errors.Wrap(err, "POST /api/v1/kolide/spec/enroll_secret")
}
diff --git a/server/service/client_carves.go b/server/service/client_carves.go
new file mode 100644
index 000000000..34fc98f9b
--- /dev/null
+++ b/server/service/client_carves.go
@@ -0,0 +1,192 @@
+package service
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+
+ "github.com/kolide/fleet/server/kolide"
+ "github.com/pkg/errors"
+)
+
+// ListCarves lists the file carving sessions
+func (c *Client) ListCarves(opt kolide.CarveListOptions) ([]*kolide.CarveMetadata, error) {
+ endpoint := "/api/v1/kolide/carves"
+ rawQuery := ""
+ if opt.Expired {
+ rawQuery = "expired=1"
+ }
+ response, err := c.AuthenticatedDo("GET", endpoint, rawQuery, nil)
+ if err != nil {
+ return nil, errors.Wrap(err, "GET /api/v1/kolide/carves")
+ }
+ defer response.Body.Close()
+
+ if response.StatusCode != http.StatusOK {
+ return nil, errors.Errorf(
+ "list carves received status %d %s",
+ response.StatusCode,
+ extractServerErrorText(response.Body),
+ )
+ }
+
+ var responseBody listCarvesResponse
+ err = json.NewDecoder(response.Body).Decode(&responseBody)
+ if err != nil {
+ return nil, errors.Wrap(err, "decode get carves response")
+ }
+ if responseBody.Err != nil {
+ return nil, errors.Errorf("get carves: %s", responseBody.Err)
+ }
+
+ carves := []*kolide.CarveMetadata{}
+ for _, carve := range responseBody.Carves {
+ c := carve
+ carves = append(carves, &c)
+ }
+
+ return carves, nil
+}
+
+func (c *Client) GetCarve(carveId int64) (*kolide.CarveMetadata, error) {
+ endpoint := fmt.Sprintf("/api/v1/kolide/carves/%d", carveId)
+ response, err := c.AuthenticatedDo("GET", endpoint, "", nil)
+ if err != nil {
+ return nil, errors.Wrap(err, "GET "+endpoint)
+ }
+ defer response.Body.Close()
+
+ if response.StatusCode != http.StatusOK {
+ return nil, errors.Errorf(
+ "get carve received status %d %s",
+ response.StatusCode,
+ extractServerErrorText(response.Body),
+ )
+ }
+ var responseBody getCarveResponse
+ err = json.NewDecoder(response.Body).Decode(&responseBody)
+ if err != nil {
+ return nil, errors.Wrap(err, "decode carve response")
+ }
+ if responseBody.Err != nil {
+ return nil, errors.Errorf("get carve: %s", responseBody.Err)
+ }
+
+ return &responseBody.Carve, nil
+}
+
+
+func (c *Client) getCarveBlock(carveId, blockId int64) ([]byte, error) {
+ path := fmt.Sprintf(
+ "/api/v1/kolide/carves/%d/block/%d",
+ carveId,
+ blockId,
+ )
+ response, err := c.AuthenticatedDo("GET", path, "", nil)
+ if err != nil {
+ return nil, errors.Wrapf(err, "GET %s", path)
+ }
+ defer response.Body.Close()
+
+ if response.StatusCode != http.StatusOK {
+ return nil, errors.Errorf(
+ "get carve block received status %d: %s",
+ response.StatusCode,
+ extractServerErrorText(response.Body),
+ )
+ }
+
+ var responseBody getCarveBlockResponse
+ err = json.NewDecoder(response.Body).Decode(&responseBody)
+ if err != nil {
+ return nil, errors.Wrap(err, "decode get carve block response")
+ }
+ if responseBody.Err != nil {
+ return nil, errors.Errorf("get carve block: %s", responseBody.Err)
+ }
+
+ return responseBody.Data, nil
+}
+
+type carveReader struct {
+ carve kolide.CarveMetadata
+ bytesRead int64
+ curBlock int64
+ buffer []byte
+ client *Client
+}
+
+func newCarveReader(carve kolide.CarveMetadata, client *Client) *carveReader {
+ return &carveReader{
+ carve: carve,
+ client: client,
+ bytesRead: 0,
+ curBlock: 0,
+ }
+}
+
+func (r *carveReader) Read(p []byte) (n int, err error) {
+ if len(p) == 0 {
+ return 0, nil
+ }
+
+ if r.bytesRead >= r.carve.CarveSize {
+ return 0, io.EOF
+ }
+
+ // Load data from API if necessary
+ if len(r.buffer) == 0 {
+ var err error
+ r.buffer, err = r.client.getCarveBlock(r.carve.ID, r.curBlock)
+ if err != nil {
+ return 0, errors.Wrapf(err, "get block %d", r.curBlock)
+ }
+ r.curBlock++
+ }
+
+ // Calculate length we can copy
+ copyLen := len(p)
+ if copyLen > len(r.buffer) {
+ copyLen = len(r.buffer)
+ }
+
+ // Perform copy and clear copied contents from buffer
+ copy(p, r.buffer[:copyLen])
+ r.buffer = r.buffer[copyLen:]
+
+ r.bytesRead += int64(copyLen)
+
+ return copyLen, nil
+}
+
+// ListCarves lists the file carving sessio
+func (c *Client) DownloadCarve(id int64) (io.Reader, error) {
+ path := fmt.Sprintf("/api/v1/kolide/carves/%d", id)
+ response, err := c.AuthenticatedDo("GET", path, "", nil)
+ if err != nil {
+ return nil, errors.Wrapf(err, "GET %s", path)
+ }
+ defer response.Body.Close()
+
+ if response.StatusCode != http.StatusOK {
+ return nil, errors.Errorf(
+ "download carve received status %d: %s",
+ response.StatusCode,
+ extractServerErrorText(response.Body),
+ )
+ }
+
+ var responseBody getCarveResponse
+ err = json.NewDecoder(response.Body).Decode(&responseBody)
+ if err != nil {
+ return nil, errors.Wrap(err, "decode get carve by name response")
+ }
+ if responseBody.Err != nil {
+ return nil, errors.Errorf("get carve by name: %s", responseBody.Err)
+ }
+
+ reader := newCarveReader(responseBody.Carve, c)
+
+ return reader, nil
+}
diff --git a/server/service/client_hosts.go b/server/service/client_hosts.go
index 1ae5a239e..450550077 100644
--- a/server/service/client_hosts.go
+++ b/server/service/client_hosts.go
@@ -10,7 +10,7 @@ import (
// GetHosts retrieves the list of all Hosts
func (c *Client) GetHosts() ([]HostResponse, error) {
- response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/hosts", nil)
+ response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/hosts", "", nil)
if err != nil {
return nil, errors.Wrap(err, "GET /api/v1/kolide/hosts")
}
@@ -38,7 +38,7 @@ func (c *Client) GetHosts() ([]HostResponse, error) {
// HostByIdentifier retrieves a host by the uuid, osquery_host_id, hostname, or
// node_key.
func (c *Client) HostByIdentifier(identifier string) (*HostResponse, error) {
- response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/hosts/identifier/"+identifier, nil)
+ response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/hosts/identifier/"+identifier, "", nil)
if err != nil {
return nil, errors.Wrap(err, "GET /api/v1/kolide/hosts/identifier")
}
@@ -67,7 +67,7 @@ func (c *Client) HostByIdentifier(identifier string) (*HostResponse, error) {
func (c *Client) DeleteHost(id uint) error {
verb := "DELETE"
path := fmt.Sprintf("/api/v1/kolide/hosts/%d", id)
- response, err := c.AuthenticatedDo(verb, path, nil)
+ response, err := c.AuthenticatedDo(verb, path, "", nil)
if err != nil {
return errors.Wrapf(err, "%s %s", verb, path)
}
diff --git a/server/service/client_labels.go b/server/service/client_labels.go
index 3df60610c..a87ef805d 100644
--- a/server/service/client_labels.go
+++ b/server/service/client_labels.go
@@ -13,7 +13,7 @@ import (
// Fleet instance.
func (c *Client) ApplyLabels(specs []*kolide.LabelSpec) error {
req := applyLabelSpecsRequest{Specs: specs}
- response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/spec/labels", req)
+ response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/spec/labels", "", req)
if err != nil {
return errors.Wrap(err, "POST /api/v1/kolide/spec/labels")
}
@@ -43,7 +43,7 @@ func (c *Client) ApplyLabels(specs []*kolide.LabelSpec) error {
// GetLabel retrieves information about a label by name
func (c *Client) GetLabel(name string) (*kolide.LabelSpec, error) {
verb, path := "GET", "/api/v1/kolide/spec/labels/"+url.PathEscape(name)
- response, err := c.AuthenticatedDo(verb, path, nil)
+ response, err := c.AuthenticatedDo(verb, path, "", nil)
if err != nil {
return nil, errors.Wrap(err, "GET /api/v1/kolide/spec/labels")
}
@@ -76,7 +76,7 @@ func (c *Client) GetLabel(name string) (*kolide.LabelSpec, error) {
// GetLabels retrieves the list of all Labels.
func (c *Client) GetLabels() ([]*kolide.LabelSpec, error) {
- response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/spec/labels", nil)
+ response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/spec/labels", "", nil)
if err != nil {
return nil, errors.Wrap(err, "GET /api/v1/kolide/spec/labels")
}
@@ -106,7 +106,7 @@ func (c *Client) GetLabels() ([]*kolide.LabelSpec, error) {
// DeleteLabel deletes the label with the matching name.
func (c *Client) DeleteLabel(name string) error {
verb, path := "DELETE", "/api/v1/kolide/labels/"+url.PathEscape(name)
- response, err := c.AuthenticatedDo(verb, path, nil)
+ response, err := c.AuthenticatedDo(verb, path, "", nil)
if err != nil {
return errors.Wrapf(err, "%s %s", verb, path)
}
diff --git a/server/service/client_live_query.go b/server/service/client_live_query.go
index 854195a34..9096e0fce 100644
--- a/server/service/client_live_query.go
+++ b/server/service/client_live_query.go
@@ -64,7 +64,7 @@ func (c *Client) LiveQuery(query string, labels []string, hosts []string) (*Live
Query: query,
Selected: distributedQueryCampaignTargetsByNames{Labels: labels, Hosts: hosts},
}
- response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/queries/run_by_names", req)
+ response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/queries/run_by_names", "", req)
if err != nil {
return nil, errors.Wrap(err, "POST /api/v1/kolide/queries/run_by_names")
}
diff --git a/server/service/client_options.go b/server/service/client_options.go
index ab561a471..3a0e20171 100644
--- a/server/service/client_options.go
+++ b/server/service/client_options.go
@@ -11,7 +11,7 @@ import (
// ApplyOptions sends the osquery options to be applied to the Fleet instance.
func (c *Client) ApplyOptions(spec *kolide.OptionsSpec) error {
req := applyOsqueryOptionsSpecRequest{Spec: spec}
- response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/spec/osquery_options", req)
+ response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/spec/osquery_options", "", req)
if err != nil {
return errors.Wrap(err, "POST /api/v1/kolide/spec/osquery_options")
}
@@ -41,7 +41,7 @@ func (c *Client) ApplyOptions(spec *kolide.OptionsSpec) error {
// GetOptions retrieves the configured osquery options.
func (c *Client) GetOptions() (*kolide.OptionsSpec, error) {
verb, path := "GET", "/api/v1/kolide/spec/osquery_options"
- response, err := c.AuthenticatedDo(verb, path, nil)
+ response, err := c.AuthenticatedDo(verb, path, "", nil)
if err != nil {
return nil, errors.Wrap(err, verb+" "+path)
}
diff --git a/server/service/client_packs.go b/server/service/client_packs.go
index f8bc253bb..8fa2268d8 100644
--- a/server/service/client_packs.go
+++ b/server/service/client_packs.go
@@ -13,7 +13,7 @@ import (
// Fleet instance.
func (c *Client) ApplyPacks(specs []*kolide.PackSpec) error {
req := applyPackSpecsRequest{Specs: specs}
- response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/spec/packs", req)
+ response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/spec/packs", "", req)
if err != nil {
return errors.Wrap(err, "POST /api/v1/kolide/spec/packs")
}
@@ -43,7 +43,7 @@ func (c *Client) ApplyPacks(specs []*kolide.PackSpec) error {
// GetPack retrieves information about a pack
func (c *Client) GetPack(name string) (*kolide.PackSpec, error) {
verb, path := "GET", "/api/v1/kolide/spec/packs/"+url.PathEscape(name)
- response, err := c.AuthenticatedDo(verb, path, nil)
+ response, err := c.AuthenticatedDo(verb, path, "", nil)
if err != nil {
return nil, errors.Wrap(err, "GET /api/v1/kolide/spec/packs")
}
@@ -76,7 +76,7 @@ func (c *Client) GetPack(name string) (*kolide.PackSpec, error) {
// GetPacks retrieves the list of all Packs.
func (c *Client) GetPacks() ([]*kolide.PackSpec, error) {
- response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/spec/packs", nil)
+ response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/spec/packs", "", nil)
if err != nil {
return nil, errors.Wrap(err, "GET /api/v1/kolide/spec/packs")
}
@@ -106,7 +106,7 @@ func (c *Client) GetPacks() ([]*kolide.PackSpec, error) {
// DeletePack deletes the pack with the matching name.
func (c *Client) DeletePack(name string) error {
verb, path := "DELETE", "/api/v1/kolide/packs/"+url.PathEscape(name)
- response, err := c.AuthenticatedDo(verb, path, nil)
+ response, err := c.AuthenticatedDo(verb, path, "", nil)
if err != nil {
return errors.Wrapf(err, "%s %s", verb, path)
}
diff --git a/server/service/client_queries.go b/server/service/client_queries.go
index 721f56b5c..2ab258d13 100644
--- a/server/service/client_queries.go
+++ b/server/service/client_queries.go
@@ -13,7 +13,7 @@ import (
// Fleet instance.
func (c *Client) ApplyQueries(specs []*kolide.QuerySpec) error {
req := applyQuerySpecsRequest{Specs: specs}
- response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/spec/queries", req)
+ response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/spec/queries", "", req)
if err != nil {
return errors.Wrap(err, "POST /api/v1/kolide/spec/queries")
}
@@ -43,7 +43,7 @@ func (c *Client) ApplyQueries(specs []*kolide.QuerySpec) error {
// GetQuery retrieves the list of all Queries.
func (c *Client) GetQuery(name string) (*kolide.QuerySpec, error) {
verb, path := "GET", "/api/v1/kolide/spec/queries/"+url.PathEscape(name)
- response, err := c.AuthenticatedDo(verb, path, nil)
+ response, err := c.AuthenticatedDo(verb, path, "", nil)
if err != nil {
return nil, errors.Wrapf(err, "%s %s", verb, path)
}
@@ -76,7 +76,7 @@ func (c *Client) GetQuery(name string) (*kolide.QuerySpec, error) {
// GetQueries retrieves the list of all Queries.
func (c *Client) GetQueries() ([]*kolide.QuerySpec, error) {
- response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/spec/queries", nil)
+ response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/spec/queries", "", nil)
if err != nil {
return nil, errors.Wrap(err, "GET /api/v1/kolide/spec/queries")
}
@@ -106,7 +106,7 @@ func (c *Client) GetQueries() ([]*kolide.QuerySpec, error) {
// DeleteQuery deletes the query with the matching name.
func (c *Client) DeleteQuery(name string) error {
verb, path := "DELETE", "/api/v1/kolide/queries/"+url.PathEscape(name)
- response, err := c.AuthenticatedDo(verb, path, nil)
+ response, err := c.AuthenticatedDo(verb, path, "", nil)
if err != nil {
return errors.Wrapf(err, "%s %s", verb, path)
}
diff --git a/server/service/client_sessions.go b/server/service/client_sessions.go
index d4b52d64a..a3c041fb6 100644
--- a/server/service/client_sessions.go
+++ b/server/service/client_sessions.go
@@ -15,7 +15,7 @@ func (c *Client) Login(email, password string) (string, error) {
Password: password,
}
- response, err := c.Do("POST", "/api/v1/kolide/login", params)
+ response, err := c.Do("POST", "/api/v1/kolide/login", "", params)
if err != nil {
return "", errors.Wrap(err, "POST /api/v1/kolide/login")
}
@@ -50,7 +50,7 @@ func (c *Client) Login(email, password string) (string, error) {
// Logout attempts to logout to the current Fleet instance.
func (c *Client) Logout() error {
- response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/logout", nil)
+ response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/logout", "", nil)
if err != nil {
return errors.Wrap(err, "POST /api/v1/kolide/logout")
}
diff --git a/server/service/client_setup.go b/server/service/client_setup.go
index 816bead91..e9ad42a9f 100644
--- a/server/service/client_setup.go
+++ b/server/service/client_setup.go
@@ -25,7 +25,7 @@ func (c *Client) Setup(email, username, password, org string) (string, error) {
KolideServerURL: &c.addr,
}
- response, err := c.Do("POST", "/api/v1/setup", params)
+ response, err := c.Do("POST", "/api/v1/setup", "", params)
if err != nil {
return "", errors.Wrap(err, "POST /api/v1/setup")
}
diff --git a/server/service/client_targets.go b/server/service/client_targets.go
index 90227fdda..b80de14a1 100644
--- a/server/service/client_targets.go
+++ b/server/service/client_targets.go
@@ -21,7 +21,7 @@ func (c *Client) SearchTargets(query string, selectedHostIDs, selectedLabelIDs [
},
}
- response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/targets", req)
+ response, err := c.AuthenticatedDo("POST", "/api/v1/kolide/targets", "", req)
if err != nil {
return nil, errors.Wrap(err, "POST /api/v1/kolide/targets")
}
diff --git a/server/service/client_users.go b/server/service/client_users.go
new file mode 100644
index 000000000..7c3b18d40
--- /dev/null
+++ b/server/service/client_users.go
@@ -0,0 +1,39 @@
+package service
+
+import (
+ "encoding/json"
+ "net/http"
+
+ "github.com/kolide/fleet/server/kolide"
+ "github.com/pkg/errors"
+)
+
+// CreateUser creates a new user, skipping the invitation process.
+func (c *Client) CreateUser(p kolide.UserPayload) error {
+ verb, path := "POST", "/api/v1/kolide/users/admin"
+ response, err := c.AuthenticatedDo(verb, path, "", p)
+ if err != nil {
+ return errors.Wrapf(err, "%s %s", verb, path)
+ }
+ defer response.Body.Close()
+
+ if response.StatusCode != http.StatusOK {
+ return errors.Errorf(
+ "create user received status %d %s",
+ response.StatusCode,
+ extractServerErrorText(response.Body),
+ )
+ }
+
+ var responseBody createUserResponse
+ err = json.NewDecoder(response.Body).Decode(&responseBody)
+ if err != nil {
+ return errors.Wrap(err, "decode create user response")
+ }
+
+ if responseBody.Err != nil {
+ return errors.Errorf("create user: %s", responseBody.Err)
+ }
+
+ return nil
+}
diff --git a/server/service/endpoint_carves.go b/server/service/endpoint_carves.go
new file mode 100644
index 000000000..a3d92178d
--- /dev/null
+++ b/server/service/endpoint_carves.go
@@ -0,0 +1,177 @@
+package service
+
+import (
+ "context"
+
+ "github.com/go-kit/kit/endpoint"
+ "github.com/kolide/fleet/server/kolide"
+)
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin File Carve
+////////////////////////////////////////////////////////////////////////////////
+
+type carveBeginRequest struct {
+ NodeKey string `json:"node_key"`
+ BlockCount int64 `json:"block_count"`
+ BlockSize int64 `json:"block_size"`
+ CarveSize int64 `json:"carve_size"`
+ CarveId string `json:"carve_id"`
+ RequestId string `json:"request_id"`
+}
+
+type carveBeginResponse struct {
+ SessionId string `json:"session_id"`
+ Success bool `json:"success,omitempty"`
+ Err error `json:"error,omitempty"`
+}
+
+func (r carveBeginResponse) error() error { return r.Err }
+
+func makeCarveBeginEndpoint(svc kolide.Service) endpoint.Endpoint {
+ return func(ctx context.Context, request interface{}) (interface{}, error) {
+ req := request.(carveBeginRequest)
+
+ payload := kolide.CarveBeginPayload{
+ BlockCount: req.BlockCount,
+ BlockSize: req.BlockSize,
+ CarveSize: req.CarveSize,
+ CarveId: req.CarveId,
+ RequestId: req.RequestId,
+ }
+
+ carve, err := svc.CarveBegin(ctx, payload)
+ if err != nil {
+ return carveBeginResponse{Err: err}, nil
+ }
+
+ return carveBeginResponse{SessionId: carve.SessionId, Success: true}, nil
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Receive Block for File Carve
+////////////////////////////////////////////////////////////////////////////////
+
+type carveBlockRequest struct {
+ NodeKey string `json:"node_key"`
+ BlockId int64 `json:"block_id"`
+ SessionId string `json:"session_id"`
+ RequestId string `json:"request_id"`
+ Data []byte `json:"data"`
+}
+
+type carveBlockResponse struct {
+ Success bool `json:"success,omitempty"`
+ Err error `json:"error,omitempty"`
+}
+
+func (r carveBlockResponse) error() error { return r.Err }
+
+func makeCarveBlockEndpoint(svc kolide.Service) endpoint.Endpoint {
+ return func(ctx context.Context, request interface{}) (interface{}, error) {
+ req := request.(carveBlockRequest)
+
+ payload := kolide.CarveBlockPayload{
+ SessionId: req.SessionId,
+ RequestId: req.RequestId,
+ BlockId: req.BlockId,
+ Data: req.Data,
+ }
+
+ err := svc.CarveBlock(ctx, payload)
+ if err != nil {
+ return carveBlockResponse{Err: err}, nil
+ }
+
+
+ return carveBlockResponse{Success: true}, nil
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Get Carve
+////////////////////////////////////////////////////////////////////////////////
+
+type getCarveRequest struct {
+ ID int64
+}
+
+type getCarveResponse struct {
+ Carve kolide.CarveMetadata `json:"carve"`
+ Err error `json:"error,omitempty"`
+}
+
+func (r getCarveResponse) error() error { return r.Err }
+
+func makeGetCarveEndpoint(svc kolide.Service) endpoint.Endpoint {
+ return func(ctx context.Context, request interface{}) (interface{}, error) {
+ req := request.(getCarveRequest)
+ carve, err := svc.GetCarve(ctx, req.ID)
+ if err != nil {
+ return getCarveResponse{Err: err}, nil
+ }
+
+ return getCarveResponse{Carve:*carve}, nil
+
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// List Carves
+////////////////////////////////////////////////////////////////////////////////
+
+type listCarvesRequest struct {
+ ListOptions kolide.CarveListOptions
+}
+
+type listCarvesResponse struct {
+ Carves []kolide.CarveMetadata `json:"carves"`
+ Err error `json:"error,omitempty"`
+}
+
+func (r listCarvesResponse) error() error { return r.Err }
+
+func makeListCarvesEndpoint(svc kolide.Service) endpoint.Endpoint {
+ return func(ctx context.Context, request interface{}) (interface{}, error) {
+ req := request.(listCarvesRequest)
+ carves, err := svc.ListCarves(ctx, req.ListOptions)
+ if err != nil {
+ return listCarvesResponse{Err: err}, nil
+ }
+
+ resp := listCarvesResponse{}
+ for _, carve := range carves {
+ resp.Carves = append(resp.Carves, *carve)
+ }
+ return resp, nil
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Get Carve Block
+////////////////////////////////////////////////////////////////////////////////
+
+type getCarveBlockRequest struct {
+ ID int64
+ BlockId int64
+}
+
+type getCarveBlockResponse struct {
+ Data []byte `json:"data"`
+ Err error `json:"error,omitempty"`
+}
+
+func (r getCarveBlockResponse) error() error { return r.Err }
+
+func makeGetCarveBlockEndpoint(svc kolide.Service) endpoint.Endpoint {
+ return func(ctx context.Context, request interface{}) (interface{}, error) {
+ req := request.(getCarveBlockRequest)
+ data, err := svc.GetBlock(ctx, req.ID, req.BlockId)
+ if err != nil {
+ return getCarveBlockResponse{Err: err}, nil
+ }
+
+ return getCarveBlockResponse{Data: data}, nil
+ }
+}
diff --git a/server/service/endpoint_setup.go b/server/service/endpoint_setup.go
index 8bd0eabd7..bda7c48e7 100644
--- a/server/service/endpoint_setup.go
+++ b/server/service/endpoint_setup.go
@@ -57,7 +57,7 @@ func makeSetupEndpoint(svc kolide.Service) endpoint.Endpoint {
err := errors.Errorf("admin password cannot be empty")
return setupResponse{Err: err}, nil
}
- admin, err = svc.NewAdminCreatedUser(ctx, *req.Admin)
+ admin, err = svc.CreateUser(ctx, *req.Admin)
if err != nil {
return setupResponse{Err: err}, nil
}
diff --git a/server/service/endpoint_users.go b/server/service/endpoint_users.go
index 2544bcfa1..28126cbb2 100644
--- a/server/service/endpoint_users.go
+++ b/server/service/endpoint_users.go
@@ -9,7 +9,7 @@ import (
)
////////////////////////////////////////////////////////////////////////////////
-// Create User
+// Create User With Invite
////////////////////////////////////////////////////////////////////////////////
type createUserRequest struct {
@@ -23,10 +23,25 @@ type createUserResponse struct {
func (r createUserResponse) error() error { return r.Err }
+func makeCreateUserWithInviteEndpoint(svc kolide.Service) endpoint.Endpoint {
+ return func(ctx context.Context, request interface{}) (interface{}, error) {
+ req := request.(createUserRequest)
+ user, err := svc.CreateUserWithInvite(ctx, req.payload)
+ if err != nil {
+ return createUserResponse{Err: err}, nil
+ }
+ return createUserResponse{User: user}, nil
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Create User
+////////////////////////////////////////////////////////////////////////////////
+
func makeCreateUserEndpoint(svc kolide.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(createUserRequest)
- user, err := svc.NewUser(ctx, req.payload)
+ user, err := svc.CreateUser(ctx, req.payload)
if err != nil {
return createUserResponse{Err: err}, nil
}
diff --git a/server/service/handler.go b/server/service/handler.go
index ed8f71d7b..869459563 100644
--- a/server/service/handler.go
+++ b/server/service/handler.go
@@ -22,6 +22,7 @@ type KolideEndpoints struct {
ResetPassword endpoint.Endpoint
Me endpoint.Endpoint
ChangePassword endpoint.Endpoint
+ CreateUserWithInvite endpoint.Endpoint
CreateUser endpoint.Endpoint
GetUser endpoint.Endpoint
ListUsers endpoint.Endpoint
@@ -73,6 +74,8 @@ type KolideEndpoints struct {
GetDistributedQueries endpoint.Endpoint
SubmitDistributedQueryResults endpoint.Endpoint
SubmitLogs endpoint.Endpoint
+ CarveBegin endpoint.Endpoint
+ CarveBlock endpoint.Endpoint
CreateLabel endpoint.Endpoint
ModifyLabel endpoint.Endpoint
GetLabel endpoint.Endpoint
@@ -98,20 +101,23 @@ type KolideEndpoints struct {
SSOSettings endpoint.Endpoint
StatusResultStore endpoint.Endpoint
StatusLiveQuery endpoint.Endpoint
+ ListCarves endpoint.Endpoint
+ GetCarve endpoint.Endpoint
+ GetCarveBlock endpoint.Endpoint
}
// MakeKolideServerEndpoints creates the Kolide API endpoints.
func MakeKolideServerEndpoints(svc kolide.Service, jwtKey, urlPrefix string) KolideEndpoints {
return KolideEndpoints{
- Login: makeLoginEndpoint(svc),
- Logout: makeLogoutEndpoint(svc),
- ForgotPassword: makeForgotPasswordEndpoint(svc),
- ResetPassword: makeResetPasswordEndpoint(svc),
- CreateUser: makeCreateUserEndpoint(svc),
- VerifyInvite: makeVerifyInviteEndpoint(svc),
- InitiateSSO: makeInitiateSSOEndpoint(svc),
- CallbackSSO: makeCallbackSSOEndpoint(svc, urlPrefix),
- SSOSettings: makeSSOSettingsEndpoint(svc),
+ Login: makeLoginEndpoint(svc),
+ Logout: makeLogoutEndpoint(svc),
+ ForgotPassword: makeForgotPasswordEndpoint(svc),
+ ResetPassword: makeResetPasswordEndpoint(svc),
+ CreateUserWithInvite: makeCreateUserWithInviteEndpoint(svc),
+ VerifyInvite: makeVerifyInviteEndpoint(svc),
+ InitiateSSO: makeInitiateSSOEndpoint(svc),
+ CallbackSSO: makeCallbackSSOEndpoint(svc, urlPrefix),
+ SSOSettings: makeSSOSettingsEndpoint(svc),
// Authenticated user endpoints
// Each of these endpoints should have exactly one
@@ -128,6 +134,7 @@ func MakeKolideServerEndpoints(svc kolide.Service, jwtKey, urlPrefix string) Kol
AdminUser: authenticatedUser(jwtKey, svc, mustBeAdmin(makeAdminUserEndpoint(svc))),
EnableUser: authenticatedUser(jwtKey, svc, mustBeAdmin(makeEnableUserEndpoint(svc))),
RequirePasswordReset: authenticatedUser(jwtKey, svc, mustBeAdmin(makeRequirePasswordResetEndpoint(svc))),
+ CreateUser: authenticatedUser(jwtKey, svc, mustBeAdmin(makeCreateUserEndpoint(svc))),
// PerformRequiredPasswordReset needs only to authenticate the
// logged in user
PerformRequiredPasswordReset: authenticatedUser(jwtKey, svc, canPerformPasswordReset(makePerformRequiredPasswordResetEndpoint(svc))),
@@ -188,6 +195,9 @@ func MakeKolideServerEndpoints(svc kolide.Service, jwtKey, urlPrefix string) Kol
GetOsqueryOptionsSpec: authenticatedUser(jwtKey, svc, makeGetOsqueryOptionsSpecEndpoint(svc)),
GetCertificate: authenticatedUser(jwtKey, svc, makeCertificateEndpoint(svc)),
ChangeEmail: authenticatedUser(jwtKey, svc, makeChangeEmailEndpoint(svc)),
+ ListCarves: authenticatedUser(jwtKey, svc, makeListCarvesEndpoint(svc)),
+ GetCarve: authenticatedUser(jwtKey, svc, makeGetCarveEndpoint(svc)),
+ GetCarveBlock: authenticatedUser(jwtKey, svc, makeGetCarveBlockEndpoint(svc)),
// Authenticated status endpoints
StatusResultStore: authenticatedUser(jwtKey, svc, makeStatusResultStoreEndpoint(svc)),
@@ -199,6 +209,11 @@ func MakeKolideServerEndpoints(svc kolide.Service, jwtKey, urlPrefix string) Kol
GetDistributedQueries: authenticatedHost(svc, makeGetDistributedQueriesEndpoint(svc)),
SubmitDistributedQueryResults: authenticatedHost(svc, makeSubmitDistributedQueryResultsEndpoint(svc)),
SubmitLogs: authenticatedHost(svc, makeSubmitLogsEndpoint(svc)),
+ CarveBegin: authenticatedHost(svc, makeCarveBeginEndpoint(svc)),
+ // For some reason osquery does not provide a node key with the block
+ // data. Instead the carve session ID should be verified in the service
+ // method.
+ CarveBlock: makeCarveBlockEndpoint(svc),
}
}
@@ -209,6 +224,7 @@ type kolideHandlers struct {
ResetPassword http.Handler
Me http.Handler
ChangePassword http.Handler
+ CreateUserWithInvite http.Handler
CreateUser http.Handler
GetUser http.Handler
ListUsers http.Handler
@@ -260,6 +276,8 @@ type kolideHandlers struct {
GetDistributedQueries http.Handler
SubmitDistributedQueryResults http.Handler
SubmitLogs http.Handler
+ CarveBegin http.Handler
+ CarveBlock http.Handler
CreateLabel http.Handler
ModifyLabel http.Handler
GetLabel http.Handler
@@ -285,6 +303,9 @@ type kolideHandlers struct {
SettingsSSO http.Handler
StatusResultStore http.Handler
StatusLiveQuery http.Handler
+ ListCarves http.Handler
+ GetCarve http.Handler
+ GetCarveBlock http.Handler
}
func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *kolideHandlers {
@@ -298,6 +319,7 @@ func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *koli
ResetPassword: newServer(e.ResetPassword, decodeResetPasswordRequest),
Me: newServer(e.Me, decodeNoParamsRequest),
ChangePassword: newServer(e.ChangePassword, decodeChangePasswordRequest),
+ CreateUserWithInvite: newServer(e.CreateUserWithInvite, decodeCreateUserRequest),
CreateUser: newServer(e.CreateUser, decodeCreateUserRequest),
GetUser: newServer(e.GetUser, decodeGetUserRequest),
ListUsers: newServer(e.ListUsers, decodeListUsersRequest),
@@ -349,6 +371,8 @@ func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *koli
GetDistributedQueries: newServer(e.GetDistributedQueries, decodeGetDistributedQueriesRequest),
SubmitDistributedQueryResults: newServer(e.SubmitDistributedQueryResults, decodeSubmitDistributedQueryResultsRequest),
SubmitLogs: newServer(e.SubmitLogs, decodeSubmitLogsRequest),
+ CarveBegin: newServer(e.CarveBegin, decodeCarveBeginRequest),
+ CarveBlock: newServer(e.CarveBlock, decodeCarveBlockRequest),
CreateLabel: newServer(e.CreateLabel, decodeCreateLabelRequest),
ModifyLabel: newServer(e.ModifyLabel, decodeModifyLabelRequest),
GetLabel: newServer(e.GetLabel, decodeGetLabelRequest),
@@ -374,6 +398,9 @@ func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *koli
SettingsSSO: newServer(e.SSOSettings, decodeNoParamsRequest),
StatusResultStore: newServer(e.StatusResultStore, decodeNoParamsRequest),
StatusLiveQuery: newServer(e.StatusLiveQuery, decodeNoParamsRequest),
+ ListCarves: newServer(e.ListCarves, decodeListCarvesRequest),
+ GetCarve: newServer(e.GetCarve, decodeGetCarveRequest),
+ GetCarveBlock: newServer(e.GetCarveBlock, decodeGetCarveBlockRequest),
}
}
@@ -427,7 +454,8 @@ func attachKolideAPIRoutes(r *mux.Router, h *kolideHandlers) {
r.Handle("/api/v1/kolide/sso", h.SettingsSSO).Methods("GET").Name("sso_config")
r.Handle("/api/v1/kolide/sso/callback", h.CallbackSSO).Methods("POST").Name("callback_sso")
r.Handle("/api/v1/kolide/users", h.ListUsers).Methods("GET").Name("list_users")
- r.Handle("/api/v1/kolide/users", h.CreateUser).Methods("POST").Name("create_user")
+ r.Handle("/api/v1/kolide/users", h.CreateUserWithInvite).Methods("POST").Name("create_user_with_invite")
+ r.Handle("/api/v1/kolide/users/admin", h.CreateUser).Methods("POST").Name("create_user")
r.Handle("/api/v1/kolide/users/{id}", h.GetUser).Methods("GET").Name("get_user")
r.Handle("/api/v1/kolide/users/{id}", h.ModifyUser).Methods("PATCH").Name("modify_user")
r.Handle("/api/v1/kolide/users/{id}/enable", h.EnableUser).Methods("POST").Name("enable_user")
@@ -504,11 +532,17 @@ func attachKolideAPIRoutes(r *mux.Router, h *kolideHandlers) {
r.Handle("/api/v1/kolide/status/result_store", h.StatusResultStore).Methods("GET").Name("status_result_store")
r.Handle("/api/v1/kolide/status/live_query", h.StatusLiveQuery).Methods("GET").Name("status_live_query")
+ r.Handle("/api/v1/kolide/carves", h.ListCarves).Methods("GET").Name("list_carves")
+ r.Handle("/api/v1/kolide/carves/{id}", h.GetCarve).Methods("GET").Name("get_carve")
+ r.Handle("/api/v1/kolide/carves/{id}/block/{block_id}", h.GetCarveBlock).Methods("GET").Name("get_carve_block")
+
r.Handle("/api/v1/osquery/enroll", h.EnrollAgent).Methods("POST").Name("enroll_agent")
r.Handle("/api/v1/osquery/config", h.GetClientConfig).Methods("POST").Name("get_client_config")
r.Handle("/api/v1/osquery/distributed/read", h.GetDistributedQueries).Methods("POST").Name("get_distributed_queries")
r.Handle("/api/v1/osquery/distributed/write", h.SubmitDistributedQueryResults).Methods("POST").Name("submit_distributed_query_results")
r.Handle("/api/v1/osquery/log", h.SubmitLogs).Methods("POST").Name("submit_logs")
+ r.Handle("/api/v1/osquery/carve/begin", h.CarveBegin).Methods("POST").Name("carve_begin")
+ r.Handle("/api/v1/osquery/carve/block", h.CarveBlock).Methods("POST").Name("carve_block")
}
// WithSetup is an http middleware that checks is setup procedures have been completed.
diff --git a/server/service/logging_users.go b/server/service/logging_users.go
index e054e7fb8..5a6a4fad3 100644
--- a/server/service/logging_users.go
+++ b/server/service/logging_users.go
@@ -70,7 +70,7 @@ func (mw loggingMiddleware) ChangeUserEnabled(ctx context.Context, id uint, isEn
return user, err
}
-func (mw loggingMiddleware) NewAdminCreatedUser(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
+func (mw loggingMiddleware) CreateUser(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
var (
user *kolide.User
err error
@@ -85,7 +85,7 @@ func (mw loggingMiddleware) NewAdminCreatedUser(ctx context.Context, p kolide.Us
defer func(begin time.Time) {
_ = mw.loggerInfo(err).Log(
- "method", "NewAdminCreatedUser",
+ "method", "CreateUser",
"user", username,
"created_by", loggedInUser,
"err", err,
@@ -93,7 +93,7 @@ func (mw loggingMiddleware) NewAdminCreatedUser(ctx context.Context, p kolide.Us
)
}(time.Now())
- user, err = mw.Service.NewAdminCreatedUser(ctx, p)
+ user, err = mw.Service.CreateUser(ctx, p)
if user != nil {
username = user.Username
}
@@ -151,7 +151,7 @@ func (mw loggingMiddleware) RequirePasswordReset(ctx context.Context, uid uint,
}
-func (mw loggingMiddleware) NewUser(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
+func (mw loggingMiddleware) CreateUserWithInvite(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
var (
user *kolide.User
err error
@@ -166,7 +166,7 @@ func (mw loggingMiddleware) NewUser(ctx context.Context, p kolide.UserPayload) (
defer func(begin time.Time) {
_ = mw.loggerInfo(err).Log(
- "method", "NewUser",
+ "method", "CreateUserWithInvite",
"user", username,
"created_by", loggedInUser,
"err", err,
@@ -174,7 +174,7 @@ func (mw loggingMiddleware) NewUser(ctx context.Context, p kolide.UserPayload) (
)
}(time.Now())
- user, err = mw.Service.NewUser(ctx, p)
+ user, err = mw.Service.CreateUserWithInvite(ctx, p)
if user != nil {
username = user.Username
diff --git a/server/service/metrics_users.go b/server/service/metrics_users.go
index 440246b89..157349f75 100644
--- a/server/service/metrics_users.go
+++ b/server/service/metrics_users.go
@@ -40,19 +40,19 @@ func (mw metricsMiddleware) ChangeUserEnabled(ctx context.Context, id uint, isEn
return user, err
}
-func (mw metricsMiddleware) NewUser(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
+func (mw metricsMiddleware) CreateUserWithInvite(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
var (
user *kolide.User
err error
)
defer func(begin time.Time) {
- lvs := []string{"method", "NewUser", "error", fmt.Sprint(err != nil)}
+ lvs := []string{"method", "CreateUserWithInvite", "error", fmt.Sprint(err != nil)}
mw.requestCount.With(lvs...).Add(1)
mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
}(time.Now())
- user, err = mw.Service.NewUser(ctx, p)
+ user, err = mw.Service.CreateUserWithInvite(ctx, p)
return user, err
}
diff --git a/server/service/service_carves.go b/server/service/service_carves.go
new file mode 100644
index 000000000..3bc885695
--- /dev/null
+++ b/server/service/service_carves.go
@@ -0,0 +1,130 @@
+package service
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/google/uuid"
+ hostctx "github.com/kolide/fleet/server/contexts/host"
+ "github.com/kolide/fleet/server/kolide"
+ "github.com/pkg/errors"
+)
+
+const (
+ maxCarveSize = 8 * 1024 * 1024 * 1024 // 8MB
+ maxBlockSize = 256 * 1024 * 1024 // 256MB
+)
+
+func (svc service) CarveBegin(ctx context.Context, payload kolide.CarveBeginPayload) (*kolide.CarveMetadata, error) {
+ host, ok := hostctx.FromContext(ctx)
+ if !ok {
+ return nil, osqueryError{message: "internal error: missing host from request context"}
+ }
+
+ if payload.CarveSize == 0 {
+ return nil, osqueryError{message: "carve_size must be greater than 0"}
+ }
+
+ if payload.BlockSize > maxBlockSize {
+ return nil, osqueryError{message: "block_size exceeds max"}
+ }
+ if payload.CarveSize > maxCarveSize {
+ return nil, osqueryError{message: "carve_size exceeds max"}
+ }
+
+ // The carve should have a total size that fits appropriately into the
+ // number of blocks of the specified size.
+ if payload.CarveSize <= (payload.BlockCount-1)*payload.BlockSize ||
+ payload.CarveSize > payload.BlockCount*payload.BlockSize {
+ return nil, osqueryError{message: "carve_size does not match block_size and block_count"}
+ }
+
+ sessionId, err := uuid.NewRandom()
+ if err != nil {
+ return nil, osqueryError{message: "internal error: generate session ID for carve: " + err.Error()}
+ }
+
+ carve := &kolide.CarveMetadata{
+ Name: fmt.Sprintf("%s-%s-%s", host.HostName, time.Now().Format(time.RFC3339), payload.RequestId),
+ HostId: host.ID,
+ BlockCount: payload.BlockCount,
+ BlockSize: payload.BlockSize,
+ CarveSize: payload.CarveSize,
+ CarveId: payload.CarveId,
+ RequestId: payload.RequestId,
+ SessionId: sessionId.String(),
+ }
+
+ carve, err = svc.ds.NewCarve(carve)
+ if err != nil {
+ return nil, osqueryError{message: "internal error: new carve: " + err.Error()}
+ }
+
+ return carve, nil
+}
+
+func (svc service) CarveBlock(ctx context.Context, payload kolide.CarveBlockPayload) error {
+ // Note host did not authenticate via node key. We need to authenticate them
+ // by the session ID and request ID
+ carve, err := svc.ds.CarveBySessionId(payload.SessionId)
+ if err != nil {
+ return errors.Wrap(err, "find carve by session_id")
+ }
+
+ if payload.RequestId != carve.RequestId {
+ return fmt.Errorf("request_id does not match")
+ }
+
+ // Request is now authenticated
+
+ if payload.BlockId > carve.BlockCount-1 {
+ return fmt.Errorf("block_id exceeds expected max (%d): %d", carve.BlockCount-1, payload.BlockId)
+ }
+
+ if payload.BlockId != carve.MaxBlock+1 {
+ return fmt.Errorf("block_id does not match expected block (%d): %d", carve.MaxBlock+1, payload.BlockId)
+ }
+
+ if int64(len(payload.Data)) > carve.BlockSize {
+ return fmt.Errorf("exceeded declared block size %d: %d", carve.BlockSize, len(payload.Data))
+ }
+
+ if err := svc.ds.NewBlock(carve.ID, payload.BlockId, payload.Data); err != nil {
+ return errors.Wrap(err, "save block data")
+ }
+
+ return nil
+}
+
+func (svc service) GetCarve(ctx context.Context, id int64) (*kolide.CarveMetadata, error) {
+ return svc.ds.Carve(id)
+}
+
+func (svc service) ListCarves(ctx context.Context, opt kolide.CarveListOptions) ([]*kolide.CarveMetadata, error) {
+ return svc.ds.ListCarves(opt)
+}
+
+func (svc service) GetBlock(ctx context.Context, carveId, blockId int64) ([]byte, error) {
+ metadata, err := svc.ds.Carve(carveId)
+ if err != nil {
+ return nil, errors.Wrap(err, "get carve by name")
+ }
+
+ if metadata.Expired {
+ return nil, fmt.Errorf("cannot get block for expired carve")
+ }
+
+ if blockId > metadata.MaxBlock {
+ return nil, fmt.Errorf("block %d not yet available", blockId)
+ }
+
+ data, err := svc.ds.GetBlock(metadata.ID, blockId)
+ if err != nil {
+ return nil, errors.Wrapf(err, "get block %d", blockId)
+ }
+
+ return data, nil
+}
+
+
diff --git a/server/service/service_carves_test.go b/server/service/service_carves_test.go
new file mode 100644
index 000000000..849403079
--- /dev/null
+++ b/server/service/service_carves_test.go
@@ -0,0 +1,441 @@
+package service
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ hostctx "github.com/kolide/fleet/server/contexts/host"
+ "github.com/kolide/fleet/server/kolide"
+ "github.com/kolide/fleet/server/mock"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestCarveBegin(t *testing.T) {
+ host := kolide.Host{ID: 3}
+ payload := kolide.CarveBeginPayload{
+ BlockCount: 23,
+ BlockSize: 64,
+ CarveSize: 23 * 64,
+ RequestId: "carve_request",
+ }
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+ expectedMetadata := kolide.CarveMetadata{
+ ID: 7,
+ HostId: host.ID,
+ BlockCount: 23,
+ BlockSize: 64,
+ CarveSize: 23 * 64,
+ RequestId: "carve_request",
+ }
+ ms.NewCarveFunc = func(metadata *kolide.CarveMetadata) (*kolide.CarveMetadata, error) {
+ metadata.ID = 7
+ return metadata, nil
+ }
+
+ ctx := hostctx.NewContext(context.Background(), host)
+
+ metadata, err := svc.CarveBegin(ctx, payload)
+ require.NoError(t, err)
+ assert.NotEmpty(t, metadata.SessionId)
+ metadata.SessionId = "" // Clear this before comparison
+ metadata.Name = "" // Clear this before comparison
+ assert.Equal(t, expectedMetadata, *metadata)
+}
+
+func TestCarveBeginNewCarveError(t *testing.T) {
+ host := kolide.Host{ID: 3}
+ payload := kolide.CarveBeginPayload{
+ BlockCount: 23,
+ BlockSize: 64,
+ CarveSize: 23 * 64,
+ RequestId: "carve_request",
+ }
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+ ms.NewCarveFunc = func(metadata *kolide.CarveMetadata) (*kolide.CarveMetadata, error) {
+ return nil, fmt.Errorf("ouch!")
+ }
+
+ ctx := hostctx.NewContext(context.Background(), host)
+
+ _, err := svc.CarveBegin(ctx, payload)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "ouch!")
+}
+
+func TestCarveBeginEmptyError(t *testing.T) {
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+ ctx := hostctx.NewContext(context.Background(), kolide.Host{})
+
+ _, err := svc.CarveBegin(ctx, kolide.CarveBeginPayload{})
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "carve_size must be greater than 0")
+}
+
+func TestCarveBeginMissingHostError(t *testing.T) {
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+
+ _, err := svc.CarveBegin(context.Background(), kolide.CarveBeginPayload{})
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "missing host")
+}
+
+func TestCarveBeginBlockSizeMaxError(t *testing.T) {
+ host := kolide.Host{ID: 3}
+ payload := kolide.CarveBeginPayload{
+ BlockCount: 10,
+ BlockSize: 1024 * 1024 * 1024 * 1024, // 1TB
+ CarveSize: 10 * 1024 * 1024 * 1024 * 1024, // 10TB
+ RequestId: "carve_request",
+ }
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+
+ ctx := hostctx.NewContext(context.Background(), host)
+
+ _, err := svc.CarveBegin(ctx, payload)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "block_size exceeds max")
+}
+
+func TestCarveBeginCarveSizeMaxError(t *testing.T) {
+ host := kolide.Host{ID: 3}
+ payload := kolide.CarveBeginPayload{
+ BlockCount: 1024 * 1024,
+ BlockSize: 10 * 1024 * 1024, // 1TB
+ CarveSize: 10 * 1024 * 1024 * 1024 * 1024, // 10TB
+ RequestId: "carve_request",
+ }
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+
+ ctx := hostctx.NewContext(context.Background(), host)
+
+ _, err := svc.CarveBegin(ctx, payload)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "carve_size exceeds max")
+}
+
+func TestCarveBeginCarveSizeError(t *testing.T) {
+ host := kolide.Host{ID: 3}
+ payload := kolide.CarveBeginPayload{
+ BlockCount: 7,
+ BlockSize: 13,
+ CarveSize: 7*13 + 1,
+ RequestId: "carve_request",
+ }
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+ ctx := hostctx.NewContext(context.Background(), host)
+
+ // Too big
+ _, err := svc.CarveBegin(ctx, payload)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "carve_size does not match")
+
+ // Too small
+ payload.CarveSize = 6 * 13
+ _, err = svc.CarveBegin(ctx, payload)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "carve_size does not match")
+}
+
+func TestCarveCarveBlockGetCarveError(t *testing.T) {
+ sessionId := "foobar"
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+ ms.CarveBySessionIdFunc = func(sessionId string) (*kolide.CarveMetadata, error) {
+ return nil, fmt.Errorf("ouch!")
+ }
+
+ payload := kolide.CarveBlockPayload{
+ Data: []byte("this is the carve data :)"),
+ SessionId: sessionId,
+ }
+
+ err := svc.CarveBlock(context.Background(), payload)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "ouch!")
+}
+
+func TestCarveCarveBlockRequestIdError(t *testing.T) {
+ sessionId := "foobar"
+ metadata := &kolide.CarveMetadata{
+ ID: 2,
+ HostId: 3,
+ BlockCount: 23,
+ BlockSize: 64,
+ CarveSize: 23 * 64,
+ RequestId: "carve_request",
+ SessionId: sessionId,
+ }
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+ ms.CarveBySessionIdFunc = func(sessionId string) (*kolide.CarveMetadata, error) {
+ assert.Equal(t, metadata.SessionId, sessionId)
+ return metadata, nil
+ }
+
+ payload := kolide.CarveBlockPayload{
+ Data: []byte("this is the carve data :)"),
+ RequestId: "not_matching",
+ SessionId: sessionId,
+ }
+
+ err := svc.CarveBlock(context.Background(), payload)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "request_id does not match")
+}
+
+func TestCarveCarveBlockBlockCountExceedError(t *testing.T) {
+ sessionId := "foobar"
+ metadata := &kolide.CarveMetadata{
+ ID: 2,
+ HostId: 3,
+ BlockCount: 23,
+ BlockSize: 64,
+ CarveSize: 23 * 64,
+ RequestId: "carve_request",
+ SessionId: sessionId,
+ }
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+ ms.CarveBySessionIdFunc = func(sessionId string) (*kolide.CarveMetadata, error) {
+ assert.Equal(t, metadata.SessionId, sessionId)
+ return metadata, nil
+ }
+
+ payload := kolide.CarveBlockPayload{
+ Data: []byte("this is the carve data :)"),
+ RequestId: "carve_request",
+ SessionId: sessionId,
+ BlockId: 23,
+ }
+
+ err := svc.CarveBlock(context.Background(), payload)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "block_id exceeds expected max")
+}
+
+func TestCarveCarveBlockBlockCountMatchError(t *testing.T) {
+ sessionId := "foobar"
+ metadata := &kolide.CarveMetadata{
+ ID: 2,
+ HostId: 3,
+ BlockCount: 23,
+ BlockSize: 64,
+ CarveSize: 23 * 64,
+ RequestId: "carve_request",
+ SessionId: sessionId,
+ MaxBlock: 3,
+ }
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+ ms.CarveBySessionIdFunc = func(sessionId string) (*kolide.CarveMetadata, error) {
+ assert.Equal(t, metadata.SessionId, sessionId)
+ return metadata, nil
+ }
+
+ payload := kolide.CarveBlockPayload{
+ Data: []byte("this is the carve data :)"),
+ RequestId: "carve_request",
+ SessionId: sessionId,
+ BlockId: 7,
+ }
+
+ err := svc.CarveBlock(context.Background(), payload)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "block_id does not match")
+}
+
+func TestCarveCarveBlockBlockSizeError(t *testing.T) {
+ sessionId := "foobar"
+ metadata := &kolide.CarveMetadata{
+ ID: 2,
+ HostId: 3,
+ BlockCount: 23,
+ BlockSize: 16,
+ CarveSize: 23 * 64,
+ RequestId: "carve_request",
+ SessionId: sessionId,
+ MaxBlock: 3,
+ }
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+ ms.CarveBySessionIdFunc = func(sessionId string) (*kolide.CarveMetadata, error) {
+ assert.Equal(t, metadata.SessionId, sessionId)
+ return metadata, nil
+ }
+
+ payload := kolide.CarveBlockPayload{
+ Data: []byte("this is the carve data :) TOO LONG!!!"),
+ RequestId: "carve_request",
+ SessionId: sessionId,
+ BlockId: 4,
+ }
+
+ err := svc.CarveBlock(context.Background(), payload)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "exceeded declared block size")
+}
+
+func TestCarveCarveBlockNewBlockError(t *testing.T) {
+ sessionId := "foobar"
+ metadata := &kolide.CarveMetadata{
+ ID: 2,
+ HostId: 3,
+ BlockCount: 23,
+ BlockSize: 64,
+ CarveSize: 23 * 64,
+ RequestId: "carve_request",
+ SessionId: sessionId,
+ MaxBlock: 3,
+ }
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+ ms.CarveBySessionIdFunc = func(sessionId string) (*kolide.CarveMetadata, error) {
+ assert.Equal(t, metadata.SessionId, sessionId)
+ return metadata, nil
+ }
+ ms.NewBlockFunc = func(carveId int64, blockId int64, data []byte) error {
+ return fmt.Errorf("kaboom!")
+ }
+
+ payload := kolide.CarveBlockPayload{
+ Data: []byte("this is the carve data :)"),
+ RequestId: "carve_request",
+ SessionId: sessionId,
+ BlockId: 4,
+ }
+
+ err := svc.CarveBlock(context.Background(), payload)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "kaboom!")
+}
+
+func TestCarveCarveBlock(t *testing.T) {
+ sessionId := "foobar"
+ metadata := &kolide.CarveMetadata{
+ ID: 2,
+ HostId: 3,
+ BlockCount: 23,
+ BlockSize: 64,
+ CarveSize: 23 * 64,
+ RequestId: "carve_request",
+ SessionId: sessionId,
+ MaxBlock: 3,
+ }
+ payload := kolide.CarveBlockPayload{
+ Data: []byte("this is the carve data :)"),
+ RequestId: "carve_request",
+ SessionId: sessionId,
+ BlockId: 4,
+ }
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+ ms.CarveBySessionIdFunc = func(sessionId string) (*kolide.CarveMetadata, error) {
+ assert.Equal(t, metadata.SessionId, sessionId)
+ return metadata, nil
+ }
+ ms.NewBlockFunc = func(carveId int64, blockId int64, data []byte) error {
+ assert.Equal(t, metadata.ID, carveId)
+ assert.Equal(t, int64(4), blockId)
+ assert.Equal(t, payload.Data, data)
+ return nil
+ }
+
+ err := svc.CarveBlock(context.Background(), payload)
+ require.NoError(t, err)
+ assert.True(t, ms.NewBlockFuncInvoked)
+}
+
+func TestCarveGetBlock(t *testing.T) {
+ sessionId := "foobar"
+ metadata := &kolide.CarveMetadata{
+ ID: 2,
+ HostId: 3,
+ BlockCount: 23,
+ BlockSize: 64,
+ CarveSize: 23 * 64,
+ RequestId: "carve_request",
+ SessionId: sessionId,
+ MaxBlock: 3,
+ }
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+ ms.CarveFunc = func(carveId int64) (*kolide.CarveMetadata, error) {
+ assert.Equal(t, metadata.ID, carveId)
+ return metadata, nil
+ }
+ ms.GetBlockFunc = func(metadataId int64, blockId int64) ([]byte, error) {
+ assert.Equal(t, metadata.ID, metadataId)
+ assert.Equal(t, int64(3), blockId)
+ return []byte("foobar"), nil
+ }
+
+ data, err := svc.GetBlock(context.Background(), metadata.ID, 3)
+ require.NoError(t, err)
+ assert.Equal(t, []byte("foobar"), data)
+}
+
+func TestCarveGetBlockNotAvailableError(t *testing.T) {
+ sessionId := "foobar"
+ metadata := &kolide.CarveMetadata{
+ ID: 2,
+ HostId: 3,
+ BlockCount: 23,
+ BlockSize: 64,
+ CarveSize: 23 * 64,
+ RequestId: "carve_request",
+ SessionId: sessionId,
+ MaxBlock: 3,
+ }
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+ ms.CarveFunc = func(carveId int64) (*kolide.CarveMetadata, error) {
+ assert.Equal(t, metadata.ID, carveId)
+ return metadata, nil
+ }
+
+ // Block requested is great than max block
+ _, err := svc.GetBlock(context.Background(), metadata.ID, 7)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "not yet available")
+}
+
+func TestCarveGetBlockGetBlockError(t *testing.T) {
+ sessionId := "foobar"
+ metadata := &kolide.CarveMetadata{
+ ID: 2,
+ HostId: 3,
+ BlockCount: 23,
+ BlockSize: 64,
+ CarveSize: 23 * 64,
+ RequestId: "carve_request",
+ SessionId: sessionId,
+ MaxBlock: 3,
+ }
+ ms := new(mock.Store)
+ svc := service{ds: ms}
+ ms.CarveFunc = func(carveId int64) (*kolide.CarveMetadata, error) {
+ assert.Equal(t, metadata.ID, carveId)
+ return metadata, nil
+ }
+ ms.GetBlockFunc = func(metadataId int64, blockId int64) ([]byte, error) {
+ assert.Equal(t, metadata.ID, metadataId)
+ assert.Equal(t, int64(3), blockId)
+ return nil, fmt.Errorf("yow!!")
+ }
+
+ // Block requested is great than max block
+ _, err := svc.GetBlock(context.Background(), metadata.ID, 3)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "yow!!")
+}
+
+
diff --git a/server/service/service_users.go b/server/service/service_users.go
index 0836e275f..1e2260fd1 100644
--- a/server/service/service_users.go
+++ b/server/service/service_users.go
@@ -13,7 +13,7 @@ import (
"github.com/pkg/errors"
)
-func (svc service) NewUser(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
+func (svc service) CreateUserWithInvite(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
invite, err := svc.VerifyInvite(ctx, *p.InviteToken)
if err != nil {
return nil, err
@@ -34,17 +34,17 @@ func (svc service) NewUser(ctx context.Context, p kolide.UserPayload) (*kolide.U
return user, nil
}
-func (svc service) NewAdminCreatedUser(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
+func (svc service) CreateUser(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
return svc.newUser(p)
}
func (svc service) newUser(p kolide.UserPayload) (*kolide.User, error) {
var ssoEnabled bool
// if user is SSO generate a fake password
- if p.SSOInvite != nil && *p.SSOInvite {
+ if (p.SSOInvite != nil && *p.SSOInvite) || (p.SSOEnabled != nil && *p.SSOEnabled) {
fakePassword, err := generateRandomText(14)
if err != nil {
- return nil, err
+ return nil, errors.Wrap(err, "generate stand-in password")
}
p.Password = &fakePassword
ssoEnabled = true
diff --git a/server/service/service_users_test.go b/server/service/service_users_test.go
index 97a9493b3..c996f9337 100644
--- a/server/service/service_users_test.go
+++ b/server/service/service_users_test.go
@@ -335,13 +335,13 @@ func TestRequestPasswordReset(t *testing.T) {
}
}
-func TestCreateUser(t *testing.T) {
+func TestCreateUserWithInvite(t *testing.T) {
ds, _ := inmem.New(config.TestConfig())
svc, _ := newTestService(ds, nil, nil)
invites := setupInvites(t, ds, []string{"admin2@example.com"})
ctx := context.Background()
- var createUserTests = []struct {
+ var newUserTests = []struct {
Username *string
Password *string
Email *string
@@ -400,7 +400,7 @@ func TestCreateUser(t *testing.T) {
},
}
- for _, tt := range createUserTests {
+ for _, tt := range newUserTests {
t.Run("", func(t *testing.T) {
payload := kolide.UserPayload{
Username: tt.Username,
@@ -409,7 +409,7 @@ func TestCreateUser(t *testing.T) {
Admin: tt.Admin,
InviteToken: tt.InviteToken,
}
- user, err := svc.NewUser(ctx, payload)
+ user, err := svc.CreateUserWithInvite(ctx, payload)
if tt.wantErr != nil {
require.Equal(t, tt.wantErr.Error(), err.Error())
}
diff --git a/server/service/transport_carves.go b/server/service/transport_carves.go
new file mode 100644
index 000000000..cb3187a4b
--- /dev/null
+++ b/server/service/transport_carves.go
@@ -0,0 +1,71 @@
+package service
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "github.com/pkg/errors"
+ "github.com/kolide/fleet/server/kolide"
+)
+
+func decodeCarveBeginRequest(ctx context.Context, r *http.Request) (interface{}, error) {
+ defer r.Body.Close()
+
+ var req carveBeginRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ return nil, errors.Wrap(err, "decoding JSON")
+ }
+
+ return req, nil
+}
+
+func decodeCarveBlockRequest(ctx context.Context, r *http.Request) (interface{}, error) {
+ defer r.Body.Close()
+
+ var req carveBlockRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ return nil, errors.Wrap(err, "decoding JSON")
+ }
+
+ return req, nil
+}
+
+func decodeGetCarveRequest(ctx context.Context, r *http.Request) (interface{}, error) {
+ id, err := idFromRequest(r, "id")
+ if err != nil {
+ return nil, err
+ }
+ return getCarveRequest{ID: int64(id)}, nil
+}
+
+func decodeListCarvesRequest(ctx context.Context, r *http.Request) (interface{}, error) {
+ opt, err := listOptionsFromRequest(r)
+ if err != nil {
+ return nil, err
+ }
+ copt := kolide.CarveListOptions{ListOptions: opt}
+ expired := r.URL.Query().Get("expired")
+ switch expired {
+ case "1", "true":
+ copt.Expired = true
+ case "0", "":
+ copt.Expired = false
+ default:
+ return nil, errors.Errorf("invalid expired value %s", expired)
+ }
+ return listCarvesRequest{ListOptions: copt}, nil
+}
+
+func decodeGetCarveBlockRequest(ctx context.Context, r *http.Request) (interface{}, error) {
+ id, err := idFromRequest(r, "id")
+ if err != nil {
+ return nil, err
+ }
+ blockId, err := idFromRequest(r, "block_id")
+ if err != nil {
+ return nil, err
+ }
+ return getCarveBlockRequest{ID: int64(id), BlockId: int64(blockId)}, nil
+}
+
diff --git a/server/service/transport_hosts.go b/server/service/transport_hosts.go
index 87be34a37..6b5e5135e 100644
--- a/server/service/transport_hosts.go
+++ b/server/service/transport_hosts.go
@@ -3,6 +3,7 @@ package service
import (
"context"
"net/http"
+ "strings"
"github.com/kolide/fleet/server/kolide"
"github.com/pkg/errors"
@@ -48,5 +49,10 @@ func decodeListHostsRequest(ctx context.Context, r *http.Request) (interface{},
if err != nil {
return nil, err
}
+
+ additionalInfoFiltersString := r.URL.Query().Get("additional_info_filters")
+ if additionalInfoFiltersString != "" {
+ hopt.AdditionalFilters = strings.Split(additionalInfoFiltersString, ",")
+ }
return listHostsRequest{ListOptions: hopt}, nil
}
diff --git a/server/service/validation_users.go b/server/service/validation_users.go
index a9ed2f279..e331e238f 100644
--- a/server/service/validation_users.go
+++ b/server/service/validation_users.go
@@ -10,7 +10,7 @@ import (
"github.com/kolide/fleet/server/kolide"
)
-func (mw validationMiddleware) NewUser(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
+func (mw validationMiddleware) CreateUserWithInvite(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
invalid := &invalidArgumentError{}
if p.Username == nil {
invalid.Append("username", "missing required argument")
@@ -57,7 +57,51 @@ func (mw validationMiddleware) NewUser(ctx context.Context, p kolide.UserPayload
if invalid.HasErrors() {
return nil, invalid
}
- return mw.Service.NewUser(ctx, p)
+ return mw.Service.CreateUserWithInvite(ctx, p)
+}
+
+func (mw validationMiddleware) CreateUser(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
+ invalid := &invalidArgumentError{}
+ if p.Username == nil {
+ invalid.Append("username", "missing required argument username")
+ } else {
+ if *p.Username == "" {
+ invalid.Append("username", "username cannot be empty")
+ }
+
+ if strings.Contains(*p.Username, "@") {
+ invalid.Append("username", "'@' character not allowed in usernames")
+ }
+ }
+
+ // we don't need a password for single sign on
+ if (p.SSOInvite == nil || !*p.SSOInvite) && (p.SSOEnabled == nil || !*p.SSOEnabled) {
+ if p.Password == nil {
+ invalid.Append("password", "missing required argument password")
+ } else {
+ if *p.Password == "" {
+ invalid.Append("password", "password cannot be empty")
+ }
+ // Skip password validation in the case of admin created users
+ }
+ }
+
+ if p.Email == nil {
+ invalid.Append("email", "missing required argument email")
+ } else {
+ if *p.Email == "" {
+ invalid.Append("email", "email cannot be empty")
+ }
+ }
+
+ if p.InviteToken != nil {
+ invalid.Append("invite_token", "invite_token should not be specified with admin user creation")
+ }
+
+ if invalid.HasErrors() {
+ return nil, invalid
+ }
+ return mw.Service.CreateUser(ctx, p)
}
func (mw validationMiddleware) ModifyUser(ctx context.Context, userID uint, p kolide.UserPayload) (*kolide.User, error) {
diff --git a/tools/osquery/example_osquery.flags b/tools/osquery/example_osquery.flags
index 9f868c7ac..90891502b 100644
--- a/tools/osquery/example_osquery.flags
+++ b/tools/osquery/example_osquery.flags
@@ -23,3 +23,8 @@
--logger_plugin=tls
--logger_tls_endpoint=/api/v1/osquery/log
--logger_tls_period=10
+
+--disable_carver=false
+--carver_start_endpoint=/api/v1/osquery/carve/begin
+--carver_continue_endpoint=/api/v1/osquery/carve/block
+--carver_block_size=2000000
diff --git a/tools/osquery/kolide.crt b/tools/osquery/kolide.crt
index f617219e2..f0cf32198 100644
--- a/tools/osquery/kolide.crt
+++ b/tools/osquery/kolide.crt
@@ -1,19 +1,29 @@
-----BEGIN CERTIFICATE-----
-MIIDBjCCAe4CCQDjM7ghDw9OgDANBgkqhkiG9w0BAQsFADBFMQ4wDAYDVQQKDAVG
-bGVldDEUMBIGA1UECwwLRGV2ZWxvcG1lbnQxHTAbBgNVBAMMFGhvc3QuZG9ja2Vy
-LmludGVybmFsMB4XDTE5MDMyNjE5MTQyN1oXDTQ2MDgxMDE5MTQyN1owRTEOMAwG
-A1UECgwFRmxlZXQxFDASBgNVBAsMC0RldmVsb3BtZW50MR0wGwYDVQQDDBRob3N0
-LmRvY2tlci5pbnRlcm5hbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
-AOnDxrXtuB3VpmsExCVXboF0Llcfee4FF4Y0ifS75RjnLSXupZubQWHenBGmQ7qb
-3eTBuw59HiTBkB5CZb79FpFyYrxMpXn+7dVJt2N9NnpKkVidqFXaVqTS9P84JQtt
-LIPzyQlRFM482Y0RVfY1UnrWUTqze/wUpJP6xY+aPseIbnYvbsGtZ8PjrLMN+6z8
-f2Ef8Hr6uIR2mQe+qu43HuEkjbTnRi1ORj2fr4cSgoVd7FMhIy1yGEaUP4aerB3X
-k1h4eOJa/Vd2eE3b3vXg7awAtdPJDWz4LjQCkgzeI5tej+TooAHM0gyun7EPSy+9
-2fgkAkEvY/dco6h+D0ULuNECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAEhGq1t5G
-ZyhXEYJrd92HHi6zyRQNlAoQ55utmE/tlNAq1e2k1uEKEeOmIFvaLpQ3ky2z1Aok
-Qrn4+TYFcNN6vB99QxNVb3+hDm+hyKj0N7VvvYIX8ms/AUKZUaUahxM4gXDNky35
-l33IYgGmdcSzBiTkTCGABmtHwJ/QAPI1v6iWGXLQ42HNfCCflFHt5AtOs7Nfv0u5
-JsgkMVu+Vj9sOcdE+UtAJ/NQyV+kWFgzUtxdX8QX/NQVFqlNPVV4cRi8KMbBM1q+
-xN2o1vC27vM+5VWTr5sddU6MVJJXIIP4wfe+qbCnD8GE5+7j8XiUrOMsauiafaW2
-cdSv0nZ/WcEovw==
+MIIE5jCCAs6gAwIBAgIJAKq0+FAVArUhMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV
+BAMMCkZsZWV0IFRlc3QwHhcNMjAxMDE1MTg1ODE5WhcNMzAwNzE1MTg1ODE5WjAV
+MRMwEQYDVQQDDApGbGVldCBUZXN0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+CgKCAgEAttQ62lpMq48/XjQFxYg47D2fgKgTBMjDNSfCt9VpqE3xPnybmWo8VZtk
+jmrFM50IhagyYSjvl9iqdrnsl3ZV8KYbWEy6849zDYF1SudmC7/pJyH7QvpKgL7V
+4McM62jM905hyFy9KZTAlFiaeezWSWJre7kHsK2u5tsqS6ElatEZmF59sInixaRw
+RqVxOhtDm7Sl1c5xKWM6phWoJfykspFpu5J6N2jRQXCfzBYoQWN76OohGouit/BQ
+C1xvm+f7wgGZgbbfDjUoHAe9Yhd3XzZsYTgMDt/SRJRDnxFZwo8BAkY9yJm7f3dQ
+AEhgJ66KbyoxITdgma1hgmeWibZY4hVymcRxB1B3RNN2at19RNy2J+brMxlG0KZk
+nD77EqidrwLAlYcdeU3yLjt0vYPxT+RW7l1jiZlVi/oaykAmVfOhWnTnTwbsYs7O
+UMyMyYHQECEs98ex7wrjThIBJScqhsSN1ipAxr5RgaDr+U5IR+tLhMewBy8So+nf
+2YuMhLfkCgoY80ELhz5F8avts5hksB0hqnNYr+Nlwm6eXqEPZSzFJmdc1IbmWzq2
+7UH1OQmBFF2qr2j/8dcM+oPNgjrEEQjtyW0S4j2PhjSEbINgcwu0AaABssLI80Vm
+Gp1TjUGA92rMwIjlAtcUUB5FOKSS8vAXb1VcDWMkybh9sHj4Z0ECAwEAAaM5MDcw
+NQYDVR0RBC4wLIIJbG9jYWxob3N0ghRob3N0LmRvY2tlci5pbnRlcm5hbIIJMTI3
+LjAuMC4xMA0GCSqGSIb3DQEBCwUAA4ICAQBZOY++LNRTVG8XlQGVlOloEKA2WY3P
+gXKJLSM7xWSxj2co0v+noyhoInHT7ysub8en59Et+vN53/0OobxNIdEKDUdqs38R
+F++Oy6s/MhFHSo87F06t+W91/60ub4iFRHfev1qeNFV6Yzv9sFJ5LpXLFk+qVDb8
+pPyFFE1bXjctDPjD5gUj+Y34XikVKzMb7xddWCNs34v1KCaCBW7kkfefxiZiDR6g
+lCEkDzp6xaLS898oCbfFakjr4bvOgBP1IqXLIDLPMhivaxNAooHTtu/3ezp7puix
+TSDkjlkStDtEFw/wjyaMcEkk51Gs1ponBbADLRxQ50AHDWk/4vy8GcIVc6CdVEOA
+Zw12FN06C4Jviiiv6uCXZ6iZ+V+pjGiGmSNYF+kruUs8BfrJIB89lqxpdQ4Kx01j
+AuSFvjRRvIPmvApSdKEjLcY3AYRivXsB/hASMBbjh/p1f/JzSJdxoqSvONhNQJuh
++wcdNVQhGAv3kkLn/HMHTBl2Ur+9tQaJrnR1tWl1IzwLRJIi0Soyp/q5ZjQyFglj
+32xW83DZhtpQ2SI1QGy4AvWIPnGHZhMfav02KnKRhZdOMW4oekXRMrwiyXCqIazc
+xXzAlCq8dHdP2Y9uvfFxVFyE+uSfkcPxX+DG/ZnpgCS27oKA/qLCybJamlqtveNs
+RSjNe5qwGi0ifA==
-----END CERTIFICATE-----
diff --git a/tools/osquery/kolide.key b/tools/osquery/kolide.key
index e5024c873..f8ac67b1e 100644
--- a/tools/osquery/kolide.key
+++ b/tools/osquery/kolide.key
@@ -1,28 +1,52 @@
-----BEGIN PRIVATE KEY-----
-MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDpw8a17bgd1aZr
-BMQlV26BdC5XH3nuBReGNIn0u+UY5y0l7qWbm0Fh3pwRpkO6m93kwbsOfR4kwZAe
-QmW+/RaRcmK8TKV5/u3VSbdjfTZ6SpFYnahV2lak0vT/OCULbSyD88kJURTOPNmN
-EVX2NVJ61lE6s3v8FKST+sWPmj7HiG52L27BrWfD46yzDfus/H9hH/B6+riEdpkH
-vqruNx7hJI2050YtTkY9n6+HEoKFXexTISMtchhGlD+Gnqwd15NYeHjiWv1XdnhN
-29714O2sALXTyQ1s+C40ApIM3iObXo/k6KABzNIMrp+xD0svvdn4JAJBL2P3XKOo
-fg9FC7jRAgMBAAECggEAPPViohqFgrIuHYY2qA4usO9dDjcVEq6dPdABkaJV0bKn
-IVckNHm+CQmF5UxYKRdwO7ERWGvkKm2XaWyjH7Tb573OZZAPLsr9tjrs6bLDPAyp
-7lPei9TN91lTriIz9tDXZOlzqUxNgqJ3kMPOiM/K3GF6+TXIxSmU6UnhrtroOBuq
-K+o/qC5K8CnqpBhBQt6qtn/YdfbcABogRbwojnHRl068nMV9kJLp7/I5hgI8Vr+v
-xMXI87wbL7kglcBZweG2EL//C31eSEVG2cW+0cIefQil0ZIPWgX0z5Oex2OD+cLL
-E+FIp/waZ7plCiikNNMo+gm/4uy9iupxg+z5W71NyQKBgQD2OSTe8w2Ns9zzLIXC
-5ZrFTa5LmTJ8bIv7/5kCS7fSGnoDPZvaRfnqzVEoCUp5D2N09NOJIsoNGrIGnMCc
-q6HwGEzGwmOgtzYu1r2jC+6jlcJW4tMr8k52CRO3y3LvsYkmt6tYIBgMVoxIupce
-QyBm5zVccyIvdhYhapgcSTJrtwKBgQDzC/30lTlBWsLiqLtfHAwM3YJgdVczJrbe
-WDieUd8ywTVupDOhBSGwVGmXGLQE5G/eNfwlsBq2iij7H/WdkX6mD1YmnQJUit3x
-0cPmDwWMo3vGLUUUI34WJ/2WSxdTtfaIcaFlV6S9Bp9B5K208kG/cZliqBWG1mtS
-a/AdsdcPtwKBgFHan2pKzNet0qc4xuMK54/uCiJxtHnur/6/cwNzXpHHYYaQRa6j
-kri/NtqjdBGYzwyDk4tEeH0wwbw3AkVTPYuO2H8/AlXccnPRyctZXSUe1TODRoaW
-kATncZmpVfVfROQNLTYnvTbk0tAez7wsvqnW9UNdtyCmFidXw2er4cozAoGADuPK
-MCTAODfGPrqVmBMQzez4Is6tg+24QFDpTxG2+dYKXvfiTdgRo0rYmTAjPzV7gQKP
-qwNp74rxTck8c+XI+4VvAriVvvYu+LSgKsT60w3k9FQrqjsua08R4xZAnJlGPD+x
-4pKG/imcsh2/YpmA26iq+/dOMk+KjacdM8SEZ2sCgYB/qypw7LpskkGA5ITODv5A
-cAHVCCDMGNJiR4XpdOwHNAcd6EWjB/366/MwKfV24KnZH0RZe+pU2e4dP8miHNN0
-dFOpAokCD3Eh7LGDVM6bYkQabG3XTmN8g9a4V2e63n5Bl2HZx0Qp1bHEtqsOujpj
-5G5maHPlTRoNlvk4BiEZDA==
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC21DraWkyrjz9e
+NAXFiDjsPZ+AqBMEyMM1J8K31WmoTfE+fJuZajxVm2SOasUznQiFqDJhKO+X2Kp2
+ueyXdlXwphtYTLrzj3MNgXVK52YLv+knIftC+kqAvtXgxwzraMz3TmHIXL0plMCU
+WJp57NZJYmt7uQewra7m2ypLoSVq0RmYXn2wieLFpHBGpXE6G0ObtKXVznEpYzqm
+Fagl/KSykWm7kno3aNFBcJ/MFihBY3vo6iEai6K38FALXG+b5/vCAZmBtt8ONSgc
+B71iF3dfNmxhOAwO39JElEOfEVnCjwECRj3Imbt/d1AASGAnropvKjEhN2CZrWGC
+Z5aJtljiFXKZxHEHUHdE03Zq3X1E3LYn5uszGUbQpmScPvsSqJ2vAsCVhx15TfIu
+O3S9g/FP5FbuXWOJmVWL+hrKQCZV86FadOdPBuxizs5QzIzJgdAQISz3x7HvCuNO
+EgElJyqGxI3WKkDGvlGBoOv5TkhH60uEx7AHLxKj6d/Zi4yEt+QKChjzQQuHPkXx
+q+2zmGSwHSGqc1iv42XCbp5eoQ9lLMUmZ1zUhuZbOrbtQfU5CYEUXaqvaP/x1wz6
+g82COsQRCO3JbRLiPY+GNIRsg2BzC7QBoAGywsjzRWYanVONQYD3aszAiOUC1xRQ
+HkU4pJLy8BdvVVwNYyTJuH2wePhnQQIDAQABAoICAHY0mSh7oX56SzoY4HJqNINp
+BCsmf8VkF/HSFy7MKFfMrOD9p4x1BzVCFq2NkppgORZRPOFcmivOxcelRbZXqBDD
+FILueSDn6jcdMDyRh4SpO5E1g0I2mNzMhXOivlOlmn96ifYFH7g2mJxQ/O8/lPw1
+SdDgPHyajq5rnNjxA0QglS/SR+lP/PJT8tN6O86Zugh9r4qgwsFDirO+5MxKoCFj
+qry/Y41Xk5N/wZEt8jD0pTRdy7Fh/n52PQpbZT2jqz4v0pPvLB//dkNJLcSx71f7
+0+63UfR7+XKleWLq0OHdm4Vg7Kk52+P5vBuyr5h4XMCqwsD7ENXLJ3QsjUPURXot
+RWHy2oqVLb2/zFpYTc/pLtIkJtkgZB9kq7O7+kjlO/k8FeKjHU109ERWS2Wj2md+
+hWLXxlYAT9XJmKQAtt7BsKWw1XncO+9TCZ1NDmRLr4oAfLF+nQ8r9N/Uy2Grk82G
+cY3mRrGADS8ioX4DXvxxbsDu8iPAfcoN/wKtZ+cTBIaUW8qaaBHavbl+ZPi/HXo0
+sGFLgMApHlygqAmY5LEXs7lz7psh8MwbDllHHipABmgttWBQ3qSdpb/dTw4eyHX/
+9QlEF5MzL/SqcMJsfh1NVhYoOKrroDwyS96eFtNYs8lpiYzf18Wm/69FtdWVTZDL
+EpVc+dQsRvgBzlVQDBARAoIBAQDzD6l/zcqKKUATG9WgbVXK3E6q6srx2csE+HCI
+OAa9dq893YX35Z6DNZdE95NxO2OG+iAb4kSl498uOct0nKIdWF8VsSPvWN5igjaM
+r3E/7JQE6nRLdyRDN+m4KGJCpmSoGhs9SjyjRUs7mLyxzNpADfpWmNo1MQFGSlUy
+W8xD2nrB0BZiLFHCntMVhOZXFenjqmG1KVA1IsrtLebEwDrLeMEXH9miNAFKnYgf
+W3b42MWDdfUqbgAQFtloiuwQRItvf4frrXoo++pYTPZlROHK0wQZcRG33eMvDhYd
+XjDF0dvIzrGKikj64Xf3rXXUQT7zKQ2PdZokK7Q6mQOzF/h/AoIBAQDAj7+qw5aU
+nZtNSFLx2pihe4d75JVBCQVJlNEpl0A3+UKpp/r8xGG57iXxZC0ZNhWWfN3u5TaB
+32d6IbqQT8cSDa4Z9EwYABoSGcj1xVCPS9PJqkxpwC7AlpwYC4Vk5++Wt85nujIx
+rQYit6V3WxHtxtfD/RVTMWUWUnMjmMGSGneFXT3jLn+7Hh2rITNPuM+KMSPK3D3c
+I6j+SBJanaXMTfWqFyPs6fJuku2rVx8kivfZz1XLDv/K0Fjp3pZfVebYLH2Heyk6
+0FElVksLbGUUZCvdI02d4ds4DmZQzNMfDrk9x05TnH/RlM2hNYCslNmpDTdc4C8b
+VP0s2PWSzcA/AoIBAQC88lXvVgi28m4u7JpaxePFeRFmaHMvIwFhccf4/KSwsAx2
+aTh1hvh8QzK+mD7z7RjnmpVPV8vJsaSTCLaShhi3+zSfZj7rFwh5R4QkRVYiPUSr
+tZ6F864q2tJDKJGKAlOJIhI+yPDuczWStJ8rEHYxCSysGNK4Osok3C/yn83giXyY
+Iazk0FMWmeS5e8Cbvhs9sfuWmvEQ/WUGj4IvPMf32H0x/r5uC0Ndv6xLxAxuUgTo
+ts/JFg3SdBC55hSwaLiECn2cxhSKu5pm3h1EiBGGXBd5t53wcvriX27tkYUUopja
+N2NosSsebBuYXC2WvMm0uRsjhGY47AiE7OIlXOL9AoIBAA04EQ1U/fpX03h7tY36
+1q2HQGbF62UajG1ftPgo9PSivOvOp1FA3gCYk6w9l0b6yoKZMdcbjyFdR8o/lIIh
+p+XaYJBRkAO5xhBmCsCsefpMV06pzTMZSVxZOoAAEnk53t224omGY7m7SgeKGebW
+rqVnGBrRPeyHIIxmvpa7/tXb0Uarfvsgjw42ZA0Ca3ZWlpDDDNT5R5ncLNa/9dqY
+pfAfjfTOP94ctVLX7U7s3StyCs++BwK4leDDascrS6Fh0UYXz8pELzFlaZypjt9K
+4qmCsuwsZ0CmZ5kyi92SIhAov5i5HOxqeu/VSkR92sZ+NW8AhENw9Gro67RvdHRO
+gZsCggEBAIjpsGAL1P4zmXW07EWfOKSmQmfusl/hbaKl2FKaM5rtku73GjW2kwBw
+0RE/cKqENWasAryYPaDjGys+c6xzrYS3Mo2FT5vP6jRl0TxPsQjE1JY2wNgXbHss
+y1Afjumv5vHBLaSKm0v3UF57NCndKWTebBURY4V1CiXetOaR2hNgbI6zPSNP2srz
+Cphr4i3O2H98wKhC6mmYA46zF1imkFu5ZYBEnue4pHPMkyGzwqrvLRmahIr+C313
+5rW3mumKvHHGbMYJCQVdM7edxZJ4Qn3gIAlJlOQtcPyqVwuwyXnVybTIsnhRiHP8
+mWZen+G8ANnds10V7TmcpoaPSSoJMF0=
-----END PRIVATE KEY-----
diff --git a/www.fleetdm.com/README.md b/www.fleetdm.com/README.md
new file mode 100644
index 000000000..a99ae3dae
--- /dev/null
+++ b/www.fleetdm.com/README.md
@@ -0,0 +1,5 @@
+# www.fleetdm.com
+
+This is where the code for the public https://fleetdm.com website will live.
+
+Soon. But for now, it's actually here: https://github.com/fleetdm/fleetdm.com
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 7f82b61d9..766588af0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -219,6 +219,11 @@ accepts@~1.3.5:
mime-types "~2.1.24"
negotiator "0.6.2"
+ace-builds@1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.3.1.tgz#c7f9d7a657e7d9c630acd78f1dc2fa1e0e2a84f6"
+ integrity sha512-MJtPAqeGaiIpfgUCXi3/oowqcIw4wSkKTDGvtfUoQHrfZGfjNnH3frPdHzd1VfKF62JFeNJOl4q0TRDiHwoBFg==
+
acorn-globals@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf"
|