Skip to content

Instantly share code, notes, and snippets.

@Exchizz
Last active January 14, 2025 09:01
Show Gist options
  • Select an option

  • Save Exchizz/505583c3e7a54b20f3e41d5f868a9272 to your computer and use it in GitHub Desktop.

Select an option

Save Exchizz/505583c3e7a54b20f3e41d5f868a9272 to your computer and use it in GitHub Desktop.
import requests
import json
import boto3
from bs4 import BeautifulSoup
from pprint import pprint
from datetime import datetime
# This code signs-in to Cost Explorer so that Reports can be created programmatically
# (At the moment of writing, this is not supported via officiel API)
# For some reasy, creating/updating reports requires different credentials than boto3-credentials obtains. Trying to create # a report fails with "status: 400".
# To create a report, you need to set header '"x-amz-target": "AWSInsightsIndexService.CreateReport"', sign the request (fx. # using SigV4Auth from botocore.aut) and POST whatever you'd like the report to consist of.
# For inspiration, see the first comment in this gist.
class AWSAPI:
def __init__(self):
self._rsession = requests.Session()
def getSignInTokenFromCredentials(self, credentials):
signInToken = json.loads(self._rsession.get(
"https://signin.aws.amazon.com/federation",
params={
"Action": "getSigninToken",
"Session": json.dumps({
"sessionId": credentials.access_key,
"sessionKey": credentials.secret_key,
"sessionToken": credentials.token
})
}
).text)["SigninToken"]
return signInToken
def loginToAws(self, signInToken):
print("Exchanging credentials for SignInToken")
login_rsp = self._rsession.get(
"https://signin.aws.amazon.com/federation",
params={
"Action": "login",
"Issuer": None,
"Destination": "https://us-east-1.console.aws.amazon.com/costmanagement",
"SigninToken": signInToken
}
)
if login_rsp.status_code != 200:
raise Exception("Failed to login to AWS")
else:
return True
def getCSRFToken(self):
print("Getting CSRF Token and cookies from SignInToken")
cm_rsp = self._rsession.get("https://us-east-1.console.aws.amazon.com/costmanagement", params={"hashArgs": "","oauthStart": int(datetime.now().timestamp())})
_csrf_token = None
for m in BeautifulSoup(cm_rsp.text, "html.parser").find_all("meta"):
if m.get("name") == "tb-data":
_csrf_token = json.loads(m["content"])["csrfToken"]
return _csrf_token
def getCostExplorerCredentials(self, csrf_token):
print("Getting Cost Explorer credentials from CSRF Token and cookies")
rsp = self._rsession.post("https://us-east-1.console.aws.amazon.com/costmanagement/tb/creds", headers={"X-CSRF-Token": csrf_token})
key = rsp.json()
return key
if __name__ == "__main__":
boto_session = boto3.Session(region_name='us-east-1')
credentials = boto_session.get_credentials()
awsapi = AWSAPI()
signInToken = awsapi.getSignInTokenFromCredentials(credentials)
awsapi.loginToAws(signInToken)
csrf_token = awsapi.getCSRFToken()
credentials = awsapi.getCostExplorerCredentials(csrf_token)
print()
print("export AWS_ACCESS_KEY_ID='" + credentials["accessKeyId"] + "'")
print("export AWS_SECRET_ACCESS_KEY='" + credentials["secretAccessKey"] + "'")
print("export AWS_SESSION_TOKEN='" + credentials["sessionToken"] + "'")
@Exchizz
Copy link
Author

Exchizz commented Jan 14, 2025

To create a cost explorer report, see example here:

# credentials obtained via script shown in gist

aws_access_key = credentials["accessKeyId"]
aws_secret_key = credentials["secretAccessKey"]
aws_session_token = credentials["sessionToken"]

# Create body of request - see the browser's network trafic and create a report from the browser, so see how this request is created
body = json.dumps({
    "ChartStyle": "STACK",
    "QueryAttributes": {
        "ExcludeForecast": False,
        "Granularity": "MONTHLY",
        "GroupBy": [
            {
                "Key": "SERVICE",
                "Type": "DIMENSION"
            }
        ],
        "HistoricalTimePeriod": {
            "HistoricalOption": "LAST_6_MONTHS"
        },
        "Metrics": [
            "UNBLENDED_COST"
        ]
    },
    "ReportName": "report name",
    "ReportType": "COST_USAGE"
})

signed_headers = {
    "Host":host,
    "x-amz-user-agent":'aws-sdk-js/1.0.0 ua/2.0 os/macOS#10.15 lang/js md/browser#Firefox_134.0 api/costexplorer#1.0.0',
    "content-type":"application/x-amz-json-1.1",
    "amz-sdk-request":"attempt=1; max=3",
    "amz-sdk-invocation-id":"09254e74-7b00-4385-b64e-1795ff7f55f2",
    "x-amz-source": "CMC",
    "x-amz-target": "AWSInsightsIndexService.CreateReport",
}

# Unsigned headers can be added here
headers = {
}

request = AWSRequest(
    method = method,
    url = url,
    headers=signed_headers,
    data=body,
)

ce_session = boto3.Session(
    aws_access_key_id=aws_access_key,
    aws_secret_access_key=aws_secret_key,
    aws_session_token=aws_session_token
)

# Sign request
SigV4Auth(ce_session.get_credentials(), service_name=service, region_name=region).add_auth(request)

# Update headers with signed headers
headers.update(dict(request.headers))

# Prepare request
req = request.prepare()

try:
    # send request
    response = requests.request(
        method=req.method,
        url=req.url,
        headers=headers,
        data=req.body
    )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment