Custom email device-mapping: implement the REST API changes (#15748)

This commit is contained in:
Martin Angers 2023-12-21 12:21:39 -05:00 committed by GitHub
parent 6a3b7b8315
commit 235d2cf2dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 430 additions and 55 deletions

View file

@ -0,0 +1 @@
* Added the `PUT /api/fleet/orbit/device_mapping` and `PUT /api/v1/fleet/hosts/{id}/device_mapping` endpoints (orbit-authenticated and user-authenticated) to set or replace the custom email address associated with a host.

View file

@ -425,7 +425,7 @@ func loadHostScheduledQueryStatsDB(ctx context.Context, db sqlx.QueryerContext,
AND (q.team_id IS NULL OR q.team_id = ?) AND (q.team_id IS NULL OR q.team_id = ?)
OR EXISTS ( OR EXISTS (
SELECT 1 FROM query_results SELECT 1 FROM query_results
WHERE query_results.query_id = q.id WHERE query_results.query_id = q.id
AND query_results.host_id = ? AND query_results.host_id = ?
) )
GROUP BY q.id GROUP BY q.id
@ -952,14 +952,14 @@ func (ds *Datastore) applyHostFilters(
) (string, []interface{}, error) { ) (string, []interface{}, error) {
opt.OrderKey = defaultHostColumnTableAlias(opt.OrderKey) opt.OrderKey = defaultHostColumnTableAlias(opt.OrderKey)
deviceMappingJoin := `LEFT JOIN ( deviceMappingJoin := fmt.Sprintf(`LEFT JOIN (
SELECT SELECT
host_id, host_id,
CONCAT('[', GROUP_CONCAT(JSON_OBJECT('email', email, 'source', source)), ']') AS device_mapping CONCAT('[', GROUP_CONCAT(JSON_OBJECT('email', email, 'source', %s)), ']') AS device_mapping
FROM FROM
host_emails host_emails
GROUP BY GROUP BY
host_id) dm ON dm.host_id = h.id` host_id) dm ON dm.host_id = h.id`, deviceMappingTranslateSourceColumn(""))
if !opt.DeviceMapping { if !opt.DeviceMapping {
deviceMappingJoin = "" deviceMappingJoin = ""
} }
@ -2856,21 +2856,35 @@ func (ds *Datastore) CleanupExpiredHosts(ctx context.Context) ([]uint, error) {
} }
func (ds *Datastore) ListHostDeviceMapping(ctx context.Context, id uint) ([]*fleet.HostDeviceMapping, error) { func (ds *Datastore) ListHostDeviceMapping(ctx context.Context, id uint) ([]*fleet.HostDeviceMapping, error) {
stmt := ` return ds.listHostDeviceMappingDB(ctx, ds.reader(ctx), id)
}
func deviceMappingTranslateSourceColumn(hostEmailsTableAlias string) string {
if hostEmailsTableAlias != "" && !strings.HasSuffix(hostEmailsTableAlias, ".") {
hostEmailsTableAlias += "."
}
// this means:
// if source starts with "custom_" then return "custom" else return source as-is
return fmt.Sprintf(` CASE WHEN %ssource LIKE '%s%%' THEN '%s' ELSE %[1]ssource END `,
hostEmailsTableAlias, fleet.DeviceMappingCustomPrefix, fleet.DeviceMappingCustomReplacement)
}
func (ds *Datastore) listHostDeviceMappingDB(ctx context.Context, q sqlx.QueryerContext, hostID uint) ([]*fleet.HostDeviceMapping, error) {
stmt := fmt.Sprintf(`
SELECT SELECT
id, id,
host_id, host_id,
email, email,
source %s as source
FROM FROM
host_emails host_emails
WHERE WHERE
host_id = ? host_id = ?
ORDER BY ORDER BY
email, source` email, source`, deviceMappingTranslateSourceColumn(""))
var mappings []*fleet.HostDeviceMapping var mappings []*fleet.HostDeviceMapping
err := sqlx.SelectContext(ctx, ds.reader(ctx), &mappings, stmt, id) err := sqlx.SelectContext(ctx, q, &mappings, stmt, hostID)
if err != nil { if err != nil {
return nil, ctxerr.Wrap(ctx, err, "select host emails by host id") return nil, ctxerr.Wrap(ctx, err, "select host emails by host id")
} }
@ -2963,6 +2977,65 @@ func (ds *Datastore) ReplaceHostDeviceMapping(ctx context.Context, hid uint, map
}) })
} }
func (ds *Datastore) SetOrUpdateCustomHostDeviceMapping(ctx context.Context, hostID uint, email, source string) ([]*fleet.HostDeviceMapping, error) {
const (
delStmt = `DELETE FROM host_emails WHERE host_id = ? AND source = ?`
updStmt = `UPDATE host_emails SET email = ? WHERE host_id = ? AND source = ?`
insStmt = `INSERT INTO host_emails (email, host_id, source) VALUES (?, ?, ?)`
// for the custom_installer source, we insert it only if there is no
// existing custom_override source for that host.
insInstallerStmt = `INSERT INTO host_emails (email, host_id, source)
(
SELECT ?, ?, ?
FROM DUAL
WHERE
NOT EXISTS (
SELECT 1 FROM host_emails WHERE host_id = ? AND source = ?
)
)`
)
err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
if source == fleet.DeviceMappingCustomOverride {
// must delete any existing custom installer
if _, err := tx.ExecContext(ctx, delStmt, hostID, fleet.DeviceMappingCustomInstaller); err != nil {
return ctxerr.Wrap(ctx, err, "delete custom installer device mapping")
}
}
// first attempt an update, and if it updates nothing proceed with an
// insert (this is the same logic as our insertOrUpdate helper function,
// but it is not used directly because we may need a more complex insert
// statement). We cannot use ON DUPLICATE KEY UPDATE because there is no
// unique constraint on that table.
res, err := tx.ExecContext(ctx, updStmt, email, hostID, source)
if err != nil {
return ctxerr.Wrap(ctx, err, "update custom device mapping")
}
rowsAffected, _ := res.RowsAffected() // cannot fail for mysql driver
if rowsAffected == 0 {
// use an insert, no existing email for that source
stmt := insStmt
params := []any{email, hostID, source}
if source == fleet.DeviceMappingCustomInstaller {
// custom installer can only insert if there's no custom override
stmt = insInstallerStmt
params = append(params, hostID, fleet.DeviceMappingCustomOverride)
}
if _, err := tx.ExecContext(ctx, stmt, params...); err != nil {
return ctxerr.Wrap(ctx, err, "insert custom device mapping")
}
}
return nil
})
if err != nil {
return nil, err
}
return ds.listHostDeviceMappingDB(ctx, ds.writer(ctx), hostID)
}
func (ds *Datastore) ReplaceHostBatteries(ctx context.Context, hid uint, mappings []*fleet.HostBattery) error { func (ds *Datastore) ReplaceHostBatteries(ctx context.Context, hid uint, mappings []*fleet.HostBattery) error {
for _, m := range mappings { for _, m := range mappings {
if hid != m.HostID { if hid != m.HostID {
@ -3347,8 +3420,6 @@ func (ds *Datastore) SetOrUpdateMDMData(
) )
} }
const hostEmailsSourceMdmIdpAccounts = "mdm_idp_accounts"
func (ds *Datastore) SetOrUpdateHostEmailsFromMdmIdpAccounts( func (ds *Datastore) SetOrUpdateHostEmailsFromMdmIdpAccounts(
ctx context.Context, ctx context.Context,
hostID uint, hostID uint,
@ -3367,7 +3438,7 @@ func (ds *Datastore) SetOrUpdateHostEmailsFromMdmIdpAccounts(
ctx, ctx,
`UPDATE host_emails SET email = ? WHERE host_id = ? AND source = ?`, `UPDATE host_emails SET email = ? WHERE host_id = ? AND source = ?`,
`INSERT INTO host_emails (email, host_id, source) VALUES (?, ?, ?)`, `INSERT INTO host_emails (email, host_id, source) VALUES (?, ?, ?)`,
email, hostID, hostEmailsSourceMdmIdpAccounts, email, hostID, fleet.DeviceMappingMDMIdpAccounts,
) )
} }

View file

@ -124,6 +124,7 @@ func TestHosts(t *testing.T) {
{"HostsNoSeenTime", testHostsNoSeenTime}, {"HostsNoSeenTime", testHostsNoSeenTime},
{"HostDeviceMapping", testHostDeviceMapping}, {"HostDeviceMapping", testHostDeviceMapping},
{"ReplaceHostDeviceMapping", testHostsReplaceHostDeviceMapping}, {"ReplaceHostDeviceMapping", testHostsReplaceHostDeviceMapping},
{"CustomHostDeviceMapping", testHostsCustomHostDeviceMapping},
{"HostMDMAndMunki", testHostMDMAndMunki}, {"HostMDMAndMunki", testHostMDMAndMunki},
{"AggregatedHostMDMAndMunki", testAggregatedHostMDMAndMunki}, {"AggregatedHostMDMAndMunki", testAggregatedHostMDMAndMunki},
{"MunkiIssuesBatchSize", testMunkiIssuesBatchSize}, {"MunkiIssuesBatchSize", testMunkiIssuesBatchSize},
@ -4888,6 +4889,102 @@ func testHostsReplaceHostDeviceMapping(t *testing.T, ds *Datastore) {
}) })
} }
func testHostsCustomHostDeviceMapping(t *testing.T, ds *Datastore) {
ctx := context.Background()
h1, err := ds.NewHost(ctx, &fleet.Host{
OsqueryHostID: ptr.String("1"),
NodeKey: ptr.String("1"),
Platform: "linux",
Hostname: "host1",
DetailUpdatedAt: time.Now(),
LabelUpdatedAt: time.Now(),
PolicyUpdatedAt: time.Now(),
SeenTime: time.Now(),
})
require.NoError(t, err)
h2, err := ds.NewHost(ctx, &fleet.Host{
OsqueryHostID: ptr.String("2"),
NodeKey: ptr.String("2"),
Platform: "linux",
Hostname: "host2",
DetailUpdatedAt: time.Now(),
LabelUpdatedAt: time.Now(),
PolicyUpdatedAt: time.Now(),
SeenTime: time.Now(),
})
require.NoError(t, err)
// create a custom installer email for h1
dms, err := ds.SetOrUpdateCustomHostDeviceMapping(ctx, h1.ID, "a@b.c", fleet.DeviceMappingCustomInstaller)
require.NoError(t, err)
assertHostDeviceMapping(t, dms, []*fleet.HostDeviceMapping{{Email: "a@b.c", Source: fleet.DeviceMappingCustomReplacement}})
// custom installer can be updated
dms, err = ds.SetOrUpdateCustomHostDeviceMapping(ctx, h1.ID, "b@b.c", fleet.DeviceMappingCustomInstaller)
require.NoError(t, err)
assertHostDeviceMapping(t, dms, []*fleet.HostDeviceMapping{{Email: "b@b.c", Source: fleet.DeviceMappingCustomReplacement}})
// set a custom override, custom installer is removed
dms, err = ds.SetOrUpdateCustomHostDeviceMapping(ctx, h1.ID, "c@b.c", fleet.DeviceMappingCustomOverride)
require.NoError(t, err)
assertHostDeviceMapping(t, dms, []*fleet.HostDeviceMapping{{Email: "c@b.c", Source: fleet.DeviceMappingCustomReplacement}})
// updating the custom installer is now ignored
dms, err = ds.SetOrUpdateCustomHostDeviceMapping(ctx, h1.ID, "d@b.c", fleet.DeviceMappingCustomInstaller)
require.NoError(t, err)
assertHostDeviceMapping(t, dms, []*fleet.HostDeviceMapping{{Email: "c@b.c", Source: fleet.DeviceMappingCustomReplacement}})
// updating the custom override works
dms, err = ds.SetOrUpdateCustomHostDeviceMapping(ctx, h1.ID, "e@b.c", fleet.DeviceMappingCustomOverride)
require.NoError(t, err)
assertHostDeviceMapping(t, dms, []*fleet.HostDeviceMapping{{Email: "e@b.c", Source: fleet.DeviceMappingCustomReplacement}})
// set some unrelated emails for h2
err = ds.ReplaceHostDeviceMapping(ctx, h2.ID, []*fleet.HostDeviceMapping{
{HostID: h2.ID, Email: "a@c.d", Source: fleet.DeviceMappingGoogleChromeProfiles},
{HostID: h2.ID, Email: "b@c.d", Source: fleet.DeviceMappingGoogleChromeProfiles},
}, fleet.DeviceMappingGoogleChromeProfiles)
require.NoError(t, err)
// create a custom override immediately, without a custom installer
_, err = ds.SetOrUpdateCustomHostDeviceMapping(ctx, h2.ID, "c@c.d", fleet.DeviceMappingCustomOverride)
require.NoError(t, err)
// adding a custom installer is ignored
dms, err = ds.SetOrUpdateCustomHostDeviceMapping(ctx, h2.ID, "d@c.d", fleet.DeviceMappingCustomInstaller)
require.NoError(t, err)
assertHostDeviceMapping(t, dms, []*fleet.HostDeviceMapping{
{Email: "a@c.d", Source: fleet.DeviceMappingGoogleChromeProfiles},
{Email: "b@c.d", Source: fleet.DeviceMappingGoogleChromeProfiles},
{Email: "c@c.d", Source: fleet.DeviceMappingCustomReplacement},
})
// updating the custom override works
dms, err = ds.SetOrUpdateCustomHostDeviceMapping(ctx, h2.ID, "e@c.d", fleet.DeviceMappingCustomOverride)
require.NoError(t, err)
assertHostDeviceMapping(t, dms, []*fleet.HostDeviceMapping{
{Email: "a@c.d", Source: fleet.DeviceMappingGoogleChromeProfiles},
{Email: "b@c.d", Source: fleet.DeviceMappingGoogleChromeProfiles},
{Email: "e@c.d", Source: fleet.DeviceMappingCustomReplacement},
})
// deleting the host deletes the mappings
err = ds.DeleteHost(ctx, h2.ID)
require.NoError(t, err)
dms, err = ds.ListHostDeviceMapping(ctx, h2.ID)
require.NoError(t, err)
require.Empty(t, dms)
// other host was left untouched
dms, err = ds.ListHostDeviceMapping(ctx, h1.ID)
require.NoError(t, err)
assertHostDeviceMapping(t, dms, []*fleet.HostDeviceMapping{{Email: "e@b.c", Source: fleet.DeviceMappingCustomReplacement}})
}
func assertHostDeviceMapping(t *testing.T, got, want []*fleet.HostDeviceMapping) { func assertHostDeviceMapping(t *testing.T, got, want []*fleet.HostDeviceMapping) {
t.Helper() t.Helper()

View file

@ -535,14 +535,14 @@ func (ds *Datastore) ListHostsInLabel(ctx context.Context, filter fleet.TeamFilt
failingPoliciesJoin = "" failingPoliciesJoin = ""
} }
deviceMappingJoin := `LEFT JOIN ( deviceMappingJoin := fmt.Sprintf(`LEFT JOIN (
SELECT SELECT
host_id, host_id,
CONCAT('[', GROUP_CONCAT(JSON_OBJECT('email', email, 'source', source)), ']') AS device_mapping CONCAT('[', GROUP_CONCAT(JSON_OBJECT('email', email, 'source', %s)), ']') AS device_mapping
FROM FROM
host_emails host_emails
GROUP BY GROUP BY
host_id) dm ON dm.host_id = h.id` host_id) dm ON dm.host_id = h.id`, deviceMappingTranslateSourceColumn(""))
if !opt.DeviceMapping { if !opt.DeviceMapping {
deviceMappingJoin = "" deviceMappingJoin = ""
} }
@ -605,7 +605,7 @@ func (ds *Datastore) CountHostsInLabel(ctx context.Context, filter fleet.TeamFil
query := `SELECT count(*) FROM label_membership lm query := `SELECT count(*) FROM label_membership lm
JOIN hosts h ON (lm.host_id = h.id) JOIN hosts h ON (lm.host_id = h.id)
LEFT JOIN host_seen_times hst ON (h.id=hst.host_id) LEFT JOIN host_seen_times hst ON (h.id=hst.host_id)
LEFT JOIN host_disks hd ON (h.id=hd.host_id) LEFT JOIN host_disks hd ON (h.id=hd.host_id)
` `
query += hostMDMJoin query += hostMDMJoin

View file

@ -269,6 +269,9 @@ type Datastore interface {
CountHosts(ctx context.Context, filter TeamFilter, opt HostListOptions) (int, error) CountHosts(ctx context.Context, filter TeamFilter, opt HostListOptions) (int, error)
CountHostsInLabel(ctx context.Context, filter TeamFilter, lid uint, opt HostListOptions) (int, error) CountHostsInLabel(ctx context.Context, filter TeamFilter, lid uint, opt HostListOptions) (int, error)
ListHostDeviceMapping(ctx context.Context, id uint) ([]*HostDeviceMapping, error) ListHostDeviceMapping(ctx context.Context, id uint) ([]*HostDeviceMapping, error)
// SetOrUpdateCustomHostDeviceMapping replaces the custom email address
// associated with the host with the provided one.
SetOrUpdateCustomHostDeviceMapping(ctx context.Context, hostID uint, email, source string) ([]*HostDeviceMapping, error)
// ListHostBatteries returns the list of batteries for the given host ID. // ListHostBatteries returns the list of batteries for the given host ID.
ListHostBatteries(ctx context.Context, id uint) ([]*HostBattery, error) ListHostBatteries(ctx context.Context, id uint) ([]*HostBattery, error)

View file

@ -836,6 +836,18 @@ func ExpandPlatform(platform string) []string {
} }
} }
// List of valid sources for HostDeviceMapping (host_emails table in the
// database).
const (
DeviceMappingGoogleChromeProfiles = "google_chrome_profiles"
DeviceMappingMDMIdpAccounts = "mdm_idp_accounts"
DeviceMappingCustomInstaller = "custom_installer" // set by fleetd via device-authenticated API
DeviceMappingCustomOverride = "custom_override" // set by user via user-authenticated API
DeviceMappingCustomPrefix = "custom_" // if host_emails.source starts with this, replace with DeviceMappingCustomReplacement
DeviceMappingCustomReplacement = "custom" // replaces a source that starts with CustomPrefix - in the UI, we want to display those as only "custom"
)
// HostDeviceMapping represents a mapping of a user email address to a host, // HostDeviceMapping represents a mapping of a user email address to a host,
// as reported by the specified source (e.g. Google Chrome Profiles). // as reported by the specified source (e.g. Google Chrome Profiles).
type HostDeviceMapping struct { type HostDeviceMapping struct {

View file

@ -359,6 +359,11 @@ type Service interface {
// ListHostDeviceMapping returns the list of device-mapping of user's email address // ListHostDeviceMapping returns the list of device-mapping of user's email address
// for the host. // for the host.
ListHostDeviceMapping(ctx context.Context, id uint) ([]*HostDeviceMapping, error) ListHostDeviceMapping(ctx context.Context, id uint) ([]*HostDeviceMapping, error)
// SetCustomHostDeviceMapping sets the custom email address associated with
// the host, which is either set by the fleetd installer at startup (via a
// device-authenticated API), or manually by the user (via the
// user-authenticated API).
SetCustomHostDeviceMapping(ctx context.Context, hostID uint, email string) ([]*HostDeviceMapping, error)
// ListDevicePolicies lists all policies for the given host, including passing / failing summaries // ListDevicePolicies lists all policies for the given host, including passing / failing summaries
ListDevicePolicies(ctx context.Context, host *Host) ([]*HostPolicy, error) ListDevicePolicies(ctx context.Context, host *Host) ([]*HostPolicy, error)

View file

@ -204,6 +204,8 @@ type CountHostsInLabelFunc func(ctx context.Context, filter fleet.TeamFilter, li
type ListHostDeviceMappingFunc func(ctx context.Context, id uint) ([]*fleet.HostDeviceMapping, error) type ListHostDeviceMappingFunc func(ctx context.Context, id uint) ([]*fleet.HostDeviceMapping, error)
type SetOrUpdateCustomHostDeviceMappingFunc func(ctx context.Context, hostID uint, email string, source string) ([]*fleet.HostDeviceMapping, error)
type ListHostBatteriesFunc func(ctx context.Context, id uint) ([]*fleet.HostBattery, error) type ListHostBatteriesFunc func(ctx context.Context, id uint) ([]*fleet.HostBattery, error)
type LoadHostByDeviceAuthTokenFunc func(ctx context.Context, authToken string, tokenTTL time.Duration) (*fleet.Host, error) type LoadHostByDeviceAuthTokenFunc func(ctx context.Context, authToken string, tokenTTL time.Duration) (*fleet.Host, error)
@ -1064,6 +1066,9 @@ type DataStore struct {
ListHostDeviceMappingFunc ListHostDeviceMappingFunc ListHostDeviceMappingFunc ListHostDeviceMappingFunc
ListHostDeviceMappingFuncInvoked bool ListHostDeviceMappingFuncInvoked bool
SetOrUpdateCustomHostDeviceMappingFunc SetOrUpdateCustomHostDeviceMappingFunc
SetOrUpdateCustomHostDeviceMappingFuncInvoked bool
ListHostBatteriesFunc ListHostBatteriesFunc ListHostBatteriesFunc ListHostBatteriesFunc
ListHostBatteriesFuncInvoked bool ListHostBatteriesFuncInvoked bool
@ -2588,6 +2593,13 @@ func (s *DataStore) ListHostDeviceMapping(ctx context.Context, id uint) ([]*flee
return s.ListHostDeviceMappingFunc(ctx, id) return s.ListHostDeviceMappingFunc(ctx, id)
} }
func (s *DataStore) SetOrUpdateCustomHostDeviceMapping(ctx context.Context, hostID uint, email string, source string) ([]*fleet.HostDeviceMapping, error) {
s.mu.Lock()
s.SetOrUpdateCustomHostDeviceMappingFuncInvoked = true
s.mu.Unlock()
return s.SetOrUpdateCustomHostDeviceMappingFunc(ctx, hostID, email, source)
}
func (s *DataStore) ListHostBatteries(ctx context.Context, id uint) ([]*fleet.HostBattery, error) { func (s *DataStore) ListHostBatteries(ctx context.Context, id uint) ([]*fleet.HostBattery, error) {
s.mu.Lock() s.mu.Lock()
s.ListHostBatteriesFuncInvoked = true s.ListHostBatteriesFuncInvoked = true

View file

@ -495,6 +495,10 @@ func (e *authEndpointer) GET(path string, f handlerFunc, v interface{}) {
e.handleEndpoint(path, f, v, "GET") e.handleEndpoint(path, f, v, "GET")
} }
func (e *authEndpointer) PUT(path string, f handlerFunc, v interface{}) {
e.handleEndpoint(path, f, v, "PUT")
}
func (e *authEndpointer) PATCH(path string, f handlerFunc, v interface{}) { func (e *authEndpointer) PATCH(path string, f handlerFunc, v interface{}) {
e.handleEndpoint(path, f, v, "PATCH") e.handleEndpoint(path, f, v, "PATCH")
} }

View file

@ -383,6 +383,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
ue.POST("/api/_version_/fleet/hosts/transfer/filter", addHostsToTeamByFilterEndpoint, addHostsToTeamByFilterRequest{}) ue.POST("/api/_version_/fleet/hosts/transfer/filter", addHostsToTeamByFilterEndpoint, addHostsToTeamByFilterRequest{})
ue.POST("/api/_version_/fleet/hosts/{id:[0-9]+}/refetch", refetchHostEndpoint, refetchHostRequest{}) ue.POST("/api/_version_/fleet/hosts/{id:[0-9]+}/refetch", refetchHostEndpoint, refetchHostRequest{})
ue.GET("/api/_version_/fleet/hosts/{id:[0-9]+}/device_mapping", listHostDeviceMappingEndpoint, listHostDeviceMappingRequest{}) ue.GET("/api/_version_/fleet/hosts/{id:[0-9]+}/device_mapping", listHostDeviceMappingEndpoint, listHostDeviceMappingRequest{})
ue.PUT("/api/_version_/fleet/hosts/{id:[0-9]+}/device_mapping", putHostDeviceMappingEndpoint, putHostDeviceMappingRequest{})
ue.GET("/api/_version_/fleet/hosts/report", hostsReportEndpoint, hostsReportRequest{}) ue.GET("/api/_version_/fleet/hosts/report", hostsReportEndpoint, hostsReportRequest{})
ue.GET("/api/_version_/fleet/os_versions", osVersionsEndpoint, osVersionsRequest{}) ue.GET("/api/_version_/fleet/os_versions", osVersionsEndpoint, osVersionsRequest{})
ue.GET("/api/_version_/fleet/hosts/{id:[0-9]+}/queries/{query_id:[0-9]+}", getHostQueryReportEndpoint, getHostQueryReportRequest{}) ue.GET("/api/_version_/fleet/hosts/{id:[0-9]+}/queries/{query_id:[0-9]+}", getHostQueryReportEndpoint, getHostQueryReportRequest{})
@ -652,6 +653,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
// endpoints are POST due to passing the device token in the JSON body. // endpoints are POST due to passing the device token in the JSON body.
oe.POST("/api/fleet/orbit/scripts/request", getOrbitScriptEndpoint, orbitGetScriptRequest{}) oe.POST("/api/fleet/orbit/scripts/request", getOrbitScriptEndpoint, orbitGetScriptRequest{})
oe.POST("/api/fleet/orbit/scripts/result", postOrbitScriptResultEndpoint, orbitPostScriptResultRequest{}) oe.POST("/api/fleet/orbit/scripts/result", postOrbitScriptResultEndpoint, orbitPostScriptResultRequest{})
oe.PUT("/api/fleet/orbit/device_mapping", putOrbitDeviceMappingEndpoint, orbitPutDeviceMappingRequest{})
oeWindowsMDM := oe.WithCustomMiddleware(mdmConfiguredMiddleware.VerifyWindowsMDM()) oeWindowsMDM := oe.WithCustomMiddleware(mdmConfiguredMiddleware.VerifyWindowsMDM())
oeWindowsMDM.POST("/api/fleet/orbit/disk_encryption_key", postOrbitDiskEncryptionKeyEndpoint, orbitPostDiskEncryptionKeyRequest{}) oeWindowsMDM.POST("/api/fleet/orbit/disk_encryption_key", postOrbitDiskEncryptionKeyEndpoint, orbitPostDiskEncryptionKeyRequest{})

View file

@ -1278,6 +1278,57 @@ func (svc *Service) ListHostDeviceMapping(ctx context.Context, id uint) ([]*flee
return svc.ds.ListHostDeviceMapping(ctx, id) return svc.ds.ListHostDeviceMapping(ctx, id)
} }
////////////////////////////////////////////////////////////////////////////////
// Put Custom Host Device Mapping
////////////////////////////////////////////////////////////////////////////////
type putHostDeviceMappingRequest struct {
ID uint `url:"id"`
Email string `json:"email"`
}
type putHostDeviceMappingResponse struct {
HostID uint `json:"host_id"`
DeviceMapping []*fleet.HostDeviceMapping `json:"device_mapping"`
Err error `json:"error,omitempty"`
}
func (r putHostDeviceMappingResponse) error() error { return r.Err }
func putHostDeviceMappingEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
req := request.(*putHostDeviceMappingRequest)
dms, err := svc.SetCustomHostDeviceMapping(ctx, req.ID, req.Email)
if err != nil {
return putHostDeviceMappingResponse{Err: err}, nil
}
return putHostDeviceMappingResponse{HostID: req.ID, DeviceMapping: dms}, nil
}
func (svc *Service) SetCustomHostDeviceMapping(ctx context.Context, hostID uint, email string) ([]*fleet.HostDeviceMapping, error) {
isInstallerSource := svc.authz.IsAuthenticatedWith(ctx, authzctx.AuthnOrbitToken)
if !isInstallerSource {
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
return nil, err
}
host, err := svc.ds.HostLite(ctx, hostID)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "get host")
}
// Authorize again with team loaded now that we have team_id
if err := svc.authz.Authorize(ctx, host, fleet.ActionWrite); err != nil {
return nil, err
}
}
source := fleet.DeviceMappingCustomOverride
if isInstallerSource {
source = fleet.DeviceMappingCustomInstaller
}
return svc.ds.SetOrUpdateCustomHostDeviceMapping(ctx, hostID, email, source)
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// MDM // MDM
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View file

@ -582,6 +582,9 @@ func TestHostAuth(t *testing.T) {
ds.ListHostsLiteByIDsFunc = func(ctx context.Context, ids []uint) ([]*fleet.Host, error) { ds.ListHostsLiteByIDsFunc = func(ctx context.Context, ids []uint) ([]*fleet.Host, error) {
return nil, nil return nil, nil
} }
ds.SetOrUpdateCustomHostDeviceMappingFunc = func(ctx context.Context, hostID uint, email, source string) ([]*fleet.HostDeviceMapping, error) {
return nil, nil
}
testCases := []struct { testCases := []struct {
name string name string
@ -615,6 +618,14 @@ func TestHostAuth(t *testing.T) {
true, true,
false, false,
}, },
{
"team admin, belongs to team",
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleAdmin}}},
true,
true,
false,
false,
},
{ {
"team maintainer, belongs to team", "team maintainer, belongs to team",
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleMaintainer}}}, &fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleMaintainer}}},
@ -631,6 +642,14 @@ func TestHostAuth(t *testing.T) {
true, true,
false, false,
}, },
{
"team admin, DOES NOT belong to team",
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 2}, Role: fleet.RoleAdmin}}},
true,
true,
true,
true,
},
{ {
"team maintainer, DOES NOT belong to team", "team maintainer, DOES NOT belong to team",
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 2}, Role: fleet.RoleMaintainer}}}, &fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 2}, Role: fleet.RoleMaintainer}}},
@ -694,6 +713,12 @@ func TestHostAuth(t *testing.T) {
err = svc.RefetchHost(ctx, 1) err = svc.RefetchHost(ctx, 1)
checkAuthErr(t, tt.shouldFailTeamRead, err) checkAuthErr(t, tt.shouldFailTeamRead, err)
_, err = svc.SetCustomHostDeviceMapping(ctx, 1, "a@b.c")
checkAuthErr(t, tt.shouldFailTeamWrite, err)
_, err = svc.SetCustomHostDeviceMapping(ctx, 2, "a@b.c")
checkAuthErr(t, tt.shouldFailGlobalWrite, err)
}) })
} }

