Einführung in die Automatisierung mit GÉANT TCS

Praktisch alle Funktionen von GÉANT TCS, die über die Web-Oberfläche angeboten werden, sind auch über ein REST-API verfügbar. Mit diesem API können verschiedenste Aufgaben mit eigenen Werkzeugen ausgeführt werden, ohne den Weg über die manchmal langsame oder auch unübersichtliche Web-Oberfläche gehen zu müssen. Beispiele für nützliche Aufgaben:

  • Ausstellung von Serverzertifikaten
  • Eintragen von neuen Domains
  • Erzeugen von ACME-Accounts
  • Monitoring des Validierungszustandes von Domains

Eine Dokumentation des APIs ist verfügbar über: https://sectigo.com/knowledge-base/detail/Sectigo-Certificate-Manager-SCM-REST-API/kA01N000000XDkE

Die hier skizzierten Beispiele sollen keine fertige Lösung darstellen, sondern einen Eindruck von den Möglichkeiten des TCS REST-APIs vermitteln. Zum Nachvollziehen der Beispiele muss unbedingt die Dokumentation des APIs herangezogen werden.

Aufruf-Schema

Wie bei REST-APIs üblich, müssen die Ressourcen per HTTP GET oder POST angesprochen werden. Bei jedem Aufruf ist ein Header zu übermitteln, der auch Nutzername/Passwort zur Authentifizierungs enthält:

    login: <login>
    password: <Passwort>
    customerUri: DFN
    Content-Type: application/json;charset=utf-8

Die customerUri ist für alle Teilnehmer der DFN-PKI identisch und hat schlicht den Wert „DFN“.

