Skip to content

Commit d8083e5

Browse files
david-mohrgpad
andauthored
Add support for EXTERNAL auth (#213)
* feat: add support for EXTERNAL auth Co-authored-by: GPad <[email protected]> * refactor: throw from a function --------- Co-authored-by: GPad <[email protected]>
1 parent 701e1d6 commit d8083e5

File tree

11 files changed

+122
-30
lines changed

11 files changed

+122
-30
lines changed

.github/workflows/main.yml

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,18 @@ jobs:
2121
services:
2222
rabbitmq:
2323
image: rabbitmq:3.13-rc-management
24-
options: --hostname test-node
24+
options: --hostname test-node --name test-node
2525
env:
2626
RABBITMQ_DEFAULT_USER: "test-user"
2727
RABBITMQ_DEFAULT_PASS: "test-password"
28+
volumes:
29+
# these directories will be empty until checkout, but they will be
30+
# populated by the time we restart the service
31+
- ${{ github.workspace }}/conf:/etc/rabbitmq
32+
- ${{ github.workspace }}/certs:/certs
2833
ports:
2934
- 5552:5552
35+
- 5551:5551
3036
- 5672:5672
3137
- 15672:15672
3238
- 1883:1883
@@ -41,17 +47,32 @@ jobs:
4147
with:
4248
node-version: ${{ matrix.node-version }}
4349
cache: "npm"
44-
- name: Enable RabbitMQ Plugins
45-
run: docker exec $(docker ps --filter ancestor=rabbitmq:3.13-rc-management -q) rabbitmq-plugins enable rabbitmq_stream rabbitmq_stream_management
50+
- name: Generate certificates
51+
env:
52+
CN: test-node
53+
run: |
54+
git clone https://github.com/rabbitmq/tls-gen tls-gen
55+
cd tls-gen/basic
56+
make
57+
cd ../..
58+
cp -a tls-gen/basic/result certs/
59+
sudo chown -R 999:999 certs
60+
sudo mv certs/server_test-node_certificate.pem certs/server_rabbitmq_certificate.pem
61+
sudo mv certs/server_test-node_key.pem certs/server_rabbitmq_key.pem
4662
- name: Restart RabbitMQ
47-
run: docker restart $(docker ps --filter ancestor=rabbitmq:3.13-rc-management -q)
48-
- name: Wait for rabbit instance restart
49-
run: sleep 10
63+
run: |
64+
docker restart test-node
65+
sleep 2
66+
docker exec test-node rabbitmqctl await_startup
5067
- name: Create SuperStream
51-
run: docker exec $(docker ps --filter ancestor=rabbitmq:3.13-rc-management -q) rabbitmq-streams add_super_stream super-stream-test --partitions 2
68+
run: docker exec test-node rabbitmq-streams add_super_stream super-stream-test --partitions 2
5269
- run: npm ci
5370
- run: npm run check
5471
- run: npm run build --if-present
72+
- run: |
73+
docker exec test-node rabbitmqctl add_user 'O=client,CN=test-node' ''
74+
docker exec test-node rabbitmqctl clear_password 'O=client,CN=test-node'
75+
docker exec test-node rabbitmqctl set_permissions 'O=client,CN=test-node' '.*' '.*' '.*'
5576
- run: npm test
5677
env:
5778
RABBITMQ_USER: "test-user"

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ dist/
22
node_modules/
33
performance_test/node_modules
44
.envrc
5+
tls-gen/

Makefile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,16 @@ rabbitmq-cluster:
66
cd cluster; docker build -t haproxy-rabbitmq-cluster .
77
cd cluster; chmod 755 -R tls-gen
88
cd cluster; docker compose down
9-
cd cluster; docker compose up -d
9+
cd cluster; docker compose up -d
10+
11+
rabbitmq-test:
12+
rm -rf tls-gen;
13+
git clone https://github.com/rabbitmq/tls-gen tls-gen; cd tls-gen/basic; CN=rabbitmq make
14+
chmod 755 -R tls-gen
15+
docker compose down
16+
docker compose up -d
17+
sleep 5
18+
docker exec rabbitmq-stream rabbitmqctl await_startup
19+
docker exec rabbitmq-stream rabbitmqctl add_user 'O=client,CN=rabbitmq' ''
20+
docker exec rabbitmq-stream rabbitmqctl clear_password 'O=client,CN=rabbitmq'
21+
docker exec rabbitmq-stream rabbitmqctl set_permissions 'O=client,CN=rabbitmq' '.*' '.*' '.*'

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ npm i
374374
run the docker-compose to launch a rabbit instance already stream enabled
375375

376376
```shell
377-
docker-compose up -d
377+
make rabbitmq-test
378378
```
379379

380380
add this line to your host file (on linux `/etc/hosts`) to correctly resolve rabbitmq
@@ -400,8 +400,8 @@ npm run build
400400
Test:
401401

402402
```shell
403-
docker-compose up -d
404-
npm run test
403+
make rabbitmq-test
404+
RABBIT_MQ_TEST_NODES=rabbitmq:5552 npm run test
405405
```
406406

407407
Check everything:

conf/enabled_plugins

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
[rabbitmq_management,rabbitmq_prometheus,rabbitmq_stream_management].
1+
[rabbitmq_management,rabbitmq_prometheus,rabbitmq_stream_management,rabbitmq_auth_mechanism_ssl].

conf/rabbitmq.conf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
loopback_users.guest = false
2+
3+
ssl_options.cacertfile = /certs/ca_certificate.pem
4+
ssl_options.certfile = /certs/server_rabbitmq_certificate.pem
5+
ssl_options.keyfile = /certs/server_rabbitmq_key.pem
6+
listeners.ssl.default = 5671
7+
listeners.tcp.default = 5672
8+
stream.listeners.tcp.default = 5552
9+
stream.listeners.ssl.default = 5551
10+
auth_mechanisms.1 = PLAIN
11+
auth_mechanisms.2 = EXTERNAL
12+
ssl_options.verify = verify_peer
13+
ssl_options.fail_if_no_peer_cert = false
14+
log.file.level = debug
15+
log.console = true

docker-compose.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
version: "2"
2-
31
services:
42
rabbitmq-stream:
53
image: rabbitmq:3.13-rc-management
@@ -8,10 +6,13 @@ services:
86
hostname: "rabbitmq"
97
ports:
108
- "15672:15672"
9+
- "5671:5671"
1110
- "5672:5672"
11+
- "5551:5551"
1212
- "5552:5552"
1313
environment:
1414
RABBITMQ_DEFAULT_USER: "rabbit"
1515
RABBITMQ_DEFAULT_PASS: "rabbit"
1616
volumes:
17-
- ./conf/enabled_plugins:/etc/rabbitmq/enabled_plugins
17+
- ./conf/:/etc/rabbitmq/
18+
- "./tls-gen/basic/result/:/certs"

src/client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,7 @@ export interface ClientParams {
707707
port: number
708708
username: string
709709
password: string
710+
mechanism?: "PLAIN" | "EXTERNAL"
710711
vhost: string
711712
frameMax?: number
712713
heartbeat?: number

src/connection.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,11 @@ export class Connection {
146146
this.logger.info(`Connected to RabbitMQ ${this.params.hostname}:${this.params.port}`)
147147
this.peerProperties = (await this.exchangeProperties()).properties
148148
this.filteringEnabled = lt(coerce(this.rabbitManagementVersion)!, REQUIRED_MANAGEMENT_VERSION) ? false : true
149-
await this.auth({ username: this.params.username, password: this.params.password })
149+
await this.auth({
150+
username: this.params.username,
151+
password: this.params.password,
152+
mechanism: this.params.mechanism ?? "PLAIN",
153+
})
150154
const { heartbeat } = await this.tune(this.params.heartbeat ?? 0)
151155
await this.open({ virtualHost: this.params.vhost })
152156
if (!this.heartbeat.started) this.heartbeat.start(heartbeat)
@@ -436,19 +440,17 @@ export class Connection {
436440
return this.setupCompleted
437441
}
438442

439-
private async auth(params: { username: string; password: string }) {
443+
private async auth(params: { username: string; password: string; mechanism: string }) {
440444
this.logger.debug(`Start authentication process ...`)
441445
this.logger.debug(`Start SASL handshake ...`)
442446
const handshakeResponse = await this.sendAndWait<SaslHandshakeResponse>(new SaslHandshakeRequest())
443447
this.logger.debug(`Mechanisms: ${handshakeResponse.mechanisms}`)
444-
if (!handshakeResponse.mechanisms.find((m) => m === "PLAIN")) {
445-
throw new Error(`Unable to find PLAIN mechanism in ${handshakeResponse.mechanisms}`)
448+
if (!handshakeResponse.mechanisms.find((m) => m === params.mechanism)) {
449+
throw new Error(`Unable to find ${params.mechanism} mechanism in ${handshakeResponse.mechanisms}`)
446450
}
447451

448-
this.logger.debug(`Start SASL PLAIN authentication ...`)
449-
const authResponse = await this.sendAndWait<SaslAuthenticateResponse>(
450-
new SaslAuthenticateRequest({ ...params, mechanism: "PLAIN" })
451-
)
452+
this.logger.debug(`Start SASL ${params.mechanism} authentication ...`)
453+
const authResponse = await this.sendAndWait<SaslAuthenticateResponse>(new SaslAuthenticateRequest(params))
452454
this.logger.debug(`Authentication: ${authResponse.ok} - '${authResponse.data}'`)
453455
if (!authResponse.ok) {
454456
throw new Error(`Unable Authenticate -> ${authResponse.code}`)

src/requests/sasl_authenticate_request.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { SaslAuthenticateResponse } from "../responses/sasl_authenticate_respons
22
import { AbstractRequest } from "./abstract_request"
33
import { DataWriter } from "./data_writer"
44

5+
function assertUnreachable(mechanism: string): never {
6+
throw new Error(`Auth mechanism '${mechanism}' not implemented`)
7+
}
8+
59
export class SaslAuthenticateRequest extends AbstractRequest {
610
readonly responseKey = SaslAuthenticateResponse.key
711
static readonly Key = 0x0013
@@ -14,10 +18,19 @@ export class SaslAuthenticateRequest extends AbstractRequest {
1418

1519
protected writeContent(writer: DataWriter): void {
1620
writer.writeString(this.params.mechanism)
17-
writer.writeUInt32(this.params.password.length + this.params.username.length + 2)
18-
writer.writeUInt8(0)
19-
writer.writeData(this.params.username)
20-
writer.writeUInt8(0)
21-
writer.writeData(this.params.password)
21+
switch (this.params.mechanism) {
22+
case "PLAIN":
23+
writer.writeUInt32(this.params.password.length + this.params.username.length + 2)
24+
writer.writeUInt8(0)
25+
writer.writeData(this.params.username)
26+
writer.writeUInt8(0)
27+
writer.writeData(this.params.password)
28+
break
29+
case "EXTERNAL":
30+
writer.writeUInt32(0)
31+
break
32+
default:
33+
assertUnreachable(this.params.mechanism)
34+
}
2235
}
2336
}

0 commit comments

Comments
 (0)