Adding the REDIS_PASSWORD env variable for authentication

This commit is contained in:
2025-11-13 14:25:48 -06:00
parent 76df7f9c52
commit ad7d8533a0

View File

@@ -1,276 +1,320 @@
const { fork } = require('node:child_process');
const fs = require('fs');
const yaml = require('js-yaml');
const redis = require('redis');
const { nsupdate } = require('./nsupdate');
const { dig } = require('./dig');
const { elevenLogJSON } = require('/labels/lib/util.js');
const { fork } = require("node:child_process");
const fs = require("fs");
const yaml = require("js-yaml");
const redis = require("redis");
const { nsupdate } = require("./nsupdate");
const { dig } = require("./dig");
const { elevenLogJSON } = require("/labels/lib/util.js");
process
.on('unhandledRejection', (e, p) => {
elevenLogJSON('error', {unhandledRejection:e.toString()});
.on("unhandledRejection", (e, p) => {
elevenLogJSON("error", { unhandledRejection: e.toString() });
})
.on('uncaughtException', e => {
elevenLogJSON('error', {uncaughtException:e.toString()});
.on("uncaughtException", (e) => {
elevenLogJSON("error", { uncaughtException: e.toString() });
});
class Labels{
#config = {webhook:{headers:{'Content-Type':'application/json'}}};
class Labels {
#config = { webhook: { headers: { "Content-Type": "application/json" } } };
#workers = {};
#interval = {run:false, fok:false};
#interval = { run: false, fok: false };
#redis;
constructor(){
const config = yaml.load(fs.readFileSync(`${process.env.APP_ROOT}/etc/config.yaml`, 'utf8'))?.labels;
this.#config.redis = {url:(config?.redis?.url || 'rediss://localhost:6379/0')};
this.#config.webhook.url = (config?.webhook?.url || null);
if(this.#config.webhook.url && config?.webhook?.auth?.basic){
this.#config.webhook.headers['Authorization'] = 'Basic ' + Buffer.from(config.webhook.auth.basic).toString('base64');
elevenLogJSON('info', `using webhook ${this.#config.webhook.url} with basic authentication`);
constructor() {
const config = yaml.load(
fs.readFileSync(`${process.env.APP_ROOT}/etc/config.yaml`, "utf8"),
)?.labels;
this.#config.redis = {
url: config?.redis?.url || "rediss://localhost:6379/0",
password: process.env.REDIS_PASSWORD,
};
this.#config.webhook.url = config?.webhook?.url || null;
if (this.#config.webhook.url && config?.webhook?.auth?.basic) {
this.#config.webhook.headers["Authorization"] =
"Basic " + Buffer.from(config.webhook.auth.basic).toString("base64");
elevenLogJSON(
"info",
`using webhook ${this.#config.webhook.url} with basic authentication`,
);
}
this.#config.rfc2136 = {verify:(config?.rfc2136?.verify || false), remove:(config?.rfc2136?.remove || false)};
this.#config.poll = {interval:(config?.poll?.interval || 300)};
this.#config.ping = {interval:(config?.ping?.interval || 2.5)};
this.#config.port = (config?.port || 2376);
this.#config.timeout = (config?.timeout || 5);
this.#config.interval = (config?.interval || 0);
this.#config.rfc2136 = {
verify: config?.rfc2136?.verify || false,
remove: config?.rfc2136?.remove || false,
};
this.#config.poll = { interval: config?.poll?.interval || 300 };
this.#config.ping = { interval: config?.ping?.interval || 2.5 };
this.#config.port = config?.port || 2376;
this.#config.timeout = config?.timeout || 5;
this.#config.interval = config?.interval || 0;
this.#config.tls = {
ca:(config?.tls?.ca || `${process.env.APP_ROOT}/ssl/ca.crt`),
crt:(config?.tls?.crt || `${process.env.APP_ROOT}/ssl/labels.crt`),
key:(config?.tls?.key || `${process.env.APP_ROOT}/ssl/labels.key`),
ca: config?.tls?.ca || `${process.env.APP_ROOT}/ssl/ca.crt`,
crt: config?.tls?.crt || `${process.env.APP_ROOT}/ssl/labels.crt`,
key: config?.tls?.key || `${process.env.APP_ROOT}/ssl/labels.key`,
};
if(this.#config.interval > 0){
elevenLogJSON('info', `${process.env.APP_ROOT}/etc/config.yaml will reload labels.nodes every ${this.#config.interval}s`);
if (this.#config.interval > 0) {
elevenLogJSON(
"info",
`${process.env.APP_ROOT}/etc/config.yaml will reload labels.nodes every ${this.#config.interval}s`,
);
}
}
async run(){
async run() {
this.#redis = await redis.createClient({
url:this.#config.redis.url,
pingInterval:30000,
socket:{
rejectUnauthorized:false,
url: this.#config.redis.url,
pingInterval: 30000,
socket: {
rejectUnauthorized: false,
},
disableOfflineQueue:false,
commandsQueueMaxLength:1024
password: this.#config.redis.url,
disableOfflineQueue: false,
commandsQueueMaxLength: 1024,
});
this.#redis.on('ready', async()=>{
elevenLogJSON('info', `connected to redis`);
this.#redis.on("ready", async () => {
elevenLogJSON("info", `connected to redis`);
this.#run();
if(this.#config.interval > 0){
setInterval(async ()=>{
if(!this.#interval.run){
if (this.#config.interval > 0) {
setInterval(async () => {
if (!this.#interval.run) {
this.#interval.run = true;
try{
try {
await this.#run();
}catch(e){
elevenLogJSON('error', {error:e});
}finally{
} catch (e) {
elevenLogJSON("error", { error: e });
} finally {
this.#interval.run = false;
}
}
}, this.#config.interval*1000);
}, this.#config.interval * 1000);
}
setInterval(async()=>{
if(!this.#interval.fork){
setInterval(async () => {
if (!this.#interval.fork) {
this.#interval.fork = true;
try{
try {
await this.#fork();
}catch(e){
elevenLogJSON('error', {error:e});
}finally{
} catch (e) {
elevenLogJSON("error", { error: e });
} finally {
this.#interval.fork = false;
}
}
}, this.#config.ping.interval*1000);
}, this.#config.ping.interval * 1000);
});
this.#redis.on('error', error =>{
elevenLogJSON('error', {redis:error.toString()});
this.#redis.on("error", (error) => {
elevenLogJSON("error", { redis: error.toString() });
});
this.#redis.connect();
}
#run(){
const nodes = yaml.load(fs.readFileSync(`${process.env.APP_ROOT}/etc/config.yaml`, 'utf8'))?.labels?.nodes;
for(const node of nodes){
if(!this.#workers[node]){
#run() {
const nodes = yaml.load(
fs.readFileSync(`${process.env.APP_ROOT}/etc/config.yaml`, "utf8"),
)?.labels?.nodes;
for (const node of nodes) {
if (!this.#workers[node]) {
this.#workers[node] = new Worker(this.#config, node, this);
this.#workers[node].fork();
elevenLogJSON('info', `created new worker for node [${node}]`);
elevenLogJSON("info", `created new worker for node [${node}]`);
}
}
}
async #fork(){
for(const node in this.#workers){
if(!this.#workers[node].run){
async #fork() {
for (const node in this.#workers) {
if (!this.#workers[node].run) {
await this.#workers[node].fork();
if(!this.#workers[node].log.disconnect){
if (!this.#workers[node].log.disconnect) {
this.#workers[node].log.disconnect = true;
elevenLogJSON('info', `trying to fork existing worker for node [${node}] after disconnect`);
elevenLogJSON(
"info",
`trying to fork existing worker for node [${node}] after disconnect`,
);
}
}
}
}
async inspect(container){
try{
async inspect(container) {
try {
const counter = {
add:0,
del:0,
add: 0,
del: 0,
};
const rfc2136 = {
WAN:{server:'', key:'', commands:[]},
LAN:{server:'', key:'', commands:[]},
}
for(const label in container.labels){
switch(true){
WAN: { server: "", key: "", commands: [] },
LAN: { server: "", key: "", commands: [] },
};
for (const label in container.labels) {
switch (true) {
case /traefik\//i.test(label):
if(container.start){
if (container.start) {
counter.add++;
await this.#redis.set(label, container.labels[label], {EX:this.#config.poll.interval + 30});
}else{
await this.#redis.set(label, container.labels[label], {
EX: this.#config.poll.interval + 30,
});
} else {
counter.del++;
await this.#redis.del(label);
}
break;
break;
case /rfc2136\//i.test(label):
const type = ((label.match(/rfc2136\/WAN\//i)) ? 'WAN' : 'LAN');
switch(true){
const type = label.match(/rfc2136\/WAN\//i) ? "WAN" : "LAN";
switch (true) {
case /rfc2136\/\S+\/server/i.test(label):
rfc2136[type].server = container.labels[label];
break;
break;
case /rfc2136\/\S+\/key/i.test(label):
rfc2136[type].key = container.labels[label];
break;
break;
default:
if(!container.start && this.#config.rfc2136.remove){
container.labels[label] = container.labels[label].replace(/update add/i, 'update delete');
if (!container.start && this.#config.rfc2136.remove) {
container.labels[label] = container.labels[label].replace(
/update add/i,
"update delete",
);
}
rfc2136[type].commands.push(container.labels[label]);
}
break;
break;
}
}
if(rfc2136.LAN.commands.length > 0 || rfc2136.WAN.commands.length){
if (rfc2136.LAN.commands.length > 0 || rfc2136.WAN.commands.length) {
await this.#rfc2136(rfc2136);
}
if(this.#config?.webhook?.url){
if (this.#config?.webhook?.url) {
await this.#webhook(container);
}
}catch(e){
elevenLogJSON('error', {inspect:e.toString(), exception:e});
} catch (e) {
elevenLogJSON("error", { inspect: e.toString(), exception: e });
}
}
async #rfc2136(rfc2136){
for(const type in rfc2136){
if(rfc2136[type].commands.length > 0 && rfc2136[type].server && rfc2136[type].key){
if(this.#config.rfc2136.verify){
for(let i=0; i<rfc2136[type].commands.length; i++){
if(await this.#rfc2136knownRecord(rfc2136[type].server, rfc2136[type].commands[i])){
async #rfc2136(rfc2136) {
for (const type in rfc2136) {
if (
rfc2136[type].commands.length > 0 &&
rfc2136[type].server &&
rfc2136[type].key
) {
if (this.#config.rfc2136.verify) {
for (let i = 0; i < rfc2136[type].commands.length; i++) {
if (
await this.#rfc2136knownRecord(
rfc2136[type].server,
rfc2136[type].commands[i],
)
) {
rfc2136[type].commands.splice(i, 1);
}
}
}
try{
if(rfc2136[type].commands.length > 0){
await nsupdate(rfc2136[type].server, rfc2136[type].key, rfc2136[type].commands);
try {
if (rfc2136[type].commands.length > 0) {
await nsupdate(
rfc2136[type].server,
rfc2136[type].key,
rfc2136[type].commands,
);
}
}catch(e){
elevenLogJSON('error', {nsupdate:{exception:e.toString()}});
} catch (e) {
elevenLogJSON("error", { nsupdate: { exception: e.toString() } });
}
}
}
}
async #rfc2136knownRecord(server, nsupdate){
async #rfc2136knownRecord(server, nsupdate) {
const matches = nsupdate.match(/update add (\S+) \d+ (\S+) (\S+)/i);
if(matches && matches.length >= 4){
try{
if (matches && matches.length >= 4) {
try {
const record = await dig(server, matches[2], matches[1]);
return((record.match(new RegExp(matches[3], 'ig'))));
}catch(e){
return(false);
return record.match(new RegExp(matches[3], "ig"));
} catch (e) {
return false;
}
}
}
}
async #webhook(container){
try{
await fetch(this.#config.webhook.url, {method:(
(container.start) ? 'PUT' : 'DELETE'
), body:JSON.stringify(container), headers:this.#config.webhook.headers, signal:AbortSignal.timeout(2500)});
}catch(e){
elevenLogJSON('error', {webhook:{exception:e.toString()}});
async #webhook(container) {
try {
await fetch(this.#config.webhook.url, {
method: container.start ? "PUT" : "DELETE",
body: JSON.stringify(container),
headers: this.#config.webhook.headers,
signal: AbortSignal.timeout(2500),
});
} catch (e) {
elevenLogJSON("error", { webhook: { exception: e.toString() } });
}
}
}
class Worker{
class Worker {
run = false;
#fork;
#config;
#parent;
log = {
disconnect:false,
}
disconnect: false,
};
constructor(config, node, parent){
constructor(config, node, parent) {
this.#config = {
tls:config.tls,
poll:config.poll.interval,
ping:config.ping.interval,
port:config.port,
node:node,
tls: config.tls,
poll: config.poll.interval,
ping: config.ping.interval,
port: config.port,
node: node,
};
this.#parent = parent;
}
async fork(){
return(new Promise((resolve, reject) => {
this.#fork = fork(`${process.env.APP_ROOT}/worker.js`, [JSON.stringify(this.#config)], {stdio: 'inherit'});
this.#fork.on('spawn', () =>{
async fork() {
return new Promise((resolve, reject) => {
this.#fork = fork(
`${process.env.APP_ROOT}/worker.js`,
[JSON.stringify(this.#config)],
{ stdio: "inherit" },
);
this.#fork.on("spawn", () => {
this.run = true;
resolve();
});
this.#fork.on('error', () =>{
this.#fork.on("error", () => {
this.run = false;
reject();
});
this.#fork.on('close', (code) =>{
this.#fork.on("close", (code) => {
this.run = false;
});
this.#fork.on('message', (message) =>{
if(message.error){
if(!this.log.disconnect){
elevenLogJSON('error', {fork:message.error});
this.#fork.on("message", (message) => {
if (message.error) {
if (!this.log.disconnect) {
elevenLogJSON("error", { fork: message.error });
}
}else{
} else {
this.run = true;
this.log.disconnect = false;
if(message?.labels){
if (message?.labels) {
this.#parent.inspect.apply(this.#parent, [message]);
}
}
});
}));
});
}
}
new Labels().run();
new Labels().run();