Last active
August 17, 2021 18:50
-
-
Save PlamenHristov/7709bac9e5e38c59e29ed81244c848c1 to your computer and use it in GitHub Desktop.
How to generate a valid auth token for EKS control plane requests
This file contains 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
import crypto, { BinaryToTextEncoding } from 'crypto' | |
const AUTH_SERVICE = 'sts' | |
const AUTH_COMMAND = 'GetCallerIdentity' | |
const AUTH_API_VERSION = '2011-06-15' | |
const URL_TIMEOUT = 60 | |
const TOKEN_PREFIX = 'k8s-aws-v1.' | |
const CLUSTER_NAME_HEADER = 'x-k8s-aws-id' | |
const KEY_TYPE_IDENTIFIER = 'aws4_request' | |
const ALGORITHM_QUERY_PARAM = 'X-Amz-Algorithm' | |
const CREDENTIAL_QUERY_PARAM = 'X-Amz-Credential' | |
const AMZ_DATE_QUERY_PARAM = 'X-Amz-Date' | |
const AMZ_EXPIRES_QUERY_PARAM = 'X-Amz-Expires' | |
const AMZ_SIGNED_HEADERS_QUERY_PARAM = 'X-Amz-SignedHeaders' | |
const TOKEN_QUERY_PARAM = 'X-Amz-Security-Token' | |
const SIGNATURE_QUERY_PARAM = 'X-Amz-Signature' | |
const ALGORITHM_IDENTIFIER = 'AWS4-HMAC-SHA256' | |
const buildScope = (shortDate: string, region: string, service: string): string => { | |
return `${shortDate}/${region}/${service}/${KEY_TYPE_IDENTIFIER}` | |
} | |
const buildStringToSign = (timestamp: string, scope: string, canonicalRequest: string): string => | |
[ALGORITHM_IDENTIFIER, timestamp, scope, sha256(canonicalRequest)].join('\n') | |
const buildCanonicalRequest = (method: string, path: string, params: string, headers: { [key:string]: any }): string => { | |
const res = [method, path, params] | |
const headersToSign = Object.keys(headers).sort() | |
const headerValues = headersToSign.map((k) => `${k}:${headers[k]}`).join('\n') + '\n' | |
res.push(headerValues) | |
res.push(headersToSign.join(';')) | |
// magic hash for empty body | |
res.push('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') | |
return res.join('\n') | |
} | |
const buildParams = (accessKeyId: string, credentialScope: string, longDate: string, sessionToken?: string): string => { | |
return [ | |
`Action=${AUTH_COMMAND}`, | |
`Version=${AUTH_API_VERSION}`, | |
`${ALGORITHM_QUERY_PARAM}=${ALGORITHM_IDENTIFIER}`, | |
`${CREDENTIAL_QUERY_PARAM}=${encodeURIComponent(`${accessKeyId}/${credentialScope}`)}`, | |
`${AMZ_DATE_QUERY_PARAM}=${longDate}`, | |
`${AMZ_EXPIRES_QUERY_PARAM}=${URL_TIMEOUT}`, | |
sessionToken ? `${TOKEN_QUERY_PARAM}=${encodeURIComponent(sessionToken)}` : undefined, | |
`${AMZ_SIGNED_HEADERS_QUERY_PARAM}=${encodeURIComponent(`host;${CLUSTER_NAME_HEADER}`)}`, | |
] | |
.filter((val) => val) | |
.join('&') | |
} | |
const iso8601 = (date: Date) => date.toISOString().replace(/\.\d{3}Z$/, 'Z') | |
const sha256 = (msg: string, encoding: BinaryToTextEncoding = 'hex'): string => | |
crypto.createHash('sha256').update(msg).digest(encoding) | |
const hmacSha256 = (key: string | Buffer, msg: string, encoding?: BinaryToTextEncoding): string | Buffer => { | |
const res = crypto.createHmac('sha256', key).update(msg) | |
if (encoding) { | |
return res.digest(encoding) | |
} | |
return res.digest() | |
} | |
const sign = ( | |
key: string, | |
stringToSign: string, | |
dateStamp: string, | |
regionName: string, | |
serviceName: string | |
): string => { | |
const kDate = hmacSha256('AWS4' + key, dateStamp) | |
const kRegion = hmacSha256(kDate, regionName) | |
const kService = hmacSha256(kRegion, serviceName) | |
const kSigning = hmacSha256(kService, KEY_TYPE_IDENTIFIER) | |
return hmacSha256(kSigning, stringToSign, 'hex') as string | |
} | |
const rtrim = (str: string, stripped: string): string => { | |
let end = str.length - 1 | |
while (stripped.indexOf(str[end]) >= 0) { | |
end -= 1 | |
} | |
return str.substr(0, end + 1) | |
} | |
export const generateAWSToken = ( | |
clusterName: string, | |
accessKeyId: string, | |
secretAccessKey: string, | |
sessionToken: string, | |
clientRegion: string | |
): string => { | |
const now = new Date() | |
const signingDate = iso8601(now).replace(/[\-:]/g, '') | |
const shortDate = signingDate.substr(0, 8) | |
const credentialScope = buildScope(shortDate, clientRegion, AUTH_SERVICE) | |
const params = buildParams(accessKeyId, credentialScope, signingDate, sessionToken) | |
const method = 'GET' | |
const path = '/' | |
const headers = { | |
host: `sts.${clientRegion}.amazonaws.com`, | |
[CLUSTER_NAME_HEADER]: clusterName, | |
} | |
const canonicalRequest = buildCanonicalRequest(method, path, params, headers) | |
const stringToSign = buildStringToSign(signingDate, credentialScope, canonicalRequest) | |
const signature = sign(secretAccessKey, stringToSign, shortDate, clientRegion, AUTH_SERVICE) | |
const url = `https://${AUTH_SERVICE}.${clientRegion}.amazonaws.com/?${params}&${SIGNATURE_QUERY_PARAM}=${signature}` | |
return TOKEN_PREFIX + rtrim(Buffer.from(url).toString('base64'), '=') | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment