mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
feat: move vault ssh signing into a plugin (C5)
This commit is contained in:
1 parent
b027717c46
commit
6e56c90aac
198 files changed
+19717
-3445
No files matched your search
@@ -267,7 +267,7 @@ http {
|
||||
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||
}
|
||||
|
||||
location ~ ^/vault(/.*)?$ {
|
||||
location = /vault/oidc/callback {
|
||||
proxy_pass http://127.0.0.1:30001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
+1
-1
@@ -247,7 +247,7 @@ http {
|
||||
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||
}
|
||||
|
||||
location ~ ^/vault(/.*)?$ {
|
||||
location = /vault/oidc/callback {
|
||||
proxy_pass http://127.0.0.1:30001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-- vault_profiles and vault_tokens move to the vault plugin's adopted tables
|
||||
-- at activation, and ssh_data.vault_profile_id is copied into the vault
|
||||
-- plugin's host settings by a boot migration after this runs. Dropping any
|
||||
-- of them here would lose the data first.
|
||||
SELECT 1;
|
||||
File diff suppressed because it is too large.
Load diff
@@ -379,6 +379,13 @@
|
||||
"when": 1790295071302,
|
||||
"tag": "0053_magical_groot",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 54,
|
||||
"version": "5",
|
||||
"when": 1790298804983,
|
||||
"tag": "0054_wakeful_misty_knight",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
-- vault_profiles and vault_tokens move to the vault plugin's adopted tables
|
||||
-- at activation, and ssh_data.vault_profile_id is copied into the vault
|
||||
-- plugin's host settings by a boot migration after this runs. Dropping any
|
||||
-- of them here would lose the data first.
|
||||
SELECT 1;
|
||||
File diff suppressed because it is too large.
Load diff
@@ -386,6 +386,13 @@
|
||||
"when": 1790295070561,
|
||||
"tag": "0054_cloudy_galactus",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 55,
|
||||
"version": "7",
|
||||
"when": 1790298804216,
|
||||
"tag": "0055_jazzy_quasimodo",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
-- vault_profiles and vault_tokens move to the vault plugin's adopted tables
|
||||
-- at activation, and ssh_data.vault_profile_id is copied into the vault
|
||||
-- plugin's host settings by a boot migration after this runs. Dropping any
|
||||
-- of them here would lose the data first.
|
||||
SELECT 1;
|
||||
File diff suppressed because it is too large.
Load diff
@@ -358,6 +358,13 @@
|
||||
"when": 1790295069807,
|
||||
"tag": "0050_clear_white_queen",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 51,
|
||||
"version": "6",
|
||||
"when": 1790298803438,
|
||||
"tag": "0051_conscious_blur",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
Generated
+11
@@ -7060,6 +7060,10 @@
|
||||
"resolved": "plugins/tunnels",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@termix-plugin/vault": {
|
||||
"resolved": "plugins/vault",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@termix-plugin/wake-on-lan": {
|
||||
"resolved": "plugins/wake-on-lan",
|
||||
"link": true
|
||||
@@ -18772,6 +18776,13 @@
|
||||
"@termix/plugin-sdk": "*"
|
||||
}
|
||||
},
|
||||
"plugins/vault": {
|
||||
"name": "@termix-plugin/vault",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@termix/plugin-sdk": "*"
|
||||
}
|
||||
},
|
||||
"plugins/wake-on-lan": {
|
||||
"name": "@termix-plugin/wake-on-lan",
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -185,7 +185,7 @@ artifacts in lockstep: the `schema.ts` declaration, the `index.ts` DDL, and the
|
||||
regenerated `schema.pg.ts`/`schema.mysql.ts` plus drizzle migrations.
|
||||
|
||||
**Sync entities are registered, not hardcoded.** `ctx.sync.registerEntity`
|
||||
adds an entity to remote sync; core registers its own ten the same way in
|
||||
adds an entity to remote sync; core registers its own the same way in
|
||||
`database/routes/sync-entities.ts`. The wire names are unchanged, because
|
||||
existing tombstones and rows on the far side match on those strings. The
|
||||
Electron client asks the server for the ordered list and falls back to its
|
||||
@@ -1008,12 +1008,10 @@ auto-session and trusted devices, plus the base SSH auth types password, key,
|
||||
stored credential, agent and none. Every other login method, second factor and
|
||||
SSH auth method is a plugin through `ctx.auth`.
|
||||
|
||||
In 2.9.0, Vault still lives in core. It registers through the same
|
||||
interface from `src/backend/auth/legacy-providers.ts` with
|
||||
`pluginId: "core"`. Its Phase C step moves its block out of that file; D1
|
||||
deletes it. **C4** moved Step-CA out and deleted
|
||||
`src/ui/auth/legacy-auth-ui.tsx`, which had no other editor left.
|
||||
Nothing else in core branches on those type names.
|
||||
**C5** moved Vault, the last SSH auth method core still registered, into
|
||||
its plugin and deleted `src/backend/auth/legacy-providers.ts`. **C4** had
|
||||
already deleted `src/ui/auth/legacy-auth-ui.tsx`. Core registers only the
|
||||
base types and branches on no plugin type name.
|
||||
|
||||
**C1** moved TOTP and passkeys out:
|
||||
|
||||
@@ -1137,7 +1135,7 @@ Nothing else in core branches on those type names.
|
||||
`requires_browser_sign_in` and continue on `connect-browser-sign-in`.
|
||||
- `@termix/plugin-sdk/ssh-certs` (`applyCertificateAuth`) replaces core's
|
||||
`opkssh-cert-auth.ts`: opkssh, step-ca, core key auth with a CA
|
||||
certificate, and the Vault provider still in core use it.
|
||||
certificate, and vault use it.
|
||||
|
||||
**C4** moved Step-CA out:
|
||||
|
||||
@@ -1159,6 +1157,39 @@ Nothing else in core branches on those type names.
|
||||
- The sign-in dialog is a `terminal.overlay` contribution speaking
|
||||
`stepca_*`.
|
||||
|
||||
**C5** moved HashiCorp Vault out:
|
||||
|
||||
- `vault` registers the `vault` SSH auth provider. It adopts
|
||||
`vault_profiles` and `vault_tokens` as `p_vault_profiles` and
|
||||
`p_vault_tokens`. Profiles keep their ids and sync ids; cached
|
||||
certificates are sealed with `ctx.secrets.seal`, and the ones 2.8 wrote
|
||||
with the user's data key do not unseal, so they are dropped and cost one
|
||||
sign-in. A fresh install has no foreign key from a token to its profile
|
||||
(the SDK has no builder for one to a plugin's own table), so deleting a
|
||||
profile deletes its tokens in code.
|
||||
- A host's profile is the `profileId` host setting (hidden, edited in the
|
||||
plugin's `registerSshAuthEditor` editor, which also holds the profile
|
||||
manager where the host editor always had it).
|
||||
`vault-settings-migration.ts` copies `ssh_data.vault_profile_id` into it;
|
||||
the column is out of `schema.ts` and its drizzle drop is a no-op.
|
||||
- Vault is reached through `ctx.fetch`, with the profile's own Vault host
|
||||
passed as `allowPrivateHosts`, since Vault servers are usually internal.
|
||||
- Profile routes are `/plugin-api/vault/profiles`, gated on `vault.use`;
|
||||
sharing a profile needs `vault.share` (admins by default), which replaces
|
||||
2.8's admin check. Only the owner edits or deletes one.
|
||||
- The sync entity keeps its wire name `vaultProfiles` and order 20. Hosts no
|
||||
longer reference it, so core's host entity lost its `vaultProfileId`
|
||||
reference and `sync-references.ts` has no Vault case.
|
||||
- The callback is `/plugin-api/vault/oidc/callback` (public). The boot
|
||||
migration decides `legacyCallback` once: on when the 2.8 database used
|
||||
Vault, so Termix keeps sending `<base>/vault/oidc/callback`, the URI Vault
|
||||
roles allow, which core answers with a 307 to the plugin
|
||||
(`registerVaultCompatRoutes` in `host-compat-routes.ts`; nginx proxies
|
||||
only that path now).
|
||||
- The sign-in dialog is a `terminal.overlay` contribution. It keeps the 2.8
|
||||
message names (`vault_auth_url`, `vault_completed`, `vault_error`) for
|
||||
Termix-Mobile.
|
||||
|
||||
#### One SSH connect pipeline
|
||||
|
||||
Every SSH connection, core's and plugins', goes through
|
||||
@@ -2298,8 +2329,7 @@ upgrade on all three engines, and the checks in step 15 are green.
|
||||
(`ctx.disposables.add`). Nothing at module scope. `deactivate` is usually
|
||||
empty.
|
||||
4. **When the SDK does not have it, add it to the SDK.** Never work around a
|
||||
gap with a core import, and never add to
|
||||
`src/backend/auth/legacy-providers.ts`. A new ctx member is:
|
||||
gap with a core import. A new ctx member is:
|
||||
1. the type and a doc comment in `packages/plugin-sdk/src/backend.ts`;
|
||||
2. the implementation in `src/backend/plugins/ctx.ts`, wrapped in `guarded()`
|
||||
when it is privileged, so it checks the capability and writes an audit
|
||||
@@ -2540,7 +2570,7 @@ Then, with the app running:
|
||||
The bundled plugins predate the SDK, apart from workspaces (A9), snippets
|
||||
(B2), remote-desktop (B14), docker (B15), host-metrics (B16), automations
|
||||
(B17), ai (B18), homepage (B19), totp and webauthn (C1), sso and ldap (C2),
|
||||
opkssh and warpgate (C3), step-ca (C4),
|
||||
opkssh and warpgate (C3), step-ca (C4), vault (C5),
|
||||
which import nothing from core. The others still reach core by relative
|
||||
path (`../../../../src/backend/...`), which an esbuild plugin,
|
||||
`packages/plugin-sdk/cli/lib/legacy-core-imports.mjs`, keeps out of the bundle
|
||||
|
||||
@@ -529,6 +529,31 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite
|
||||
rejects (it then downloads the pinned version instead). Pass
|
||||
`OPKSSH_VERSION` and the matching `OPKSSH_SHA256` as `ENV` in the final
|
||||
stage. Owner: D0.
|
||||
- **C5 (vault):** the Termix docs still describe Vault profiles with the
|
||||
`/vault/oidc/callback` redirect URI. They need the plugin settings page,
|
||||
the new redirect URI (`/plugin-api/vault/oidc/callback`) and the
|
||||
`legacyCallback` setting. Owner: D0 (docs repo).
|
||||
- **C5 (vault):** a host's Vault profile is the plugin's `profileId` host
|
||||
setting now, and host-scope plugin settings do not travel over remote sync
|
||||
(the host entity used to carry `vaultProfileSyncId`). Profiles still sync
|
||||
as `vaultProfiles`; the host on the other side has to pick its profile
|
||||
again. Needs a generic way to sync host-scope plugin settings, the same gap
|
||||
the B7 export/import line describes. Owner: D1.
|
||||
- **C5 (vault), for D2:** `vaultProfileId` was an owner-private auth field a
|
||||
shared-host recipient with edit access could not change. As a host setting
|
||||
it is written through `/plugins/vault/settings/host/:hostId`, which only
|
||||
checks edit access, so such a recipient can now point the host at another
|
||||
profile. Same question as the B14 line on per-field sharing levels.
|
||||
Owner: D2.
|
||||
- **C5 (vault):** `ssh_data.vault_profile_id` stays in the database,
|
||||
unused, after `vault-settings-migration.ts` copies it (kept so a downgrade
|
||||
works, and the pg/mysql foreign key on it now points at
|
||||
`p_vault_profiles`). Drop it in 3.0.0. Owner: 3.0.0.
|
||||
- **Pre-existing, found in C5:** `npm run test -w plugins/ssh-terminal`
|
||||
fails "ssh-terminal activate > serves the terminal socket": it expects
|
||||
`mock.wsRoutes` to equal `[{ path, raw }]`, but `FakeWsRoute` now also
|
||||
records `handler` and `options`. Use `toMatchObject` or
|
||||
`expect.objectContaining`. Owner: D0.
|
||||
|
||||
## Manual checks after 2.9.0
|
||||
|
||||
@@ -624,3 +649,11 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite
|
||||
and the host says it needs it. A Warpgate host's toggle is on in the host
|
||||
editor's Plugins group after the upgrade, and the terminal, file manager
|
||||
and docker each show the sign-in dialog and connect after it.
|
||||
- Vault: on a 2.8 database every Vault profile is there and each Vault host
|
||||
still has its profile selected in the host editor; the admin page shows the
|
||||
old redirect URI; a terminal connect opens Vault's sign-in, signs a
|
||||
certificate and connects, and a second connect skips the browser until the
|
||||
certificate expires; create, edit, share and delete a profile from the host
|
||||
editor (sharing only offered with "Share Vault profiles"); switch to the new
|
||||
redirect URI after allowing it in the Vault role; disable the vault plugin
|
||||
and a Vault host says it needs it, then enable it again.
|
||||
@@ -584,7 +584,6 @@ export async function activate(ctx: PluginContext) {
|
||||
let resolvedSocks5Username = socks5Username;
|
||||
let resolvedSocks5Password = socks5Password;
|
||||
let resolvedSocks5ProxyChain = socks5ProxyChain;
|
||||
let resolvedVaultProfileId: number | null = null;
|
||||
|
||||
const resolveFrom = async () => {
|
||||
const resolvedHost = hostSyncId
|
||||
@@ -607,8 +606,6 @@ export async function activate(ctx: PluginContext) {
|
||||
resolvedTerminalConfig = resolvedHost.terminalConfig as unknown as
|
||||
Record<string, unknown> | undefined;
|
||||
resolvedScpLegacy = resolvedHost.scpLegacy ?? false;
|
||||
resolvedVaultProfileId =
|
||||
(resolvedHost.vaultProfile as { id?: number } | undefined)?.id ?? null;
|
||||
if (resolvedHost.useSocks5) {
|
||||
resolvedUseSocks5 = resolvedHost.useSocks5;
|
||||
resolvedSocks5Host = resolvedHost.socks5Host;
|
||||
@@ -717,9 +714,6 @@ export async function activate(ctx: PluginContext) {
|
||||
keyPassword: resolvedCredentials.keyPassword,
|
||||
certPublicKey: resolvedCredentials.certPublicKey,
|
||||
terminalConfig: resolvedTerminalConfig ?? null,
|
||||
vaultProfile: resolvedVaultProfileId
|
||||
? { id: resolvedVaultProfileId }
|
||||
: null,
|
||||
jumpHosts: resolvedJumpHosts,
|
||||
useSocks5: resolvedUseSocks5,
|
||||
socks5Host: resolvedSocks5Host,
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} window",
|
||||
"tmuxWindows": "Windows",
|
||||
"unknownError": "Unknown error occurred",
|
||||
"vaultAuthDescription": "A window has opened to sign in to HashiCorp Vault. Complete the sign-in there; this connection will continue automatically.",
|
||||
"vaultAuthFailed": "Vault authentication failed. Please try again.",
|
||||
"vaultAuthTitle": "Vault sign-in required",
|
||||
"vaultReopen": "Reopen sign-in window",
|
||||
"websocketAbnormalClose": "Connection closed unexpectedly. This may be due to a reverse proxy or SSL configuration issue. Please check server logs.",
|
||||
"websocketError": "WebSocket connection error"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} venster",
|
||||
"tmuxWindows": "Vensters",
|
||||
"unknownError": "Onbekende fout het voorgekom",
|
||||
"vaultAuthDescription": "'n Venster het oopgemaak om by HashiCorp Vault aan te meld. Voltooi die aanmelding daar; hierdie verbinding sal outomaties voortgaan.",
|
||||
"vaultAuthFailed": "Kluisverifikasie het misluk. Probeer asseblief weer.",
|
||||
"vaultAuthTitle": "Kluisaanmelding vereis",
|
||||
"vaultReopen": "Maak aanmeldvenster weer oop",
|
||||
"websocketAbnormalClose": "Verbinding het onverwags gesluit. Dit kan wees as gevolg van 'n omgekeerde proxy- of SSL-konfigurasieprobleem. Gaan asseblief bedienerlogboeke na.",
|
||||
"websocketError": "WebSocket-verbindingsfout"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} نافذة",
|
||||
"tmuxWindows": "النوافذ",
|
||||
"unknownError": "حدث خطأ غير معروف",
|
||||
"vaultAuthDescription": "تم فتح نافذة لتسجيل الدخول إلى HashiCorp Vault. أكمل تسجيل الدخول هناك؛ سيستمر هذا الاتصال تلقائيًا.",
|
||||
"vaultAuthFailed": "فشلت مصادقة Vault. يرجى المحاولة مرة أخرى.",
|
||||
"vaultAuthTitle": "مطلوب تسجيل الدخول إلى Vault",
|
||||
"vaultReopen": "أعد فتح نافذة تسجيل الدخول",
|
||||
"websocketAbnormalClose": "أُغلق الاتصال بشكل غير متوقع. قد يكون السبب مشكلة في الوكيل العكسي أو إعدادات SSL. يرجى مراجعة سجلات الخادم.",
|
||||
"websocketError": "خطأ في اتصال WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} прозорец",
|
||||
"tmuxWindows": "Прозорци",
|
||||
"unknownError": "Възникна неизвестна грешка",
|
||||
"vaultAuthDescription": "Отвори се прозорец за влизане в HashiCorp Vault. Завършете влизането там; тази връзка ще продължи автоматично.",
|
||||
"vaultAuthFailed": "Удостоверяването на трезора не бе успешно. Моля, опитайте отново.",
|
||||
"vaultAuthTitle": "Изисква се влизане в трезора",
|
||||
"vaultReopen": "Отваряне отново на прозореца за влизане",
|
||||
"websocketAbnormalClose": "Връзката се прекъсна неочаквано. Това може да се дължи на проблем с обратен прокси или SSL конфигурация. Моля, проверете лог файловете на сървъра.",
|
||||
"websocketError": "Грешка при свързване с WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} জানালা",
|
||||
"tmuxWindows": "উইন্ডোজ",
|
||||
"unknownError": "অজানা ত্রুটি ঘটেছে",
|
||||
"vaultAuthDescription": "হ্যাশিকর্প ভল্টে সাইন ইন করার জন্য একটি উইন্ডো খুলেছে। সেখানে সাইন-ইন সম্পন্ন করুন; এই সংযোগটি স্বয়ংক্রিয়ভাবে চলতে থাকবে।",
|
||||
"vaultAuthFailed": "ভল্ট প্রমাণীকরণ ব্যর্থ হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন।",
|
||||
"vaultAuthTitle": "ভল্টে সাইন-ইন করা আবশ্যক",
|
||||
"vaultReopen": "সাইন-ইন উইন্ডো পুনরায় খুলুন",
|
||||
"websocketAbnormalClose": "সংযোগটি অপ্রত্যাশিতভাবে বন্ধ হয়ে গেছে। এটি রিভার্স প্রক্সি বা SSL কনফিগারেশন সংক্রান্ত সমস্যার কারণে হতে পারে। অনুগ্রহ করে সার্ভার লগ পরীক্ষা করুন।",
|
||||
"websocketError": "ওয়েবসকেট সংযোগ ত্রুটি"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "finestra {{count}}",
|
||||
"tmuxWindows": "Finestres",
|
||||
"unknownError": "S'ha produït un error desconegut",
|
||||
"vaultAuthDescription": "S'ha obert una finestra per iniciar la sessió a HashiCorp Vault. Completeu l'inici de sessió allà; aquesta connexió continuarà automàticament.",
|
||||
"vaultAuthFailed": "L'autenticació de la caixa forta ha fallat. Torna-ho a provar.",
|
||||
"vaultAuthTitle": "Cal iniciar sessió a la caixa forta",
|
||||
"vaultReopen": "Torna a obrir la finestra d'inici de sessió",
|
||||
"websocketAbnormalClose": "La connexió s'ha tancat inesperadament. Això pot ser degut a un problema de configuració del proxy invers o de l'SSL. Si us plau, reviseu els registres del servidor.",
|
||||
"websocketError": "Error de connexió de WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} okno",
|
||||
"tmuxWindows": "Okna",
|
||||
"unknownError": "Došlo k neznámé chybě",
|
||||
"vaultAuthDescription": "Otevřelo se okno pro přihlášení do HashiCorp Vault. Dokončete přihlášení tam; toto připojení bude automaticky pokračovat.",
|
||||
"vaultAuthFailed": "Ověření v trezoru se nezdařilo. Zkuste to prosím znovu.",
|
||||
"vaultAuthTitle": "Vyžaduje se přihlášení do trezoru",
|
||||
"vaultReopen": "Znovu otevřít přihlašovací okno",
|
||||
"websocketAbnormalClose": "Spojení se neočekávaně ukončilo. Může to být způsobeno problémem s reverzní proxy nebo konfigurací SSL. Zkontrolujte prosím protokoly serveru.",
|
||||
"websocketError": "Chyba připojení k WebSocketu"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} vindue",
|
||||
"tmuxWindows": "Vinduer",
|
||||
"unknownError": "Der opstod en ukendt fejl",
|
||||
"vaultAuthDescription": "Et vindue er åbnet, hvor du kan logge ind på HashiCorp Vault. Fuldfør loginprocessen der; forbindelsen fortsætter automatisk.",
|
||||
"vaultAuthFailed": "Godkendelse af arkiv mislykkedes. Prøv igen.",
|
||||
"vaultAuthTitle": "Login til Vault kræves",
|
||||
"vaultReopen": "Genåbn loginvinduet",
|
||||
"websocketAbnormalClose": "Forbindelsen blev lukket uventet. Dette kan skyldes et problem med reverse proxy eller SSL-konfigurationen. Tjek venligst serverlogfilerne.",
|
||||
"websocketError": "WebSocket-forbindelsesfejl"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} Fenster",
|
||||
"tmuxWindows": "Fenster",
|
||||
"unknownError": "Unbekannter Fehler aufgetreten",
|
||||
"vaultAuthDescription": "Ein Fenster zur Anmeldung bei HashiCorp Vault wurde geöffnet. Schließen Sie die Anmeldung dort ab; diese Verbindung wird automatisch fortgesetzt.",
|
||||
"vaultAuthFailed": "Vault-Authentifizierung fehlgeschlagen. Bitte versuchen Sie es erneut.",
|
||||
"vaultAuthTitle": "Vault-Anmeldung erforderlich",
|
||||
"vaultReopen": "Anmeldefenster erneut öffnen",
|
||||
"websocketAbnormalClose": "Verbindung unerwartet geschlossen. Dies kann auf ein Problem mit dem Reverse-Proxy oder der SSL-Konfiguration zurückzuführen sein. Bitte überprüfen Sie die Server-Logs.",
|
||||
"websocketError": "WebSocket-Verbindungsfehler"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "παράθυρο {{count}}",
|
||||
"tmuxWindows": "Παράθυρα",
|
||||
"unknownError": "Παρουσιάστηκε άγνωστο σφάλμα",
|
||||
"vaultAuthDescription": "Έχει ανοίξει ένα παράθυρο για να συνδεθείτε στο HashiCorp Vault. Ολοκληρώστε τη σύνδεση εκεί. Αυτή η σύνδεση θα συνεχιστεί αυτόματα.",
|
||||
"vaultAuthFailed": "Ο έλεγχος ταυτότητας του vault απέτυχε. Δοκιμάστε ξανά.",
|
||||
"vaultAuthTitle": "Απαιτείται σύνδεση στο Vault",
|
||||
"vaultReopen": "Επανάνοιγμα παραθύρου σύνδεσης",
|
||||
"websocketAbnormalClose": "Η σύνδεση έκλεισε απροσδόκητα. Αυτό μπορεί να οφείλεται σε πρόβλημα αντίστροφης μεσολάβησης ή ρύθμισης παραμέτρων SSL. Ελέγξτε τα αρχεία καταγραφής διακομιστή.",
|
||||
"websocketError": "Σφάλμα σύνδεσης WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} ventana",
|
||||
"tmuxWindows": "Ventanas",
|
||||
"unknownError": "Se ha producido un error desconocido.",
|
||||
"vaultAuthDescription": "Se ha abierto una ventana para iniciar sesión en HashiCorp Vault. Completa el inicio de sesión allí; esta conexión continuará automáticamente.",
|
||||
"vaultAuthFailed": "La autenticación de Vault falló. Inténtalo de nuevo.",
|
||||
"vaultAuthTitle": "Inicio de sesión en Vault requerido",
|
||||
"vaultReopen": "Reabrir ventana de inicio de sesión",
|
||||
"websocketAbnormalClose": "La conexión se cerró de forma inesperada. Esto puede deberse a un problema con el proxy inverso o la configuración SSL. Revisa los registros del servidor.",
|
||||
"websocketError": "Error de conexión WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} ikkuna",
|
||||
"tmuxWindows": "Ikkunat",
|
||||
"unknownError": "Tuntematon virhe tapahtui",
|
||||
"vaultAuthDescription": "Ikkuna HashiCorp-holviin kirjautumista varten on avautunut. Kirjaudu sisään siinä. Yhteys jatkuu automaattisesti.",
|
||||
"vaultAuthFailed": "Holvin todennus epäonnistui. Yritä uudelleen.",
|
||||
"vaultAuthTitle": "Holviin kirjautuminen vaaditaan",
|
||||
"vaultReopen": "Avaa kirjautumisikkuna uudelleen",
|
||||
"websocketAbnormalClose": "Yhteys katkesi odottamatta. Tämä voi johtua käänteisestä välityspalvelimesta tai SSL-määritysongelmasta. Tarkista palvelimen lokit.",
|
||||
"websocketError": "WebSocket-yhteysvirhe"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} fenêtre(s)",
|
||||
"tmuxWindows": "Fenêtres",
|
||||
"unknownError": "Une erreur inconnue s'est produite",
|
||||
"vaultAuthDescription": "Une fenêtre s'est ouverte pour vous connecter à HashiCorp Vault. Terminez la connexion dans cette fenêtre ; cette connexion continuera automatiquement.",
|
||||
"vaultAuthFailed": "Échec de l'authentification Vault. Veuillez réessayer.",
|
||||
"vaultAuthTitle": "Connexion à Vault requise",
|
||||
"vaultReopen": "Rouvrir la fenêtre de connexion",
|
||||
"websocketAbnormalClose": "Connexion fermée de manière inattendue. Cela peut être dû à un problème de proxy inverse ou de configuration SSL. Veuillez vérifier les journaux du serveur.",
|
||||
"websocketError": "Erreur de connexion WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "חלון {{count}}",
|
||||
"tmuxWindows": "חלונות",
|
||||
"unknownError": "אירעה שגיאה לא ידועה",
|
||||
"vaultAuthDescription": "נפתח חלון לכניסה לכספת HashiCorp. השלם את תהליך הכניסה שם; חיבור זה ימשיך אוטומטית.",
|
||||
"vaultAuthFailed": "אימות הכספת נכשל. אנא נסה שוב.",
|
||||
"vaultAuthTitle": "נדרשת כניסה לכספת",
|
||||
"vaultReopen": "פתח מחדש את חלון הכניסה",
|
||||
"websocketAbnormalClose": "החיבור נסגר באופן בלתי צפוי. ייתכן שהדבר נובע מבעיית פרוקסי הפוך או מבעיית תצורת SSL. אנא בדוק את יומני השרת.",
|
||||
"websocketError": "שגיאת חיבור WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} विंडो",
|
||||
"tmuxWindows": "विंडो",
|
||||
"unknownError": "अज्ञात त्रुटि हुई",
|
||||
"vaultAuthDescription": "HashiCorp Vault में साइन इन करने के लिए एक विंडो खुल गई है। वहाँ साइन-इन पूरा करें; यह कनेक्शन स्वचालित रूप से जारी रहेगा।",
|
||||
"vaultAuthFailed": "Vault प्रमाणीकरण विफल। कृपया पुनः प्रयास करें।",
|
||||
"vaultAuthTitle": "Vault साइन-इन आवश्यक है",
|
||||
"vaultReopen": "साइन-इन विंडो फिर से खोलें",
|
||||
"websocketAbnormalClose": "कनेक्शन अप्रत्याशित रूप से बंद हो गया। यह रिवर्स प्रॉक्सी या SSL कॉन्फ़िगरेशन समस्या के कारण हो सकता है। कृपया सर्वर लॉग जाँचें।",
|
||||
"websocketError": "WebSocket कनेक्शन त्रुटि"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} ablak",
|
||||
"tmuxWindows": "Ablakok",
|
||||
"unknownError": "Ismeretlen hiba történt",
|
||||
"vaultAuthDescription": "Megnyílt egy ablak a HashiCorp Vaultba való bejelentkezéshez. Fejezze be a bejelentkezést ott; a kapcsolat automatikusan folytatódik.",
|
||||
"vaultAuthFailed": "A trezor hitelesítése sikertelen. Próbálja újra.",
|
||||
"vaultAuthTitle": "Bejelentkezés szükséges a széfbe",
|
||||
"vaultReopen": "Bejelentkezési ablak újranyitása",
|
||||
"websocketAbnormalClose": "A kapcsolat váratlanul megszakadt. Ennek oka lehet fordított proxy vagy SSL konfigurációs probléma. Kérjük, ellenőrizze a szervernaplókat.",
|
||||
"websocketError": "WebSocket csatlakozási hiba"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} jendela",
|
||||
"tmuxWindows": "Windows",
|
||||
"unknownError": "Terjadi kesalahan yang tidak diketahui.",
|
||||
"vaultAuthDescription": "Jendela telah terbuka untuk masuk ke HashiCorp Vault. Selesaikan proses masuk di sana; koneksi ini akan berlanjut secara otomatis.",
|
||||
"vaultAuthFailed": "Autentikasi Vault gagal. Silakan coba lagi.",
|
||||
"vaultAuthTitle": "Login ke Vault diperlukan.",
|
||||
"vaultReopen": "Buka kembali jendela masuk",
|
||||
"websocketAbnormalClose": "Koneksi terputus secara tidak terduga. Ini mungkin disebabkan oleh masalah konfigurasi reverse proxy atau SSL. Silakan periksa log server.",
|
||||
"websocketError": "Kesalahan koneksi WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} finestra",
|
||||
"tmuxWindows": "Finestre",
|
||||
"unknownError": "Si è verificato un errore sconosciuto",
|
||||
"vaultAuthDescription": "Una finestra si è aperta per accedere a HashiCorp Vault. Completa l'accesso lì; la connessione continuerà automaticamente.",
|
||||
"vaultAuthFailed": "Autenticazione Vault fallita. Riprova.",
|
||||
"vaultAuthTitle": "Accesso a Vault richiesto",
|
||||
"vaultReopen": "Riapri la finestra di accesso",
|
||||
"websocketAbnormalClose": "La connessione si è chiusa in modo imprevisto. Potrebbe trattarsi di un problema di reverse proxy o configurazione SSL. Controlla i log del server.",
|
||||
"websocketError": "Errore di connessione WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} ウィンドウ",
|
||||
"tmuxWindows": "ウィンドウ",
|
||||
"unknownError": "不明なエラーが発生しました",
|
||||
"vaultAuthDescription": "HashiCorp Vaultへのサインイン用のウィンドウが開きました。そこでサインインを完了すると、この接続は自動的に続行されます。",
|
||||
"vaultAuthFailed": "Vault 認証に失敗しました。もう一度お試しください。",
|
||||
"vaultAuthTitle": "Vault サインインが必要です",
|
||||
"vaultReopen": "サインインウィンドウを再表示",
|
||||
"websocketAbnormalClose": "接続が予期せず切断されました。リバースプロキシまたはSSLの設定に問題がある可能性があります。サーバーログを確認してください。",
|
||||
"websocketError": "WebSocket接続エラー"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}}개 윈도우",
|
||||
"tmuxWindows": "윈도우",
|
||||
"unknownError": "알 수 없는 오류가 발생했습니다.",
|
||||
"vaultAuthDescription": "HashiCorp Vault 로그인을 위한 창이 열렸습니다. 해당 창에서 로그인을 완료하면 연결이 자동으로 계속됩니다.",
|
||||
"vaultAuthFailed": "Vault 인증에 실패했습니다. 다시 시도하세요.",
|
||||
"vaultAuthTitle": "Vault 로그인 필요",
|
||||
"vaultReopen": "로그인 창 다시 열기",
|
||||
"websocketAbnormalClose": "연결이 예기치 않게 종료되었습니다. 리버스 프록시 또는 SSL 구성 문제일 수 있습니다. 서버 로그를 확인하세요.",
|
||||
"websocketError": "WebSocket 연결 오류"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} venster",
|
||||
"tmuxWindows": "Windows",
|
||||
"unknownError": "Er is een onbekende fout opgetreden.",
|
||||
"vaultAuthDescription": "Er is een venster geopend om in te loggen bij HashiCorp Vault. Voltooi de aanmelding daar; deze verbinding wordt automatisch tot stand gebracht.",
|
||||
"vaultAuthFailed": "Authenticatie voor de kluis is mislukt. Probeer het opnieuw.",
|
||||
"vaultAuthTitle": "Aanmelden bij Vault is vereist.",
|
||||
"vaultReopen": "Heropen het aanmeldvenster",
|
||||
"websocketAbnormalClose": "De verbinding is onverwacht verbroken. Dit kan te wijten zijn aan een probleem met de reverse proxy of de SSL-configuratie. Controleer de serverlogboeken.",
|
||||
"websocketError": "WebSocket-verbindingsfout"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} vindu",
|
||||
"tmuxWindows": "Vinduer",
|
||||
"unknownError": "Ukjent feil oppstod",
|
||||
"vaultAuthDescription": "Et vindu har åpnet seg for å logge på HashiCorp Vault. Fullfør påloggingen der; denne tilkoblingen vil fortsette automatisk.",
|
||||
"vaultAuthFailed": "Hvelv-autentisering mislyktes. Prøv på nytt.",
|
||||
"vaultAuthTitle": "Pålogging til arkivet kreves",
|
||||
"vaultReopen": "Åpne påloggingsvinduet på nytt",
|
||||
"websocketAbnormalClose": "Tilkoblingen ble avsluttet uventet. Dette kan skyldes et problem med omvendt proxy eller SSL-konfigurasjon. Sjekk serverloggene.",
|
||||
"websocketError": "WebSocket-tilkoblingsfeil"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} okno",
|
||||
"tmuxWindows": "Okna",
|
||||
"unknownError": "Wystąpił nieznany błąd",
|
||||
"vaultAuthDescription": "Otworzyło się okno logowania do HashiCorp Vault. Dokończ logowanie, a połączenie zostanie nawiązane automatycznie.",
|
||||
"vaultAuthFailed": "Uwierzytelnienie w Vault nie powiodło się. Spróbuj ponownie.",
|
||||
"vaultAuthTitle": "Wymagane logowanie do Vault",
|
||||
"vaultReopen": "Otwórz ponownie okno logowania",
|
||||
"websocketAbnormalClose": "Połączenie zostało nieoczekiwanie zamknięte. Przyczyną może być reverse proxy lub problem z konfiguracją SSL. Sprawdź logi serwera.",
|
||||
"websocketError": "Błąd połączenia WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} janela",
|
||||
"tmuxWindows": "Windows",
|
||||
"unknownError": "Ocorreu um erro desconhecido.",
|
||||
"vaultAuthDescription": "Uma janela foi aberta para fazer login no HashiCorp Vault. Conclua o login; esta conexão será estabelecida automaticamente.",
|
||||
"vaultAuthFailed": "A autenticação do cofre falhou. Tente novamente.",
|
||||
"vaultAuthTitle": "É necessário fazer login no cofre.",
|
||||
"vaultReopen": "Reabrir janela de login",
|
||||
"websocketAbnormalClose": "A conexão foi encerrada inesperadamente. Isso pode ter ocorrido devido a um problema de configuração de proxy reverso ou SSL. Verifique os registros do servidor.",
|
||||
"websocketError": "Erro de conexão WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} janela",
|
||||
"tmuxWindows": "Janelas",
|
||||
"unknownError": "Ocorreu um erro desconhecido.",
|
||||
"vaultAuthDescription": "Foi aberta uma janela para iniciar sessão no HashiCorp Vault. Conclua o início de sessão aí; esta ligação continuará automaticamente.",
|
||||
"vaultAuthFailed": "A autenticação no Vault falhou. Tente novamente.",
|
||||
"vaultAuthTitle": "Início de sessão no Vault necessário",
|
||||
"vaultReopen": "Reabrir janela de início de sessão",
|
||||
"websocketAbnormalClose": "A ligação foi fechada inesperadamente. Isto pode dever-se a um problema de configuração do proxy reverso ou SSL. Verifique os registos do servidor.",
|
||||
"websocketError": "Erro de ligação WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "fereastră {{count}}",
|
||||
"tmuxWindows": "Ferestre",
|
||||
"unknownError": "A apărut o eroare necunoscută",
|
||||
"vaultAuthDescription": "S-a deschis o fereastră pentru a vă conecta la HashiCorp Vault. Finalizați conectarea acolo; această conexiune va continua automat.",
|
||||
"vaultAuthFailed": "Autentificarea în seif a eșuat. Vă rugăm să încercați din nou.",
|
||||
"vaultAuthTitle": "Este necesară conectarea la seif",
|
||||
"vaultReopen": "Redeschideți fereastra de conectare",
|
||||
"websocketAbnormalClose": "Conexiunea s-a închis pe neașteptate. Acest lucru se poate datora unei probleme de configurare a proxy-ului invers sau a SSL. Vă rugăm să verificați jurnalele serverului.",
|
||||
"websocketError": "Eroare de conexiune WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} окно",
|
||||
"tmuxWindows": "Окна",
|
||||
"unknownError": "Произошла неизвестная ошибка",
|
||||
"vaultAuthDescription": "Открыто окно для входа в HashiCorp Vault. Завершите вход там; подключение продолжится автоматически.",
|
||||
"vaultAuthFailed": "Аутентификация Vault не удалась. Попробуйте снова.",
|
||||
"vaultAuthTitle": "Требуется вход в Vault",
|
||||
"vaultReopen": "Повторно открыть окно входа",
|
||||
"websocketAbnormalClose": "Соединение неожиданно закрыто. Возможно, проблема с обратным прокси или конфигурацией SSL. Проверьте журналы сервера.",
|
||||
"websocketError": "Ошибка подключения WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} прозор",
|
||||
"tmuxWindows": "Прозори",
|
||||
"unknownError": "Дошло је до непознате грешке",
|
||||
"vaultAuthDescription": "Отворио се прозор за пријављивање на HashiCorp Vault. Завршите пријављивање тамо; ова веза ће се аутоматски наставити.",
|
||||
"vaultAuthFailed": "Аутентификација трезора није успела. Молимо покушајте поново.",
|
||||
"vaultAuthTitle": "Потребно је пријављивање у трезор",
|
||||
"vaultReopen": "Поново отвори прозор за пријављивање",
|
||||
"websocketAbnormalClose": "Веза је неочекивано прекинута. Ово може бити због проблема са обрнутим проксијем или SSL конфигурацијом. Молимо вас да проверите логове сервера.",
|
||||
"websocketError": "Грешка у повезивању са WebSocket-ом"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} fönster",
|
||||
"tmuxWindows": "Fönster",
|
||||
"unknownError": "Okänt fel inträffade",
|
||||
"vaultAuthDescription": "Ett fönster har öppnats för att logga in på HashiCorp Vault. Slutför inloggningen där; anslutningen fortsätter automatiskt.",
|
||||
"vaultAuthFailed": "Valvautentisering misslyckades. Försök igen.",
|
||||
"vaultAuthTitle": "Inloggning till arkivet krävs",
|
||||
"vaultReopen": "Öppna inloggningsfönstret igen",
|
||||
"websocketAbnormalClose": "Anslutningen stängdes oväntat. Detta kan bero på ett problem med omvänd proxy eller SSL-konfiguration. Kontrollera serverloggarna.",
|
||||
"websocketError": "WebSocket-anslutningsfel"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} หน้าต่าง",
|
||||
"tmuxWindows": "วินโดวส์",
|
||||
"unknownError": "เกิดข้อผิดพลาดที่ไม่ทราบสาเหตุ",
|
||||
"vaultAuthDescription": "หน้าต่างสำหรับเข้าสู่ระบบ HashiCorp Vault ได้เปิดขึ้นแล้ว โปรดทำการเข้าสู่ระบบให้เสร็จสมบูรณ์ การเชื่อมต่อนี้จะดำเนินการต่อโดยอัตโนมัติ",
|
||||
"vaultAuthFailed": "การตรวจสอบสิทธิ์ Vault ล้มเหลว โปรดลองอีกครั้ง",
|
||||
"vaultAuthTitle": "ต้องลงชื่อเข้าใช้ Vault",
|
||||
"vaultReopen": "เปิดหน้าต่างเข้าสู่ระบบอีกครั้ง",
|
||||
"websocketAbnormalClose": "การเชื่อมต่อถูกตัดโดยไม่คาดคิด อาจเกิดจากปัญหาเกี่ยวกับรีเวิร์สพร็อกซีหรือการกำหนดค่า SSL โปรดตรวจสอบบันทึกของเซิร์ฟเวอร์",
|
||||
"websocketError": "ข้อผิดพลาดในการเชื่อมต่อ WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} pencere",
|
||||
"tmuxWindows": "Pencereler",
|
||||
"unknownError": "Bilinmeyen bir hata oluştu",
|
||||
"vaultAuthDescription": "HashiCorp Vault'ta oturum açmak için bir pencere açıldı. Orada oturum açmayı tamamlayın; bu bağlantı otomatik olarak devam edecek.",
|
||||
"vaultAuthFailed": "Vault kimlik doğrulaması başarısız oldu. Lütfen tekrar deneyin.",
|
||||
"vaultAuthTitle": "Vault Oturum Açması Gerekli",
|
||||
"vaultReopen": "Oturum açma penceresini yeniden aç",
|
||||
"websocketAbnormalClose": "Bağlantı beklenmedik şekilde kapandı. Bu, ters proxy veya SSL yapılandırma sorunundan kaynaklanabilir. Lütfen sunucu günlüklerini kontrol edin.",
|
||||
"websocketError": "WebSocket bağlantı hatası"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "вікно {{count}}",
|
||||
"tmuxWindows": "Вікна",
|
||||
"unknownError": "Сталася невідома помилка",
|
||||
"vaultAuthDescription": "Відкрилося вікно для входу в HashiCorp Vault. Завершіть вхід там; це з’єднання буде продовжено автоматично.",
|
||||
"vaultAuthFailed": "Не вдалося виконати автентифікацію сховища. Спробуйте ще раз.",
|
||||
"vaultAuthTitle": "Потрібен вхід до Сейфа",
|
||||
"vaultReopen": "Знову відкрити вікно входу",
|
||||
"websocketAbnormalClose": "З’єднання несподівано завершилося. Можливо, це пов’язано зі зворотним проксі-сервером або проблемою конфігурації SSL. Перевірте журнали сервера.",
|
||||
"websocketError": "Помилка підключення WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} cửa sổ",
|
||||
"tmuxWindows": "Cửa sổ",
|
||||
"unknownError": "Lỗi không xác định đã xảy ra",
|
||||
"vaultAuthDescription": "Một cửa sổ đã mở để đăng nhập vào HashiCorp Vault. Hoàn tất đăng nhập ở đó; kết nối này sẽ tự động tiếp tục.",
|
||||
"vaultAuthFailed": "Xác thực Vault thất bại. Vui lòng thử lại.",
|
||||
"vaultAuthTitle": "Yêu cầu đăng nhập Vault",
|
||||
"vaultReopen": "Mở lại cửa sổ đăng nhập",
|
||||
"websocketAbnormalClose": "Kết nối đóng đột ngột. Có thể do sự cố cấu hình reverse proxy hoặc SSL. Vui lòng kiểm tra nhật ký máy chủ.",
|
||||
"websocketError": "Lỗi kết nối WebSocket"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} 个窗口",
|
||||
"tmuxWindows": "窗口",
|
||||
"unknownError": "发生未知错误",
|
||||
"vaultAuthDescription": "已打开窗口以登录 HashiCorp Vault。在该处完成登录;此连接将自动继续。",
|
||||
"vaultAuthFailed": "Vault 认证失败,请重试。",
|
||||
"vaultAuthTitle": "需要 Vault 登录",
|
||||
"vaultReopen": "重新打开登录窗口",
|
||||
"websocketAbnormalClose": "连接意外关闭。这可能是因为反向代理或SSL配置问题。请检查服务器日志。",
|
||||
"websocketError": "WebSocket 连接错误"
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
"tmuxWindowCount": "{{count}} 個視窗",
|
||||
"tmuxWindows": "視窗",
|
||||
"unknownError": "發生未知錯誤",
|
||||
"vaultAuthDescription": "已開啟視窗以登入 HashiCorp Vault。在該處完成登入;此連線將自動繼續。",
|
||||
"vaultAuthFailed": "Vault 驗證失敗。請再試一次。",
|
||||
"vaultAuthTitle": "需要 Vault 登入",
|
||||
"vaultReopen": "重新開啟登入視窗",
|
||||
"websocketAbnormalClose": "連線意外關閉。這可能是由於反向代理或 SSL 設定問題所導致。請檢查伺服器記錄。",
|
||||
"websocketError": "WebSocket 連線錯誤"
|
||||
},
|
||||
|
||||
@@ -1141,7 +1141,7 @@ export function createTerminalSocket(deps: TerminalSocketDeps) {
|
||||
/**
|
||||
* Starts the browser sign-in behind an "<interaction>_auth_required"
|
||||
* message. The provider for the host's auth type wins; otherwise the first
|
||||
* provider that owns this interaction (Vault attaches to any auth type).
|
||||
* provider that owns this interaction.
|
||||
*/
|
||||
async function startAuthInteraction(
|
||||
interaction: string,
|
||||
|
||||
@@ -342,15 +342,6 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
const currentHostIdRef = useRef<number | null>(null);
|
||||
const currentHostConfigRef = useRef<TerminalHostConfig | null>(null);
|
||||
|
||||
// Vault SSH signer interactive OIDC flow
|
||||
const [vaultDialog, setVaultDialog] = useState<{
|
||||
stage: "waiting" | "error";
|
||||
error?: string;
|
||||
} | null>(null);
|
||||
const vaultFailedRef = useRef(false);
|
||||
const vaultPopupRef = useRef<Window | null>(null);
|
||||
const vaultTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const [hostKeyVerification, setHostKeyVerification] = useState<{
|
||||
isOpen: boolean;
|
||||
scenario: "new" | "changed";
|
||||
@@ -1909,7 +1900,6 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
updateConnectionError(errorMessage);
|
||||
setIsConnecting(false);
|
||||
} else if (msg.type === "connected") {
|
||||
vaultFailedRef.current = false;
|
||||
wasConnectedRef.current = true;
|
||||
setIsConnected(true);
|
||||
setIsConnecting(false);
|
||||
@@ -2104,86 +2094,6 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
webSocketRef.current.close();
|
||||
}
|
||||
}, 300000);
|
||||
} else if (msg.type === "vault_auth_required") {
|
||||
if (connectionTimeoutRef.current) {
|
||||
clearTimeout(connectionTimeoutRef.current);
|
||||
connectionTimeoutRef.current = null;
|
||||
}
|
||||
if (vaultFailedRef.current) {
|
||||
setVaultDialog({
|
||||
stage: "error",
|
||||
error: t("terminal.vaultAuthFailed"),
|
||||
});
|
||||
updateConnectionError(t("terminal.vaultAuthFailed"));
|
||||
addLog({
|
||||
type: "error",
|
||||
stage: "auth",
|
||||
message: t("terminal.vaultAuthFailed"),
|
||||
});
|
||||
} else {
|
||||
vaultFailedRef.current = true;
|
||||
webSocketRef.current?.send(
|
||||
JSON.stringify({
|
||||
type: "vault_start_auth",
|
||||
data: { hostId: msg.hostId },
|
||||
}),
|
||||
);
|
||||
}
|
||||
} else if (msg.type === "vault_auth_url") {
|
||||
if (connectionErrorRef.current) return;
|
||||
try {
|
||||
vaultPopupRef.current = window.open(
|
||||
msg.url,
|
||||
"termix-vault-oidc",
|
||||
"width=540,height=720",
|
||||
);
|
||||
} catch {
|
||||
vaultPopupRef.current = null;
|
||||
}
|
||||
setVaultDialog({ stage: "waiting" });
|
||||
if (vaultTimeoutRef.current) clearTimeout(vaultTimeoutRef.current);
|
||||
vaultTimeoutRef.current = setTimeout(() => {
|
||||
setVaultDialog(null);
|
||||
webSocketRef.current?.close();
|
||||
}, 300000);
|
||||
} else if (msg.type === "vault_completed") {
|
||||
if (vaultTimeoutRef.current) {
|
||||
clearTimeout(vaultTimeoutRef.current);
|
||||
vaultTimeoutRef.current = null;
|
||||
}
|
||||
try {
|
||||
vaultPopupRef.current?.close();
|
||||
} catch {
|
||||
// popup may already be closed
|
||||
}
|
||||
setVaultDialog(null);
|
||||
if (webSocketRef.current && terminal) {
|
||||
webSocketRef.current.send(
|
||||
JSON.stringify({
|
||||
type: "vault_auth_completed",
|
||||
data: {
|
||||
hostId: currentHostIdRef.current,
|
||||
cols: terminal.cols || 80,
|
||||
rows: terminal.rows || 24,
|
||||
hostConfig: currentHostConfigRef.current,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
} else if (msg.type === "vault_error") {
|
||||
if (connectionErrorRef.current) return;
|
||||
vaultFailedRef.current = true;
|
||||
if (vaultTimeoutRef.current) {
|
||||
clearTimeout(vaultTimeoutRef.current);
|
||||
vaultTimeoutRef.current = null;
|
||||
}
|
||||
try {
|
||||
vaultPopupRef.current?.close();
|
||||
} catch {
|
||||
// popup may already be closed
|
||||
}
|
||||
setVaultDialog({ stage: "error", error: msg.error });
|
||||
setIsConnecting(false);
|
||||
} else if (msg.type === "keyboard_interactive_available") {
|
||||
setKeyboardInteractiveDetected(true);
|
||||
setIsConnecting(false);
|
||||
@@ -2240,7 +2150,6 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
}
|
||||
} else if (msg.type === "sessionAttached") {
|
||||
isAttachingSessionRef.current = false;
|
||||
vaultFailedRef.current = false;
|
||||
wasConnectedRef.current = true;
|
||||
setIsConnected(true);
|
||||
setIsConnecting(false);
|
||||
@@ -3884,70 +3793,6 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
props={overlayProps as unknown as Record<string, unknown>}
|
||||
/>
|
||||
|
||||
{vaultDialog && (
|
||||
<div className="absolute inset-0 z-50 flex items-center justify-center bg-black/60">
|
||||
<div className="w-[420px] max-w-[90%] border border-border bg-background p-5 shadow-lg">
|
||||
<h3 className="text-sm font-bold mb-2 text-foreground">
|
||||
{vaultDialog.stage === "error"
|
||||
? t("terminal.vaultAuthFailed")
|
||||
: t("terminal.vaultAuthTitle")}
|
||||
</h3>
|
||||
{vaultDialog.stage === "error" ? (
|
||||
<p className="text-xs text-destructive mb-4 break-words">
|
||||
{vaultDialog.error || t("terminal.vaultAuthFailed")}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground mb-4">
|
||||
{t("terminal.vaultAuthDescription")}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex justify-end gap-2">
|
||||
{vaultDialog.stage === "waiting" && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
try {
|
||||
vaultPopupRef.current?.focus();
|
||||
} catch {
|
||||
// popup may be gone
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t("terminal.vaultReopen")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (vaultTimeoutRef.current) {
|
||||
clearTimeout(vaultTimeoutRef.current);
|
||||
vaultTimeoutRef.current = null;
|
||||
}
|
||||
try {
|
||||
vaultPopupRef.current?.close();
|
||||
} catch {
|
||||
// popup may already be closed
|
||||
}
|
||||
webSocketRef.current?.send(
|
||||
JSON.stringify({
|
||||
type: "vault_cancel",
|
||||
data: { hostId: currentHostIdRef.current },
|
||||
}),
|
||||
);
|
||||
setVaultDialog(null);
|
||||
}}
|
||||
>
|
||||
{vaultDialog.stage === "error"
|
||||
? t("common.close")
|
||||
: t("hosts.cancelBtn")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hostKeyVerification?.isOpen && (
|
||||
<HostKeyVerificationDialog
|
||||
isOpen={true}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## 1.0.0
|
||||
|
||||
Bundled with Termix 2.9.0.
|
||||
|
||||
- Vault SSH signing moved out of core. The callback is now
|
||||
`/plugin-api/vault/oidc/callback`; the 2.8 URI `/vault/oidc/callback`
|
||||
still works and upgraded installs keep sending it until an admin turns
|
||||
off "Use the old redirect URI".
|
||||
- Profiles keep their ids and sync ids. A host's profile is now a host
|
||||
setting of this plugin.
|
||||
- Signed certificates are stored encrypted with the installation key.
|
||||
Certificates cached by 2.8 are not carried over; the next connection asks
|
||||
for one sign-in.
|
||||
- Sharing a profile needs the "Share Vault profiles" permission, which
|
||||
admins have by default.
|
||||
@@ -0,0 +1,29 @@
|
||||
# HashiCorp Vault
|
||||
|
||||
Adds the `vault` SSH auth type: Termix signs you in to Vault with OIDC in the
|
||||
browser, has Vault's SSH secrets engine sign a fresh key, and keeps the
|
||||
short-lived certificate until it expires.
|
||||
|
||||
Termix talks to Vault's HTTP API directly (`auth/<mount>/oidc/auth_url`,
|
||||
`auth/<mount>/oidc/callback`, `<ssh mount>/sign/<role>`). No Vault token,
|
||||
AppRole secret or long-lived key is ever stored.
|
||||
|
||||
## Profiles
|
||||
|
||||
A profile holds the connection settings only: the Vault address and
|
||||
namespace, the OIDC mount and role, the SSH secrets mount and signer role,
|
||||
the valid principals and the key type. Pick one in the host editor after
|
||||
choosing "Vault" as the authentication type, and manage profiles from the
|
||||
same place. Users with the "Share Vault profiles" permission can share a
|
||||
profile with everyone.
|
||||
|
||||
## Settings
|
||||
|
||||
Admin settings, under Settings > Plugins > HashiCorp Vault:
|
||||
|
||||
- **Redirect URI**: add it to `allowed_redirect_uris` in each Vault OIDC
|
||||
role: `<base URL>/plugin-api/vault/oidc/callback`, or
|
||||
`<base URL>/vault/oidc/callback` while "Use the old redirect URI" is on.
|
||||
|
||||
An install upgraded from 2.8 that used Vault keeps the old redirect URI
|
||||
turned on.
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Vault Signer Profile",
|
||||
"selectProfile": "Select a Vault profile...",
|
||||
"hint": "Settings come from the shared profile; you'll sign in to Vault via OIDC when you connect. No secrets are stored.",
|
||||
"docs": "View docs",
|
||||
"sharedProfile": "{{name}} (shared)"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "New profile",
|
||||
"manage": "Manage Vault profiles",
|
||||
"name": "Friendly Name",
|
||||
"vaultAddr": "Vault Address",
|
||||
"namespace": "Namespace",
|
||||
"oidcMount": "OIDC Auth Mount",
|
||||
"oidcRole": "OIDC Role",
|
||||
"sshMount": "SSH Secrets Mount",
|
||||
"sshRole": "SSH Signer Role",
|
||||
"validPrincipals": "Valid Principals",
|
||||
"keyType": "Ephemeral Key Type",
|
||||
"shareWithAll": "Share with all users",
|
||||
"create": "Create profile",
|
||||
"created": "Vault profile created",
|
||||
"saved": "Vault profile saved",
|
||||
"deleted": "Vault profile deleted",
|
||||
"saveFailed": "Failed to save Vault profile",
|
||||
"deleteFailed": "Failed to delete Vault profile",
|
||||
"save": "Save profile",
|
||||
"validationError": "Name, Vault address and SSH signer role are required",
|
||||
"none": "No Vault profiles yet.",
|
||||
"sharedBadge": "shared",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete"
|
||||
},
|
||||
"hosts": {
|
||||
"filterAuthVault": "Vault"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Vault sign-in required",
|
||||
"description": "A window has opened to sign in to HashiCorp Vault. Complete the sign-in there; this connection will continue automatically.",
|
||||
"failed": "Vault authentication failed. Please try again.",
|
||||
"reopen": "Reopen sign-in window"
|
||||
},
|
||||
"permissions": {
|
||||
"use": {
|
||||
"title": "Use Vault SSH signing",
|
||||
"description": "Create Vault signer profiles and connect to hosts that use Vault."
|
||||
},
|
||||
"share": {
|
||||
"title": "Share Vault profiles",
|
||||
"description": "Mark a Vault signer profile as shared so every user can pick it."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"redirectUri": {
|
||||
"label": "Redirect URI",
|
||||
"description": "Add this URL to allowed_redirect_uris in each Vault OIDC role that Termix signs in with."
|
||||
},
|
||||
"legacyCallback": {
|
||||
"label": "Use the old redirect URI",
|
||||
"description": "Keep sending /vault/oidc/callback, the redirect URI used before 2.9. Turn this off once your Vault roles allow the new one."
|
||||
},
|
||||
"host": {
|
||||
"profileId": {
|
||||
"label": "Vault signer profile"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Kluis Ondertekenaar Profiel",
|
||||
"selectProfile": "Kies 'n kluisprofiel...",
|
||||
"hint": "Instellings kom van die gedeelde profiel; jy sal by Vault aanmeld via OIDC wanneer jy koppel. Geen geheime word gestoor nie.",
|
||||
"docs": "Bekyk dokumente"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Nuwe profiel",
|
||||
"manage": "Bestuur Vault-profiele",
|
||||
"name": "Vriendelike Naam",
|
||||
"vaultAddr": "Kluisadres",
|
||||
"namespace": "Naamruimte",
|
||||
"oidcMount": "OIDC-magtigingsmontering",
|
||||
"oidcRole": "OIDC-rol",
|
||||
"sshMount": "SSH-geheime berg",
|
||||
"sshRole": "SSH-ondertekenaarrol",
|
||||
"validPrincipals": "Geldige Prinsipale",
|
||||
"keyType": "Efemere Sleuteltipe",
|
||||
"shareWithAll": "Deel met alle gebruikers",
|
||||
"create": "Skep profiel",
|
||||
"created": "Kluisprofiel geskep",
|
||||
"saved": "Kluisprofiel gestoor",
|
||||
"deleted": "Kluisprofiel uitgevee",
|
||||
"saveFailed": "Kon nie kluisprofiel stoor nie",
|
||||
"deleteFailed": "Kon nie kluisprofiel uitvee nie",
|
||||
"save": "Stoor profiel",
|
||||
"validationError": "Naam, kluisadres en SSH-ondertekenrol word vereis",
|
||||
"none": "Geen Vault-profiele nog nie.",
|
||||
"sharedBadge": "gedeel"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Kluisaanmelding vereis",
|
||||
"description": "'n Venster het oopgemaak om by HashiCorp Vault aan te meld. Voltooi die aanmelding daar; hierdie verbinding sal outomaties voortgaan.",
|
||||
"failed": "Kluisverifikasie het misluk. Probeer asseblief weer.",
|
||||
"reopen": "Maak aanmeldvenster weer oop"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "ملف توقيع Vault",
|
||||
"selectProfile": "اختيار ملف توقيع Vault...",
|
||||
"hint": "تأتي الإعدادات من الملف المشترك؛ ستقوم بتسجيل الدخول إلى Vault عبر OIDC عند الاتصال. لا يتم تخزين أي أسرار.",
|
||||
"docs": "عرض المستندات"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "ملف جديد",
|
||||
"manage": "إدارة ملفات Vault",
|
||||
"name": "الاسم المألوف",
|
||||
"vaultAddr": "عنوان Vault",
|
||||
"namespace": "مساحة الاسم",
|
||||
"oidcMount": "تثبيت مصادقة OIDC",
|
||||
"oidcRole": "دور OIDC",
|
||||
"sshMount": "تثبيت أسرار SSH",
|
||||
"sshRole": "دور موقع SSH",
|
||||
"validPrincipals": "المستخدمون المسموح بهم",
|
||||
"keyType": "نوع المفتاح المؤقت",
|
||||
"shareWithAll": "مشاركة مع جميع المستخدمين",
|
||||
"create": "إنشاء ملف تعريف",
|
||||
"created": "تم إنشاء ملف تعريف Vault",
|
||||
"saved": "تم حفظ ملف تعريف Vault",
|
||||
"deleted": "تم حذف ملف تعريف Vault",
|
||||
"saveFailed": "فشل حفظ ملف تعريف Vault",
|
||||
"deleteFailed": "فشل حذف ملف تعريف Vault",
|
||||
"save": "حفظ ملف التعريف",
|
||||
"validationError": "الاسم وعنوان Vault ودور الموقّع SSH مطلوب",
|
||||
"none": "لا توجد ملفات تعريف Vault بعد.",
|
||||
"sharedBadge": "مشترك"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "مطلوب تسجيل الدخول إلى Vault",
|
||||
"description": "تم فتح نافذة لتسجيل الدخول إلى HashiCorp Vault. أكمل تسجيل الدخول هناك؛ سيستمر هذا الاتصال تلقائيًا.",
|
||||
"failed": "فشلت مصادقة Vault. يرجى المحاولة مرة أخرى.",
|
||||
"reopen": "أعد فتح نافذة تسجيل الدخول"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Профил на подписващ в трезора",
|
||||
"selectProfile": "Изберете профил в трезора...",
|
||||
"hint": "Настройките идват от споделения профил; ще влезете в Vault чрез OIDC, когато се свържете. Не се съхраняват тайни.",
|
||||
"docs": "Преглед на документи"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Нов профил",
|
||||
"manage": "Управление на профили в хранилището",
|
||||
"name": "Приятелско име",
|
||||
"vaultAddr": "Адрес на трезора",
|
||||
"namespace": "Именно пространство",
|
||||
"oidcMount": "OIDC Authentication Mount",
|
||||
"oidcRole": "Роля на OIDC",
|
||||
"sshMount": "SSH Secrets Mount",
|
||||
"sshRole": "Роля на SSH подписващ",
|
||||
"validPrincipals": "Валидни принципали",
|
||||
"keyType": "Тип ефимерен ключ",
|
||||
"shareWithAll": "Споделяне с всички потребители",
|
||||
"create": "Създаване на профил",
|
||||
"created": "Профилът на трезора е създаден",
|
||||
"saved": "Профилът на трезора е запазен",
|
||||
"deleted": "Профилът в трезора е изтрит",
|
||||
"saveFailed": "Запазването на профила в Трезора не бе успешно",
|
||||
"deleteFailed": "Изтриването на профила в Vault не бе успешно",
|
||||
"save": "Запазване на профил",
|
||||
"validationError": "Име, адрес на трезора и роля на SSH подписващ са задължителни",
|
||||
"none": "Все още няма профили в Трезора.",
|
||||
"sharedBadge": "споделено"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Изисква се влизане в трезора",
|
||||
"description": "Отвори се прозорец за влизане в HashiCorp Vault. Завършете влизането там; тази връзка ще продължи автоматично.",
|
||||
"failed": "Удостоверяването на трезора не бе успешно. Моля, опитайте отново.",
|
||||
"reopen": "Отваряне отново на прозореца за влизане"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "ভল্ট স্বাক্ষরকারীর প্রোফাইল",
|
||||
"selectProfile": "একটি ভল্ট প্রোফাইল নির্বাচন করুন...",
|
||||
"hint": "সেটিংস শেয়ার করা প্রোফাইল থেকে আসে; সংযোগ করার সময় আপনি OIDC-এর মাধ্যমে ভল্টে সাইন ইন করবেন। কোনো গোপনীয় তথ্য সংরক্ষিত থাকে না।",
|
||||
"docs": "নথি দেখুন"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "নতুন প্রোফাইল",
|
||||
"manage": "ভল্ট প্রোফাইলগুলি পরিচালনা করুন",
|
||||
"name": "বন্ধুত্বপূর্ণ নাম",
|
||||
"vaultAddr": "ভল্ট ঠিকানা",
|
||||
"namespace": "নেমস্পেস",
|
||||
"oidcMount": "OIDC অথোরাইজেশন মাউন্ট",
|
||||
"oidcRole": "OIDC ভূমিকা",
|
||||
"sshMount": "SSH সিক্রেটস মাউন্ট",
|
||||
"sshRole": "SSH স্বাক্ষরকারীর ভূমিকা",
|
||||
"validPrincipals": "বৈধ অধ্যক্ষরা",
|
||||
"keyType": "ক্ষণস্থায়ী কী টাইপ",
|
||||
"shareWithAll": "সকল ব্যবহারকারীর সাথে শেয়ার করুন",
|
||||
"create": "প্রোফাইল তৈরি করুন",
|
||||
"created": "ভল্ট প্রোফাইল তৈরি করা হয়েছে",
|
||||
"saved": "ভল্ট প্রোফাইল সংরক্ষিত হয়েছে",
|
||||
"deleted": "ভল্ট প্রোফাইল মুছে ফেলা হয়েছে",
|
||||
"saveFailed": "ভল্ট প্রোফাইল সংরক্ষণ করতে ব্যর্থ হয়েছে",
|
||||
"deleteFailed": "ভল্ট প্রোফাইল মুছে ফেলা ব্যর্থ হয়েছে",
|
||||
"save": "প্রোফাইল সংরক্ষণ করুন",
|
||||
"validationError": "নাম, ভল্ট অ্যাড্রেস এবং SSH সাইনার রোল প্রয়োজন।",
|
||||
"none": "এখনো কোনো ভল্ট প্রোফাইল তৈরি হয়নি।",
|
||||
"sharedBadge": "শেয়ার করা"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "ভল্টে সাইন-ইন করা আবশ্যক",
|
||||
"description": "হ্যাশিকর্প ভল্টে সাইন ইন করার জন্য একটি উইন্ডো খুলেছে। সেখানে সাইন-ইন সম্পন্ন করুন; এই সংযোগটি স্বয়ংক্রিয়ভাবে চলতে থাকবে।",
|
||||
"failed": "ভল্ট প্রমাণীকরণ ব্যর্থ হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন।",
|
||||
"reopen": "সাইন-ইন উইন্ডো পুনরায় খুলুন"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Perfil del signant de la caixa forta",
|
||||
"selectProfile": "Seleccioneu un perfil de Vault...",
|
||||
"hint": "La configuració prové del perfil compartit; iniciaràs la sessió a Vault mitjançant OIDC quan et connectis. No s'emmagatzemen secrets.",
|
||||
"docs": "Veure documents"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Nou perfil",
|
||||
"manage": "Gestiona els perfils de Vault",
|
||||
"name": "Nom amigable",
|
||||
"vaultAddr": "Adreça de la caixa forta",
|
||||
"namespace": "Espai de noms",
|
||||
"oidcMount": "Muntatge d'autenticació OIDC",
|
||||
"oidcRole": "Rol de l'OIDC",
|
||||
"sshMount": "Muntanya de Secrets SSH",
|
||||
"sshRole": "Rol de signant SSH",
|
||||
"validPrincipals": "Principals vàlids",
|
||||
"keyType": "Tipus de clau efímera",
|
||||
"shareWithAll": "Comparteix amb tots els usuaris",
|
||||
"create": "Crea un perfil",
|
||||
"created": "Perfil de la caixa forta creat",
|
||||
"saved": "Perfil de la caixa forta desat",
|
||||
"deleted": "Perfil de la caixa forta suprimit",
|
||||
"saveFailed": "No s'ha pogut desar el perfil de Vault",
|
||||
"deleteFailed": "No s'ha pogut suprimir el perfil de Vault",
|
||||
"save": "Desa el perfil",
|
||||
"validationError": "El nom, l'adreça de la caixa forta i el rol de signant SSH són obligatoris.",
|
||||
"none": "Encara no hi ha perfils de Vault.",
|
||||
"sharedBadge": "compartit"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Cal iniciar sessió a la caixa forta",
|
||||
"description": "S'ha obert una finestra per iniciar la sessió a HashiCorp Vault. Completeu l'inici de sessió allà; aquesta connexió continuarà automàticament.",
|
||||
"failed": "L'autenticació de la caixa forta ha fallat. Torna-ho a provar.",
|
||||
"reopen": "Torna a obrir la finestra d'inici de sessió"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Profil podepisujícího trezoru",
|
||||
"selectProfile": "Vyberte profil úložiště...",
|
||||
"hint": "Nastavení pocházejí ze sdíleného profilu; po připojení se do úložiště přihlásíte přes OIDC. Žádné tajné kódy se neukládají.",
|
||||
"docs": "Zobrazit dokumenty"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Nový profil",
|
||||
"manage": "Správa profilů úložiště",
|
||||
"name": "Přátelský název",
|
||||
"vaultAddr": "Adresa trezoru",
|
||||
"namespace": "Jmenný prostor",
|
||||
"oidcMount": "Připojení autorizace OIDC",
|
||||
"oidcRole": "Role OIDC",
|
||||
"sshMount": "SSH tajemství Mount",
|
||||
"sshRole": "Role SSH podepisujícího",
|
||||
"validPrincipals": "Platné principy",
|
||||
"keyType": "Typ dočasného klíče",
|
||||
"shareWithAll": "Sdílet se všemi uživateli",
|
||||
"create": "Vytvořit profil",
|
||||
"created": "Profil úložiště vytvořen",
|
||||
"saved": "Profil trezoru uložen",
|
||||
"deleted": "Profil trezoru smazán",
|
||||
"saveFailed": "Uložení profilu úložiště se nezdařilo.",
|
||||
"deleteFailed": "Profil v úložišti se nepodařilo smazat.",
|
||||
"save": "Uložit profil",
|
||||
"validationError": "Jméno, adresa trezoru a role SSH signatáře jsou povinné.",
|
||||
"none": "Zatím žádné profily v úložišti Vault.",
|
||||
"sharedBadge": "sdílené"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Vyžaduje se přihlášení do trezoru",
|
||||
"description": "Otevřelo se okno pro přihlášení do HashiCorp Vault. Dokončete přihlášení tam; toto připojení bude automaticky pokračovat.",
|
||||
"failed": "Ověření v trezoru se nezdařilo. Zkuste to prosím znovu.",
|
||||
"reopen": "Znovu otevřít přihlašovací okno"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Profil for underskriver af arkiv",
|
||||
"selectProfile": "Vælg en Vault-profil...",
|
||||
"hint": "Indstillingerne kommer fra den delte profil; du logger ind på Vault via OIDC, når du opretter forbindelse. Der gemmes ingen hemmeligheder.",
|
||||
"docs": "Se dokumenter"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Ny profil",
|
||||
"manage": "Administrer Vault-profiler",
|
||||
"name": "Venligt navn",
|
||||
"vaultAddr": "Adresse i boksen",
|
||||
"namespace": "Navnerum",
|
||||
"oidcMount": "OIDC-godkendelsesmontering",
|
||||
"oidcRole": "OIDC-rolle",
|
||||
"sshMount": "SSH-hemmeligheder monteres",
|
||||
"sshRole": "SSH-underskriverrolle",
|
||||
"validPrincipals": "Gyldige principper",
|
||||
"keyType": "Kortvarig nøgletype",
|
||||
"shareWithAll": "Del med alle brugere",
|
||||
"create": "Opret profil",
|
||||
"created": "Vault-profil oprettet",
|
||||
"saved": "Vault-profil gemt",
|
||||
"deleted": "Vault-profil slettet",
|
||||
"saveFailed": "Kunne ikke gemme Vault-profilen",
|
||||
"deleteFailed": "Kunne ikke slette Vault-profilen",
|
||||
"save": "Gem profil",
|
||||
"validationError": "Navn, Vault-adresse og SSH-underskriverrolle er påkrævet",
|
||||
"none": "Ingen Vault-profiler endnu.",
|
||||
"sharedBadge": "delt"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Login til Vault kræves",
|
||||
"description": "Et vindue er åbnet, hvor du kan logge ind på HashiCorp Vault. Fuldfør loginprocessen der; forbindelsen fortsætter automatisk.",
|
||||
"failed": "Godkendelse af arkiv mislykkedes. Prøv igen.",
|
||||
"reopen": "Genåbn loginvinduet"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Vault-Signer-Profil",
|
||||
"selectProfile": "Vault-Profil auswählen...",
|
||||
"hint": "Die Einstellungen stammen aus dem freigegebenen Profil; Sie melden sich beim Verbinden per OIDC bei Vault an. Es werden keine Geheimnisse gespeichert.",
|
||||
"docs": "Dokumentation anzeigen"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Neues Profil",
|
||||
"manage": "Vault-Profile verwalten",
|
||||
"name": "Anzeigename",
|
||||
"vaultAddr": "Vault-Adresse",
|
||||
"namespace": "Namespace",
|
||||
"oidcMount": "OIDC-Auth-Mount",
|
||||
"oidcRole": "OIDC-Rolle",
|
||||
"sshMount": "SSH-Secrets-Mount",
|
||||
"sshRole": "SSH-Signer-Rolle",
|
||||
"validPrincipals": "Gültige Principals",
|
||||
"keyType": "Ephemerer Schlüsseltyp",
|
||||
"shareWithAll": "Mit allen Benutzern teilen",
|
||||
"create": "Profil erstellen",
|
||||
"created": "Vault-Profil erstellt",
|
||||
"saved": "Vault-Profil gespeichert",
|
||||
"deleted": "Vault-Profil gelöscht",
|
||||
"saveFailed": "Fehler beim Speichern des Vault-Profils",
|
||||
"deleteFailed": "Fehler beim Löschen des Vault-Profils",
|
||||
"save": "Profil speichern",
|
||||
"validationError": "Name, Vault-Adresse und SSH-Signer-Rolle sind erforderlich",
|
||||
"none": "Noch keine Vault-Profile.",
|
||||
"sharedBadge": "geteilt"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Vault-Anmeldung erforderlich",
|
||||
"description": "Ein Fenster zur Anmeldung bei HashiCorp Vault wurde geöffnet. Schließen Sie die Anmeldung dort ab; diese Verbindung wird automatisch fortgesetzt.",
|
||||
"failed": "Vault-Authentifizierung fehlgeschlagen. Bitte versuchen Sie es erneut.",
|
||||
"reopen": "Anmeldefenster erneut öffnen"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Προφίλ Υπογράφοντος Vault",
|
||||
"selectProfile": "Επιλέξτε ένα προφίλ Vault...",
|
||||
"hint": "Οι ρυθμίσεις προέρχονται από το κοινόχρηστο προφίλ. Θα συνδεθείτε στο Vault μέσω OIDC όταν συνδεθείτε. Δεν αποθηκεύονται μυστικά.",
|
||||
"docs": "Προβολή εγγράφων"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Νέο προφίλ",
|
||||
"manage": "Διαχείριση προφίλ Vault",
|
||||
"name": "Φιλικό όνομα",
|
||||
"vaultAddr": "Διεύθυνση θησαυροφυλακίου",
|
||||
"namespace": "Χώρος ονομάτων",
|
||||
"oidcMount": "Μοντάζ ελέγχου ταυτότητας OIDC",
|
||||
"oidcRole": "Ρόλος OIDC",
|
||||
"sshMount": "SSH Secrets Mount",
|
||||
"sshRole": "Ρόλος Υπογράφοντος SSH",
|
||||
"validPrincipals": "Έγκυρες Αρχές",
|
||||
"keyType": "Εφήμερος Τύπος Κλειδιού",
|
||||
"shareWithAll": "Κοινοποίηση με όλους τους χρήστες",
|
||||
"create": "Δημιουργία προφίλ",
|
||||
"created": "Δημιουργήθηκε προφίλ Vault",
|
||||
"saved": "Το προφίλ του Vault αποθηκεύτηκε",
|
||||
"deleted": "Το προφίλ Vault διαγράφηκε",
|
||||
"saveFailed": "Αποτυχία αποθήκευσης προφίλ Vault",
|
||||
"deleteFailed": "Αποτυχία διαγραφής προφίλ Vault",
|
||||
"save": "Αποθήκευση προφίλ",
|
||||
"validationError": "Απαιτούνται όνομα, διεύθυνση vault και ρόλος υπογράφοντος SSH",
|
||||
"none": "Δεν υπάρχουν ακόμη προφίλ Vault.",
|
||||
"sharedBadge": "κοινόχρηστο"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Απαιτείται σύνδεση στο Vault",
|
||||
"description": "Έχει ανοίξει ένα παράθυρο για να συνδεθείτε στο HashiCorp Vault. Ολοκληρώστε τη σύνδεση εκεί. Αυτή η σύνδεση θα συνεχιστεί αυτόματα.",
|
||||
"failed": "Ο έλεγχος ταυτότητας του vault απέτυχε. Δοκιμάστε ξανά.",
|
||||
"reopen": "Επανάνοιγμα παραθύρου σύνδεσης"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Perfil de firmante de Vault",
|
||||
"selectProfile": "Seleccionar un perfil de Vault...",
|
||||
"hint": "La configuración proviene del perfil compartido; se iniciará sesión en Vault a través de OIDC al conectarse. No se almacenan secretos.",
|
||||
"docs": "Ver documentación"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Nuevo perfil",
|
||||
"manage": "Administrar perfiles de Vault",
|
||||
"name": "Nombre descriptivo",
|
||||
"vaultAddr": "Dirección de Vault",
|
||||
"namespace": "Espacio de nombres",
|
||||
"oidcMount": "Montaje de autenticación OIDC",
|
||||
"oidcRole": "Rol OIDC",
|
||||
"sshMount": "Montaje de secretos SSH",
|
||||
"sshRole": "Rol de firmante SSH",
|
||||
"validPrincipals": "Principals válidos",
|
||||
"keyType": "Tipo de clave efímera",
|
||||
"shareWithAll": "Compartir con todos los usuarios",
|
||||
"create": "Crear perfil",
|
||||
"created": "Perfil de Vault creado",
|
||||
"saved": "Perfil de Vault guardado",
|
||||
"deleted": "Perfil de Vault eliminado",
|
||||
"saveFailed": "Error al guardar el perfil de Vault",
|
||||
"deleteFailed": "Error al eliminar el perfil de Vault",
|
||||
"save": "Guardar perfil",
|
||||
"validationError": "Se requieren el nombre, la dirección de Vault y el rol de firmante SSH",
|
||||
"none": "Aún no hay perfiles de Vault.",
|
||||
"sharedBadge": "compartido"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Inicio de sesión en Vault requerido",
|
||||
"description": "Se ha abierto una ventana para iniciar sesión en HashiCorp Vault. Completa el inicio de sesión allí; esta conexión continuará automáticamente.",
|
||||
"failed": "La autenticación de Vault falló. Inténtalo de nuevo.",
|
||||
"reopen": "Reabrir ventana de inicio de sesión"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Varaston allekirjoittajan profiili",
|
||||
"selectProfile": "Valitse holviprofiili...",
|
||||
"hint": "Asetukset tulevat jaetusta profiilista; kirjaudut Holviin OIDC:n kautta, kun muodostat yhteyden. Salaisuuksia ei tallenneta.",
|
||||
"docs": "Näytä dokumentit"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Uusi profiili",
|
||||
"manage": "Hallinnoi holviprofiileja",
|
||||
"name": "Ystävällinen nimi",
|
||||
"vaultAddr": "Holvin osoite",
|
||||
"namespace": "Nimiavaruus",
|
||||
"oidcMount": "OIDC-todennusliitos",
|
||||
"oidcRole": "OIDC-rooli",
|
||||
"sshMount": "SSH-salaisuuksien kiinnitys",
|
||||
"sshRole": "SSH-allekirjoittajan rooli",
|
||||
"validPrincipals": "Voimassa olevat pääasialliset",
|
||||
"keyType": "Lyhytaikainen avaintyyppi",
|
||||
"shareWithAll": "Jaa kaikkien käyttäjien kanssa",
|
||||
"create": "Luo profiili",
|
||||
"created": "Holvin profiili luotu",
|
||||
"saved": "Holvin profiili tallennettu",
|
||||
"deleted": "Holvin profiili poistettu",
|
||||
"saveFailed": "Holvin profiilin tallentaminen epäonnistui",
|
||||
"deleteFailed": "Holviprofiilin poistaminen epäonnistui",
|
||||
"save": "Tallenna profiili",
|
||||
"validationError": "Nimi, holvin osoite ja SSH-allekirjoittajan rooli vaaditaan",
|
||||
"none": "Ei vielä holviprofiileja.",
|
||||
"sharedBadge": "jaettu"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Holviin kirjautuminen vaaditaan",
|
||||
"description": "Ikkuna HashiCorp-holviin kirjautumista varten on avautunut. Kirjaudu sisään siinä. Yhteys jatkuu automaattisesti.",
|
||||
"failed": "Holvin todennus epäonnistui. Yritä uudelleen.",
|
||||
"reopen": "Avaa kirjautumisikkuna uudelleen"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Profil de signature Vault",
|
||||
"selectProfile": "Sélectionner un profil Vault...",
|
||||
"hint": "Les paramètres proviennent du profil partagé ; vous vous connecterez à Vault via OIDC lors de la connexion. Aucun secret n'est stocké.",
|
||||
"docs": "Voir la documentation"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Nouveau profil",
|
||||
"manage": "Gérer les profils Vault",
|
||||
"name": "Nom convivial",
|
||||
"vaultAddr": "Adresse Vault",
|
||||
"namespace": "Espace de noms",
|
||||
"oidcMount": "Point de montage d'authentification OIDC",
|
||||
"oidcRole": "Rôle OIDC",
|
||||
"sshMount": "Point de montage des secrets SSH",
|
||||
"sshRole": "Rôle de signature SSH",
|
||||
"validPrincipals": "Principaux valides",
|
||||
"keyType": "Type de clé éphémère",
|
||||
"shareWithAll": "Partager avec tous les utilisateurs",
|
||||
"create": "Créer un profil",
|
||||
"created": "Profil Vault créé",
|
||||
"saved": "Profil Vault enregistré",
|
||||
"deleted": "Profil Vault supprimé",
|
||||
"saveFailed": "Échec de l'enregistrement du profil Vault",
|
||||
"deleteFailed": "Échec de la suppression du profil Vault",
|
||||
"save": "Enregistrer le profil",
|
||||
"validationError": "Le nom, l'adresse Vault et le rôle de signataire SSH sont requis",
|
||||
"none": "Aucun profil Vault pour le moment",
|
||||
"sharedBadge": "partagé"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Connexion à Vault requise",
|
||||
"description": "Une fenêtre s'est ouverte pour vous connecter à HashiCorp Vault. Terminez la connexion dans cette fenêtre ; cette connexion continuera automatiquement.",
|
||||
"failed": "Échec de l'authentification Vault. Veuillez réessayer.",
|
||||
"reopen": "Rouvrir la fenêtre de connexion"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "פרופיל חותם בכספת",
|
||||
"selectProfile": "בחר פרופיל כספת...",
|
||||
"hint": "ההגדרות מגיעות מהפרופיל המשותף; תיכנס לכספת דרך OIDC כשתתחבר. לא נשמרים סודות.",
|
||||
"docs": "הצג מסמכים"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "פרופיל חדש",
|
||||
"manage": "ניהול פרופילי כספת",
|
||||
"name": "שם ידידותי",
|
||||
"vaultAddr": "כתובת הכספת",
|
||||
"namespace": "מרחב השמות",
|
||||
"oidcMount": "אישור OIDC",
|
||||
"oidcRole": "תפקיד OIDC",
|
||||
"sshMount": "סודות SSH מאוורר",
|
||||
"sshRole": "תפקיד חותם SSH",
|
||||
"validPrincipals": "עקרונות תקפים",
|
||||
"keyType": "סוג מפתח זמני",
|
||||
"shareWithAll": "שתף עם כל המשתמשים",
|
||||
"create": "צור פרופיל",
|
||||
"created": "פרופיל הכספת נוצר",
|
||||
"saved": "פרופיל הכספת נשמר",
|
||||
"deleted": "פרופיל הכספת נמחק",
|
||||
"saveFailed": "שמירת פרופיל הכספת נכשלה",
|
||||
"deleteFailed": "נכשלה מחיקת פרופיל הכספת",
|
||||
"save": "שמור פרופיל",
|
||||
"validationError": "שם, כתובת כספת ותפקיד חתימה ב-SSH נדרשים",
|
||||
"none": "אין עדיין פרופילים בכספת.",
|
||||
"sharedBadge": "מְשׁוּתָף"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "נדרשת כניסה לכספת",
|
||||
"description": "נפתח חלון לכניסה לכספת HashiCorp. השלם את תהליך הכניסה שם; חיבור זה ימשיך אוטומטית.",
|
||||
"failed": "אימות הכספת נכשל. אנא נסה שוב.",
|
||||
"reopen": "פתח מחדש את חלון הכניסה"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Vault साइनर प्रोफ़ाइल",
|
||||
"selectProfile": "एक Vault प्रोफ़ाइल चुनें...",
|
||||
"hint": "सेटिंग साझा प्रोफ़ाइल से आती हैं; कनेक्ट करते समय आप OIDC के माध्यम से Vault में साइन इन करेंगे। कोई सीक्रेट संग्रहीत नहीं किए जाते।",
|
||||
"docs": "दस्तावेज़ देखें"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "नई प्रोफ़ाइल",
|
||||
"manage": "Vault प्रोफ़ाइल प्रबंधित करें",
|
||||
"name": "अनुकूल नाम",
|
||||
"vaultAddr": "Vault पता",
|
||||
"namespace": "नेमस्पेस",
|
||||
"oidcMount": "OIDC ऑथ माउंट",
|
||||
"oidcRole": "OIDC रोल",
|
||||
"sshMount": "SSH सीक्रेट माउंट",
|
||||
"sshRole": "SSH साइनर रोल",
|
||||
"validPrincipals": "मान्य प्रिंसिपल",
|
||||
"keyType": "एफेमरल कुंजी प्रकार",
|
||||
"shareWithAll": "सभी उपयोगकर्ताओं के साथ साझा करें",
|
||||
"create": "प्रोफ़ाइल बनाएँ",
|
||||
"created": "Vault प्रोफ़ाइल बनाई गई",
|
||||
"saved": "Vault प्रोफ़ाइल सहेजी गई",
|
||||
"deleted": "Vault प्रोफ़ाइल हटाई गई",
|
||||
"saveFailed": "Vault प्रोफ़ाइल सहेजने में विफल",
|
||||
"deleteFailed": "Vault प्रोफ़ाइल हटाने में विफल",
|
||||
"save": "प्रोफ़ाइल सहेजें",
|
||||
"validationError": "नाम, Vault पता और SSH हस्ताक्षरकर्ता भूमिका आवश्यक हैं",
|
||||
"none": "अभी तक कोई Vault प्रोफ़ाइल नहीं है।",
|
||||
"sharedBadge": "साझा"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Vault साइन-इन आवश्यक है",
|
||||
"description": "HashiCorp Vault में साइन इन करने के लिए एक विंडो खुल गई है। वहाँ साइन-इन पूरा करें; यह कनेक्शन स्वचालित रूप से जारी रहेगा।",
|
||||
"failed": "Vault प्रमाणीकरण विफल। कृपया पुनः प्रयास करें।",
|
||||
"reopen": "साइन-इन विंडो फिर से खोलें"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Vault aláírói profil",
|
||||
"selectProfile": "Válasszon ki egy Vault profilt...",
|
||||
"hint": "A beállítások a megosztott profilból származnak; a csatlakozáskor OIDC-n keresztül fog bejelentkezni a Vaultba. A rendszer nem tárol titkos adatokat.",
|
||||
"docs": "Dokumentumok megtekintése"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Új profil",
|
||||
"manage": "Vault profilok kezelése",
|
||||
"name": "Barátságos név",
|
||||
"vaultAddr": "Trezor címe",
|
||||
"namespace": "Névtér",
|
||||
"oidcMount": "OIDC hitelesítési csatolás",
|
||||
"oidcRole": "OIDC szerepkör",
|
||||
"sshMount": "SSH titkok felhúzása",
|
||||
"sshRole": "SSH aláíró szerepkör",
|
||||
"validPrincipals": "Érvényes megbízók",
|
||||
"keyType": "Efemer kulcstípus",
|
||||
"shareWithAll": "Megosztás az összes felhasználóval",
|
||||
"create": "Profil létrehozása",
|
||||
"created": "Létrehozva a trezor profilja",
|
||||
"saved": "A trezor profilja mentve",
|
||||
"deleted": "Tárolóprofil törölve",
|
||||
"saveFailed": "Nem sikerült menteni a Vault profilját",
|
||||
"deleteFailed": "Nem sikerült törölni a Vault profilját",
|
||||
"save": "Profil mentése",
|
||||
"validationError": "Név, trezor címe és SSH aláírói szerepkör megadása kötelező.",
|
||||
"none": "Még nincsenek Vault-profilok.",
|
||||
"sharedBadge": "megosztott"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Bejelentkezés szükséges a széfbe",
|
||||
"description": "Megnyílt egy ablak a HashiCorp Vaultba való bejelentkezéshez. Fejezze be a bejelentkezést ott; a kapcsolat automatikusan folytatódik.",
|
||||
"failed": "A trezor hitelesítése sikertelen. Próbálja újra.",
|
||||
"reopen": "Bejelentkezési ablak újranyitása"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Profil Penandatangan Vault",
|
||||
"selectProfile": "Pilih profil Vault...",
|
||||
"hint": "Pengaturan berasal dari profil bersama; Anda akan masuk ke Vault melalui OIDC saat terhubung. Tidak ada rahasia yang disimpan.",
|
||||
"docs": "Lihat dokumen"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Profil baru",
|
||||
"manage": "Kelola profil Vault",
|
||||
"name": "Nama yang Ramah",
|
||||
"vaultAddr": "Alamat Brankas",
|
||||
"namespace": "Ruang nama",
|
||||
"oidcMount": "OIDC Otorisasi Pemasangan",
|
||||
"oidcRole": "Peran OIDC",
|
||||
"sshMount": "Pemasangan Rahasia SSH",
|
||||
"sshRole": "Peran Penandatangan SSH",
|
||||
"validPrincipals": "Prinsip-prinsip yang Sah",
|
||||
"keyType": "Jenis Kunci Sementara",
|
||||
"shareWithAll": "Bagikan dengan semua pengguna",
|
||||
"create": "Buat profil",
|
||||
"created": "Profil Vault telah dibuat.",
|
||||
"saved": "Profil Vault tersimpan.",
|
||||
"deleted": "Profil Vault dihapus",
|
||||
"saveFailed": "Gagal menyimpan profil Vault",
|
||||
"deleteFailed": "Gagal menghapus profil Vault",
|
||||
"save": "Simpan profil",
|
||||
"validationError": "Nama, alamat Vault, dan peran penandatangan SSH wajib diisi.",
|
||||
"none": "Belum ada profil Vault.",
|
||||
"sharedBadge": "dibagikan"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Login ke Vault diperlukan.",
|
||||
"description": "Jendela telah terbuka untuk masuk ke HashiCorp Vault. Selesaikan proses masuk di sana; koneksi ini akan berlanjut secara otomatis.",
|
||||
"failed": "Autentikasi Vault gagal. Silakan coba lagi.",
|
||||
"reopen": "Buka kembali jendela masuk"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Profilo firmatario Vault",
|
||||
"selectProfile": "Seleziona un profilo Vault...",
|
||||
"hint": "Le impostazioni provengono dal profilo condiviso; effettuerai l'accesso a Vault tramite OIDC quando ti connetti. Nessun segreto è memorizzato.",
|
||||
"docs": "Visualizza documentazione"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Nuovo profilo",
|
||||
"manage": "Gestisci profili Vault",
|
||||
"name": "Nome descrittivo",
|
||||
"vaultAddr": "Indirizzo Vault",
|
||||
"namespace": "Namespace",
|
||||
"oidcMount": "Mount di autenticazione OIDC",
|
||||
"oidcRole": "Ruolo OIDC",
|
||||
"sshMount": "Mount dei segreti SSH",
|
||||
"sshRole": "Ruolo firmatario SSH",
|
||||
"validPrincipals": "Principal validi",
|
||||
"keyType": "Tipo di chiave effimera",
|
||||
"shareWithAll": "Condividi con tutti gli utenti",
|
||||
"create": "Crea profilo",
|
||||
"created": "Profilo Vault creato",
|
||||
"saved": "Profilo Vault salvato",
|
||||
"deleted": "Profilo Vault eliminato",
|
||||
"saveFailed": "Salvataggio profilo Vault non riuscito",
|
||||
"deleteFailed": "Eliminazione profilo Vault non riuscita",
|
||||
"save": "Salva profilo",
|
||||
"validationError": "Nome, indirizzo Vault e ruolo di firma SSH sono obbligatori",
|
||||
"none": "Nessun profilo Vault ancora.",
|
||||
"sharedBadge": "condiviso"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Accesso a Vault richiesto",
|
||||
"description": "Una finestra si è aperta per accedere a HashiCorp Vault. Completa l'accesso lì; la connessione continuerà automaticamente.",
|
||||
"failed": "Autenticazione Vault fallita. Riprova.",
|
||||
"reopen": "Riapri la finestra di accesso"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Vault 署名者プロファイル",
|
||||
"selectProfile": "Vault プロファイルを選択...",
|
||||
"hint": "設定は共有プロファイルから取得され、接続時に OIDC で Vault にサインインします。シークレットは保存されません。",
|
||||
"docs": "ドキュメントを表示"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "新規プロファイル",
|
||||
"manage": "Vault プロファイルの管理",
|
||||
"name": "表示名",
|
||||
"vaultAddr": "Vault アドレス",
|
||||
"namespace": "名前空間",
|
||||
"oidcMount": "OIDC 認証マウント",
|
||||
"oidcRole": "OIDC ロール",
|
||||
"sshMount": "SSH シークレットマウント",
|
||||
"sshRole": "SSH 署名者ロール",
|
||||
"validPrincipals": "有効なプリンシパル",
|
||||
"keyType": "エフェメラルキータイプ",
|
||||
"shareWithAll": "すべてのユーザーと共有",
|
||||
"create": "プロファイルを作成",
|
||||
"created": "Vaultプロファイルが作成されました",
|
||||
"saved": "Vaultプロファイルが保存されました",
|
||||
"deleted": "Vaultプロファイルが削除されました",
|
||||
"saveFailed": "Vaultプロファイルの保存に失敗しました",
|
||||
"deleteFailed": "Vaultプロファイルの削除に失敗しました",
|
||||
"save": "プロファイルを保存",
|
||||
"validationError": "名前、Vaultアドレス、SSH署名ロールは必須です",
|
||||
"none": "まだVaultプロファイルがありません。",
|
||||
"sharedBadge": "共有"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Vault サインインが必要です",
|
||||
"description": "HashiCorp Vaultへのサインイン用のウィンドウが開きました。そこでサインインを完了すると、この接続は自動的に続行されます。",
|
||||
"failed": "Vault 認証に失敗しました。もう一度お試しください。",
|
||||
"reopen": "サインインウィンドウを再表示"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Vault 서명자 프로필",
|
||||
"selectProfile": "Vault 프로필 선택...",
|
||||
"hint": "설정은 공유 프로필에서 가져옵니다. 연결 시 OIDC를 통해 Vault에 로그인하게 됩니다. 비밀은 저장되지 않습니다.",
|
||||
"docs": "문서 보기"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "새 프로필",
|
||||
"manage": "Vault 프로필 관리",
|
||||
"name": "별칭",
|
||||
"vaultAddr": "Vault 주소",
|
||||
"namespace": "네임스페이스",
|
||||
"oidcMount": "OIDC 인증 마운트",
|
||||
"oidcRole": "OIDC 역할",
|
||||
"sshMount": "SSH 시크릿 마운트",
|
||||
"sshRole": "SSH 서명자 역할",
|
||||
"validPrincipals": "유효한 프린시펄",
|
||||
"keyType": "임시 키 유형",
|
||||
"shareWithAll": "모든 사용자와 공유",
|
||||
"create": "프로필 생성",
|
||||
"created": "Vault 프로필 생성됨",
|
||||
"saved": "Vault 프로필 저장됨",
|
||||
"deleted": "Vault 프로필 삭제됨",
|
||||
"saveFailed": "Vault 프로필 저장 실패",
|
||||
"deleteFailed": "Vault 프로필 삭제 실패",
|
||||
"save": "프로필 저장",
|
||||
"validationError": "이름, Vault 주소 및 SSH 서명자 역할이 필요합니다.",
|
||||
"none": "아직 Vault 프로필이 없습니다.",
|
||||
"sharedBadge": "공유"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Vault 로그인 필요",
|
||||
"description": "HashiCorp Vault 로그인을 위한 창이 열렸습니다. 해당 창에서 로그인을 완료하면 연결이 자동으로 계속됩니다.",
|
||||
"failed": "Vault 인증에 실패했습니다. 다시 시도하세요.",
|
||||
"reopen": "로그인 창 다시 열기"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Vault-ondertekenaarprofiel",
|
||||
"selectProfile": "Selecteer een Vault-profiel...",
|
||||
"hint": "De instellingen zijn afkomstig van het gedeelde profiel; u meldt zich bij Vault aan via OIDC wanneer u verbinding maakt. Er worden geen geheimen opgeslagen.",
|
||||
"docs": "Documenten bekijken"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Nieuw profiel",
|
||||
"manage": "Beheer Vault-profielen",
|
||||
"name": "Vriendelijke naam",
|
||||
"vaultAddr": "Kluisadres",
|
||||
"namespace": "Naamruimte",
|
||||
"oidcMount": "OIDC-authenticatiekoppeling",
|
||||
"oidcRole": "OIDC-rol",
|
||||
"sshMount": "SSH-geheimen Mount",
|
||||
"sshRole": "SSH-ondertekenaarrol",
|
||||
"validPrincipals": "Geldige principes",
|
||||
"keyType": "Tijdelijk sleuteltype",
|
||||
"shareWithAll": "Delen met alle gebruikers",
|
||||
"create": "Profiel aanmaken",
|
||||
"created": "Vault-profiel aangemaakt",
|
||||
"saved": "Vault-profiel opgeslagen",
|
||||
"deleted": "Kluisprofiel verwijderd",
|
||||
"saveFailed": "Het opslaan van het Vault-profiel is mislukt.",
|
||||
"deleteFailed": "Het verwijderen van het Vault-profiel is mislukt.",
|
||||
"save": "Profiel opslaan",
|
||||
"validationError": "Naam, kluisadres en SSH-ondertekeningsrol zijn vereist.",
|
||||
"none": "Nog geen Vault-profielen beschikbaar.",
|
||||
"sharedBadge": "gedeeld"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Aanmelden bij Vault is vereist.",
|
||||
"description": "Er is een venster geopend om in te loggen bij HashiCorp Vault. Voltooi de aanmelding daar; deze verbinding wordt automatisch tot stand gebracht.",
|
||||
"failed": "Authenticatie voor de kluis is mislukt. Probeer het opnieuw.",
|
||||
"reopen": "Heropen het aanmeldvenster"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Profil for hvelvsigner",
|
||||
"selectProfile": "Velg en Vault-profil...",
|
||||
"hint": "Innstillingene kommer fra den delte profilen. Du logger deg på Vault via OIDC når du kobler til. Ingen hemmeligheter lagres.",
|
||||
"docs": "Vis dokumenter"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Ny profil",
|
||||
"manage": "Administrer Vault-profiler",
|
||||
"name": "Vennlig navn",
|
||||
"vaultAddr": "Hvelvadresse",
|
||||
"namespace": "Navnerom",
|
||||
"oidcMount": "OIDC-autentiseringsmontering",
|
||||
"oidcRole": "OIDC-rolle",
|
||||
"sshMount": "SSH-hemmeligheter monteres",
|
||||
"sshRole": "SSH-signatørrolle",
|
||||
"validPrincipals": "Gyldige prinsipaler",
|
||||
"keyType": "Kortvarig nøkkeltype",
|
||||
"shareWithAll": "Del med alle brukere",
|
||||
"create": "Opprett profil",
|
||||
"created": "Vault-profil opprettet",
|
||||
"saved": "Vault-profilen er lagret",
|
||||
"deleted": "Vault-profilen er slettet",
|
||||
"saveFailed": "Kunne ikke lagre Vault-profilen",
|
||||
"deleteFailed": "Kunne ikke slette Vault-profilen",
|
||||
"save": "Lagre profil",
|
||||
"validationError": "Navn, hvelvadresse og SSH-signerrolle er obligatorisk",
|
||||
"none": "Ingen Vault-profiler ennå.",
|
||||
"sharedBadge": "delt"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Pålogging til arkivet kreves",
|
||||
"description": "Et vindu har åpnet seg for å logge på HashiCorp Vault. Fullfør påloggingen der; denne tilkoblingen vil fortsette automatisk.",
|
||||
"failed": "Hvelv-autentisering mislyktes. Prøv på nytt.",
|
||||
"reopen": "Åpne påloggingsvinduet på nytt"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Profil podpisujący Vault",
|
||||
"selectProfile": "Wybierz profil Vault...",
|
||||
"hint": "Ustawienia pochodzą z profilu współdzielonego. Po połączeniu zalogujesz się do Vault przez OIDC. Nie są przechowywane żadne sekrety.",
|
||||
"docs": "Zobacz dokumentację"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Nowy profil",
|
||||
"manage": "Zarządzaj profilami Vault",
|
||||
"name": "Przyjazna nazwa",
|
||||
"vaultAddr": "Adres Vault",
|
||||
"namespace": "Przestrzeń nazw",
|
||||
"oidcMount": "Mount uwierzytelniania OIDC",
|
||||
"oidcRole": "Rola OIDC",
|
||||
"sshMount": "Mount sekretów SSH",
|
||||
"sshRole": "Rola podpisująca SSH",
|
||||
"validPrincipals": "Dozwoleni użytkownicy (principals)",
|
||||
"keyType": "Typ klucza efemerycznego",
|
||||
"shareWithAll": "Udostępnij wszystkim użytkownikom",
|
||||
"create": "Utwórz profil",
|
||||
"created": "Utworzono profil Vault",
|
||||
"saved": "Zapisano profil Vault",
|
||||
"deleted": "Usunięto profil Vault",
|
||||
"saveFailed": "Nie udało się zapisać profilu Vault",
|
||||
"deleteFailed": "Nie udało się usunąć profilu Vault",
|
||||
"save": "Zapisz profil",
|
||||
"validationError": "Wymagane są nazwa, adres Vault i rola podpisująca SSH",
|
||||
"none": "Brak profili Vault.",
|
||||
"sharedBadge": "współdzielony"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Wymagane logowanie do Vault",
|
||||
"description": "Otworzyło się okno logowania do HashiCorp Vault. Dokończ logowanie, a połączenie zostanie nawiązane automatycznie.",
|
||||
"failed": "Uwierzytelnienie w Vault nie powiodło się. Spróbuj ponownie.",
|
||||
"reopen": "Otwórz ponownie okno logowania"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Perfil do assinante do Vault",
|
||||
"selectProfile": "Selecione um perfil do Vault...",
|
||||
"hint": "As configurações são provenientes do perfil compartilhado; você fará login no Vault via OIDC ao se conectar. Nenhum segredo é armazenado.",
|
||||
"docs": "Ver documentos"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Novo perfil",
|
||||
"manage": "Gerenciar perfis do Vault",
|
||||
"name": "Nome amigável",
|
||||
"vaultAddr": "Endereço do cofre",
|
||||
"namespace": "Espaço de nomes",
|
||||
"oidcMount": "Montagem de autenticação OIDC",
|
||||
"oidcRole": "Função OIDC",
|
||||
"sshMount": "Montagem de segredos SSH",
|
||||
"sshRole": "Função de assinatura SSH",
|
||||
"validPrincipals": "Principais válidos",
|
||||
"keyType": "Tipo de chave efêmera",
|
||||
"shareWithAll": "Compartilhe com todos os usuários",
|
||||
"create": "Criar perfil",
|
||||
"created": "Perfil do Vault criado",
|
||||
"saved": "Perfil do Vault salvo",
|
||||
"deleted": "Perfil do Vault excluído",
|
||||
"saveFailed": "Falha ao salvar o perfil do Vault",
|
||||
"deleteFailed": "Falha ao excluir o perfil do Vault",
|
||||
"save": "Salvar perfil",
|
||||
"validationError": "Nome, endereço do Vault e função de assinatura SSH são obrigatórios.",
|
||||
"none": "Ainda não há perfis no Vault.",
|
||||
"sharedBadge": "compartilhado"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "É necessário fazer login no cofre.",
|
||||
"description": "Uma janela foi aberta para fazer login no HashiCorp Vault. Conclua o login; esta conexão será estabelecida automaticamente.",
|
||||
"failed": "A autenticação do cofre falhou. Tente novamente.",
|
||||
"reopen": "Reabrir janela de login"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Perfil de assinatura Vault",
|
||||
"selectProfile": "Selecionar um perfil Vault...",
|
||||
"hint": "As definições provêm do perfil partilhado; ao ligar, iniciará sessão no Vault via OIDC. Nenhum segredo é armazenado.",
|
||||
"docs": "Ver documentação"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Novo perfil",
|
||||
"manage": "Gerir perfis Vault",
|
||||
"name": "Nome Amigável",
|
||||
"vaultAddr": "Endereço Vault",
|
||||
"namespace": "Namespace",
|
||||
"oidcMount": "Mount de autenticação OIDC",
|
||||
"oidcRole": "Função OIDC",
|
||||
"sshMount": "Mount de segredos SSH",
|
||||
"sshRole": "Função de assinatura SSH",
|
||||
"validPrincipals": "Principais válidos",
|
||||
"keyType": "Tipo de chave efémera",
|
||||
"shareWithAll": "Partilhar com todos os utilizadores",
|
||||
"create": "Criar perfil",
|
||||
"created": "Perfil Vault criado",
|
||||
"saved": "Perfil Vault guardado",
|
||||
"deleted": "Perfil Vault eliminado",
|
||||
"saveFailed": "Falha ao guardar perfil Vault",
|
||||
"deleteFailed": "Falha ao eliminar perfil Vault",
|
||||
"save": "Guardar perfil",
|
||||
"validationError": "Nome, endereço Vault e função de signatário SSH são obrigatórios",
|
||||
"none": "Ainda sem perfis Vault.",
|
||||
"sharedBadge": "partilhado"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Início de sessão no Vault necessário",
|
||||
"description": "Foi aberta uma janela para iniciar sessão no HashiCorp Vault. Conclua o início de sessão aí; esta ligação continuará automaticamente.",
|
||||
"failed": "A autenticação no Vault falhou. Tente novamente.",
|
||||
"reopen": "Reabrir janela de início de sessão"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Profilul semnatarului din seif",
|
||||
"selectProfile": "Selectați un profil Vault...",
|
||||
"hint": "Setările provin din profilul partajat; te vei conecta la Vault prin OIDC când te conectezi. Nu sunt stocate secrete.",
|
||||
"docs": "Vizualizați documentele"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Profil nou",
|
||||
"manage": "Gestionați profilurile din seif",
|
||||
"name": "Nume prietenos",
|
||||
"vaultAddr": "Adresă seif",
|
||||
"namespace": "Spațiu de nume",
|
||||
"oidcMount": "Montare autentificare OIDC",
|
||||
"oidcRole": "Rolul OIDC",
|
||||
"sshMount": "Secretele SSH Mount",
|
||||
"sshRole": "Rolul semnatarului SSH",
|
||||
"validPrincipals": "Principali validi",
|
||||
"keyType": "Tip de cheie efemeră",
|
||||
"shareWithAll": "Partajați cu toți utilizatorii",
|
||||
"create": "Creați un profil",
|
||||
"created": "Profilul seifului a fost creat",
|
||||
"saved": "Profilul seifului a fost salvat",
|
||||
"deleted": "Profilul seifului a fost șters",
|
||||
"saveFailed": "Nu s-a putut salva profilul din Seif",
|
||||
"deleteFailed": "Nu s-a putut șterge profilul Vault",
|
||||
"save": "Salvați profilul",
|
||||
"validationError": "Numele, adresa seifului și rolul semnatarului SSH sunt obligatorii.",
|
||||
"none": "Încă nu există profiluri Vault.",
|
||||
"sharedBadge": "partajat"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Este necesară conectarea la seif",
|
||||
"description": "S-a deschis o fereastră pentru a vă conecta la HashiCorp Vault. Finalizați conectarea acolo; această conexiune va continua automat.",
|
||||
"failed": "Autentificarea în seif a eșuat. Vă rugăm să încercați din nou.",
|
||||
"reopen": "Redeschideți fereastra de conectare"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Профиль подписчика Vault",
|
||||
"selectProfile": "Выберите профиль Vault...",
|
||||
"hint": "Настройки берутся из общего профиля; при подключении вы войдёте в Vault через OIDC. Секреты не хранятся.",
|
||||
"docs": "Документация"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Новый профиль",
|
||||
"manage": "Управление профилями Vault",
|
||||
"name": "Дружественное имя",
|
||||
"vaultAddr": "Адрес Vault",
|
||||
"namespace": "Пространство имён",
|
||||
"oidcMount": "Монтирование аутентификации OIDC",
|
||||
"oidcRole": "Роль OIDC",
|
||||
"sshMount": "Монтирование секретов SSH",
|
||||
"sshRole": "Роль подписчика SSH",
|
||||
"validPrincipals": "Допустимые принципалы",
|
||||
"keyType": "Тип эфемерного ключа",
|
||||
"shareWithAll": "Общий доступ для всех пользователей",
|
||||
"create": "Создать профиль",
|
||||
"created": "Профиль Vault создан",
|
||||
"saved": "Профиль Vault сохранён",
|
||||
"deleted": "Профиль Vault удалён",
|
||||
"saveFailed": "Не удалось сохранить профиль Vault",
|
||||
"deleteFailed": "Не удалось удалить профиль Vault",
|
||||
"save": "Сохранить профиль",
|
||||
"validationError": "Требуются имя, адрес Vault и роль SSH подписанта",
|
||||
"none": "Пока нет профилей Vault.",
|
||||
"sharedBadge": "общий"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Требуется вход в Vault",
|
||||
"description": "Открыто окно для входа в HashiCorp Vault. Завершите вход там; подключение продолжится автоматически.",
|
||||
"failed": "Аутентификация Vault не удалась. Попробуйте снова.",
|
||||
"reopen": "Повторно открыть окно входа"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Профил потписника трезора",
|
||||
"selectProfile": "Изаберите профил трезора...",
|
||||
"hint": "Подешавања долазе из дељеног профила; пријавићете се у Vault преко OIDC-а када се повежете. Ниједне тајне се не чувају.",
|
||||
"docs": "Прикажи документе"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Нови профил",
|
||||
"manage": "Управљање профилима трезора",
|
||||
"name": "Пријатељско име",
|
||||
"vaultAddr": "Адреса трезора",
|
||||
"namespace": "Именски простор",
|
||||
"oidcMount": "OIDC аутентификација монтирања",
|
||||
"oidcRole": "Улога OIDC-а",
|
||||
"sshMount": "SSH тајне носач",
|
||||
"sshRole": "Улога SSH потписника",
|
||||
"validPrincipals": "Важећи принципали",
|
||||
"keyType": "Тип ефемерног кључа",
|
||||
"shareWithAll": "Подели са свим корисницима",
|
||||
"create": "Направи профил",
|
||||
"created": "Профил трезора је креиран",
|
||||
"saved": "Профил трезора је сачуван",
|
||||
"deleted": "Профил трезора је обрисан",
|
||||
"saveFailed": "Чување профила у трезору није успело",
|
||||
"deleteFailed": "Брисање профила у Трезору није успело",
|
||||
"save": "Сачувај профил",
|
||||
"validationError": "Име, адреса трезора и улога SSH потписника су обавезни",
|
||||
"none": "Још нема профила у Трезору.",
|
||||
"sharedBadge": "дељено"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Потребно је пријављивање у трезор",
|
||||
"description": "Отворио се прозор за пријављивање на HashiCorp Vault. Завршите пријављивање тамо; ова веза ће се аутоматски наставити.",
|
||||
"failed": "Аутентификација трезора није успела. Молимо покушајте поново.",
|
||||
"reopen": "Поново отвори прозор за пријављивање"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Profil för valvsignerare",
|
||||
"selectProfile": "Välj en Vault-profil...",
|
||||
"hint": "Inställningarna kommer från den delade profilen; du loggar in på Vault via OIDC när du ansluter. Inga hemligheter lagras.",
|
||||
"docs": "Visa dokument"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Ny profil",
|
||||
"manage": "Hantera Vault-profiler",
|
||||
"name": "Vänligt namn",
|
||||
"vaultAddr": "Valvadress",
|
||||
"namespace": "Namnrymd",
|
||||
"oidcMount": "OIDC-autentiseringsmontering",
|
||||
"oidcRole": "OIDC-roll",
|
||||
"sshMount": "SSH-hemligheter monteras",
|
||||
"sshRole": "SSH-signerarroll",
|
||||
"validPrincipals": "Giltiga principer",
|
||||
"keyType": "Tillfällig nyckeltyp",
|
||||
"shareWithAll": "Dela med alla användare",
|
||||
"create": "Skapa profil",
|
||||
"created": "Arkivprofil skapad",
|
||||
"saved": "Arkivprofilen har sparats",
|
||||
"deleted": "Arkivprofil raderad",
|
||||
"saveFailed": "Kunde inte spara arkivprofilen",
|
||||
"deleteFailed": "Misslyckades med att ta bort Arkiv-profilen",
|
||||
"save": "Spara profil",
|
||||
"validationError": "Namn, valvadress och SSH-signerarroll krävs",
|
||||
"none": "Inga Vault-profiler ännu.",
|
||||
"sharedBadge": "delad"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Inloggning till arkivet krävs",
|
||||
"description": "Ett fönster har öppnats för att logga in på HashiCorp Vault. Slutför inloggningen där; anslutningen fortsätter automatiskt.",
|
||||
"failed": "Valvautentisering misslyckades. Försök igen.",
|
||||
"reopen": "Öppna inloggningsfönstret igen"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "โปรไฟล์ผู้ลงนาม Vault",
|
||||
"selectProfile": "เลือกโปรไฟล์ Vault...",
|
||||
"hint": "การตั้งค่าต่างๆ มาจากโปรไฟล์ที่ใช้ร่วมกัน คุณจะเข้าสู่ระบบ Vault ผ่าน OIDC เมื่อเชื่อมต่อ ไม่มีข้อมูลลับใดๆ ถูกจัดเก็บไว้",
|
||||
"docs": "ดูเอกสาร"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "โปรไฟล์ใหม่",
|
||||
"manage": "จัดการโปรไฟล์ Vault",
|
||||
"name": "ชื่อที่เป็นมิตร",
|
||||
"vaultAddr": "ที่อยู่ห้องนิรภัย",
|
||||
"namespace": "เนมสเปซ",
|
||||
"oidcMount": "OIDC Auth Mount",
|
||||
"oidcRole": "บทบาทของ OIDC",
|
||||
"sshMount": "SSH Secrets Mount",
|
||||
"sshRole": "บทบาทผู้ลงนาม SSH",
|
||||
"validPrincipals": "หลักการที่ถูกต้อง",
|
||||
"keyType": "ประเภทคีย์ชั่วคราว",
|
||||
"shareWithAll": "แชร์กับผู้ใช้ทั้งหมด",
|
||||
"create": "สร้างโปรไฟล์",
|
||||
"created": "สร้างโปรไฟล์ Vault แล้ว",
|
||||
"saved": "บันทึกโปรไฟล์ Vault แล้ว",
|
||||
"deleted": "โปรไฟล์ Vault ถูกลบแล้ว",
|
||||
"saveFailed": "ไม่สามารถบันทึกโปรไฟล์ Vault ได้",
|
||||
"deleteFailed": "ไม่สามารถลบโปรไฟล์ Vault ได้",
|
||||
"save": "บันทึกโปรไฟล์",
|
||||
"validationError": "จำเป็นต้องระบุชื่อ ที่อยู่ Vault และบทบาทผู้ลงนาม SSH",
|
||||
"none": "ยังไม่มีโปรไฟล์ Vault ใดๆ",
|
||||
"sharedBadge": "แชร์"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "ต้องลงชื่อเข้าใช้ Vault",
|
||||
"description": "หน้าต่างสำหรับเข้าสู่ระบบ HashiCorp Vault ได้เปิดขึ้นแล้ว โปรดทำการเข้าสู่ระบบให้เสร็จสมบูรณ์ การเชื่อมต่อนี้จะดำเนินการต่อโดยอัตโนมัติ",
|
||||
"failed": "การตรวจสอบสิทธิ์ Vault ล้มเหลว โปรดลองอีกครั้ง",
|
||||
"reopen": "เปิดหน้าต่างเข้าสู่ระบบอีกครั้ง"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Vault İmzalayıcı Profili",
|
||||
"selectProfile": "Bir Vault profili seçin...",
|
||||
"hint": "Ayarlar paylaşılan profilden gelir; bağlandığınızda OIDC ile Vault'ta oturum açarsınız. Hiçbir gizli bilgi saklanmaz.",
|
||||
"docs": "Dokümanları görüntüle"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Yeni profil",
|
||||
"manage": "Vault Profillerini Yönet",
|
||||
"name": "Gösterim Adı",
|
||||
"vaultAddr": "Vault Adresi",
|
||||
"namespace": "Ad Alanı",
|
||||
"oidcMount": "OIDC Kimlik Doğrulama Bağlama",
|
||||
"oidcRole": "OIDC Rolü",
|
||||
"sshMount": "SSH Gizli Dizin Bağlama",
|
||||
"sshRole": "SSH İmzalayıcı Rolü",
|
||||
"validPrincipals": "Geçerli Asıllar",
|
||||
"keyType": "Geçici Anahtar Türü",
|
||||
"shareWithAll": "Tüm Kullanıcılarla Paylaş",
|
||||
"create": "Profil oluştur",
|
||||
"created": "Vault profili oluşturuldu",
|
||||
"saved": "Vault profili kaydedildi",
|
||||
"deleted": "Vault profili silindi",
|
||||
"saveFailed": "Vault profili kaydedilemedi",
|
||||
"deleteFailed": "Vault profili silinemedi",
|
||||
"save": "Profili kaydet",
|
||||
"validationError": "Ad, Vault adresi ve SSH imzalama rolü gereklidir",
|
||||
"none": "Henüz Vault profili yok.",
|
||||
"sharedBadge": "paylaşılan"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Vault Oturum Açması Gerekli",
|
||||
"description": "HashiCorp Vault'ta oturum açmak için bir pencere açıldı. Orada oturum açmayı tamamlayın; bu bağlantı otomatik olarak devam edecek.",
|
||||
"failed": "Vault kimlik doğrulaması başarısız oldu. Lütfen tekrar deneyin.",
|
||||
"reopen": "Oturum açma penceresini yeniden aç"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Профіль підписанта сховища",
|
||||
"selectProfile": "Виберіть профіль Сховища...",
|
||||
"hint": "Налаштування надходять зі спільного профілю; ви входите у Сейф через OIDC під час підключення. Секрети не зберігаються.",
|
||||
"docs": "Переглянути документи"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Новий профіль",
|
||||
"manage": "Керування профілями Сховища",
|
||||
"name": "Зрозуміле ім'я",
|
||||
"vaultAddr": "Адреса сховища",
|
||||
"namespace": "Простір імен",
|
||||
"oidcMount": "Монтування автентифікації OIDC",
|
||||
"oidcRole": "Роль OIDC",
|
||||
"sshMount": "SSH-секрети кріплення",
|
||||
"sshRole": "Роль підписувача SSH",
|
||||
"validPrincipals": "Дійсні принципали",
|
||||
"keyType": "Тип тимчасового ключа",
|
||||
"shareWithAll": "Поділитися з усіма користувачами",
|
||||
"create": "Створити профіль",
|
||||
"created": "Профіль сховища створено",
|
||||
"saved": "Профіль сховища збережено",
|
||||
"deleted": "Профіль сховища видалено",
|
||||
"saveFailed": "Не вдалося зберегти профіль Сейфа",
|
||||
"deleteFailed": "Не вдалося видалити профіль Сейфа",
|
||||
"save": "Зберегти профіль",
|
||||
"validationError": "Ім'я, адреса сховища та роль підписувача SSH обов'язкові",
|
||||
"none": "Поки що немає профілів Сейфа.",
|
||||
"sharedBadge": "спільний"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Потрібен вхід до Сейфа",
|
||||
"description": "Відкрилося вікно для входу в HashiCorp Vault. Завершіть вхід там; це з’єднання буде продовжено автоматично.",
|
||||
"failed": "Не вдалося виконати автентифікацію сховища. Спробуйте ще раз.",
|
||||
"reopen": "Знову відкрити вікно входу"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Hồ sơ người ký Vault",
|
||||
"selectProfile": "Chọn hồ sơ Vault...",
|
||||
"hint": "Cài đặt từ hồ sơ dùng chung; bạn sẽ đăng nhập Vault qua OIDC khi kết nối. Không lưu bí mật.",
|
||||
"docs": "Xem tài liệu"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "Hồ sơ mới",
|
||||
"manage": "Quản lý hồ sơ Vault",
|
||||
"name": "Tên hiển thị",
|
||||
"vaultAddr": "Địa chỉ Vault",
|
||||
"namespace": "Không gian tên",
|
||||
"oidcMount": "Điểm gắn xác thực OIDC",
|
||||
"oidcRole": "Vai trò OIDC",
|
||||
"sshMount": "Điểm gắn bí mật SSH",
|
||||
"sshRole": "Vai trò ký SSH",
|
||||
"validPrincipals": "Các danh tính hợp lệ",
|
||||
"keyType": "Loại khóa tạm thời",
|
||||
"shareWithAll": "Chia sẻ với tất cả người dùng",
|
||||
"create": "Tạo hồ sơ",
|
||||
"created": "Đã tạo hồ sơ Vault",
|
||||
"saved": "Đã lưu hồ sơ Vault",
|
||||
"deleted": "Đã xóa hồ sơ Vault",
|
||||
"saveFailed": "Không thể lưu hồ sơ Vault",
|
||||
"deleteFailed": "Không thể xóa hồ sơ Vault",
|
||||
"save": "Lưu hồ sơ",
|
||||
"validationError": "Tên, địa chỉ Vault và vai trò ký SSH là bắt buộc",
|
||||
"none": "Chưa có hồ sơ Vault nào.",
|
||||
"sharedBadge": "được chia sẻ"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Yêu cầu đăng nhập Vault",
|
||||
"description": "Một cửa sổ đã mở để đăng nhập vào HashiCorp Vault. Hoàn tất đăng nhập ở đó; kết nối này sẽ tự động tiếp tục.",
|
||||
"failed": "Xác thực Vault thất bại. Vui lòng thử lại.",
|
||||
"reopen": "Mở lại cửa sổ đăng nhập"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Vault 签名者配置文件",
|
||||
"selectProfile": "选择 Vault 配置文件...",
|
||||
"hint": "设置来自共享配置文件;连接时将使用 OIDC 登录 Vault。不会存储任何机密。",
|
||||
"docs": "查看文档"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "新建配置文件",
|
||||
"manage": "管理 Vault 配置文件",
|
||||
"name": "友好名称",
|
||||
"vaultAddr": "Vault 地址",
|
||||
"namespace": "命名空间",
|
||||
"oidcMount": "OIDC 认证挂载点",
|
||||
"oidcRole": "OIDC 角色",
|
||||
"sshMount": "SSH 机密挂载点",
|
||||
"sshRole": "SSH 签名者角色",
|
||||
"validPrincipals": "有效主体",
|
||||
"keyType": "临时密钥类型",
|
||||
"shareWithAll": "与所有用户共享",
|
||||
"create": "创建配置文件",
|
||||
"created": "Vault 配置文件已创建",
|
||||
"saved": "Vault 配置文件已保存",
|
||||
"deleted": "Vault 配置文件已删除",
|
||||
"saveFailed": "保存 Vault 配置文件失败",
|
||||
"deleteFailed": "删除 Vault 配置文件失败",
|
||||
"save": "保存配置文件",
|
||||
"validationError": "名称、Vault 地址和 SSH 签名者角色为必填项",
|
||||
"none": "暂无 Vault 配置文件。",
|
||||
"sharedBadge": "共享"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "需要 Vault 登录",
|
||||
"description": "已打开窗口以登录 HashiCorp Vault。在该处完成登录;此连接将自动继续。",
|
||||
"failed": "Vault 认证失败,请重试。",
|
||||
"reopen": "重新打开登录窗口"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor": {
|
||||
"profile": "Vault 簽署者設定檔",
|
||||
"selectProfile": "選取 Vault 設定檔...",
|
||||
"hint": "設定來自共用設定檔;連線時您將透過 OIDC 登入 Vault。不會儲存任何機密。",
|
||||
"docs": "檢視文件"
|
||||
},
|
||||
"profiles": {
|
||||
"new": "新增設定檔",
|
||||
"manage": "管理 Vault 設定檔",
|
||||
"name": "易記名稱",
|
||||
"vaultAddr": "Vault 位址",
|
||||
"namespace": "命名空間",
|
||||
"oidcMount": "OIDC 驗證掛載",
|
||||
"oidcRole": "OIDC 角色",
|
||||
"sshMount": "SSH 機密掛載",
|
||||
"sshRole": "SSH 簽署者角色",
|
||||
"validPrincipals": "有效主體",
|
||||
"keyType": "暫時金鑰類型",
|
||||
"shareWithAll": "與所有使用者共用",
|
||||
"create": "建立設定檔",
|
||||
"created": "Vault 設定檔已建立",
|
||||
"saved": "Vault 設定檔已儲存",
|
||||
"deleted": "Vault 設定檔已刪除",
|
||||
"saveFailed": "無法儲存 Vault 設定檔",
|
||||
"deleteFailed": "無法刪除 Vault 設定檔",
|
||||
"save": "儲存設定檔",
|
||||
"validationError": "名稱、Vault 位址和 SSH 簽署者角色為必填",
|
||||
"none": "尚無 Vault 設定檔。",
|
||||
"sharedBadge": "已共用"
|
||||
},
|
||||
"dialog": {
|
||||
"title": "需要 Vault 登入",
|
||||
"description": "已開啟視窗以登入 HashiCorp Vault。在該處完成登入;此連線將自動繼續。",
|
||||
"failed": "Vault 驗證失敗。請再試一次。",
|
||||
"reopen": "重新開啟登入視窗"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"id": "vault",
|
||||
"name": "HashiCorp Vault",
|
||||
"version": "1.0.0",
|
||||
"description": "Sign in to hosts with short-lived SSH certificates from a HashiCorp Vault SSH secrets engine, after an OIDC sign-in to Vault in the browser.",
|
||||
"author": {
|
||||
"name": "Termix"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/Termix-SSH/Termix",
|
||||
"category": "Access & Security",
|
||||
"icon": "KeyRound",
|
||||
"engine": {
|
||||
"termix": ">=2.9.0",
|
||||
"api": "1"
|
||||
},
|
||||
"capabilities": [
|
||||
"db:own",
|
||||
"secrets:own",
|
||||
"network:outbound",
|
||||
"network:serve",
|
||||
"auth:provide",
|
||||
"ui:surface"
|
||||
],
|
||||
"contributes": {
|
||||
"auth": {
|
||||
"sshAuthTypes": ["vault"]
|
||||
},
|
||||
"permissions": [
|
||||
{
|
||||
"name": "use",
|
||||
"titleKey": "permissions.use.title",
|
||||
"descriptionKey": "permissions.use.description",
|
||||
"defaultRoles": ["admin", "user"]
|
||||
},
|
||||
{
|
||||
"name": "share",
|
||||
"titleKey": "permissions.share.title",
|
||||
"descriptionKey": "permissions.share.description",
|
||||
"defaultRoles": ["admin"]
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"admin": [
|
||||
{
|
||||
"key": "redirectUri",
|
||||
"type": "custom",
|
||||
"component": "redirectUri"
|
||||
},
|
||||
{
|
||||
"key": "legacyCallback",
|
||||
"type": "boolean",
|
||||
"labelKey": "settings.legacyCallback.label",
|
||||
"descriptionKey": "settings.legacyCallback.description",
|
||||
"default": false
|
||||
}
|
||||
],
|
||||
"host": {
|
||||
"fields": [
|
||||
{
|
||||
"key": "profileId",
|
||||
"type": "number",
|
||||
"labelKey": "settings.host.profileId.label",
|
||||
"hidden": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"locales": "locales",
|
||||
"platforms": ["linux", "win32", "darwin"]
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
-- vault 0001: adopt_vault_tables
|
||||
-- Generated by termix-plugin migrations. Review before committing.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `vault_profiles` (
|
||||
`id` int AUTO_INCREMENT PRIMARY KEY,
|
||||
`user_id` varchar(255) NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`description` text,
|
||||
`folder` text,
|
||||
`tags` text,
|
||||
`vault_addr` text NOT NULL,
|
||||
`vault_namespace` text,
|
||||
`oidc_mount` text,
|
||||
`oidc_role` text,
|
||||
`ssh_mount` text,
|
||||
`ssh_role` text NOT NULL,
|
||||
`valid_principals` text,
|
||||
`key_type` text,
|
||||
`shared` boolean NOT NULL DEFAULT false,
|
||||
`sync_id` varchar(255) UNIQUE,
|
||||
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
);
|
||||
RENAME TABLE `vault_profiles` TO `p_vault_profiles`;
|
||||
CREATE TABLE IF NOT EXISTS `vault_tokens` (
|
||||
`id` int AUTO_INCREMENT PRIMARY KEY,
|
||||
`user_id` varchar(255) NOT NULL,
|
||||
`profile_id` int NOT NULL,
|
||||
`ssh_cert` text NOT NULL,
|
||||
`private_key` text NOT NULL,
|
||||
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||
`expires_at` varchar(255) NOT NULL,
|
||||
`last_used` text,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||
);
|
||||
RENAME TABLE `vault_tokens` TO `p_vault_tokens`;
|
||||
@@ -0,0 +1,38 @@
|
||||
-- vault 0001: adopt_vault_tables
|
||||
-- Generated by termix-plugin migrations. Review before committing.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "vault_profiles" (
|
||||
"id" serial PRIMARY KEY,
|
||||
"user_id" varchar(255) NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"folder" text,
|
||||
"tags" text,
|
||||
"vault_addr" text NOT NULL,
|
||||
"vault_namespace" text,
|
||||
"oidc_mount" text,
|
||||
"oidc_role" text,
|
||||
"ssh_mount" text,
|
||||
"ssh_role" text NOT NULL,
|
||||
"valid_principals" text,
|
||||
"key_type" text,
|
||||
"shared" boolean NOT NULL DEFAULT false,
|
||||
"sync_id" varchar(255) UNIQUE,
|
||||
"created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE
|
||||
);
|
||||
ALTER TABLE "vault_profiles" RENAME TO "p_vault_profiles";
|
||||
CREATE TABLE IF NOT EXISTS "vault_tokens" (
|
||||
"id" serial PRIMARY KEY,
|
||||
"user_id" varchar(255) NOT NULL,
|
||||
"profile_id" integer NOT NULL,
|
||||
"ssh_cert" text NOT NULL,
|
||||
"private_key" text NOT NULL,
|
||||
"created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expires_at" varchar(255) NOT NULL,
|
||||
"last_used" text,
|
||||
FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "idx_vault_tokens_user_profile" ON "vault_tokens" ("user_id", "profile_id");
|
||||
ALTER TABLE "vault_tokens" RENAME TO "p_vault_tokens";
|
||||
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"version": 1,
|
||||
"tables": {
|
||||
"profiles": {
|
||||
"columns": {
|
||||
"id": {
|
||||
"type": "id",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"userId": {
|
||||
"type": "refUser",
|
||||
"length": 255,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"type": "text",
|
||||
"notNull": true
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"folder": {
|
||||
"type": "text"
|
||||
},
|
||||
"tags": {
|
||||
"type": "text"
|
||||
},
|
||||
"vaultAddr": {
|
||||
"type": "text",
|
||||
"notNull": true
|
||||
},
|
||||
"vaultNamespace": {
|
||||
"type": "text"
|
||||
},
|
||||
"oidcMount": {
|
||||
"type": "text"
|
||||
},
|
||||
"oidcRole": {
|
||||
"type": "text"
|
||||
},
|
||||
"sshMount": {
|
||||
"type": "text"
|
||||
},
|
||||
"sshRole": {
|
||||
"type": "text",
|
||||
"notNull": true
|
||||
},
|
||||
"validPrincipals": {
|
||||
"type": "text"
|
||||
},
|
||||
"keyType": {
|
||||
"type": "text"
|
||||
},
|
||||
"shared": {
|
||||
"type": "boolean",
|
||||
"notNull": true,
|
||||
"defaultValue": false
|
||||
},
|
||||
"syncId": {
|
||||
"type": "varchar",
|
||||
"length": 255,
|
||||
"unique": true
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "text",
|
||||
"notNull": true,
|
||||
"defaultNow": true
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "text",
|
||||
"notNull": true,
|
||||
"defaultNow": true
|
||||
}
|
||||
},
|
||||
"indexes": [],
|
||||
"adopts": "vault_profiles"
|
||||
},
|
||||
"tokens": {
|
||||
"columns": {
|
||||
"id": {
|
||||
"type": "id",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"userId": {
|
||||
"type": "refUser",
|
||||
"length": 255,
|
||||
"notNull": true
|
||||
},
|
||||
"profileId": {
|
||||
"type": "integer",
|
||||
"notNull": true
|
||||
},
|
||||
"sshCert": {
|
||||
"type": "text",
|
||||
"notNull": true
|
||||
},
|
||||
"privateKey": {
|
||||
"type": "text",
|
||||
"notNull": true
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "text",
|
||||
"notNull": true,
|
||||
"defaultNow": true
|
||||
},
|
||||
"expiresAt": {
|
||||
"type": "varchar",
|
||||
"length": 255,
|
||||
"notNull": true
|
||||
},
|
||||
"lastUsed": {
|
||||
"type": "text"
|
||||
}
|
||||
},
|
||||
"indexes": [
|
||||
{
|
||||
"name": "idx_vault_tokens_user_profile",
|
||||
"columns": ["userId", "profileId"],
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"adopts": "vault_tokens"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
-- vault 0001: adopt_vault_tables
|
||||
-- Generated by termix-plugin migrations. Review before committing.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "vault_profiles" (
|
||||
"id" integer PRIMARY KEY AUTOINCREMENT,
|
||||
"user_id" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"folder" text,
|
||||
"tags" text,
|
||||
"vault_addr" text NOT NULL,
|
||||
"vault_namespace" text,
|
||||
"oidc_mount" text,
|
||||
"oidc_role" text,
|
||||
"ssh_mount" text,
|
||||
"ssh_role" text NOT NULL,
|
||||
"valid_principals" text,
|
||||
"key_type" text,
|
||||
"shared" integer NOT NULL DEFAULT 0,
|
||||
"sync_id" text UNIQUE,
|
||||
"created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE
|
||||
);
|
||||
ALTER TABLE "vault_profiles" RENAME TO "p_vault_profiles";
|
||||
CREATE TABLE IF NOT EXISTS "vault_tokens" (
|
||||
"id" integer PRIMARY KEY AUTOINCREMENT,
|
||||
"user_id" text NOT NULL,
|
||||
"profile_id" integer NOT NULL,
|
||||
"ssh_cert" text NOT NULL,
|
||||
"private_key" text NOT NULL,
|
||||
"created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expires_at" text NOT NULL,
|
||||
"last_used" text,
|
||||
FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "idx_vault_tokens_user_profile" ON "vault_tokens" ("user_id", "profile_id");
|
||||
ALTER TABLE "vault_tokens" RENAME TO "p_vault_tokens";
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "@termix-plugin/vault",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "termix-plugin build",
|
||||
"test": "termix-plugin test",
|
||||
"typecheck": "tsc -p tsconfig.json",
|
||||
"validate": "termix-plugin validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@termix/plugin-sdk": "*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
import type { PluginContext } from "@termix/plugin-sdk/backend";
|
||||
import type { TokenStore } from "./token-store.js";
|
||||
import {
|
||||
completeVaultOidc,
|
||||
generateEphemeralKeyPair,
|
||||
signWithVault,
|
||||
startVaultOidc,
|
||||
type VaultProfileConfig,
|
||||
} from "./vault-client.js";
|
||||
|
||||
/** The redirect URI 2.8 had Vault roles allow. */
|
||||
export const LEGACY_CALLBACK_PATH = "/vault/oidc/callback";
|
||||
export const CALLBACK_PATH = "/plugin-api/vault/oidc/callback";
|
||||
|
||||
const AUTH_TIMEOUT_MS = 5 * 60 * 1000;
|
||||
|
||||
/** The terminal socket, as far as a sign-in needs it. */
|
||||
export interface SignInSocket {
|
||||
send: (data: string) => void;
|
||||
on?: (event: "close", listener: () => void) => void;
|
||||
}
|
||||
|
||||
export interface StartRequest {
|
||||
userId: string;
|
||||
hostId: number;
|
||||
profile: VaultProfileConfig;
|
||||
socket: SignInSocket;
|
||||
requestOrigin: string;
|
||||
}
|
||||
|
||||
export interface CallbackResult {
|
||||
ok: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface Session {
|
||||
state: string;
|
||||
userId: string;
|
||||
hostId: number;
|
||||
profile: VaultProfileConfig;
|
||||
clientNonce: string;
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
socket: SignInSocket;
|
||||
timeout: ReturnType<typeof setTimeout>;
|
||||
}
|
||||
|
||||
function send(socket: SignInSocket, message: Record<string, unknown>): void {
|
||||
try {
|
||||
socket.send(JSON.stringify(message));
|
||||
} catch {
|
||||
// socket already gone
|
||||
}
|
||||
}
|
||||
|
||||
function errorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
export type AuthSessions = ReturnType<typeof createAuthSessions>;
|
||||
|
||||
/**
|
||||
* Vault OIDC sign-ins in flight, keyed by the state Vault put in its auth
|
||||
* URL so the unauthenticated browser callback finds its way back. Created
|
||||
* per activation; closeAll runs on deactivate.
|
||||
*/
|
||||
export function createAuthSessions(ctx: PluginContext, tokens: TokenStore) {
|
||||
const sessions = new Map<string, Session>();
|
||||
|
||||
function end(session: Session): void {
|
||||
clearTimeout(session.timeout);
|
||||
sessions.delete(session.state);
|
||||
}
|
||||
|
||||
async function callbackPath(): Promise<string> {
|
||||
const legacy = await ctx.settings.get<boolean>("legacyCallback");
|
||||
return legacy ? LEGACY_CALLBACK_PATH : CALLBACK_PATH;
|
||||
}
|
||||
|
||||
return {
|
||||
/** Asks Vault for an auth URL and sends it to the terminal. */
|
||||
async start(request: StartRequest): Promise<void> {
|
||||
try {
|
||||
const keyPair = generateEphemeralKeyPair(request.profile.keyType);
|
||||
const redirectUri = `${request.requestOrigin}${await callbackPath()}`;
|
||||
const { authUrl, state, clientNonce } = await startVaultOidc(
|
||||
ctx.fetch,
|
||||
request.profile,
|
||||
redirectUri,
|
||||
);
|
||||
|
||||
const existing = sessions.get(state);
|
||||
if (existing) end(existing);
|
||||
|
||||
const session: Session = {
|
||||
state,
|
||||
userId: request.userId,
|
||||
hostId: request.hostId,
|
||||
profile: request.profile,
|
||||
clientNonce,
|
||||
privateKey: keyPair.privateKey,
|
||||
publicKey: keyPair.publicKey,
|
||||
socket: request.socket,
|
||||
timeout: setTimeout(() => {
|
||||
const current = sessions.get(state);
|
||||
if (current) end(current);
|
||||
}, AUTH_TIMEOUT_MS),
|
||||
};
|
||||
sessions.set(state, session);
|
||||
request.socket.on?.("close", () => {
|
||||
const current = sessions.get(state);
|
||||
if (current) end(current);
|
||||
});
|
||||
|
||||
send(request.socket, {
|
||||
type: "vault_auth_url",
|
||||
hostId: request.hostId,
|
||||
requestId: state,
|
||||
url: authUrl,
|
||||
});
|
||||
} catch (error) {
|
||||
const message = errorMessage(error);
|
||||
ctx.log.error(`Failed to start Vault authentication: ${message}`);
|
||||
send(request.socket, {
|
||||
type: "vault_error",
|
||||
hostId: request.hostId,
|
||||
error: message,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The identity provider's redirect, via Vault: exchanges the code for a
|
||||
* Vault token, has Vault sign the ephemeral key and caches the result.
|
||||
*/
|
||||
async complete(state: string, code: string): Promise<CallbackResult> {
|
||||
const session = sessions.get(state);
|
||||
if (!session) {
|
||||
return {
|
||||
ok: false,
|
||||
message: "This sign-in request is no longer active.",
|
||||
};
|
||||
}
|
||||
end(session);
|
||||
|
||||
try {
|
||||
const token = await completeVaultOidc(ctx.fetch, session.profile, {
|
||||
state,
|
||||
code,
|
||||
clientNonce: session.clientNonce,
|
||||
});
|
||||
const signed = await signWithVault(
|
||||
ctx.fetch,
|
||||
session.profile,
|
||||
token,
|
||||
session.publicKey,
|
||||
);
|
||||
const expiresAt = await tokens.save(
|
||||
session.userId,
|
||||
session.profile.id,
|
||||
session.privateKey,
|
||||
signed,
|
||||
);
|
||||
ctx.log.info(
|
||||
`Vault signed a certificate for host ${session.hostId} with profile ${session.profile.id}`,
|
||||
);
|
||||
send(session.socket, {
|
||||
type: "vault_completed",
|
||||
hostId: session.hostId,
|
||||
requestId: state,
|
||||
expiresAt,
|
||||
});
|
||||
return {
|
||||
ok: true,
|
||||
message: "You can close this window and return to Termix.",
|
||||
};
|
||||
} catch (error) {
|
||||
const message = errorMessage(error);
|
||||
ctx.log.error(`Vault sign-in failed: ${message}`);
|
||||
send(session.socket, {
|
||||
type: "vault_error",
|
||||
hostId: session.hostId,
|
||||
requestId: state,
|
||||
error: message,
|
||||
});
|
||||
return { ok: false, message };
|
||||
}
|
||||
},
|
||||
|
||||
/** Cancels by request id, or every sign-in the user has for a host. */
|
||||
cancel(userId: string, hostId?: number, requestId?: string): void {
|
||||
for (const session of [...sessions.values()]) {
|
||||
if (session.userId !== userId) continue;
|
||||
if (
|
||||
requestId ? session.state === requestId : session.hostId === hostId
|
||||
) {
|
||||
end(session);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
closeAll(): void {
|
||||
for (const session of [...sessions.values()]) end(session);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { Router } from "express";
|
||||
import type { PluginContext } from "@termix/plugin-sdk/backend";
|
||||
import { profiles as profilesTable, tokens as tokensTable } from "./tables.js";
|
||||
import { createProfileStore, SYNC_ENTITY } from "./profile-store.js";
|
||||
import { createTokenStore } from "./token-store.js";
|
||||
import { createAuthSessions } from "./auth-session.js";
|
||||
import { createVaultProvider } from "./provider.js";
|
||||
import { PUBLIC_PATHS, registerRoutes } from "./routes.js";
|
||||
|
||||
export async function activate(ctx: PluginContext) {
|
||||
const profilesDb = await ctx.db.define(profilesTable);
|
||||
const tokensDb = await ctx.db.define(tokensTable);
|
||||
const profiles = createProfileStore(ctx, profilesDb, tokensDb);
|
||||
const tokens = createTokenStore(ctx, tokensDb);
|
||||
|
||||
const sessions = createAuthSessions(ctx, tokens);
|
||||
ctx.disposables.add(() => sessions.closeAll());
|
||||
|
||||
ctx.auth.registerSshAuthProvider(
|
||||
createVaultProvider(ctx, profiles, tokens, sessions),
|
||||
);
|
||||
|
||||
// Same wire name as 2.8, where core synced it. Hosts point at a profile
|
||||
// through the profileId host setting, not a column, so nothing references it.
|
||||
ctx.sync.registerEntity({
|
||||
type: SYNC_ENTITY,
|
||||
table: profilesDb,
|
||||
order: 20,
|
||||
});
|
||||
|
||||
registerRoutes(
|
||||
ctx,
|
||||
ctx.http.router<Router>({ public: PUBLIC_PATHS }),
|
||||
profiles,
|
||||
sessions,
|
||||
);
|
||||
}
|
||||
|
||||
export async function deactivate() {
|
||||
// Everything above was registered through ctx and is disposed by core.
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { desc, eq, or } from "drizzle-orm";
|
||||
import type { PluginContext } from "@termix/plugin-sdk/backend";
|
||||
import type { VaultProfileConfig } from "./vault-client.js";
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// ctx.db.define hands the table back untyped and the drizzle handle is the
|
||||
// server's own, so both are typed at this module's edge.
|
||||
type Table = any;
|
||||
type Drizzle = any;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export const SYNC_ENTITY = "vaultProfiles";
|
||||
|
||||
export interface ProfileRow {
|
||||
id: number;
|
||||
userId: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
folder: string | null;
|
||||
tags: string | null;
|
||||
vaultAddr: string;
|
||||
vaultNamespace: string | null;
|
||||
oidcMount: string | null;
|
||||
oidcRole: string | null;
|
||||
sshMount: string | null;
|
||||
sshRole: string;
|
||||
validPrincipals: string | null;
|
||||
keyType: string | null;
|
||||
shared: boolean | number;
|
||||
syncId: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export type ProfileInput = Partial<
|
||||
Omit<ProfileRow, "id" | "userId" | "syncId" | "createdAt" | "updatedAt">
|
||||
>;
|
||||
|
||||
export function toConfig(row: ProfileRow): VaultProfileConfig {
|
||||
return {
|
||||
id: row.id,
|
||||
vaultAddr: row.vaultAddr,
|
||||
vaultNamespace: row.vaultNamespace ?? null,
|
||||
oidcMount: row.oidcMount ?? null,
|
||||
oidcRole: row.oidcRole ?? null,
|
||||
sshMount: row.sshMount ?? null,
|
||||
sshRole: row.sshRole,
|
||||
validPrincipals: row.validPrincipals ?? null,
|
||||
keyType: row.keyType ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export type ProfileStore = ReturnType<typeof createProfileStore>;
|
||||
|
||||
export function createProfileStore(
|
||||
ctx: PluginContext,
|
||||
table: Table,
|
||||
tokensTable: Table,
|
||||
) {
|
||||
const client = () => ctx.db.client<Drizzle>();
|
||||
|
||||
async function findById(id: number): Promise<ProfileRow | null> {
|
||||
const rows = await (
|
||||
await client()
|
||||
)
|
||||
.select()
|
||||
.from(table)
|
||||
.where(eq(table.id, id))
|
||||
.limit(1);
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
|
||||
async function findBySyncId(syncId: string): Promise<ProfileRow | null> {
|
||||
const rows = await (
|
||||
await client()
|
||||
)
|
||||
.select()
|
||||
.from(table)
|
||||
.where(eq(table.syncId, syncId))
|
||||
.limit(1);
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
|
||||
return {
|
||||
findById,
|
||||
|
||||
/** The user's own profiles plus every shared one. */
|
||||
async listVisible(userId: string): Promise<ProfileRow[]> {
|
||||
return (await client())
|
||||
.select()
|
||||
.from(table)
|
||||
.where(or(eq(table.userId, userId), eq(table.shared, true)))
|
||||
.orderBy(desc(table.updatedAt));
|
||||
},
|
||||
|
||||
async create(
|
||||
userId: string,
|
||||
input: ProfileInput & {
|
||||
name: string;
|
||||
vaultAddr: string;
|
||||
sshRole: string;
|
||||
},
|
||||
): Promise<ProfileRow> {
|
||||
const syncId = randomUUID();
|
||||
const now = new Date().toISOString();
|
||||
await (await client()).insert(table).values({
|
||||
...input,
|
||||
userId,
|
||||
shared: input.shared ? true : false,
|
||||
syncId,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
await ctx.db.persist();
|
||||
return (await findBySyncId(syncId)) as ProfileRow;
|
||||
},
|
||||
|
||||
async update(id: number, input: ProfileInput): Promise<ProfileRow | null> {
|
||||
await (
|
||||
await client()
|
||||
)
|
||||
.update(table)
|
||||
.set({ ...input, updatedAt: new Date().toISOString() })
|
||||
.where(eq(table.id, id));
|
||||
await ctx.db.persist();
|
||||
return findById(id);
|
||||
},
|
||||
|
||||
/** Deletes the profile, its cached certificates and records a tombstone. */
|
||||
async remove(row: ProfileRow): Promise<void> {
|
||||
const db = await client();
|
||||
await db.delete(tokensTable).where(eq(tokensTable.profileId, row.id));
|
||||
await db.delete(table).where(eq(table.id, row.id));
|
||||
await ctx.db.persist();
|
||||
await ctx.sync.recordTombstone(row.userId, SYNC_ENTITY, row.syncId);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import type {
|
||||
PluginContext,
|
||||
PluginSshAuthProvider,
|
||||
PluginSshHost,
|
||||
} from "@termix/plugin-sdk/backend";
|
||||
import { applyCertificateAuth } from "@termix/plugin-sdk/ssh-certs";
|
||||
import type { AuthSessions, SignInSocket } from "./auth-session.js";
|
||||
import {
|
||||
toConfig,
|
||||
type ProfileRow,
|
||||
type ProfileStore,
|
||||
} from "./profile-store.js";
|
||||
import type { TokenStore } from "./token-store.js";
|
||||
|
||||
export const AUTH_TYPE = "vault";
|
||||
|
||||
const REQUIRED_MESSAGE =
|
||||
"Vault SSH signer authentication required. Please open a Terminal connection first.";
|
||||
|
||||
const AUTH_FAILED_PATTERN = /All configured authentication methods failed/i;
|
||||
|
||||
/** The profile a host points at, from its host settings. */
|
||||
export async function profileForHost(
|
||||
ctx: PluginContext,
|
||||
profiles: ProfileStore,
|
||||
hostId: number,
|
||||
): Promise<ProfileRow | null> {
|
||||
const raw = await ctx.settings.getHost<number | string>(hostId, "profileId");
|
||||
const id = Number(raw);
|
||||
if (!Number.isInteger(id) || id <= 0) return null;
|
||||
return profiles.findById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hosts with auth type "vault" connect with a certificate Vault signed for
|
||||
* the host's profile. Without one, the terminal is asked for a browser
|
||||
* sign-in ("vault_auth_required"), which startInteraction runs.
|
||||
*/
|
||||
export function createVaultProvider(
|
||||
ctx: PluginContext,
|
||||
profiles: ProfileStore,
|
||||
tokens: TokenStore,
|
||||
sessions: AuthSessions,
|
||||
): PluginSshAuthProvider {
|
||||
return {
|
||||
type: AUTH_TYPE,
|
||||
labelKey: "hosts.filterAuthVault",
|
||||
needsUserInteraction: true,
|
||||
supportsBackground: false,
|
||||
interaction: AUTH_TYPE,
|
||||
prepare: async (config, host: PluginSshHost, env) => {
|
||||
const profile = await profileForHost(ctx, profiles, host.id);
|
||||
if (!profile) {
|
||||
return {
|
||||
status: "error",
|
||||
code: "failed",
|
||||
message: "Host has no Vault signer profile configured",
|
||||
};
|
||||
}
|
||||
const cert = await tokens.get(env.userId, profile.id);
|
||||
if (!cert) {
|
||||
env.log(
|
||||
"info",
|
||||
"No valid Vault certificate found, requesting authentication",
|
||||
);
|
||||
return {
|
||||
status: "interaction-required",
|
||||
interaction: AUTH_TYPE,
|
||||
message: REQUIRED_MESSAGE,
|
||||
flag: "requiresVaultAuth",
|
||||
};
|
||||
}
|
||||
try {
|
||||
await applyCertificateAuth(
|
||||
config as never,
|
||||
env.client as never,
|
||||
{ privateKey: cert.privateKey, certificate: cert.sshCert },
|
||||
host.username,
|
||||
);
|
||||
} catch (error) {
|
||||
return {
|
||||
status: "error",
|
||||
code: "failed",
|
||||
message:
|
||||
"Vault SSH signer authentication failed: " +
|
||||
(error instanceof Error ? error.message : String(error)),
|
||||
};
|
||||
}
|
||||
env.log("info", "Using cached Vault-signed certificate");
|
||||
return { status: "ready" };
|
||||
},
|
||||
onAuthFailed: (host, env, context) => {
|
||||
if (!AUTH_FAILED_PATTERN.test(context.error.message)) return undefined;
|
||||
ctx.log.warn("Vault certificate authentication failed, forgetting it");
|
||||
void profileForHost(ctx, profiles, host.id)
|
||||
.then((profile) =>
|
||||
profile ? tokens.remove(env.userId, profile.id) : undefined,
|
||||
)
|
||||
.catch(() => undefined);
|
||||
return {
|
||||
status: "interaction-required",
|
||||
interaction: AUTH_TYPE,
|
||||
message:
|
||||
"Vault authentication failed or expired. Please authenticate again.",
|
||||
};
|
||||
},
|
||||
startInteraction: async (request) => {
|
||||
const profile = await profileForHost(ctx, profiles, request.hostId);
|
||||
if (!profile) {
|
||||
throw new Error("No Vault signer profile configured for this host");
|
||||
}
|
||||
await sessions.start({
|
||||
userId: request.userId,
|
||||
hostId: request.hostId,
|
||||
profile: toConfig(profile),
|
||||
socket: request.socket as SignInSocket,
|
||||
requestOrigin: request.requestOrigin,
|
||||
});
|
||||
},
|
||||
cancelInteraction: (request) => {
|
||||
sessions.cancel(request.userId, request.hostId, request.requestId);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
import type { Request, Response, Router } from "express";
|
||||
import type { PluginContext } from "@termix/plugin-sdk/backend";
|
||||
import type { AuthSessions } from "./auth-session.js";
|
||||
import type {
|
||||
ProfileInput,
|
||||
ProfileRow,
|
||||
ProfileStore,
|
||||
} from "./profile-store.js";
|
||||
|
||||
/**
|
||||
* Vault sends the browser back here without a Termix session: the sign-in is
|
||||
* matched by the state Vault issued.
|
||||
*/
|
||||
export const PUBLIC_PATHS = ["/oidc/callback"];
|
||||
|
||||
export function escapeHtml(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
export function resultPage(ok: boolean, message: string): string {
|
||||
return `<!doctype html><html><head><meta charset="utf-8"><title>Termix</title>
|
||||
<style>body{font-family:system-ui,sans-serif;background:#111;color:#eee;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}
|
||||
main{max-width:28rem;padding:2rem;border:1px solid #333;background:#181818}h1{font-size:1.1rem;margin:0 0 .5rem}p{margin:0;color:#aaa}</style></head>
|
||||
<body><main><h1>${ok ? "Vault sign-in complete" : "Vault sign-in failed"}</h1><p>${escapeHtml(message)}</p></main>
|
||||
${ok ? "<script>setTimeout(function(){window.close()},1500)</script>" : ""}</body></html>`;
|
||||
}
|
||||
|
||||
function isNonEmptyString(value: unknown): value is string {
|
||||
return typeof value === "string" && value.trim().length > 0;
|
||||
}
|
||||
|
||||
function optional(value: unknown): string | null {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : null;
|
||||
}
|
||||
|
||||
function formatProfile(row: ProfileRow, userId: string) {
|
||||
return {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
folder: row.folder,
|
||||
tags: row.tags ? row.tags.split(",").filter(Boolean) : [],
|
||||
vaultAddr: row.vaultAddr,
|
||||
vaultNamespace: row.vaultNamespace,
|
||||
oidcMount: row.oidcMount,
|
||||
oidcRole: row.oidcRole,
|
||||
sshMount: row.sshMount,
|
||||
sshRole: row.sshRole,
|
||||
validPrincipals: row.validPrincipals,
|
||||
keyType: row.keyType,
|
||||
shared: !!row.shared,
|
||||
owned: row.userId === userId,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
function parseId(req: Request): number | null {
|
||||
const id = Number.parseInt(String(req.params.id), 10);
|
||||
return Number.isFinite(id) ? id : null;
|
||||
}
|
||||
|
||||
export function registerRoutes(
|
||||
ctx: PluginContext,
|
||||
router: Router,
|
||||
profiles: ProfileStore,
|
||||
sessions: AuthSessions,
|
||||
): void {
|
||||
const userId = () => ctx.currentActor() as string;
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /plugin-api/vault/oidc/callback:
|
||||
* get:
|
||||
* summary: Vault OIDC callback
|
||||
* description: Public. The redirect URI a Vault OIDC role allows. Matches the sign-in by the state Vault issued, has Vault sign the ephemeral key and tells the terminal to reconnect. The 2.8 URI /vault/oidc/callback redirects here.
|
||||
* tags: [Vault]
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: state
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: query
|
||||
* name: code
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: query
|
||||
* name: error
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Signed in; an HTML page that closes itself.
|
||||
* 400:
|
||||
* description: The sign-in failed or is no longer active.
|
||||
*/
|
||||
router.get("/oidc/callback", async (req: Request, res: Response) => {
|
||||
const state = typeof req.query.state === "string" ? req.query.state : "";
|
||||
const code = typeof req.query.code === "string" ? req.query.code : "";
|
||||
const error = typeof req.query.error === "string" ? req.query.error : "";
|
||||
|
||||
const reply = (ok: boolean, message: string) =>
|
||||
res
|
||||
.status(ok ? 200 : 400)
|
||||
.type("html")
|
||||
.send(resultPage(ok, message));
|
||||
|
||||
if (error) return reply(false, `Vault returned: ${error}`);
|
||||
if (!state || !code) return reply(false, "Missing state or code.");
|
||||
const result = await sessions.complete(state, code);
|
||||
return reply(result.ok, result.message);
|
||||
});
|
||||
|
||||
router.use("/profiles", ctx.rbac.require("use") as never);
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /plugin-api/vault/profiles:
|
||||
* get:
|
||||
* summary: List Vault profiles
|
||||
* description: The caller's own Vault signer profiles and every shared one. Needs vault.use.
|
||||
* tags: [Vault]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Array of Vault profile objects.
|
||||
* 403:
|
||||
* description: Missing vault.use.
|
||||
*/
|
||||
router.get("/profiles", async (_req: Request, res: Response) => {
|
||||
const rows = await profiles.listVisible(userId());
|
||||
res.json(rows.map((row) => formatProfile(row, userId())));
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /plugin-api/vault/profiles:
|
||||
* post:
|
||||
* summary: Create a Vault profile
|
||||
* description: Creates a Vault signer profile owned by the caller. Needs vault.use, and vault.share to mark it shared.
|
||||
* tags: [Vault]
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [name, vaultAddr, sshRole]
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* vaultAddr:
|
||||
* type: string
|
||||
* vaultNamespace:
|
||||
* type: string
|
||||
* oidcMount:
|
||||
* type: string
|
||||
* oidcRole:
|
||||
* type: string
|
||||
* sshMount:
|
||||
* type: string
|
||||
* sshRole:
|
||||
* type: string
|
||||
* validPrincipals:
|
||||
* type: string
|
||||
* keyType:
|
||||
* type: string
|
||||
* shared:
|
||||
* type: boolean
|
||||
* responses:
|
||||
* 201:
|
||||
* description: The created profile.
|
||||
* 400:
|
||||
* description: Missing required fields.
|
||||
* 403:
|
||||
* description: Missing vault.use, or vault.share for a shared profile.
|
||||
*/
|
||||
router.post("/profiles", async (req: Request, res: Response) => {
|
||||
const body = (req.body ?? {}) as Record<string, unknown>;
|
||||
if (
|
||||
!isNonEmptyString(body.name) ||
|
||||
!isNonEmptyString(body.vaultAddr) ||
|
||||
!isNonEmptyString(body.sshRole)
|
||||
) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "name, vaultAddr and sshRole are required" });
|
||||
}
|
||||
const shared = !!body.shared;
|
||||
if (shared && !(await ctx.rbac.has("share"))) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ error: "You are not allowed to share Vault profiles" });
|
||||
}
|
||||
const row = await profiles.create(userId(), {
|
||||
name: body.name.trim(),
|
||||
description: optional(body.description),
|
||||
folder: optional(body.folder),
|
||||
tags: Array.isArray(body.tags)
|
||||
? body.tags.join(",")
|
||||
: typeof body.tags === "string"
|
||||
? body.tags
|
||||
: "",
|
||||
vaultAddr: body.vaultAddr.trim(),
|
||||
vaultNamespace: optional(body.vaultNamespace),
|
||||
oidcMount: optional(body.oidcMount),
|
||||
oidcRole: optional(body.oidcRole),
|
||||
sshMount: optional(body.sshMount),
|
||||
sshRole: body.sshRole.trim(),
|
||||
validPrincipals: optional(body.validPrincipals),
|
||||
keyType: optional(body.keyType),
|
||||
shared,
|
||||
});
|
||||
res.status(201).json(formatProfile(row, userId()));
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /plugin-api/vault/profiles/{id}:
|
||||
* put:
|
||||
* summary: Update a Vault profile
|
||||
* description: Only the owner may edit a profile. Needs vault.use, and vault.share to mark it shared.
|
||||
* tags: [Vault]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The updated profile.
|
||||
* 400:
|
||||
* description: Invalid profile id.
|
||||
* 403:
|
||||
* description: Not the owner, or sharing without vault.share.
|
||||
* 404:
|
||||
* description: Profile not found.
|
||||
*/
|
||||
router.put("/profiles/:id", async (req: Request, res: Response) => {
|
||||
const id = parseId(req);
|
||||
if (id === null)
|
||||
return res.status(400).json({ error: "Invalid profile id" });
|
||||
const existing = await profiles.findById(id);
|
||||
if (!existing) return res.status(404).json({ error: "Profile not found" });
|
||||
if (existing.userId !== userId()) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ error: "Only the owner can edit this profile" });
|
||||
}
|
||||
|
||||
const body = (req.body ?? {}) as Record<string, unknown>;
|
||||
const fields: ProfileInput = {};
|
||||
if (isNonEmptyString(body.name)) fields.name = body.name.trim();
|
||||
if (body.description !== undefined)
|
||||
fields.description = optional(body.description);
|
||||
if (body.folder !== undefined) fields.folder = optional(body.folder);
|
||||
if (body.tags !== undefined) {
|
||||
fields.tags = Array.isArray(body.tags)
|
||||
? body.tags.join(",")
|
||||
: typeof body.tags === "string"
|
||||
? body.tags
|
||||
: "";
|
||||
}
|
||||
if (isNonEmptyString(body.vaultAddr))
|
||||
fields.vaultAddr = body.vaultAddr.trim();
|
||||
if (body.vaultNamespace !== undefined)
|
||||
fields.vaultNamespace = optional(body.vaultNamespace);
|
||||
if (body.oidcMount !== undefined)
|
||||
fields.oidcMount = optional(body.oidcMount);
|
||||
if (body.oidcRole !== undefined) fields.oidcRole = optional(body.oidcRole);
|
||||
if (body.sshMount !== undefined) fields.sshMount = optional(body.sshMount);
|
||||
if (isNonEmptyString(body.sshRole)) fields.sshRole = body.sshRole.trim();
|
||||
if (body.validPrincipals !== undefined)
|
||||
fields.validPrincipals = optional(body.validPrincipals);
|
||||
if (body.keyType !== undefined) fields.keyType = optional(body.keyType);
|
||||
if (body.shared !== undefined) {
|
||||
if (body.shared && !(await ctx.rbac.has("share"))) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ error: "You are not allowed to share Vault profiles" });
|
||||
}
|
||||
fields.shared = !!body.shared;
|
||||
}
|
||||
|
||||
const updated = await profiles.update(id, fields);
|
||||
if (!updated) return res.status(404).json({ error: "Profile not found" });
|
||||
res.json(formatProfile(updated, userId()));
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /plugin-api/vault/profiles/{id}:
|
||||
* delete:
|
||||
* summary: Delete a Vault profile
|
||||
* description: Deletes a profile and every certificate cached for it. Only the owner may delete it. Needs vault.use.
|
||||
* tags: [Vault]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Deleted.
|
||||
* 400:
|
||||
* description: Invalid profile id.
|
||||
* 403:
|
||||
* description: Not the owner.
|
||||
* 404:
|
||||
* description: Profile not found.
|
||||
*/
|
||||
router.delete("/profiles/:id", async (req: Request, res: Response) => {
|
||||
const id = parseId(req);
|
||||
if (id === null)
|
||||
return res.status(400).json({ error: "Invalid profile id" });
|
||||
const existing = await profiles.findById(id);
|
||||
if (!existing) return res.status(404).json({ error: "Profile not found" });
|
||||
if (existing.userId !== userId()) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ error: "Only the owner can delete this profile" });
|
||||
}
|
||||
await profiles.remove(existing);
|
||||
res.json({ success: true });
|
||||
});
|
||||
}
|
||||
Loaded 100 of 198 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user