Преминете към основното съдържание

Протокол на 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Операции от клас readvertex-read: две успоредно, до текуща промяна

И двете носят едно и също съдържание, OpJob:

ПолеЗначение
operation_idИдентификаторът на операцията във Vertex: именува тайните и частите на задачата.
opОперацията (Операции).
managed_ousDN на управляваните OU на тенанта (най-много 100). Промени извън тях се отказват; защитата ги сравнява с приетия списък.
serverИме на хоста на домейн контролер по желание (по подразбиране – локалният).
paramsПараметрите на операцията (JSON; типове в proto/vertex/params.go).
expires_atКонекторът не започва промяна след този момент (expired).

Съдържанието никога не съдържа тайни. Vertex поставя четенията с приоритет 10, а промените – с приоритет 5; промяната изтича след 1 час (партида от импорт – след 6) и се изпълнява най-много 10 минути (четенията – 30 минути, партидите от импорт – един час). Axis ги ограничава до 24 часа и 2 часа.

Операции​

opКласИзтриванеПаролиparams
testreadняма → резултат test (TestResult)
syncreadSyncParams {kinds?, user_attributes?, search_base?} → части objects, gpos
schemareadSchemaParams {classes?} → части schema
object.getreadGetParams {target, attributes?} → object с writable
gpo.reportreadGPORef {id} → части report
gpo.backupreadGPORef {id} → backup_id
user.createuser✓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.passworduser✓PasswordParams {target, password_key, must_change, unlock?}
user.unlockuserTargetParams {target}
user.enableuserEnableParams {target, enabled}
object.moveна целтаMoveParams {target, target_ou}
object.renameна целтаRenameParams {target, new_name}
object.deleteна целта✓DeleteParams {target, recursive?} (OU: рекурсивно)
group.creategroupGroupCreateParams {ou, name, sam_account_name?, scope, category, description?, attrs?}
member.changegroupMemberParams {group, add?, remove?} (DN, най-много 500 всяко)
ou.createouOUCreateParams {parent_dn, name, description?, protect_from_deletion}
pso.setpsoPSOParams {target?, name?, settings?, apply_to?, unapply?} (без target: създаване)
gpo.creategpoGPOCreateParams {name, comment?, link_to?}
gpo.updategpoGPOUpdateParams {id, name?, status?, comment?}
gpo.deletegpo✓GPORef {id}
gpo.linkgpoGPOLinkParams {id, target_dn, action: link|unlink|set, enabled?, enforced?, order?}
gpo.registrygpoGPORegistryParams {id, set?, remove?} (RegistryValue {scope, key, value_name, type?, value?}, най-много 100)
gpo.permissiongpoGPOPermissionParams {id, trustee, trustee_type, level, replace?}
bulk.usersuser✓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 на реда от импорта) – конекторът взема веднъж за задача, след като защитата му е позволила задачата:

  1. Конектор → Axis: POST /api/connector/v1/vertex/jobs/{jobID}/secrets {"operation_id": "…"} с ключа на конектора.
  2. Axis проверява, че задачата е активна задача на Vertex на този конектор (негова задача, тип vertex.op/vertex.read, неприключила, неизтекла, със същия operation_id); иначе 403 job_not_live.
  3. Axis → Vertex: POST /internal/v1/axis/secrets {tenant_id, connector_id, job_id, operation_id}.
  4. Vertex проверява, че операцията съществува за тази задача и конектор и не е приключила или изтекла, че тенантът все още е настроен с този конектор, че превключвателите за промени все още позволяват класа (и изтриването), че авторът все още има правото за операцията (активен потребител с ролята; author_revoked) и че тайните не са взети преди това (secrets_used). Едва тогава ги декриптира и отговаря със Secrets; запечатаните пароли на операцията се изтриват при предаването им.
  5. 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).

kinditemsИзпраща се от
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/secretsBridgeSecretsRequest → Secrets.
POST /internal/v1/axis/chunksBridgeChunk → 204.
POST /internal/v1/axis/jobs/finishedBridgeJobFinished → 204.

Крайните точки на конектора в Axis POST /vertex/jobs/{jobID}/secrets и /chunks съществуват само когато Vertex е настроен в Axis (RMM_VERTEX_INTERNAL_URL). Axis не пази тайни на Vertex. Когато Vertex не отговаря, Axis отговаря с 503 vertex_unavailable.

На конектора​

За всяка задача vertex.op конекторът:

  1. валидира OpJob (invalid_payload);
  2. при промени: отказва изтекла задача (expired), записва управляваните OU в локалната си защита и проверява защитата: приета (guard_pending), включени клас и изтриване (guard_denied), лимит в минута (rate_limited);
  3. взема тайните (unauthorized при отказ);
  4. стартира Windows PowerShell 5.1 (powershell.exe -NoProfile -NonInteractive -EncodedCommand <bootstrap>) и записва входа, заедно с вградения скрипт, кодиран в base64, на стандартния му вход; никоя стойност не се поставя в командния ред и не се вмъква в код;
  5. скриптът отваря PowerShell сесия към същата машина (New-PSSession -ComputerName $env:COMPUTERNAME) с идентификационните данни на AD акаунта и изпълнява операцията там с модулите ActiveDirectory и GroupPolicy, като преди запис проверява отново управляваните OU, защитата и забранените атрибути;
  6. превръща изходните редове на скрипта в напредък, части и резултат.

Скриптът извежда по един 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.
unauthorizedVertex е отказал заявката за тайни (съобщението казва защо).
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.

Ограничения​

ОграничениеСтойност
Управлявани OU100
Редове на задача bulk.users200
Членове на member.change (добавяне, премахване)по 500
Записи на конектора в минута (локална защита)1000 по подразбиране, от 1 до 2000
Стойности в регистъра на gpo.registry100
Стойности на атрибут / размер на стойност1000 / 64 KiB
Дължина на DN2048
Дължина на парола256
Елементи в част500
Тяло на заявка за създаване на задача (Axis)4 MiB