Ver a proveniência

Upgrade LiveKit JWT Service to 0.7.0

Document the multi-SFU publishing policy and re-enable the repaired image healthcheck. Verify trusted and remote grants and room creation against a real SFU.

Supersedes #5596.
pull/5597/head
Slavi Pantaleev há 1 dia
ascendente
cometimento
6e9fcfc9d6
11 ficheiros alterados com 299 adições e 107 eliminações
  1. +11
    -0
      CHANGELOG.md
  2. +25
    -1
      docs/configuring-playbook-livekit-jwt-service.md
  3. +3
    -1
      docs/configuring-playbook-matrix-rtc.md
  4. +2
    -1
      group_vars/matrix_servers
  5. +4
    -0
      molecule-shared/vars.yml
  6. +8
    -16
      roles/custom/matrix-livekit-jwt-service/defaults/main.yml
  7. +6
    -4
      roles/custom/matrix-livekit-jwt-service/molecule/default/files/openid-fixture.py
  8. +4
    -8
      roles/custom/matrix-livekit-jwt-service/molecule/default/molecule.yml
  9. +60
    -0
      roles/custom/matrix-livekit-jwt-service/molecule/default/prepare.yml
  10. +117
    -0
      roles/custom/matrix-livekit-jwt-service/molecule/default/tasks/verify-token.yml
  11. +59
    -76
      roles/custom/matrix-livekit-jwt-service/molecule/default/verify.yml

+ 11
- 0
CHANGELOG.md Ver ficheiro

@@ -1,3 +1,14 @@
# 2026-09-18

## LiveKit JWT Service 0.7.0 and federated calls

LiveKit JWT Service, part of the [Matrix RTC stack](docs/configuring-playbook-matrix-rtc.md), has been upgraded to **0.7.0**. This release fixes the image healthcheck, which the playbook enables again.

The service now restricts media publishing to users from trusted homeservers (your `matrix_domain` by default). Other federated users can receive media from your SFU but publish on their own homeserver's SFU. This is an [intentional upstream change for multi-SFU calls](https://github.com/element-hq/lk-jwt-service/issues/238#issuecomment-5709655222), already supported by current Element Call in its `compatibility` mode.

