使用IAM授权无效的Pytnon Websocket订阅AWS Appsync

使用IAM授权无效的Pytnon Websocket订阅AWS Appsync,websocket,amazon-iam,subscription,aws-appsync,Websocket,Amazon Iam,Subscription,Aws Appsync,我遵循了这些文件: 使python websocket客户端使用IAM授权订阅aws appsync。 值得一提的是,我成功地获得了IAM授权以使用变异客户端,但使用相同的实现和IAM用户无法使用订阅客户端。以下是订阅客户端的代码: # subscription_client.py from base64 import b64encode, decode from datetime import datetime from uuid import uuid4 import websock

我遵循了这些文件:

使python websocket客户端使用IAM授权订阅aws appsync。 值得一提的是,我成功地获得了IAM授权以使用变异客户端,但使用相同的实现和IAM用户无法使用订阅客户端。以下是订阅客户端的代码:

# subscription_client.py

from base64 import b64encode, decode
from datetime import datetime
from uuid import uuid4

import websocket, hashlib, hmac, datetime
import threading

import json

# Constants Copied from AppSync API 'Settings'
API_URL = "***"
receiverId = "***"

# GraphQL subscription Registration object
request_parameters = json.dumps({
        'query': 'subscription MySubscription {  receivedMessage(receiverId: "'+ receiverId +'") {receiverId}}',
        'variables': {}
})

# Discovered values from the AppSync endpoint (API_URL)
WSS_URL = API_URL.replace('https','wss').replace('appsync-api','appsync-realtime-api')
HOST = API_URL.replace('https://','').replace('/graphql','')

# Subscription ID (client generated)
SUB_ID = str(uuid4())

# Set up Timeout Globals
timeout_timer = None
timeout_interval = 10

# Calculate UTC time in ISO format (AWS Friendly): YYYY-MM-DDTHH:mm:ssZ
def header_time():
    return datetime.utcnow().isoformat(sep='T',timespec='seconds') + 'Z'

# Encode Using Base 64
def header_encode( header_obj ):
    return b64encode(json.dumps(header_obj).encode('utf-8')).decode('utf-8')

# reset the keep alive timeout daemon thread
def reset_timer( ws ):
    global timeout_timer
    global timeout_interval

    if (timeout_timer):
        timeout_timer.cancel()
    timeout_timer = threading.Timer( timeout_interval, lambda: ws.close() )
    timeout_timer.daemon = True
    timeout_timer.start()

# Create IAM credentials header

def sign(key, msg):
    return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()

def getSignatureKey(key, date_stamp, regionName, serviceName):
    kDate = sign(('AWS4' + key).encode('utf-8'), date_stamp)
    kRegion = sign(kDate, regionName)
    kService = sign(kRegion, serviceName)
    kSigning = sign(kService, 'aws4_request')
    return kSigning

t = datetime.datetime.utcnow()
amz_date = t.strftime('%Y%m%dT%H%M%SZ')
date_stamp = t.strftime('%Y%m%d')
access_key = os.environ.get('AWS_ACCESS_KEY_ID')
secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY')
canonical_uri = '/graphql/connect'
region = 'eu-central-1'
service = 'appsync'
canonical_querystring = ''
method = 'POST'
content_type = 'application/json; charset=UTF-8'
canonical_headers = 'content-type:' + content_type + '\n' + 'host:' + HOST + '\n' + 'x-amz-date:' + amz_date + '\n'
signed_headers = 'content-type;host;x-amz-date'
payload_hash = hashlib.sha256(request_parameters.encode('utf-8')).hexdigest()
canonical_request = method + '\n' + canonical_uri + '\n' + canonical_querystring + '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash

algorithm = 'AWS4-HMAC-SHA256'
credential_scope = date_stamp + '/' + region + '/' + service + '/' + 'aws4_request'
string_to_sign = algorithm + '\n' +  amz_date + '\n' +  credential_scope + '\n' +  hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()

