关于云计算:失去了SDK云计算将会怎样

56次阅读

共计 12495 个字符,预计需要花费 32 分钟才能阅读完成。

前言

最近,我花了一些工夫在编译、优化我的一台运行 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-python
defsign(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 -*-

importsys
importos
importdatetime
importhashlib
importhmac
importrequests
fromexceptions importHTTPError
fromexceptions importTimeout

ALGORITHM = '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')
returnsigning


defget_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_parameters
canonical_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, headers


defmain():
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:56

importsys
importos
importdatetime
importhashlib
importhmac
importjson
importrequests
fromexceptions importHTTPError
fromexceptions importTimeout

ALGORITHM = '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_signing


defget_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 headers


defmain():
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:AES256
x-amz-storage-class:${storageClass}

${headerList}
${payloadHash}"

# Hash it
canonicalRequestHash=$(printf '%s'"${canonicalRequest}"| openssl dgst -sha256 -hex 2>/dev/null | sed's/^.* //')

# 2. Create string to sign
stringToSign="\
${authType}
${dateValueL}
${dateValueS}/${region}/${service}/aws4_request
${canonicalRequestHash}"

# 3. Sign the string
signature=$(awsStringSign4 "${awsSecret}""${dateValueS}""${region}" "${service}" "${stringToSign}")

# Upload
curl -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 畛域利用、挪动利用以及机器学习等的开发,也从事过多个大型软件我的项目的设计、开发与项目管理。目前他专一于云计算以及互联网等技术畛域,致力于帮忙中国的开发者构建基于云计算的新一代的互联网利用。

正文完
 0