Blog

Verwendung nicht dokumentierter AWS-APIs

Jacco Kulman

Aktualisiert Oktober 15, 2025
5 Minuten

TL;DR Geben Sie mir einfach den Code

Bei der Bewertung einiger bestehender IAM-Richtlinien in einer Codebasis habe ich immer wieder dieselben Schritte wiederholt: Navigieren Sie in Google und suchen Sie unter iam actions servicename nach Informationen zu den verwendeten Aktionen.

Profi-Tipp: Es ist viel einfacher, diese Seite einfach als Lesezeichen zu speichern: Aktionen, Ressourcen und Zustandsschlüssel für AWS-Services

Die Informationen, die ich für meine Arbeit zur Richtlinienvalidierung benötigte, sind recht einfach:

  • Was sind die Dienstleistungen?
  • Was sind alle Aktionen, die ein Dienst hat?
  • Welche Aktionen passen zu einem Muster wie "Desc*" für einen Dienst?
  • Was sind obligatorische und optionale Ressourcen für jede Aktion?
  • Welche Bedingungsschlüssel können verwendet werden?
  • Ist die Aktion schreibgeschützt, schreibgeschützt oder etwas anderes?

Die erste Anlaufstelle, um herauszufinden, ob diese Informationen irgendwie offengelegt werden, wären die AWS SDKs. Ich habe die boto3-Dokumentation für den Service iam durchgesehen und bin leer ausgegangen.

Nachdem ich viele Richtlinien manuell erstellt hatte, erinnerte ich mich daran, dass AWS über ein Tool zur Richtlinienbearbeitung in der Konsole verfügt, das die Informationen zu verwenden scheint, die ich manuell nachgeschlagen hatte. Also begann ich mein Abenteuer, indem ich versuchte, meine Kämpfe zu automatisieren.

policyeditor

Ich beschloss, etwas Zeit in den Richtlinieneditor zu investieren, der eine Art API verwendet, mit der ich einige Dinge automatisieren kann. Mit dem Inspektionsbereich von Chrome, der die Registerkarte Netzwerk verwendet, sah ich eine Menge http-Anfragen an:

us-east-1.console.aws.amazon.com/iamv2/api/iamv2

Nach einigen Experimenten und Spielereien mit Cookies und CSRF-Tokens fand ich heraus, wie diese undokumentierte API funktioniert. Also habe ich mir ein kleines Python-Programm ausgedacht, um das zu automatisieren. Da es nur 80 Zeilen Code sind, werde ich ihn hier veröffentlichen. Ich werde wahrscheinlich bald ein installierbares Python-Paket daraus machen. Das Repository mit dem Code und einigen Beispielen ist GitHub - binxio/aws-iamv2.

import requests
import json
import boto3
from bs4 import BeautifulSoup

# composePolicy, decomposePolicy, checkMultiMFAStatus, createX509, cuid, generateKeyPairs
methods = { "services"                    : lambda p: None,
            "actions"                     : lambda p: { "serviceName": p, "RegionName": "eu-central-1" },
            "resources"                   : lambda p: None,
            "contextKeys"                 : lambda p: None,
            "globalConditionKeys"         : lambda p: None,
            "getServiceLinkedRoleTemplate": lambda p: { "serviceName": p },
            "policySummary"               : lambda p: { "policyDocument": p },
            "validate"                    : lambda p: { "policy": json.dumps(p), "type": "" } }

class ConsoleSession:
    def __init__(self, boto3_session):
        self._credentials = boto3_session.get_credentials()
        self._signed_in = False
        self._csrf_token = None
        self._cache = {method: {} for method in methods}
        self._rsession = requests.Session()

    def __getattribute__(self, name):
        if name in methods:
            def make_lambda(method, converter):
                return lambda param=None: self.get_api_result(method, converter(param))
            return make_lambda(name, methods[name])
        else:
            return object.__getattribute__(self, name)

    def signin(self):
        token = json.loads(self._rsession.get(
            "https://signin.aws.amazon.com/federation", 
            params={
                "Action": "getSigninToken",
                "Session": json.dumps({
                    "sessionId": self._credentials.access_key,
                    "sessionKey": self._credentials.secret_key,
                    "sessionToken": self._credentials.token
                })
            }
        ).text)["SigninToken"]
        self._rsession.get(
            "https://signin.aws.amazon.com/federation",
            params={
                "Action": "login",
                "Issuer": None,
                "Destination": "https://console.aws.amazon.com/",
                "SigninToken": token
            }
        )
        for m in BeautifulSoup(self._rsession.get(
            "https://us-east-1.console.aws.amazon.com/iamv2/home#",
            params={ "region": "eu-central-1", "state": "hashArgs" }
        ).text, "html.parser").find_all("meta"):
            if m.get("name") == "awsc-csrf-token":
                self._csrf_token = m["content"]
        self._signed_in = True

    def get_api_result(self, path, param=None):
        not self._signed_in and self.signin()
        params = json.dumps(param)
        if self._cache[path].get(params, None):
            return self._cache[path][params]
        self._cache[path][params] = json.loads(self._rsession.post(
            "https://us-east-1.console.aws.amazon.com/iamv2/api/iamv2",
            headers={
                "Content-Type": "application/json",
                "X-CSRF-Token": self._csrf_token,
            },
            data=json.dumps({
                "headers": { "Content-Type": "application/json" },
                "path": f"/prod/{path}",
                "method": "POST",
                "region": "us-east-1",
                "params": {},
                **({ "contentString": params } if params else {})
            })
        ).text)
        return self._cache[path][params]

