前言
最近,我花了一些工夫在编译、优化我的一台运行OpenWRT的路由器,型号是 Linksys WRT 1900ACS 。为了扩大路由器的性能,就须要在OpenWRT上开发一些新的性能,尤其是须要应用到亚马逊云科技上的一些乏味的服务。当我习惯性地开始应用Amazon SDK 的时候才忽然意识到,在一台硬件配置不高,软件极度精简的零碎中应用这些 SDK 无疑是一件极为侈靡的想法。即便如我手上的这台硬件配置颇高的路由器,也不过只有128M 的存储、512M的内存资源而已。
这或者让我的工作更加乏味,让我能够更深刻地去钻研亚马逊云科技的API的调用机制以及如何更无效应用,而不是依赖于高度封装好的SDK。这个工作的次要挑战是胜利的执行通过身份验证的Amazon REST API申请,例如EC2 的平安组、VPC的ACL规定、Amazon S3上文件的存取以及其它一些有意思的性能。
为什么?
我的工作并非如“黑客”那样的非法应用零碎。事实上亚马逊云科技早就针对REST API 的调用提供了规范的调用接口,其中最要害的环节就是名为Signatura Version 4 的API申请报头的签名过程。在亚马逊云科技的文档中对这个流程有具体的介绍,但我置信应该只有很少的人可能读完这个文档,起因是因为这个过程切实是繁-琐-无-比。理智的开发者通常会疏忽这些API而更习惯于应用各类亚马逊云科技的SDK。甚至,某些简略的工作也齐全能够通过Amazon-Cli,应用一段脚本来解决问题。
然而,就像我的状况一样。某些场景下可能无奈应用实用于工作平台或者编程语言的SDK,这些需要包含但不仅限于这些
1.资源限度。例如嵌入式环境中
2.性能要求。例如性能较低的CPU这是我做的一个简略的性能比照。场景是针对S3 上的一个文件下载到本地
3.SDK的缺失。例如macOS 上的Amazon SDK
4.短少特定的语言的SDK。例如Rust等 (注:rusoto为非官方的SDK包)
5.现有SDK性能的缺失。例如 Amazon Transcribe 实时转录的性能
5.缩小依赖。例如应用Python 的boto3, 就须要装置这样的一些依赖项python3、python3-yaml、python3-pyasn1、python3-botocore、python3-rsa、 python3-colorama、python3-docutils、python3-s3transfer 等等
此外,理解并把握了Amazon REST API 的细节,对于开发人员在进行系统优化、架构设计以及晋升零碎安全性等方面肯定大有裨益。
咱们须要的工具
对于这项工作,咱们将会用到:
1.python3+ (python2 实践上也能够实现,但我没有去尝试)2.能够装置Python 的requests 包(pip3 install requests)。也能够应用Python内置的urllib而不必requests。
2.文本编辑器 (例如我罕用的vim)
3.curl (用来申请 Web 服务的命令行工具)
4.openssl (平安通信的根底软件包)
5.sed (一种流编辑器,罕用于Linux 脚本中)
咱们将应用这些工具别离在Python程序以及shell 脚本中实现对于Amazon API的调用。通常,亚马逊云科技的SDK (例如用于Python的boto3)会帮忙咱们的利用主动实现申请的签名,因而对于开发者来说这个环节是通明的。而对于明天的这个工作咱们将须要本人入手实现最重要的签名的操作。
相干的参考实现
相似于我的这个想法,早就有人实际过并分享进去。其中较为出名的有这样几个:
1.requests-amazon4auth
https://github.com/sam-washin...
Amazon Web Service身份验证版本4 的Python Request库的
2.amazon-requests-auth
https://github.com/DavidMulle...
亚马逊云科技签名版本4签名过程的Python requests module
3.amazon-request-signer
https://github.com/iksteen/aw...
应用亚马逊云科技签名V4签订亚马逊云科技申请的Python库
上述3个开源的Python库,除了最初一个在4个月前有过更新以外,其它的两个曾经超过2年以上没有更新了,很难有信念去应用啊!最初介绍的一个比拟乏味,因为这个办法没有应用boto3 却利用botocore 来实现签名,算是一种投机取巧的做法。
# Key derivation functions. See:# http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-pythondefsign(key, msg):returnnew(key, msg.encode('utf-8'), hashlib.sha256).digest()defgetSignatureKey(key, dateStamp, regionName, serviceName):kDate = sign(('AWS4' + key).encode('utf-8'), dateStamp)kRegion = sign(kDate, regionName)kService = sign(kRegion, serviceName)kSigning = sign(kService, 'aws4_request')returnkSigning
为什么须要对API的申请签名?
简直亚马逊云科技所有服务的每一个性能都提供了一个API,并且这些API都是REST API。这就意味着咱们能够通过HTTP 申请的形式实现对于Amazon API 的调用。实现这样的调用是非常简单的事件,然而咱们还须要在这个调用过程中满足这样的三个需要:
1.验证请求者的身份
确保申请是由某个具备无效拜访密钥的用户发送的
2.爱护传输中的数据
为了避免传输时申请被篡改,一些申请元素将用于计算申请的哈希(摘要),失去的哈希值将包含在申请中。在Amazon服务收到申请时,它将应用雷同信息计算哈希,并将其与申请中包含的哈希值进行匹配。如果值不匹配,Amazon将拒绝请求。
3.避免潜在的反演攻打
在大多数状况下,申请必须在申请中的工夫戳的5分钟内达到Amazon。否则,Amazon将回绝该申请。
这就引入了十分重要的一个办法-签名申请。当咱们的利用将HTTP 申请发送到Amazon时,须要对申请签名,以便Amazon可能辨认发送它们的用户。应用Amazon拜访密钥来签名申请,该拜访密钥蕴含拜访密钥 ID 和机密拜访密钥。有一些申请不须要签名,如发送到Amazon S3的匿名申请以及Amazon STS 中的一些 API 操作以外,其它的API 申请都须要签名。
Signature Version 4 的工作流程
要对申请签名,先要计算申请的哈希 (摘要)值。而后,应用这个哈希值、来自申请的其余一些信息以及Amazon私密拜访密钥,计算另一个称为“签名”的哈希值。
1.针对签名版本 4 创立标准申请将申请的内容(主机、操作、标头等)组织为规范(标准)格局。标准申请是用于创立待签字符串的输出之一。申请标准具备以下格局:
“ HTTP_Method” \ n“ Canonical_URI” \ n“ Canonical_Query” \ n“ Canonical_Headers” \ n“ Signed_Headers” \ n“ Request_payload”
2.创立签名版本 4 的待签字符串应用标准申请和额定信息(例如算法、申请日期、凭证范畴和标准申请的摘要(哈希))创立待签字符串。字符串具备以下格局:
“AWS4-HMAC-SHA256”\n “UTC 日期” \n“日期/区域ID / s3 / aws4_request” \ n“ Canonical_str”
3.为 Amazon Signature 版本 4 计算签名应用Amazon机密拜访密钥作为初始哈希操作的密钥,对申请日期、区域和服务执行一系列加密哈希操作(HMAC 操作),从而派生签名密钥。在派生签名密钥后,通过看待签字符串执行加密哈希操作来计算签名。应用派生的签名密钥作为此操作的哈希密钥。格局如下:
MAC_SHA256(HMAC_SHA256(HMAC_SHA256(HMAC_SHA256(“Amazon4”机密密钥,日期),区域ID),“ s3”),“amazon4_request”)
4.向 HTTP 申请增加签名在计算签名后,将其增加到申请的 HTTP 标头或查问字符串中。具体说来,就是应用步骤3中的签名密钥,将步骤2中创立的签名字符串的SHA256 HMAC计算结果转换为十六进制字符。格局如下:
HMAC_SHA(签名密钥,签名字符串)
接下来,能够通过以下两种形式之一将签名增加到申请:
1.应用 HTTP Authorization 标头
2.将查问字符串值增加到申请中。因为签名是 URL 的一部分,因而这类 URL 被称为预签名URL
Amazon服务收到申请后,将执行您实现的雷同步骤来计算申请中发送的签名。之后,Amazon会将计算失去的签名与您在申请中发送的签名进行比拟。如果签名匹配,则解决申请。如果签名不匹配,则拒绝请求。
对于实现的细节,咱们能够通过两个要害的函数一窥到底(Python 代码)
#!/usr/bin/env python3# -*- coding:utf-8 -*-importsysimportosimportdatetimeimporthashlibimporthmacimportrequestsfromexceptions importHTTPErrorfromexceptions importTimeoutALGORITHM = 'AWS4-HMAC-SHA256'METHOD = 'GET'def_sign(key, msg):returnnew(key, msg.encode('utf-8'), hashlib.sha256).digest()defget_SignatureKey(key, dateStamp, regionName, serviceName):date = _sign(('AWS4'+ key).encode('utf-8'), dateStamp)region = _sign(date, regionName)service = _sign(region, serviceName)signing = _sign(service, 'aws4_request')returnsigningdefget_key():returnenviron.get('AWS_ACCESS_KEY_ID'), \environ.get('AWS_SECRET_ACCESS_KEY')defget_datetime():current = datetime.datetime.utcnow()returnstrftime('%Y%m%dT%H%M%SZ'), current.strftime('%Y%m%d')defget_endpoint(service, region):return'https://{}.{}.amazonaws.com'.format(service, region)defget_host(endpoint):returnreplace('https://', '')defget_reqUrl(endpoint, canonical_querystring):return'{}?{}'.format(endpoint, canonical_querystring)defget_header(region, service, request_parameters):amzdate, datestamp = get_datetime()endpoint = get_endpoint(service, region)host = get_host(endpoint)access_key, secret_key = get_key()ifaccess_key is None or secret_key is None:print('No access key is available.')exit()canonical_uri = '/'canonical_querystring = request_parameterscanonical_headers = 'host:{}\nx-amz-date:{}\n'.format(host, amzdate)signed_headers = 'host;x-amz-date'payload_hash = hashlib.sha256(('').encode('utf-8')).hexdigest()canonical_request = '{}\n{}\n{}\n{}\n{}\n{}'.format(METHOD,canonical_uri,canonical_querystring,canonical_headers,signed_headers,payload_hash)credential_scope = '{}/{}/{}/aws4_request'.format(datestamp, region, service)string_to_sign = '{}\n{}\n{}\n{}'.format(ALGORITHM,amzdate,credential_scope,sha256(encode('utf-8')).hexdigest())signing_key = get_SignatureKey(secret_key, datestamp, region, service)signature = hmac.new(signing_key,(string_to_sign).encode('utf-8'),sha256).hexdigest()authorization_header = \'{} Credential={}/{},SignedHeaders={},Signature={}'.format(ALGORITHM,access_key,credential_scope,signed_headers,signature)headers = {'x-amz-date': amzdate, 'Authorization': authorization_header}request_url = get_reqUrl(endpoint, canonical_querystring)return request_url, headersdefmain():service = 'ec2'region = 'us-west-1'action = 'DescribeInstances'\'&Filter.1.Name=instance-state-name&Filter.1.Value.1=running'version = "2016-11-15"request_parameters = 'Action={}&Version={}'.format(action, version)request_url, headers = get_header(region, service, request_parameters)print('\nBEGIN REQUEST++++++++++++++++++++++++++++++++++++')print('Request URL = {}'.format(request_url))print('Request header = {}'.format(str(headers)))try:res = requests.get(request_url, headers=headers, timeout=(2, 5))except Timeout:print('The request timed out')except HTTPError as http_err:print(f'HTTP error occurred: {http_err}')except Exception as err:print(f'Other error occurred: {err}')else:print('\nRESPONSE++++++++++++++++++++++++++++++++++++')print('Response code: %d\n' % res.status_code)print(res.text)if__name__ == "__main__":main()
应用Python3、rrequests 实现的对于Amazon Translate 的调用,实现英文-中文的翻译
#!/usr/bin/env python3# -*- coding:utf-8 -*-# Writen by Lianghong2020-03-12 11:42:56importsysimportosimportdatetimeimporthashlibimporthmacimportjsonimportrequestsfromexceptions importHTTPErrorfromexceptions importTimeoutALGORITHM = 'AWS4-HMAC-SHA256'METHOD = 'POST'def_sign(key, msg):returnnew(key, msg.encode("utf-8"), hashlib.sha256).digest()defget_SignatureKey(key, datestamp, regionName, serviceName):k_date = _sign(('AWS4'+ key).encode('utf-8'), datestamp)k_region = _sign(k_date, regionName)k_service = _sign(k_region, serviceName)k_signing = _sign(k_service, 'aws4_request')returnk_signingdefget_key():returnenviron.get('AWS_ACCESS_KEY_ID'), \environ.get('AWS_SECRET_ACCESS_KEY')defget_datetime():current = datetime.datetime.utcnow()returnstrftime('%Y%m%dT%H%M%SZ'), current.strftime('%Y%m%d')defget_host(service, region):return'{}.{}.amazonaws.com'.format(service, region)defget_header(service, region, request_parameters):access_key, secret_key = get_key()ifaccess_key is None or secret_key is None:print('No access key is available.')exit()amz_date, date_stamp = get_datetime()host = get_host(service, region)canonical_uri = '/'canonical_querystring = ''content_type = 'application/x-amz-json-1.1'amz_target = 'AWSShineFrontendService_20170701.TranslateText'canonical_headers = \'content-type:{}\nhost:{}\nx-amz-date:{}\nx-amz-target:{}\n'.format(content_type,host,amz_date,amz_target)signed_headers = 'content-type;host;x-amz-date;x-amz-target'payload_hash = hashlib.sha256(encode('utf-8')).hexdigest()canonical_request = '{}\n{}\n{}\n{}\n{}\n{}'.format(METHOD,canonical_uri,canonical_querystring,canonical_headers,signed_headers,payload_hash)credential_scope = '{}/{}/{}/aws4_request'.format(date_stamp, region, service)string_to_sign = '{}\n{}\n{}\n{}'.format(ALGORITHM, amz_date, credential_scope,sha256(canonical_request.encode('utf-8')).hexdigest())signing_key = get_SignatureKey(secret_key, date_stamp, region, service)signature = hmac.new(signing_key,(string_to_sign).encode('utf-8'),sha256).hexdigest()authorization_header = \'{} Credential={}/{},SignedHeaders={},Signature={}'.format(ALGORITHM,access_key,credential_scope,signed_headers,signature)headers = {'Content-Type': content_type,'X-Amz-Date': amz_date,'X-Amz-Target': amz_target,'Authorization': authorization_header}return headersdefmain():service = 'translate'region = 'ap-northeast-1'host = get_host(service, region)endpoint = 'https://{}/'.format(host)text = 'Amazon Translate is a text translation service that use '\'advanced machine learning technologies to provide high-quality '\'translation on demand. You can use Amazon Translate to translate '\'unstructured text documents or to build applications that work in '\'multiple languages.'\'Amazon Translate provides translation between a source language '\'(the input language) and a target language (the output language). ' \'A source language-target language combination is known as a '\'language pair.'source_lang_code = 'en'target_lang_code = 'zh'request_parameters = '{{"{}": "{}","{}": "{}","{}": "{}"}}'.format("Text",text,"SourceLanguageCode",source_lang_code,"TargetLanguageCode",target_lang_code)headers = get_header(service, region, request_parameters)# print('endpoint is ==>\n{}\n'.format(endpoint))# print('request_parameters is ==>\n{}\n'.format(request_parameters))# print('headers is ==>\n{}\n'.format(headers))try:res = requests.post(endpoint,data=request_parameters,headers=headers)except Timeout:print('The request timed out')except HTTPError as http_err:print(f'HTTP error occurred: {http_err}')except Exception as err:print(f'Other error occurred: {err}')else:json_content = json.loads(res.text)print('The original is -->\n{}\n'.format(text))print('The translation is -->\n{}\n'.format(json_content['TranslatedText']))# print('Response:\n\t{}'.format(res.text))if__name__ == "__main__":main()
如果不喜爱Python也没有关系。即便shell的脚本仅仅应用curl、openssl以及sed,就能够实现上传文件到Amazon S3的存储桶之中的操作
content-type:${contentType}host:${bucket}${baseUrl}x-amz-content-sha256:${payloadHash}x-amz-date:${dateValueL}x-amz-server-side-encryption:AES256x-amz-storage-class:${storageClass}${headerList}${payloadHash}"# Hash itcanonicalRequestHash=$(printf '%s'"${canonicalRequest}"| openssl dgst -sha256 -hex 2>/dev/null | sed 's/^.* //')# 2. Create string to signstringToSign="\${authType}${dateValueL}${dateValueS}/${region}/${service}/aws4_request${canonicalRequestHash}"# 3. Sign the stringsignature=$(awsStringSign4 "${awsSecret}""${dateValueS}" "${region}" "${service}" "${stringToSign}")# Uploadcurl -s -L --proto-redir =https -X "${httpReq}"-T "${fileLocal}" \-H "Content-Type: ${contentType}" \-H "Host: ${bucket}${baseUrl}" \-H "X-Amz-Content-SHA256: ${payloadHash}" \-H "X-Amz-Date: ${dateValueL}" \-H "X-Amz-Server-Side-Encryption: AES256" \-H "X-Amz-Storage-Class: ${storageClass}" \-H "Authorization: ${authType} Credential=${awsAccess}/${dateValueS}/${region}/${service}/aws4_request, SignedHeaders=${headerList}, Signature=${signature}" \"https://${bucket}${baseUrl}/${fileRemote}"
纸上得来终觉浅,绝知此事要躬行。最后开始浏览Signature Version 4 的文档倍觉繁琐,简直不能坚持下去。屡经挫折,尤其是那个脚本实现的S3上传的例子足足折磨了我一天的工夫。然而当胜利的实现几个例子之后就登时感觉死记硬背,骑虎难下了。这个小小的实际,让我对于Amazon API的设计与实现有了更进一层的理解。
参考资料
Signatura Version:
https://docs.aws.amazon.com/z...
Amazon-Cli:
https://aws.amazon.com/it/cli/
本篇作者
费良宏
Amazon Web Services Principal Developer Advocate
在过来的20多年始终从事软件架构、程序开发以及技术推广等畛域的工作。他常常在各类技术会议上发表演讲进行分享,他还是多个技术社区的热心参与者。他善于Web畛域利用、挪动利用以及机器学习等的开发,也从事过多个大型软件我的项目的设计、开发与项目管理。目前他专一于云计算以及互联网等技术畛域,致力于帮忙中国的开发者构建基于云计算的新一代的互联网利用。