ldap-rest 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.mjs +8 -7
- package/dist/abstract/ldapFlat.js +1 -1
- package/dist/abstract/plugin.js +1 -1
- package/dist/abstract/twakePlugin.js +1 -1
- package/dist/bin/index.js +1 -1
- package/dist/config/args.js +1 -1
- package/dist/config/schema.js +1 -1
- package/dist/lib/changeContext.js +1 -1
- package/dist/lib/entitySchema/validator.js +1 -0
- package/dist/lib/expressFormatedResponses.js +1 -1
- package/dist/lib/health.js +1 -0
- package/dist/lib/instanceProviders.js +1 -1
- package/dist/lib/ldapActions.js +1 -1
- package/dist/lib/metrics.js +1 -0
- package/dist/lib/requestContext.js +1 -1
- package/dist/lib/shutdown.js +1 -0
- package/dist/lib/staticFiles.js +1 -0
- package/dist/lib/storage/store.js +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/logger/winston.js +1 -1
- package/dist/plugins/auth/crowdsec.js +1 -1
- package/dist/plugins/auth/openidconnect.js +1 -1
- package/dist/plugins/demo/helloworld.js +1 -1
- package/dist/plugins/ldap/bulkImport.js +1 -1
- package/dist/plugins/ldap/flatGeneric.js +1 -1
- package/dist/plugins/ldap/groups.js +1 -1
- package/dist/plugins/ldap/organizations.js +1 -1
- package/dist/plugins/metrics.js +1 -0
- package/dist/plugins/rabbitmq.js +1 -1
- package/dist/plugins/scim/scim.js +1 -1
- package/dist/plugins/static.js +1 -1
- package/dist/plugins/storage/file.js +1 -1
- package/dist/plugins/storage/postgres.js +1 -1
- package/dist/plugins/storage/valkey.js +1 -1
- package/dist/plugins/storage.js +1 -1
- package/dist/plugins/twake/calendar.js +1 -1
- package/dist/plugins/twake/clouderyProvision.js +1 -1
- package/dist/plugins/twake/cozyProvision.js +1 -1
- package/dist/plugins/twake/drive.js +1 -1
- package/dist/plugins/twake/james.js +1 -1
- package/dist/plugins/twake/migrationProxy.js +1 -0
- package/dist/plugins/weblogs.js +1 -1
- package/dist/src/abstract/ldapFlat.d.ts +3 -13
- package/dist/src/abstract/plugin.d.ts +20 -0
- package/dist/src/abstract/twakePlugin.d.ts +23 -1
- package/dist/src/bin/index.d.ts +46 -1
- package/dist/src/config/args.d.ts +12 -1
- package/dist/src/lib/changeContext.d.ts +11 -0
- package/dist/src/lib/entitySchema/validator.d.ts +64 -0
- package/dist/src/lib/expressFormatedResponses.d.ts +19 -0
- package/dist/src/lib/health.d.ts +63 -0
- package/dist/src/lib/instanceProviders.d.ts +4 -0
- package/dist/src/lib/ldapActions.d.ts +28 -0
- package/dist/src/lib/metrics.d.ts +44 -0
- package/dist/src/lib/requestContext.d.ts +40 -0
- package/dist/src/lib/shutdown.d.ts +20 -0
- package/dist/src/lib/staticFiles.d.ts +33 -0
- package/dist/src/lib/storage/store.d.ts +8 -0
- package/dist/src/lib/utils.d.ts +9 -0
- package/dist/src/logger/winston.d.ts +4 -0
- package/dist/src/plugins/ldap/groups.d.ts +18 -3
- package/dist/src/plugins/ldap/organizations.d.ts +3 -5
- package/dist/src/plugins/metrics.d.ts +17 -0
- package/dist/src/plugins/rabbitmq.d.ts +37 -1
- package/dist/src/plugins/static.d.ts +12 -0
- package/dist/src/plugins/storage/file.d.ts +4 -0
- package/dist/src/plugins/storage/postgres.d.ts +7 -14
- package/dist/src/plugins/storage/valkey.d.ts +2 -15
- package/dist/src/plugins/storage.d.ts +8 -0
- package/dist/src/plugins/twake/drive.d.ts +8 -0
- package/dist/src/plugins/twake/james.d.ts +2 -0
- package/dist/src/plugins/twake/migrationProxy.d.ts +71 -0
- package/docs/client-development/api/reference.md +5 -9
- package/docs/client-development/api/rest-api.md +4 -10
- package/docs/client-development/schemas/README.md +60 -21
- package/docs/plugin-development/README.md +15 -13
- package/docs/plugin-development/dependencies.md +8 -0
- package/docs/plugin-development/hooks.md +1 -1
- package/docs/usage/README.md +1 -0
- package/docs/usage/configuration.md +49 -11
- package/docs/usage/operations.md +193 -0
- package/docs/usage/plugins/README.md +2 -0
- package/docs/usage/plugins/auth/authz-per-branch.md +2 -1
- package/docs/usage/plugins/integrations/README.md +8 -4
- package/docs/usage/plugins/integrations/mail-migration.md +102 -0
- package/docs/usage/plugins/integrations/scim.md +4 -2
- package/docs/usage/plugins/ldap/bulk-import.md +3 -1
- package/docs/usage/plugins/ldap/flat-generic.md +5 -2
- package/docs/usage/plugins/ldap/on-change.md +2 -1
- package/docs/usage/plugins/ldap/organizations.md +7 -7
- package/docs/usage/plugins/utilities/metrics.md +21 -0
- package/docs/usage/plugins/utilities/static.md +16 -8
- package/docs/usage/plugins/utilities/storage.md +7 -0
- package/docs/usage/plugins/utilities/weblogs.md +51 -30
- package/docs/usage/upgrading.md +71 -0
- package/package.json +11 -2
- package/static/schemas/example/README.md +1 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Operations
|
|
2
|
+
|
|
3
|
+
## Health and readiness
|
|
4
|
+
|
|
5
|
+
Two routes answer outside the API prefix, without authentication, and are
|
|
6
|
+
never cached:
|
|
7
|
+
|
|
8
|
+
| Route | Answers |
|
|
9
|
+
| -------------- | ------------------------------------------------------------- |
|
|
10
|
+
| `GET /healthz` | `200 {"status":"ok"}` as long as the process serves |
|
|
11
|
+
| `GET /readyz` | `200` when the server can serve, `503` when it cannot (below) |
|
|
12
|
+
|
|
13
|
+
`/readyz` answers a JSON body:
|
|
14
|
+
|
|
15
|
+
| `status` | Code | Meaning |
|
|
16
|
+
| ------------- | ---- | ------------------------------------------------------------------------------------------------------ |
|
|
17
|
+
| `starting` | 503 | The plugins are still loading (a host serving before `ready`; the shipped binary opens the port after) |
|
|
18
|
+
| `stopping` | 503 | The server was told to stop and finishes what is in flight; a new connection is refused |
|
|
19
|
+
| `unavailable` | 503 | The directory does not answer a read of its root DSE in 2 s (`ldap: failed`) |
|
|
20
|
+
| `ok` | 200 | Everything answers |
|
|
21
|
+
| `degraded` | 200 | The directory answers, a plugin reports a resource of its own down |
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{ "status": "degraded", "ldap": "ok", "plugins": { "rabbitmq": "degraded" } }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`plugins` lists the plugins that report their health; `failed` means the
|
|
28
|
+
report threw or did not answer in time. Only the directory takes a replica
|
|
29
|
+
out of rotation: a broken outbound service is visible in the body, and the
|
|
30
|
+
server keeps serving what does not need it.
|
|
31
|
+
|
|
32
|
+
The directory and the plugins are asked at once, so the answer takes 2 s at
|
|
33
|
+
most. The probe reads the root DSE, which any bound account may: a wrong
|
|
34
|
+
`--ldap-base` is not what it reports. The directory is probed on a connection of its own, bound as the
|
|
35
|
+
server, outside the connection pool and the search cache. One answer serves
|
|
36
|
+
5 seconds whatever the number of probes; the probes arriving meanwhile
|
|
37
|
+
share it.
|
|
38
|
+
|
|
39
|
+
Both routes are served before the authentication and the rate limiting,
|
|
40
|
+
and `/readyz` names the loaded plugins that report: on an ingress exposing
|
|
41
|
+
the server to the internet, keep the two paths to the cluster.
|
|
42
|
+
|
|
43
|
+
The Helm chart probes both routes over HTTP (`livenessProbe` and
|
|
44
|
+
`readinessProbe` in `values.yaml`).
|
|
45
|
+
|
|
46
|
+
### What the shipped plugins report
|
|
47
|
+
|
|
48
|
+
| Plugin | `degraded` when |
|
|
49
|
+
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
50
|
+
| `storage` | the backend refuses a `SELECT 1` or a `PING`, or the `file` backend's directory is not a directory this user may write in; the `ldap` backend has no answer of its own, the directory check above covers it |
|
|
51
|
+
| `rabbitmq` | a broker is configured and no connection to it stands: until the first publish or subscription opens one, and while the client reconnects |
|
|
52
|
+
| `james` | James answers its `GET /healthcheck` with 503, one of its own checks unhealthy, or not within 1.5 s; a James whose checks are only degraded answers 200, so `ok` here |
|
|
53
|
+
| `migrationProxy` | the migration proxy answers its `GET /healthcheck` with an error, or not within 1.5 s |
|
|
54
|
+
|
|
55
|
+
A check that leaves the probe to time out reads as `failed`.
|
|
56
|
+
|
|
57
|
+
### For plugin authors
|
|
58
|
+
|
|
59
|
+
A plugin reports its own resources with an optional `health()` method
|
|
60
|
+
returning `ok` or `degraded`, called at most once every 5 seconds; it must
|
|
61
|
+
answer within 2 s and never throw for an ordinary outage. The probe stops
|
|
62
|
+
waiting at 2 s but cancels nothing: a plugin bounds its own call, as
|
|
63
|
+
`james` does with its 1.5 s.
|
|
64
|
+
|
|
65
|
+
## Request id
|
|
66
|
+
|
|
67
|
+
Every response the API sends carries an `X-Request-Id` header: an
|
|
68
|
+
identifier the server generates for the request, the same one its writes
|
|
69
|
+
hand to the plugins and its log lines carry. An `X-Request-Id` the client sends is not taken: a
|
|
70
|
+
caller could otherwise make the writes of two requests look like one.
|
|
71
|
+
|
|
72
|
+
A proxy or an ingress generating an identifier of its own does not pass
|
|
73
|
+
it on either: log the answer's `X-Request-Id` there to match its lines with
|
|
74
|
+
the server's.
|
|
75
|
+
|
|
76
|
+
Every line logged while a request is served carries `requestId`, the lines
|
|
77
|
+
of the hooks it ran included, as a field of the JSON console output and as
|
|
78
|
+
`[<id>]` after the level in the log file. The line of `core/weblogs` carries
|
|
79
|
+
it too. A line without one was written outside any request: at startup, by
|
|
80
|
+
a scheduled task, by a message consumer, by a connection the server keeps.
|
|
81
|
+
Work shared between requests, a schema load or a readiness answer, logs
|
|
82
|
+
under the request that started it.
|
|
83
|
+
|
|
84
|
+
A line a hook writes carries `plugin`, the plugin that registered the hook,
|
|
85
|
+
and `op`, the hook's name, the `Hook error` line of a hook that threw
|
|
86
|
+
included: in the JSON console output as fields, in the log file as
|
|
87
|
+
`[<plugin> <op>]` after the request id. A line naming another `plugin`
|
|
88
|
+
itself keeps it, with no `op` from the hook, and reads `[<plugin>]` in the
|
|
89
|
+
file wherever it is written. These are the labels of
|
|
90
|
+
`ldap_rest_hook_failures_total`.
|
|
91
|
+
|
|
92
|
+
A line carrying an `error`, the reason of a failure, has it as a field of
|
|
93
|
+
the JSON console output and in parentheses after the message in the log
|
|
94
|
+
file; the file keeps no field other than these.
|
|
95
|
+
|
|
96
|
+
## Metrics
|
|
97
|
+
|
|
98
|
+
`core/metrics` serves Prometheus metrics on `GET /metrics`, on
|
|
99
|
+
`--metrics-port` (9464): a port of its own, outside the authentication,
|
|
100
|
+
the rate limiting and the request id, to keep to the cluster or the host.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
--plugin core/metrics --metrics-port 9464
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
| Metric | Type | Labels | Meaning |
|
|
107
|
+
| --------------------------------------------- | --------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
108
|
+
| `ldap_rest_http_request_duration_seconds` | histogram | `method`, `route`, `status` | Time to answer, by route template (`/api/v1/users/:id`; `none` unmatched or answered by a middleware, `core/static` included) |
|
|
109
|
+
| `ldap_rest_ldap_operation_duration_seconds` | histogram | `operation`, `result` | Time the directory takes: `bind`, `search`, `add`, `modify`, `rename`, `delete`; `ok`, the LDAP result code of a refusal (`32`: no such entry), `error` when it did not answer |
|
|
110
|
+
| `ldap_rest_ldap_pool_wait_seconds` | histogram | | Time to get a pooled connection, opening one included |
|
|
111
|
+
| `ldap_rest_ldap_pool_connections` | gauge | `state` | Pooled connections, `busy` or `idle` |
|
|
112
|
+
| `ldap_rest_ldap_pool_waiting` | gauge | | Operations waiting for a connection, the pool being full |
|
|
113
|
+
| `ldap_rest_ldap_pool_max` | gauge | | `--ldap-pool-size` |
|
|
114
|
+
| `ldap_rest_ldap_search_cache_total` | counter | `result` | Cacheable searches, `hit` or `miss`; nothing when the cache is off |
|
|
115
|
+
| `ldap_rest_hook_failures_total` | counter | `hook`, `plugin` | Notification hooks (`*done`, `onLdap*`) that threw, which the server logs and goes on |
|
|
116
|
+
| `ldap_rest_outbound_request_duration_seconds` | histogram | `target`, `status` | Time a service the server calls takes to answer: the calling plugin (`james`, `migrationProxy`, `calendar` or its old name `calendarResources`, `drive`, `cozyProvision`, `clouderyProvision`, `crowdsec`) or the instance provider (`cloudery`, `cozy-stack`); the HTTP status, `error` when it did not answer |
|
|
117
|
+
| `ldap_rest_unhandled_rejections_total` | counter | | Promise rejections nothing handled; counted by the binary, a host skipping `exitOnCrash()` leaves it at zero |
|
|
118
|
+
| `process_*`, `nodejs_*` | | | The process: CPU, memory, event loop lag, handles, GC |
|
|
119
|
+
|
|
120
|
+
A search the caller pages is timed page by page, what the caller does
|
|
121
|
+
between two pages left out; the readiness ping is not timed. The route
|
|
122
|
+
label is the template a route was declared with, never the URL: a label
|
|
123
|
+
per URL would grow without bound; under a router a plugin mounted, the
|
|
124
|
+
mount reads `*`. An outbound call is timed from the moment it is sent,
|
|
125
|
+
after the plugin's own queue, to the answer's headers, each attempt of a
|
|
126
|
+
retried call on its own; a plugin's health check that asks its service,
|
|
127
|
+
James's does, is a call like another. A plugin calling a service through
|
|
128
|
+
`this.fetch` has its calls counted under its name; the OpenID Connect
|
|
129
|
+
client's calls to its provider are made by its library, and not timed.
|
|
130
|
+
|
|
131
|
+
The instruments come from `prom-client` 15, kept while the server supports
|
|
132
|
+
Node 20, which its successor `@prometheus-io/client` dropped: the
|
|
133
|
+
deprecation notice `npm install` prints is expected.
|
|
134
|
+
|
|
135
|
+
The port listens on every interface. What to alert on: `/readyz` for a
|
|
136
|
+
replica out of rotation, `result="error"` in
|
|
137
|
+
`ldap_rest_ldap_operation_duration_seconds` (the directory not answering;
|
|
138
|
+
a refusal is a result code), a
|
|
139
|
+
`ldap_rest_ldap_pool_waiting` that stays above zero (raise
|
|
140
|
+
`--ldap-pool-size`), `ldap_rest_hook_failures_total` rising (a
|
|
141
|
+
synchronization that fails only in the log otherwise), `status="error"`
|
|
142
|
+
or the 5xx share in `ldap_rest_outbound_request_duration_seconds` by
|
|
143
|
+
`target` (a service the server depends on failing), the 5xx share of the
|
|
144
|
+
requests.
|
|
145
|
+
|
|
146
|
+
The Helm chart exposes the port on the Service with `metrics.enabled`, and
|
|
147
|
+
renders a `ServiceMonitor` for the Prometheus operator with
|
|
148
|
+
`metrics.serviceMonitor.enabled`; `core/metrics` still has to be in
|
|
149
|
+
`DM_PLUGINS`.
|
|
150
|
+
|
|
151
|
+
## Shutdown
|
|
152
|
+
|
|
153
|
+
On `SIGTERM` or `SIGINT`, the binary stops in order:
|
|
154
|
+
|
|
155
|
+
1. the listening socket closes: a new connection is refused, and
|
|
156
|
+
`/readyz` answers 503 `stopping` on the connections still open;
|
|
157
|
+
2. idle keep-alive connections close at once; the others close with the
|
|
158
|
+
response they carry, sent with `Connection: close`, or are cut when the
|
|
159
|
+
drain is spent: `--shutdown-timeout` (20 s) less the 5 s kept for the
|
|
160
|
+
steps below, 1 s at least;
|
|
161
|
+
3. every plugin's `close()` runs, at once: the broker connection, the
|
|
162
|
+
storage backend;
|
|
163
|
+
4. the directory connections are closed;
|
|
164
|
+
5. the signal ends the process, with its usual code (143 on `SIGTERM`).
|
|
165
|
+
|
|
166
|
+
The whole takes `--shutdown-timeout` at most: a `close()` that does not
|
|
167
|
+
settle in what remains of the budget is logged and left behind, by
|
|
168
|
+
`shutdown()` itself, so a host gets the same bound, and the directory
|
|
169
|
+
connections are closed anyway. The drain and the closes get 1 s each at
|
|
170
|
+
least, so a budget under 2 s is exceeded. Kubernetes takes the pod out
|
|
171
|
+
of its endpoints as it sends the signal, and the proxies follow a moment
|
|
172
|
+
later: a request routed meanwhile is refused. A `preStop` hook sleeping a
|
|
173
|
+
few seconds, before the signal, closes that window; the chart's `lifecycle`
|
|
174
|
+
value takes one. The sleep and `--shutdown-timeout` together must stay
|
|
175
|
+
under `terminationGracePeriodSeconds`, 30 s by default, which the chart
|
|
176
|
+
leaves to Kubernetes: 5 s and 20 s fit.
|
|
177
|
+
|
|
178
|
+
A host assembling the server in its own process calls `shutdown()` itself,
|
|
179
|
+
`shutdownOnSignals()` when it wants the same behaviour on a signal, and
|
|
180
|
+
`exitOnCrash()` for the policy below; without the latter, Node's own action
|
|
181
|
+
on an uncaught exception applies.
|
|
182
|
+
|
|
183
|
+
An exception nothing caught leaves the process in a state nobody knows: it
|
|
184
|
+
is logged, the shutdown above runs, and the process exits with 1, within
|
|
185
|
+
`--shutdown-timeout` whatever hangs, for its supervisor to start it again.
|
|
186
|
+
A promise rejection nothing handled is logged and counted, and the process
|
|
187
|
+
keeps serving.
|
|
188
|
+
|
|
189
|
+
### For plugin authors
|
|
190
|
+
|
|
191
|
+
A plugin holding a connection, a timer or a queue implements `close()`,
|
|
192
|
+
called once after the server stopped serving; it must settle, a failure is
|
|
193
|
+
logged and does not hold the others.
|
|
@@ -57,6 +57,7 @@ Connect to external systems:
|
|
|
57
57
|
| ---------------------------------------------------- | ------------------------------------ |
|
|
58
58
|
| [james-mail](integrations/james-mail.md) | Apache James synchronization |
|
|
59
59
|
| [james-mailboxes](integrations/james-mailboxes.md) | Apache James team mailboxes |
|
|
60
|
+
| [mail-migration](integrations/mail-migration.md) | Twake Mail migration proxy |
|
|
60
61
|
| [calendar](integrations/calendar.md) | Twake Calendar resources and users |
|
|
61
62
|
| [app-accounts](integrations/app-accounts.md) | Applicative accounts API |
|
|
62
63
|
| [lifecycle-events](integrations/lifecycle-events.md) | Account lifecycle events to RabbitMQ |
|
|
@@ -70,6 +71,7 @@ Connect to external systems:
|
|
|
70
71
|
| ------------------------------- | ------------------------------------------------------------------- |
|
|
71
72
|
| [static](utilities/static.md) | Static file server |
|
|
72
73
|
| [weblogs](utilities/weblogs.md) | HTTP logging |
|
|
74
|
+
| [metrics](utilities/metrics.md) | Prometheus metrics on a port of their own |
|
|
73
75
|
| [storage](utilities/storage.md) | Keyed storage with a deadline, in LDAP, files, PostgreSQL or Valkey |
|
|
74
76
|
|
|
75
77
|
## Plugin Dependencies
|
|
@@ -4,10 +4,11 @@ Plugins for connecting LDAP-Rest to external systems.
|
|
|
4
4
|
|
|
5
5
|
## Apache James
|
|
6
6
|
|
|
7
|
-
| Plugin | Description
|
|
8
|
-
| ------------------------------------- |
|
|
9
|
-
| [james-mail](james-mail.md) | User and domain synchronization
|
|
10
|
-
| [james-mailboxes](james-mailboxes.md) | Team mailbox management
|
|
7
|
+
| Plugin | Description |
|
|
8
|
+
| ------------------------------------- | -------------------------------- |
|
|
9
|
+
| [james-mail](james-mail.md) | User and domain synchronization |
|
|
10
|
+
| [james-mailboxes](james-mailboxes.md) | Team mailbox management |
|
|
11
|
+
| [mail-migration](mail-migration.md) | Migrated-users list of the proxy |
|
|
11
12
|
|
|
12
13
|
## Twake
|
|
13
14
|
|
|
@@ -45,6 +46,9 @@ core/twake/james
|
|
|
45
46
|
core/twake/calendar
|
|
46
47
|
└─ requires: core/ldap/onChange
|
|
47
48
|
|
|
49
|
+
core/twake/migrationProxy
|
|
50
|
+
└─ requires: core/ldap/onChange
|
|
51
|
+
|
|
48
52
|
core/twake/lifecycleEvents
|
|
49
53
|
├─ requires: core/ldap/onChange
|
|
50
54
|
└─ requires: core/rabbitmq
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Mail migration proxy
|
|
2
|
+
|
|
3
|
+
`core/twake/migrationProxy` keeps the migrated-users list of the
|
|
4
|
+
[Twake Mail migration proxy](https://github.com/linagora/tmail-backend/blob/master/docs/modules/ROOT/pages/migration-proxy/objective.adoc)
|
|
5
|
+
in step with the directory, while a mail platform is moved to Twake Mail.
|
|
6
|
+
|
|
7
|
+
The proxy sits in front of the old and the new mail backends. It relays
|
|
8
|
+
each IMAP (and, when enabled, submission) connection to the backend hosting
|
|
9
|
+
the authenticated user, and routes each inbound recipient to the backend
|
|
10
|
+
hosting it. An address on its migrated list lives on the new backend; any
|
|
11
|
+
other address lives on the old one. An account created during the migration
|
|
12
|
+
has no mailbox on the old system, so it has to be on the list before its
|
|
13
|
+
first login and its first mail: this plugin puts it there, and keeps the
|
|
14
|
+
addresses of a migrated account on the list as they change.
|
|
15
|
+
|
|
16
|
+
Load it for the duration of the migration, next to `core/twake/james`, and
|
|
17
|
+
drop it once the old backend is gone. It requires `core/ldap/onChange`.
|
|
18
|
+
|
|
19
|
+
## Scope
|
|
20
|
+
|
|
21
|
+
Only the entries under `--migration-proxy-branch` are followed, the
|
|
22
|
+
branches holding the accounts whose addresses the proxy routes: users, and
|
|
23
|
+
the groups whose address is a team mailbox or a mailing list when those are
|
|
24
|
+
created during the migration, as inbound mail for them is routed by
|
|
25
|
+
recipient too. Keep out the branches whose entries carry an address that is
|
|
26
|
+
not their own, the applicative accounts of
|
|
27
|
+
[app-accounts](app-accounts.md) for instance: they would put their owner on
|
|
28
|
+
the list or take it off.
|
|
29
|
+
|
|
30
|
+
The addresses of an entry are every value of `--mail-attribute` and of
|
|
31
|
+
`--alias-attribute`, the first mail being the one that says where the
|
|
32
|
+
account lives. The proxy folds usernames to lower case, so a change of case
|
|
33
|
+
is no change.
|
|
34
|
+
|
|
35
|
+
## Behaviour
|
|
36
|
+
|
|
37
|
+
Through the `onLdapEntryChange` hook, after the write has answered, one
|
|
38
|
+
entry at a time:
|
|
39
|
+
|
|
40
|
+
- An entry that gains its mail, when created or later, is put on the list
|
|
41
|
+
with all its addresses: `PUT /migratedUsers/{address}`.
|
|
42
|
+
- An entry that keeps its mail and whose addresses change asks the proxy
|
|
43
|
+
about its first mail (`HEAD /migratedUsers/{mail}`). When the account is
|
|
44
|
+
migrated, the addresses it gained are put on the list, then the ones it
|
|
45
|
+
lost are taken off, once every addition holds; a user the proxy does not
|
|
46
|
+
know is on the old system, and its change is left to the migration, which
|
|
47
|
+
puts the new addresses on the list when it moves the mailbox.
|
|
48
|
+
- An entry deleted, or losing its mail, is taken off the list with all its
|
|
49
|
+
addresses: `DELETE /migratedUsers/{address}`. A tombstone or a trashed
|
|
50
|
+
entry keeps its address, so it stays on the list until it is erased.
|
|
51
|
+
|
|
52
|
+
The plugin cannot tell a new account from an existing one whose mail was
|
|
53
|
+
cleared and set again, or from one created anew over its tombstone: those
|
|
54
|
+
are put on the list too. Marking the existing accounts migrated is the
|
|
55
|
+
migration's job, through the proxy's webadmin or its `migration:switch`
|
|
56
|
+
exchange.
|
|
57
|
+
|
|
58
|
+
### Failures
|
|
59
|
+
|
|
60
|
+
A call the proxy answers with 500, 502, 503 or 504 is made again,
|
|
61
|
+
`--migration-proxy-retry-attempts` times in all, after
|
|
62
|
+
`--migration-proxy-retry-delay` milliseconds times the number of attempts
|
|
63
|
+
made. A refusal, such as 400 for an address the proxy does not take as a
|
|
64
|
+
username or 401 for a wrong token, and a call the proxy does not answer at
|
|
65
|
+
all, connection refused or reset, are not. Each failed attempt logs a warning, the last failure an
|
|
66
|
+
error naming the address, the call and the entry. Every call is counted in
|
|
67
|
+
the [outbound metrics](../../operations.md#metrics), under the target
|
|
68
|
+
`migrationProxy`.
|
|
69
|
+
|
|
70
|
+
A failed `PUT` leaves the account routed to the old backend, which has no
|
|
71
|
+
mailbox for it: watch those errors, and put the address on the list by hand
|
|
72
|
+
through the proxy's webadmin. When the proxy has not said whether a renamed
|
|
73
|
+
account is migrated, nothing is changed and the error is logged.
|
|
74
|
+
|
|
75
|
+
## Where an account lives
|
|
76
|
+
|
|
77
|
+
`GET /api/v1/users/:user/mail-migration` answers `{ "migrated": true }`
|
|
78
|
+
when the proxy routes the user to the new backend, `{ "migrated": false }`
|
|
79
|
+
otherwise, from the proxy's `HEAD`. The user is found by `uid` across the
|
|
80
|
+
directory, and answered only when the caller may read the entry: another
|
|
81
|
+
one gets 404, as a user that is not there. A user without mail gets 400,
|
|
82
|
+
and a proxy that does not answer 502.
|
|
83
|
+
|
|
84
|
+
## Readiness
|
|
85
|
+
|
|
86
|
+
The plugin asks the proxy's `GET /healthcheck` for `/readyz`, and reports
|
|
87
|
+
`degraded` when it answers an error or not within 1.5 s, see
|
|
88
|
+
[what the shipped plugins report](../../operations.md#what-the-shipped-plugins-report).
|
|
89
|
+
|
|
90
|
+
## Configuration
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
--plugin core/ldap/onChange \
|
|
94
|
+
--plugin core/twake/james \
|
|
95
|
+
--plugin core/twake/migrationProxy \
|
|
96
|
+
--migration-proxy-url http://migration-proxy:8000 \
|
|
97
|
+
--migration-proxy-token "$MIGRATION_PROXY_TOKEN" \
|
|
98
|
+
--migration-proxy-branch "ou=users,dc=example,dc=com"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The options are listed in
|
|
102
|
+
[configuration](../../configuration.md#coretwakemigrationproxy).
|
|
@@ -734,10 +734,12 @@ Notable fields:
|
|
|
734
734
|
- `schemaUrls` — direct download URLs of the schema files SCIM reads
|
|
735
735
|
(`User.json` and `Group.json` under `--schemas-path`, with the
|
|
736
736
|
`default-mapping.json` template beside them when it is there), when a
|
|
737
|
-
`core/static` plugin serves them,
|
|
737
|
+
`core/static` plugin serves them merged, through its schema routes, under
|
|
738
|
+
its own `--static-name` (editor
|
|
738
739
|
UIs typically fetch them to render attribute pickers). Among plugins
|
|
739
740
|
serving different paths, the one registered as `static` is preferred; a
|
|
740
|
-
path an earlier plugin already serves
|
|
741
|
+
path an earlier plugin already serves, lies under or holds, is left to
|
|
742
|
+
that one. Absent when
|
|
741
743
|
none serves them: read it with a guard.
|
|
742
744
|
- `resourceTypes[].mapping` — the live SCIM↔LDAP mapping used server-side, so
|
|
743
745
|
UI builders can render filter helpers / attribute selectors without second
|
|
@@ -174,7 +174,9 @@ mail: jdoe@company.com
|
|
|
174
174
|
|
|
175
175
|
### Fixed Attributes
|
|
176
176
|
|
|
177
|
-
Attributes marked as `"fixed": true` in the schema are automatically added
|
|
177
|
+
Attributes marked as `"fixed": true` in the schema are automatically added. A
|
|
178
|
+
row with a value under such a column fails, whatever the case of the column
|
|
179
|
+
name; an empty cell is ignored as any other:
|
|
178
180
|
|
|
179
181
|
**Schema**:
|
|
180
182
|
|
|
@@ -74,7 +74,9 @@ Each schema file must include an `entity` metadata section and attribute definit
|
|
|
74
74
|
|
|
75
75
|
- **name**: Internal name used for hooks (e.g., `ldap{name}add`)
|
|
76
76
|
- **mainAttribute**: DN attribute (e.g., `uid`, `cn`)
|
|
77
|
-
- **objectClass**:
|
|
77
|
+
- **objectClass**: the classes of every entry created, whatever `objectClass`
|
|
78
|
+
the request carried; the schema's `objectClass` attribute only describes
|
|
79
|
+
them
|
|
78
80
|
- **singularName**: Used in API paths and messages (e.g., "user")
|
|
79
81
|
- **pluralName**: Used in API paths (e.g., "users")
|
|
80
82
|
- **base**: LDAP base DN, supports config placeholders like `{ldap_base}`
|
|
@@ -138,7 +140,8 @@ never what it holds.
|
|
|
138
140
|
|
|
139
141
|
Fixed attributes:
|
|
140
142
|
|
|
141
|
-
- Auto-provisioned with default value on creation
|
|
143
|
+
- Auto-provisioned with default value on creation; `objectClass` takes the
|
|
144
|
+
classes of the entity metadata instead
|
|
142
145
|
- Cannot be modified after creation
|
|
143
146
|
- Cannot be deleted
|
|
144
147
|
|
|
@@ -28,7 +28,8 @@ side:
|
|
|
28
28
|
says who made the write, and through which door:
|
|
29
29
|
|
|
30
30
|
- `actor`: the caller's `userName`, or its `user` when it has none;
|
|
31
|
-
- `requestId`:
|
|
31
|
+
- `requestId`: the answer's `X-Request-Id`, the same for every write of one
|
|
32
|
+
request, see [operations](../../operations.md#request-id);
|
|
32
33
|
- `source`: `scim` for the SCIM API, `rest` otherwise.
|
|
33
34
|
|
|
34
35
|
A write no request is behind, such as a scheduled task's, gets `{}`. The LDAP
|
|
@@ -28,13 +28,13 @@ This plugin manages hierarchical organizations stored as `organizationalUnit` (o
|
|
|
28
28
|
|
|
29
29
|
### Configuration Options
|
|
30
30
|
|
|
31
|
-
| Argument | Environment Variable | Default
|
|
32
|
-
| ----------------------------------------------------------- | ------------------------------------- |
|
|
33
|
-
| `--ldap-top-organization` | `DM_LDAP_TOP_ORGANIZATION` | _Required_
|
|
34
|
-
| `--ldap-organization-class` / `--ldap-organization-classes` | `DM_LDAP_ORGANIZATION_CLASSES` | `
|
|
35
|
-
| `--ldap-organization-link-attribute` | `DM_LDAP_ORGANIZATION_LINK_ATTRIBUTE` | `twakeDepartmentLink`
|
|
36
|
-
| `--ldap-organization-path-attribute` | `DM_LDAP_ORGANIZATION_PATH_ATTRIBUTE` | `twakeDepartmentPath`
|
|
37
|
-
| `--ldap-organization-path-separator` | `DM_LDAP_ORGANIZATION_PATH_SEPARATOR` | `" / "`
|
|
31
|
+
| Argument | Environment Variable | Default | Description |
|
|
32
|
+
| ----------------------------------------------------------- | ------------------------------------- | ---------------------------------------- | --------------------------------------------------- |
|
|
33
|
+
| `--ldap-top-organization` | `DM_LDAP_TOP_ORGANIZATION` | _Required_ | DN of the top-level organization |
|
|
34
|
+
| `--ldap-organization-class` / `--ldap-organization-classes` | `DM_LDAP_ORGANIZATION_CLASSES` | `top,organizationalUnit,twakeDepartment` | Object classes for organizations |
|
|
35
|
+
| `--ldap-organization-link-attribute` | `DM_LDAP_ORGANIZATION_LINK_ATTRIBUTE` | `twakeDepartmentLink` | Attribute for linking users/groups to organizations |
|
|
36
|
+
| `--ldap-organization-path-attribute` | `DM_LDAP_ORGANIZATION_PATH_ATTRIBUTE` | `twakeDepartmentPath` | Attribute for the organizational path |
|
|
37
|
+
| `--ldap-organization-path-separator` | `DM_LDAP_ORGANIZATION_PATH_SEPARATOR` | `" / "` | Separator for path components |
|
|
38
38
|
|
|
39
39
|
### Important Notes
|
|
40
40
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Metrics Plugin
|
|
2
|
+
|
|
3
|
+
Prometheus metrics of the server, on a port of their own.
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
--plugin core/metrics --metrics-port 9464
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`--metrics-port` (`DM_METRICS_PORT`, 9464) is the port `GET /metrics` is
|
|
12
|
+
served on, see [configuration](../../configuration.md#coremetrics).
|
|
13
|
+
|
|
14
|
+
The port is separate from the API's: nothing on it goes through the
|
|
15
|
+
authentication, the rate limiting or the request id, so it must not be
|
|
16
|
+
reachable from outside the cluster or the host. The Helm chart exposes it
|
|
17
|
+
on the Service and to a Prometheus operator with `metrics.enabled` and
|
|
18
|
+
`metrics.serviceMonitor.enabled`.
|
|
19
|
+
|
|
20
|
+
What is measured, and how to read it, is in
|
|
21
|
+
[operations](../../operations.md#metrics).
|
|
@@ -242,12 +242,7 @@ for instance to `/static/index.html`.
|
|
|
242
242
|
|
|
243
243
|
### Example 4: Multiple Static Plugins
|
|
244
244
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
```bash
|
|
248
|
-
--plugin core/static \
|
|
249
|
-
--static-path ./static
|
|
250
|
-
```
|
|
245
|
+
One plugin serves several applications from sub-directories:
|
|
251
246
|
|
|
252
247
|
```
|
|
253
248
|
./static/
|
|
@@ -259,14 +254,27 @@ Not directly supported, but you can nest directories:
|
|
|
259
254
|
users.json
|
|
260
255
|
```
|
|
261
256
|
|
|
262
|
-
Access via:
|
|
263
|
-
|
|
264
257
|
```
|
|
265
258
|
/static/docs/index.html
|
|
266
259
|
/static/app/index.html
|
|
267
260
|
/static/schemas/users.json
|
|
268
261
|
```
|
|
269
262
|
|
|
263
|
+
A second directory takes a second instance, loaded under a name of its own
|
|
264
|
+
with its own `static_name` and `static_path`
|
|
265
|
+
([`module:name:{json}`](../auth/README.md#serving-several-populations-from-one-server)):
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
--plugin core/static --static-path ./static \
|
|
269
|
+
--plugin 'core/static:console:{"static_name":"console","static_path":"./console"}'
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Give them paths that do not nest: `console` and `console/app` would have the
|
|
273
|
+
first one answer requests meant for the second. The plugins that announce a
|
|
274
|
+
schema URL (groups, organizations, flat entities, SCIM) ask every instance,
|
|
275
|
+
the one registered as `static` first, and leave out one whose path lies under
|
|
276
|
+
an earlier one's, or holds it.
|
|
277
|
+
|
|
270
278
|
## Security Considerations
|
|
271
279
|
|
|
272
280
|
### Path Traversal
|
|
@@ -110,6 +110,13 @@ answer "absent" with a warning and writes fail at once instead of waiting.
|
|
|
110
110
|
does not answer or a package that is not installed; the server starts anyway,
|
|
111
111
|
and every operation tries to connect again.
|
|
112
112
|
|
|
113
|
+
## Readiness
|
|
114
|
+
|
|
115
|
+
`GET /readyz` lists the plugin as `degraded` when the backend does not
|
|
116
|
+
answer; the `ldap` backend keeps its records in the directory the probe
|
|
117
|
+
checks on its own, see
|
|
118
|
+
[operations](../../operations.md#what-the-shipped-plugins-report).
|
|
119
|
+
|
|
113
120
|
## Expiry
|
|
114
121
|
|
|
115
122
|
Two rules the backends do not get to reinterpret:
|