Problem: Nubus4K's - ox-connector - Groups with special character “&” can't be synced to Open-Xchange

Problem

When a group name contains the special character &, the group cannot be synchronized by the ox-connector to Open-Xchange. This causes the ox-connector consumer to stop processing further messages, leading to a broken synchronization state in the environment.

The issue occurs because the Open-Xchange SOAP provisioning API rejects illegal characters during object creation, based on a configurable validation regex.


Symptoms

  • Groups containing & in the name are not created in Open-Xchange.
  • The ox-connector stops processing NATS messages.
  • Error messages appear in the ox-connector pod logs.

Logs from the ox-connector pod

2026-02-25 11:04:52,617 INFO  [consumer.handle_message:245] Received message with topic: groups/group, sequence_number: 52, num_delivered: 1
2026-02-25 11:04:52,618 INFO  [__init__.get_group_objs:175] Group OX Test & Group will be OX Group
2026-02-25 11:04:52,619 INFO  [__init__.get_group_objs:206] Object('groups/group', 'cn=ox test & group,cn=groups,dc=swp-ldap,dc=internal') will be processed with context 1
2026-02-25 11:04:52,619 INFO  [groups.create_group:104] Creating Object('groups/group', 'cn=ox test & group,cn=groups,dc=swp-ldap,dc=internal')
2026-02-25 11:04:52,984 ERROR [consumer.create:343] Failed to handle creation
Traceback (most recent call last):
  File "/consumer.py", line 318, in create
    run(obj)
  File "/usr/local/lib/python3.11/dist-packages/univention/ox/provisioning/__init__.py", line 121, in run
    create_group(new_obj)
  File "/usr/local/lib/python3.11/dist-packages/univention/ox/provisioning/groups.py", line 123, in create_group
    group.create()
  ...
  File "/usr/local/lib/python3.11/dist-packages/zeep/wsdl/bindings/soap.py", line 329, in process_error
    raise Fault(
zeep.exceptions.Fault: Illegal chars: "&"; exceptionId 192484105-4
2026-02-25 11:04:52,988 WARNING [consumer.create:344] Illegal chars: "&"; exceptionId 192484105-4

Root cause exception from the OX SOAP backend

zeep.exceptions.Fault: Illegal chars: "&"

Root Cause

The Open-Xchange SOAP provisioning API validates the group name field (the technical identifier) against a configurable regular expression. By default, this regex rejects special characters such as &.

Important distinction:

  • name → technical identifier, strict regex validation (rejects &).
  • displayname → human-readable label, allows & and other special characters.

This can be reproduced directly on the OX backend CLI:

./creategroup -c 1 -A oxadmin -P <password> -n 'j&b' -d 'j&b'
→ Illegal chars: "&"; exceptionId -319055231-121
   (rejected on -n, accepted on -d)

Processing flow when the issue occurs

  1. The group object is received via the NATS provisioning stream.
  2. The ox-connector tries to create the group via SOAP.
  3. The SOAP call fails due to invalid character validation.
  4. The message remains in the NATS stream and blocks further processing.

Reference: Univention Bugzilla #59077


Investigation

Verify the LDAP group exists

kubectl exec -n $NAMESPACE ums-ldap-server-primary-0 -- ldapsearch -x \
  -D "$(kubectl get -n $NAMESPACE configmaps ums-ldap-server-primary -o json | jq -r '.data.ADMIN_DN')" \
  -w "$(kubectl get -n $NAMESPACE secrets ums-ldap-server-admin -o json | jq -r '.data.password' | base64 -d)" \
  -b "$(kubectl get -n $NAMESPACE configmaps ums-ldap-server-primary -o json | jq -r '.data.LDAP_BASEDN')" \
  cn="OX Test & Group"

See also: How can I use ldapsearch in Nubus openDesk?

Expected result: the LDAP object exists, confirming that the group was created in Nubus but failed to propagate to OX:

dn: cn=OX Test & Group,cn=groups,dc=swp-ldap,dc=internal
cn: OX Test & Group
gidNumber: 5024
isOxGroup: OK
oxContextIDNum: 1
uniqueMember: uid=test.ox.02,cn=users,dc=swp-ldap,dc=internal
uniqueMember: uid=test.ox.01,cn=users,dc=swp-ldap,dc=internal
objectClass: oxGroup
objectClass: univentionGroup
...

Solution

Permanent Fix (Recommended): Allow & in OX Group Names

Open-Xchange validates group name values against a configurable regular expression in /opt/open-xchange/etc/Group.properties. By default, this regex does not include &, which causes the SOAP fault Illegal chars: "&".

The displayname field is not subject to this validation — only name (the technical identifier) is affected.

Step 1: Check the current configuration

Inspect the active Group.properties inside the OX middleware pod to see which validation properties are currently set:

kubectl exec -n <ox-namespace> <core-mw-pod> -- \
  cat /opt/open-xchange/etc/Group.properties | grep -i CHECK_GROUP_UID

Example output (default configuration in opendesk):

kubectl exec -n $NAMESPACE open-xchange-core-mw-groupware-6b95f48d94-zdmj2 -- \
  cat /opt/open-xchange/etc/Group.properties | grep -i CHECK_GROUP_UID
Defaulted container "core-mw" out of: core-mw, init-middleware (init)
CHECK_GROUP_UID_FOR_NOT_ALLOWED_CHARS = false
CHECK_GROUP_UID_REGEXP = [ $@%\\.+a-zA-Z0-9_-]

:information_source: Important observation: Despite CHECK_GROUP_UID_FOR_NOT_ALLOWED_CHARS = false, the CHECK_GROUP_UID_REGEXP is still enforced by the OX backend. The boolean switch alone is not sufficient — the regex itself must be extended to permit &.

Step 2: Understand the two properties

Property Default Effect
CHECK_GROUP_UID_FOR_NOT_ALLOWED_CHARS false Boolean toggle; in practice insufficient to disable validation
CHECK_GROUP_UID_REGEXP [ $@%\.+a-zA-Z0-9_-] Character class applied to the group name; must include & to allow it

Step 3: Extend the regex in the opendesk Helm values

Edit helmfile/apps/open-xchange/values-openxchange.yaml.gotmpl and add / adjust the CHECK_GROUP_UID_REGEXP entry:

propertiesFiles:
  /opt/open-xchange/etc/Group.properties:
    CHECK_GROUP_UID_FOR_NOT_ALLOWED_CHARS: "false"
    CHECK_GROUP_UID_REGEXP: "[ $@%\\.+a-zA-Z0-9_&-]"

Variations:

  • Minimal (only add &):
    CHECK_GROUP_UID_REGEXP: "[ $@%\\.+a-zA-Z0-9_&-]"
    
  • Including German umlauts and additional common characters:
    CHECK_GROUP_UID_REGEXP: "[ $@%\\.+a-zA-Z0-9_&äöüÄÖÜß-]"
    

Step 4: Apply and restart

helmfile apply -e dev -n ${NAMESPACE}
kubectl rollout restart deployment/open-xchange-core-mw-groupware \
  -n ${NAMESPACE}

Step 5: Verify the fix

5.1 Confirm the new property is active inside the pod:

kubectl exec -n ${NAMESPACE} <core-mw-pod> -- \
  grep CHECK_GROUP_UID /opt/open-xchange/etc/Group.properties

Expected output:

CHECK_GROUP_UID_FOR_NOT_ALLOWED_CHARS = false
CHECK_GROUP_UID_REGEXP = [ $@%\.+a-zA-Z0-9_&-]

5.2 Test directly via the OX CLI (bypasses ox-connector / SOAP):

kubectl exec -n ${NAMESPACE} <core-mw-pod> -it -- \
  /opt/open-xchange/sbin/creategroup \
    -c 1 -A oxadmin -P <password> \
    -n 'test&group' -d 'test & group'

Expected: success message with group ID, no Illegal chars exception.

5.3 End-to-end test via UMC and ox-connector:

  • Create a group containing & in UMC.
  • Check ox-connector pod logs: the message must be processed and acknowledged without zeep.exceptions.Fault.
  • If a previously stuck message exists in the NATS stream, follow the Recovery section to remove it.

Caveats and notes

  • Position of - in the character class matters: keep it at the end (or escape it). Otherwise it will be interpreted as a range operator and silently break the regex.
  • & is an XML/SOAP special character. The fact that the OX server returns the fault confirms the SOAP request transports correctly — the issue is purely backend-side validation, which is resolved by this configuration change.
  • Only add characters you actually need. Including <, >, ", ' etc. may affect downstream consumers (search, URL handling, client UIs).
  • Supportability: Confirm with Open-Xchange / Univention support whether this customization is covered by your subscription before applying it to production.
  • Other object types: The same pattern likely applies to Users (CHECK_USER_UID_REGEXP in User.properties) and Resources (CHECK_RESOURCE_UID_REGEXP in Resource.properties). Extend analogously if needed.

Workaround: Rename the Affected Group

If adjusting the OX configuration is not feasible (policy, supportability concerns, or limited access), the simplest mitigation is to rename the group through UMC.

Steps

  1. Open UMC.
  2. Edit the affected group.
  3. Replace the & character in the group name with and, or remove it entirely.
  4. Save the changes.

Example:

OX Test & Group → OX Test and Group
OX Test & Group → OX Test Group

This prevents SOAP provisioning failures during synchronization. Note that this only avoids the issue for future syncs — if the connector is already stuck on a blocking message, additionally follow the Recovery section below.


Recovery: Remove Stuck Messages from the NATS Stream

If the ox-connector is already stuck and not processing messages, the blocking NATS message must be removed before the connector can resume.

:warning: Decision point: If you do not want to enable the NATS debug container or redeploy, you can skip directly to Step 3 (NATS debug session) using kubectl debug only.

Step 1: Enable the NATS debug container

The natsBox debug container of the bundled NATS is not deployed by default. Enable it via Helm values as described in the official documentation: Provisioning Service.

nubusProvisioning:
  nats:
    natsBox:
      enabled: true

Optional: edit the values file using yq

Install yq (see yq on GitHub):

snap install yq

Change into the opendesk deployment directory and update the values file:

cd ~/git/opendesk/opendesk-latest/opendesk/

cat helmfile/environments/dev/values.yaml.gotmpl \
  | yq ".nubusProvisioning.nats.natsBox.enabled=true" \
  > helmfile/environments/dev/values.yaml

Verify the configuration:

cat helmfile/environments/dev/values.yaml

Expected output (excerpt):

replicas:
  umsPortalServer: 2
certificate:
  issuerRef:
    name: "letsencrypt-prod-http"
jitsi:
  enabled: false
nubusProvisioning:
  nats:
    natsBox:
      enabled: true

Step 2: Redeploy the environment

helmfile apply -e dev -n ${NAMESPACE}

Step 3: NATS debug session

Access the NATS debug shell as described in the official docs.

Retrieve credentials:

NATS_USER=admin
NATS_PASSWORD=$(kubectl get secrets \
  -n ${NAMESPACE} \
  ums-provisioning-nats-admin \
  -o "jsonpath={.data.password}" | base64 -d)

Start the debug container:

kubectl debug \
  -n ${NAMESPACE} \
  -it ums-provisioning-nats-0 \
  --env NATS_USER="${NATS_USER}" \
  --env NATS_PASSWORD="${NATS_PASSWORD}" \
  --image=docker.io/natsio/nats-box:0.18.1-nonroot \
  -- /bin/sh

Create a NATS context:

nats context add nubus \
  --server nats://127.0.0.1:4222 \
  --user "${NATS_USER}" \
  --password "${NATS_PASSWORD}"

Expected output:

NATS Configuration Context "nubus"
  Server URLs: nats://127.0.0.1:4222
     Username: admin
     Password: *****************************************
         Path: /nsc/.config/nats/context/nubus.json
WARNING: Shell environment overrides in place using NATS_USER, NATS_PASSWORD

Step 4: Identify the problematic message sequence

List streams:

nats stream ls

Example output:

╭─────────────────────────────────────────────────────────────────────────────────────────────────╮
│                                             Streams                                             │
├─────────────────────────┬─────────────┬─────────────────────┬──────────┬─────────┬──────────────┤
│ Name                    │ Description │ Created             │ Messages │ Size    │ Last Message │
├─────────────────────────┼─────────────┼─────────────────────┼──────────┼─────────┼──────────────┤
│ stream:ox-connector     │             │ 2026-02-24 19:40:33 │ 2        │ 2.8 KiB │ 12m10s       │
│ ...                                                                                             │
╰─────────────────────────┴─────────────┴─────────────────────┴──────────┴─────────┴──────────────╯

The relevant stream is stream:ox-connector. Inspect it:

nats stream info stream:ox-connector

Look at the State section at the end of the output:

State:
  Messages: 2
  Bytes: 2.8 KiB
  First Sequence: 52 @ 2026-02-25 11:04:52
  Last Sequence:  53 @ 2026-02-25 11:15:48
  Active Consumers: 1
  Number of Subjects: 1

:information_source: The first sequence number is typically the blocking one. The exact number can also be read from the ox-connector logs:

INFO  [consumer.handle_message:245] Received message with topic: groups/group, sequence_number: 52, num_delivered: 1

Step 5: Remove only the problematic message

:stop_sign: Do not remove the entire stream with rm. Use rmm (stream rmm — securely removes an individual message) instead.

nats stream rmm stream:ox-connector

Interactive prompt:

? Message Sequence to remove 52
? Really remove message 52 from Stream stream:ox-connector Yes

Step 6: Restart the ox-connector pod

In k9s, simply kill the pod (<ctrl-k>); it will restart and continue processing the remaining messages. Alternatively:

kubectl rollout restart deployment/ox-connector -n ${NAMESPACE}

Verification

The pod should run healthy again and only the unblocked sequence remains in the stream:

nats stream info stream:ox-connector
...
State:
  Messages: 1
  Bytes: 1.4 KiB
  First Sequence: 53 @ 2026-02-25 11:15:48
  Last Sequence:  53 @ 2026-02-25 11:15:48

Successful processing logs:

2026-02-25 11:30:42,769 INFO  [consumer.start_listening_for_changes:219] Listening for changes in topics: {'oxmail/accessprofile', 'groups/group', ...}
2026-02-25 11:30:43,216 INFO  [consumer.handle_message:245] Received message with topic: groups/group, sequence_number: 53, num_delivered: 1
2026-02-25 11:30:43,632 INFO  [consumer.handle_message:296] Committing database entries
2026-02-25 11:30:43,646 INFO  [api.acknowledge_message_with_retries:155] Message 53 was acknowledged.

Important Notes

  • Always verify sequence numbers before removing NATS messages.
  • Use rmm instead of rm to avoid deleting the entire stream.
  • Pods may need to be restarted after configuration changes.
  • The permanent fix (regex extension) is preferred over the rename workaround for environments where group names with & are expected by business policy.

Conclusion

This issue is caused by the OX SOAP provisioning API’s regex-based validation of group name values. The default regex ([ $@%\.+a-zA-Z0-9_-]) does not include &, leading to SOAP faults that block the ox-connector NATS consumer.

  • Permanent fix: Extend CHECK_GROUP_UID_REGEXP in Group.properties via the opendesk Helm values to include &.
  • Workaround: Rename affected groups to remove &.
  • Recovery: Remove stuck NATS messages with nats stream rmm and restart the connector pod.

See also