Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ are recorded, and no full sovereignty claim is made. See [LICENSE](LICENSE) and
admin-ui (Custom Client form)
↓ custom-build request
Go API (DeskForge)
↓ workflow_dispatch + authenticated DFP1 enc_payload (AES-256-CBC + PBKDF2 + HMAC)
↓ workflow_dispatch + provider-derived public workflow_sha + authenticated DFP1 enc_payload (AES-256-CBC + PBKDF2 + HMAC)
GitHub Actions [configured RustDesk fork, owned platform workflow]
↓ L1: config.rs (server + key)
↓ L2: custom_.txt (permanent password, allowCustom patch)
Expand All @@ -100,9 +100,13 @@ Go API validates/extracts/publishes locally → admin-ui Download
```

**Security:** password never published — `enc_payload`, decrypted inside runner via
GitHub Secret `WORKFLOW_PAYLOAD_KEY`. The runner does not callback to the API;
the API retrieves the artifact through the provider API and publishes it locally,
not to a public release.
GitHub Secret `WORKFLOW_PAYLOAD_KEY`. The provider-derived outer `workflow_sha` is
checked against `github.sha` before secret-bearing jobs; the same authenticated inner
payload field is checked again before exports, checkout, or build use. This is defense
in depth, not an atomic defense against a malicious workflow file; the verified tag
and active no-bypass ruleset remain required controls. The runner does not callback to
the API; the API retrieves the artifact through the provider API and publishes it
locally, not to a public release.

### Workflow approval and schema evidence

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ only; no APK/package/install/runtime evidence exists.
The local schema target is `DatabaseVersion 282`; the published DeskForge schema is
272, and SQLite-only checks do not establish cross-database verification.

### GitHub Actions PAT permissions

See the [fine-grained PAT permission checklist](api/README.md#github-fine-grained-pat-permissions)
for the custom-client workflow, including why Administration, Actions, and
Secrets write access are required.

**Not implemented (vs RustDesk Pro):** 2FA, RBAC, session recording, device policy, remote script,
HA, backup/restore.

Expand Down
4 changes: 4 additions & 0 deletions admin-ui/src/api/custom_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function create (data) {
url: '/custom_build/create',
method: 'post',
data,
useServerErrorMessage: true,
})
}

Expand All @@ -27,11 +28,14 @@ export function download (id) {
return request({
url: `/custom_build/download/${id}`,
responseType: 'blob',
useServerErrorMessage: true,
})
}

export function getVersions () {
return request({
url: '/custom_build/versions',
skipErrorMessage: true,
useServerErrorMessage: true,
})
}
22 changes: 15 additions & 7 deletions admin-ui/src/api/github_build_config.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,46 @@
import request from '@/utils/request'

export function get () {
return request({ url: '/github_build_config/get' })
return request({ url: '/github_build_config/get', skipErrorMessage: true, useServerErrorMessage: true })
}

export function save (data) {
return request({ url: '/github_build_config/save', method: 'post', data })
return request({ url: '/github_build_config/save', method: 'post', data, skipErrorMessage: true, useServerErrorMessage: true })
}

export function getWorkflowTags () {
return request({ url: '/github_build_config/workflow_tags' })
return request({ url: '/github_build_config/workflow_tags', skipErrorMessage: true, useServerErrorMessage: true })
}

export function approveWorkflowRef (workflowTag) {
return request({
url: '/github_build_config/approve_workflow_ref',
method: 'post',
data: { confirm: true, workflow_tag: workflowTag },
skipErrorMessage: true,
useServerErrorMessage: true,
})
}

export function generateKey () {
return request({ url: '/github_build_config/generate_key', method: 'post' })
return request({ url: '/github_build_config/generate_key', method: 'post', skipErrorMessage: true, useServerErrorMessage: true })
}

export function test () {
return request({ url: '/github_build_config/test', method: 'post' })
return request({ url: '/github_build_config/test', method: 'post', skipErrorMessage: true, useServerErrorMessage: true })
}

export function syncSecret () {
return request({ url: '/github_build_config/sync_secret', method: 'post' })
return request({ url: '/github_build_config/sync_secret', method: 'post', skipErrorMessage: true, useServerErrorMessage: true })
}

export function dispatchTest () {
// B-009: confirm=true — это реальный билд (тратит минуты Actions), не дешёвый чек.
return request({ url: '/github_build_config/dispatch_test', method: 'post', data: { confirm: true } })
return request({
url: '/github_build_config/dispatch_test',
method: 'post',
data: { confirm: true },
skipErrorMessage: true,
useServerErrorMessage: true,
})
}
85 changes: 83 additions & 2 deletions admin-ui/src/utils/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@
"Password": {
"One": "Password"
},
"ShowPassword": {
"One": "Show password"
},
"HidePassword": {
"One": "Hide password"
},
"GeneratedKeyLabel": {
"One": "Generated encryption key"
},
"GeneratedKeyWarning": {
"One": "Copy this key now; it will not be shown again."
},
"GeneratedKeyCreated": {
"One": "A new encryption key was generated."
},
"LoginSuccess": {
"One": "Login Success"
},
Expand All @@ -38,6 +53,33 @@
"ParamRequired": {
"One": "{param} is required"
},
"CustomClientPlatformRequired": {
"One": "Choose Windows as the supported build platform."
},
"CustomClientVersionRequired": {
"One": "Choose the client version to build."
},
"CustomClientAppNameRequired": {
"One": "Enter the artifact application name."
},
"CustomClientHostRequired": {
"One": "Provide the ID server endpoint (hostname/IP and optional port)."
},
"CustomClientKeyRequired": {
"One": "Provide the public key."
},
"CustomClientApiServerRequired": {
"One": "Provide the API server URL."
},
"CustomClientRelayServerRequired": {
"One": "Provide the relay endpoint (hostname/IP and optional port)."
},
"CustomClientPermanentPasswordRequired": {
"One": "Provide a permanent password when connection management is hidden."
},
"ClearSavedPassword": {
"One": "Clear saved password"
},
"HasBind": {
"One": "Has bind"
},
Expand Down Expand Up @@ -125,6 +167,42 @@
"WorkflowApprovalRequestFailed": {
"One": "Approval was not recorded. Try again after reviewing the provider status."
},
"GithubPatPermissionsIntro": {
"One": "Required fine-grained PAT repository permissions:"
},
"GithubPatPermissionMetadata": {
"One": "Metadata — Read"
},
"GithubPatPermissionContents": {
"One": "Contents — Read"
},
"GithubPatPermissionActions": {
"One": "Actions — Read and write"
},
"GithubPatPermissionAdministration": {
"One": "Administration — Read and write"
},
"GithubPatPermissionSecrets": {
"One": "Secrets — Read and write"
},
"GithubPatPermissionsNote": {
"One": "Actions write is required for dispatch; Administration write is required for ruleset bypass metadata; Secrets write is required for secret synchronization. Empty value keeps the existing token."
},
"GithubBuildSaveError": {
"One": "GitHub build settings could not be saved. Check the configuration and try again."
},
"GithubBuildSaveSaving": {
"One": "Saving GitHub build settings..."
},
"GithubBuildSaveSuccess": {
"One": "GitHub build settings saved."
},
"GithubBuildRepositoryRequired": {
"One": "Enter a repository in owner/name format before continuing."
},
"ViewBuildLog": {
"One": "View build log"
},
"Status": {
"One": "Status"
},
Expand Down Expand Up @@ -806,7 +884,7 @@
"One": "Host"
},
"HostEndpointHint": {
"One": "Optional. Accepts a hostname or IP address with an optional port. RustDesk uses port 21116 only when the port is omitted."
"One": "Provide the ID server endpoint: a hostname or IP address with an optional port. RustDesk uses port 21116 when omitted."
},
"HostEndpointPlaceholder": {
"One": "e.g. your-server.com or your-server.com:21116 (default when omitted)"
Expand All @@ -818,7 +896,7 @@
"One": "Relay Server"
},
"RelayEndpointHint": {
"One": "Optional. Accepts a hostname or IP address with an optional port. RustDesk uses port 21117 only when the port is omitted."
"One": "Provide the relay endpoint: a hostname or IP address with an optional port. RustDesk uses port 21117 when omitted."
},
"RelayEndpointPlaceholder": {
"One": "e.g. your-server.com or your-server.com:21117 (default when omitted)"
Expand Down Expand Up @@ -943,6 +1021,9 @@
"BuildStatus": {
"One": "Status"
},
"BuildHistoryStatusChanged": {
"One": "Build status updated: {param}."
},
"Pending": {
"One": "Pending"
},
Expand Down
85 changes: 83 additions & 2 deletions admin-ui/src/utils/i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@
"Password": {
"One": "Пароль"
},
"ShowPassword": {
"One": "Показать пароль"
},
"HidePassword": {
"One": "Скрыть пароль"
},
"GeneratedKeyLabel": {
"One": "Созданный ключ шифрования"
},
"GeneratedKeyWarning": {
"One": "Скопируйте этот ключ сейчас — повторно он показан не будет."
},
"GeneratedKeyCreated": {
"One": "Создан новый ключ шифрования."
},
"LoginSuccess": {
"One": "Успешный вход"
},
Expand All @@ -38,6 +53,33 @@
"ParamRequired": {
"One": "Поле {param} обязательно"
},
"CustomClientPlatformRequired": {
"One": "Выберите поддерживаемую платформу сборки (Windows)."
},
"CustomClientVersionRequired": {
"One": "Выберите версию клиента для сборки."
},
"CustomClientAppNameRequired": {
"One": "Введите имя приложения для артефакта."
},
"CustomClientHostRequired": {
"One": "Укажите адрес ID-сервера (имя хоста/IP и необязательный порт)."
},
"CustomClientKeyRequired": {
"One": "Укажите публичный ключ."
},
"CustomClientApiServerRequired": {
"One": "Укажите URL API-сервера."
},
"CustomClientRelayServerRequired": {
"One": "Укажите адрес relay-сервера (имя хоста/IP и необязательный порт)."
},
"CustomClientPermanentPasswordRequired": {
"One": "Если управление подключением скрыто, укажите постоянный пароль."
},
"ClearSavedPassword": {
"One": "Очистить сохранённый пароль"
},
"HasBind": {
"One": "Связано"
},
Expand Down Expand Up @@ -125,6 +167,45 @@
"WorkflowApprovalRequestFailed": {
"One": "Подтверждение не записано. Проверьте состояние провайдера и повторите попытку."
},
"GithubPatPermissionsIntro": {
"One": "Необходимые разрешения репозитория для fine-grained PAT:"
},
"GithubPatPermissionMetadata": {
"One": "Metadata — Read"
},
"GithubPatPermissionContents": {
"One": "Contents — Read"
},
"GithubPatPermissionActions": {
"One": "Actions — Read and write"
},
"GithubPatPermissionAdministration": {
"One": "Administration — Read and write"
},
"GithubPatPermissionSecrets": {
"One": "Secrets — Read and write"
},
"GithubPatPermissionsNote": {
"One": "Для dispatch требуется Actions write; для метаданных обхода защиты workflow требуется Administration write; для синхронизации секретов требуется Secrets write. Пустое значение сохраняет текущий токен."
},
"GithubBuildSaveError": {
"One": "Не удалось сохранить настройки GitHub Build. Проверьте конфигурацию и повторите попытку."
},
"GithubBuildSaveSaving": {
"One": "Сохранение настроек GitHub Build..."
},
"GithubBuildSaveSuccess": {
"One": "Настройки GitHub Build сохранены."
},
"GithubBuildRepositoryRequired": {
"One": "Перед продолжением укажите репозиторий в формате owner/name."
},
"ViewBuildLog": {
"One": "Просмотреть журнал сборки"
},
"BuildHistoryStatusChanged": {
"One": "Статус сборки обновлён: {param}."
},
"Status": {
"One": "Статус"
},
Expand Down Expand Up @@ -683,13 +764,13 @@
"One": "Загрузить"
},
"HostEndpointHint": {
"One": "Необязательное поле. Принимает имя хоста или IP-адрес с необязательным портом. RustDesk использует порт 21116 только если порт не указан."
"One": "Укажите адрес ID-сервера: имя хоста или IP-адрес с необязательным портом. RustDesk использует порт 21116, если порт не указан."
},
"HostEndpointPlaceholder": {
"One": "например, your-server.com или your-server.com:21116 (по умолчанию, если порт не указан)"
},
"RelayEndpointHint": {
"One": "Необязательное поле. Принимает имя хоста или IP-адрес с необязательным портом. RustDesk использует порт 21117 только если порт не указан."
"One": "Укажите адрес relay-сервера: имя хоста или IP-адрес с необязательным портом. RustDesk использует порт 21117, если порт не указан."
},
"RelayEndpointPlaceholder": {
"One": "например, your-server.com или your-server.com:21117 (по умолчанию, если порт не указан)"
Expand Down
Loading