mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 21:47:20 +00:00
Changes: - Updated the create-vanta-authorization-request action to return a `fleetInstanceNotResponding` response if requests fail with a 404 status code.
186 lines
9.2 KiB
JavaScript
Vendored
186 lines
9.2 KiB
JavaScript
Vendored
module.exports = {
|
|
|
|
|
|
friendlyName: 'Create Vanta authorization request',
|
|
|
|
|
|
description: 'Returns a URL used to authorize requests to the user\'s Vanta account from fleetdm.com on behalf of the user.',
|
|
|
|
|
|
inputs: {
|
|
emailAddress: {
|
|
type: 'string',
|
|
required: true,
|
|
isEmail: true,
|
|
},
|
|
fleetInstanceUrl: {
|
|
type: 'string',
|
|
required: true,
|
|
},
|
|
fleetApiKey: {
|
|
type: 'string',
|
|
required: true,
|
|
},
|
|
redirectToExternalPageAfterAuthorization: {
|
|
type: 'string',
|
|
description: 'If provided, the user will be sent to this URL after they complete the setup of this integration'
|
|
},
|
|
sharedSecret: {
|
|
type: 'string',
|
|
description: 'A shared secret used to verify external requests to this endpoint.',
|
|
extendedDescription: 'This input is used only when this action runs at the "/api/v1/create-external-vanta-authorization-request" endpoint'
|
|
}
|
|
},
|
|
|
|
|
|
exits: {
|
|
success: {
|
|
outputType: 'string'
|
|
},
|
|
connectionAlreadyExists: {
|
|
description: 'The Fleet instance url provided is already connected to a Vanta account.',
|
|
statusCode: 409,
|
|
},
|
|
fleetInstanceNotResponding: {
|
|
description: 'A http request to the user\'s Fleet instance failed.',
|
|
statusCode: 404,
|
|
},
|
|
invalidToken: {
|
|
description: 'The provided token for the api-only user could not be used to authorize requests from fleetdm.com',
|
|
statusCode: 401,
|
|
},
|
|
requestToFleetInstanceForbidden: {
|
|
description: 'The user\'s Fleet instance returned a "403 Forbidden" response',
|
|
statusCode: 403,
|
|
},
|
|
invalidLicense: {
|
|
description: 'The Fleet instance provided is using a Free license.',
|
|
statusCode: 400,
|
|
},
|
|
invalidResponseFromFleetInstance: {
|
|
description: 'The response body from the Fleet API was invalid.',
|
|
statusCode: 400,
|
|
},
|
|
nonApiOnlyUser: {
|
|
description: 'The provided API token for this Fleet instance is not associated with an api-only user.',
|
|
statusCode: 400,
|
|
},
|
|
insufficientPermissions:{
|
|
description: 'The api-only user associated with the provided token does not have the propper permissions to query the users endpoint.',
|
|
statusCode: 403,
|
|
},
|
|
missingOrInvalidSharedSecret: {
|
|
description: 'The request to set up a Vanta integration has an invalid shared secret',
|
|
statusCode: 401
|
|
}
|
|
},
|
|
|
|
fn: async function (inputs) {
|
|
require('assert')(sails.config.custom.sharedSecretForExternalVantaRequests);
|
|
if(this.req.url === '/api/v1/create-external-vanta-authorization-request' && inputs.sharedSecret !== sails.config.custom.sharedSecretForExternalVantaRequests) {
|
|
throw 'missingOrInvalidSharedSecret';
|
|
}
|
|
|
|
let url = require('url');
|
|
|
|
// Look for any existing VantaConnection records that use this fleet instance URL.
|
|
let existingConnectionRecord = await VantaConnection.findOne({fleetInstanceUrl: inputs.fleetInstanceUrl});
|
|
|
|
// Generate the `state` string for this request.
|
|
let generatedStateForThisRequest = await sails.helpers.strings.random.with({len: 10});
|
|
|
|
// Generate a sourceId for this user. This value will be used as the indentifier of ther user's vanta connection
|
|
let generatedSourceIdSuffix = await sails.helpers.strings.random.with({len: 20, style: 'url-friendly'});
|
|
let sourceIDForThisRequest = 'fleet_'+generatedSourceIdSuffix;
|
|
|
|
if(existingConnectionRecord) {
|
|
// If an active Vanta connection exists for the provided Fleet instance url, we'll throw a 'connectionAlreadyExists' exit, and the user will be asked to contact us to make changes to the existing vanta connection.
|
|
if(existingConnectionRecord.isConnectedToVanta) {
|
|
throw 'connectionAlreadyExists';
|
|
} else if(existingConnectionRecord.fleetApiKey !== inputs.fleetApiKey && existingConnectionRecord.emailAddress !== inputs.emailAddress) {
|
|
// If an incomplete connection exists, and the API token and email address provided do not match. The user will be asked to contact us to make changes to their connection.
|
|
throw 'connectionAlreadyExists';
|
|
} else {
|
|
// If an inactive and incomplete Vanta connection exists that uses the same API token and email address, we'll use the sourceId from that record for this request.
|
|
sourceIDForThisRequest = existingConnectionRecord.vantaSourceId;
|
|
}
|
|
}
|
|
|
|
|
|
// Check the fleet instance url and API key provided
|
|
let responseFromFleetInstance = await sails.helpers.http.get(inputs.fleetInstanceUrl+'/api/v1/fleet/me',{},{'Authorization': 'Bearer ' +inputs.fleetApiKey})
|
|
.intercept('requestFailed', 'fleetInstanceNotResponding')
|
|
.intercept({raw: {statusCode: 401}}, 'invalidToken')
|
|
.intercept({raw: {statusCode: 403}}, 'requestToFleetInstanceForbidden')
|
|
.intercept({raw: {statusCode: 404}}, 'fleetInstanceNotResponding')
|
|
.intercept((error)=>{
|
|
return new Error(`When sending a request to a user's (${inputs.emailAddress}) Fleet instance's (${inputs.fleetInstanceUrl}) /me endpoint to verify that a token meets the requirements for a Vanta connection, an error occurred: ${error}`);
|
|
});
|
|
|
|
// Throw an error if the response from the Fleet instance's /me API endpoint does not contain a user.
|
|
if(!responseFromFleetInstance.user){
|
|
throw 'invalidResponseFromFleetInstance';
|
|
}
|
|
|
|
// Throw an error if the provided API token is not an API-only user.
|
|
if(!responseFromFleetInstance.user.api_only) {
|
|
throw 'nonApiOnlyUser';
|
|
}
|
|
|
|
// If the API-only user associated with the token provided does not have the admin role, we'll throw an error.
|
|
// We require an admin token so we can send Vanta data about all of the active user accounts on the requesting user's Fleet instance
|
|
if(responseFromFleetInstance.user.global_role !== 'admin') {
|
|
throw 'insufficientPermissions';
|
|
}
|
|
|
|
// Send a request to the provided Fleet instance's /config endpoint to check their license tier.
|
|
let configResponse = await sails.helpers.http.get(inputs.fleetInstanceUrl+'/api/v1/fleet/config', {}, {'Authorization': 'Bearer ' +inputs.fleetApiKey})
|
|
.intercept('requestFailed','fleetInstanceNotResponding')
|
|
.intercept({raw: {statusCode: 401}}, 'invalidToken')
|
|
.intercept({raw: {statusCode: 403}}, 'requestToFleetInstanceForbidden')
|
|
.intercept({raw: {statusCode: 404}}, 'fleetInstanceNotResponding')
|
|
.intercept((error)=>{
|
|
return new Error(`When sending a request to a user's (email: ${inputs.emailAddress}) Fleet instance's (${inputs.fleetInstanceUrl}) /config API endpoint for a Vanta connection, an error occurred: ${error}`);
|
|
});
|
|
|
|
|
|
// Throw an error if the response from the Fleet instance's /config API endpoint does not contain a license.
|
|
if(!configResponse.license){
|
|
throw 'invalidResponseFromFleetInstance';
|
|
}
|
|
|
|
// If the user's Fleet instance has a free license, we'll throw the 'invalidLicense' exit and let the user know that this is only available for Fleet Premium subscribers.
|
|
if(configResponse.license.tier === 'free') {
|
|
throw 'invalidLicense';
|
|
}
|
|
|
|
// If we're not using an existing vantaConnection record for this request, we'll create a new one.
|
|
if(!existingConnectionRecord) {
|
|
// Create the VantaConnection record for this request.
|
|
await VantaConnection.create({
|
|
emailAddress: inputs.emailAddress,
|
|
vantaSourceId: sourceIDForThisRequest,
|
|
fleetInstanceUrl: inputs.fleetInstanceUrl,
|
|
fleetApiKey: inputs.fleetApiKey,
|
|
});
|
|
}
|
|
// Build the authorization URL for this request.
|
|
let vantaAuthorizationRequestURL = `https://app.vanta.com/oauth/authorize?client_id=${encodeURIComponent(sails.config.custom.vantaAuthorizationClientId)}&scope=connectors.self:write-resource connectors.self:read-resource&state=${encodeURIComponent(generatedStateForThisRequest)}&source_id=${encodeURIComponent(sourceIDForThisRequest)}&redirect_uri=${encodeURIComponent(url.resolve(sails.config.custom.baseUrl, '/vanta-authorization'))}&response_type=code`;
|
|
|
|
if(inputs.redirectToExternalPageAfterAuthorization){
|
|
let internalRedirectUrl = `${sails.config.custom.baseUrl}/redirect-vanta-authorization-request?vantaSourceId=${encodeURIComponent(sourceIDForThisRequest)}&state=${encodeURIComponent(generatedStateForThisRequest)}&vantaAuthorizationRequestURL=${encodeURIComponent(vantaAuthorizationRequestURL)}&redirectAfterSetup=${encodeURIComponent(inputs.redirectToExternalPageAfterAuthorization)}`;
|
|
|
|
return internalRedirectUrl;
|
|
// If the useInternalRedirect input was provided, we'll return the URL of an internal endpoiint that will set the required cookies for this request.
|
|
} else {
|
|
// Otherwise, if this request came from a user on the connect-vanta page, we'll set the cookies are redirect them directly to Vanta.
|
|
// Set a `state` cookie on the user's browser. This value will be checked against a query parameter when the user returns to fleetdm.com.
|
|
this.res.cookie('state', generatedStateForThisRequest, {signed: true});
|
|
// Set the sourceId to a cookie, we'll use this value to find the database record we created for this request when the user returns to fleetdm.com.
|
|
this.res.cookie('vantaSourceId', sourceIDForThisRequest, {signed: true});
|
|
return vantaAuthorizationRequestURL;
|
|
}
|
|
}
|
|
|
|
|
|
};
|