Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into hs/update-dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
Half-Shot committed Nov 28, 2024
2 parents 45e4724 + 052d42f commit ec9ffaf
Show file tree
Hide file tree
Showing 19 changed files with 279 additions and 159 deletions.
18 changes: 12 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,15 @@ jobs:
homerunnersha: ${{ steps.gitsha.outputs.sha }}
steps:
- name: Checkout matrix-org/complement
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
repository: matrix-org/complement
- name: Get complement git sha
id: gitsha
run: echo sha=`git rev-parse --short HEAD` >> "$GITHUB_OUTPUT"
- name: Cache homerunner
id: cached
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: homerunner
key: ${{ runner.os }}-homerunner-${{ steps.gitsha.outputs.sha }}
Expand All @@ -123,23 +123,28 @@ jobs:
needs:
- test
- build-homerunner
services:
redis:
image: redis
ports:
- 6379:6379
steps:
- name: Install Complement Dependencies
run: |
sudo apt-get update && sudo apt-get install -y libolm3
- name: Load cached homerunner bin
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: homerunner
key: ${{ runner.os }}-homerunner-${{ needs.build-synapse.outputs.homerunnersha }}
fail-on-cache-miss: true # Shouldn't happen, we build this in the needs step.
- name: Checkout matrix-hookshot
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
path: matrix-hookshot
# Setup node & run tests
- name: Use Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version-file: matrix-hookshot/.node-version
- uses: Swatinem/rust-cache@v2
Expand All @@ -150,8 +155,9 @@ jobs:
timeout-minutes: 10
env:
HOMERUNNER_SPAWN_HS_TIMEOUT_SECS: 100
HOMERUNNER_IMAGE: ghcr.io/element-hq/synapse/complement-synapse:latest
HOMERUNNER_IMAGE: ghcr.io/element-hq/synapse/complement-synapse:nightly
NODE_OPTIONS: --dns-result-order ipv4first
REDIS_DATABASE_URI: "redis://localhost:6379"
run: |
docker pull $HOMERUNNER_IMAGE
cd matrix-hookshot
Expand Down
2 changes: 2 additions & 0 deletions changelog.d/989.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Support for E2E Encrypted rooms is now considered stable and can be enabled in production. Please see the [documentation](https://matrix-org.github.io/matrix-hookshot/latest/advanced/encryption.html)
on the requirements for enabling support.
22 changes: 12 additions & 10 deletions config.sample.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# This is an example configuration file

logging:
# Logging settings. You can have a severity debug,info,warn,error
level: info
colorize: true
json: false
timestampFormat: HH:mm:ss:SSS
bridge:
# Basic homeserver configuration
domain: example.com
Expand All @@ -11,12 +17,6 @@ passFile:
# A passkey used to encrypt tokens stored inside the bridge.
# Run openssl genpkey -out passkey.pem -outform PEM -algorithm RSA -pkeyopt rsa_keygen_bits:4096 to generate
./passkey.pem
logging:
# Logging settings. You can have a severity debug,info,warn,error
level: info
colorize: true
json: false
timestampFormat: HH:mm:ss:SSS
listeners:
# HTTP Listener configuration.
# Bind resource endpoints to ports and addresses.
Expand Down Expand Up @@ -143,10 +143,12 @@ listeners:
# # For encryption to work, this must be configured.
# redisUri: redis://localhost:6379

#queue:
# # (Optional) Message queue configuration options for large scale deployments.
# # For encryption to work, this must not be configured.
# redisUri: redis://localhost:6379
#encryption:
# # (Optional) Configuration for encryption support in the bridge.
# # If omitted, encryption support will be disabled.
# storagePath:
# # Path to the directory used to store encryption files. These files must be persist between restarts of the service.
# ./cryptostore

#widgets:
# # (Optional) EXPERIMENTAL support for complimentary widgets
Expand Down
20 changes: 13 additions & 7 deletions docs/advanced/encryption.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,37 @@
Encryption
==========

<section class="warning">
Encryption support is <strong>HIGHLY EXPERIMENTAL AND SUBJECT TO CHANGE</strong>. It should not be enabled for production workloads.
For more details, see <a href="https://github.com/matrix-org/matrix-hookshot/issues/594">issue 594</a>.
<section class="notice">
Support for encryption is considered stable, but the underlying specification changes are not yet.

Hookshot supports end-to-bridge encryption via [MSC3202](https://github.com/matrix-org/matrix-spec-proposals/pull/3202), and [MSC4203](https://github.com/matrix-org/matrix-spec-proposals/pull/4203). Hookshot needs to be configured against a a homeserver that supports these features, such as [Synapse](#running-with-synapse).

Please check with your homeserver implementation before reporting bugs against matrix-hookshot.
</section>

Hookshot supports end-to-bridge encryption via [MSC3202](https://github.com/matrix-org/matrix-spec-proposals/pull/3202). As such, encryption requires Hookshot to be connected to a homeserver that supports that MSC, such as [Synapse](#running-with-synapse).


## Enabling encryption in Hookshot

In order for Hookshot to use encryption, it must be configured as follows:
- The `experimentalEncryption.storagePath` setting must point to a directory that Hookshot has permissions to write files into. If running with Docker, this path should be within a volume (for persistency). Hookshot uses this directory for its crypto store (i.e. long-lived state relating to its encryption keys).
- The `encryption.storagePath` setting must point to a directory that Hookshot has permissions to write files into. If running with Docker, this path should be within a volume (for persistency). Hookshot uses this directory for its crypto store (i.e. long-lived state relating to its encryption keys).
- Once a crypto store has been initialized, its files must not be modified, and Hookshot cannot be configured to use another crypto store of the same type as one it has used before. If a crypto store's files get lost or corrupted, Hookshot may fail to start up, or may be unable to decrypt command messages. To fix such issues, stop Hookshot, then reset its crypto store by running `yarn start:resetcrypto`.
- [Redis](./workers.md) must be enabled. Note that worker mode is not yet supported with encryption, so `queue` MUST **NOT be configured**.

If you ever reset your homeserver's state, ensure you also reset Hookshot's encryption state. This includes clearing the `experimentalEncryption.storagePath` directory and all worker state stored in your redis instance. Otherwise, Hookshot may fail on start up with registration errors.
If you ever reset your homeserver's state, ensure you also reset Hookshot's encryption state. This includes clearing the `storagePath` directory and all worker state stored in your redis instance. Otherwise, Hookshot may fail on start up with registration errors.

Also ensure that Hookshot's appservice registration file contains every line from `registration.sample.yml` that appears after the `If enabling encryption` comment. Note that changing the registration file may require restarting the homeserver that Hookshot is connected to.

## Running with Synapse

[Synapse](https://github.com/matrix-org/synapse/) has functional support for MSC3202 as of [v1.63.0](https://github.com/matrix-org/synapse/releases/tag/v1.63.0). To enable it, add the following section to Synapse's configuration file (typically named `homeserver.yaml`):
[Synapse](https://github.com/matrix-org/synapse/) has functional support for MSC3202 and MSC4203 as of [v1.63.0](https://github.com/matrix-org/synapse/releases/tag/v1.63.0). To enable it, add the following section to Synapse's configuration file (typically named `homeserver.yaml`):

You may notice that MSC2409 is not listed above. Due to the changes being split out from MSC2409, `msc2409_to_device_messages_enabled` refers to MSC4203.

```yaml
experimental_features:
msc3202_device_masquerading: true
msc3202_transaction_extensions: true
msc2409_to_device_messages_enabled: true
```
33 changes: 0 additions & 33 deletions spec/basic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,37 +26,4 @@ describe('Basic test setup', () => {
// Expect help text.
expect((await msg).data.content.body).to.include('!hookshot help` - This help text\n');
});

// TODO: Move test to it's own generic connections file.
it('should be able to setup a webhook', async () => {
const user = testEnv.getUser('user');
const testRoomId = await user.createRoom({ name: 'Test room', invite:[testEnv.botMxid] });
await user.waitForRoomJoin({sender: testEnv.botMxid, roomId: testRoomId });
await user.setUserPowerLevel(testEnv.botMxid, testRoomId, 50);
await user.sendText(testRoomId, "!hookshot webhook test-webhook");
const inviteResponse = await user.waitForRoomInvite({sender: testEnv.botMxid});
await user.waitForRoomEvent<MessageEventContent>({
eventType: 'm.room.message', sender: testEnv.botMxid, roomId: testRoomId,
body: 'Room configured to bridge webhooks. See admin room for secret url.'
});
const webhookUrlMessage = user.waitForRoomEvent<MessageEventContent>({
eventType: 'm.room.message', sender: testEnv.botMxid, roomId: inviteResponse.roomId
});
await user.joinRoom(inviteResponse.roomId);
const msgData = (await webhookUrlMessage).data.content.body;
const webhookUrl = msgData.split('\n')[2];
const webhookNotice = user.waitForRoomEvent<MessageEventContent>({
eventType: 'm.room.message', sender: testEnv.botMxid, roomId: testRoomId, body: 'Hello world!'
});

// Send a webhook
await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({text: 'Hello world!'})
});

// And await the notice.
await webhookNotice;
});
});
66 changes: 66 additions & 0 deletions spec/e2ee.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { MessageEventContent } from "matrix-bot-sdk";
import { E2ESetupTestTimeout, E2ETestEnv } from "./util/e2e-test";
import { describe, it, beforeEach, afterEach } from "@jest/globals";

const CryptoRoomState = [{
content: {
"algorithm": "m.megolm.v1.aes-sha2"
},
state_key: "",
type: "m.room.encryption"
}];

describe('End-2-End Encryption support', () => {
let testEnv: E2ETestEnv;

beforeEach(async () => {
testEnv = await E2ETestEnv.createTestEnv({ matrixLocalparts: ['user'], enableE2EE: true });
await testEnv.setUp();
}, E2ESetupTestTimeout);

afterEach(() => {
return testEnv?.tearDown();
});

it('should be able to send the help command', async () => {
const user = testEnv.getUser('user');
const testRoomId = await user.createRoom({ name: 'Test room', invite:[testEnv.botMxid], initial_state: CryptoRoomState});
await user.setUserPowerLevel(testEnv.botMxid, testRoomId, 50);
await user.waitForRoomJoin({sender: testEnv.botMxid, roomId: testRoomId });
await user.sendText(testRoomId, "!hookshot help");
await user.waitForRoomEvent<MessageEventContent>({
eventType: 'm.room.message', sender: testEnv.botMxid, roomId: testRoomId,
});
});
it('should send notices in an encrypted format', async () => {
const user = testEnv.getUser('user');
const testRoomId = await user.createRoom({ name: 'Test room', invite:[testEnv.botMxid], initial_state: CryptoRoomState});
await user.setUserPowerLevel(testEnv.botMxid, testRoomId, 50);
await user.waitForRoomJoin({sender: testEnv.botMxid, roomId: testRoomId });
await user.sendText(testRoomId, "!hookshot webhook test-webhook");
const inviteResponse = await user.waitForRoomInvite({sender: testEnv.botMxid});
await user.waitForEncryptedEvent<MessageEventContent>({
eventType: 'm.room.message', sender: testEnv.botMxid, roomId: testRoomId,
body: 'Room configured to bridge webhooks. See admin room for secret url.'
});
const webhookUrlMessage = user.waitForEncryptedEvent<MessageEventContent>({
eventType: 'm.room.message', sender: testEnv.botMxid, roomId: inviteResponse.roomId
});
await user.joinRoom(inviteResponse.roomId);
const msgData = (await webhookUrlMessage).data.content.body;
const webhookUrl = msgData.split('\n')[2];
const webhookNotice = user.waitForEncryptedEvent<MessageEventContent>({
eventType: 'm.room.message', sender: testEnv.botMxid, roomId: testRoomId, body: 'Hello world!'
});

// Send a webhook
await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({text: 'Hello world!'})
});

// And await the notice.
await webhookNotice;
});
});
Loading

0 comments on commit ec9ffaf

Please sign in to comment.