安全设置
概述
本文档介绍 PlantPulse 平台的安全设置方法。通过网络、传输、应用和数据层面的安全设置,可以确保平台安全运行。
重要: 工业控制系统 (ICS) 以可用性为最优先,因此在更改安全设置时,务必先在测试环境中验证,然后再应用到生产环境。
安全架构
PlantPulse 的安全防护由 4 个层级组成。
| 层级 | 安全领域 | 主要功能 |
|---|---|---|
| 1. 网络 | 防火墙、分段、VPN | 阻断外部访问,网络隔离 |
| 2. 传输 | HTTPS/TLS、加密 | 通信数据加密 |
| 3. 应用 | 认证、授权、过滤、会话 | 用户认证及权限管理 |
| 4. 数据 | 数据库访问控制、加密 | 存储数据保护 |
TLS / 证书(自动管理)
生成证书的是一个 plantpulse-certs 容器。当堆栈启动时,它最先运行,并通过 pd_ssl_prepare(plantpulse-datalake-cli 的 lib/lib-ssl.sh)创建所有材料,然后写入 pp-security 卷中。之后,它会持续运行,每 6 小时检查一次过期情况,并且只有在证书进入到期前 30 天时才会重新签发。其余容器将此卷以只读方式挂载并使用。
| 项目 | 值 |
|---|---|
| 生成主体 | plantpulse-certs(常驻运行 — (healthy) 正常) |
| 材料位置 | 卷 pp-security,容器内路径 /var/security/plantpulse |
| 读取方 | 数据湖和应用 — 全部为 :ro |
| 例外 | 只有 plantpulse-plugin-opcua 是读写模式。因为启动时需要创建安全临时目录 |
| 设置 | compose 中的 PP_TLS_* 环境变量 |
只有当请求的 SAN 与 .certs_generated 中记录的不同时,pd_ssl_prepare 才会重新签发。因此,
即使重启容器,材料也不会改变。如果需要重新签发,请按以下流程明确操作。
这是 restart: unless-stopped 的情况 — 如果被人为停止,即使重启 Docker 守护进程也会保持停止状态。
这是为了在事故应对期间,手动放入的材料得以保留。
env.sh TLS 变量
| 变量 | 默认值 | 说明 |
|---|---|---|
PP_TLS_ENABLED | true | TLS 启用 |
PP_TLS_CERT_DIR | /var/security/plantpulse | 证书目录 |
PP_TLS_DOMAIN | plantpulse.io | 默认域名 |
PP_TLS_KEYSTORE_PASSWORD | kopens123! (必须更改) | keystore 密码 |
PP_TLS_TRUSTSTORE_PASSWORD | ${PP_TLS_KEYSTORE_PASSWORD} | truststore 密码 |
PP_TLS_VALID_DAYS | 3650 | 有效期 (天) |
PP_TLS_EC_GROUP | secp256r1 | ECDSA 曲线 |
PP_TLS_SIGALG | SHA256withECDSA | 签名算法 |
PP_TLS_OPCUA_APP_URI | urn:plantpulse:opcua:server | OPC-UA 应用程序 URI |
PP_TLS_NODE_NAMES | master worker-1 ... worker-5 | 集群节点名 |
PP_TLS_SAN_DNS | localhost,<hostname>,<domain> | SAN DNS |
PP_TLS_SAN_IPS | <HOST_IP>,<SERVICE_IP>,<PUBLIC_IP>,127.0.0.1 | SAN IP (必须包含 NAT/外部 IP) |
PP_TLS_FORCE_REGENERATE | false | 为 true 时强制重新生成 |
证书生成流程
安装时会自动执行一次,因此通常无需任何操作。仅在 SAN 发生变化或证书过期、损坏时,才执行以下操作。
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
PP_TLS_SAN_IPS 由 PP_HOST_IP · PP_SERVICE_IP · PP_MASTER_IP · PP_PUBLIC_IP · 127.0.0.1 组装而成。只要有一个格式不正确的值,openssl 就会拒绝整个扩展文件。请勿在非 NAT 的机器上填写 DOCKER_PP_EXTERNAL_IP。
日志中不会有任何关于证书的信息。PP_TLS_SAN_DNS 的默认值是二十一个,
分为三组。
| 组别 | 数量 | 名称 |
|---|---|---|
| 当前使用的容器名称 | 8 | localhost · plantpulse-datalake · plantpulse-server-web · plantpulse-batch-web · plantpulse-warehouse-web · plantpulse-plugin-opcua · plantpulse-plugin-aasx · plantpulse-proxy |
| 改名前的旧名称 | 8 | plantpulse-warehouse · plantpulse-server · plantpulse-batch · plantpulse-warehouse-s3 · plantpulse-opcua · plantpulse-aasx · plantpulse-plugin-opcua-server · plantpulse-plugin-aasx-server |
| 工作节点 | 5 | plantpulse-datalake-worker-1 ~ -5 |
在此之前签发的证书中没有包含任何工作节点,工作节点 IP 也不在 IP SAN 中 (dev 41 实测:十六个 DNS 名称中工作节点 0 个)。因此,工作节点终止 TLS 时, 客户端既没有可验证的名称,也没有地址。如果集群仍在使用之前生成的证书,则需要进行重新签发。
如果更改了容器名称或直接指定了后端主机,也必须同时修改 SAN 列表。
如果将工作节点扩展到六个或更多,也需要在 PP_TLS_SAN_DNS 中添加这些名称 —
默认值最多为五个。
:::
pd_ssl_prepare 生成的产物:
/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/ # 클라이언트 인증서 (선택)
使用外部 CA 证书
如果要使用公司/Let's Encrypt CA 证书代替自签名证书:
# 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
过期监控: 请配置外部监控(
openssl x509 -enddate),以便在证书到期前 30 天触发报警。
Web 安全过滤器
PlantPulse 通过 web.xml 中定义的安全过滤器来保护 Web 请求。
SecurityFilter
这是阻止未经认证的用户访问的核心过滤器。
<!-- 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>
- 对所有请求(
/*)检查认证状态。 - 在
excludeUrls中指定的路径无需认证即可访问。 - 未经认证的请求将被重定向到登录页面。
XSSFilter
这是防止跨站脚本 (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>
- 从请求参数中移除
<script>、<iframe>等危险标签。 - 由于 API 路径应用了单独的输入验证,因此可以从过滤器中排除。
CSRF 防御
为防止跨站请求伪造 (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>
- 对 POST、PUT、DELETE 请求验证 CSRF 令牌。
- 表单请求时需要
_csrf参数或X-CSRF-TOKEN头。 - REST API(
/api/v5/)使用令牌认证,因此从 CSRF 过滤器中排除。
EncodingFilter
统一字符编码,以防止与编码相关的安全漏洞。
<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 设置
用户看到的 80 / 443 由 plantpulse-proxy(nginx)终结。 Web 服务器容器不打开主机端口,因此,更换浏览器用证书的位置是代理,而不是 Tomcat。
代理证书 — 默认为自签名
安装后,无需任何准备,443 端口即可直接打开。 这是因为代理在首次启动时会创建自签名证书并放入 pp-proxy-certs 卷中。
如果重启会将客户的真实证书回复到自签名状态,这是一个静默事故,因此,如果文件已存在,代理会直接使用它。
替换为真实证书
无需修改 nginx 配置 — 路径是固定的,只需替换文件即可。
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
| 项目 | 值 |
|---|---|
| 卷 | pp-proxy-certs |
| 容器内路径 | /etc/nginx/certs/server.crt · /etc/nginx/certs/server.key |
| 自签名 CN | PP_PROXY_CERT_CN (默认 plantpulse.local) |
请将证书文件作为包含中间证书的完整链 (fullchain) 放入。
代理将 1884 端口以 passthrough 方式转发,由 Broker 终结 TLS。因此,即使更换代理证书,MQTT 客户端验证的证书也不会改变 — 那边使用的是 plantpulse-certs 创建的材料。
(参考)直接在 Web 服务器上安装证书
以下内容供不使用代理而直接暴露 Web 服务器的环境参考。在标准配置中,只需更换上面的代理证书即可。容器内后端区域的 TLS 材料已由 plantpulse-certs 创建好。
要使用 HTTPS,必须安装 SSL 证书。
# 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 连接器
在 Tomcat 的 server.xml 中添加 HTTPS 连接器。
<!-- 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" />
从 HTTP 重定向到 HTTPS
自动将所有 HTTP 请求重定向到 HTTPS。
<!-- 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 版本管理
为安全起见,建议仅允许 TLS 1.2 及以上版本。
| TLS 版本 | 状态 | 建议 |
|---|---|---|
| SSLv3 | 禁止使用 | POODLE 漏洞 |
| TLS 1.0 | 禁止使用 | 存在漏洞 |
| TLS 1.1 | 禁止使用 | 存在漏洞 |
| TLS 1.2 | 允许 | 推荐 |
| TLS 1.3 | 允许 | 最新,最安全 |
API 认证
PlantPulse REST API(/api/v5)通过在请求头中携带 api_key 进行调用。获取 api_key
的路径有两种,其后的调用方式相同。
/api/v5/auth 不会生成新的字符串。如果提交的令牌有效,它会将该令牌值
原样作为 api_key 返回(APIManager — api_key = token.getToken())。令牌是 UUID
格式,不是 JWT。
令牌认证(外部集成 · 自动化)
使用预先签发的 API 令牌获取 api_key。这是运营集成中使用的路径。
# 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>"
| 响应 | 含义 |
|---|---|
200 | 签发成功 |
400 | 正文不是 JSON 或缺少 token |
401 (E1100) | 令牌无效 |
429 (E1101) | 暴力破解阻止。同时返回 Retry-After 头 |
暴力破解防御是 1 种基于 IP 的计数器。 每次失败都会增加该 IP 的计数器,
如果在窗口内超过限制,则返回 429 进行阻止。可在 plantpulse-engine.properties 中进行调整。
| 属性 | 默认值 |
|---|---|
engine.api.v5.auth.bruteforce.enabled | true |
engine.api.v5.auth.bruteforce.ip_limit | 30 |
engine.api.v5.auth.bruteforce.window.seconds | 60 — 如果存在,优先于 window.minutes |
engine.api.v5.auth.bruteforce.window.minutes | 15 |
X-API-Key 是第一优先级,如果没有,则回退读取 Authorization: Bearer <api_key>。
Bearer 回退是为只能发送该头的 MCP 客户端提供的标准路径,值也是相同的
api_key。忽略除 Bearer 之外的任何其他 scheme。
ID/密码认证(控制台 SPA 专用)
通过用户名和密码登录获取 api_key。这是控制台界面使用的路径,对于服务器间的 集成,请使用令牌认证 — 因为每次都会发送明文凭据,会扩大暴力破解的攻击面。
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": "..."}
其他 V5 端点是 {data, meta, errors} 格式,但 /api/v5/auth/login 无论成功还是失败
都是纯 JSON。如果您使用通用的解析器,请仅为该路径进行分流。
expires_at 是 epoch 毫秒,如果是永久令牌,则为 null。
令牌签发/回收 API(/api/v5/token)
使用 REST 完成与控制台 System > API 令牌(/token/index)界面相同的操作。用于
在自动布线脚本中创建和删除令牌。界面使用方法请参阅
安全管理。
| HTTP | 路径 | 功能 |
|---|---|---|
GET | /api/v5/token | 令牌列表 — token 值会被掩码(前 8 位 + ****) |
GET | /api/v5/token/{token} | 单个查询 — 掩码 |
POST | /api/v5/token | 签发 |
DELETE | /api/v5/token/{token} | 回收 (revoke) — 执行者为 X-API-Key 所有者 |
POST | /api/v5/admin/tokens/{token}/revoke | 仅限 ADMIN 回收 — 用于断开他人的令牌 |
DELETE /api/v5/token/{token} 是清理自己的令牌的路径,
而 POST /api/v5/admin/tokens/{token}/revoke 是管理员断开他人令牌的路径。
后者只允许 ADMIN 角色通过 — 使用 API 角色令牌调用也会返回 403。
事故应对中使用的是后者。
两者都会记录 revoked_at 日志并使令牌缓存失效,因此会立即阻止。
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}'
| 请求体字段 | 说明 |
|---|---|
login_id | 接收令牌的账户。如果省略,则签发给调用者(X-API-Key 所有者) |
ip | 允许使用令牌的 IP 模式 |
description | 用途备注 |
ttl_days | 有效期(天)。如果省略,则使用全局默认值(engine.api.token.default_ttl_days),如果为 0 则为永久,正数则为此天数 |
如果请求账户(X-API-Key 所有者)的角色不是 ADMIN,则返回 403 E1200,并且审计日志中
会记录 1 条 TOKEN_ISSUE_DENIED。这是为了防止 API 角色账户自行或为他人签发令牌导致权限提升。
目标账户(login_id)的角色无关。
ttl_days 不扩大此权限。
ttl_days如果将全局默认值改为 0(永久),客户在界面上创建的令牌也会全部变为永久。
这是为了避免为了只让一个自动布线令牌永久有效而修改全局设置。对于负数或非整数值,
将返回 400 VALIDATION_FAILED — 因为吞掉异常值并静默创建永久令牌更危险。
签发成功的响应中包含一次令牌原文。列表和查询中不会再出现,因此请在此处
获取并妥善保管。签发会以 TOKEN_ISSUED 记录在审计日志中,永久令牌会在过期位置
记录为 PERMANENT。
密码安全
密码哈希
PlantPulse 使用 SHA-256 哈希存储用户密码。原始密码不存储在服务器上。
密码策略推荐值
| 项目 | 推荐值 | 说明 |
|---|---|---|
| 最小长度 | 8 个字符以上 | 建议 12 个字符以上 |
| 复杂度 | 3 种以上组合 | 英文字母大小写、数字、特殊字符中的 3 种 |
| 更改周期 | 90 天 | 每季度更改 |
| 历史管理 | 最近 3 个 | 禁止复用旧密码 |
| 连续失败锁定 | 5 次 | 连续失败 5 次后锁定账户 |
| 解锁方式 | 30 分钟或管理员 | 自动解锁或手动解锁 |
HTTP 安全头
为启用 Web 浏览器的安全功能,设置 HTTP 响应头。
推荐的 HTTP 安全头
| 头 | 值 | 说明 |
|---|---|---|
X-XSS-Protection | 1; mode=block | 启用浏览器 XSS 过滤器 |
X-Content-Type-Options | nosniff | 防止 MIME 类型嗅探 |
X-Frame-Options | SAMEORIGIN | 防止点击劫持 (Clickjacking) |
Content-Security-Policy | default-src 'self' | 限制内容源 |
Strict-Transport-Security | max-age=31536000; includeSubDomains | 强制 HTTPS (HSTS) |
Referrer-Policy | strict-origin-when-cross-origin | 限制引用来源信息 |
Nginx 配置示例
如果使用 Nginx 作为反向代理,请按以下方式添加安全头。
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 方法限制
通过阻止不必要的 HTTP 方法来防止信息泄露。
<!-- 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>
提示: OPTIONS 方法用于 CORS 预检请求。如果需要进行外部系统 API 集成,可能需要允许 OPTIONS 方法。
会话安全
Cookie 安全设置
<!-- 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>
| 设置 | 值 | 说明 |
|---|---|---|
http-only | true | 防止通过 XSS 窃取会话 |
secure | true | 防止在 HTTP 中传输 Cookie(需要 HTTPS) |
tracking-mode | COOKIE | 防止会话 ID 在 URL 中暴露 |
防止会话固定攻击
PlantPulse 在登录成功时会重新生成会话 ID,以防止会话固定攻击。此功能内置于 SecurityFilter 中,无需额外设置即可自动应用。
数据库安全
PostgreSQL
在 pg_hba.conf 文件中设置访问允许范围。
# /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
其他安全设置(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 문만 로깅
Aster TSDB
# /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
网络安全
防火墙设置
请仅在防火墙中允许 PlantPulse 所需的外部访问端口。
# 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
网络分段
在工业环境中,建议按以下方式对网络进行隔离。
| 网络区域 | 用途 | 示例 |
|---|---|---|
| OT 网络 | PLC、SCADA、传感器 | 10.0.1.0/24 |
| DMZ | PlantPulse Web 服务器、API | 10.0.2.0/24 |
| IT 网络 | 用户访问、管理 | 10.0.3.0/24 |
| 数据网络 | 数据库、消息传递 | 10.0.4.0/24 |
- 请确保 OT 网络和 IT 网络不直接通信。
- 将 PlantPulse 服务器置于 DMZ 中,仅允许其与两侧网络进行受限通信。
- 将数据库置于数据网络中,并仅允许 PlantPulse 服务器访问。
VPN 访问
如果需要远程访问管理控制台,请配置为通过 VPN 连接。
- 管理控制台和 SSH 访问必须仅通过 VPN 允许。
- VPN 账户应逐个签发,请不要使用共享账户。
- 记录 VPN 连接日志并定期审查。
安全巡检清单
每日巡检
| 巡检项目 | 确认方法 |
|---|---|
| 异常登录尝试 | 在审计日志中检查登录失败次数 |
| 系统错误日志 | 检查与安全相关的错误/警告日志 |
| 服务可用性 | 检查健康检查 API 的响应 |
每周巡检
| 巡检项目 | 确认方法 |
|---|---|
| 用户账户状况 | 检查未使用账户、非活动账户 |
| 权限变更历史 | 在审计日志中检查权限变更 |
| 防火墙日志 | 检查被阻止的访问尝试 |
| SSL 证书有效期 | 检查证书到期日 |
每月巡检
| 巡检项目 | 确认方法 |
|---|---|
| OS 安全补丁 | 确认已应用最新的安全更新 |
| 密码策略遵守情况 | 检查长期未更改的密码 |
| 数据库访问权限 | 检查 pg_hba.conf 等访问控制设置 |
| 备份数据安全 | 检查备份文件的加密和访问权限 |
| 端口扫描 | 检查不必要的开放端口 |
| TLS 设置 | 检查弱协议/加密套件 |
安全事件响应
当发生或怀疑发生安全事件时,请按以下流程进行响应。
第 1 步:检测与隔离
# 의심 IP 차단
sudo firewall-cmd --add-rich-rule='rule family="ipv4" source address="의심IP" drop' --permanent
sudo firewall-cmd --reload
# 의심 사용자 계정 비활성화 (관리 콘솔 또는 DB)
# 관리 콘솔: 보안 관리 > 사용자 관리 > 상태 변경
# 의심 계정의 API 토큰을 즉시 회수 (ADMIN 전용 — 아래 참고)
curl -X POST http://localhost/api/v5/admin/tokens/<TOKEN>/revoke \
-H "X-API-Key: <ADMIN_API_KEY>"
没有用于列出登录会话或强制断开的界面,也没有相应的 API。会话只能等待过期或 重启服务器来断开。如果需要立即阻止,请禁用账户并回收 API 令牌 — 这是目前可用的手段。
关于谁在何时进入,请查看审计日志和下面第 2 步的
system.log grep。
第 2 步:分析
# 로그인 이력 조회
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
第 3 步:恢复
- 更改受影响账户的密码。
- 检查受影响系统的完整性。
- 如有必要,从备份中恢复数据。
第 4 步:事后处理
- 编写事件报告。
- 制定防止再次发生的措施。
- 加强安全设置。
- 对相关人员进行安全教育。
联系方式: 有关安全方面的咨询,请联系 webmaster@kopens.com。