Updated on 2024-01-29 GMT+08:00

Python

Scenarios

To use Python to sign backend requests, obtain the Python SDK, import the project, and verify the backend signature by referring to the example provided in this section.

This section uses IntelliJ IDEA 2018.3.5 as an example.

Preparing the Environment

  • You have created a signature key on the APIG console and bound it to the API to be called. For more information, see Creating and Using a Signature Key.
  • You have obtained the signature key and secret. For details, see Pre-signature Preparations.
  • Log in to the APIG console, and download the SDK on the SDKs page by referring to section "SDKs" in the API Gateway User Guide. Or download the latest SDK version.
  • You have installed Python 2.7.9 or 3.X. If not, download the Python installation package from the official Python website and install it.
  • You have installed IntelliJ IDEA 2018.3.5 or a later version. If not, download it from the official IntelliJ IDEA website and install it.
  • You have installed the Python plug-in on IntelliJ IDEA. If not, install the Python plug-in according to Figure 1.
    Figure 1 Installing the Python plug-in

Importing a Project

  1. Start IntelliJ IDEA and choose File > New > Project.

    On the displayed New Project page, choose Python and click Next.

    Figure 2 New Python

  2. Click Next. Click ..., select the directory where the SDK is decompressed, and click Finish.

    Figure 3 Selecting the SDK directory of Python after decompression

  3. View the directory structure shown in the following figure.

    Figure 4 Directory structure

  4. Click Edit Configurations.

    Figure 5 Edit Configurations

  5. Click + and choose Flask server.

    Figure 6 Choosing Flask server

  6. Set Target type to Script path, select backend_signature.py from the Target drop-down list box, and click OK.

Backend Signature Verification Example

This example demonstrates how to build a Flask-based server as the backend of an API and implement a wrapper to verify the signature of requests sent from APIG (API Management).

Signature information is added to requests sent to access the backend of an API only after a signature key is bound to the API.

  1. Compile an interface that returns Hello World! Configure the GET, POST, PUT, and DELETE methods and the requires_apigateway_signature wrapper.

    1
    2
    3
    4
    5
    6
    app = Flask(__name__)
    
    @app.route("/<id>", methods=['GET', 'POST', 'PUT', 'DELETE'])
    @requires_apigateway_signature()
    def hello(id):
        return "Hello World!"
    

  2. Implement requires_apigateway_signature by putting the signature key and secret in a dict.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    def requires_apigateway_signature():
        def wrapper(f):
    
            secrets = {
            # Hard-coded or plaintext AK/SK is risky. For security, encrypt your AK/SK and store them in the configuration file or environment variables.
            # In this example, the AK/SK stored in the environment variables are used. Configure variables HUAWEICLOUD_SDK_AK1, HUAWEICLOUD_SDK_SK1, HUAWEICLOUD_SDK_AK2, and HUAWEICLOUD_SDK_SK2 in the local environment first.
                os.getenv('HUAWEICLOUD_SDK_AK1'): os.getenv('HUAWEICLOUD_SDK_SK1'),
                os.getenv('HUAWEICLOUD_SDK_AK2'): os.getenv('HUAWEICLOUD_SDK_SK2'),
    
            }
            authorizationPattern = re.compile(
                r'SDK-HMAC-SHA256\s+Access=([^,]+),\s?SignedHeaders=([^,]+),\s?Signature=(\w+)')
            BasicDateFormat = "%Y%m%dT%H%M%SZ"
    
            @wraps(f)
            def wrapped(*args, **kwargs):
                //Signature verification code
                ...
    
                return f(*args, **kwargs)
            return wrapped
        return wrapper
    

  3. Use a regular expression to parse the Authorization header. The key and signedHeaders are obtained. The wrapped function is used for signature verification.

    1
    2
    3
    4
    5
    6
    7
    8
    if "authorization" not in request.headers:
    	return 'Authorization not found.', 401
    authorization = request.headers['authorization']
    m = authorizationPattern.match(authorization)
    if m is None:
    	return 'Authorization format incorrect.', 401
    signingKey = m.group(1)
    signedHeaders = m.group(2).split(";")
    

    For example, for Authorization header:

    1
    SDK-HMAC-SHA256 Access=signature_key1, SignedHeaders=host;x-sdk-date, Signature=e11adf65a20d1b82c25419b5********8d0ba12fed1ceb13ed00
    

    The parsing result is as follows:

    1
    2
    signingKey=signature_key1
    signedHeaders=host;x-sdk-date
    

  4. Find secret based on key. If key does not exist, the authentication failed.

    1
    2
    3
    if signingKey not in secrets:
    	return 'Signing key not found.', 401
    signingSecret = secrets[signingKey]
    

  5. Create an HttpRequest, and add the method, URL, query, and signedHeaders headers to the request. Determine whether the body needs to be set.

    The body is read if there is no x-sdk-content-sha256 header with value UNSIGNED-PAYLOAD.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    r = signer.HttpRequest()
    r.method = request.method
    r.uri = request.path
    r.query = {}
    for k in request.query_string.decode('utf-8').split('&'):
    	spl = k.split("=", 1)
    	if len(spl) < 2:
    		r.query[spl[0]] = ""
    	else:
    		r.query[spl[0]] = spl[1]
    r.headers = {}
    needbody = True
    dateHeader = None
    for k in signedHeaders:
    	if k not in request.headers:
    		return 'Signed header ' + k + ' not found', 401
    	v = request.headers[k]
    	if k.lower() == 'x-sdk-content-sha256' and v == 'UNSIGNED-PAYLOAD':
    		needbody = False
    	if k.lower() == 'x-sdk-date':
    		dateHeader = v
    	r.headers[k] = v
    if needbody:
    	r.body = request.get_data()
    

  6. Check whether the signature has expired. Obtain the time from the X-Sdk-Date header, and check whether the difference between this time and the server time is within 15 minutes. If signedHeaders does not contain X-Sdk-Date, the authentication failed.

    1
    2
    3
    4
    5
    if dateHeader is None:
    	return 'Header x-sdk-date not found.', 401
    t = datetime.strptime(dateHeader, BasicDateFormat)
    if abs(t - datetime.utcnow()) > timedelta(minutes=15):
    	return 'Signature expired.', 401
    

  7. Invoke the verify method to verify the signature of the request, and check whether the verification is successful.

    1
    2
    3
    4
    5
    sig = signer.Signer()
    sig.Key = signingKey
    sig.Secret = signingSecret
    if not sig.Verify(r, m.group(3)):
    	return 'Verify authroization failed.', 401
    

  8. Run the server to verify the code. The following example uses the HTML signature tool in the JavaScript SDK to generate a signature.

    Set the parameters according to the following figure, and click Send request. Copy the generated curl command, execute it in the CLI, and check whether the server returns 200.

    If an incorrect key or secret is used, the server returns 401, which means authentication failure.