Add admin route for SSE on get-protected channels

The auth channel does not allow users to get messages (access them over
the internet). Hence, a regular SSE client should not be able to monitor
the events generated on the auth channel. In contrast, an admin host
should be able to monitor the auth events. For that, this patch adds an
/admin/subscribe endpoint that is SSL-authenticated in production, so
only the admin hosts can connect to it.
This commit is contained in:
Blockstream Satellite 2023-01-31 18:28:50 -03:00
parent 50df236d6b
commit f2058ac3f8
3 changed files with 44 additions and 11 deletions

View File

@ -16,4 +16,11 @@ server {
proxy_http_version 1.1;
proxy_pass http://sse-server:4500/stream?channels=;
}
location /admin/subscribe/ {
proxy_buffering off;
proxy_request_buffering off;
proxy_cache off;
proxy_http_version 1.1;
proxy_pass http://sse-server:4500/admin/stream?channels=;
}
}

View File

@ -1,24 +1,20 @@
// Setup redis
const redis = require('redis').createClient(process.env.REDIS_URI),
channels = process.env.SUB_CHANNELS.split(',')
channels = process.env.SUB_CHANNELS.split(',')
console.log(`Subscribing to Redis on ${channels.join(',')}`)
channels.forEach(chan => redis.subscribe(chan))
// Log messages and number of SSE subscribers
redis.on('message', (chan, msg) => console.log(`Broadcasting ${chan}: ${msg}`))
setInterval(_ => console.log(`Total subscribers: ${ redis.listenerCount('message') - 1 }`), 60000)
setInterval(_ => console.log(`Total subscribers: ${redis.listenerCount('message') - 1}`), 60000)
// Setup express server
const app = require('express')()
app.set('trust proxy', process.env.PROXIED || 'loopback')
app.use(require('morgan')('dev'))
// SSE endpoint
app.get('/stream', (req, res) => {
const subscriptions = req.query.channels && req.query.channels.split(',')
console.log(`New subscriber for ${ subscriptions ? subscriptions.join(',') : 'all channels' }`)
function configureStream(req, res, subscriptions) {
res.set({
'X-Accel-Buffering': 'no',
'Cache-Control': 'no-cache',
@ -26,7 +22,7 @@ app.get('/stream', (req, res) => {
'Connection': 'keep-alive'
}).flushHeaders()
function onMsg (chan, msg) {
function onMsg(chan, msg) {
if (!subscriptions || subscriptions.includes(chan)) {
res.write(`event:${chan}\ndata:${msg}\n\n`)
}
@ -36,11 +32,28 @@ app.get('/stream', (req, res) => {
const keepAlive = setInterval(_ => res.write(': keepalive\n\n'), 25000)
req.once('close', _ => (redis.removeListener('message', onMsg),
clearInterval(keepAlive),
console.log('Subscriber disconnected')))
clearInterval(keepAlive),
console.log('Subscriber disconnected')))
}
app.get('/stream', (req, res) => {
const subscriptions = req.query.channels && req.query.channels.split(',')
// Filter out the channels that can only be monitored by the admin
if (subscriptions.includes('auth')) {
res.status(401).send("Operation not supported on the auth channel");
return;
}
console.log(`New subscriber for ${subscriptions ? subscriptions.join(',') : 'all channels'}`)
configureStream(req, res, subscriptions);
})
app.get('/admin/stream', (req, res) => {
const subscriptions = req.query.channels && req.query.channels.split(',')
console.log(`New admin subscriber for ${subscriptions ? subscriptions.join(',') : 'all channels'}`)
configureStream(req, res, subscriptions);
})
app.listen(
process.env.PORT || 4500,
function() { console.log(`HTTP server running on ${this.address().address}:${this.address().port}`) }
function () { console.log(`HTTP server running on ${this.address().address}:${this.address().port}`) }
)

View File

@ -147,6 +147,19 @@ write_files:
}
# Proxy to mainnet SSE container
location /admin/subscribe/ {
if ($ssl_client_verify != SUCCESS) {
return 403;
}
chunked_transfer_encoding on;
proxy_buffering off;
proxy_request_buffering off;
proxy_cache off;
proxy_http_version 1.1;
proxy_pass http://${mainnet_ip}:4500/admin/stream?channels=;
}
location /subscribe/ {
chunked_transfer_encoding on;
proxy_buffering off;