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:
@@ -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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user