Skip to content

Commit

Permalink
chore: Encrypt site build params #4464
Browse files Browse the repository at this point in the history
  • Loading branch information
apburnes committed May 15, 2024
1 parent 0b044b0 commit f5ddd02
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 29 deletions.
1 change: 1 addition & 0 deletions .cloudgov/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ applications:
- pages-((env))-redis
- pages-((env))-proxy
- pages-((env))-domain
- pages-((env))-encryption
- federalist-((env))-uev-key
- federalist-deploy-user
- federalist-site-wide-error
Expand Down
16 changes: 15 additions & 1 deletion api/services/Encryptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,18 @@ function encrypt(value, key, { hintSize = 4 } = {}) {
return { ciphertext, hint };
}

module.exports = { ALGORITHM, encrypt };
function decrypt(ciphertext, key) {
const hashedKey = Crypto.createHash('sha256').update(key).digest();
const [authTagHex, ivHex, encrypted] = ciphertext.split(':');

const iv = Buffer.from(ivHex, 'hex');
const authTag = Buffer.from(authTagHex, 'hex');

const decipher = Crypto.createDecipheriv(ALGORITHM, hashedKey, iv);

decipher.setAuthTag(authTag);

return decipher.update(encrypted, 'hex', 'utf8') + decipher.final('utf8');
}

module.exports = { ALGORITHM, encrypt, decrypt };
9 changes: 7 additions & 2 deletions api/utils/cfApiClient.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const _ = require('underscore');
const parse = require('json-templates');
const { app: { appEnv } } = require('../../config');
const { app: { appEnv }, encryption } = require('../../config');
const CloudFoundryAuthClient = require('./cfAuthClient');
const Encryptor = require('../services/Encryptor');
const HttpClient = require('./httpClient');
const { wait } = require('.');

Expand Down Expand Up @@ -244,7 +245,11 @@ class CloudFoundryAPIClient {
(acc, current) => ({ ...acc, [current.name]: current.value }),
{}
);
const command = `cd app && python main.py -p '${JSON.stringify(commandParams)}'`;
const encryptedParams = Encryptor.encrypt(
JSON.stringify(commandParams),
encryption.key
);
const command = `cd app && python main.py -p '${encryptedParams.ciphertext}'`;