Ein paar Dinge zu diesem Code:

  • Die Klasse ConsoleSession nimmt eine boto3.Session als Eingabe entgegen. Diese Sitzung benötigt keine aktuellen Rechte für AWS.
  • Der Code sieht vielleicht etwas seltsam aus, weil ich ihn als dynamische Klasse erstellen wollte. Daher musste ich nur eine Zeile hinzufügen, um einen zusätzlichen API-Endpunkt zu implementieren. Dieser verwendet die Überschreibung __getattribute__ und das Objekt methods.
  • Ich verwende das Modul requests und starte eine requests.Session(), die einen Großteil der Arbeit erledigt, die für die erfolgreiche Bearbeitung von Cookies für http-Anfragen erforderlich ist.
  • Um die awsc-csrf-token von der Seite zu holen, verwende ich BeautifulSoup
  • Die Methoden in methods wurden nicht alle von mir entdeckt. Ich habe eine Suche auf iamv2 auf github durchgeführt und einige json-Dateien gefunden, in denen diese API bereits detailliert beschrieben ist.
  • Da sich die Informationen, die für die von mir benötigten API-Aufrufe abgerufen werden, nicht pro Anfrage ändern, habe ich eine einfache Zwischenspeicherfunktion implementiert. Für einige Methoden wie policySummary und validate ist dies möglicherweise nicht optimal.

Die eigentliche Anmeldung erfolgt über drei HTTP-Anfragen:

  1. Sie erhalten eine SigninToken von signin.aws.amazon.com/federation
  2. Verwenden Sie den Token auf login, um https://console.aws.amazon.com/
  3. Rufen Sie https://us-east-1.console.aws.amazon.com/iamv2/home# ab, um die awsc-csrf-token von der Seite zu erhalten.

Beispiel Verwendung

Der folgende Code zeigt ein Beispiel für die Verwendung:

import boto3
from iamv2 import ConsoleSession
import re

awssvcs = {}
console_session = None

def get_iam_info():
    global console_session
    boto_session = boto3.Session(region_name='us-east-1')
    console_session = ConsoleSession(boto_session)

    services = console_session.services()
    for service in services:
        name = service["serviceName"]
        if name not in awssvcs:
            awssvcs[name] = { "parts": [] }
        awssvcs[name]["parts"].append(service)

def get_statement_actions(statement):
    result = []
    actions = statement.get("Action") or statement.get("NotAction")
    reverse = "NotAction" in statement
    reverse = not reverse if statement["Effect"] == "Deny" else reverse
    actions = [actions] if isinstance(actions, str) else actions
    for action in actions:
        service, act = action.split(':')
        if "Actions" not in awssvcs[service]:
            awssvcs[service]["Actions"] = console_session.actions(awssvcs[service]["parts"][0]["serviceKeyName"])
        actrgx = act.replace('*', '[A-Za-z]+')
        for svc_action in awssvcs[service]["Actions"]:
            if bool(re.match(actrgx, svc_action["actionName"], flags=re.IGNORECASE)) ^ reverse:
                result.append(svc_action)
    return result

def get_policy_actions(policy):
    for statement in policy["Statement"]:
        yield get_statement_actions(statement)

get_policy_actions listet einfach alle Aktionen auf, die durch die Anweisungen in der Richtlinie erlaubt sind. Hier ist es in Aktion:

    policy = {
        "Version": "2012-10-17", 
        "Statement": [{
            "Sid": "ReadOnlyCloudTrail",
            "Effect": "Deny", 
            "NotAction": "cloudtrail:De*", 
            "Resource": "*"
        }]
    }

    get_iam_info()
    for statement_actions in get_policy_actions(policy):
        statement_actions = sorted(statement_actions, key=lambda x: x["actionName"])
        for action in statement_actions:
            print(f'{action["actionName"]:40} {", ".join(action["actionGroups"])}')

Sie erhalten dann die folgende Liste:

DeleteChannel                            ReadWrite
DeleteEventDataStore                     ReadWrite
DeleteResourcePolicy                     ReadWrite
DeleteServiceLinkedChannel               ReadWrite
DeleteTrail                              ReadWrite
DeregisterOrganizationDelegatedAdmin     ReadWrite
DescribeQuery                            ReadOnly, ReadWrite
DescribeTrails                           ReadOnly, ReadWrite

Sie können sehen, dass diese Richtlinie einige Aktionen zulässt, die nicht schreibgeschützt sind. Sie könnten diesen Code in Tests verwenden, die in Ihrer Pipeline ausgewertet werden, um sicherzustellen, dass Sie in dieser Richtlinie nicht versehentlich nicht-lesbare Aktionen zulassen.

Zukunft

Ich werde ein installierbares Python-Paket aus dem API-Teil erstellen. Außerdem habe ich einige Ideen für ein paar nette Policy-Tools:

  • Prüfen Sie auf häufige Fehler in Policen.
  • Generieren Sie schreibgeschützte Dienstanweisungen für in einer Berechtigungsgrenze

Verfasst von

Jacco Kulman

Jacco is a Cloud Consultant at Binx.io. As an experienced development team lead he coded for the banking- and hospitality- and media-industries. He is a big fan of serverless architectures. In his free time he reads science fiction, contributes to open source projects and enjoys being a life-long-learner.

Contact

Let’s discuss how we can support your journey.