Skip to content

Commit 679c854

Browse files
authored
feat: remove Dex and use Synapse LDAP (#26)
Simplify the stack and ease integration with other modules like NethVoice. Changes: * Add synapse ldap config and remove dex * Fix element template * Remove dex from README
1 parent 90495be commit 679c854

21 files changed

Lines changed: 81 additions & 246 deletions

File tree

‎README.md‎

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,13 @@ This module is in early development and should be used for testing purposes only
77
A NethServer 8 module for [Matrix](https://matrix.org/) chat service integration with LDAP authentication via OpenID Connect.
88

99
This module provides a complete Matrix chat solution including:
10-
- **Dex** (OpenID Connect Provider) - Connects to NethServer LDAP for user authentication
1110
- **Synapse** (Matrix Homeserver) - Core Matrix server with OIDC authentication
1211
- **Element Web** (Web Client) - Web interface for Matrix chat
1312
- **Cinny** (Web Client) - Alternative web interface for Matrix chat
1413

1514
## Features
1615

1716
- Seamless integration with NethServer 8 LDAP user directory
18-
- OpenID Connect authentication through Dex
1917
- Configurable domain names for both Matrix server and Element web client
2018
- Automatic SSL/TLS certificate management via Traefik
2119
- Matrix Federation support
@@ -42,16 +40,15 @@ Launch `configure-module`, by setting the following parameters:
4240
- `element_domain_name`: The fully qualified domain name for the Element web client (e.g., `chat.example.com`)
4341
- `cinny_domain_name`: The fully qualified domain name for the Cinny client (e.g., `cinny.example.com`)
4442
- `lets_encrypt`: Set to `true` to enable automatic SSL certificate generation via Let's Encrypt for both domains.
45-
- `dex_ldap_domain`: The LDAP domain name to be used by Dex for authentication. If not provided, the default LDAP domain will be used.
43+
- `ldap_domain`: The LDAP domain name to be used for authentication.
4644
- `mail_from`: The mail sender address for notification mails
4745

4846
Example:
4947

50-
api-cli run module/matrix1/configure-module --data '{"synapse_domain_name": "matrix.example.com", "element_domain_name": "chat.example.com", "cinny_domain_name": "cinny.example.com", "lets_encrypt": true, "dex_ldap_domain": "users.example.com", "mail_from": "noreply@example.com"}'
48+
api-cli run module/matrix1/configure-module --data '{"synapse_domain_name": "matrix.example.com", "element_domain_name": "chat.example.com", "cinny_domain_name": "cinny.example.com", "lets_encrypt": true, "ldap_domain": "users.example.com", "mail_from": "noreply@example.com"}'
5149

5250
The above command will:
53-
- Configure Dex as OpenID Connect provider with LDAP/AD backend
54-
- Start and configure the Synapse Matrix homeserver with OIDC authentication
51+
- Start and configure the Synapse Matrix homeserver with LDAP authentication
5552
- Deploy Element Web client and Cinny client configured to connect to the local Synapse instance
5653
- Set up Traefik routes for both domains with automatic SSL certificates
5754

@@ -108,11 +105,11 @@ The Element client supports customized themes, examples are added to the configu
108105

109106
### Cinny Client
110107

111-
Cinny is an alternative web client for Matrix chat, included in this module to provide users with a modern, lightweight interface. It can be accessed via a web browser and supports essential Matrix features such as messaging, rooms, and user authentication via OpenID Connect. Cinny is pre-configured to work with the Synapse server and Dex for seamless integration with NethServer 8 LDAP.
108+
Cinny is an alternative web client for Matrix chat, included in this module to provide users with a modern, lightweight interface. It can be accessed via a web browser and supports essential Matrix features such as messaging, rooms, and user authentication via OpenID Connect. Cinny is pre-configured to work with the Synapse server for seamless integration with NethServer 8 LDAP.
112109
## LDAP Integration
113110

114111
The module automatically discovers LDAP settings from the NethServer 8 configuration.
115-
Dex is configured to connect to the NS8 LDAP proxy to authenticate users against
112+
Synapse is configured to connect to the NS8 LDAP proxy to authenticate users against
116113
the centralized user directory.
117114

118115
The LDAP discovery happens every time the services start through the `bin/discover-ldap`
@@ -159,29 +156,22 @@ To setup the translation process:
159156

160157
Stack explanation:
161158
- [Synapse](https://element-hq.github.io/synapse/latest/setup/installation.html) is the standard de-fact Matrix homeserver implementation
162-
- Dex is used as OpenID Connect Provider to authenticate users against LDAP, it's not strictly required unless MAS (Matrix Authentication Service) is used (currently MAS is not implemented in this module)
163159
- Element web is the official Matrix web client, it can be replaced with other web client clients like [Cinny](https://cinny.in/)
164160

165161
Alternative stacks:
166-
- Remove Dex, use old LDAP integration [matrix-synapse-ldap3](https://github.com/matrix-org/matrix-synapse-ldap3) (currently deprecated
167-
)
168162
- Use [MAS](https://element-hq.github.io/matrix-authentication-service/) to add more authentication methods (SAML, OIDC, LDAP, etc.) and integrate with [authentik](https://goauthentik.io/) [1](https://integrations.goauthentik.io/chat-communication-collaboration/matrix-synapse/) [2](https://element-hq.github.io/matrix-authentication-service/setup/sso.html#authentik)
169163

170164
### To be done
171165

172166
If the current stack will be used, the following must be made.
173167

174-
Dex:
175-
- export preferred user name from LDAP to synapse (currently Dex exports only email and username)
176-
- add support for LDAP groups (currently Dex exports only users)
177-
178168
NethVoice CTI integration:
179169
- test current available client integrations
180170
- [Chatterbox](https://github.com/element-hq/chatterbox)
181171
- [Cactus](https://cactus.chat/)
182172
- Plain [Javascript SDK](https://github.com/matrix-org/matrix-js-sdk)
183173
- implement authentication, there 2 possible ways:
184-
- add support for OIDC ID inside [CTI middleware](https://github.com/nethesis/nethcti-middleware): the user authenticate against the Oauth 2 provider (like Dex or authentik, if 2FA is required) and the middleware uses the ID token to authenticate against Synapse with the obtained token
174+
- add support for OIDC ID inside [CTI middleware](https://github.com/nethesis/nethcti-middleware): the user authenticate against the Oauth 2 provider (like authentik, if 2FA is required) and the middleware uses the ID token to authenticate against Synapse with the obtained token
185175
- use normal LDAP auhtentication: the CTI middleware takes care also to authenticate against Synapse using LDAP credentials
186176

187177
### Future improvements

‎build-images.sh‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ buildah add "${container}" ui/dist /ui
3737
# Setup the entrypoint, ask to reserve three TCP ports and set a rootless container
3838
buildah config --entrypoint=/ \
3939
--label="org.nethserver.authorizations=traefik@node:routeadm cluster:accountconsumer" \
40-
--label="org.nethserver.tcp-ports-demand=5" \
40+
--label="org.nethserver.tcp-ports-demand=4" \
4141
--label="org.nethserver.rootfull=0" \
4242
--label="org.nethserver.max-per-node=1" \
43-
--label="org.nethserver.images=ghcr.io/dexidp/dex:v2.44.0-alpine docker.io/postgres:14.19-alpine ghcr.io/element-hq/synapse:v1.138.2 ghcr.io/element-hq/element-web:v1.12.0 ghcr.io/cinnyapp/cinny:v4.10.2" \
43+
--label="org.nethserver.images=docker.io/postgres:14.19-alpine ghcr.io/element-hq/synapse:v1.138.2 ghcr.io/element-hq/element-web:v1.12.0 ghcr.io/cinnyapp/cinny:v4.10.2" \
4444
"${container}"
4545
# Commit the image
4646
buildah commit "${container}" "${repobase}/${reponame}"

‎imageroot/actions/configure-module/20configure‎

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ synapse_domain_name = request.get('synapse_domain_name', '')
1818
element_domain_name = request.get('element_domain_name', '')
1919
cinny_domain_name = request.get('cinny_domain_name', '')
2020
lets_encrypt = request.get('lets_encrypt', False)
21-
dex_ldap_domain = request.get('dex_ldap_domain', '')
21+
ldap_domain = request.get('ldap_domain', '')
2222
mail_from = request.get('mail_from', '')
2323

2424
if not synapse_domain_name:
@@ -35,19 +35,18 @@ else:
3535
env_vars['SYNAPSE_DOMAIN_NAME'] = synapse_domain_name
3636
env_vars['ELEMENT_DOMAIN_NAME'] = element_domain_name
3737
env_vars['CINNY_DOMAIN_NAME'] = cinny_domain_name
38-
env_vars['DEX_LDAP_DOMAIN'] = dex_ldap_domain
38+
env_vars['LDAP_DOMAIN'] = ldap_domain
3939
env_vars['MAIL_FROM'] = mail_from
4040
env_vars['LETS_ENCRYPT'] = '1' if lets_encrypt else '0'
4141

4242
# Get allocated TCP ports from environment - NS8 allocates sequential ports
4343
tcp_ports = os.environ.get('TCP_PORTS', '20001').split(',')
4444

4545
# Assign ports to services
46-
env_vars['DEX_PORT'] = tcp_ports[0]
47-
env_vars['SYNAPSE_PORT'] = tcp_ports[1]
48-
env_vars['ELEMENT_WEB_PORT'] = tcp_ports[2]
49-
env_vars['CINNY_PORT'] = tcp_ports[3]
50-
env_vars['POSTGRES_PORT'] = tcp_ports[4]
46+
env_vars['SYNAPSE_PORT'] = tcp_ports[0]
47+
env_vars['ELEMENT_WEB_PORT'] = tcp_ports[1]
48+
env_vars['CINNY_PORT'] = tcp_ports[2]
49+
env_vars['POSTGRES_PORT'] = tcp_ports[3]
5150

5251
# Write updated configuration
5352
agent.write_envfile("config.env", env_vars)
@@ -70,21 +69,6 @@ response = agent.tasks.run(
7069
)
7170
agent.assert_exp(response['exit_code'] == 0)
7271

73-
# Configure Traefik routes for Dex OIDC server using Synapse domain
74-
response = agent.tasks.run(
75-
agent_id=agent.resolve_agent_id('traefik@node'),
76-
action='set-route',
77-
data={
78-
'instance': os.environ['MODULE_ID'] + '-dex',
79-
'url': 'http://127.0.0.1:' + env_vars['DEX_PORT'] + '/dex',
80-
'host': synapse_domain_name,
81-
'path': '/dex',
82-
'http2https': True,
83-
'lets_encrypt': lets_encrypt,
84-
},
85-
)
86-
agent.assert_exp(response['exit_code'] == 0)
87-
8872
# Configure Traefik routes for Element Web client
8973
if element_domain_name:
9074
response = agent.tasks.run(
@@ -132,4 +116,4 @@ else:
132116
)
133117

134118
# Bind the new domain, overriding previous values (unbind)
135-
agent.bind_user_domains([dex_ldap_domain])
119+
agent.bind_user_domains([ldap_domain])

‎imageroot/actions/configure-module/80start_services‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ source config.env
1616

1717
# Enable Matrix services
1818
systemctl --user enable postgresql.service
19-
systemctl --user enable dex.service
2019
systemctl --user enable synapse.service
2120

2221
# Enable element web client when enabled in UI
@@ -37,7 +36,6 @@ systemctl --user enable matrix.service
3736

3837
# Restart Matrix services
3938
systemctl --user restart postgresql.service
40-
systemctl --user restart dex.service
4139
systemctl --user restart synapse.service
4240

4341
# Restart element web client when enabled in UI, else stop it

‎imageroot/actions/configure-module/validate-input.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
"element_domain_name": "chat.example.com",
1010
"cinny_domain_name": "cinny.example.com",
1111
"lets_encrypt": true,
12-
"dex_ldap_domain": "users.example.com",
12+
"ldap_domain": "users.example.com",
1313
"mail_from": "noreply@example.com"
1414
}
1515
],
1616
"type": "object",
1717
"required": [
1818
"synapse_domain_name",
1919
"lets_encrypt",
20-
"dex_ldap_domain"
20+
"ldap_domain"
2121
],
2222
"properties": {
2323
"synapse_domain_name": {
@@ -54,9 +54,9 @@
5454
"type": "boolean",
5555
"description": "Enable Let's Encrypt certificate management"
5656
},
57-
"dex_ldap_domain": {
57+
"ldap_domain": {
5858
"type": "string",
59-
"description": "LDAP domain name for Dex authentication (optional)",
59+
"description": "LDAP domain name",
6060
"format": "domain-name"
6161
},
6262
"mail_from": {

‎imageroot/actions/get-configuration/20read‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ if os.path.exists("config.env"):
3131
config['synapse_domain_name'] = env_vars.get('SYNAPSE_DOMAIN_NAME', '')
3232
config['element_domain_name'] = env_vars.get('ELEMENT_DOMAIN_NAME', '')
3333
config['cinny_domain_name'] = env_vars.get('CINNY_DOMAIN_NAME', '')
34-
config['dex_ldap_domain'] = env_vars.get('DEX_LDAP_DOMAIN', '')
34+
config['ldap_domain'] = env_vars.get('LDAP_DOMAIN', '')
3535
config['mail_from'] = env_vars.get('MAIL_FROM', '')
3636
config['lets_encrypt'] = env_vars.get('LETS_ENCRYPT', '0') == '1'
3737
config['domains_list'] = domains
@@ -40,7 +40,7 @@ else:
4040
config['synapse_domain_name'] = ''
4141
config['element_domain_name'] = ''
4242
config['cinny_domain_name'] = ''
43-
config['dex_ldap_domain'] = ''
43+
config['ldap_domain'] = ''
4444
config['mail_from'] = ''
4545
config['lets_encrypt'] = False
4646
config['domains_list'] = domains

‎imageroot/actions/get-configuration/validate-output.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"element_domain_name": "chat.example.com",
1010
"cinny_domain_name": "cinny.example.com",
1111
"lets_encrypt": true,
12-
"dex_ldap_domain": "users.example.com",
12+
"ldap_domain": "users.example.com",
1313
"mail_from": "noreply@example.com"
1414
}
1515
],
@@ -31,9 +31,9 @@
3131
"type": "boolean",
3232
"description": "Enable Let's Encrypt certificate management"
3333
},
34-
"dex_ldap_domain": {
34+
"ldap_domain": {
3535
"type": "string",
36-
"description": "LDAP domain name for Dex authentication (optional)",
36+
"description": "LDAP domain name",
3737
"format": "domain-name"
3838
},
3939
"mail_from": {

‎imageroot/bin/configure-matrix‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,21 +60,13 @@ smarthost_env = agent.read_envfile("smarthost.env")
6060
env_vars.update(smarthost_env)
6161

6262
# Create configuration directories
63-
os.makedirs('dex-config', exist_ok=True)
6463
os.makedirs('synapse-config', exist_ok=True)
6564
os.makedirs('element-config', exist_ok=True)
6665
os.makedirs('cinny-config', exist_ok=True)
6766

6867
# Template directory
6968
template_dir = '../templates'
7069

71-
# Process Dex configuration
72-
with open(f'{template_dir}/dex-config.yaml', 'r') as f:
73-
dex_config = substitute_env_vars(f.read(), env_vars)
74-
75-
with open('dex-config/config.yaml', 'w') as f:
76-
f.write(dex_config)
77-
7870
# Process Synapse configuration
7971
with open(f'{template_dir}/synapse-homeserver.yaml', 'r') as f:
8072
synapse_config = substitute_env_vars(f.read(), env_vars)

‎imageroot/bin/discover-ldap‎

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ if os.path.exists("config.env"):
1818
else:
1919
config = {}
2020

21-
udomname = config.get('DEX_LDAP_DOMAIN','')
21+
udomname = config.get('LDAP_DOMAIN','')
2222

2323
try:
2424
odom = Ldapproxy().get_domain(udomname)
@@ -32,44 +32,34 @@ except:
3232
'port': 20000,
3333
'schema': 'rfc2307',
3434
'location': 'internal',
35-
'base_dn': 'dc=dex,dc=invalid',
36-
'bind_dn': 'cn=example,dc=dex,dc=invalid',
35+
'base_dn': 'dc=synapse,dc=invalid',
36+
'bind_dn': 'cn=example,dc=synapse,dc=invalid',
3737
'bind_password': 'invalid',
3838
}
3939

4040
tmpfile = "ldap.env." + str(os.getpid())
4141

4242
if odom['schema'] == "rfc2307":
4343
with open(tmpfile, "w") as denv:
44-
print('DEX_LDAP_ID=ldap', file=denv)
45-
print('DEX_LDAP_NAME=LDAP', file=denv)
46-
print('DEX_LDAP_PORT=' + str(odom['port']), file=denv)
47-
print('DEX_LDAP_BIND_DN=' + odom['bind_dn'], file=denv)
48-
print('DEX_LDAP_HOST=' + odom['host'], file=denv)
49-
print('DEX_LDAP_BIND_PASSWORD=' + odom['bind_password'], file=denv)
50-
print('DEX_LDAP_SCHEMA=' + odom['schema'], file=denv)
51-
print('DEX_LDAP_BASE_DN=' + odom['base_dn'], file=denv)
52-
print('DEX_LDAP_USER_FILTER=(objectClass=inetOrgPerson)', file=denv)
53-
print('DEX_LDAP_USERNAME=uid', file=denv)
54-
print('DEX_LDAP_ID_ATTR=uid', file=denv)
55-
print('DEX_LDAP_EMAIL_ATTR=mail', file=denv)
56-
print('DEX_LDAP_NAME_ATTR=cn', file=denv)
57-
print('DEX_LDAP_GROUP_FILTER=(objectClass=groupOfNames)', file=denv)
44+
print('LDAP_PORT=' + str(odom['port']), file=denv)
45+
print('LDAP_BIND_DN=' + odom['bind_dn'], file=denv)
46+
print('LDAP_HOST=' + odom['host'], file=denv)
47+
print('LDAP_BIND_PASSWORD=' + odom['bind_password'], file=denv)
48+
print('LDAP_SCHEMA=' + odom['schema'], file=denv)
49+
print('LDAP_BASE_DN=ou=people,' + odom['base_dn'], file=denv)
50+
print('LDAP_USERNAME=uid', file=denv)
51+
print('LDAP_EMAIL_ATTR=mail', file=denv)
52+
print('LDAP_NAME_ATTR=cn', file=denv)
5853
elif odom['schema'] == "ad":
5954
with open(tmpfile, "w") as denv:
60-
print('DEX_LDAP_ID=ad', file=denv)
61-
print('DEX_LDAP_NAME=ActiveDirectory', file=denv)
62-
print('DEX_LDAP_PORT=' + str(odom['port']), file=denv)
63-
print('DEX_LDAP_BIND_DN=' + odom['bind_dn'], file=denv)
64-
print('DEX_LDAP_HOST=' + odom['host'], file=denv)
65-
print('DEX_LDAP_BIND_PASSWORD=' + odom['bind_password'], file=denv)
66-
print('DEX_LDAP_SCHEMA=' + odom['schema'], file=denv)
67-
print('DEX_LDAP_BASE_DN=' + odom['base_dn'], file=denv)
68-
print('DEX_LDAP_USER_FILTER=(objectClass=person)', file=denv)
69-
print('DEX_LDAP_USERNAME=cn', file=denv)
70-
print('DEX_LDAP_ID_ATTR=cn', file=denv)
71-
print('DEX_LDAP_EMAIL_ATTR=mail', file=denv)
72-
print('DEX_LDAP_NAME_ATTR=cn', file=denv)
73-
print('DEX_LDAP_GROUP_FILTER=(objectClass=group)', file=denv)
55+
print('LDAP_PORT=' + str(odom['port']), file=denv)
56+
print('LDAP_BIND_DN=' + odom['bind_dn'], file=denv)
57+
print('LDAP_HOST=' + odom['host'], file=denv)
58+
print('LDAP_BIND_PASSWORD=' + odom['bind_password'], file=denv)
59+
print('LDAP_SCHEMA=' + odom['schema'], file=denv)
60+
print('LDAP_BASE_DN=cn=Users,' + odom['base_dn'], file=denv)
61+
print('LDAP_USERNAME=cn', file=denv)
62+
print('LDAP_EMAIL_ATTR=mail', file=denv)
63+
print('LDAP_NAME_ATTR=cn', file=denv)
7464

7565
os.replace(tmpfile, "ldap.env")

‎imageroot/etc/state-include.conf‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@ state/postgresql.pg_dump
22
state/database.env
33
state/config.json
44

5-
volumes/dex-data
65
volumes/synapse-data

0 commit comments

Comments
 (0)