Skip to content

Commit

Permalink
feat: Auto rotate site bucket keys #153
Browse files Browse the repository at this point in the history
  • Loading branch information
apburnes committed Oct 27, 2023
1 parent 7345776 commit 0b96f83
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 3 deletions.
5 changes: 5 additions & 0 deletions api/models/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ module.exports = (sequelize, DataTypes) => {
type: DataTypes.STRING,
allowNull: false,
},
awsBucketKeyUpdatedAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
config: {
type: DataTypes.JSONB,
defaultValue: {},
Expand Down
44 changes: 44 additions & 0 deletions api/services/SiteBucketKeyRotator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const { Site } = require('../models');
const CFApiClient = require('../utils/cfApiClient');

async function rotateBucketKey(site) {
const cfApi = new CFApiClient();
const serviceName = site.s3ServiceName;
const serviceBindingName = `${serviceName}-key`;
const serviceInstance = await cfApi.fetchServiceInstance(serviceName);
const credentials = await cfApi
.fetchCredentialBindingsInstance(serviceBindingName)
.catch((error) => {
// Return null to skip credentials delete if not found
// fetchCredentialBindingsInstance throws error
// with name string starting "Not found" when credentials
// do not exist
if (error.name.toLowerCase().trim().startswith('not found')) {
return null;
}

throw error;
});

if (credentials) {
// Delete existing credential service if they exist
await cfApi.deleteServiceInstanceCredentials(credentials.guid);
await cfApi.sleep('3000');
}

await cfApi.createServiceKey(serviceInstance.name, serviceInstance.guid);
}

async function rotateSitesBucketKeys(limit = 20) {
const sites = await Site.findAll({
order: [['awsBucketKeyUpdatedAt', 'ASC']],
limit,
});

return Promise.allSettled(sites.map(site => rotateBucketKey(site)));
}

module.exports = {
rotateBucketKey,
rotateSitesBucketKeys,
};
12 changes: 10 additions & 2 deletions api/utils/cfApiClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,20 @@ class CloudFoundryAPIClient {
}

fetchCredentialBindingsInstance(name) {
const path = `/v3/service_credential_bindings?names=${name}`;
const endpoint = `/v3/service_credential_bindings?names=${name}`;

return this.accessToken()
.then(token => this.request('GET', path, token))
.then(token => this.request('GET', endpoint, token))
.then(res => findEntity(res, name));
}

deleteServiceInstanceCredentials(guid) {
const endpoint = `/v3/service_credential_bindings/${guid}`;

return this.accessToken()
.then(token => this.request('DELETE', endpoint, token));
}

fetchServiceInstanceCredentials(name) {
return this.accessToken()
.then(token => this.request(
Expand Down Expand Up @@ -332,5 +339,6 @@ CloudFoundryAPIClient.findEntity = findEntity;
CloudFoundryAPIClient.firstEntity = firstEntity;
CloudFoundryAPIClient.objToQueryParams = objToQueryParams;
CloudFoundryAPIClient.buildRequestBody = buildRequestBody;
CloudFoundryAPIClient.sleep = sleep;

module.exports = CloudFoundryAPIClient;
7 changes: 7 additions & 0 deletions ci/partials/rotate-bucket-keys.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
platform: linux
inputs: [name: src]
outputs: [name: src]
run:
dir: src/admin-client
path: bash
args: [-c, yarn rotate-bucket-keys]
24 changes: 24 additions & 0 deletions ci/pipeline-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,30 @@ jobs:
<<: *env-cf
CF_APP_NAME: pages-queues-ui-((deploy-env))

- name: nightly-site-bucket-key-rotator
plan:
- get: src
resource: pr-((deploy-env))
passed: [set-pipeline]
# - get: nightly
# trigger: true
- get: node
- task: install-deps-admin-client
file: src/ci/partials/install-deps-api
image: node
- task: rotate-keys
platform: linux
inputs: [name: src]
outputs: [name: src]
run:
dir: src/admin-client
path: bash
args: [-c, yarn rotate-bucket-keys]
params:
<<: *env-cf
CF_APP_NAME: pages-((deploy-env))


- name: set-pipeline
plan:
- get: src
Expand Down
15 changes: 15 additions & 0 deletions migrations/20231027154728-site-aws-bucket-key-updated-at.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const TABLE = 'site';
const COLUMN_NAME = 'awsBucketKeyUpdatedAt';
const COLUMN_TYPE = {
type: 'timestamp',
notNull: true,
defaultValue: new String('now()'),
};

exports.up = async (db) => {
await db.addColumn(TABLE, COLUMN_NAME, COLUMN_TYPE);
};

exports.down = async (db) => {
await db.removeColumn(TABLE, COLUMN_NAME);
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@
"migrate-build-notification-settings": "node ./scripts/migrate-build-notification-settings.js",
"remove-bucket-website-configs": "node ./scripts/remove-bucket-website-configs.js",
"check-object-paths": "node ./scripts/check-object-paths.js",
"queued-builds-check": "node ./scripts/queued-builds-check.js"
"queued-builds-check": "node ./scripts/queued-builds-check.js",
"rotate-bucket-keys": "node ./scripts/rotate-bucket-keys.js"
},
"main": "index.js",
"repository": {
Expand Down
15 changes: 15 additions & 0 deletions scripts/rotate-bucket-keys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const {
rotateSitesBucketKeys,
} = require('../api/services/SiteBucketKeyRotator');

async function main() {
const rotatedKeyServices = await rotateSitesBucketKeys();

// eslint-disable-next-line
rotatedKeyServices.map(result => {
// eslint-disable-next-line
console.log(JSON.stringify(result, null, 2));
});
}

main();

0 comments on commit 0b96f83

Please sign in to comment.