-
Notifications
You must be signed in to change notification settings - Fork 8
TRITON-2513 - Update Access Keys to better support Manta S3 #12
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
cf5cd46
TRITON-2513 - Update Access Keys to better support Manta S3
travispaul 3e0ffc4
Export defaults params, patch up older records.
travispaul 6fed73e
Add listActiveAccessKeys convenience function
travispaul d0beab1
Add additional subuser test
travispaul 8b64c76
updateAccessKey should return the updated key
travispaul cf6fcc9
Fix copyright comment
travispaul c8f93c1
Add cross account checks
travispaul c1da5c0
Restore Node v0.10 support
travispaul File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| /* | ||
| * This Source Code Form is subject to the terms of the Mozilla Public | ||
| * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
| */ | ||
|
|
||
| /* | ||
| * Copyright 2025 Edgecast Cloud LLC. | ||
| */ | ||
|
|
||
| /* | ||
| * Routines for generating and validating secret access keys. | ||
| */ | ||
|
|
||
| var crypto = require('crypto'); | ||
| var crc32 = require('crc').buffer.crc32; | ||
| var assert = require('assert-plus'); | ||
|
|
||
| var TO_B64_REG = new RegExp('[+/=]', 'g'); | ||
| var FROM_B64_REG = new RegExp('[-_]', 'g'); | ||
|
|
||
| var DEFAULT_PREFIX = 'tdc_'; | ||
| var DEFAULT_BYTE_LENGTH = 32; | ||
|
|
||
| // Don't have base64url encoded Buffers until Node v14 | ||
| function toBase64url(input) { | ||
| return input | ||
| .toString('base64') | ||
| .replace(TO_B64_REG, function (c) { | ||
| if (c === '+') { | ||
| return '-'; | ||
| } | ||
| if (c === '/') { | ||
| return '_'; | ||
| } | ||
| if (c === '=') { | ||
| return ''; | ||
| } | ||
| return null; | ||
| }); | ||
| } | ||
|
|
||
| function fromBase64url(input) { | ||
| var base64 = input.replace(FROM_B64_REG, function (c) { | ||
| if (c === '-') { | ||
| return '+'; | ||
| } | ||
| if (c === '_') { | ||
| return '/'; | ||
| } | ||
| return null; | ||
| }); | ||
|
|
||
| // Restore padding | ||
| while (base64.length % 4 !== 0) { | ||
| base64 += '='; | ||
| } | ||
|
|
||
| // Buffer.from not available until Node v5 | ||
| if (typeof (Buffer.from) === 'function') { | ||
| return Buffer.from(base64, 'base64'); | ||
| } | ||
|
|
||
| return new Buffer(base64, 'base64'); | ||
| } | ||
|
|
||
| /* | ||
| * Node v0.10 didn't yet have `Buffer.alloc()` and `new Buffer()` was the only | ||
| * option until v5. However, using `new Buffer()` in newer Node versions emits a | ||
| * deprecation message with a security warning so `Buffer.alloc` is used where | ||
| * available. | ||
| */ | ||
| function newBuffer(size) { | ||
| assert.number(size, 'size'); | ||
| if (typeof (Buffer.alloc) === 'function') { | ||
| return Buffer.alloc(size); | ||
| } | ||
| return new Buffer(size); | ||
| } | ||
|
|
||
| /** | ||
| * Generate a random secret access key inspired by suggestions from Github's | ||
| * Secret Scanning Partner Program[0] and how they structure their keys[1]: | ||
| * [0] https://i.no.de/c12f50d544eececf | ||
| * [1] https://i.no.de/5a4e8cea87c0a873 | ||
| * | ||
| * Instead of using Base62 as Github does, base64url encoding is used instead. | ||
| * | ||
| * Keys generated from this function have: | ||
| * - A uniquely defined prefix (e.g. "tdc_" for "Triton DataCenter") | ||
| * - High entropy random strings (32 random bytes from node crypto) | ||
danmcd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * - A 32-bit crc checksum (to validate token structure) | ||
| * | ||
| * An example key: | ||
| * | ||
| * tdc_SU4xWXL-HzrMIDM_A8GH94sl-uc-aX8mqsEMiK4JSVdAGyjH | ||
| * | ||
| * +--------+--------------------------------------------+--------+ | ||
| * | PREFIX | RANDOM BYTES | CRC32 | | ||
| * +--------+--------------------------------------------+--------+ | ||
| * | tdc_ | SU4xWXL-HzrMIDM_A8GH94sl-uc-aX8mqsEMiK4JSV | dAGyjH |---+ | ||
| * +--------+--------------------------------------------+--------+ | | ||
| * | BASE64 URL ENCODED | | | ||
| * +--------+--------------------------------------------+--------+ | | ||
| * | CRC32 coverage (PREFIX + RANDOM BYTES) | <----------+ | ||
| * +-----------------------------------------------------+ | ||
| * | ||
| * @param {String} prefix string for the token. | ||
| * @param {Number} byte count to randomly generate. | ||
| * @param {Function} callback of the form fn(err, key). | ||
| * @throws {TypeError} on bad input. | ||
| */ | ||
| function generate(prefix, bytes, done) { | ||
| assert.string(prefix, 'prefix'); | ||
| assert.number(bytes, 'bytes'); | ||
| assert.func(done, 'done'); | ||
|
|
||
| crypto.randomBytes(bytes, function generateBytes(err, randBytes) { | ||
| if (err) { | ||
| done(err); | ||
| return; | ||
| } | ||
|
|
||
| // Create a buffer containing the prefix and random bytes | ||
| var prefixBuf = newBuffer(prefix.length); | ||
| prefixBuf.write(prefix); | ||
|
|
||
| var tokenBuf = Buffer.concat([prefixBuf, randBytes]); | ||
|
|
||
| // Obtain CRC32 from prefix + random bytes | ||
| var crc = crc32(tokenBuf); | ||
|
|
||
| // Write the CRC32 into a new buffer encoded as a 32-bit signed int | ||
| var crcBuf = newBuffer(4); | ||
|
|
||
| // Some anicent versions of Node return undefined for writeInt32LE (at | ||
| // least v0.10.48 but not v0.12.14) | ||
danmcd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| var wrote = crcBuf.writeInt32LE(crc, 0); | ||
| if (wrote !== undefined && wrote !== 4) { | ||
| done(new Error('Failed to generate access key')); | ||
| return; | ||
| } | ||
|
|
||
| // Base64 URL the encode random bytes + CRC32, prepend the prefix | ||
| var key = prefix + toBase64url(Buffer.concat([randBytes, crcBuf])); | ||
|
|
||
| done(null, key); | ||
| return; | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Validates the structure of a secret access key. Does NOT validate that the | ||
| * token is active and valid for authentication purposes it only validates that | ||
| * the token structure is correct. This function can be used to toss out a | ||
| * garbage token before attempting to look it up against UFDS. | ||
| * | ||
| * @param {String} prefix string for the token. | ||
| * @param {Number} byte count expected in the token. | ||
| * @param {String} secret key string. | ||
| * @throws {TypeError} on bad input. | ||
| */ | ||
| function validate(prefix, bytes, secret) { | ||
| assert.string(prefix, 'prefix'); | ||
| assert.number(bytes, 'bytes'); | ||
| assert.string(secret, 'secret'); | ||
|
|
||
| if (secret.indexOf(prefix) !== 0) { | ||
| return false; | ||
| } | ||
|
|
||
| // Remove prefix from the secret | ||
| var body = secret.slice(prefix.length); | ||
|
|
||
| // Base64 URL decode the body containing random bytes + CRC32 | ||
| var parts = fromBase64url(body); | ||
|
|
||
| // Must contain the expected number of random bytes + 4 bytes for the CRC32 | ||
| if (parts.length !== (bytes + 4)) { | ||
| return false; | ||
| } | ||
|
|
||
| // Create a buffer containg the prefix | ||
| var prefixBuf = newBuffer(prefix.length); | ||
| prefixBuf.write(secret.slice(0, prefix.length)); | ||
|
|
||
| // Create a buffer containing the random bytes | ||
| var randBytesBuf = parts.slice(0, -4); | ||
|
|
||
| // Create a buffer from the CRC32 at the end of the secret | ||
| var crc32Buf = parts.slice(-4); | ||
|
|
||
| // Create a new buffer containing the prefix + random bytes | ||
| var tokenBuf = Buffer.concat([prefixBuf, randBytesBuf]); | ||
|
|
||
| // Recompute CRC32 and compare with the CRC32 obtained from the secret | ||
| return (crc32(tokenBuf) === crc32Buf.readInt32LE(0)); | ||
| } | ||
|
|
||
| module.exports = { | ||
| generate: generate, | ||
| validate: validate, | ||
| DEFAULT_PREFIX: DEFAULT_PREFIX, | ||
| DEFAULT_BYTE_LENGTH: DEFAULT_BYTE_LENGTH | ||
| }; | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.