Zum Hauptinhalt springen

Sicherheitskonfiguration

Übersicht

Dieses Dokument beschreibt die Sicherheitskonfiguration der PlantPulse-Plattform. Durch Sicherheitseinstellungen auf Netzwerk-, Übertragungs-, Anwendungs- und Datenschicht kann die Plattform sicher betrieben werden.

Wichtig: In industriellen Steuerungssystemen (ICS) hat die Verfügbarkeit oberste Priorität. Validieren Sie alle Änderungen an den Sicherheitseinstellungen zunächst in einer Testumgebung, bevor Sie sie auf die Produktionsumgebung anwenden.

Sicherheitsarchitektur

Die Sicherheit von PlantPulse besteht aus 4 Schichten.

SchichtSicherheitsbereichHauptfunktion
1. NetzwerkFirewall, Segmentierung, VPNSperrung von Außenzugriff, Netzwerktrennung
2. ÜbertragungHTTPS/TLS, VerschlüsselungVerschlüsselung von Kommunikationsdaten
3. AnwendungAuthentifizierung, Autorisierung, Filter, SitzungBenutzerauthentifizierung und Zugriffskontrolle
4. DatenDB-Zugriffskontrolle, VerschlüsselungSchutz ruhender Daten

TLS / Zertifikate (automatische Verwaltung)

Das Zertifikat wird von plantpulse-certs einem einmaligen Container erstellt. Er läuft beim Stack-Start zuerst und erstellt alle Materialien mit prepare-ssl.sh, schreibt sie in das Volumen pp-security und beendet sich dann selbst. Die übrigen Container mounten dieses Volumen schreibgeschützt.

ElementWert
Erstellerplantpulse-certs (Einmalig — Exited (0) ist normal)
MaterialspeicherortVolumen pp-security, Container-Pfad /var/security/plantpulse
LeserDatalake und App — beide :ro
Ausnahmeplantpulse-plugin-opcua-server nur Lese-/Schreibzugriff. Bei der Initialisierung muss ein sicheres temporäres Verzeichnis erstellt werden
KonfigurationUmgebungsvariablen PP_TLS_* in Compose
Starten Sie plantpulse-certs nicht neu

Die Deklaration als restart: "no" ist kein Fehler. Ein Neustart erstellt Zertifikate neu. Führen Sie eine Neuausstellung nur aus, wenn sie erforderlich ist, indem Sie das folgende Verfahren explizit ausführen.

Dieser Container hat keinen Healthcheck (healthcheck: disable) — urteilen Sie nur nach dem Exit-Code. Nachfolgende Container warten nicht auf Health, sondern darauf, ob er normal beendet wurde (service_completed_successfully).

env.sh TLS-Variablen

VariableStandardBeschreibung
PP_TLS_ENABLEDtrueTLS aktivieren
PP_TLS_CERT_DIR/var/security/plantpulseZertifikatverzeichnis
PP_TLS_DOMAINplantpulse.ioStandard-Domain
PP_TLS_KEYSTORE_PASSWORDkopens123! (Änderung erforderlich)Keystore-Passwort
PP_TLS_TRUSTSTORE_PASSWORD${PP_TLS_KEYSTORE_PASSWORD}Truststore-Passwort
PP_TLS_VALID_DAYS3650Gültigkeitsdauer (Tage)
PP_TLS_EC_GROUPsecp256r1ECDSA-Kurve
PP_TLS_SIGALGSHA256withECDSASignaturalgorithmus
PP_TLS_OPCUA_APP_URIurn:plantpulse:opcua:serverOPC-UA Application URI
PP_TLS_NODE_NAMESmaster worker-1 ... worker-5Clusterknotenname
PP_TLS_SAN_DNSlocalhost,<hostname>,<domain>SAN DNS
PP_TLS_SAN_IPS<HOST_IP>,<SERVICE_IP>,<PUBLIC_IP>,127.0.0.1SAN IP (NAT/externe IP erforderlich)
PP_TLS_FORCE_REGENERATEfalsetrue erzwingt Neugenerierung

Zertifikaterstellungsverfahren

Dies wird während der Installation automatisch einmal durchgeführt. Normalerweise ist kein Handeln erforderlich. Führen Sie Folgendes nur durch, wenn sich die SAN geändert hat oder das Zertifikat abgelaufen oder beschädigt ist.