const taskParams = {
...containerSettings,
Expand Down
4 changes: 4 additions & 0 deletions config/env/development.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ module.exports = {
org: 'FederalistLocal',
team: 'federalist-local-admins',
},
encryption: {
algorithm: 'aes-256-gcm',
key: 'a-random-dev-key',
},
userEnvVar: {
key: 'shhhhhhhhhhh',
},
Expand Down
16 changes: 15 additions & 1 deletion config/env/production.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,22 @@ if (rdsCreds) {
throw new Error('No database credentials found.');
}

// Encryption Config
const encryptCreds = appEnv.getServiceCreds(`pages-${APP_ENV}-encryption`);

if (encryptCreds) {
module.exports.encryption = {
key: encryptCreds.key,
algorithm: encryptCreds.algorithm,
};
} else {
throw new Error('No encryption key found');
}

// S3 Configs for Build Logs
const s3BuildLogsCreds = appEnv.getServiceCreds(`${servicePrefix}-s3-build-logs`);
const s3BuildLogsCreds = appEnv.getServiceCreds(
`${servicePrefix}-s3-build-logs`
);
if (s3BuildLogsCreds) {
module.exports.s3BuildLogs = {
accessKeyId: s3BuildLogsCreds.access_key_id,
Expand Down
8 changes: 5 additions & 3 deletions config/env/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ module.exports = {
build: {
token: '123abc',
},
encryption: {
algorithm: 'aes-256-gcm',
key: 'a-secret-test-key',
},
s3: {
accessKeyId: '123abc',
secretAccessKey: '456def',
Expand All @@ -55,9 +59,7 @@ module.exports = {
callbackURL: 'http://localhost:1337/external/auth/github/callback',
scope: ['user', 'repo'],
},
organizations: [
123456,
],
organizations: [123456],
},
uaa: {
host: 'https://uaa.example.com',
Expand Down
5 changes: 5 additions & 0 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ The app expects the following user provided services to be provided:
- `tokenURL`: The UAA url to get a user's token
- `userURL`: The UAA url to get a user's info
- `logoutURL`: The UAA url to logout a user
- `pages-<environment>-encryption`: The credentials used to encrypt data sent to other platform components
- `key`: The secret key to be shared across components
- `algorithm`: The algorithm used to encrypt the data

#### Deploy in CloudFoundry
To deploy to CloudFoundry submit the following:
Expand Down Expand Up @@ -571,6 +574,8 @@ When a queue is processing the maximum number of jobs, all new jobs will be plac

Queue jobs are actions that add a job to a queue and are kept in the [./api/queue-jobs](../api/queue-jobs) directory. The `QueueJobs` class provides methods to add jobs to a variety of queues. These methods should normally take two arguments. The first argument should be the message/data the worker will recieve to process the job. The second argument should be the job priority number.

Site build jobs use the CF Task functionality to remotely execute the command on the independently deployed pages-build-container app. To successfully build a site, the site build job passes the build params when executing a new site build. The command and params are sent via the CF API using the [startSiteBuildTask](../api/utils/cfApiClient.js) method where the method also encrypts the params sent to the CF Tasks via the [Encryptor](../api/services/Encryptor.js). When the site build is executed on the pages-build-container app, it decrypts the site params and starts the build process. The pages-core and pages-build-container apps share the user-provided service `pages-<env>-encryption` to encrypt and decrypt the params with a shared key.

### Workers

Workers are the processors that handle a job in a queue and are kept in the [./api/workers](../api/workers) directory. Workers are the functions that process the job added to a queue. They run in a separate worker application deployed along side the app and can either process the job on the worker itself or launch a CF task in an external application and listen to the CF task status until the status completes.
Expand Down
14 changes: 1 addition & 13 deletions test/api/unit/services/Encryptor.test.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,14 @@
const { expect } = require('chai');
const Crypto = require('crypto');
const Encryptor = require('../../../../api/services/Encryptor');

function decrypt(ciphertext, key) {
const hashedKey = Crypto.createHash('sha256').update(key).digest();
const [authTagHex, ivHex, encrypted] = ciphertext.split(':');
const iv = Buffer.from(ivHex, 'hex');
const authTag = Buffer.from(authTagHex, 'hex');
const decipher = Crypto.createDecipheriv(Encryptor.ALGORITHM, hashedKey, iv);
decipher.setAuthTag(authTag);
const decrypted = decipher.update(encrypted, 'hex', 'utf8') + decipher.final('utf8');
return decrypted;
}

describe('Encryptor', () => {
describe('.encrypt', () => {
const value = 'hello world';
const key = 'shhhhhhh';

it('encrypts the value', () => {
const { ciphertext } = Encryptor.encrypt(value, key);
const decrypted = decrypt(ciphertext, key);
const decrypted = Encryptor.decrypt(ciphertext, key);

expect(decrypted).to.eq(value);
});
Expand Down
28 changes: 19 additions & 9 deletions test/api/unit/utils/cfApiClient.tasks.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { expect } = require('chai');
const sinon = require('sinon');
const CloudFoundryAPIClient = require('../../../../api/utils/cfApiClient');
const Encryptor = require('../../../../api/services/Encryptor');

describe('CloudFoundryAPIClient', () => {
afterEach(() => {
Expand All @@ -26,13 +27,16 @@ describe('CloudFoundryAPIClient', () => {
const method = 'POST';
const path = `/v3/apps/${guid}/tasks`;
const commandParam = message.environment[0];
const ciphertext = JSON.stringify({
[commandParam.name]: commandParam.value,
});
sinon.stub(Encryptor, 'encrypt')
.returns({ ciphertext });
const taskParams = {
disk_in_mb: 4 * 1024,
memory_in_mb: 2 * 1024,
name: `build-${jobId}`,
command: `cd app && python main.py -p '${JSON.stringify({
[commandParam.name]: commandParam.value,
})}'`,
command: `cd app && python main.py -p '${ciphertext}'`,
metadata: { labels: { type: 'build-task' } },
};

Expand Down Expand Up @@ -66,13 +70,16 @@ describe('CloudFoundryAPIClient', () => {
const method = 'POST';
const path = `/v3/apps/${guid}/tasks`;
const commandParam = message.environment[0];
const ciphertext = JSON.stringify({
[commandParam.name]: commandParam.value,
});
sinon.stub(Encryptor, 'encrypt')
.returns({ ciphertext });
const taskParams = {
disk_in_mb: 7 * 1024,
memory_in_mb: 8 * 1024,
name: `build-${jobId}`,
command: `cd app && python main.py -p '${JSON.stringify({
[commandParam.name]: commandParam.value,
})}'`,
command: `cd app && python main.py -p '${ciphertext}'`,
metadata: { labels: { type: 'build-task' } },
};

Expand Down Expand Up @@ -105,13 +112,16 @@ describe('CloudFoundryAPIClient', () => {
const method = 'POST';
const path = `/v3/apps/${guid}/tasks`;
const commandParam = message.environment[0];
const ciphertext = JSON.stringify({
[commandParam.name]: commandParam.value,
});
sinon.stub(Encryptor, 'encrypt')
.returns({ ciphertext });
const taskParams = {
disk_in_mb: 4 * 1024,
memory_in_mb: 2 * 1024,
name: `build-${jobId}`,
command: `cd app && python main.py -p '${JSON.stringify({
[commandParam.name]: commandParam.value,
})}'`,
command: `cd app && python main.py -p '${ciphertext}'`,
metadata: { labels: { type: 'build-task' } },
};

Expand Down

0 comments on commit f5ddd02

Please sign in to comment.