Протокол на Vertex
Entrosity Vertex няма собствен конектор. Той изпълнява всяка заявка към
директорията като задача на конектора на обекта на Entrosity Axis на
тенанта, който трябва да е инсталиран на домейн контролер. Задачите минават
през бекенда на Axis и обичайния протокол на агента и
конектора; заявките за тайни и големите резултати на
конектора се връщат през Axis към Vertex. Типовете на Go в
entrosity-shared-go/proto/vertex са единственият източник на истина;
тази страница описва намерението и поведението.
Както останалата част от протокола, тези типове само растат: полета се добавят, но никога не се преименуват и не им се сменя смисълът, така че конекторите на място продължават да работят.
Възможност и типове задачи
Конектор, който може да изпълнява задачи на Vertex, обявява възможността
vertex в своето hello (Windows версиите на
entrosity-axis-connector). Vertex предлага само конектори с нея
(supports_vertex).
| Тип задача | Операции | Лента на конектора |
|---|---|---|
vertex.op | Всяка операция от клас за промени | vertex: една по една, по ред |
vertex.read | Операции от клас read | vertex-read: две успоредно, до текуща промяна |
И двете носят едно и също съдържание, OpJob:
| Поле | Значение |
|---|---|
operation_id | Идентификаторът на операцията във Vertex: именува тайните и частите на задачата. |
op | Операцията (Операции). |
managed_ous | DN на управляваните OU на тенанта (най-много 100). Промени извън тях се отказват; защитата ги сравнява с приетия списък. |
server | Име на хоста на домейн контролер по желание (по подразбиране – локалният). |
params | Параметрите на операцията (JSON; типове в proto/vertex/params.go). |
expires_at | Конекторът не започва промяна след този момент (expired). |
Съдържанието никога не съдържа тайни. Vertex поставя четенията с приоритет 10, а промените – с приоритет 5; промяната изтича след 1 час (партида от импорт – след 6) и се изпълнява най-много 10 минути (четенията – 30 минути, партидите от импорт – един час). Axis ги ограничава до 24 часа и 2 часа.
Операции
op | Клас | Изтриване | Пароли | params |
|---|---|---|---|---|
test | read | няма → резултат test (TestResult) | ||
sync | read | SyncParams {kinds?, user_attributes?, search_base?} → части objects, gpos | ||
schema | read | SchemaParams {classes?} → части schema | ||
object.get | read | GetParams {target, attributes?} → object с writable | ||
gpo.report | read | GPORef {id} → части report | ||
gpo.backup | read | GPORef {id} → backup_id | ||
user.create | user | ✓ | UserCreateParams {ou, name, sam_account_name, user_principal_name?, given_name?, surname?, display_name?, enabled, must_change_password?, password_never_expires?, cannot_change_password?, password_key?, attrs?, groups?} | |
object.update | на целта | UpdateParams {target, set?, add?, remove?, clear?} | ||
user.password | user | ✓ | PasswordParams {target, password_key, must_change, unlock?} | |
user.unlock | user | TargetParams {target} | ||
user.enable | user | EnableParams {target, enabled} | ||
object.move | на целта | MoveParams {target, target_ou} | ||
object.rename | на целта | RenameParams {target, new_name} | ||
object.delete | на целта | ✓ | DeleteParams {target, recursive?} (OU: рекурсивно) | |
group.create | group | GroupCreateParams {ou, name, sam_account_name?, scope, category, description?, attrs?} | ||
member.change | group | MemberParams {group, add?, remove?} (DN, най-много 500 всяко) | ||
ou.create | ou | OUCreateParams {parent_dn, name, description?, protect_from_deletion} | ||
pso.set | pso | PSOParams {target?, name?, settings?, apply_to?, unapply?} (без target: създаване) | ||
gpo.create | gpo | GPOCreateParams {name, comment?, link_to?} | ||
gpo.update | gpo | GPOUpdateParams {id, name?, status?, comment?} | ||
gpo.delete | gpo | ✓ | GPORef {id} | |
gpo.link | gpo | GPOLinkParams {id, target_dn, action: link|unlink|set, enabled?, enforced?, order?} | ||
gpo.registry | gpo | GPORegistryParams {id, set?, remove?} (RegistryValue {scope, key, value_name, type?, value?}, най-много 100) | ||
gpo.permission | gpo | GPOPermissionParams {id, trustee, trustee_type, level, replace?} | ||
bulk.users | user | ✓ | BulkParams {mode: create|update|upsert, rows: [BulkRow]} (1–200 реда) → части rows |
Target е {guid, kind, dn?}: конекторът намира обекта по
objectGUID (DN е само информативен); kind е user, group, ou или
pso. Операциите object.* приемат класа на вида на целта си
(user, group, ou, pso).
Броени записи към ограниченията на промените и лимита на конектора:
по един на операция, освен member.change (по един за всеки добавен или
премахнат член), gpo.registry (по един за стойност) и bulk.users (по
един за ред).
Attrs са {ldapName: [values]}; двоичните стойности са
b64:<base64>. Имената от DeniedAttributes (proto/vertex/names.go,
повторени в скрипта) се отказват. Най-много 1000 стойности на атрибут и
64 KiB на стойност.
Тайни
Тайните на задачата – AD акаунтът (credential: {username, password}) и
паролите на операцията (passwords: {key: password}, с ключ
password_key или key на реда от импорта) – конекторът взема
веднъж за задача, след като защитата му е позволила задачата:
- Конектор → Axis:
POST /api/connector/v1/vertex/jobs/{jobID}/secrets {"operation_id": "…"}с ключа на конектора. - Axis проверява, че задачата е активна задача на Vertex на този
конектор (негова задача, тип
vertex.op/vertex.read, неприключила, неизтекла, със същияoperation_id); иначе403 job_not_live. - Axis → Vertex:
POST /internal/v1/axis/secrets {tenant_id, connector_id, job_id, operation_id}. - Vertex проверява, че операцията съществува за тази задача и конектор и
не е приключила или изтекла, че тенантът все още е настроен с този
конектор, че превключвателите за промени все още позволяват класа (и
изтриването), че авторът все още има правото за операцията (активен
потребител с ролята;
author_revoked) и че тайните не са взети преди това (secrets_used). Едва тогава ги декриптира и отговаря съсSecrets; запечатаните пароли на операцията се изтриват при предаването им. - Axis отговаря на конектора с
Cache-Control: no-store.
Отказът достига до конектора като 403 vertex_refused с причината на
Vertex; конекторът прави задачата неуспешна с unauthorized и причината в
съобщението. Нищо не се записва.
Части
Големите резултати се изпращат, докато задачата върви: POST /api/connector/v1/vertex/jobs/{jobID}/chunks с Chunk
{operation_id, seq, kind, items}. seq започва от 0 и няма пропуски;
Vertex прилага повторена част веднъж. Axis проверява задачата както при
тайните и препраща {tenant_id, connector_id, job_id, chunk} към
POST /internal/v1/axis/chunks (204).
kind | items | Изпраща се от |
|---|---|---|
objects | [Object] (по 250 в част) | sync |
gpos | [GPO] | sync |
schema | [AttributeSchema] | schema |
rows | [RowResult {key, status, error_code?, error?, object?}] (по 25 в част) | bulk.users |
report | [{xml}] (части от 200 000 знака) | gpo.report |
Резултати
job.result.result на успешна задача е OpResult: object
(промененият обект с атрибутите си и writable от
allowedAttributesEffective), gpo, deleted (GUID), backup_id,
test или counts ({kind: items} за синхронизация, схема, импорт и
регистър). Състояния на редовете от импорта: created, updated,
unchanged, failed, skipped. Object има guid, dn, kind,
sid?, attrs, writable? и protected (adminCount=1,
isCriticalSystemObject или относителен идентификатор под 1000).
Когато Axis запише резултата на задача на Vertex, той изпраща
POST /internal/v1/axis/jobs/finished {tenant_id, job_id} на Vertex
(подсказка, доколкото е възможно); Vertex също проверява отворените си
операции на всеки 2 секунди и прилага всеки резултат веднъж.
Мостът между Vertex и Axis
Два вътрешни слушателя, които никога не се публикуват, удостоверени в
двете посоки с един bearer токен (VERTEX_AXIS_TOKEN във Vertex,
RMM_VERTEX_AXIS_TOKEN в Axis, поне 32 знака):
| Посока | Крайна точка | Предназначение |
|---|---|---|
Vertex → Axis (RMM_INTERNAL_ADDR, :8089) | GET /internal/v1/tenants/{tenant}/connectors | Неотменените конектори на тенанта (AxisConnector: id, име, хост, домейн, версия, състояние, възможности, последно видян). |
POST /internal/v1/tenants/{tenant}/jobs | Поставяне на задача в опашката: AxisCreateJob {connector_id, job: OpJob, timeout_seconds, expires_in_seconds, priority} → 201 AxisJob. Типът на задачата следва от op. | |
GET /internal/v1/tenants/{tenant}/jobs/{job} | Състоянието на задачата (AxisJob: състояние, напредък, грешка, резултат). Само задачи на Vertex. | |
POST /internal/v1/tenants/{tenant}/jobs/{job}/cancel | Отмяна (и известяване на конектора). | |
Axis → Vertex (VERTEX_INTERNAL_ADDR, :8088) | POST /internal/v1/axis/secrets | BridgeSecretsRequest → Secrets. |
POST /internal/v1/axis/chunks | BridgeChunk → 204. | |
POST /internal/v1/axis/jobs/finished | BridgeJobFinished → 204. |
Крайните точки на конектора в Axis POST /vertex/jobs/{jobID}/secrets и
/chunks съществуват само когато Vertex е настроен в Axis
(RMM_VERTEX_INTERNAL_URL). Axis не пази тайни на Vertex. Когато Vertex
не отговаря, Axis отговаря с 503 vertex_unavailable.
На конектора
За всяка задача vertex.op конекторът:
- валидира
OpJob(invalid_payload); - при промени: отказва изтекла задача (
expired), записва управляваните OU в локалната си защита и проверява защитата: приета (guard_pending), включени клас и изтриване (guard_denied), лимит в минута (rate_limited); - взема тайните (
unauthorizedпри отказ); - стартира Windows PowerShell 5.1 (
powershell.exe -NoProfile -NonInteractive -EncodedCommand <bootstrap>) и записва входа, заедно с вградения скрипт, кодиран в base64, на стандартния му вход; никоя стойност не се поставя в командния ред и не се вмъква в код; - скриптът отваря PowerShell сесия към същата машина
(
New-PSSession -ComputerName $env:COMPUTERNAME) с идентификационните данни на AD акаунта и изпълнява операцията там с модулитеActiveDirectoryиGroupPolicy, като преди запис проверява отново управляваните OU, защитата и забранените атрибути; - превръща изходните редове на скрипта в напредък, части и резултат.
Скриптът извежда по един JSON обект на ред: {"t":"progress","pct","msg"},
{"t":"chunk","kind","items"}, {"t":"result","result"},
{"t":"error","code","msg"} и {"t":"log","msg"} (пази се в края на
изхода на задачата). GPO се архивират в %ProgramData%\Entrosity\Vertex GPO Backups преди gpo.update, gpo.registry и gpo.delete
(задължително за изтриването). Политиките по подразбиране на домейна се
отказват (protected).
Кодове на грешки
Кодове на грешки на задачите (job.result.error_code, error_code на
операцията):
| Код | Значение |
|---|---|
guard_pending | Локалната защита не е приета, управляваните OU на задачата се различават от приетите или файлът на защитата не е надежден. |
guard_denied | Локалната защита не позволява този клас или изтриването. |
rate_limited | Локалният лимит на записи в минута на конектора (по подразбиране 1000, най-много 2000). |
expired | Задачата е пристигнала след expires_at. |
unauthorized | Vertex е отказал заявката за тайни (съобщението казва защо). |
out_of_scope | Обектът или целта е извън управляваните OU (PSO: извън Password Settings Container). |
protected | Привилегирован или вграден обект или политика по подразбиране на домейна. |
denied_attribute | Атрибут от забранения списък. |
not_found | Обектът не съществува. |
already_exists | Вече съществува обект с това име. |
password_policy | Паролата не отговаря на политиката. |
access_denied | На AD акаунта липсва правото (или не може да отвори PowerShell сесията: add it to Remote Management Users). |
logon_failed | Идентификационните данни на AD акаунта са отказани. |
missing_module | Липсва модулът ActiveDirectory или GroupPolicy. |
ad_error | Всяка друга грешка на AD или PowerShell, или резултати, които не са могли да бъдат доставени. |
invalid_payload, unsupported, cancelled | Както за всяка задача (Кодове на грешки на задачите). |
Какво означават те за потребителите: Отстраняване на проблеми с Vertex.
Ограничения
| Ограничение | Стойност |
|---|---|
| Управлявани OU | 100 |
Редове на задача bulk.users | 200 |
Членове на member.change (добавяне, премахване) | по 500 |
| Записи на конектора в минута (локална защита) | 1000 по подразбиране, от 1 до 2000 |
Стойности в регистъра на gpo.registry | 100 |
| Стойности на атрибут / размер на стойност | 1000 / 64 KiB |
| Дължина на DN | 2048 |
| Дължина на парола | 256 |
| Елементи в част | 500 |
| Тяло на заявка за създаване на задача (Axis) | 4 MiB |