Skip to content

Instantly share code, notes, and snippets.

@PlamenHristov
Last active August 17, 2021 18:50
Show Gist options
  • Save PlamenHristov/7709bac9e5e38c59e29ed81244c848c1 to your computer and use it in GitHub Desktop.
Save PlamenHristov/7709bac9e5e38c59e29ed81244c848c1 to your computer and use it in GitHub Desktop.
How to generate a valid auth token for EKS control plane requests
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