login und password sind die Zugangsdaten eines RAO- oder DRAO-Accounts aus TCS (angelegt in https://cert-manager.com/customer/DFN). Es ist möglich, einen Account so zu beschränken, dass er nur über das REST-API und nicht über die Web-Oberfläche genutzt werden kann, siehe hierzu den Punkt „Api-Only-User“ unter https://doku.tid.dfn.de/de:dfnpki:tcsfaq#admins_rollen_privilegien. Da im REST-API aber nicht weniger Funktionalität zur Verfügung steht als über die Web-Oberfläche, ist diese Einschränkung eher kosmetischer Natur.

Beispiel der Grundstruktur für die Linux-Shell mit curl:

curl 'https://cert-manager.com/api/<....>' -i -X POST \
    -H "login: <login>" \
    -H "password: <password>" \
    -H "Content-Type: application/json;charset=utf-8" \
    -H "customerUri: DFN"
    -d "{...}"

Beispiel der Struktur für Python:

d = {
    "xyz": "value"
}

url = 'https://cert-manager.com/api/....'
headers = {
    "login": username,
    "password": password,
    "Content-Type": "application/json;charset=utf-8",
    "customerUri": "DFN"
}
req = requests.post(url,data=json.dumps(d),headers=headers)
# Antwort dann in req.json()

Beispiel: Ausstellung von Server-Zertifikaten

Bevor das API zum Ausstellen von Zertifikaten genutzt werden kann, muss in den Einstellungen der Organisation unter ☰→Organizations→Edit→Certificate Settings jeweils bei SSL Certificates und Client Certificates der Punkt „Web API“ angekreuzt werden. Zusätzlich wird die Eingabe eines Secret Keys erzwungen, der aber für die Nutzung des REST-API nicht benötigt wird.

Organisations-ID und Web API-Einstellung

Es werden zwei weitere Parameter benötigt:

  • Die ID der Organisation, in der das Zertifikat ausgestellt werden soll, kann man direkt in cert-manager ablesen unter ☰→Organizations→Edit. Der blau hinterlegte Wert rechts neben dem Organisationsnamen ist die gewünschte ID.
  • Die ID der Zertifikatprofile muss mit einem Aufruf des REST-API ermittelt werden. Hierzu wird der folgende Aufruf durchgeführt, im Beispiel in einer Linux-Shell mit curl:
  echo -n Password:
  read -s PASS
  echo
  curl 'https://cert-manager.com/api/ssl/v1/types' -i -X GET \
      -H "login: <login>" \
      -H "password: $PASS" \
      -H "Content-Type: application/json;charset=utf-8" \
      -H "customerUri: DFN"

Ausgabe:

[{"id":15860,"name":"OV SSL","description":"","terms":[365],"keyTypes":{"RSA":["2048","3072","4096","8192"],"EC":["P-256","P-384"]},"useSecondaryOrgName":false},
{"id":15862,"name":"Wildcard SSL","description":"","terms":[365],"keyTypes":{"RSA":["2048","3072","4096"],"EC":["P-256","P-384"]},"useSecondaryOrgName":false},
{"id":15863,"name":"OV Multi-Domain","description":"","terms":[365],"keyTypes":{"RSA":["2048","3072","4096"],"EC":["P-256","P-384"]},"useSecondaryOrgName":false},
{"id":15865,"name":"EV Anchor","description":"","terms":[395],"keyTypes":{"RSA":["2048","3072","4096","8192"]},"useSecondaryOrgName":false},
usw.

Es ist zu empfehlen, dass Profil OV Multi-Domain zu verwenden. In der Ausgabe findet man hierzu die  ID 15863.

Mit der ermittelten Organisations- und der Profil-ID wird nun nur noch ein CSR benötigt, der z.B. mit openssl erzeugt wurde. Mit dem folgendem Aufruf kann der CSR eingereicht werden:

curl 'https://cert-manager.com/api/ssl/v1/enroll' -i -X POST \
    -H "login: <login>" \
    -H "password: <passwort> \
    -H "Content-Type: application/json;charset=utf-8" \
    -H "customerUri: DFN"
    -d "{\"orgId\":<Organisations-ID>,\"subjAltNames\":\"\",\"certType\":15863,\"numberServers\":0,\"serverType\":-1,\"term\":365,\"comments\":\"\",\"externalRequester\":\"\",\"customFields\": [],\"csr\":\"-----BEGIN CERTIFICATE REQUEST-----\nMIICtDCCAZwCAQAwbzELMAkGA1UEBhMCREUxEDAOBgNVBAgMB0hhbWJ1cmcxEDAO\nBgNVBAcMB0hhbWJ1cmcxHzAdBgNVBAoMFkRGTiBDRVJ....vfoLQu9d\n-----END CERTIFICATE REQUEST-----\n\"}"

Ausgabe:

{"sslId":<SSLID>,"renewId":"<renewId>"}

Damit ist der Antrag im cert-manager eingegangen. Ob das Zertifikat sofort ausgestellt wird oder noch ein manueller Eingriff im cert-manager notwendig ist, hängt von den Rechten des Login-Users ab: Wenn der Login-User in seinem Profil „Allow SSL auto approve“ gesetzt hat, wird das Zertifikat sofort ausgestellt.

Nach Ausstellung des Zertifikats kann dieses mit der SSLID aus der Ausgabe abgerufen werden:

curl '"https://cert-manager.com/api/ssl/v1/collect/<SSLID>/base64" -i -X GET \
     -H "login: <login>" \
     -H "password: <passwort> \
     -H "Content-Type: application/json;charset=utf-8" \
     -H "customerUri: DFN"

Beispiel: Eintragung einer Domain

Um eine Domain automatisiert in SCM einzutragen, benötigt man wieder die Organisations-ID, ablesbar beispielsweise im cert-manager unter Organisation->Edit. Mit der Organisations-ID kann direkt eine Domain eingetragen und die Delegation für die einzelnen Zertifikattypen an die Organisation eingeleitet werden:

curl 'https://cert-manager.com/api/domain/v1' -i -X POST \
    -H "login: <login>" \
    -H "password: <passwort> \
    -H "Content-Type: application/json;charset=utf-8" \
    -H "customerUri: DFN"
    -d '{"name":"<Domainname>","active":true,"delegations":[{"orgId": "<Organisations-ID>","certTypes":["CodeSign","SMIME","SSL"]}]}'

Rückgabewert ist ein HTTP-Status 201 und ein Location-Header mit der ID der neuen Domain, Beispiel:

HTTP/1.1 201 Created
Location: https://cert-manager.com/api/domain/v1/481

Wenn gewünscht, kann für die Domain anschließend eine Validierung durchgeführt werden. Hierzu muss zunächst ein Aufruf zum Starten der gewünschten Methode (HTTP, HTTPS, CNAME oder EMAIL) durchgeführt werden:

https://cert-manager.com/api/dcv/v1/validation/start/domain/http
https://cert-manager.com/api/dcv/v1/validation/start/domain/https
https://cert-manager.com/api/dcv/v1/validation/start/domain/cname
https://cert-manager.com/api/dcv/v1/validation/start/domain/email

Es muss dabei gepostet werden:

{„domain“:“<Domainname>“}

Der Rückgabewert des Start-Aufrufs ist ein Geheimnis, dass an der angezeigten Stelle per HTTP/HTTPS oder DNS verfügbar gemacht werden muss (Details müssen in der Dokumentation nachgeschlagen werden), bzw. eine Auswahl an E-Mail-Adressen für EMAIL DCV.

Im zweiten Schritt wird mit einem Aufruf von „submit“ signalisiert, dass das Geheimnis von Sectigo per HTTP/HTTPS oder DNS überprüft werden kann bzw. an eine spezifizierte E-Mail-Adresse eine Challenge gesendet werden soll:

https://cert-manager.com/api/dcv/v1/validation/submit/domain/http
https://cert-manager.com/api/dcv/v1/validation/submit/domain/https
https://cert-manager.com/api/dcv/v1/validation/submit/domain/cname

Es muss dabei wieder gepostet werden:

{„domain“:“<Domainname>“}

 

Oder für EMAIL-DCV an https://cert-manager.com/api/dcv/v1/validation/start/domain/email:

{„domain“:“<Domainname>“, "email":"<Email>"}

Das System versucht daraufhin im Hintergrund die Validierung durchzuführen, also z.B. eine HTTP/HTTPS/DNS-Abfrage auf <Domainname> zur Prüfung des Geheimnisses zu machen.

Der Status kann mit einem einfachen POST an https://cert-manager.com/api/dcv/v2/validation/ abgefragt werden:

{„domain“:“<Domainname>“}

 

Beispiel: Automatisiertes Erzeugen eines ACME-Accounts

Um ACME-Accounts per API zu erzeugen und für die Ausstellung von Zertifikaten nutzen zu können, muss in drei Schritten vorgegangen werden.

Im ersten Schritt wird der Account angelegt mit einem POST an https://cert-manager.com/api/acme/v1/account mit den folgenden Daten:

{"name": "<Beliebig gewählter Account-Name>",
"acmeServer": "https://acme.sectigo.com/v2/OV",
"organizationId": "<Organisations-ID>"
}

Die ID des neu erzeugten Accounts wird in einem Location-Header zurückgeliefert.

Im Anschluss müssen dem Account Domains hinzugefügt werden. Dies geschieht über einen POST an https://cert-manager.com/api/acme/v1/account/<AccountId>/domains mit den folgenden Daten:

{ "domains": [ {"name":"<domain1>"},{"name":"<domain2>"}...]}

Ist dies geschehen, können die Parameter für das External Account Binding abgerufen werden, die für die Einbindung des ACME-Accounts in die Client-seitigen Werkzeuge wie certbot benötigt werden. Dies geschieht mit einem GET an https://cert-manager.com/api/acme/v1/account/<AccountId>

Die umfangreiche Rückgabe hat die folgende Struktur:

{"id":<Account-ID>, "name":"<Gewählter Account-Name>","status":"pending",
"macKey":"Qdr1P53XA9G3HL_....._WyqUNsBDwPtXU6",
"macId":"WyqUNsBDwPtXU6_...",
"acmeServer":"https://acme.sectigo.com/v2/OV",
"organizationId":<Organisations-ID>,"certValidationType":"OV","accountId":"z-PYNnsxepUVwFWs-qeTfA","ovOrderNumber":<ordernumber>,"contacts":"","evDetails":{},"domains":[{"name":"<domain1"},...]}

Die in der Rückgabe enthaltenen Parameter macKey und macId müssen beim Aufruf von z.B. certbot als Parameter –eab-hmac-key und –eab-kid übergeben werden.

Beispiel: Monitoring des Validierungszustandes von Domains

Es kann nützlich sein, den Status, den die wichtigsten Domains im cert-manager haben, in ein Monitoring zu integrieren.

Der Status kann mit der Ressource https://cert-manager.com/api/domain/v1/<Domain-ID> abgefragt werden. Die Domain-ID kann, soweit sie noch nicht bei der Anlage der Domain gespeichert wird, nur über eine Schleife mit Auflistung über alle eingetragenen Domains ermittelt werden. Die Auflistung muss in Blöcken durchgeführt werden:
https://cert-manager.com/api/domain/v1?size=<Blockgröße>&position=<Blockstart>

Hat man in den zurückgegebenen Elementen den gesuchten Domain-Namen gefunden, kann mit der zugehörigen Domain-ID der Status abgefragt werden: https://cert-manager.com/api/domain/v1/<Domain-ID>

Beispielhaftes Vorgehen als Python-Skizze:

# domains_to_check: Liste mit zu überwachenden Domains
def monitor_domains(login,password,domains_to_check):
    headers = {
        "password": password,
        "Content-Type": "application/json;charset=utf-8",
        "login": login,
        "customerUri": "DFN"
    }
    # Ermitteln der Anzahl der eingetragenen Domains:
    url = 'https://cert-manager.com/api/domain/v1/count'
    req = requests.get(url,headers=headers)
    if req.status_code != 200:
        print("CRIT: cannot get domain count "+domain+": "+str(req.status_code)+" "+req.text)
        return(2)
    count=req.json()["count"]

    # Loop über alle Domains. Das API erzwingt ein blockweises Vorgehen über die Parameter size und position
    for i in range(int(count/50)+1):
        position=i*50
        size=50
        url = 'https://cert-manager.com/api/domain/v1?size='+str(size)+'&position='+str(position)
        req = requests.get(url,headers=headers)
        if req.status_code != 200:
             print("CRIT: cannot get domain list with "+url+": "+str(req.status_code)+" "+req.text)
             return(2)
        # Einen Block erhalten, Schleife
        domainlist=req.json()
        for domain in domainlist:
            if domain["name"] in domains_to_check:
                # Wenn die Domain geprüft werden soll, mit der eben erhaltenen ID die Domain-Details abrufen.
                url = 'https://cert-manager.com/api/domain/v1/'+str(domain["id"])
                req = requests.get(url,headers=headers)
                if req.status_code != 200:
                    print("--CRIT: cannot get info of domain "+domain["name"]+" url:"+url+": "+str(req.status_code)+" "+req.text)
                    continue
                # Delegationszustand prüfen
                if req.json()["delegationStatus"] != 'ACTIVE':
                    print("--CRIT: delegationStatus of domain "+domain["name"]+" is not ACTIVE: "+req.json()["delegationStatus"])
                # Domain überhaupt Activ?
                if req.json()["state"] != 'ACTIVE':
                    print("--CRIT: state of domain "+domain["name"]+" is not ACTIVE: "+req.json()["state"])
                # Validierungszustand
                if req.json()["validationStatus"] != 'VALIDATED':
                    print("--CRIT: validationStatus of domain "+domain["name"]+" is not VALIDATED: "+req.json()["validationStatus"])

                # Ablaufdatum des Validierungszustand prüfen
                if "dcvExpiration" in req.json():
                    expdate=req.json()["dcvExpiration"]
                    expdate_obj = datetime.strptime(expdate,'%Y-%m-%d')
                    # Differenz zum aktuellen Datum bilden
                    now_obj = datetime.now()
                    ddiff=abs(expdate_obj-now_obj).days

                    if ddiff < 5:
                        # Weniger als 5 Tage gültig?
                        print("--CRIT: domain "+domain["name"]+" validated until "+expdate+", less than 5 days left!")
                    elif ddiff < 30:
                        # Weniger als 30 Tage gültig?
                        print("--WARN: domain "+domain["name"]+" validated until "+expdate+", less than 30 days left!")
                    else:
                        print("--OK: domain "+domain["name"]+" active and validated until "+expdate)

(Jürgen Brauckmann, 21.02.2022)