cd /opt/kopens/plantpulse-platform-docker

# 1. compose 의 PP_TLS_* 검토 (외부 노출 IP/도메인 포함)
vi compose/docker-compose.yml

# 2. 강제 재생성 — 원샷을 다시 돌립니다
PP_TLS_FORCE_REGENERATE=true \
docker compose -f compose/docker-compose.yml up --force-recreate plantpulse-certs

# 3. 자재를 읽는 쪽을 재시작
bin/restart.sh
Wenn eine ungültige IP in der SAN auch nur enthalten ist, wird kein einziges Zertifikat erstellt

PP_TLS_SAN_IPS wird aus PP_HOST_IP · PP_SERVICE_IP · PP_MASTER_IP · PP_PUBLIC_IP · 127.0.0.1 zusammengesetzt. Wenn auch nur ein Wert das falsche Format hat, wird die gesamte Erweiterungsdatei von OpenSSL zurückgewiesen. Füllen Sie DOCKER_PP_EXTERNAL_IP nicht in Boxen ohne NAT aus.

Wenn der Name, den die App beim Verbinden mit dem Backend verwendet, nicht in der SAN steht, wird nur „TimeoutException" angezeigt

Nirgends in den Logs wird etwas über Zertifikate gesagt. Compose fügt acht Containernamen (localhost · plantpulse-datalake · plantpulse-server-web · plantpulse-batch-web · plantpulse-warehouse · plantpulse-plugin-opcua-server · plantpulse-plugin-aasx-server · plantpulse-proxy) und fünf alte Namen vor der Umbenennung in die SAN-DNS-Liste ein. Wenn Sie Container umbenennen oder den Backend-Host direkt angeben, müssen Sie auch die SAN-Liste aktualisieren.

Von prepare-ssl.sh erzeugte Ausgaben:

/var/security/plantpulse/
├── ca.crt / ca.key # Self-signed CA
├── server.jks / server.crt # Tomcat (서버)
├── cassandra.jks # Cassandra internode + client
├── kafka.jks # Kafka broker
├── mqtt.jks # HiveMQ
├── opcua/ # OPC-UA
│ ├── server.pem / server.key
│ └── private/ rejected/ trusted/
└── client/ # 클라이언트 인증서 (선택)

Externes CA-Zertifikat verwenden

Um anstelle eines selbstsigniert Zertifikates ein CA-Zertifikat von Ihrem Unternehmen oder Let's Encrypt zu verwenden:

# 1. CA 발급 인증서를 JKS keystore 로 변환
keytool -importcert -alias rootca -file company-ca.pem \
-keystore /var/security/plantpulse/server.jks \
-storepass ${PP_TLS_KEYSTORE_PASSWORD}

keytool -importkeystore -srckeystore company.p12 -srcstoretype PKCS12 \
-destkeystore /var/security/plantpulse/server.jks \
-deststoretype JKS -deststorepass ${PP_TLS_KEYSTORE_PASSWORD}

# 2. PP_TLS_FORCE_REGENERATE 가 false 인 상태로 재시작
./restart.sh

Ablauf-Monitoring: Richten Sie ein externes Monitoring (openssl x509 -enddate) ein, um 30 Tage vor Zertifikatablauf einen Alarm auszulösen.


Web-Sicherheitsfilter

PlantPulse schützt Web-Anfragen durch in web.xml definierte Sicherheitsfilter.

SecurityFilter

Dies ist der Kernfilter, der den Zugriff nicht authentifizierter Benutzer blockiert.

