Github auth admin endpoint and logging (#1267)

- Periodically log github auth information
    - Tokens are hashed which reduces the security risk inherent in the logs
        - A consistent hash is used so tokens can be correlated across the three data structures and across the three servers
- Add an admin endpoint for github auth information
    - Tokens are returned as-is to enable troubleshooting (e.g. comparing our reqRemaining to github’s)
This commit is contained in:
Paul Melnikow
2017-11-30 13:21:27 -05:00
committed by GitHub
parent f403c2b5d3
commit 127b46aef8
5 changed files with 118 additions and 45 deletions

View File

@@ -1,10 +1,12 @@
'use strict';
const crypto = require('crypto');
const log = require('./log');
const queryString = require('query-string');
const request = require('request');
const autosave = require('json-autosave');
const serverSecrets = require('./server-secrets');
const mapKeys = require('lodash.mapkeys');
// This is an initial value which makes the code work while the initial data
// is loaded. In the then() callback of scheduleAutosaving(), it's reassigned
@@ -111,6 +113,22 @@ function setRoutes(server) {
addGithubToken(data.token);
end('Thanks!');
});
// Allow the admin to obtain the tokens for operational and debugging
// purposes. This could be used to:
//
// - Ensure tokens have been propagated to all servers
// - Debug GitHub badge failures
//
// The admin can authenticate with HTTP Basic Auth, with the shields secret
// in the username and an empty/any password.
server.ajax.on('github-auth/tokens', (json, end, ask) => {
if (! constEq(ask.username, serverSecrets.shieldsSecret)) {
// An unknown entity tries to connect. Let the connection linger for a minute.
return setTimeout(function() { end('Invalid secret.'); }, 10000);
}
end(getTokenDebugInfo({ sanitize: false }));
});
}
function sendTokenToAllServers(token) {
@@ -225,6 +243,47 @@ function rmGithubToken(token) {
}
}
// Convert an ES6 Map to an object.
function mapToObject(map) {
const result = {};
for (const [k, v] of map) {
result[k] = v;
}
return result;
}
// Compute a one-way hash of the input string.
function sha(str) {
return crypto.createHash('sha256')
.update(str, 'utf-8')
.digest('hex');
}
function getTokenDebugInfo(options) {
// Apply defaults.
const { sanitize } = Object.assign({ sanitize: true }, options);
const unsanitized = {
tokens: githubUserTokens.data,
reqRemaining: mapToObject(reqRemaining),
reqReset: mapToObject(reqReset),
utcEpochSeconds: utcEpochSeconds(),
sanitized: false,
};
if (sanitize) {
return {
tokens: unsanitized.tokens.map(k => sha(k)),
reqRemaining: mapKeys(unsanitized.reqRemaining, (v, k) => sha(k)),
reqReset: mapKeys(unsanitized.reqReset, (v, k) => sha(k)),
utcEpochSeconds: unsanitized.utcEpochSeconds,
sanitized: true,
};
} else {
return unsanitized;
}
}
// When a global gh_token is configured, use that in place of our shields.io
// token-cycling logic. This produces more predictable behavior when a token
// is provided, and more predictable failures if that token is exhausted.
@@ -288,4 +347,5 @@ module.exports = {
cancelAutosaving,
request: githubRequest,
setRoutes,
getTokenDebugInfo,
};