Skip to content

Commit

Permalink
AssumeRole API
Browse files Browse the repository at this point in the history
  • Loading branch information
prakashsvmx committed Sep 1, 2021
1 parent 7eca29d commit 0dada43
Show file tree
Hide file tree
Showing 11 changed files with 471 additions and 56 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ module.exports = {
},
'extends': 'eslint:recommended',
'parserOptions': {
'sourceType': 'module'
'sourceType': 'module',
"ecmaVersion": 8
},
'rules': {
'indent': [
Expand Down
2 changes: 1 addition & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const compileJS = (src, dest) => {
.pipe(sourcemaps.init())
.pipe(babel({
presets: [['@babel/env', {
targets: { node: 4 }
targets: { node: 8 }
}]]
}))
.pipe(sourcemaps.write('.'))
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"url": "https://min.io"
},
"engines": {
"node": ">= 4"
"node": ">8 <16.8.0"
},
"license": "Apache-2.0",
"bugs": {
Expand Down
218 changes: 218 additions & 0 deletions src/main/AssumeRoleProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import Http from 'http'
import Https from 'https'
import {makeDateLong, parseXml, toSha256} from "./helpers"
import {signV4ByServiceName} from "./signing"
import CredentialProvider from "./CredentialProvider"
import Credentials from "./Credentials"

class AssumeRoleProvider extends CredentialProvider {
constructor({
stsEndpoint,
accessKey,
secretKey,
durationSeconds = 900,
sessionToken,
policy,
region = '',
roleArn,
roleSessionName,
externalId,
token,
webIdentityToken,
action = "AssumeRole"
}) {
super({})

this.stsEndpoint = stsEndpoint
this.accessKey = accessKey
this.secretKey = secretKey
this.durationSeconds = durationSeconds
this.policy = policy
this.region = region
this.roleArn = roleArn
this.roleSessionName = roleSessionName
this.externalId = externalId
this.token = token
this.webIdentityToken = webIdentityToken
this.action = action
this.sessionToken = sessionToken

/**
* Internal Tracking variables
*/
this.credentials = null
this.expirySeconds = null
this.accessExpiresAt = null

}


getRequestConfig() {
const url = new URL(this.stsEndpoint)
const hostValue = url.hostname
const portValue = url.port
const isHttp = url.protocol.includes("http:")
const qryParams = new URLSearchParams()
qryParams.set("Action", this.action)
qryParams.set("Version", "2011-06-15")

const defaultExpiry = 900
let expirySeconds = parseInt(this.durationSeconds)
if (expirySeconds < defaultExpiry) {
expirySeconds = defaultExpiry
}
this.expirySeconds = expirySeconds // for calculating refresh of credentials.

qryParams.set("DurationSeconds", this.expirySeconds)

if (this.policy) {
qryParams.set("Policy", this.policy)
}
if (this.roleArn) {
qryParams.set("RoleArn", this.roleArn)
}

if (this.roleSessionName != null) {
qryParams.set("RoleSessionName", this.roleSessionName)
}
if (this.token != null) {
qryParams.set("Token", this.token)
}

if (this.webIdentityToken) {
qryParams.set("WebIdentityToken", this.webIdentityToken)
}

if (this.externalId) {
qryParams.set("ExternalId", this.externalId)
}


const urlParams = qryParams.toString()
const contentSha256 = toSha256(urlParams)

const date = new Date()

/**
* Nodejs's Request Configuration.
*/
const requestOptions = {
hostname: hostValue,
port: portValue,
path: "/",
protocol: url.protocol,
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"content-length": urlParams.length,
"host": hostValue,
"x-amz-date": makeDateLong(date),
'x-amz-content-sha256': contentSha256
}
}

const authorization = signV4ByServiceName(requestOptions, this.accessKey, this.secretKey, this.region, date, "sts")
requestOptions.headers.authorization = authorization

return {
requestOptions,
requestData: urlParams,
isHttp: isHttp
}
}

async performRequest() {
const reqObj = this.getRequestConfig()
const requestOptions = reqObj.requestOptions
const requestData = reqObj.requestData

const isHttp = reqObj.isHttp
const Transport = isHttp ? Http : Https

const promise = new Promise((resolve, reject) => {
const requestObj = Transport.request(requestOptions, (resp) => {
let resChunks = []
resp.on('data', rChunk => {
resChunks.push(rChunk)
})
resp.on('end', () => {
let body = Buffer.concat(resChunks).toString()
const xmlobj = parseXml(body)
resolve(xmlobj)
})
resp.on('error', (err) => {
reject(err)
})
})
requestObj.on('error', (e) => {
reject(e)
})
requestObj.write(requestData)
requestObj.end()
})
return promise

}

parseCredentials(respObj={}) {
if(respObj.ErrorResponse){
throw new Error("Unable to obtain credentials:", respObj)
}
const {
AssumeRoleResponse: {
AssumeRoleResult: {
Credentials: {
AccessKeyId: accessKey,
SecretAccessKey: secretKey,
SessionToken: sessionToken,
Expiration: expiresAt
} = {}
} = {}
} = {}
} = respObj


this.accessExpiresAt = expiresAt

const newCreds = new Credentials({
accessKey,
secretKey,
sessionToken
})

this.setCredentials(newCreds)
return this.credentials

}


async refreshCredentials() {
try {
const assumeRoleCredentials = await this.performRequest()
this.credentials = this.parseCredentials(assumeRoleCredentials)
} catch (err) {
this.credentials = null
throw new Error(`Error getting credentials:` + err)
}
return this.credentials
}

async getCredentials() {
let credConfig
if (!this.credentials || (this.credentials && this.isAboutToExpire())) {
credConfig = await this.refreshCredentials()
} else {
credConfig = this.credentials
}
return credConfig
}

isAboutToExpire() {
const expiresAt = new Date(this.accessExpiresAt)
const provisionalExpiry = new Date(Date.now() + 1000 * 10)//check before 10 seconds.
const isAboutToExpire = provisionalExpiry > expiresAt
return isAboutToExpire
}
}

export default AssumeRoleProvider
56 changes: 56 additions & 0 deletions src/main/CredentialProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Credentials from "./Credentials"

class CredentialProvider {
constructor({
accessKey,
secretKey,
sessionToken
}) {
this.credentials = new Credentials({
accessKey,
secretKey,
sessionToken
})
}

getCredentials() {
return this.credentials.get()
}

setCredentials(credentials) {
if (credentials instanceof Credentials) {
this.credentials = credentials
} else {
throw new Error("Unable to set Credentials . it should be an instance of Credentials class")
}
}


setAccessKey(accessKey) {
this.credentials.setAccessKey(accessKey)
}

getAccessKey() {
return this.credentials.getAccessKey()
}

setSecretKey(secretKey) {
this.credentials.setSecretKey(secretKey)
}

getSecretKey() {
return this.credentials.getSecretKey()
}

setSessionToken(sessionToken) {
this.credentials.setSessionToken(sessionToken)
}

getSessionToken() {
return this.credentials.getSessionToken()
}


}

export default CredentialProvider
42 changes: 42 additions & 0 deletions src/main/Credentials.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class Credentials{
constructor({
accessKey,
secretKey,
sessionToken
}) {
this.accessKey = accessKey
this.secretKey = secretKey
this.sessionToken=sessionToken
}


setAccessKey(accessKey){
this.accessKey = accessKey
}
getAccessKey(){
return this.accessKey
}
setSecretKey(secretKey){
this.secretKey=secretKey
}
getSecretKey(){
return this.secretKey
}
setSessionToken (sessionToken){
this.sessionToken = sessionToken
}
getSessionToken (){
return this.sessionToken
}

get(){
return {
accessKey:this.accessKey,
secretKey:this.secretKey,
sessionToken:this.sessionToken
}
}

}

export default Credentials
15 changes: 13 additions & 2 deletions src/main/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import stream from 'stream'
import mime from 'mime-types'
import fxp from "fast-xml-parser"
var Crypto = require('crypto')

// Returns a wrapper function that will promisify a given callback function.
Expand Down Expand Up @@ -81,8 +82,8 @@ export function uriResourceEscape(string) {
return uriEscape(string).replace(/%2F/g, '/')
}

export function getScope(region, date) {
return `${makeDateShort(date)}/${region}/s3/aws4_request`
export function getScope(region, date, serviceName="s3") {
return `${makeDateShort(date)}/${region}/${serviceName}/aws4_request`
}

// isAmazonEndpoint - true if endpoint is 's3.amazonaws.com' or 's3.cn-north-1.amazonaws.com.cn'
Expand Down Expand Up @@ -396,4 +397,14 @@ export const toMd5=(payload)=>{
}
export const toSha256=(payload)=>{
return Crypto.createHash('sha256').update(payload).digest('hex')
}

export const parseXml = (xml) => {
let result = null
result = fxp.parse(xml)
if (result.Error) {
throw result.Error
}

return result
}
Loading

0 comments on commit 0dada43

Please sign in to comment.