mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 21:47:20 +00:00
Closes: https://github.com/fleetdm/fleet/issues/34358 Changes: - Updated Android enterprise proxy endpoints to log an additional warning to alert us if we exceed the Android management API rate limit.
210 lines
9.2 KiB
JavaScript
Vendored
210 lines
9.2 KiB
JavaScript
Vendored
module.exports = {
|
|
|
|
|
|
friendlyName: 'Create android enterprise',
|
|
|
|
|
|
description: 'Creates a new Android enterprise from a request from a Fleet instance.',
|
|
|
|
|
|
inputs: {
|
|
signupUrlName: {
|
|
type: 'string',
|
|
required: true,
|
|
},
|
|
enterpriseToken: {
|
|
type: 'string',
|
|
required: true,
|
|
},
|
|
fleetLicenseKey: {
|
|
type: 'string',
|
|
},
|
|
pubsubPushUrl: {
|
|
type: 'string',
|
|
required: true,
|
|
},
|
|
enterprise: {
|
|
type: {},
|
|
required: true,
|
|
moreInfoUrl: ''
|
|
}
|
|
},
|
|
|
|
|
|
exits: {
|
|
success: { description: 'An android enterprise was successfully created' },
|
|
enterpriseAlreadyExists: { description: 'An android enterprise already exists for this Fleet instance.', statusCode: 409 },
|
|
missingOriginHeader: { description: 'The request was missing an Origin header', responseType: 'badRequest'},
|
|
invalidEnterpriseToken: {
|
|
description: 'The provided enterprise token is invalid or expired.',
|
|
responseType: 'badRequest'
|
|
}
|
|
},
|
|
|
|
|
|
fn: async function ({signupUrlName, enterpriseToken, fleetLicenseKey, pubsubPushUrl, enterprise}) {
|
|
|
|
// Parse the Fleet server url from the origin header.
|
|
let fleetServerUrl = this.req.get('Origin');
|
|
if(!fleetServerUrl){
|
|
throw 'missingOriginHeader';
|
|
}
|
|
// Check the database for a record of this enterprise.
|
|
let connectionforThisInstanceExists = await AndroidEnterprise.findOne({fleetServerUrl: fleetServerUrl});
|
|
// If this request came from a Fleet instance that already has an enterprise set up, return an error.
|
|
if(connectionforThisInstanceExists) {
|
|
throw 'enterpriseAlreadyExists';
|
|
}
|
|
// Generate a uuid to use for the pubsub topic name for this Android enterprise.
|
|
let newPubSubTopicName = 'a' + sails.helpers.strings.uuid();// Google requires that topic names start with a letter, so we'll preprend an 'a' to the generated uuid.
|
|
// Build the full pubsub topic name.
|
|
let fullPubSubTopicName = `projects/${sails.config.custom.androidEnterpriseProjectId}/topics/${newPubSubTopicName}`;
|
|
enterprise.pubsubTopic = fullPubSubTopicName;
|
|
let newSubscriptionName = `projects/${sails.config.custom.androidEnterpriseProjectId}/subscriptions/${newPubSubTopicName}`;
|
|
|
|
// Complete the setup of the new Android enterprise.
|
|
// Note: We're using sails.helpers.flow.build here to handle any errors that occurr using google's node library.
|
|
let newEnterprise = await sails.helpers.flow.build(async ()=>{
|
|
let { google } = require('googleapis');
|
|
let androidmanagement = google.androidmanagement('v1');
|
|
let googleAuth = new google.auth.GoogleAuth({
|
|
scopes: [
|
|
'https://www.googleapis.com/auth/androidmanagement',
|
|
'https://www.googleapis.com/auth/pubsub'
|
|
],
|
|
credentials: {
|
|
client_email: sails.config.custom.androidEnterpriseServiceAccountEmailAddress,// eslint-disable-line camelcase
|
|
private_key: sails.config.custom.androidEnterpriseServiceAccountPrivateKey,// eslint-disable-line camelcase
|
|
},
|
|
});
|
|
// Acquire the google auth client, and bind it to all future calls
|
|
let authClient = await googleAuth.getClient();
|
|
let pubsub = google.pubsub({version: 'v1', auth: authClient});
|
|
|
|
// Create a new pubsub topic for this enterprise.
|
|
// [?]: https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.topics/create
|
|
await pubsub.projects.topics.create({
|
|
name: fullPubSubTopicName,
|
|
requestBody: {
|
|
messageRetentionDuration: '86400s'// 24 hours
|
|
},
|
|
auth: authClient,
|
|
});
|
|
|
|
// Debugging attempt - Give it a second before calling the getIamPolicy (plus excessive back-off retry delays.)
|
|
await sails.helpers.flow.pause(1000);
|
|
|
|
|
|
// [?]: https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.topics/getIamPolicy
|
|
// Retrieve the IAM policy for the created pubsub topic.
|
|
const newPubSubTopicIamPolicy = await sails.helpers.flow.build(async () => {
|
|
const policy = await pubsub.projects.topics.getIamPolicy({
|
|
resource: fullPubSubTopicName,
|
|
auth: authClient,
|
|
});
|
|
|
|
return policy.data;
|
|
}).retry(undefined, [1000, 2000, 4000]);
|
|
|
|
// Grant Android device policy the right to publish
|
|
// See: https://developers.google.com/android/management/notifications
|
|
// Default the policy bindings to an empty array if it is not set.
|
|
newPubSubTopicIamPolicy.bindings = newPubSubTopicIamPolicy.bindings || [];
|
|
// Add the Fleet android MDM service account to the policy bindings.
|
|
newPubSubTopicIamPolicy.bindings.push({
|
|
role: 'roles/pubsub.publisher',
|
|
members: ['serviceAccount:[email protected]']
|
|
});
|
|
|
|
// Update the pubsub topic's IAM policy
|
|
// [?]: https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.topics/setIamPolicy
|
|
await sails.helpers.flow.build(async () => {
|
|
await pubsub.projects.topics.setIamPolicy({
|
|
resource: fullPubSubTopicName,
|
|
requestBody: {
|
|
policy: newPubSubTopicIamPolicy
|
|
},
|
|
auth: authClient,
|
|
});
|
|
}).retry(undefined, [1000, 1500, 2000]);
|
|
|
|
// Create a new subscription for the created pubsub topic.
|
|
// [?]: https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.subscriptions/create
|
|
await pubsub.projects.subscriptions.create({
|
|
name: newSubscriptionName,
|
|
requestBody: {
|
|
topic: fullPubSubTopicName,
|
|
ackDeadlineSeconds: 60,
|
|
messageRetentionDuration: '86400s',// 24 hours
|
|
expirationPolicy: {}, // never expire, so that customers can enable Android but actually enroll devices months later
|
|
pushConfig: {
|
|
pushEndpoint: pubsubPushUrl// Use the pubsubPushUrl provided by the Fleet server.
|
|
}
|
|
},
|
|
auth: authClient,
|
|
});
|
|
|
|
// Now create the new enterprise for this Fleet server.
|
|
// [?]: https://googleapis.dev/nodejs/googleapis/latest/androidmanagement/classes/Resource$Enterprises.html#create
|
|
let createEnterpriseResponse = await androidmanagement.enterprises.create({
|
|
agreementAccepted: true,
|
|
enterpriseToken: enterpriseToken,
|
|
projectId: sails.config.custom.androidEnterpriseProjectId,
|
|
signupUrlName: signupUrlName,
|
|
requestBody: enterprise,
|
|
auth: authClient,
|
|
});
|
|
return createEnterpriseResponse.data;
|
|
}).intercept({status: 400}, (err)=>{
|
|
// Check if it's specifically an invalid token error
|
|
let errorString = err.toString();
|
|
if (errorString.includes('INVALID_ENTERPRISE_TOKEN') ||
|
|
errorString.includes('ExpiredTokenException')) {
|
|
return {'invalidEnterpriseToken': 'The provided enterprise token is invalid or expired.'};
|
|
}
|
|
|
|
sails.log.warn('Error details when creating Android enterprise with Android Management API (from 400):', require('util').inspect(err));
|
|
|
|
// For other 400 errors, still return as invalid token (client error)
|
|
return {'invalidEnterpriseToken': 'Invalid request to Android Management API.'};
|
|
}).intercept({ status: 401 }, (err) => {
|
|
sails.log.warn('Error details when creating Android enterprise with Android Management API (from 401):', require('util').inspect(err));
|
|
return {'invalidEnterpriseToken': 'Authorization failed with Android Management API.'};
|
|
}).intercept({status: 403}, (err)=>{
|
|
sails.log.warn('Error details when creating Android enterprise with Android Management API (from 403):', require('util').inspect(err));
|
|
return {'invalidEnterpriseToken': 'Access forbidden to Android Management API.'};
|
|
}).intercept({status: 429}, (err)=>{
|
|
// If the Android management API returns a 429 response, log an additional warning that will trigger a help-p1 alert.
|
|
sails.log.warn(`p1: Android management API rate limit exceeded!`);
|
|
return new Error(`When attempting to create a new Android enterprise, an error occurred. Error: ${require('util').inspect(err)}`);
|
|
}).intercept((err)=>{
|
|
// For all other errors (5XX, network errors, etc.), maintain existing behavior
|
|
return new Error(`When attempting to create a new Android enterprise, an error occurred. Error: ${require('util').inspect(err)}`);
|
|
});
|
|
|
|
|
|
let newAndroidEnterpriseId = newEnterprise.name;
|
|
// Create a new fleetServerSecret for this Fleet server. This will be included in the response body and will be required in all subsequent requests to Android proxy endpoints.
|
|
let newFleetServerSecret = await sails.helpers.strings.random.with({len: 30});
|
|
// Update the database record to include details about the created enterprise.
|
|
await AndroidEnterprise.create({
|
|
fleetServerUrl: fleetServerUrl,
|
|
fleetLicenseKey: fleetLicenseKey,
|
|
androidEnterpriseId: newAndroidEnterpriseId.replace(/enterprises\//, ''),// Remove the /enterprises prefix from the androidEnterpriseId that we save in the website database.
|
|
pubsubTopicName: fullPubSubTopicName,
|
|
pubsubSubscriptionName: newSubscriptionName,
|
|
fleetServerSecret: newFleetServerSecret,
|
|
})
|
|
.intercept('E_UNIQUE', 'enterpriseAlreadyExists');
|
|
|
|
|
|
|
|
return {
|
|
name: newAndroidEnterpriseId,
|
|
fleetServerSecret: newFleetServerSecret,
|
|
};
|
|
|
|
}
|
|
|
|
|
|
};
|