Feature Beschreibung
Microservice zur Verwaltung von Partner- & Ausstellerzertifikaten im shared-client
Dieser Service dient der automatischen Pflege von Partner- & Ausstellerzertifikaten. Er kann Zertifikate im FSS speichern sowie von Ausstellern herunterladen.
Mit Hilfe eines Scripts kann der Import von LDIF-Dateien durchgeführt werden.
Import von Zertifikaten via REST API
Mit Hilfe der API POST /certificates
können Zertifikate über den Certificate-Manager im FSS gespeichert werden. Der Certificate-Manager konfiguriert die Zertifikate automatisch, er erkennt z.B. die dem Zertifikat zugeordnete MPID sowie ob es sich um ein Partner- oder Ausstellerzertifikat handelt.
REST API
Für die API-Dokumentation wird Swagger verwendet.
Die URL für Swagger lautet: http://localhost:8070/aep-certificate-manager/swagger-ui.html
Import von LDIF Dateien via Script
Die Sub-CAs bieten LDIF Dateien an. Diese beinhalten Listen von Partnerzertifikaten.
Mit einem Script (internal) können LDIF Dateien heruntergeladen und dann über die API des Certificate-Managers in den FSS importiert werden.
Automatischer Zertifikatsimport auf Basis von AS4 und API
Ein CronJob kann so konfiguriert werden, dass er in einem bestimmten Zeitintervall ausgeführt wird, um Partnerzertifikate durch Zugriff auf SubCA Server herunterzuladen. Der erste Schritt zur Automatisierung des Downloadvorgangs von Partnerzertifikaten besteht darin, die Partnerliste abzurufen. So wird sichergestellt, dass für alle Partner Zertifikate heruntergeladen werden.
Die Partnerliste wird aus dem AS4-Address-Service ermittelt:
- Konfigurieren der Adressdienst-URL in application.properties. Stellen Sie sicher, dass die as4-Datenbank Partnerinformationen enthält
## AS4 Address Service
as4-address-service-url=
- Alternativ können die Partner auch manuell konfiguriert werden. Konfigurieren Sie in application.yml die Liste der Partner-IDs.
cert-manager:
partnerId: 990001,990002
Die Partnerliste wird aus dem API-Address-Service ermittelt:
- Konfigurieren Sie die URL des Adressdiensts in application.properties. Stellen Sie sicher, dass die API-Adressdatenbank Partnerinformationen enthält
## API Address Service
api-address-server-url=
NOTE: Konfigurieren Sie nicht beide gleichzeitig.
Die Liste der SubCA-URLs muss ebenfalls konfiguriert werden. Der CronJob greift auf alle konfigurierten URLs zu, um die Zertifikate herunterzuladen. Dies sollte in application.yml konfiguriert werden.
cert-manager:
subca-url:
- ldaps://subca1
- ldaps://subca2
Die Adressen der Sub-CA sind in unserer internen Doku verfügbar.
Konfigurieren Sie das Cron-Timing des Schedulers mithilfe der folgenden Eigenschaft:
## Download Partner Certificates Scheduler (Set cron time)
download-partner-certificate-scheduler=0 15 14 * * *
Um den Scheduler zu deaktivieren, entfernen Sie entweder die Eigenschaft vollständig aus der Datei oder setzen Sie den Wert auf false, wie zum Beispiel:
## Download Partner Certificates Scheduler (Set cron time)
download-partner-certificate-scheduler=false
Automatischer LDIF Zertifikatsimport
Um die LDIF Zertifikate aller Sub-CA z.B. täglich zu aktualisieren kann das folgende Skript angepasst auf Ihr System zum Aufruf verwendet werden. Dieses kann per Cron-Configuration (Serverseitig oder durch einen entsprechenden Start-Pod in einem Kubernetes Cluster) getriggert ausgeführt werden.
Wichtig: Bitte überprüfen und pflegen Sie selbst die URLs der Sub-CAs für die LDIF Downloads, da diese sie aktuell häufiger ändern!
#!/bin/bash
# This script uploads certificates extracted from the ldif files downloaded from the configured subca urls.
# Usage e.g. : ./upload-certificate-ldif.sh
# Keycloak usage
# Ensure that the user's keycloak password and client secret is set in the following fields: PASSWORD, CLIENT_SECRET
# Pre-requisites: curl and unzip installed on same bash terminal - edit following constants as needed
# stop script execution on error
set -e
########### Keycloak configuration ###########
keycloakEnabled=false
KEYCLOAK_URL="http://host.docker.internal:9000/auth/realms/as4/protocol/openid-connect/token"
CLIENT_ID="certificate-manager-service"
CLIENT_SECRET=""
USERNAME="certificate-manager-service-user"
PASSWORD=""
# api for uploading certificate
UPLOAD_CERT_API="http://localhost:8080/aep-certificate-manager/certificates"
current_year=$(date +'%Y')
current_month=$(date +'%m')
current_day=$(date +'%d')
# urls for subca ldif files
SUBCA_URLs=(
"https://crl.sm-pki.atos.net/emtMak_Atos-Smart-Grid.zip"
"https://energyca.telesec.de/mak/MAK_T-Systems-EnergyCA.ldif"
"https://www.countandcare.de/wp-content/uploads/$current_year/$current_month/MAK-$current_year$current_month$current_day.7z"
"https://as-4-mako-sub-ca.da-rz.net/certs/MAK-latest.ldif"
"https://www.schleupen.de/fileadmin/EWW/Dokumente/Smart_Meter_Gateway_Administration/Wirkbetrieb_AS4_Marktkommunikation_EMT.MAK/Schleupen_SE_SubCA_WIRK_$current_year-$current_month-$current_day.zip"
"http://ldaps.smartserviceca.sm-pki.smartservice.de:8080/subca_ssca_emt_export.ldif"
"https://ekn-energyca.telesec.de/mak/MAK_CA4Energy-EKN.ldif"
)
certKey="userCertificate;binary"
# function for access token
getAccessToken() {
auth_response=$(curl --silent -X POST "$KEYCLOAK_URL" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" \
-d "username=$USERNAME" \
-d "password=$PASSWORD" \
-w "\n%{http_code}")
auth_response_code=$(echo "$auth_response" | tail -n 1)
echo "Response code of getting keycloak access token: $auth_response_code"
if [ "$auth_response_code" != "200" ]; then
rm -r ldif-data
exit
fi
access_token=$(echo $auth_response | grep -o '"access_token":"[^"]*' | grep -o '[^"]*$')
AUTHORIZATION_HEADER="Authorization: Bearer $access_token"
}
#function for uploading certificate
uploadCertificate() {
echo -e "*** uploading certificate *** line number ${2}"
local cert="$(sed -e 's/[[:space:]]*$//' <<<${1})"
upload_cert_response=$(curl -s -o ldif-data/upload_cert_response.txt -w "%{http_code}" --location "$UPLOAD_CERT_API" \
--header "$AUTHORIZATION_HEADER" \
--header "Content-Type: application/json" \
--data "{
\"certificate\": \"$cert\"
}
}")
if [ "$upload_cert_response" != "200" ]; then
echo "error response from server: $upload_cert_response"
if [[ "$upload_cert_response" == "401" && "$keycloakEnabled" == true ]]; then
getAccessToken
else
exit
fi
else
echo "response from server: $(cat ldif-data/upload_cert_response.txt)"
# cat ldif-data/upload_cert_response.txt
echo
fi
}
#function for parsing ldif file
parseFile() {
echo -e "*** parsing ldif file ***\n"
lineNo=0
certLineNo=0
cert=""
isCertField=0
sed -i 's/\r$//' "${1}"
while read line; do
((lineNo = lineNo + 1))
[ "${line:0:1}" == "#" ] && continue
attr=${line%%:*}
value=${line#*: }
if [[ "$attr" == "$value" && "$isCertField" == "1" ]]; then
cert="${cert}${value}"
elif [ "$attr" == "$certKey" ]; then
if [ -n "$cert" ]; then
uploadCertificate "$cert" "$certLineNo"
fi
isCertField=1
cert="$value"
certLineNo="$lineNo"
elif [ "$attr" != "$certKey" ]; then
isCertField=0
fi
done <"${1}"
if [ -n "$cert" ]; then
uploadCertificate "$cert" "$certLineNo"
fi
}
#function for setup and process file
processFile() {
SUBCA_URL=${1}
echo "file to download from url: $SUBCA_URL"
filename="${SUBCA_URL##*/}"
extension="${SUBCA_URL##*.}"
if [ -d "ldif-tmp" ]; then
rm -r ldif-tmp
fi
mkdir ldif-tmp
cd ldif-tmp
curl -O "$SUBCA_URL"
if [ -f "$filename" ]; then
if [ "$extension" == "ldif" ]; then
mkdir ldif-data
mv $filename ldif-data/
parseFile "ldif-data/$filename"
elif [ "$extension" == "zip" ]; then
mkdir -p ldif-data
if unzip -d ldif-data/ "$filename"; then
for file in ldif-data/*; do
filename="ldif-data/${file##*/}"
parseFile "$filename"
done
else
echo "Failed to unzip downloaded file $filename"
fi
elif [ "$extension" == "7z" ]; then
mkdir -p ldif-data
if 7z x "$filename" -o./ldif-data/; then
for file in ldif-data/*; do
filename="ldif-data/${file##*/}"
parseFile "$filename"
done
else
echo "Failed to 7-unzip downloaded file $filename"
fi
fi
rm -rf ldif-data
else
echo "File does not exist."
fi
cd ..
rm -r ldif-tmp
}
if [ "$keycloakEnabled" == "true" ]; then
getAccessToken
else
echo "Skipping token access"
AUTHORIZATION_HEADER=""
fi
for SUBCA_URL in ${SUBCA_URLs[@]}; do
processFile "$SUBCA_URL"
done
Automatische Pflege der AS4-Addresse
Darüber hinaus kann umgekehrt auch das AS4 System auf die Events des Certificate-Managers reagieren. Wurde ein Zertifikat gespeichert, kann auf dessen Basis automatisch die AS4-Adresse im AS4-System gepflegt werden.
Für weitere Details vergleichen Sie bitte die Message-Broker API.
Message-Broker API
Consumer: certificate.download.command.default
Beispiel:
{
"as4Id": "9b618376-a93b-4be0-a11c-1f1946262c35",
"delivered": "2023-10-01T12:00:00Z",
"tenant": "9979046000004",
"partner": "9903111000003",
"url": "ldap://subca-server.de:389"
}
Das Platzieren einer Nachricht in dieser Warteschlange führt dazu, dass der Zertifikatsmanager das Zertifikat von der Sub-CA herunterlädt und dann im FSS speichert, sofern das Zertifikat bisher noch nicht im System hinterlegt ist. Auf dieses Vorgang können ggf. andere Systeme wieder reagieren, denn es wird ein Ereignis über die Exchange certificate.store.event
hinterlegt.
Beispiel:
{
"id": "9b618376-a93b-4be0-a11c-1f1946262c35",
"created": "2023-10-01T12:00:00Z",
"certificate": "MIICizCCAjGgAwIB...UoA=="
}
Weitere Informationen zur Architektur finden Sie intern.
Max Concurrency - Parallelität
Für jeden Consumer ist es möglich die Eigenschaft max-concurrency
zu setzen. Diese definiert die maximale Anzahl von gleichzeitigen Verarbeitungsthreads, die für einen Consumer gestartet werden können. Der Default steht für jeden Consumer auf 50.
certificateDownloadCommandMaxConcurrency=50
certificateDownloadEventMaxConcurrency=50
Feature Abgrenzung
Die Verwaltung von Mandanten-Zertifikaten erfolgt über den CSR-Service.
Installation
Hardware Anforderungen
Eine Service-Instanz erfordert mindestens 512 MB RAM.
Wir empfehlen 0,1 CPU-Kerne je Instanz.
Run the Application
Um die Anwendung zu starten, können Sie Folgendes verwenden:
mvn spring-boot:run
Standardmäßig kann auf die Anwendung lokal zugegriffen werden über: http://localhost:8070
Um den Server-Port zu ändern, konfigurieren Sie den Server-Port in application.properties.
server.port=8070
Der API-Kontext ist wie folgt: http://localhost:8070/aep-certificate-manager
Actuator
Diese Root-URL für den Aktuator lautet: http://localhost:8070/aep-certificate-manager/actuator
Umsysteme
Es wird zwingend eine Anbindung eines Message-Brokers und des FSS benötigt. Vergleichen Sie hierzu die entsprechenden Abschnitte in der Konfiguration.
Optional kann ein AS4 System per Message-Broker angebunden werden. Vergleichen Sie hierzu den Abschnitt Message-Broker.
Konfiguration
FSS Connection
Der Service muss an den FSS angebunden werden.
Um eine Verbindung zu FSS herzustellen, müssen wir die URL in der folgenden Eigenschaft konfigurieren:
fss.server.api.url
Als FSS Client wird ein shared-client empfohlen. Dieser kann wie folgt konfiguriert werden:
client=shared-client
Anbindung Message Broker
Der Service muss an einen Message Broker angebunden werden.
Beispiel application.properties
Konfiguration:
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
Folgende Queues / Exchanges werden benötigt:
certificate.download.command.default
(Queue)certificate.download.event
/certificate.download.event.default
certificate.store.event
(Exchange)
Anbindung AS4 System
Zur Anbindung von Umsystemen (z.B. eines AS4-Systems) kann es nötig sein, die Queues/Exchanges anzupassen. Eine solche Experten-Konfiguration ist wie folgt möglich:
## Consumer Properties
certificateDownloadCommandExchange=certificate.download.command
certificateDownloadCommandGroup=default
certificateDownloadCommandExchangeType=topic
certificateDownloadCommandMaxConcurrency=50
certificateDownloadEventExchange=certificate.download.event
certificateDownloadEventGroup=default
certificateDownloadEventExchangeType=topic
certificateDownloadEventMaxConcurrency=50
consumer.max.retries=1
## Producer Properties
certificateStoreEventExchange=certificate.store.event
certificateStoreEventGroup=default
spring.cloud.function.definition=certificateDownloadCommandConsumer;certificateDownloadEventConsumer
spring.cloud.stream.bindings.certificateDownloadCommandConsumer-in-0.destination=${certificateDownloadCommandExchange}
spring.cloud.stream.bindings.certificateDownloadCommandConsumer-in-0.group=${certificateDownloadCommandGroup}
spring.cloud.stream.bindings.certificateDownloadCommandConsumer-in-0.consumer.max-attempts=${consumer.max.retries}
spring.cloud.stream.rabbit.bindings.certificateDownloadCommandConsumer-in-0.consumer.exchange-type=${certificateDownloadCommandExchangeType}
spring.cloud.stream.rabbit.bindings.certificateDownloadCommandConsumer-in-0.consumer.auto-bind-dlq=true
spring.cloud.stream.rabbit.bindings.certificateDownloadCommandConsumer-in-0.consumer.dlq-quorum.enabled=true
spring.cloud.stream.rabbit.bindings.certificateDownloadCommandConsumer-in-0.consumer.quorum.enabled=true
spring.cloud.stream.rabbit.bindings.certificateDownloadCommandConsumer-in-0.consumer.max-concurrency=${certificateDownloadCommandMaxConcurrency}
spring.cloud.stream.bindings.certificateDownloadEventConsumer-in-0.destination=${certificateDownloadEventExchange}
spring.cloud.stream.bindings.certificateDownloadEventConsumer-in-0.group=${certificateDownloadEventGroup}
spring.cloud.stream.bindings.certificateDownloadEventConsumer-in-0.consumer.max-attempts=${consumer.max.retries}
spring.cloud.stream.rabbit.bindings.certificateDownloadEventConsumer-in-0.consumer.exchange-type=${certificateDownloadEventExchangeType}
spring.cloud.stream.rabbit.bindings.certificateDownloadEventConsumer-in-0.consumer.auto-bind-dlq=true
spring.cloud.stream.rabbit.bindings.certificateDownloadEventConsumer-in-0.consumer.dlq-quorum.enabled=true
spring.cloud.stream.rabbit.bindings.certificateDownloadEventConsumer-in-0.consumer.quorum.enabled=true
spring.cloud.stream.rabbit.bindings.certificateDownloadEventConsumer-in-0.consumer.max-concurrency=${certificateDownloadEventMaxConcurrency}
spring.cloud.stream.bindings.certificateDownloadEventProducer-out-0.destination=${certificateDownloadEventExchange}
spring.cloud.stream.rabbit.bindings.certificateDownloadEventProducer-out-0.producer.exchange-type=topic
spring.cloud.stream.rabbit.bindings.certificateStoreEventProducer-out-0.producer.exchange-type=topic
spring.cloud.stream.bindings.certificateStoreEventProducer-out-0.destination=${certificateStoreEventExchange}
Folgende Schnittstellen bestehen zwischen dem AS4 System und dem Certificate Manager:
- Trigger des Zertifikat-Downloads durch den Inbound Workflow
- Aktualisierung der AS4-Adresse auf Basis von Zertifikaten
Trigger des Zertifikat-Downloads durch den AS4 Inbound Workflow
Verbinden Sie die exchange as4.receipt.create
(AS4-Crypto-Operations) zusätzlich mit der queue certificate.download.command.default
des Certificate Managers.
Aktualisierung der AS4-Adresse auf Basis von Zertifikaten
Verbinden Sie die Exchange certificate.store.event
des Certificate Managers mit der Queue certificate.store.event.default
des AS4-Address-Service.
Bitte stellen Sie sicher, dass die Queue existiert & mit der Exchange verbunden ist, bevor Sie die ersten Zertifikate über den Certificate-Manager importieren.
Expiration Konfiguration
Die geladenen Zertifikate werden validiert. Die neuen Zertifikate werden nur heruntergeladen, wenn die geladenen Zertifikate aus dem FSS fehlen, der Status „revoked“ ist oder sie bald ablaufen. Die Definition des baldigen Ablaufs der Zertifikate kann konfiguriert werden (in Tagen). Setzen Sie dafür folgende Property:
renew-certificate-days-before-expiration=14
Default: 14 Tage.
LDAPS Konfiguration
Es ist davon auszugehen, dass die LDAP-Systeme der Sub-CAs durch mTLS geschützt sind. Deshalb ist folgende Konfiguration zur Aktivierung von mTLS vorzunehmen:
ldaps.enabled=true
client=shared-client
Beim Client handelt es sich hierbei um den FSS-Client, in dem die Aussteller-Zertifikate hinterlegt sind. Per default ist automatisch der shared-client
eingestellt.
Der Certificate-Manager muss für mTLS auch auf die Zertifikate der Mandanten zugreifen. Hierbei folgt er der Konvention, dass der Client des Mandanten der MPID des Mandanten entspricht. Dies ist somit Voraussetzung.
Keycloak Konfiguration
Falls Sie die REST-API des Certificate-Managers per oauth2 absichern möchten, oder falls die benötigte REST API des FSS per oauth2 gesichert ist, konfigurieren Sie Keycloak:
In der Keycloak-Admin-Oberfläche:
- Ergänzen Sie einen neuen Client:
certificate-manager-service
- Aktivieren Sie am Client die Settings Client Authentication
- Ergänzen Sie am Client den Tenant-Mapper
- (Falls noch nicht geschehen,) Erstellen Sie einen Maschinen-User, der über die nötigen Rollen und Mandanten verfügt, um auf den FSS zuzugreifen.
- Ergänzen Sie die notwendige(n) Rolle(n) für den Certificate-Manager an den Usern, die seine API aufrufen werden:
*
CERT-MANAGER-WRITE
Ergänzen Sie folgende Application-Properties um Keycloak zu aktivieren:
spring.profiles.active=keycloak-enriched
keycloak.enabled=true
keycloak.auth-server-url=https://<Ihr-Keycloak-Server>/auth
keycloak.realm=<Ihr-Keycloak-Realm>
keycloak.resource=certificate-manager-service
keycloak.credentials.secret=<Secret des Clients>
keycloakUsername=<Maschinen-User zum Zugriff auf den FSS>
keycloakPassword=<Passwort des Maschinen-Users>
Allgemeine Konfiguration
Weitere allgemeine Konfigurationsmöglichkeiten sind hier beschrieben.
View Me Edit Me