View file

@ -3023,6 +3023,7 @@ func (s *integrationTestSuite) TestHostDeviceMapping() {
t := s.T() t := s.T()
ctx := context.Background() ctx := context.Background()
orbitHost := createOrbitEnrolledHost(t, "windows", "device_mapping", s.ds)
hosts := s.createHosts(t) hosts := s.createHosts(t)
// get host device mappings of invalid host // get host device mappings of invalid host
@ -3033,21 +3034,32 @@ func (s *integrationTestSuite) TestHostDeviceMapping() {
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/device_mapping", hosts[0].ID), nil, http.StatusOK, &listResp) s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/device_mapping", hosts[0].ID), nil, http.StatusOK, &listResp)
require.Len(t, listResp.DeviceMapping, 0) require.Len(t, listResp.DeviceMapping, 0)
// create some mappings // create a custom mapping of a non-existing host
var putResp putHostDeviceMappingResponse
s.DoJSON("PUT", fmt.Sprintf("/api/latest/fleet/hosts/%d/device_mapping", hosts[2].ID+1), nil, http.StatusNotFound, &putResp)
// create some google mappings
require.NoError(t, s.ds.ReplaceHostDeviceMapping(ctx, hosts[0].ID, []*fleet.HostDeviceMapping{ require.NoError(t, s.ds.ReplaceHostDeviceMapping(ctx, hosts[0].ID, []*fleet.HostDeviceMapping{
{HostID: hosts[0].ID, Email: "a@b.c", Source: "google_chrome_profiles"}, {HostID: hosts[0].ID, Email: "a@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
{HostID: hosts[0].ID, Email: "b@b.c", Source: "google_chrome_profiles"}, {HostID: hosts[0].ID, Email: "b@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
}, "google_chrome_profiles")) }, fleet.DeviceMappingGoogleChromeProfiles))
// create a custom mapping
s.DoJSON("PUT", fmt.Sprintf("/api/latest/fleet/hosts/%d/device_mapping", hosts[0].ID), putHostDeviceMappingRequest{Email: "c@b.c"}, http.StatusOK, &putResp)
require.Equal(t, hosts[0].ID, putResp.HostID)
require.ElementsMatch(t, putResp.DeviceMapping, []*fleet.HostDeviceMapping{
{Email: "a@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
{Email: "b@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
{Email: "c@b.c", Source: fleet.DeviceMappingCustomReplacement},
})
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/device_mapping", hosts[0].ID), nil, http.StatusOK, &listResp) s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/device_mapping", hosts[0].ID), nil, http.StatusOK, &listResp)
require.Len(t, listResp.DeviceMapping, 2)
require.Equal(t, "a@b.c", listResp.DeviceMapping[0].Email)
require.Equal(t, "google_chrome_profiles", listResp.DeviceMapping[0].Source)
require.Zero(t, listResp.DeviceMapping[0].HostID)
require.Equal(t, "b@b.c", listResp.DeviceMapping[1].Email)
require.Equal(t, "google_chrome_profiles", listResp.DeviceMapping[1].Source)
require.Zero(t, listResp.DeviceMapping[1].HostID)
require.Equal(t, hosts[0].ID, listResp.HostID) require.Equal(t, hosts[0].ID, listResp.HostID)
require.ElementsMatch(t, listResp.DeviceMapping, []*fleet.HostDeviceMapping{
{Email: "a@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
{Email: "b@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
{Email: "c@b.c", Source: fleet.DeviceMappingCustomReplacement},
})
// other host still has none // other host still has none
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/device_mapping", hosts[1].ID), nil, http.StatusOK, &listResp) s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/device_mapping", hosts[1].ID), nil, http.StatusOK, &listResp)
@ -3056,7 +3068,7 @@ func (s *integrationTestSuite) TestHostDeviceMapping() {
var listHosts listHostsResponse var listHosts listHostsResponse
// list hosts response includes device mappings // list hosts response includes device mappings
s.DoJSON("GET", "/api/latest/fleet/hosts?device_mapping=true", nil, http.StatusOK, &listHosts) s.DoJSON("GET", "/api/latest/fleet/hosts?device_mapping=true", nil, http.StatusOK, &listHosts)
require.Len(t, listHosts.Hosts, 3) require.Len(t, listHosts.Hosts, len(hosts)+1)
hostsByID := make(map[uint]fleet.HostResponse) hostsByID := make(map[uint]fleet.HostResponse)
for _, h := range listHosts.Hosts { for _, h := range listHosts.Hosts {
hostsByID[h.ID] = h hostsByID[h.ID] = h
@ -3069,20 +3081,31 @@ func (s *integrationTestSuite) TestHostDeviceMapping() {
err := json.Unmarshal(*hostsByID[host1.ID].DeviceMapping, &dm) err := json.Unmarshal(*hostsByID[host1.ID].DeviceMapping, &dm)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, dm, 2) require.ElementsMatch(t, dm, []*fleet.HostDeviceMapping{
{Email: "a@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
var emails []string {Email: "b@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
for _, e := range dm { {Email: "c@b.c", Source: fleet.DeviceMappingCustomReplacement},
emails = append(emails, e.Email) })
}
assert.Contains(t, emails, "a@b.c")
assert.Contains(t, emails, "b@b.c")
assert.Equal(t, "google_chrome_profiles", dm[0].Source)
assert.Equal(t, "google_chrome_profiles", dm[1].Source)
// no device mapping for other hosts // no device mapping for other hosts
assert.Nil(t, hostsByID[hosts[1].ID].DeviceMapping) assert.Nil(t, hostsByID[hosts[1].ID].DeviceMapping)
assert.Nil(t, hostsByID[hosts[2].ID].DeviceMapping) assert.Nil(t, hostsByID[hosts[2].ID].DeviceMapping)
assert.Nil(t, hostsByID[orbitHost.ID].DeviceMapping)
// update custom email for hosts[0]
s.DoJSON("PUT", fmt.Sprintf("/api/latest/fleet/hosts/%d/device_mapping", hosts[0].ID), putHostDeviceMappingRequest{Email: "d@b.c"}, http.StatusOK, &putResp)
require.Equal(t, hosts[0].ID, putResp.HostID)
require.ElementsMatch(t, putResp.DeviceMapping, []*fleet.HostDeviceMapping{
{Email: "a@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
{Email: "b@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
{Email: "d@b.c", Source: fleet.DeviceMappingCustomReplacement},
})
// create a custom_installer email for orbit host
s.Do("PUT", "/api/fleet/orbit/device_mapping", orbitPutDeviceMappingRequest{
OrbitNodeKey: *orbitHost.OrbitNodeKey,
Email: "e@b.c",
}, http.StatusOK)
// search host by email address finds the corresponding host // search host by email address finds the corresponding host
s.DoJSON("GET", "/api/latest/fleet/hosts?device_mapping=true", nil, http.StatusOK, &listHosts, "query", "a@b.c") s.DoJSON("GET", "/api/latest/fleet/hosts?device_mapping=true", nil, http.StatusOK, &listHosts, "query", "a@b.c")
@ -3092,17 +3115,41 @@ func (s *integrationTestSuite) TestHostDeviceMapping() {
err = json.Unmarshal(*listHosts.Hosts[0].DeviceMapping, &dm) err = json.Unmarshal(*listHosts.Hosts[0].DeviceMapping, &dm)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, dm, 2) require.ElementsMatch(t, putResp.DeviceMapping, []*fleet.HostDeviceMapping{
{Email: "a@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
{Email: "b@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
{Email: "d@b.c", Source: fleet.DeviceMappingCustomReplacement},
})
for _, e := range dm { // search host by the custom email address finds the corresponding host
emails = append(emails, e.Email) s.DoJSON("GET", "/api/latest/fleet/hosts?device_mapping=true", nil, http.StatusOK, &listHosts, "query", "d@b.c")
} require.Len(t, listHosts.Hosts, 1)
assert.Contains(t, emails, "a@b.c") require.Equal(t, hosts[0].ID, listHosts.Hosts[0].ID)
assert.Contains(t, emails, "b@b.c")
assert.Equal(t, "google_chrome_profiles", dm[0].Source)
assert.Equal(t, "google_chrome_profiles", dm[1].Source)
s.DoJSON("GET", "/api/latest/fleet/hosts?device_mapping=true", nil, http.StatusOK, &listHosts, "query", "c@b.c") s.DoJSON("GET", "/api/latest/fleet/hosts?device_mapping=true", nil, http.StatusOK, &listHosts, "query", "e@b.c")
require.Len(t, listHosts.Hosts, 1)
require.Equal(t, orbitHost.ID, listHosts.Hosts[0].ID)
// override the custom email for the orbit host
s.DoJSON("PUT", fmt.Sprintf("/api/latest/fleet/hosts/%d/device_mapping", orbitHost.ID), putHostDeviceMappingRequest{Email: "f@b.c"}, http.StatusOK, &putResp)
// update the custom_installer email for orbit host, will get ignored (because a custom_override exists)
s.Do("PUT", "/api/fleet/orbit/device_mapping", orbitPutDeviceMappingRequest{
OrbitNodeKey: *orbitHost.OrbitNodeKey,
Email: "g@b.c",
}, http.StatusOK)
// searching by the old custom installer email doesn't work anymore
s.DoJSON("GET", "/api/latest/fleet/hosts?device_mapping=true", nil, http.StatusOK, &listHosts, "query", "e@b.c")
require.Len(t, listHosts.Hosts, 0)
// searching by the new custom email address finds it
s.DoJSON("GET", "/api/latest/fleet/hosts?device_mapping=true", nil, http.StatusOK, &listHosts, "query", "f@b.c")
require.Len(t, listHosts.Hosts, 1)
require.Equal(t, orbitHost.ID, listHosts.Hosts[0].ID)
// searching by a never-used email returns nothing
s.DoJSON("GET", "/api/latest/fleet/hosts?device_mapping=true", nil, http.StatusOK, &listHosts, "query", "Z@b.c")
require.Len(t, listHosts.Hosts, 0) require.Len(t, listHosts.Hosts, 0)
} }

View file

@ -27,9 +27,11 @@ func (s *integrationTestSuite) TestDeviceAuthenticatedEndpoints() {
// create some mappings and MDM/Munki data // create some mappings and MDM/Munki data
require.NoError(t, s.ds.ReplaceHostDeviceMapping(context.Background(), hosts[0].ID, []*fleet.HostDeviceMapping{ require.NoError(t, s.ds.ReplaceHostDeviceMapping(context.Background(), hosts[0].ID, []*fleet.HostDeviceMapping{
{HostID: hosts[0].ID, Email: "a@b.c", Source: "google_chrome_profiles"}, {HostID: hosts[0].ID, Email: "a@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
{HostID: hosts[0].ID, Email: "b@b.c", Source: "google_chrome_profiles"}, {HostID: hosts[0].ID, Email: "b@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
}, "google_chrome_profiles")) }, fleet.DeviceMappingGoogleChromeProfiles))
_, err = s.ds.SetOrUpdateCustomHostDeviceMapping(context.Background(), hosts[0].ID, "c@b.c", fleet.DeviceMappingCustomInstaller)
require.NoError(t, err)
require.NoError(t, s.ds.SetOrUpdateMDMData(context.Background(), hosts[0].ID, false, true, "url", false, "", "")) require.NoError(t, s.ds.SetOrUpdateMDMData(context.Background(), hosts[0].ID, false, true, "url", false, "", ""))
require.NoError(t, s.ds.SetOrUpdateMunkiInfo(context.Background(), hosts[0].ID, "1.3.0", nil, nil)) require.NoError(t, s.ds.SetOrUpdateMunkiInfo(context.Background(), hosts[0].ID, "1.3.0", nil, nil))
// create a battery for hosts[0] // create a battery for hosts[0]
@ -107,7 +109,12 @@ func (s *integrationTestSuite) TestDeviceAuthenticatedEndpoints() {
require.NoError(t, json.NewDecoder(res.Body).Decode(&listDMResp)) require.NoError(t, json.NewDecoder(res.Body).Decode(&listDMResp))
require.NoError(t, res.Body.Close()) require.NoError(t, res.Body.Close())
require.Equal(t, hosts[0].ID, listDMResp.HostID) require.Equal(t, hosts[0].ID, listDMResp.HostID)
require.Len(t, listDMResp.DeviceMapping, 2) require.Len(t, listDMResp.DeviceMapping, 3)
require.ElementsMatch(t, listDMResp.DeviceMapping, []*fleet.HostDeviceMapping{
{Email: "a@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
{Email: "b@b.c", Source: fleet.DeviceMappingGoogleChromeProfiles},
{Email: "c@b.c", Source: fleet.DeviceMappingCustomReplacement},
})
devDMs := listDMResp.DeviceMapping devDMs := listDMResp.DeviceMapping
// compare response with standard list device mapping API for that same host // compare response with standard list device mapping API for that same host

View file

@ -6730,7 +6730,7 @@ func (s *integrationMDMTestSuite) TestSSO() {
} }
source, ok := sourceByEmail["sso_user@example.com"] source, ok := sourceByEmail["sso_user@example.com"]
require.True(t, ok) require.True(t, ok)
require.Equal(t, "mdm_idp_accounts", source) require.Equal(t, fleet.DeviceMappingMDMIdpAccounts, source)
source, ok = sourceByEmail["g1@example.com"] source, ok = sourceByEmail["g1@example.com"]
require.True(t, ok) require.True(t, ok)
require.Equal(t, "google_chrome_profiles", source) require.Equal(t, "google_chrome_profiles", source)
@ -6762,7 +6762,7 @@ func (s *integrationMDMTestSuite) TestSSO() {
s.DoJSON("GET", fmt.Sprintf("/api/v1/fleet/hosts/%d/device_mapping", hostResp.Host.ID), nil, http.StatusOK, &dmResp) s.DoJSON("GET", fmt.Sprintf("/api/v1/fleet/hosts/%d/device_mapping", hostResp.Host.ID), nil, http.StatusOK, &dmResp)
require.Len(t, dmResp.DeviceMapping, 1) require.Len(t, dmResp.DeviceMapping, 1)
require.Equal(t, "sso_user@example.com", dmResp.DeviceMapping[0].Email) require.Equal(t, "sso_user@example.com", dmResp.DeviceMapping[0].Email)
require.Equal(t, "mdm_idp_accounts", dmResp.DeviceMapping[0].Source) require.Equal(t, fleet.DeviceMappingMDMIdpAccounts, dmResp.DeviceMapping[0].Source)
hostsResp = listHostsResponse{} hostsResp = listHostsResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/v1/fleet/hosts?query=%s&device_mapping=true", url.QueryEscape("sso_user@example.com")), nil, http.StatusOK, &hostsResp) s.DoJSON("GET", fmt.Sprintf("/api/v1/fleet/hosts?query=%s&device_mapping=true", url.QueryEscape("sso_user@example.com")), nil, http.StatusOK, &hostsResp)
require.Len(t, hostsResp.Hosts, 1) require.Len(t, hostsResp.Hosts, 1)
@ -6773,7 +6773,7 @@ func (s *integrationMDMTestSuite) TestSSO() {
require.NoError(t, json.Unmarshal(*gotHost.DeviceMapping, &dm)) require.NoError(t, json.Unmarshal(*gotHost.DeviceMapping, &dm))
require.Len(t, dm, 1) require.Len(t, dm, 1)
require.Equal(t, "sso_user@example.com", dm[0].Email) require.Equal(t, "sso_user@example.com", dm[0].Email)
require.Equal(t, "mdm_idp_accounts", dm[0].Source) require.Equal(t, fleet.DeviceMappingMDMIdpAccounts, dm[0].Source)
// enrolling a different user works without problems // enrolling a different user works without problems
res = s.LoginMDMSSOUser("sso_user2", "user123#") res = s.LoginMDMSSOUser("sso_user2", "user123#")

View file

@ -519,6 +519,44 @@ func (svc *Service) SaveHostScriptResult(ctx context.Context, result *fleet.Host
return fleet.ErrMissingLicense return fleet.ErrMissingLicense
} }
/////////////////////////////////////////////////////////////////////////////////
// Post Orbit device mapping (custom email)
/////////////////////////////////////////////////////////////////////////////////
type orbitPutDeviceMappingRequest struct {
OrbitNodeKey string `json:"orbit_node_key"`
Email string `json:"email"`
}
// interface implementation required by the OrbitClient
func (r *orbitPutDeviceMappingRequest) setOrbitNodeKey(nodeKey string) {
r.OrbitNodeKey = nodeKey
}
// interface implementation required by orbit authentication
func (r *orbitPutDeviceMappingRequest) orbitHostNodeKey() string {
return r.OrbitNodeKey
}
type orbitPutDeviceMappingResponse struct {
Err error `json:"error,omitempty"`
}
func (r orbitPutDeviceMappingResponse) error() error { return r.Err }
func putOrbitDeviceMappingEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
req := request.(*orbitPutDeviceMappingRequest)
host, ok := hostctx.FromContext(ctx)
if !ok {
err := newOsqueryError("internal error: missing host from request context")
return orbitPutDeviceMappingResponse{Err: err}, nil
}
_, err := svc.SetCustomHostDeviceMapping(ctx, host.ID, req.Email)
return orbitPutDeviceMappingResponse{Err: err}, nil
}
///////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////
// Post Orbit disk encryption key // Post Orbit disk encryption key
///////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////

View file

@ -1079,10 +1079,10 @@ func directIngestChromeProfiles(ctx context.Context, logger log.Logger, host *fl
mapping = append(mapping, &fleet.HostDeviceMapping{ mapping = append(mapping, &fleet.HostDeviceMapping{
HostID: host.ID, HostID: host.ID,
Email: row["email"], Email: row["email"],
Source: "google_chrome_profiles", Source: fleet.DeviceMappingGoogleChromeProfiles,
}) })
} }
return ds.ReplaceHostDeviceMapping(ctx, host.ID, mapping, "google_chrome_profiles") return ds.ReplaceHostDeviceMapping(ctx, host.ID, mapping, fleet.DeviceMappingGoogleChromeProfiles)
} }
func directIngestBattery(ctx context.Context, logger log.Logger, host *fleet.Host, ds fleet.Datastore, rows []map[string]string) error { func directIngestBattery(ctx context.Context, logger log.Logger, host *fleet.Host, ds fleet.Datastore, rows []map[string]string) error {