From 5dfd3185e03250c720c0277c35c4a96307cac34f Mon Sep 17 00:00:00 2001 From: okxlin <61420215+okxlin@users.noreply.github.com> Date: Wed, 15 Jul 2026 05:29:02 +0800 Subject: [PATCH] Add cerbos app Maintainer-workflow: appstore-pr-maintainer --- apps/cerbos/0.53.0/.env.sample | 4 ++ apps/cerbos/0.53.0/data.yml | 52 +++++++++++++++ apps/cerbos/0.53.0/data/.gitkeep | 0 apps/cerbos/0.53.0/docker-compose.yml | 26 ++++++++ apps/cerbos/0.53.0/scripts/.gitkeep | 0 apps/cerbos/0.53.0/scripts/init.sh | 80 ++++++++++++++++++++++++ apps/cerbos/0.53.0/scripts/uninstall.sh | 8 +++ apps/cerbos/0.53.0/scripts/upgrade.sh | 5 ++ apps/cerbos/README.md | 54 ++++++++++++++++ apps/cerbos/data.yml | 31 +++++++++ apps/cerbos/latest/.env.sample | 4 ++ apps/cerbos/latest/data.yml | 52 +++++++++++++++ apps/cerbos/latest/data/.gitkeep | 0 apps/cerbos/latest/docker-compose.yml | 26 ++++++++ apps/cerbos/latest/scripts/.gitkeep | 0 apps/cerbos/latest/scripts/init.sh | 80 ++++++++++++++++++++++++ apps/cerbos/latest/scripts/uninstall.sh | 8 +++ apps/cerbos/latest/scripts/upgrade.sh | 5 ++ apps/cerbos/logo.png | Bin 0 -> 4057 bytes 19 files changed, 435 insertions(+) create mode 100644 apps/cerbos/0.53.0/.env.sample create mode 100644 apps/cerbos/0.53.0/data.yml create mode 100644 apps/cerbos/0.53.0/data/.gitkeep create mode 100644 apps/cerbos/0.53.0/docker-compose.yml create mode 100644 apps/cerbos/0.53.0/scripts/.gitkeep create mode 100755 apps/cerbos/0.53.0/scripts/init.sh create mode 100755 apps/cerbos/0.53.0/scripts/uninstall.sh create mode 100755 apps/cerbos/0.53.0/scripts/upgrade.sh create mode 100644 apps/cerbos/README.md create mode 100644 apps/cerbos/data.yml create mode 100644 apps/cerbos/latest/.env.sample create mode 100644 apps/cerbos/latest/data.yml create mode 100644 apps/cerbos/latest/data/.gitkeep create mode 100644 apps/cerbos/latest/docker-compose.yml create mode 100644 apps/cerbos/latest/scripts/.gitkeep create mode 100755 apps/cerbos/latest/scripts/init.sh create mode 100755 apps/cerbos/latest/scripts/uninstall.sh create mode 100755 apps/cerbos/latest/scripts/upgrade.sh create mode 100644 apps/cerbos/logo.png diff --git a/apps/cerbos/0.53.0/.env.sample b/apps/cerbos/0.53.0/.env.sample new file mode 100644 index 000000000..47c151c5a --- /dev/null +++ b/apps/cerbos/0.53.0/.env.sample @@ -0,0 +1,4 @@ +PANEL_APP_PORT_HTTP=3592 +PANEL_APP_PORT_GRPC=3593 +APP_DATA_DIR=./data +CONTAINER_NAME=cerbos diff --git a/apps/cerbos/0.53.0/data.yml b/apps/cerbos/0.53.0/data.yml new file mode 100644 index 000000000..397682b31 --- /dev/null +++ b/apps/cerbos/0.53.0/data.yml @@ -0,0 +1,52 @@ +additionalProperties: + formFields: + - default: 3592 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: HTTP API Port + labelZh: HTTP API 端口 + label: + en: HTTP API Port + zh: HTTP API 端口 + zh-Hant: HTTP API 連接埠 + ja: HTTP API ポート + ko: HTTP API 포트 + ru: Порт HTTP API + ms: Port API HTTP + pt-br: Porta da API HTTP + required: true + rule: paramPort + type: number + - default: 3593 + edit: true + envKey: PANEL_APP_PORT_GRPC + labelEn: gRPC API Port + labelZh: gRPC API 端口 + label: + en: gRPC API Port + zh: gRPC API 端口 + zh-Hant: gRPC API 連接埠 + ja: gRPC API ポート + ko: gRPC API 포트 + ru: Порт gRPC API + ms: Port API gRPC + pt-br: Porta da API gRPC + required: true + rule: paramPort + type: number + - default: ./data + edit: true + envKey: APP_DATA_DIR + labelEn: Data Directory + labelZh: 数据目录 + label: + en: Data Directory + zh: 数据目录 + zh-Hant: 資料目錄 + ja: データディレクトリ + ko: 데이터 디렉터리 + ru: Каталог данных + ms: Direktori Data + pt-br: Diretório de dados + required: true + type: text diff --git a/apps/cerbos/0.53.0/data/.gitkeep b/apps/cerbos/0.53.0/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/cerbos/0.53.0/docker-compose.yml b/apps/cerbos/0.53.0/docker-compose.yml new file mode 100644 index 000000000..b79d6ec00 --- /dev/null +++ b/apps/cerbos/0.53.0/docker-compose.yml @@ -0,0 +1,26 @@ +services: + cerbos: + image: "cerbos/cerbos:0.53.0" + container_name: ${CONTAINER_NAME} + restart: unless-stopped + networks: + - 1panel-network + ports: + - "${PANEL_APP_PORT_HTTP}:3592" + - "${PANEL_APP_PORT_GRPC}:3593" + environment: + - CERBOS_NO_TELEMETRY=1 + volumes: + - "${APP_DATA_DIR}/policies:/policies" + healthcheck: + test: ["CMD", "/cerbos", "healthcheck"] + interval: 10s + timeout: 2s + start_period: 10s + retries: 5 + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/cerbos/0.53.0/scripts/.gitkeep b/apps/cerbos/0.53.0/scripts/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/cerbos/0.53.0/scripts/init.sh b/apps/cerbos/0.53.0/scripts/init.sh new file mode 100755 index 000000000..dd6e05be2 --- /dev/null +++ b/apps/cerbos/0.53.0/scripts/init.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}" + +fail() { + printf '%s\n' "$1" >&2 + exit 1 +} + +strip_matching_quotes() { + local value="$1" + + if [[ ${#value} -ge 2 ]]; then + if [[ "${value:0:1}" == '"' && "${value: -1}" == '"' ]]; then + value="${value:1:${#value}-2}" + elif [[ "${value:0:1}" == "'" && "${value: -1}" == "'" ]]; then + value="${value:1:${#value}-2}" + fi + fi + printf '%s\n' "$value" +} + +read_env_value() { + local key="$1" + local value="" + + if [[ -f "$ENV_FILE" ]]; then + value="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2- || true)" + fi + strip_matching_quotes "$value" +} + +path_is_dotenv_safe() { + local value="$1" + + case "$value" in + *$'\n'* | *$'\r'* | *\\* | *'$'* | *'#'* | *'"'* | *"'"*) return 1 ;; + *) return 0 ;; + esac +} + +[[ -f "$ENV_FILE" ]] || fail "$ENV_FILE not found" +[[ ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must not be a symbolic link" + +if [[ ${APP_DATA_DIR+x} ]]; then + APP_DATA_DIR_RAW="$APP_DATA_DIR" +else + APP_DATA_DIR_RAW="$(read_env_value APP_DATA_DIR)" +fi +APP_DATA_DIR_RAW="$(strip_matching_quotes "${APP_DATA_DIR_RAW:-./data}")" + +[[ -n "$APP_DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be empty" +path_is_dotenv_safe "$APP_DATA_DIR_RAW" || fail "APP_DATA_DIR contains unsupported dotenv characters" + +case "$APP_DATA_DIR_RAW" in + /*) + APP_DATA_DIR_ABS="$(realpath -m -- "$APP_DATA_DIR_RAW")" + ;; + *) + APP_DATA_DIR_ABS="$(realpath -m -- "${ROOT_DIR}/${APP_DATA_DIR_RAW#./}")" + case "$APP_DATA_DIR_ABS" in + "${ROOT_DIR}" | "${ROOT_DIR}"/*) ;; + *) fail "Relative APP_DATA_DIR must stay inside the application directory" ;; + esac + ;; +esac + +[[ "$APP_DATA_DIR_ABS" != "/" ]] || fail "APP_DATA_DIR must not be the filesystem root" +if [[ -e "$APP_DATA_DIR_ABS" && ! -d "$APP_DATA_DIR_ABS" ]]; then + fail "APP_DATA_DIR must be a directory" +fi + +POLICY_DIR="${APP_DATA_DIR_ABS}/policies" +[[ ! -L "$POLICY_DIR" ]] || fail "Cerbos policy directory must not be a symbolic link" +if [[ -e "$POLICY_DIR" && ! -d "$POLICY_DIR" ]]; then + fail "Cerbos policy path must be a directory" +fi +mkdir -p -- "$POLICY_DIR" diff --git a/apps/cerbos/0.53.0/scripts/uninstall.sh b/apps/cerbos/0.53.0/scripts/uninstall.sh new file mode 100755 index 000000000..5a3f6bd6b --- /dev/null +++ b/apps/cerbos/0.53.0/scripts/uninstall.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +if command -v docker-compose >/dev/null 2>&1; then + docker-compose down --volumes --remove-orphans +else + docker compose down --volumes --remove-orphans +fi diff --git a/apps/cerbos/0.53.0/scripts/upgrade.sh b/apps/cerbos/0.53.0/scripts/upgrade.sh new file mode 100755 index 000000000..593b31667 --- /dev/null +++ b/apps/cerbos/0.53.0/scripts/upgrade.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +exec bash "${SCRIPT_DIR}/init.sh" diff --git a/apps/cerbos/README.md b/apps/cerbos/README.md new file mode 100644 index 000000000..7fa069d5a --- /dev/null +++ b/apps/cerbos/README.md @@ -0,0 +1,54 @@ +# Cerbos + +## 产品介绍 + +Cerbos 是一个开源的策略决策点(PDP),用于把应用中的授权规则集中定义为 YAML 策略,并通过 HTTP 或 gRPC API 返回访问决策。 + +## 主要功能 + +- 使用资源策略、派生角色和主体策略表达 RBAC/ABAC 规则 +- 提供 HTTP 与 gRPC 授权决策 API +- 监听策略目录变更并自动重新加载磁盘策略 +- 提供内置 API 浏览器、健康检查与策略编译工具 + +## 访问说明 + +- HTTP API 和内置 API 浏览器默认使用 `3592` 端口。 +- gRPC API 默认使用 `3593` 端口。 +- Cerbos 不是用户登录系统,也不提供业务管理后台;业务应用需要通过 SDK、HTTP 或 gRPC 调用授权接口。 +- 授权接口不应直接暴露到不受信任的公网。建议通过防火墙、内网或反向代理限制访问范围。 + +## 策略与安全 + +- 本应用使用 Cerbos 官方镜像的默认磁盘存储配置,把安装表单选择目录下的 `policies` 子目录挂载到容器 `/policies`。 +- 初始策略目录为空时服务可以正常启动,但没有匹配策略的请求会被拒绝。请按官方格式为每个策略创建独立的 YAML 或 JSON 文件。 +- 匿名遥测通过 `CERBOS_NO_TELEMETRY=1` 默认关闭。 +- 默认配置未启用 Cerbos Admin API,也不联动 1Panel 数据库、Redis 或网站 Runtime。 +- 如需 Git、数据库、Cerbos Hub、审计日志或 Admin API 存储驱动,应在独立测试后自定义配置,不应直接复用本包的默认磁盘模式结论。 + +## 升级说明 + +- 升级前备份完整数据目录,尤其是 `policies` 子目录。 +- 固定版与 `latest` 使用相同的策略挂载路径,可通过 1Panel 执行跨版本升级。 +- 发布前应先使用 `cerbos compile` 或测试套件检查策略,并阅读目标版本的升级说明;策略语义变化需要人工审查。 + +## Introduction + +Cerbos is an open-source policy decision point for application authorization. This package runs the official image as one service, exposes the HTTP and gRPC APIs, and persists disk-backed policies under the selected data directory. + +## Features + +- YAML/JSON policies for RBAC and ABAC authorization decisions +- HTTP and gRPC decision APIs +- Automatic reload of disk-backed policy changes +- Official binary health check and telemetry disabled by default + +Back up the policy directory before upgrades. Keep the decision APIs on a trusted network, and validate policies against the target Cerbos release before production rollout. + +## 参考资料 + +- 容器安装: +- 快速开始: +- 配置参考: +- 源码仓库: +- 官方镜像: diff --git a/apps/cerbos/data.yml b/apps/cerbos/data.yml new file mode 100644 index 000000000..2312b171e --- /dev/null +++ b/apps/cerbos/data.yml @@ -0,0 +1,31 @@ +name: Cerbos +tags: + - 安全 +title: 面向应用的开源策略授权决策服务 +description: 面向应用的开源策略授权决策服务 +additionalProperties: + key: cerbos + name: Cerbos + tags: + - Security + shortDescZh: 面向应用的开源策略授权决策服务 + shortDescEn: An open-source policy decision service for application authorization + description: + en: An open-source policy decision service for application authorization + zh: 面向应用的开源策略授权决策服务 + zh-Hant: 面向應用程式的開源策略授權決策服務 + ja: アプリケーション認可向けのオープンソースポリシー決定サービス + ko: 애플리케이션 권한 부여를 위한 오픈 소스 정책 결정 서비스 + ru: Сервис принятия решений по политикам с открытым исходным кодом для авторизации приложений + ms: Perkhidmatan keputusan dasar sumber terbuka untuk kebenaran aplikasi + pt-br: Serviço de decisão de políticas de código aberto para autorização de aplicações + type: middleware + crossVersionUpdate: true + limit: 0 + recommend: 0 + website: https://cerbos.dev/ + github: https://github.com/cerbos/cerbos + document: https://docs.cerbos.dev/cerbos/latest/ + architectures: + - amd64 + - arm64 diff --git a/apps/cerbos/latest/.env.sample b/apps/cerbos/latest/.env.sample new file mode 100644 index 000000000..47c151c5a --- /dev/null +++ b/apps/cerbos/latest/.env.sample @@ -0,0 +1,4 @@ +PANEL_APP_PORT_HTTP=3592 +PANEL_APP_PORT_GRPC=3593 +APP_DATA_DIR=./data +CONTAINER_NAME=cerbos diff --git a/apps/cerbos/latest/data.yml b/apps/cerbos/latest/data.yml new file mode 100644 index 000000000..397682b31 --- /dev/null +++ b/apps/cerbos/latest/data.yml @@ -0,0 +1,52 @@ +additionalProperties: + formFields: + - default: 3592 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: HTTP API Port + labelZh: HTTP API 端口 + label: + en: HTTP API Port + zh: HTTP API 端口 + zh-Hant: HTTP API 連接埠 + ja: HTTP API ポート + ko: HTTP API 포트 + ru: Порт HTTP API + ms: Port API HTTP + pt-br: Porta da API HTTP + required: true + rule: paramPort + type: number + - default: 3593 + edit: true + envKey: PANEL_APP_PORT_GRPC + labelEn: gRPC API Port + labelZh: gRPC API 端口 + label: + en: gRPC API Port + zh: gRPC API 端口 + zh-Hant: gRPC API 連接埠 + ja: gRPC API ポート + ko: gRPC API 포트 + ru: Порт gRPC API + ms: Port API gRPC + pt-br: Porta da API gRPC + required: true + rule: paramPort + type: number + - default: ./data + edit: true + envKey: APP_DATA_DIR + labelEn: Data Directory + labelZh: 数据目录 + label: + en: Data Directory + zh: 数据目录 + zh-Hant: 資料目錄 + ja: データディレクトリ + ko: 데이터 디렉터리 + ru: Каталог данных + ms: Direktori Data + pt-br: Diretório de dados + required: true + type: text diff --git a/apps/cerbos/latest/data/.gitkeep b/apps/cerbos/latest/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/cerbos/latest/docker-compose.yml b/apps/cerbos/latest/docker-compose.yml new file mode 100644 index 000000000..1416cbd8a --- /dev/null +++ b/apps/cerbos/latest/docker-compose.yml @@ -0,0 +1,26 @@ +services: + cerbos: + image: "cerbos/cerbos:latest" + container_name: ${CONTAINER_NAME} + restart: unless-stopped + networks: + - 1panel-network + ports: + - "${PANEL_APP_PORT_HTTP}:3592" + - "${PANEL_APP_PORT_GRPC}:3593" + environment: + - CERBOS_NO_TELEMETRY=1 + volumes: + - "${APP_DATA_DIR}/policies:/policies" + healthcheck: + test: ["CMD", "/cerbos", "healthcheck"] + interval: 10s + timeout: 2s + start_period: 10s + retries: 5 + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/cerbos/latest/scripts/.gitkeep b/apps/cerbos/latest/scripts/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/cerbos/latest/scripts/init.sh b/apps/cerbos/latest/scripts/init.sh new file mode 100755 index 000000000..dd6e05be2 --- /dev/null +++ b/apps/cerbos/latest/scripts/init.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}" + +fail() { + printf '%s\n' "$1" >&2 + exit 1 +} + +strip_matching_quotes() { + local value="$1" + + if [[ ${#value} -ge 2 ]]; then + if [[ "${value:0:1}" == '"' && "${value: -1}" == '"' ]]; then + value="${value:1:${#value}-2}" + elif [[ "${value:0:1}" == "'" && "${value: -1}" == "'" ]]; then + value="${value:1:${#value}-2}" + fi + fi + printf '%s\n' "$value" +} + +read_env_value() { + local key="$1" + local value="" + + if [[ -f "$ENV_FILE" ]]; then + value="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2- || true)" + fi + strip_matching_quotes "$value" +} + +path_is_dotenv_safe() { + local value="$1" + + case "$value" in + *$'\n'* | *$'\r'* | *\\* | *'$'* | *'#'* | *'"'* | *"'"*) return 1 ;; + *) return 0 ;; + esac +} + +[[ -f "$ENV_FILE" ]] || fail "$ENV_FILE not found" +[[ ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must not be a symbolic link" + +if [[ ${APP_DATA_DIR+x} ]]; then + APP_DATA_DIR_RAW="$APP_DATA_DIR" +else + APP_DATA_DIR_RAW="$(read_env_value APP_DATA_DIR)" +fi +APP_DATA_DIR_RAW="$(strip_matching_quotes "${APP_DATA_DIR_RAW:-./data}")" + +[[ -n "$APP_DATA_DIR_RAW" ]] || fail "APP_DATA_DIR must not be empty" +path_is_dotenv_safe "$APP_DATA_DIR_RAW" || fail "APP_DATA_DIR contains unsupported dotenv characters" + +case "$APP_DATA_DIR_RAW" in + /*) + APP_DATA_DIR_ABS="$(realpath -m -- "$APP_DATA_DIR_RAW")" + ;; + *) + APP_DATA_DIR_ABS="$(realpath -m -- "${ROOT_DIR}/${APP_DATA_DIR_RAW#./}")" + case "$APP_DATA_DIR_ABS" in + "${ROOT_DIR}" | "${ROOT_DIR}"/*) ;; + *) fail "Relative APP_DATA_DIR must stay inside the application directory" ;; + esac + ;; +esac + +[[ "$APP_DATA_DIR_ABS" != "/" ]] || fail "APP_DATA_DIR must not be the filesystem root" +if [[ -e "$APP_DATA_DIR_ABS" && ! -d "$APP_DATA_DIR_ABS" ]]; then + fail "APP_DATA_DIR must be a directory" +fi + +POLICY_DIR="${APP_DATA_DIR_ABS}/policies" +[[ ! -L "$POLICY_DIR" ]] || fail "Cerbos policy directory must not be a symbolic link" +if [[ -e "$POLICY_DIR" && ! -d "$POLICY_DIR" ]]; then + fail "Cerbos policy path must be a directory" +fi +mkdir -p -- "$POLICY_DIR" diff --git a/apps/cerbos/latest/scripts/uninstall.sh b/apps/cerbos/latest/scripts/uninstall.sh new file mode 100755 index 000000000..5a3f6bd6b --- /dev/null +++ b/apps/cerbos/latest/scripts/uninstall.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +if command -v docker-compose >/dev/null 2>&1; then + docker-compose down --volumes --remove-orphans +else + docker compose down --volumes --remove-orphans +fi diff --git a/apps/cerbos/latest/scripts/upgrade.sh b/apps/cerbos/latest/scripts/upgrade.sh new file mode 100755 index 000000000..593b31667 --- /dev/null +++ b/apps/cerbos/latest/scripts/upgrade.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +exec bash "${SCRIPT_DIR}/init.sh" diff --git a/apps/cerbos/logo.png b/apps/cerbos/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6208d21ccbfbab58f81e3e5faf7e716e0072ecc5 GIT binary patch literal 4057 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 Y#A*s-?I1RM>`&*at7)iF19y!67w#vUQvd(} literal 0 HcmV?d00001