fleet/website/scripts/send-entra-heartbeat-requests.js
Eric 13eeebe548
Website: Add Microsoft compliance proxy endpoints. (#27403)
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]>
2025-06-11 13:01:36 -05:00

92 lines
4.1 KiB
JavaScript
Vendored

module.exports = {
friendlyName: 'Send entra heartbeat requests',
description: 'Sends heartbeat requests to Microsoft compliance tenants to keep the integration active.',
fn: async function () {
// Find all MicrosoftComplianceTenant records with setupComplete: true
let allActiveEntraTenants = await MicrosoftComplianceTenant.find({setupComplete: true});
sails.log('Syncing hearbeat requests for '+allActiveEntraTenants.length+(allActiveEntraTenants.length > 1 ? ' tenants.' : ' tenant.'));
// Create an empty object to store caught errors. We don't want this script to stop running if there is an error with a single entra tenant, so instead, we'll store any errors that occur and bail early for that tenant if any occur, and we'll log them individually before the script is done.
let errorReportById = {};
await sails.helpers.flow.simultaneouslyForEach(allActiveEntraTenants, async (entraTenant)=>{
let connectionIdAsString = String(entraTenant.id);
let tokenAndApiUrls = await sails.helpers.microsoftProxy.getAccessTokenAndApiUrls.with({
complianceTenantRecordId: entraTenant.id
}).tolerate((err)=>{
errorReportById[connectionIdAsString] = new Error(`Could not get an access token and API urls for a MicrosoftComplianceTenant (id: ${connectionIdAsString}). Full error: ${err}`);
});
if(errorReportById[connectionIdAsString]){// If there was an error with the previous request, bail early for this Entra tenant.
return;
}
let accessToken = tokenAndApiUrls.manageApiAccessToken;
let tenantDataSyncUrl = tokenAndApiUrls.tenantDataSyncUrl;
// Send a heartbeat request.
let tenantHeartbeatResponse = await sails.helpers.http.sendHtttpRequest.with({
method: 'PUT',
url: `${tenantDataSyncUrl}/PartnerTenantHeartbeat(guid'${encodeURIComponent(entraTenant.entraTenantId)}')?api-version=1.6`,
headers: {
'Authorization': `Bearer ${accessToken}`
},
body: {
Timestamp: new Date().toISOString(),
}
}).tolerate((err)=>{
errorReportById[connectionIdAsString] = new Error(`Could not send a heartbeat request for a MicrosoftComplianceTenant (id: ${connectionIdAsString}). Full error: ${err}`);
});
if(errorReportById[connectionIdAsString]){// If there was an error with the previous request, bail early for this Entra tenant.
return;
}
let parsedtenantHeartbeatResponse;
try {
parsedtenantHeartbeatResponse = JSON.parse(tenantHeartbeatResponse);
} catch(err){
errorReportById[connectionIdAsString] = new Error(`Could not parse the JSON response of a heartbeat request for a Microsoft compliance tenant (id: ${connectionIdAsString}). Full error: ${err}`);
}
if(errorReportById[connectionIdAsString]){// If there was an error with the previous request, bail early for this Entra tenant.
return;
}
if(parsedtenantHeartbeatResponse.ResyncTimestamp){
// TODO: do we want to do anything about the resync timestamp if it is set?
}
await MicrosoftComplianceTenant.updateOne({id: entraTenant.id}).with({
lastHeartbeatAt: Date.now(),
});
});
let numberOfLoggedErrors = 0;
// After we've sent requests for all active Entra tenants, log any errors that occured.
for (let connectionIdAsString of Object.keys(errorReportById)) {
if (false === errorReportById[connectionIdAsString]) {
// If a heartbeat was sent successfully, do nothing.
} else {
// If an error was logged for a entra tenant, log the error, and increment the numberOfLoggedErrors
numberOfLoggedErrors++;
sails.log.warn('p1: An error occurred while sending a heartbeat request for a Microsfot entra compliance tenant with the id'+connectionIdAsString+'. Logged error:\n'+errorReportById[connectionIdAsString]);
}
}//∞
sails.log('Heartbeat requests have been sent for '+(allActiveEntraTenants.length - numberOfLoggedErrors)+(allActiveEntraTenants.length - numberOfLoggedErrors > 1 || numberOfLoggedErrors === allActiveEntraTenants.length ? ' tenants.' : ' tenant.'));
}
};