Older clients or callers relying on your SFU for publishing may join but be unable to unmute, enable video, or share their screen. See [Federated calls and trusted homeservers](docs/configuring-playbook-livekit-jwt-service.md#federated-calls-and-trusted-homeservers) for client requirements, granting access to trusted homeservers, and a temporary 0.6.0 pin.


# 2026-08-20

## MatrixRTC transports are advertised in the client well-known again


+ 25
- 1
docs/configuring-playbook-livekit-jwt-service.md Ver ficheiro

@@ -1,5 +1,5 @@
<!--
SPDX-FileCopyrightText: 2025 Slavi Pantaleev
SPDX-FileCopyrightText: 2025 - 2026 Slavi Pantaleev

SPDX-License-Identifier: AGPL-3.0-or-later
-->
@@ -16,3 +16,27 @@ Take a look at:

- `roles/custom/matrix-livekit-jwt-service/defaults/main.yml` for some variables that you can customize via your `vars.yml` file
- `roles/custom/matrix-livekit-jwt-service/templates/env.j2` for the component's default configuration.

## Federated calls and trusted homeservers

Since LiveKit JWT Service 0.7.0, only users from homeservers listed in `matrix_livekit_jwt_service_environment_variable_livekit_full_access_homeservers_list` may publish audio, video, or screen shares on your SFU. The list defaults to your `matrix_domain`. Other federated users receive media here and publish on their own homeserver's SFU, using clients with multi-SFU support. See [upstream's explanation](https://github.com/element-hq/lk-jwt-service/issues/238#issuecomment-5709655222).

Current Element Call supports this in its `compatibility` mode using the existing JWT endpoint. Older clients, clients configured to use a single SFU, and callers without their own SFU may join but be unable to publish media.

If you intentionally provide SFU access to another trusted homeserver, add its Matrix server name to your `inventory/host_vars/matrix.example.com/vars.yml` file:

```yaml
matrix_livekit_jwt_service_environment_variable_livekit_full_access_homeservers_list_custom:
- example.org
```

This lets all users of that homeserver publish and trigger room creation on your SFU, including outside your calls. Using `'*'` grants these permissions to every homeserver.

To temporarily retain the previous behavior while updating clients or arranging SFU access:

```yaml
matrix_livekit_jwt_service_version: 0.6.0
matrix_livekit_jwt_service_container_healthcheck_enabled: false
```

Remove both overrides when ready to upgrade. The healthcheck must remain disabled on 0.6.0 because it is broken in that image.

+ 3
- 1
docs/configuring-playbook-matrix-rtc.md Ver ficheiro

@@ -1,6 +1,6 @@
<!--
SPDX-FileCopyrightText: 2024 wjbeckett
SPDX-FileCopyrightText: 2024 - 2025 Slavi Pantaleev
SPDX-FileCopyrightText: 2024 - 2026 Slavi Pantaleev

SPDX-License-Identifier: AGPL-3.0-or-later
-->
@@ -59,6 +59,8 @@ The shortcut commands with the [`just` program](just.md) are also available: `ju

## Usage

For federated calls, see [Federated calls and trusted homeservers](configuring-playbook-livekit-jwt-service.md#federated-calls-and-trusted-homeservers) for client requirements and sharing your SFU with other homeservers.

Once installed, Matrix clients which support Element Call (like [Element Web](configuring-playbook-client-element-web.md) and Element X on mobile (iOS and Android)) will automatically use the Matrix RTC stack.

These clients typically embed the Element Call frontend UI within them, so installing [Element Call](configuring-playbook-element-call.md) is only necessary if you'd like to use it standalone - directly via a browser.

+ 2
- 1
group_vars/matrix_servers Ver ficheiro

@@ -6590,7 +6590,8 @@ livekit_server_config_keys_auto: |-
)
}}

# We only wish for matrix-livekit-jwt-service to create rooms, only for users on trusted homeservers.
# Only users on trusted homeservers may publish media or trigger room creation on this SFU.
# Other federated users subscribe here and publish on their own SFU.
# See `matrix_livekit_jwt_service_environment_variable_livekit_full_access_homeservers`.
#
# Ref:


+ 4
- 0
molecule-shared/vars.yml Ver ficheiro

@@ -20,3 +20,7 @@ molecule_shared_image_python: "docker.io/library/python:3.14-alpine"
# well before the postgres role makes it the default for new installations.
# renovate: datasource=docker depName=docker.io/library/postgres
molecule_shared_image_postgres: "docker.io/library/postgres:18.6-alpine"

# Real SFU for the LiveKit JWT service's room creation and publishing grants.
# renovate: datasource=docker depName=docker.io/livekit/livekit-server
molecule_shared_image_livekit_server: "docker.io/livekit/livekit-server:v1.13.6"

+ 8
- 16
roles/custom/matrix-livekit-jwt-service/defaults/main.yml Ver ficheiro

@@ -25,7 +25,7 @@ matrix_livekit_jwt_service_container_additional_networks_auto: []
matrix_livekit_jwt_service_container_additional_networks_custom: []

# renovate: datasource=docker depName=ghcr.io/element-hq/lk-jwt-service
matrix_livekit_jwt_service_version: 0.6.0
matrix_livekit_jwt_service_version: 0.7.0

matrix_livekit_jwt_service_container_image_self_build: false
matrix_livekit_jwt_service_container_repo: "https://github.com/element-hq/lk-jwt-service.git"
@@ -69,19 +69,10 @@ matrix_livekit_jwt_service_container_extra_arguments: []

# Controls whether the container's built-in healthcheck is left enabled.
#
# lk-jwt-service v0.6.0 added a healthcheck which builds its URL as
# `http://localhost:$LIVEKIT_JWT_BIND/healthz`, interpolating the bind address
# into the port slot. Because LIVEKIT_JWT_BIND is a bind address (`:8080`), the
# resulting URL is invalid and the check can never pass, leaving the container
# permanently unhealthy. Traefik skips unhealthy containers, so the service
# stops being routed and Element Call cannot obtain an SFU token.
#
# There is no way to correct the check from here: the image is built `FROM
# scratch`, so it has no shell for a `--health-cmd` override to use.
#
# Re-enable this once upstream fixes the check.
# See: https://github.com/element-hq/lk-jwt-service/pull/186
matrix_livekit_jwt_service_container_healthcheck_enabled: false
# The bind-address handling was fixed in v0.7.0. If you pin v0.6.0, disable
# its broken healthcheck so Traefik does not exclude the container.
# See: https://github.com/element-hq/lk-jwt-service/pull/215
matrix_livekit_jwt_service_container_healthcheck_enabled: true

# Controls the port that the service listens on internally in the container.
# This is still used for Traefik configuration and container port binding.
@@ -103,8 +94,9 @@ matrix_livekit_jwt_service_environment_variable_livekit_url: ""
matrix_livekit_jwt_service_environment_variable_livekit_secret: ""

# Controls the LIVEKIT_FULL_ACCESS_HOMESERVERS environment variable.
# Comma-separated list of Matrix homeservers whose users are authorized with full access to LiveKit SFU features
# (like creating rooms on the SFU).
# Comma-separated list of Matrix homeservers whose users may publish media and
# trigger room creation on this SFU. Other homeservers' users can only subscribe
# to media here; they publish on their own SFU using a client with multi-SFU support.
#
# This is a required setting and the service refuses to start without it.
# Setting it to `*` grants full access to any federated Matrix user, but listing only the homeserver(s)


+ 6
- 4
roles/custom/matrix-livekit-jwt-service/molecule/default/files/openid-fixture.py Ver ficheiro

@@ -10,8 +10,10 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs, urlparse


KNOWN_TOKEN = "known-openid-token"
KNOWN_SUBJECT = "@alice:matrix-openid-fixture:8443"
KNOWN_TOKENS = {
"known-openid-token": "@alice:matrix-openid-fixture:8443",
"full-access-openid-token": "@bob:full-access.molecule.local:8443",
}
REQUESTS = []


@@ -36,8 +38,8 @@ class OpenIDHandler(BaseHTTPRequestHandler):

token = parse_qs(parsed.query).get("access_token", [""])[0]
REQUESTS.append({"path": parsed.path, "access_token": token})
if token == KNOWN_TOKEN:
self.send_json(200, {"sub": KNOWN_SUBJECT})
if token in KNOWN_TOKENS:
self.send_json(200, {"sub": KNOWN_TOKENS[token]})
return

self.send_json(401, {"errcode": "M_UNAUTHORIZED", "error": "unknown token"})


+ 4
- 8
roles/custom/matrix-livekit-jwt-service/molecule/default/molecule.yml Ver ficheiro

@@ -38,12 +38,12 @@ provisioner:

matrix_livekit_jwt_service_environment_variable_livekit_key: molecule-livekit-api-key
matrix_livekit_jwt_service_environment_variable_livekit_secret: molecule-livekit-signing-secret-32-bytes-long
matrix_livekit_jwt_service_environment_variable_livekit_url: wss://sfu.livekit.molecule.invalid:7881/rtc
matrix_livekit_jwt_service_environment_variable_livekit_url: ws://matrix-livekit-sfu-fixture:7880

# The OpenID fixture's origin is deliberately absent. This makes the
# successful request a restricted-user flow, so no SFU is contacted.
# The OpenID fixture serves two origins: one restricted remote user and
# one trusted user who can publish and trigger room creation on the SFU.
matrix_livekit_jwt_service_environment_variable_livekit_full_access_homeservers_list_default:
- full-access.molecule.local
- full-access.molecule.local:8443
matrix_livekit_jwt_service_environment_variable_livekit_full_access_homeservers_list_custom:
- second-full-access.molecule.local

@@ -56,10 +56,6 @@ provisioner:
LIVEKIT_LOG_LEVEL=debug
MOLECULE_LIVEKIT_MARKER=environment-reached

# v0.6.0 ships a malformed image healthcheck. The role intentionally
# disables it until upstream fixes its LIVEKIT_JWT_BIND interpolation.
matrix_livekit_jwt_service_container_healthcheck_enabled: false

matrix_livekit_jwt_service_hostname: jwt.livekit.molecule.local
matrix_livekit_jwt_service_path_prefix: /matrix-rtc/jwt
matrix_livekit_jwt_service_container_labels_traefik_enabled: true


+ 60
- 0
roles/custom/matrix-livekit-jwt-service/molecule/default/prepare.yml Ver ficheiro

@@ -119,9 +119,69 @@
- --name=matrix-livekit-jwt-openid-fixture
- --network={{ matrix_livekit_jwt_service_container_network }}
- --network-alias=matrix-openid-fixture
- --network-alias=full-access.molecule.local
- --mount=type=bind,src=/matrix/molecule-livekit-openid-fixture,dst=/fixture,readonly
- "{{ molecule_shared_image_python }}"
- python3
- /fixture/openid-fixture.py
register: matrix_livekit_jwt_service_openid_fixture_start
changed_when: matrix_livekit_jwt_service_openid_fixture_start.rc == 0

- name: Configure the SFU fixture with room auto-creation disabled
ansible.builtin.copy:
dest: /matrix/molecule-livekit-openid-fixture/livekit.yml
mode: "0644"
content: |
port: 7880
bind_addresses: ["0.0.0.0"]
rtc:
tcp_port: 7881
udp_port: 7882
use_external_ip: false
keys:
{{ matrix_livekit_jwt_service_environment_variable_livekit_key }}: {{ matrix_livekit_jwt_service_environment_variable_livekit_secret }}
room:
auto_create: false

- name: Ensure a previous SFU fixture is gone
ansible.builtin.command:
argv:
- docker
- rm
- --force
- matrix-livekit-sfu-fixture
register: matrix_livekit_jwt_service_sfu_fixture_removal
changed_when: matrix_livekit_jwt_service_sfu_fixture_removal.rc == 0
failed_when: false

- name: Ensure the real SFU fixture is running
ansible.builtin.command:
argv:
- docker
- run
- --detach
- --name=matrix-livekit-sfu-fixture
- --network={{ matrix_livekit_jwt_service_container_network }}
- --mount=type=bind,src=/matrix/molecule-livekit-openid-fixture/livekit.yml,dst=/livekit.yml,readonly
- "{{ molecule_shared_image_livekit_server }}"
- --config=/livekit.yml
register: matrix_livekit_jwt_service_sfu_fixture_start
changed_when: matrix_livekit_jwt_service_sfu_fixture_start.rc == 0

- name: Wait for the SFU fixture to accept requests
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --network={{ matrix_livekit_jwt_service_container_network }}
- "{{ molecule_shared_image_curl }}"
- --silent
- --show-error
- --fail
- http://matrix-livekit-sfu-fixture:7880/
register: matrix_livekit_jwt_service_sfu_fixture_ready
changed_when: false
until: matrix_livekit_jwt_service_sfu_fixture_ready.rc == 0
retries: 15
delay: 2

+ 117
- 0
roles/custom/matrix-livekit-jwt-service/molecule/default/tasks/verify-token.yml Ver ficheiro

@@ -0,0 +1,117 @@
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---
- name: Request a real LiveKit JWT
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --network={{ matrix_livekit_jwt_service_container_network }}
- "{{ molecule_shared_image_curl }}"
- --silent
- --show-error
- --header
- "Content-Type: application/json"
- --data-binary
- "{{ matrix_livekit_jwt_service_case_request | to_json }}"
- --write-out
- "\nHTTP_STATUS=%{http_code}"
- http://matrix-livekit-jwt-service:8097/get_token
register: matrix_livekit_jwt_service_token_response
changed_when: false
failed_when: false
vars:
matrix_livekit_jwt_service_case_request: >-
{{ matrix_livekit_jwt_service_valid_request | combine({
'slot_id': matrix_livekit_jwt_service_token_case.slot,
'openid_token': {'access_token': matrix_livekit_jwt_service_token_case.token, 'matrix_server_name': matrix_livekit_jwt_service_token_case.server},
'member': {'claimed_user_id': matrix_livekit_jwt_service_token_case.user}
}, recursive=True) }}

- name: Parse the successful token response
ansible.builtin.set_fact:
matrix_livekit_jwt_service_token_document: >-
{{ matrix_livekit_jwt_service_token_response.stdout | regex_replace('(?s)\s*HTTP_STATUS=[0-9]+\s*$', '') | from_json }}

- name: Assert the live service returns a JWT for the configured SFU URL
ansible.builtin.assert:
that:
- matrix_livekit_jwt_service_token_response.rc == 0
- matrix_livekit_jwt_service_token_response.stdout_lines[-1] == 'HTTP_STATUS=200'
- matrix_livekit_jwt_service_token_document.keys() | sort == ['jwt', 'url']
- matrix_livekit_jwt_service_token_document.url == matrix_livekit_jwt_service_environment_variable_livekit_url
- matrix_livekit_jwt_service_token_document.jwt.split('.') | length == 3
fail_msg: "The {{ matrix_livekit_jwt_service_token_case.name }} exchange did not return a JWT and configured SFU URL"
success_msg: "The {{ matrix_livekit_jwt_service_token_case.name }} exchange returns a real JWT for the configured SFU"

- name: Decode and authenticate the returned LiveKit JWT
ansible.builtin.command:
argv:
- python3
- -c
- >-
import base64, hashlib, hmac, json, sys;
parts = sys.argv[1].split('.');
decode = lambda value: json.loads(base64.urlsafe_b64decode(value + '=' * (-len(value) % 4)));
signature = base64.urlsafe_b64decode(parts[2] + '=' * (-len(parts[2]) % 4));
expected = hmac.new(sys.argv[2].encode(), (parts[0] + '.' + parts[1]).encode(), hashlib.sha256).digest();
print(json.dumps({'header': decode(parts[0]), 'claims': decode(parts[1]), 'signature_valid': hmac.compare_digest(signature, expected)}))
- "{{ matrix_livekit_jwt_service_token_document.jwt }}"
- "{{ matrix_livekit_jwt_service_environment_variable_livekit_secret }}"
register: matrix_livekit_jwt_service_jwt_decode
changed_when: false
no_log: true

- name: Assert the JWT signature, identity, room and grants
ansible.builtin.assert:
that:
- matrix_livekit_jwt_service_jwt.signature_valid is sameas true
- matrix_livekit_jwt_service_jwt.header.alg == 'HS256'
- matrix_livekit_jwt_service_jwt.claims.iss == matrix_livekit_jwt_service_environment_variable_livekit_key
- matrix_livekit_jwt_service_jwt.claims.sub == matrix_livekit_jwt_service_token_case.identity
- matrix_livekit_jwt_service_jwt.claims.video.room == matrix_livekit_jwt_service_token_case.room
- matrix_livekit_jwt_service_jwt.claims.video.roomJoin is sameas true
- matrix_livekit_jwt_service_jwt.claims.video.roomCreate | default(false) is sameas false
- matrix_livekit_jwt_service_jwt.claims.video.canPublish is sameas matrix_livekit_jwt_service_token_case.can_publish
- matrix_livekit_jwt_service_jwt.claims.video.canSubscribe is sameas true
- matrix_livekit_jwt_service_jwt.claims.video.canUpdateOwnMetadata is sameas true
- matrix_livekit_jwt_service_jwt.claims.exp | int - matrix_livekit_jwt_service_jwt.claims.nbf | int == 3600
fail_msg: "The returned JWT lost its authenticated MSC4195 identity, room or {{ matrix_livekit_jwt_service_token_case.name }} join grant"
success_msg: "The returned JWT has a valid signature and {{ matrix_livekit_jwt_service_token_case.name }} MSC4195 grant"
vars:
matrix_livekit_jwt_service_jwt: "{{ matrix_livekit_jwt_service_jwt_decode.stdout | from_json }}"

- name: Inspect the requested room on the real SFU
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --network={{ matrix_livekit_jwt_service_container_network }}
- "{{ molecule_shared_image_curl }}"
- --silent
- --show-error
- --fail
- --header
- "Authorization: Bearer {{ matrix_livekit_jwt_service_room_list_token.stdout }}"
- --header
- "Content-Type: application/json"
- --data-binary
- "{{ {'names': [matrix_livekit_jwt_service_token_case.room]} | to_json }}"
- http://matrix-livekit-sfu-fixture:7880/twirp/livekit.RoomService/ListRooms
register: matrix_livekit_jwt_service_sfu_rooms
changed_when: false
no_log: true

- name: Assert only a trusted user triggers SFU room creation
ansible.builtin.assert:
that:
- matrix_livekit_jwt_service_rooms | map(attribute='name') | list == matrix_livekit_jwt_service_expected_rooms
fail_msg: "The {{ matrix_livekit_jwt_service_token_case.name }} user triggered an unexpected SFU room-creation result"
success_msg: "The real SFU enforces the expected room-creation behavior for the {{ matrix_livekit_jwt_service_token_case.name }} user"
vars:
matrix_livekit_jwt_service_rooms: "{{ (matrix_livekit_jwt_service_sfu_rooms.stdout | from_json).rooms | default([]) }}"
matrix_livekit_jwt_service_expected_rooms: "{{ [matrix_livekit_jwt_service_token_case.room] if matrix_livekit_jwt_service_token_case.can_publish else [] }}"

+ 59
- 76
roles/custom/matrix-livekit-jwt-service/molecule/default/verify.yml Ver ficheiro

@@ -221,81 +221,47 @@
matrix_livekit_jwt_service_mismatched_user_document: >-
{{ matrix_livekit_jwt_service_mismatched_user.stdout | regex_replace('(?s)\s*HTTP_STATUS=[0-9]+\s*$', '') | from_json }}

# The request's server is not in FULL_ACCESS, so this validates OpenID and
# signs a join token without trying to create a room on the absent SFU.
- name: Request a real restricted-user LiveKit JWT
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --network={{ matrix_livekit_jwt_service_container_network }}
- "{{ molecule_shared_image_curl }}"
- --silent
- --show-error
- --header
- "Content-Type: application/json"
- --data-binary
- "{{ matrix_livekit_jwt_service_valid_request | to_json }}"
- --write-out
- "\nHTTP_STATUS=%{http_code}"
- http://matrix-livekit-jwt-service:8097/get_token
register: matrix_livekit_jwt_service_token_response
changed_when: false
failed_when: false

- name: Parse the successful token response
ansible.builtin.set_fact:
matrix_livekit_jwt_service_token_document: >-
{{ matrix_livekit_jwt_service_token_response.stdout | regex_replace('(?s)\s*HTTP_STATUS=[0-9]+\s*$', '') | from_json }}

- name: Assert the live service returns a JWT for the configured SFU URL
ansible.builtin.assert:
that:
- matrix_livekit_jwt_service_token_response.rc == 0
- matrix_livekit_jwt_service_token_response.stdout_lines[-1] == 'HTTP_STATUS=200'
- matrix_livekit_jwt_service_token_document.keys() | sort == ['jwt', 'url']
- matrix_livekit_jwt_service_token_document.url == matrix_livekit_jwt_service_environment_variable_livekit_url
- matrix_livekit_jwt_service_token_document.jwt.split('.') | length == 3
fail_msg: "The restricted-user exchange did not return a JWT and configured SFU URL"
success_msg: "The restricted-user exchange returns a real JWT for the configured SFU"

- name: Decode and authenticate the returned LiveKit JWT
- name: Sign a room-list token for inspecting the SFU fixture
ansible.builtin.command:
argv:
- python3
- -c
- >-
import base64, hashlib, hmac, json, sys;
parts = sys.argv[1].split('.');
decode = lambda value: json.loads(base64.urlsafe_b64decode(value + '=' * (-len(value) % 4)));
signature = base64.urlsafe_b64decode(parts[2] + '=' * (-len(parts[2]) % 4));
expected = hmac.new(sys.argv[2].encode(), (parts[0] + '.' + parts[1]).encode(), hashlib.sha256).digest();
print(json.dumps({'header': decode(parts[0]), 'claims': decode(parts[1]), 'signature_valid': hmac.compare_digest(signature, expected)}))
- "{{ matrix_livekit_jwt_service_token_document.jwt }}"
import base64, hashlib, hmac, json, sys, time;
encode = lambda value: base64.urlsafe_b64encode(value).decode().rstrip('=');
header = encode(json.dumps({'alg': 'HS256', 'typ': 'JWT'}).encode());
claims = encode(json.dumps({'iss': sys.argv[1], 'exp': int(time.time()) + 600, 'video': {'roomList': True}}).encode());
payload = header + '.' + claims;
signature = encode(hmac.new(sys.argv[2].encode(), payload.encode(), hashlib.sha256).digest());
print(payload + '.' + signature)
- "{{ matrix_livekit_jwt_service_environment_variable_livekit_key }}"
- "{{ matrix_livekit_jwt_service_environment_variable_livekit_secret }}"
register: matrix_livekit_jwt_service_jwt_decode
register: matrix_livekit_jwt_service_room_list_token
changed_when: false
no_log: true

- name: Assert the JWT signature, identity, room and restricted grant
ansible.builtin.assert:
that:
- matrix_livekit_jwt_service_jwt.signature_valid is sameas true
- matrix_livekit_jwt_service_jwt.header.alg == 'HS256'
- matrix_livekit_jwt_service_jwt.claims.iss == matrix_livekit_jwt_service_environment_variable_livekit_key
- matrix_livekit_jwt_service_jwt.claims.sub == 'BE7ZK/J/c3LsogjI1JLni7qtWV/DP6c+ev68BvHem0Y'
- matrix_livekit_jwt_service_jwt.claims.video.room == 'diNaPECp1jHt2l0dLdiDxi+dgNzgucxPYMD0lCs1tEQ'
- matrix_livekit_jwt_service_jwt.claims.video.roomJoin is sameas true
- "'roomCreate' not in matrix_livekit_jwt_service_jwt.claims.video"
- matrix_livekit_jwt_service_jwt.claims.video.canPublish is sameas true
- matrix_livekit_jwt_service_jwt.claims.video.canSubscribe is sameas true
- matrix_livekit_jwt_service_jwt.claims.video.canUpdateOwnMetadata is sameas true
- matrix_livekit_jwt_service_jwt.claims.exp | int - matrix_livekit_jwt_service_jwt.claims.nbf | int == 3600
fail_msg: "The returned JWT lost its authenticated MSC4195 identity, room or restricted join grant"
success_msg: "The returned JWT has a valid signature and restricted MSC4195 grant"
vars:
matrix_livekit_jwt_service_jwt: "{{ matrix_livekit_jwt_service_jwt_decode.stdout | from_json }}"
- name: Verify remote and trusted users' LiveKit grants
ansible.builtin.include_tasks: tasks/verify-token.yml
loop:
- name: remote
token: known-openid-token
server: matrix-openid-fixture:8443
user: "@alice:matrix-openid-fixture:8443"
identity: BE7ZK/J/c3LsogjI1JLni7qtWV/DP6c+ev68BvHem0Y
slot: molecule-slot
room: diNaPECp1jHt2l0dLdiDxi+dgNzgucxPYMD0lCs1tEQ
can_publish: false
- name: trusted
token: full-access-openid-token
server: full-access.molecule.local:8443
user: "@bob:full-access.molecule.local:8443"
identity: JJk3FtOMEllqGG0AYcNXVZGRegQfZ49zfi7H3prd3u4
slot: molecule-trusted-slot
room: pUry0+0Or4OrwbcbzZqjBjMp41AwAprwKvNJZUgHWxE
can_publish: true
loop_control:
loop_var: matrix_livekit_jwt_service_token_case
label: "{{ matrix_livekit_jwt_service_token_case.name }}"

- name: Read the OpenID fixture's observed requests
ansible.builtin.command:
@@ -315,9 +281,9 @@
- name: Assert the live service exercised both OpenID fixture outcomes
ansible.builtin.assert:
that:
- matrix_livekit_jwt_service_openid_requests.requests | length >= 3
- matrix_livekit_jwt_service_openid_requests.requests | length >= 4
- matrix_livekit_jwt_service_openid_requests.requests | map(attribute='path') | unique | list == ['/_matrix/federation/v1/openid/userinfo']
- matrix_livekit_jwt_service_openid_requests.requests[-3:] | map(attribute='access_token') | list == ['unknown-openid-token', 'known-openid-token', 'known-openid-token']
- matrix_livekit_jwt_service_openid_requests.requests[-4:] | map(attribute='access_token') | list == ['unknown-openid-token', 'known-openid-token', 'known-openid-token', 'full-access-openid-token']
fail_msg: "The OpenID fixture did not observe the expected unknown and known token exchanges"
success_msg: "The service exercised both HTTPS OpenID fixture outcomes"
vars:
@@ -346,8 +312,8 @@
- matrix_livekit_jwt_service_env_parsed.LIVEKIT_JWT_BIND == ':8097'
- matrix_livekit_jwt_service_env_parsed.LIVEKIT_KEY == 'molecule-livekit-api-key'
- matrix_livekit_jwt_service_env_parsed.LIVEKIT_SECRET == 'molecule-livekit-signing-secret-32-bytes-long'
- matrix_livekit_jwt_service_env_parsed.LIVEKIT_URL == 'wss://sfu.livekit.molecule.invalid:7881/rtc'
- matrix_livekit_jwt_service_env_parsed.LIVEKIT_FULL_ACCESS_HOMESERVERS == 'full-access.molecule.local,second-full-access.molecule.local'
- matrix_livekit_jwt_service_env_parsed.LIVEKIT_URL == 'ws://matrix-livekit-sfu-fixture:7880'
- matrix_livekit_jwt_service_env_parsed.LIVEKIT_FULL_ACCESS_HOMESERVERS == 'full-access.molecule.local:8443,second-full-access.molecule.local'
- matrix_livekit_jwt_service_env_parsed.LIVEKIT_CS_API_URL_OVERRIDES == 'matrix-openid-fixture:8443=https://client-api.override.molecule.invalid'
- matrix_livekit_jwt_service_env_parsed.LIVEKIT_INSECURE_SKIP_VERIFY_TLS == 'YES_I_KNOW_WHAT_I_AM_DOING'
- matrix_livekit_jwt_service_env_parsed.LIVEKIT_LOG_LEVEL == 'debug'
@@ -403,7 +369,7 @@
ansible.builtin.assert:
that:
- "'LIVEKIT_JWT_BIND=:8097' in matrix_livekit_jwt_service_runtime.Config.Env"
- "'LIVEKIT_FULL_ACCESS_HOMESERVERS=full-access.molecule.local,second-full-access.molecule.local' in matrix_livekit_jwt_service_runtime.Config.Env"
- "'LIVEKIT_FULL_ACCESS_HOMESERVERS=full-access.molecule.local:8443,second-full-access.molecule.local' in matrix_livekit_jwt_service_runtime.Config.Env"
- "'LIVEKIT_CS_API_URL_OVERRIDES=matrix-openid-fixture:8443=https://client-api.override.molecule.invalid' in matrix_livekit_jwt_service_runtime.Config.Env"
- "'LIVEKIT_INSECURE_SKIP_VERIFY_TLS=YES_I_KNOW_WHAT_I_AM_DOING' in matrix_livekit_jwt_service_runtime.Config.Env"
- "'MOLECULE_LIVEKIT_MARKER=environment-reached' in matrix_livekit_jwt_service_runtime.Config.Env"
@@ -457,14 +423,31 @@
fail_msg: "The running container differs from the role's capability and mount contract"
success_msg: "The running container has all capabilities dropped and no mounts"

- name: Assert the broken upstream healthcheck is disabled
- name: Wait for Docker's image healthcheck to pass on the configured port
ansible.builtin.command:
argv:
- docker
- container
- inspect
- matrix-livekit-jwt-service
register: matrix_livekit_jwt_service_healthcheck_inspect
changed_when: false
until: >-
((matrix_livekit_jwt_service_healthcheck_inspect.stdout | from_json) | first).State.Health.Status | default('missing') == 'healthy'
retries: 15
delay: 5

- name: Assert the repaired image healthcheck is enabled and healthy
ansible.builtin.assert:
that:
- matrix_livekit_jwt_service_runtime.Config.Healthcheck is mapping
- matrix_livekit_jwt_service_runtime.Config.Healthcheck.Test == ['NONE']
- matrix_livekit_jwt_service_runtime.State.Health.Status is not defined
fail_msg: "The v0.6.0 image healthcheck was not disabled by the role"
success_msg: "The broken v0.6.0 image healthcheck is disabled"
- matrix_livekit_jwt_service_runtime.Config.Healthcheck.Test == ['CMD', '/lk-jwt-service-healthcheck']
- matrix_livekit_jwt_service_healthcheck.State.Health.Status == 'healthy'
- matrix_livekit_jwt_service_healthcheck.State.Health.FailingStreak == 0
fail_msg: "The v0.7.0 image healthcheck did not pass with the role's bind address"
success_msg: "Docker reports a healthy container using the repaired image healthcheck"
vars:
matrix_livekit_jwt_service_healthcheck: "{{ (matrix_livekit_jwt_service_healthcheck_inspect.stdout | from_json) | first }}"

- name: Assert the running container is attached only to its dedicated network
ansible.builtin.assert:


Carregando…
Cancelar
Guardar