Determining your AWS Support Level via the SupportPlans API

Learn how to craft an API request against an undocumented AWS API

Posted by Steven Tan on 10th October 2022

I'm sure we all have looked at the AWS Console, used a feature and thought "I'd like to automate this process", only to find out that the API calls you are looking for do not exist in the AWS SDKs (boto3, aws-sdk etc). This may be frustrating as you are left 3 not-so-great alternatives.

  1. Wait for the SDKs to support the feature you are looking for
  2. Try to achieve your desired result in a round-about way
  3. Perform Click-Ops for only those specific processes

Sometimes, the feature you are looking for is only a quality of life improvement and you are able to wait.. But other times this action needs to be performed regularly and at scale and performing click-ops becomes problematic.

There was a tweet by @gergnz a few months ago to see if anyone knew whether you can determine your AWS support level via any of the exposed AWS APIs. The general consensus from the community and AWS Support seemed to agree that this is was not yet doable. I took this on as a challenge and came up with 2 methods to achieve this.

I've release an open-source CLI tool to retrieve the information of your AWS support levels across an entire org, a list of AWS Account IDs or the current AWS account. To use the tool, visit my GitHub Repository - sktan/aws-support-level.

Method 1 - AWS Severity Level

After a bit of research, I've found that after making an AWS Support Describe Severity Levels API call, you will receive 2 critical pieces of information:

  1. Whether or not you have an AWS Support subscription (determined by the lack of SubscriptionRequiredException error)
  2. The severity levels of support tickets you can create (assuming the above hasn't given you an error)

This provides just enough information to determine your support subscription as if you have a look at the Choosing a Severity section in the AWS Support User Guide, it provides a table of what support subscriptions gives you in terms of support levels. To summarise the relevant information:

Severity Level Support Plan
low Developer, Business, Enterprise On-Ramp, or Enterprise Support
normal Developer, Business, Enterprise On-Ramp, or Enterprise Support
high Business, Enterprise On-Ramp, or Enterprise Support
urgent Business, Enterprise On-Ramp or Enterprise Support
critical Enterprise Support

With this table, I can determine which level of support I have access to based on the API results and when putting this together, the code looked like this:

import boto3

def get_support_severity_levels():
    client = boto3.client(
        service_name="support", region_name="us-east-1"
    )

    try:
        response = client.describe_severity_levels(language="en")

        severity_levels = []
        for severity_level in response["severityLevels"]:
            severity_levels.append(severity_level["code"])
    except client.exceptions.ClientError as err:
        if err.response["Error"]["Code"] == "SubscriptionRequiredException":
            return []
        raise err

    return severity_levels

__SUPPORT_LEVELS__ = {
    "critical": "ENTERPRISE",
    "urgent": "BUSINESS",
    "high": "BUSINESS",
    "normal": "DEVELOPER",
    "low": "DEVELOPER",
}

support_levels = get_support_severity_levels()

found = False
for level, support_level in __SUPPORT_LEVELS__.items():
    if level in support_levels:
        found = True
        print(f"Your AWS support level is: {support_level}")
        break

if not found:
    print("Your AWS support level is: BASIC")

The IAM permissions required for this to work:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "support:DescribeSeverityLevels",
            "Resource": "*"
        }
    ]
}

This is not ideal as it only attempts to guess based on the support level and will also incorrectly classify "Enterprise On-Ramp" as Business.

Method 2 - Using the Undocumented AWS SupportPlans API

This wasn't enough for me though, I decided to navigate the AWS Console and try figure out how the AWS Support Console provides this information to you. When I visited the "Change Support Plans" page, I noticed in my Google Chrome developer tools network tab that there was a XHR call to a promising looking endpoint, https://service.supportplans.us-east-2.api.aws/v1/getSupportPlan. Upon looking a bit closer at the API request headers, I saw that the call was made using headers following the AWS Signature V4 signing process. This was good news, as that means I can craft a request to this API just by generating following the same signature signing process.

When looking at the AWS Python example that they provide, I saw that it was a full-blown example of how to go through the signing process. But this also incorrectly assumed that I have the following environment variables handy:

I am using AWS profiles with assumed roles, so I wasn't feeling like building an AWS credentials resolver just for this purpose. After digging through some of the open-sourced AWS projects, I found that some of their tools use the AWS CRT Python library which helps with the credentials resolution process and also provides an interface to generate a signed HTTP request with the required headers for authentication / authorisation.

The documentation only provided class / method definitions and assumed you had prior-knowledge on how to use it. After a bit of reverse engineering, I was able to generate a proper request to the API.

For this to work, I found the minimum pieces of information required for a successful API request:

# https://awslabs.github.io/aws-crt-python/api/http.html#awscrt.http.HttpRequest
from awscrt.http import HttpRequest

http_request = HttpRequest(method="GET", path="/v1/getSupportPlan")
http_request.headers.add("Host", "service.supportplans.us-east-2.api.aws")

Once you have this information, you will need to go through the Signature V4 signing process.

# https://awslabs.github.io/aws-crt-python/api/auth.html
from awscrt.auth import (
    AwsCredentialsProvider,
    AwsSignatureType,
    AwsSigningAlgorithm,
    AwsSigningConfig,
    aws_sign_request,
)

result: HttpRequest = aws_sign_request(
    http_request=http_request,
    signing_config=AwsSigningConfig(
        algorithm=AwsSigningAlgorithm.V4,
        signature_type=AwsSignatureType.HTTP_REQUEST_HEADERS,
        credentials_provider=AwsCredentialsProvider.new_default_chain(),
        service="supportplans",
        region="us-east-2",
    ),
).result()

After signing your HTTP request, you are then able to make your HTTP call to your desired API endpoint:

import requests

response = requests.get(
    url="https://service.supportplans.us-east-2.api.aws/v1/getSupportPlan",
    headers=dict(result.headers),
)

print(
    f"Your AWS support level is: {response.json()['supportPlan']}"
)

The IAM permissions required for this to work:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "supportplans:GetSupportPlan",
            "Resource": "*"
        }
    ]
}

At the moment, the code will only work for the current account you are authenticated against and I've yet to implement cross-account roles support.