signing_key = getSignatureKey(secret_key, date_stamp, region, service)
signature = hmac.new(signing_key, (string_to_sign).encode('utf-8'), hashlib.sha256).hexdigest()
authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + credential_scope + ', ' +  'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature

iam_header = {
    "accept": "application/json, text/javascript",
    "content-encoding": "amz-1.0",
    "content-type": "application/json; charset=UTF-8",
    "host":HOST,
    "x-amz-date": amz_date,
    "Authorization": authorization_header
}

# Socket Event Callbacks, used in WebSocketApp Constructor
def on_message(ws, message):
    global timeout_timer
    global timeout_interval

    print('### message ###')
    print('<< ' + message)

    message_object = json.loads(message)
    message_type   = message_object['type']

    if( message_type == 'ka' ):
        reset_timer(ws)

    elif( message_type == 'connection_ack' ):
        timeout_interval = int(json.dumps(message_object['payload']['connectionTimeoutMs']))

        register = {
            'id': SUB_ID,
            'payload': {
                'data': request_parameters,
                'extensions': {
                    'authorization': {
                        "accept": "application/json, text/javascript",
                        "content-encoding": "amz-1.0",
                        "content-type": "application/json; charset=UTF-8",
                        "host":HOST,
                        "x-amz-date": amz_date,
                        "Authorization": authorization_header
                    }
                }
            },
            'type': 'start'
        }
        start_sub = json.dumps(register)
        print('>> '+ start_sub )
        ws.send(start_sub)

    elif(message_object['type'] == 'error'):
        print ('Error from AppSync: ' + message_object['payload'])
    
def on_error(ws, error):
    print('### error ###')
    print(error)

def on_close(ws):
    print('### closed ###')

def on_open(ws):
    print('### opened ###')
    init = {
        'type': 'connection_init'
    }
    init_conn = json.dumps(init)
    print('>> '+ init_conn)
    ws.send(init_conn)

if __name__ == '__main__':
    # Uncomment to see socket bytestreams
    #websocket.enableTrace(True)

    # Set up the connection URL, which includes the Authentication Header
    #   and a payload of '{}'.  All info is base 64 encoded
    connection_url = WSS_URL + '?header=' + header_encode(iam_header) + '&payload=e30='

    # Create the websocket connection to AppSync's real-time endpoint
    #  also defines callback functions for websocket events
    #  NOTE: The connection requires a subprotocol 'graphql-ws'
    print( 'Connecting to: ' + connection_url )

    ws = websocket.WebSocketApp( connection_url,
                            subprotocols=['graphql-ws'],
                            on_open = on_open,
                            on_message = on_message,
                            on_error = on_error,
                            on_close = on_close,)

    ws.run_forever()
