mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 00:49:03 +00:00
Changes: - Created a new database model: `MicrosoftComplianceTenant`. A model that stores information about complaince tenants - Added `/policies/is-cloud-customer`: a policy that blocks requests to microsoft proxy endpoints if a `MS API KEY` header is missing or does not match a new config variable (`sails.custom.config.cloudCustomerCompliancePartnerSharedSecret`) - Added `microsoft-proxy/create-compliance-partner-tenant`: an action that creates a database record for a new compliance tenant and generates an API key that is used to authenticate future requests to microsoft proxy endpoints for an entra tenant. - Added `microsoft-proxy/get-compliance-partner-settings`: an action that returns information about Fleet's complaince partner entra application and the entra tenant's admin consent status (whether or not a tenant's entra admin has granted permissions to Fleet's compliance partner application) - Added `microsoft-proxy/get-tenants-admin-consent-status`: an action that updates the admin consent status of a compliance tenant record. - Added `microsoft-proxy/setup-compliance-partner-tenant`: an action that provisions a compliance tenant, creates a complaince policy for macOS devices assigns the created policy to the built-in "All users" user group on the tenants entra instance. - Added `microsoft-proxy/update-one-devices-compliance-status`: an action that receives information about a device on a compliance tenant's Fleet instance, sends that information to their Entra instance, and returns the messsage ID returned by the asynchronus Entra API. - Added `microsoft-proxy/get-one-compliance-status-result`: an action that returns the result of a compliance status update from the Entra API. - Added `sails.helpers.microsoft-proxy.get-access-token-and-api-urls` A helper that gets an access token for a tenant's entra instance and the URLs of the API endpoints the microsoft proxy actions use for a tenant. - Added `scripts/send-entra-heartbeat-requests` A script that will run daily to keep all microsoft compliance integrations provisioned. - --------- Co-authored-by: Lucas Rodriguez <[email protected]>
149 lines
5.1 KiB
JavaScript
Vendored
149 lines
5.1 KiB
JavaScript
Vendored
module.exports = {
|
|
|
|
|
|
friendlyName: 'Receive one devices compliance status',
|
|
|
|
|
|
description: 'Receives compliance information about a device and sends it to a tenant\'s Entra instance for processing.',
|
|
|
|
|
|
inputs: {
|
|
entraTenantId: {
|
|
type: 'string',
|
|
required: true,
|
|
},
|
|
fleetServerSecret: {
|
|
type: 'string',
|
|
requried: true,
|
|
},
|
|
deviceId: {
|
|
type: 'string',
|
|
description: 'The devices ID in entra',
|
|
required: true,
|
|
},
|
|
deviceManagementState: {
|
|
type: 'boolean',
|
|
description: 'Whether or not this device is enrolled in an MDM.',
|
|
required: true,
|
|
},
|
|
deviceName: {
|
|
type: 'string',
|
|
description: 'The device\'s display name in Fleet.',
|
|
required: true,
|
|
},
|
|
os: {
|
|
type: 'string',
|
|
required: true,
|
|
},
|
|
osVersion: {
|
|
type: 'string',
|
|
required: true,
|
|
},
|
|
userPrincipalName: {
|
|
type: 'string',
|
|
required: true,
|
|
},
|
|
compliant: {
|
|
type: 'boolean',
|
|
required: true,
|
|
},
|
|
lastCheckInTime: {
|
|
type: 'number',
|
|
description: 'The device\'s last sync with Fleet in Unix seconds',
|
|
required: true,
|
|
},
|
|
},
|
|
|
|
|
|
exits: {
|
|
success: { description: 'A devices compliance status was successfully sent to an entra tenants instance'},
|
|
},
|
|
|
|
|
|
fn: async function ({entraTenantId, fleetServerSecret, deviceId, deviceManagementState, deviceName, os, osVersion, userPrincipalName, compliant, lastCheckInTime}) {
|
|
|
|
|
|
let informationAboutThisTenant = await MicrosoftComplianceTenant.findOne({entraTenantId: entraTenantId, fleetServerSecret: fleetServerSecret});
|
|
if(!informationAboutThisTenant) {
|
|
return new Error({error: 'No MicrosoftComplianceTenant record was found that matches the provided entra_tenant_id and fleet_server_secret combination.'});
|
|
}
|
|
|
|
let tokenAndApiUrls = await sails.helpers.microsoftProxy.getAccessTokenAndApiUrls.with({
|
|
complianceTenantRecordId: informationAboutThisTenant.id
|
|
});
|
|
let graphAccessToken = tokenAndApiUrls.graphAccessToken;
|
|
let accessToken = tokenAndApiUrls.manageApiAccessToken;
|
|
let deviceDataSyncUrl = tokenAndApiUrls.deviceDataSyncUrl;
|
|
|
|
// [?]: https://learn.microsoft.com/en-us/graph/api/resources/users
|
|
// Get the GUID for this user using the UserPrincipalName
|
|
let informationAboutThisUser = await sails.helpers.http.get.with({
|
|
url: `https://graph.microsoft.com/v1.0/users('${userPrincipalName}')`,
|
|
headers: {
|
|
'Authorization': `Bearer ${graphAccessToken}`
|
|
}
|
|
}).intercept((err)=>{
|
|
return new Error({error: `An error occurred when getting a user ID from a user principal name (${userPrincipalName}) for a complaince status update. Full error: ${require('util').inspect(err, {depth: 3})}`});
|
|
});
|
|
|
|
if(!informationAboutThisUser.id) {
|
|
return new Error({error: `An error occurred when getting information about a user (${userPrincipalName}). The response from the Microsoft graph API did not include an ID.`});
|
|
}
|
|
|
|
let lastUpdateTime = new Date().toISOString();
|
|
|
|
// Build the compliance report for this device:
|
|
let complianceUpdateContent = [
|
|
{
|
|
EntityType: 1, // EntityType 1 = Device inventory data.
|
|
TenantId: informationAboutThisTenant.entraTenantId,
|
|
DeviceManagementState: deviceManagementState ? 'managed' : 'notManaged',
|
|
DeviceId: deviceId,
|
|
DeviceName: deviceName,
|
|
UserId: informationAboutThisUser.id,
|
|
// LastCheckInTime is a global timestamp indicating the time of device sync with partner service.
|
|
LastCheckInTime: new Date(lastCheckInTime * 1000).toISOString(),
|
|
// LastUpdateTime is a global timestamp indicating the order of messages.
|
|
LastUpdateTime: lastUpdateTime,
|
|
Os: os,
|
|
OsVersion: osVersion,
|
|
EasIds: [],// This field is required but can be sent as an empty array.
|
|
State: 0,
|
|
},
|
|
{
|
|
EntityType: 4, // EntityType 4 = compliance data
|
|
TenantId: informationAboutThisTenant.entraTenantId,
|
|
DeviceId: deviceId,
|
|
UserId: informationAboutThisUser.id,
|
|
// LastUpdateTime is a global timestamp indicating the order of messages.
|
|
LastUpdateTime: lastUpdateTime,
|
|
complianceStatus: compliant ? 'compliant' : 'notCompliant',
|
|
}
|
|
];
|
|
|
|
// Generate a UUID for this compliance update.
|
|
let messageId = sails.helpers.strings.uuid();
|
|
|
|
await sails.helpers.http.sendHttpRequest.with({
|
|
method: 'PUT',
|
|
url: `${deviceDataSyncUrl}/DataUploadMessages(guid'${encodeURIComponent(messageId)}')?api-version=1.2`,
|
|
headers: {
|
|
'Authorization': `Bearer ${accessToken}`
|
|
},
|
|
body: {
|
|
TenantId: informationAboutThisTenant.entraTenantId,
|
|
UploadTime: new Date().toISOString(),
|
|
Content: JSON.stringify(complianceUpdateContent),
|
|
}
|
|
}).intercept((err)=>{
|
|
return new Error({error: `An error occurred when sending a request to sync a device's compliance status for a Microsoft compliance tenant. Full error: ${require('util').inspect(err, {depth: 3})}`});
|
|
});
|
|
|
|
|
|
return {
|
|
message_id: messageId,// eslint-disable-line camelcase
|
|
};
|
|
}
|
|
|
|
|
|
};
|