|
| 1 | +--[[ |
| 2 | + Copyright 2016 Adobe Systems Incorporated. All rights reserved. |
| 3 | +
|
| 4 | + This file is licensed to you under the Apache License, Version 2.0 (the |
| 5 | + "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at |
| 6 | +
|
| 7 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +
|
| 9 | + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR RESPRESENTATIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. |
| 10 | + ]] |
| 11 | + |
| 12 | +-- |
| 13 | +-- STS Credentials provider |
| 14 | +-- User: ddascal |
| 15 | +-- Date: 19/03/16 |
| 16 | +-- Time: 21:26 |
| 17 | +-- |
| 18 | +local cjson = require "cjson" |
| 19 | +local SecurityTokenService = require "api-gateway.aws.sts.SecurityTokenService" |
| 20 | +local awsDate = require "api-gateway.aws.AwsDateConverter" |
| 21 | +local cacheCls = require "api-gateway.cache.cache" |
| 22 | + |
| 23 | +local _M = {} |
| 24 | + |
| 25 | + |
| 26 | +local IAM_DEFAULT_SECURITY_CREDENTIALS_HOST = "169.254.169.254" -- used by IAM |
| 27 | +local IAM_DEFAULT_SECURITY_CREDENTIALS_PORT = "80" -- used by IAM |
| 28 | +local IAM_DEFAULT_SECURITY_CREDENTIALS_URL = "/latest/meta-data/iam/security-credentials/" -- used by IAM |
| 29 | + |
| 30 | +--- |
| 31 | +-- default expiration for the STS token |
| 32 | +local DEFAULT_TOKEN_EXPIRATION = 60 * 60 * 1 -- in seconds |
| 33 | + |
| 34 | +-- configure cache Manager for IAM crendentials |
| 35 | +local stsCache = cacheCls:new() |
| 36 | + |
| 37 | +local function initStsCache(shared_cache_dict) |
| 38 | + local localCache = require "api-gateway.cache.store.localCache":new({ |
| 39 | + dict = shared_cache_dict, |
| 40 | + ttl = function (value) |
| 41 | + local value_o = cjson.decode(value) |
| 42 | + local expiryTimeUTC = value.ExpireAtTimestamp or value_o.ExpireAt -- ExpireAt is already an epoch time awsDate.convertDateStringToTimestamp(value_o.ExpireAt, true) |
| 43 | + local expiryTimeInSeconds = expiryTimeUTC - os.time() |
| 44 | + return math.min(DEFAULT_TOKEN_EXPIRATION, expiryTimeInSeconds) |
| 45 | + end |
| 46 | + }) |
| 47 | + |
| 48 | + stsCache:addStore(localCache) |
| 49 | +end |
| 50 | + |
| 51 | +--- |
| 52 | +-- @param o Init object |
| 53 | +-- o.role_ARN -- required. The Amazon Resource Name (ARN) of the role to assume. |
| 54 | +-- o.role_session_name -- required. An identifier for the assumed role session. |
| 55 | +-- o.policy -- optional. An IAM policy in JSON format. |
| 56 | +-- o.security_credentials_timeout -- optional. specifies when the token should expire. Defaults to 1 hour. |
| 57 | +-- o.external_id -- optional. A unique identifier used by third parties |
| 58 | +-- o.iam_user -- optional. iam_user. if not defined it'll be auto-discovered |
| 59 | +-- o.security_credentials_host -- optional. AWS Host to read IAM credentials from. Defaults to "169.254.169.254" |
| 60 | +-- o.security_credentials_port -- optional. AWS Port to read IAM credentials. Defaults to 80. |
| 61 | +-- o.security_credentials_url -- optional. AWS URI to read IAM credentials. Defaults to "/latest/meta-data/iam/security-credentials/" |
| 62 | +-- o.shared_cache_dict -- optional. For performance improvements the credentials may be stored in a share dict. |
| 63 | +-- |
| 64 | +function _M:new(o) |
| 65 | + o = o or {} |
| 66 | + setmetatable(o, self) |
| 67 | + self.__index = self |
| 68 | + if (o ~= nil) then |
| 69 | + -- config options specific to STS |
| 70 | + self.security_credentials_timeout = o.security_credentials_timeout or DEFAULT_TOKEN_EXPIRATION |
| 71 | + self.role_ARN = o.role_ARN |
| 72 | + self.role_session_name = o.role_session_name |
| 73 | + self.policy = o.policy |
| 74 | + self.external_id = o.external_id |
| 75 | + |
| 76 | + -- config options specific to IAM User |
| 77 | + self.iam_user = o.iam_user |
| 78 | + self.iam_security_credentials_host = o.iam_security_credentials_host or IAM_DEFAULT_SECURITY_CREDENTIALS_HOST |
| 79 | + self.iam_security_credentials_port = o.iam_security_credentials_port or IAM_DEFAULT_SECURITY_CREDENTIALS_PORT |
| 80 | + self.iam_security_credentials_url = o.iam_security_credentials_url or IAM_DEFAULT_SECURITY_CREDENTIALS_URL |
| 81 | + |
| 82 | + -- config options for static AWS Credetials |
| 83 | + self.aws_region = o.aws_region or "us-east-1" -- TBD: when aws_region is not provided should it go to sts.amazonaws.com ? |
| 84 | + self.aws_secret_key = o.aws_secret_key |
| 85 | + self.aws_access_key = o.aws_access_key |
| 86 | + |
| 87 | + -- shared dict used to cache STS and IAM credentials |
| 88 | + self.shared_cache_dict = o.shared_cache_dict |
| 89 | + if (self.shared_cache_dict) then |
| 90 | + initStsCache(self.shared_cache_dict) |
| 91 | + end |
| 92 | + end |
| 93 | + return o |
| 94 | +end |
| 95 | + |
| 96 | +--- |
| 97 | +-- Return credentials from AWS' Security Token Service |
| 98 | +-- Sample response from STS : |
| 99 | + |
| 100 | +-- { |
| 101 | +-- "AssumeRoleResponse": { |
| 102 | +-- "AssumeRoleResult": { |
| 103 | +-- "AssumedRoleUser": { |
| 104 | +-- "Arn": "arn:aws:sts::123456789012:assumed-role/xaccounts3access/s3-access-example", |
| 105 | +-- "AssumedRoleId": "AROAJEMMODNR6NAEO0000:s3-access-example" |
| 106 | +-- }, |
| 107 | +-- "Credentials": { |
| 108 | +-- "AccessKeyId": "access-key", |
| 109 | +-- "Expiration": 1460355675, |
| 110 | +-- "SecretAccessKey": "some-secret", |
| 111 | +-- "SessionToken": "session-token" |
| 112 | +-- }, |
| 113 | +-- "PackedPolicySize": null |
| 114 | +-- }, |
| 115 | +-- "ResponseMetadata": { |
| 116 | +-- "RequestId": "91609a0a-ffa2-11e5-97e6-bbbbbbbbb" |
| 117 | +-- } |
| 118 | +-- } |
| 119 | +--} |
| 120 | + |
| 121 | +function _M:getCredentialsFromSTS() |
| 122 | + local sts = SecurityTokenService:new({ |
| 123 | + shared_cache_dict = self.shared_cache_dict, |
| 124 | + security_credentials_host = self.iam_security_credentials_host, |
| 125 | + security_credentials_port = self.iam_security_credentials_port, |
| 126 | + aws_debug = self.aws_debug, |
| 127 | + aws_region = self.aws_region, |
| 128 | + aws_conn_keepalive = 60000, -- how long to keep the sockets used for AWS alive |
| 129 | + aws_conn_pool = 10 -- the connection pool size for sockets used to connect to AWS |
| 130 | + }) |
| 131 | + local response, code, headers, status, body = sts:assumeRole(self.role_ARN, |
| 132 | + self.role_session_name, |
| 133 | + self.policy, |
| 134 | + self.security_credentials_timeout, |
| 135 | + self.external_id) |
| 136 | + |
| 137 | + if (code ~= ngx.HTTP_OK) then |
| 138 | + ngx.log(ngx.WARN, "Could not get STS credentials. Response Code=", tostring(code), ", response body=", tostring(body)) |
| 139 | + return nil |
| 140 | + end |
| 141 | + |
| 142 | + local stsCreds = response.AssumeRoleResponse.AssumeRoleResult.Credentials |
| 143 | + stsCreds.ExpireAt = tostring(stsCreds["Expiration"]) |
| 144 | + stsCreds.ExpireAtTimestamp = stsCreds["Expiration"] |
| 145 | + -- stsCreds.Expiration is already an epoch time |
| 146 | +-- stsCreds.ExpireAtTimestamp = awsDate.convertDateStringToTimestamp(stsCreds.ExpireAt, true) |
| 147 | + |
| 148 | + -- store it in cache |
| 149 | + stsCache:put("sts_" .. tostring(self.role_ARN) .. "_" .. tostring(self.role_session_name), cjson.encode(stsCreds)) |
| 150 | + return stsCreds |
| 151 | +end |
| 152 | + |
| 153 | +function _M:getSecurityCredentials() |
| 154 | + --1. try to read it from the local cache |
| 155 | + local stsCreds = stsCache:get("sts_" .. tostring(self.role_ARN) .. "_" .. tostring(self.role_session_name)) |
| 156 | + if (stsCreds ~= nil) then |
| 157 | + local sts = cjson.decode(stsCreds) |
| 158 | + -- check to see if the cached credentials are still valid |
| 159 | + local now_in_secs = ngx.time() |
| 160 | + local expireAtTimestamp = sts.ExpireAtTimestamp or now_in_secs |
| 161 | + if (now_in_secs >= expireAtTimestamp) then |
| 162 | + ngx.log(ngx.WARN, "Current STS token expired " .. tostring(expireAtTimestamp - now_in_secs) .. " seconds ago. Obtaining a new token.") |
| 163 | + else |
| 164 | + return sts.AccessKeyId, sts.SecretAccessKey, sts.SessionToken, sts.ExpireAt, sts.ExpireAtTimestamp |
| 165 | + end |
| 166 | + end |
| 167 | + --2. get credentials from SecurityTokenService |
| 168 | + stsCreds = self:getCredentialsFromSTS() |
| 169 | + if (stsCreds == nil) then |
| 170 | + return "","" |
| 171 | + end |
| 172 | + return stsCreds.AccessKeyId, stsCreds.SecretAccessKey, stsCreds.SessionToken, stsCreds.ExpireAt, stsCreds.ExpireAtTimestamp |
| 173 | +end |
| 174 | + |
| 175 | +return _M |
| 176 | + |
0 commit comments