<!-- WEB-INF/web.xml inside the webapp (bundled in the WAR — reset on deploy, so change it in the source) -->
<filter>
<filter-name>SecurityFilter</filter-name>
<filter-class>com.kopens.plantpulse.server.web.filter.SecurityFilter</filter-class>
<init-param>
<param-name>excludeUrls</param-name>
<param-value>/api/v5/ping,/login,/css/,/js/,/images/</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SecurityFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
  • Überprüft die Authentifizierung für alle Anfragen (/*).
  • Pfade in excludeUrls sind ohne Authentifizierung zugänglich.
  • Nicht authentifizierte Anfragen werden auf die Login-Seite umgeleitet.

XSSFilter

Ein Filter zur Verhinderung von Cross-Site-Scripting-Angriffen (XSS).

<filter>
<filter-name>XSSFilter</filter-name>
<filter-class>com.kopens.plantpulse.server.web.filter.XSSFilter</filter-class>
<init-param>
<param-name>excludeUrls</param-name>
<param-value>/api/</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>XSSFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
  • Entfernt gefährliche Tags wie <script>, <iframe> aus Anfrageparametern.
  • API-Pfade werden durch separate Eingabevalidierung geschützt und können aus dem Filter ausgenommen werden.

CSRF-Schutz

Token-basierte Validierung zur Verhinderung von Cross-Site Request Forgery (CSRF).

<filter>
<filter-name>CSRFFilter</filter-name>
<filter-class>com.kopens.plantpulse.server.web.filter.CSRFFilter</filter-class>
<init-param>
<param-name>excludeUrls</param-name>
<param-value>/api/v5/</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CSRFFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
  • Validiert CSRF-Token bei POST-, PUT- und DELETE-Anfragen.
  • Bei Formularanfragen ist der _csrf Parameter oder der X-CSRF-TOKEN Header erforderlich.
  • REST APIs (/api/v5/) verwenden Token-Authentifizierung und sind von CSRF-Filterung ausgenommen.

EncodingFilter

Vereinheitlicht Zeichencodierung zur Verhinderung von Sicherheitslücken im Zusammenhang mit Codierung.

<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.kopens.plantpulse.server.web.filter.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

HTTPS-Konfiguration

Die Benutzer sehen 80 / 443 wird von plantpulse-proxy(nginx) beendet. Der Web-Server-Container öffnet keine Host-Ports. Der Ort zum Ändern des Browser-Zertifikats ist daher nicht Tomcat, sondern der Proxy.

Proxy-Zertifikat — Standard ist selbstsigniert

Direkt nach der Installation ist 443 ohne Vorbereitung sofort offen. Der Proxy erstellt bei der ersten Initialisierung ein selbstsigniertes Zertifikat und schreibt es in das Volumen pp-proxy-certs.

Überschreibt nicht vorhandene Zertifikate

Ein Neustart, der das echte Zertifikat des Kunden durch selbstsigniert zurücksetzt, ist ein stiller Fehler. Wenn die Datei bereits vorhanden ist, verwendet der Proxy sie wie sie ist.

Mit echtem Zertifikat austauschen

Sie müssen die Nginx-Konfiguration nicht ändern — der Pfad ist fest und Sie müssen nur die Datei austauschen.

cd /opt/kopens/plantpulse-platform-docker

# pp-proxy-certs 볼륨의 server.crt / server.key 를 교체
docker cp /path/to/fullchain.crt plantpulse-proxy:/etc/nginx/certs/server.crt
docker cp /path/to/private.key plantpulse-proxy:/etc/nginx/certs/server.key

docker compose -f compose/docker-compose.yml restart plantpulse-proxy
ElementWert
Volumenpp-proxy-certs
Container-Pfad/etc/nginx/certs/server.crt · /etc/nginx/certs/server.key
Selbstsignierter CNPP_PROXY_CERT_CN (Standard plantpulse.local)

Setzen Sie die Zertifikatdatei als vollständige Kette (Fullchain) einschließlich Zwischenzertifikat.

MQTT TLS(1884) wird nicht vom Proxy beendet

Der Proxy leitet 1884 als Passthrough weiter und der Broker beendet TLS. Wenn Sie das Proxy-Zertifikat ändern, ändert sich daher nicht das Zertifikat, das der MQTT-Client validiert — das verwendet Material von plantpulse-certs.

(Referenz) Direktes Zertifikat auf dem Web-Server

Dies ist normalerweise nicht erforderlich für die Container-Basis-Images

Dies ist eine Referenz für Umgebungen, in denen der Web-Server ohne Proxy direkt offen liegt. In der Standard-Konfiguration reicht es, nur das Proxy-Zertifikat oben zu ändern. Das TLS-Material für den Backend-Abschnitt im Container wird bereits von plantpulse-certs erstellt.

Um HTTPS zu verwenden, muss ein SSL-Zertifikat installiert werden.

# 1. 키스토어 생성 (자체 서명 인증서 - 테스트용)
keytool -genkeypair -alias plantpulse -keyalg RSA -keysize 2048 \
-validity 365 -keystore /opt/kopens/plantpulse-platform/plantpulse-server/server/conf/keystore.jks \
-storepass changeit -keypass changeit \
-dname "CN=plantpulse.example.com, OU=IT, O=Company, L=Seoul, ST=Seoul, C=KR"

# 2. 공인 인증서 가져오기 (운영 환경)
keytool -importcert -alias plantpulse -file /path/to/certificate.crt \
-keystore /opt/kopens/plantpulse-platform/plantpulse-server/server/conf/keystore.jks \
-storepass changeit

# 3. 중간 인증서(Chain) 가져오기
keytool -importcert -alias intermediate -file /path/to/intermediate.crt \
-keystore /opt/kopens/plantpulse-platform/plantpulse-server/server/conf/keystore.jks \
-storepass changeit

# 4. 인증서 확인
keytool -list -v -keystore /opt/kopens/plantpulse-platform/plantpulse-server/server/conf/keystore.jks \
-storepass changeit

Tomcat HTTPS-Connector

Fügen Sie einen HTTPS-Connector zu server.xml des Tomcat hinzu.

<!-- plantpulse-server/server/conf/server.xml -->
<!-- HTTP connector (redirects to HTTPS) -->
<Connector port="80" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="443" />

<!-- HTTPS connector -->
<Connector port="443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="200"
SSLEnabled="true"
scheme="https"
secure="true"
keystoreFile="/opt/kopens/plantpulse-platform/plantpulse-server/server/conf/keystore.jks"
keystorePass="changeit"
clientAuth="false"
sslProtocol="TLSv1.2"
sslEnabledProtocols="TLSv1.2,TLSv1.3"
ciphers="TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" />

Umleitung von HTTP zu HTTPS

Leiten Sie alle HTTP-Anfragen automatisch zu HTTPS um.

<!-- WEB-INF/web.xml inside the webapp (bundled in the WAR — reset on deploy, so change it in the source) -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Secure</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>

TLS-Versionsverwaltung

Für Sicherheit wird empfohlen, nur TLS 1.2 und höher zuzulassen.

TLS-VersionStatusEmpfehlung
SSLv3DeaktiviertPOODLE-Sicherheitslücke
TLS 1.0DeaktiviertSicherheitslücken vorhanden
TLS 1.1DeaktiviertSicherheitslücken vorhanden
TLS 1.2ZulässigEmpfohlen
TLS 1.3ZulässigAktuell, sicherste

API-Authentifizierung

Die PlantPulse REST API (/api/v5) wird durch api_key im Header aufgerufen. Es gibt zwei Wege, api_key zu erhalten. Die nachfolgenden Aufrufe sind danach identisch.

api_key ist sofort ein Token-Wert

/api/v5/auth erstellt keine neue Zeichenkette. Wenn das eingereichte Token gültig ist, wird dieser Token-Wert direkt als api_key zurückgegeben (APIManagerapi_key = token.getToken()). Das Token hat UUID-Format und ist nicht JWT.

Token-Authentifizierung (externe Integrationen · Automatisierung)

Erhalten Sie api_key mit einem zuvor ausgestellten API-Token. Dies ist der Pfad, der bei Betriebsintegrationen verwendet wird.

# 1. api_key 발급 — 인증 헤더 없이 호출 가능(V5_Filter bypass 경로)
curl -X POST http://localhost/api/v5/auth \
-H "Content-Type: application/json" \
-d '{"token": "<API_TOKEN>"}'
# 200: {"data": {"api_key": "..."}, "meta": {"status": "OK", ...}, "errors": null}

# 2. 이후 모든 호출에 헤더로 싣는다
curl -X GET http://localhost/api/v5/asset \
-H "X-API-Key: <api_key>"
AntwortBedeutung
200Ausstellung erfolgreich
400Body ist kein JSON oder token fehlt
401 (E1100)Token ungültig
429 (E1101)Brute-Force-Blockade. Retry-After Header ist enthalten

Die Brute-Force-Verteidigung ist ein Zähler pro IP-Adresse. Bei jedem Fehler erhöht sich der Zähler für diese IP, und wenn die Grenze im Fenster überschritten wird, wird es mit 429 blockiert. Passen Sie dies in plantpulse-engine.properties an.

EigenschaftStandardwert
engine.api.v5.auth.bruteforce.enabledtrue
engine.api.v5.auth.bruteforce.ip_limit30
engine.api.v5.auth.bruteforce.window.seconds60 — wenn vorhanden, hat Priorität über window.minutes
engine.api.v5.auth.bruteforce.window.minutes15
Zwei Header

X-API-Key hat Vorrang; wenn nicht vorhanden, wird Authorization: Bearer <api_key> als Fallback gelesen. Bearer-Fallback ist der Standard-Pfad für MCP-Clients, die nur diesen Header senden können, und der Wert ist das gleiche api_key. Andere Schemata als Bearer werden ignoriert.

ID/Passwort-Authentifizierung (nur Konsolen-SPA)

Erhalten Sie api_key durch Login mit ID und Passwort. Dies ist der Pfad, den der Konsolenbildschirm verwendet. Verwenden Sie Token-Authentifizierung für Server-zu-Server-Integration — dies würde dazu führen, dass unverschlüsselte Anmeldeinformationen jedes Mal gesendet werden, und die Brute-Force-Oberfläche wird breiter.

curl -X POST http://localhost/api/v5/auth/login \
-H "Content-Type: application/json" \
-d '{"userId": "admin", "password": "<PASSWORD>"}'
# 200: {"api_key": "...", "expires_at": 1790000000000, "login_id": "admin"}
# 401: {"error": "..."}
Nur diese Antwort ist nicht in einem Envelope

Andere V5-Endpunkte verwenden das Format {data, meta, errors}, aber /api/v5/auth/login ist erfolgreich und fehlgeschlagen unverschlüsseltes JSON. Wenn Sie einen gemeinsamen Parser verwenden, teilen Sie nur diesen Pfad. expires_at ist Epoch-Millisekunden, und bei ewigen Token ist null.

Token-Ausstellung/-Widerruf API (/api/v5/token)

Führt die gleiche Aktion wie der Konsolenbildschirm System > API-Token (/token/index) über REST durch. Wird in automatischen Provisioning-Skripten verwendet, um Tokens zu erstellen und zu löschen. Für die Verwendung der Benutzeroberfläche siehe Sicherheitsverwaltung.

HTTPPfadAktion
GET/api/v5/tokenToken-Liste — Token-Wert ist maskiert(erste 8 Zeichen + ****)
GET/api/v5/token/{token}Einzelabfrage — maskiert
POST/api/v5/tokenAusstellung
DELETE/api/v5/token/{token}Widerruf
curl -X POST http://localhost/api/v5/token \
-H "X-API-Key: <ADMIN_API_KEY>" \
-H "Content-Type: application/json" \
-d '{"login_id": "svc-mes", "ip": "192.168.0.0/24",
"description": "MES", "ttl_days": 0}'
Body-FeldBeschreibung
login_idDas Konto, das das Token erhält. Wenn weggelassen, wird es für den Aufrufer ausgestellt (X-API-Key Besitzer)
ipIP-Muster, die Token-Verwendung zulassen
descriptionVerwendungsnotiz
ttl_daysGültigkeitsdauer (Tage). Wenn weggelassen, globaler Standard (engine.api.token.default_ttl_days), wenn 0 dann ewig, positive Zahl sind diese Tage
Ausstellung ist nur für ADMIN möglich

Wenn die Rolle des anfordernden Kontos (X-API-Key Besitzer) nicht ADMIN ist, ist es 403 E1200 und ein TOKEN_ISSUE_DENIED wird im Audit-Log protokolliert. Dies verhindert Berechtigungserweiterung, bei der API-Rollen-Konten ihre eigenen oder fremde Tokens erstellen. Die Rolle des Zielkontos (login_id) ist unerheblich. ttl_days erweitert diese Berechtigung nicht.

Warum war ttl_days notwendig

Wenn Sie den globalen Standard auf 0(ewig) ändern, werden alle von Kunden erstellten Tokens ebenfalls für immer. Um nur ein Automation-Token ewig zu halten, ohne die globale Einstellung zu ändern, gibt es diesen Ort. Negative oder Nicht-Integer-Werte sind 400 VALIDATION_FAILED — eine merkwürdige Absorbierung führt stille zu einem ewigen Token, was gefährlicher ist.

Die erfolgreiche Ausstellungsantwort enthält das Token-Original einmal. Es erscheint nicht wieder in Listen oder Abfragen. Speichern Sie es daher, wenn Sie es erhalten. Die Ausstellung wird mit TOKEN_ISSUED im Audit-Log protokolliert, und ewige Tokens werden mit PERMANENT im Ablauf-Feld registriert.


Passwort-Sicherheit

Passwort-Hashing

PlantPulse speichert Benutzerkennwörter als SHA-256-Hash. Das ursprüngliche Passwort wird nicht auf dem Server gespeichert.

Empfohlene Passwortrichtlinie

ElementEmpfohlener WertBeschreibung
Mindestlänge8 Zeichen oder mehr12 Zeichen oder mehr empfohlen
Komplexität3 oder mehr ZeichentypenKombination aus Großbuchstaben, Kleinbuchstaben, Zahlen und Sonderzeichen
Änderungszyklus90 TageVierteljährliche Änderung
VersionsverlaufLetzte 3Wiederverwendung früherer Passwörter verhindern
Sperrung nach Fehlversuchen5 VersucheKonto nach 5 aufeinanderfolgenden fehlgeschlagenen Versuchen sperren
Entsperrung30 Minuten oder AdministratorenAutomatische oder manuelle Entsperrung

HTTP-Sicherheits-Header

Sicherheitsheader in HTTP-Antworten werden gesetzt, um Sicherheitsfunktionen des Web-Browsers zu aktivieren.

Empfohlene Sicherheits-Header

HeaderWertBeschreibung
X-XSS-Protection1; mode=blockBrowser XSS-Filter aktivieren
X-Content-Type-OptionsnosniffMIME-Typ-Sniffing verhindern
X-Frame-OptionsSAMEORIGINClickjacking-Schutz
Content-Security-Policydefault-src 'self'Inhaltsquelle einschränken
Strict-Transport-Securitymax-age=31536000; includeSubDomainsHTTPS erzwingen (HSTS)
Referrer-Policystrict-origin-when-cross-originReferrer-Informationen einschränken

Nginx-Konfigurationsbeispiel

Wenn Sie Nginx als Reverse Proxy verwenden, fügen Sie wie folgt Sicherheits-Header hinzu.

server {
listen 443 ssl http2;
server_name plantpulse.example.com;

# SSL 인증서
ssl_certificate /etc/ssl/certs/plantpulse.crt;
ssl_certificate_key /etc/ssl/private/plantpulse.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;

# 보안 헤더
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

location / {
proxy_pass http://127.0.0.1:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

# HTTP → HTTPS 리다이렉트
server {
listen 80;
server_name plantpulse.example.com;
return 301 https://$host$request_uri;
}

HTTP-Methodenbeschränkung

Blockieren Sie unnötige HTTP-Methoden, um Informationslecks zu verhindern.

<!-- WEB-INF/web.xml inside the webapp (bundled in the WAR — reset on deploy, so change it in the source) -->
<security-constraint>
<web-resource-collection>
<web-resource-name>RestrictMethods</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>OPTIONS</http-method>
<http-method>TRACE</http-method>
<http-method>HEAD</http-method>
</web-resource-collection>
<auth-constraint/>
</security-constraint>

Hinweis: Die OPTIONS-Methode wird für CORS-Preflight-Anfragen verwendet. Wenn eine API-Integration mit externen Systemen erforderlich ist, muss die OPTIONS-Methode möglicherweise zulässig sein.


Sitzungssicherheit

<!-- WEB-INF/web.xml inside the webapp (bundled in the WAR — reset on deploy, so change it in the source) -->
<session-config>
<session-timeout>30</session-timeout>
<cookie-config>
<http-only>true</http-only> <!-- block cookie access from JavaScript -->
<secure>true</secure> <!-- send cookies over HTTPS only -->
<max-age>1800</max-age> <!-- cookie lifetime (seconds) -->
</cookie-config>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
EinstellungWertBeschreibung
http-onlytrueVerhindert Session-Hijacking durch XSS
securetrueVerhindert Cookie-Übertragung über HTTP (HTTPS erforderlich)
tracking-modeCOOKIEVerhindert Session-ID-Offenlegung in URLs

Session Fixation-Schutz

PlantPulse regeneriert die Session-ID nach erfolgreichem Login, um Session Fixation-Angriffe zu verhindern. Diese Funktion ist in SecurityFilter integriert und wird automatisch ohne separate Konfiguration angewendet.


Datenbank-Sicherheit

PostgreSQL

Legen Sie in der Datei pg_hba.conf den Bereich der zulässigen Zugriffe fest.

# /opt/kopens/plantpulse-platform/plantpulse-storage/db/postgres/data/pg_hba.conf

# TYPE DATABASE USER ADDRESS METHOD
# 로컬 접근 (Unix 소켓)
local all all md5

# 로컬호스트 접근
host all all 127.0.0.1/32 md5
host all all ::1/128 md5

# PlantPulse 서버 접근 (특정 IP만 허용)
host plantpulse plantpulse 10.0.0.0/24 md5

# 그 외 접근 차단 (기본)
# host all all 0.0.0.0/0 reject

Zusätzliche Sicherheitseinstellungen (postgresql.conf):

# 접속 제한
listen_addresses = '127.0.0.1' # 로컬만 허용 (기본값: '*')
max_connections = 200

# 인증 타임아웃
authentication_timeout = 60 # 초

# SSL 활성화
ssl = on
ssl_cert_file = '/path/to/server.crt'
ssl_key_file = '/path/to/server.key'

# 로그
log_connections = on
log_disconnections = on
log_statement = 'ddl' # DDL 문만 로깅

Cassandra

# /opt/kopens/plantpulse-platform/plantpulse-storage/db/cassandra/conf/cassandra.yaml

# 인증 활성화
authenticator: PasswordAuthenticator

# 인가 활성화
authorizer: CassandraAuthorizer

# 클라이언트 암호화 (TLS)
client_encryption_options:
enabled: true
optional: false
keystore: /opt/kopens/plantpulse-platform/plantpulse-storage/db/cassandra/conf/.keystore
keystore_password: changeit
truststore: /opt/kopens/plantpulse-platform/plantpulse-storage/db/cassandra/conf/.truststore
truststore_password: changeit
protocol: TLS
algorithm: SunX509
cipher_suites: [TLS_RSA_WITH_AES_256_CBC_SHA]

# 노드 간 암호화
server_encryption_options:
internode_encryption: all
keystore: /opt/kopens/plantpulse-platform/plantpulse-storage/db/cassandra/conf/.keystore
keystore_password: changeit
truststore: /opt/kopens/plantpulse-platform/plantpulse-storage/db/cassandra/conf/.truststore
truststore_password: changeit

Redis (Valkey)

# /opt/kopens/plantpulse-platform/plantpulse-storage/db/valkey/conf/valkey.conf

# 비밀번호 설정
requirepass your_strong_password_here

# 바인드 주소 (로컬만 허용)
bind 127.0.0.1

# 보호 모드 활성화
protected-mode yes

# 위험 명령어 비활성화
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command CONFIG ""
rename-command DEBUG ""
rename-command SHUTDOWN PLANTPULSE_SHUTDOWN

Netzwerksicherheit

Firewall-Konfiguration

Lassen Sie in der Firewall nur die Ports zu, für die PlantPulse einen Außenzugriff benötigt.

# firewalld 설정 예시

# 웹 서비스 (외부 접근 허용)
sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --permanent --add-port=443/tcp

# 내부 서비스 (외부 접근 차단 - 기본값)
# 아래 포트들은 외부에서 접근하지 않도록 해 주세요
# PostgreSQL: 5432, Redis: 6379, Cassandra: 9042
# Kafka: 9092, MQTT: 1883

# OPC Agent (필요한 클라이언트 IP만 허용)
sudo firewall-cmd --permanent --add-rich-rule='
rule family="ipv4"
source address="10.0.1.0/24"
port protocol="tcp" port="60000"
accept'

# 적용
sudo firewall-cmd --reload

# 확인
sudo firewall-cmd --list-all

Netzwerk-Segmentierung

In industriellen Umgebungen wird empfohlen, das Netzwerk wie folgt zu unterteilen.

Netzwerk-BereichVerwendungBeispiel
OT-NetzwerkPLC, SCADA, Sensoren10.0.1.0/24
DMZPlantPulse Web-Server, API10.0.2.0/24
IT-NetzwerkBenutzerzugriff, Verwaltung10.0.3.0/24
DatennetzwerkDB, Messaging10.0.4.0/24
  • OT- und IT-Netzwerk sollten nicht direkt miteinander kommunizieren.
  • Platzieren Sie den PlantPulse-Server in der DMZ und erlauben Sie nur eingeschränkte Kommunikation mit beiden Netzwerken.
  • Platzieren Sie die Datenbank im Datennetzwerk und erlauben Sie den Zugriff nur vom PlantPulse-Server.

VPN-Zugriff

Wenn Sie von Remote auf die Verwaltungskonsole zugreifen müssen, konfigurieren Sie den Zugriff über VPN.

  • Zugriff auf die Verwaltungskonsole und SSH ist nur über VPN zulässig.
  • VPN-Konten sollten pro Person ausgestellt werden; verwenden Sie keine gemeinsamen Konten.
  • Protokollieren und überprüfen Sie VPN-Zugriffslogs regelmäßig.

Sicherheits-Checkliste

Tägliche Inspektion

InspektionselementÜberprüfungsmethode
Verdächtige Login-VersucheFehlerhafte Login-Versuche im Audit-Log prüfen
System-FehlerprotokolleSicherheitsbezogene Fehler/Warnprotokolle überprüfen
DienstverfügbarkeitHealth-Check-API-Antwort überprüfen

Wöchentliche Inspektion

InspektionselementÜberprüfungsmethode
Benutzerkonten-StatusUngenutzte und inaktive Konten überprüfen
Berechtigungsänderungs-VerlaufBerechtigungsänderungen im Audit-Log überprüfen
Firewall-ProtokolleBlockierte Zugriffsmeldungen überprüfen
SSL-Zertifikat-GültigkeitsdauerZertifikat-Ablaufdatum überprüfen

Monatliche Inspektion

InspektionselementÜberprüfungsmethode
OS-Sicherheits-PatchesNeueste Sicherheitsupdates überprüfen
Passwortrichtlinie-EinhaltungLangfristig ungeänderte Passwörter überprüfen
DB-Zugriffsberechtigungpg_hba.conf und andere Zugriffskontrolleinstellungen überprüfen
Backup-Daten-SicherheitBackup-Dateien-Verschlüsselung und Zugriffsberechtigung überprüfen
Port-ScanUnnötig offene Ports überprüfen
TLS-EinstellungenSchwache Protokolle/Cipher-Suites überprüfen

Sicherheits-Vorfallreaktion

Wenn ein Sicherheitsvorfall auftritt oder verdächtigt wird, folgen Sie diesem Verfahren.

Stufe 1: Erkennung und Isolierung

# 의심 IP 차단
sudo firewall-cmd --add-rich-rule='rule family="ipv4" source address="의심IP" drop' --permanent
sudo firewall-cmd --reload

# 의심 사용자 계정 비활성화 (관리 콘솔 또는 DB)
# 관리 콘솔: 보안 관리 > 사용자 관리 > 상태 변경

# 현재 활성 세션 확인
curl -X GET http://localhost/api/v5/sessions \
-H "Authorization: Bearer <admin-token>"

Stufe 2: Analyse

# 로그인 이력 조회
grep "LOGIN\|LOGOUT\|AUTH" /opt/kopens/plantpulse-platform/plantpulse-server/logs/system.log

# 접근 로그 분석
grep "의심IP" /opt/kopens/plantpulse-platform/plantpulse-server/server/logs/localhost_access_log.*.txt

# DB 접근 로그 (PostgreSQL)
grep "의심IP" /opt/kopens/plantpulse-platform/plantpulse-storage/db/postgres/data/log/postgresql-*.log

Stufe 3: Wiederherstellung

  • Ändern Sie das Passwort für das betroffene Konto.
  • Überprüfen Sie die Integrität der betroffenen Systeme.
  • Stellen Sie bei Bedarf Daten aus dem Backup wieder her.

Stufe 4: Nachbereitung

  • Erstellen Sie einen Vorfallbericht.
  • Erarbeiten Sie Maßnahmen zur Wiederholung.
  • Verstärken Sie die Sicherheitseinstellungen.
  • Führen Sie Sicherheitsschulung für betroffene Personen durch.

Kontakt: Für Sicherheitsfragen kontaktieren Sie bitte webmaster@kopens.com.