--[[ Copyright 2018 JobTeaser Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 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 CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --]] local cjson = require('cjson') local resty_hmac = require('resty.hmac') local resty_sha256 = require('resty.sha256') local str = require('resty.string') local _M = { _VERSION = '0.1.2' } local function get_credentials () local access_key = os.getenv('AWS_ACCESS_KEY_ID') local secret_key = os.getenv('AWS_SECRET_ACCESS_KEY') return { access_key = access_key, secret_key = secret_key } end local function get_iso8601_basic(timestamp) return os.date('!%Y%m%dT%H%M%SZ', timestamp) end local function get_iso8601_basic_short(timestamp) return os.date('!%Y%m%d', timestamp) end local function get_derived_signing_key(keys, timestamp, region, service) local h_date = resty_hmac:new('AWS4' .. keys['secret_key'], resty_hmac.ALGOS.SHA256) h_date:update(get_iso8601_basic_short(timestamp)) k_date = h_date:final() local h_region = resty_hmac:new(k_date, resty_hmac.ALGOS.SHA256) h_region:update(region) k_region = h_region:final() local h_service = resty_hmac:new(k_region, resty_hmac.ALGOS.SHA256) h_service:update(service) k_service = h_service:final() local h = resty_hmac:new(k_service, resty_hmac.ALGOS.SHA256) h:update('aws4_request') return h:final() end local function get_cred_scope(timestamp, region, service) return get_iso8601_basic_short(timestamp) .. '/' .. region .. '/' .. service .. '/aws4_request' end local function get_signed_headers() return 'host;x-amz-content-sha256;x-amz-date' end local function get_sha256_digest(s) local h = resty_sha256:new() h:update(s or '') return str.to_hex(h:final()) end local function get_hashed_canonical_request(timestamp, host, uri) local digest = get_sha256_digest(ngx.var.request_body) local canonical_request = ngx.req.get_method() .. '\n' .. uri .. '\n' .. '\n' .. 'host:' .. host .. '\n' .. 'x-amz-content-sha256:' .. digest .. '\n' .. 'x-amz-date:' .. get_iso8601_basic(timestamp) .. '\n' .. '\n' .. get_signed_headers() .. '\n' .. digest return get_sha256_digest(canonical_request) end local function get_string_to_sign(timestamp, region, service, host, uri) return 'AWS4-HMAC-SHA256\n' .. get_iso8601_basic(timestamp) .. '\n' .. get_cred_scope(timestamp, region, service) .. '\n' .. get_hashed_canonical_request(timestamp, host, uri) end local function get_signature(derived_signing_key, string_to_sign) local h = resty_hmac:new(derived_signing_key, resty_hmac.ALGOS.SHA256) h:update(string_to_sign) return h:final(nil, true) end local function get_authorization(keys, timestamp, region, service, host, uri) local derived_signing_key = get_derived_signing_key(keys, timestamp, region, service) local string_to_sign = get_string_to_sign(timestamp, region, service, host, uri) local auth = 'AWS4-HMAC-SHA256 ' .. 'Credential=' .. keys['access_key'] .. '/' .. get_cred_scope(timestamp, region, service) .. ', SignedHeaders=' .. get_signed_headers() .. ', Signature=' .. get_signature(derived_signing_key, string_to_sign) return auth end local function get_service_and_region(host) local patterns = { {'s3.amazonaws.com', 's3', 'us-east-1'}, {'s3-external-1.amazonaws.com', 's3', 'us-east-1'}, {'s3%-([a-z0-9-]+)%.amazonaws%.com', 's3', nil} } for i,data in ipairs(patterns) do local region = host:match(data[1]) if region ~= nil and data[3] == nil then return data[2], region elseif region ~= nil then return data[2], data[3] end end return 's3', 'auto' end function _M.aws_set_headers(host, uri) local creds = get_credentials() local timestamp = tonumber(ngx.time()) local service, region = get_service_and_region(host) local auth = get_authorization(creds, timestamp, region, service, host, uri) ngx.req.set_header('Authorization', auth) ngx.req.set_header('Host', host) ngx.req.set_header('x-amz-date', get_iso8601_basic(timestamp)) end function _M.s3_set_headers(host, uri) _M.aws_set_headers(host, uri) ngx.req.set_header('x-amz-content-sha256', get_sha256_digest(ngx.var.request_body)) end return _M