#订阅_client.py
从base64导入B64编码,解码
从日期时间导入日期时间
从uuid导入uuid4
导入websocket、hashlib、hmac、datetime
导入线程
导入json
#从AppSync API“设置”复制的常量
API_URL=“***”
receiverId=“***”
#GraphQL订阅注册对象
请求_参数=json.dumps({
'query':'subscription MySubscription{receivedMessage(receiverId:“+receiverId+”){receiverId}},
'变量':{}
})
#从AppSync终结点(API_URL)发现的值
WSS_URL=API_URL.replace('https','WSS')。replace('appsync-API','appsync-realtime-API'))
HOST=API_URL.replace('https://','').replace('/graphql','')
#订阅ID(客户端生成)
SUB_ID=str(uuid4())
#设置全局超时
超时\计时器=无
超时时间间隔=10
#以ISO格式计算UTC时间(AWS友好型):YYYY-MM-DDTHH:MM:ssZ
def头_时间():
return datetime.utcnow().isoformat(sep='T',timespec='seconds')+'Z'
#使用Base 64进行编码
def收割台编码(收割台对象):
返回b64encode(json.dumps(header_obj).encode('utf-8')).decode('utf-8'))
#重置保持活动超时守护程序线程
def复位定时器(ws):
全局超时计时器
全局超时时间间隔
如果(超时计时器):
超时\u计时器。取消()
timeout\u timer=threading.timer(timeout\u interval,lambda:ws.close())
timeout\u timer.daemon=True
timeout\u timer.start()超时
#创建IAM凭据标头
def标志(键,消息):
返回hmac.new(key,msg.encode(“utf-8”),hashlib.sha256.digest()
def getSignatureKey(密钥、日期戳、区域名称、服务名称):
kDate=符号(('AWS4'+键)。编码('utf-8'),日期戳)
kRegion=符号(kDate,regionName)
K服务=标志(kRegion,serviceName)
kSigning=sign(kService,“aws4\U请求”)
返回信号
t=datetime.datetime.utcnow()
amz_date=t.strftime(“%Y%m%dT%H%m%SZ”)
日期戳=t.strftime(“%Y%m%d”)
access\u key=os.environ.get('AWS\u access\u key\u ID')
secret\u key=os.environ.get('AWS\u secret\u ACCESS\u key'))
规范的_uri='/graphql/connect'
地区='eu-central-1'
服务='appsync'
规范_查询字符串=“”
方法='POST'
content_type='应用程序/json;字符集=UTF-8'
规范_头='内容类型:'+content_类型+'\n'+'主机:'+host+'\n'+'x-amz-date:'+amz_日期+'\n'
签名的_头='内容类型;主办x-amz-date'
有效负载\u hash=hashlib.sha256(请求\u参数.encode('utf-8')).hexdigest()
canonical_请求=方法+'\n'+规范_uri+'\n'+规范_查询字符串+'\n'+规范_头+'\n'+签名_头+'\n'+有效负载\u哈希
算法='AWS4-HMAC-SHA256'
凭证\范围=日期\戳记+'/'+区域+'/'+服务+'/'+'aws4\请求'
string_to_sign=algorithm+'\n'+amz_date+'\n'+credential_scope+'\n'+hashlib.sha256(规范的请求.encode('utf-8')).hexdigest()
签名密钥=getSignatureKey(密钥、日期戳、地区、服务)
signature=hmac.new(signing_key,(string_to_sign).encode('utf-8'),hashlib.sha256).hexdigest()
授权头=算法+'+'凭证='+访问密钥+'/'+凭证范围+','+'签名头='+签名头+','+'签名='+签名
iam_标题={
“接受”:“应用程序/json,文本/javascript”,
“内容编码”:“amz-1.0”,
“内容类型”:“应用程序/json;字符集=UTF-8”,
“主持人”:主持人,
“x-amz-date”:amz_日期,
“授权”:授权头
}
#套接字事件回调,在WebSocketApp构造函数中使用
def on_消息(ws,消息):
全局超时计时器
全局超时时间间隔
打印(“####消息####”)
打印(“>”+开始子项)
ws.send(启动子节点)
elif(消息对象['type']=='error'):
打印('来自AppSync的错误:'+message_对象['payload']))
def on_错误(ws,错误):
打印(“错误”)
打印(错误)
def on_关闭(ws):
打印('#########')
def打开(ws):
打印(已打开)
初始={
'type':'connection_init'
}
init_conn=json.dumps(init)
打印('>>'+init_conn)
ws.send(初始连接)
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
#取消注释以查看socket ByTestStreams
#websocket.enableTrace(真)
#设置连接URL,其中包括身份验证标头
#和{}的有效负载。所有信息都是基于64位编码的
连接\u url=WSS\u url+'?头='+头\u编码(iam\u头)+'&有效负载=e30='
#创建到AppSync实时端点的websocket连接
#还定义websocket事件的回调函数
#注意:连接需要一个子目录“graphql ws”
打印('连接到:'+连接\u url)
ws=websocket.WebSocketApp(连接url,
子目录=['graphql-ws'],
开着=开着,
on_message=on_message,
on_错误=on_错误,
on_close=on_close,)
ws.run_forever()
下面是我运行脚本时包含错误的输出:

