Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable configuration of API_SECRET as a Docker secret #8300

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs/ or
### Required

* `MONGODB_URI` - The connection string for your Mongo database. Something like `mongodb://sally:sallypass@ds099999.mongolab.com:99999/nightscout`.
* `API_SECRET` - A secret passphrase that must be at least 12 characters long.
* `API_SECRET` - A secret passphrase that must be at least 12 characters long. Alternatively, if `API_SECRET_FILE` is defined, the secret passphrase will be read from the specified file.
* `MONGODB_COLLECTION` (`entries`) - The Mongo collection where CGM entries are stored.
* `DISPLAY_UNITS` (`mg/dl`) - Options are `mg/dl` or `mmol/L` (or just `mmol`). Setting to `mmol/L` puts the entire server into `mmol/L` mode by default, no further settings needed.

Expand Down
2 changes: 1 addition & 1 deletion lib/server/enclave.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const init = function init () {
}

enclave.isApiKeySet = function isApiKeySet () {
return isApiKeySet;
return apiKeySet;
}

enclave.isApiKey = function isApiKey (keyValue) {
Expand Down
27 changes: 19 additions & 8 deletions lib/server/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,22 +77,19 @@ function setSSL () {
env.secureCspReportOnly = readENVTruthy("SECURE_CSP_REPORT_ONLY", false);
}

// A little ugly, but we don't want to read the secret into a var
function setAPISecret () {
var useSecret = (readENV('API_SECRET') && readENV('API_SECRET').length > 0);
// if no value is provided as an environment variable, try to read it from a file
const apiSecret = readENV('API_SECRET') || readEnvFile('API_SECRET_FILE');
//TODO: should we clear API_SECRET from process env?
env.api_secret = null;
// if a passphrase was provided, get the hex digest to mint a single token
if (useSecret) {
if (readENV('API_SECRET').length < consts.MIN_PASSPHRASE_LENGTH) {
if (apiSecret && apiSecret.length > 0) {
if (apiSecret.length < consts.MIN_PASSPHRASE_LENGTH) {
var msg = ['API_SECRET should be at least', consts.MIN_PASSPHRASE_LENGTH, 'characters'].join(' ');
console.error(msg);
env.err.push({ desc: msg });
} else {

const apiSecret = readENV('API_SECRET');
delete process.env.API_SECRET;

env.enclave.setApiKey(apiSecret);
var testresult = stringEntropy(apiSecret);

Expand All @@ -108,7 +105,6 @@ function setAPISecret () {
env.notifies.push({ persistent: true, title: 'Security issue', message: 'MongoDB password and API_SECRET match. This is a really bad idea. Please change both and do not reuse passwords across the system.' });
}
}

}
}
}
Expand Down Expand Up @@ -185,6 +181,21 @@ function readENVTruthy (varName, defaultValue) {
return value;
}

function readEnvFile(varName) {
let value = null;
const fileName = readENV(varName);

if (fileName && fileName.length > 0) {
try {
value = fs.readFileSync(fileName);
} catch (error) {
env.err.push({ desc: `Unable to read ${varName}: ${error.message}` });
}
}

return value;
}

function findExtendedSettings (envs) {
var extended = {};

Expand Down
63 changes: 63 additions & 0 deletions tests/env.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,71 @@
'use strict';

require('should');
const fs = require('fs');
const os = require('os');
const path = require('path');

describe('env', function () {
function writeTempFile(fileName, data) {
const fullPath = path.join(os.tmpdir(), fileName);
fs.writeFileSync(fullPath, data);
return fullPath;
}

it('should not set the API key without API_SECRET or API_SECRET_FILE', function () {
delete process.env.API_SECRET;
var env = require( '../lib/server/env' )();
env.enclave.isApiKeySet().should.equal(false);
});

it('should read the API key from API_SECRET_FILE if it is valid', function () {
const apiSecretFile = 'this is another pass phrase';
const hashFile = 'c79c6db1070da3537d0162e60647b0a588769f8d';
process.env.API_SECRET_FILE = writeTempFile('api_secret_file', apiSecretFile);

var env = require( '../lib/server/env' )();
env.enclave.isApiKeySet().should.equal(true);
env.enclave.isApiKey(hashFile).should.equal(true);

fs.rmSync(process.env.API_SECRET_FILE);
delete process.env.API_SECRET_FILE;
});

it('should raise an error when API_SECRET_FILE does not exist', function () {
const nonexistentPath = path.join(os.tmpdir(), 'api_secret_file');
process.env.API_SECRET_FILE = nonexistentPath;

var env = require( '../lib/server/env' )();
env.enclave.isApiKeySet().should.equal(false);
env.err.length.should.equal(1);

const error = env.err.pop();
error.should.have.property('desc');
error.desc.should.match(/API_SECRET_FILE/);
error.desc.should.match(/no such file or directory/);

delete process.env.API_SECRET_FILE;
});

it('should use API_SECRET when API_SECRET_FILE is also specified', function () {
const apiSecretEnv = 'this is my long pass phrase';
const hashEnv = 'b723e97aa97846eb92d5264f084b2823f57c4aa1';
process.env.API_SECRET = apiSecretEnv;

const apiSecretFile = 'this is another pass phrase';
const hashFile = 'c79c6db1070da3537d0162e60647b0a588769f8d';
process.env.API_SECRET_FILE = writeTempFile('api_secret_file', apiSecretFile);

var env = require( '../lib/server/env' )();
env.enclave.isApiKeySet().should.equal(true);
env.enclave.isApiKey(hashEnv).should.equal(true);
env.enclave.isApiKey(hashFile).should.equal(false);

fs.rmSync(process.env.API_SECRET_FILE);
delete process.env.API_SECRET_FILE;
delete process.env.API_SECRET;
});

it( 'show the right plugins', function () {
process.env.SHOW_PLUGINS = 'iob';
process.env.ENABLE = 'iob cob';
Expand Down