(aws) ➜  appsync-websockets-python git:(master) ✗ python subscription_client.py
Connecting to: wss://4akngv4mibcr3jz6ffzu3vcnji.appsync-realtime-api.eu-central-1.amazonaws.com/graphql?header=eyJhY2NlcHQiOiAiYXBwbGljYXRpb24vanNvbiwgdGV4dC9qYXZhc2NyaXB0IiwgImNvbnRlbnQtZW5jb2RpbmciOiAiYW16LTEuMCIsICJjb250ZW50LXR5cGUiOiAiYXBwbGljYXRpb24vanNvbjsgY2hhcnNldD1VVEYtOCIsICJob3N0IjogIjRha25ndjRtaWJjcjNqejZmZnp1M3ZjbmppLmFwcHN5bmMtYXBpLmV1LWNlbnRyYWwtMS5hbWF6b25hd3MuY29tIiwgIngtYW16LWRhdGUiOiAiMjAyMTA1MjhUMDkzODMzWiIsICJBdXRob3JpemF0aW9uIjogIkFXUzQtSE1BQy1TSEEyNTYgQ3JlZGVudGlhbD1BS0lBNUxNQVdJMjZNSUw3N0VIQi8yMDIxMDUyOC9ldS1jZW50cmFsLTEvYXBwc3luYy9hd3M0X3JlcXVlc3QsIFNpZ25lZEhlYWRlcnM9Y29udGVudC10eXBlO2hvc3Q7eC1hbXotZGF0ZSwgU2lnbmF0dXJlPTA4ZjUzNWE0N2M0ZGI1MmQ3YWE3ODU3Mzc1MjZlYTJmN2Q0YjY2NWY2NGViYTYwYzM2Mjk3YjU4NmFjZDZmNDgifQ==&payload=e30=
### opened ###
>> {"type": "connection_init"}
### message ###
<< {"payload":{"errors":[{"errorType":"com.amazonaws.deepdish.graphql.auth#BadRequestException","message":"The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.\n\nThe Canonical String for this request should have been\n'POST\n/graphql/connect\n\ncontent-type:application/json; charset=UTF-8\nhost:4akngv4mibcr3jz6ffzu3vcnji.appsync-api.eu-central-1.amazonaws.com\nx-amz-date:20210528T093833Z\n\ncontent-type;host;x-amz-date\n44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a'\n\nThe String-to-Sign should have been\n'AWS4-HMAC-SHA256\n20210528T093833Z\n20210528/eu-central-1/appsync/aws4_request\n1bb8f8f7e4a68a2b3ee51853ca2280b25fea64cdf5b5958f38e918f09beaa65f'\n","errorCode":400}]},"type":"connection_error"}
### error ###
Connection is already closed.
### closed ###
(aws)➜  appsync websockets python git:(主)✗ python订阅\u client.py
连接到:wss://4akngv4mibcr3jz6ffzu3vcnji.appsync-realtime-api.eu-central-1.amazonaws.com/graphql?header=eyJhY2NlcHQiOiAiYXBwbGljYXRpb24vanNvbiwgdGV4dC9qYXZhc2NyaXB0IiwgImNvbnRlbnQtZW5jb2RpbmciOiAiYW16LTEuMCIsICJjb250ZW50LXR5cGUiOiAiYXBwbGljYXRpb24vanNvbjsgY2hhcnNldD1VVEYtOCIsICJob3N0IjogIjRha25ndjRtaWJjcjNqejZmZnp1M3ZjbmppLmFwcHN5bmMtYXBpLmV1LWNlbnRyYWwtMS5HBWF6B25HD3MUY29TIIWGINGTYW16WRHDGU