forked from mirrors/amnezia-wg-easy
		
	Feat Prometheus metrics (#1299)
* Russian translation * Add Prometheus metrics [Feat]: Simple Stats API #1285 * Revert "Add Prometheus metrics" This reverts commit a998f6be8a0c54a5daffe70a0fc3d8b9ed53a960. * Add Prometheus metrics [Feat]: Simple Stats API #1285 * Fix short link. Generate One Time Link (#1301) Co-authored-by: Vadim Babadzhanyan <vadim.babadzhanyan@my.games> * fix one time links (#1304) Closes #1302 Co-authored-by: Bernd Storath <999999bst@gmail.com> * fixup: issue templates due to labels reorg Signed-off-by: Philip H <47042125+pheiduck@users.noreply.github.com> * Separate port for prometheus metrics Add Prometheus metrics [Feat]: Simple Stats API #1285 * Separate port for prometheus metrics Add Prometheus metrics [Feat]: Simple Stats API #1285 * Fix port in Readme Separate port for prometheus metrics Add Prometheus metrics [Feat]: Simple Stats API #1285 * Add Prometheus port in Service Separate port for prometheus metrics Add Prometheus metrics [Feat]: Simple Stats API #1285 * Revert "Add Prometheus port in Service" This reverts commit a7376abcf1fe2b729ab05ba0d49977ab5a2642ea. * Revert "Fix port in Readme" This reverts commit 9760bde2f2dc4428b0bf0b27d91bbded1c2ad05d. * Revert "Separate port for prometheus metrics" This reverts commit 58f5b6806e20c7704ff04247f384d30c2845a34e. * Revert "Separate port for prometheus metrics" This reverts commit 6d246ea4bda265f8b8b9e99acb336aeb26c9fa17. * Add Prometheus metrics with Basic Auth [Feat]: Simple Stats API #1285 * Disable by default [Feat]: Simple Stats API #1285 * [Feat]: Simple Stats API #1285 * Update README.md --------- Co-authored-by: Vadim Babadzhanyan <vadim.babadzhanyan@my.games> Co-authored-by: Bernd Storath <bernd.storath@offizium.de> Co-authored-by: Philip H <47042125+pheiduck@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									41be774761
								
							
						
					
					
						commit
						7be9884aec
					
				
					 7 changed files with 156 additions and 6 deletions
				
			
		| 
						 | 
				
			
			@ -41,3 +41,5 @@ module.exports.UI_CHART_TYPE = process.env.UI_CHART_TYPE || 0;
 | 
			
		|||
module.exports.WG_ENABLE_ONE_TIME_LINKS = process.env.WG_ENABLE_ONE_TIME_LINKS || 'false';
 | 
			
		||||
module.exports.UI_ENABLE_SORT_CLIENTS = process.env.UI_ENABLE_SORT_CLIENTS || 'false';
 | 
			
		||||
module.exports.WG_ENABLE_EXPIRES_TIME = process.env.WG_ENABLE_EXPIRES_TIME || 'false';
 | 
			
		||||
module.exports.ENABLE_PROMETHEUS_METRICS = process.env.ENABLE_PROMETHEUS_METRICS || 'false';
 | 
			
		||||
module.exports.PROMETHEUS_METRICS_PASSWORD = process.env.PROMETHEUS_METRICS_PASSWORD;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
 | 
			
		||||
const bcrypt = require('bcryptjs');
 | 
			
		||||
const crypto = require('node:crypto');
 | 
			
		||||
const basicAuth = require('basic-auth');
 | 
			
		||||
const { createServer } = require('node:http');
 | 
			
		||||
const { stat, readFile } = require('node:fs/promises');
 | 
			
		||||
const { resolve, sep } = require('node:path');
 | 
			
		||||
| 
						 | 
				
			
			@ -36,9 +37,12 @@ const {
 | 
			
		|||
  WG_ENABLE_ONE_TIME_LINKS,
 | 
			
		||||
  UI_ENABLE_SORT_CLIENTS,
 | 
			
		||||
  WG_ENABLE_EXPIRES_TIME,
 | 
			
		||||
  ENABLE_PROMETHEUS_METRICS,
 | 
			
		||||
  PROMETHEUS_METRICS_PASSWORD,
 | 
			
		||||
} = require('../config');
 | 
			
		||||
 | 
			
		||||
const requiresPassword = !!PASSWORD_HASH;
 | 
			
		||||
const requiresPrometheusPassword = !!PROMETHEUS_METRICS_PASSWORD;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Checks if `password` matches the PASSWORD_HASH.
 | 
			
		||||
| 
						 | 
				
			
			@ -48,13 +52,12 @@ const requiresPassword = !!PASSWORD_HASH;
 | 
			
		|||
 * @param {string} password String to test
 | 
			
		||||
 * @returns {boolean} true if matching environment, otherwise false
 | 
			
		||||
 */
 | 
			
		||||
const isPasswordValid = (password) => {
 | 
			
		||||
const isPasswordValid = (password, hash) => {
 | 
			
		||||
  if (typeof password !== 'string') {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (PASSWORD_HASH) {
 | 
			
		||||
    return bcrypt.compareSync(password, PASSWORD_HASH);
 | 
			
		||||
  if (hash) {
 | 
			
		||||
    return bcrypt.compareSync(password, hash);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -162,7 +165,7 @@ module.exports = class Server {
 | 
			
		|||
          });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!isPasswordValid(password)) {
 | 
			
		||||
        if (!isPasswordValid(password, PASSWORD_HASH)) {
 | 
			
		||||
          throw createError({
 | 
			
		||||
            status: 401,
 | 
			
		||||
            message: 'Incorrect Password',
 | 
			
		||||
| 
						 | 
				
			
			@ -192,7 +195,7 @@ module.exports = class Server {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        if (req.url.startsWith('/api/') && req.headers['authorization']) {
 | 
			
		||||
          if (isPasswordValid(req.headers['authorization'])) {
 | 
			
		||||
          if (isPasswordValid(req.headers['authorization'], PASSWORD_HASH)) {
 | 
			
		||||
            return next();
 | 
			
		||||
          }
 | 
			
		||||
          return res.status(401).json({
 | 
			
		||||
| 
						 | 
				
			
			@ -332,6 +335,51 @@ module.exports = class Server {
 | 
			
		|||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Prometheus Metrics API
 | 
			
		||||
    const routerPrometheusMetrics = createRouter();
 | 
			
		||||
    app.use(routerPrometheusMetrics);
 | 
			
		||||
 | 
			
		||||
    // Check Prometheus credentials
 | 
			
		||||
    app.use(
 | 
			
		||||
      fromNodeMiddleware((req, res, next) => {
 | 
			
		||||
        if (!requiresPrometheusPassword || !req.url.startsWith('/metrics')) {
 | 
			
		||||
          return next();
 | 
			
		||||
        }
 | 
			
		||||
        const user = basicAuth(req);
 | 
			
		||||
        if (requiresPrometheusPassword && !user) {
 | 
			
		||||
          res.statusCode = 401;
 | 
			
		||||
          return { error: 'Not Logged In' };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (user.pass) {
 | 
			
		||||
          if (isPasswordValid(user.pass, PROMETHEUS_METRICS_PASSWORD)) {
 | 
			
		||||
            return next();
 | 
			
		||||
          }
 | 
			
		||||
          res.statusCode = 401;
 | 
			
		||||
          return { error: 'Incorrect Password' };
 | 
			
		||||
        }
 | 
			
		||||
        res.statusCode = 401;
 | 
			
		||||
        return { error: 'Not Logged In' };
 | 
			
		||||
      }),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Prometheus Routes
 | 
			
		||||
    routerPrometheusMetrics
 | 
			
		||||
      .get('/metrics', defineEventHandler(async (event) => {
 | 
			
		||||
        setHeader(event, 'Content-Type', 'text/plain');
 | 
			
		||||
        if (ENABLE_PROMETHEUS_METRICS === 'true') {
 | 
			
		||||
          return WireGuard.getMetrics();
 | 
			
		||||
        }
 | 
			
		||||
        return '';
 | 
			
		||||
      }))
 | 
			
		||||
      .get('/metrics/json', defineEventHandler(async (event) => {
 | 
			
		||||
        setHeader(event, 'Content-Type', 'application/json');
 | 
			
		||||
        if (ENABLE_PROMETHEUS_METRICS === 'true') {
 | 
			
		||||
          return WireGuard.getMetricsJSON();
 | 
			
		||||
        }
 | 
			
		||||
        return '';
 | 
			
		||||
      }));
 | 
			
		||||
 | 
			
		||||
    // backup_restore
 | 
			
		||||
    const router3 = createRouter();
 | 
			
		||||
    app.use(router3);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -160,6 +160,7 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
 | 
			
		|||
      latestHandshakeAt: null,
 | 
			
		||||
      transferRx: null,
 | 
			
		||||
      transferTx: null,
 | 
			
		||||
      endpoint: null,
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
    // Loop WireGuard status
 | 
			
		||||
| 
						 | 
				
			
			@ -188,6 +189,7 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
 | 
			
		|||
        client.latestHandshakeAt = latestHandshakeAt === '0'
 | 
			
		||||
          ? null
 | 
			
		||||
          : new Date(Number(`${latestHandshakeAt}000`));
 | 
			
		||||
        client.endpoint = endpoint === '(none)' ? null : endpoint;
 | 
			
		||||
        client.transferRx = Number(transferRx);
 | 
			
		||||
        client.transferTx = Number(transferTx);
 | 
			
		||||
        client.persistentKeepalive = persistentKeepalive;
 | 
			
		||||
| 
						 | 
				
			
			@ -430,4 +432,75 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getMetrics() {
 | 
			
		||||
    const clients = await this.getClients();
 | 
			
		||||
    let wireguardPeerCount = 0;
 | 
			
		||||
    let wireguardEnabledPeersCount = 0;
 | 
			
		||||
    let wireguardConnectedPeersCount = 0;
 | 
			
		||||
    let wireguardSentBytes = '';
 | 
			
		||||
    let wireguardReceivedBytes = '';
 | 
			
		||||
    let wireguardLatestHandshakeSeconds = '';
 | 
			
		||||
    for (const client of Object.values(clients)) {
 | 
			
		||||
      wireguardPeerCount++;
 | 
			
		||||
      if (client.enabled === true) {
 | 
			
		||||
        wireguardEnabledPeersCount++;
 | 
			
		||||
      }
 | 
			
		||||
      if (client.endpoint !== null) {
 | 
			
		||||
        wireguardConnectedPeersCount++;
 | 
			
		||||
      }
 | 
			
		||||
      wireguardSentBytes += `wireguard_sent_bytes{interface="wg0",enabled="${client.enabled}",address="${client.address}",name="${client.name}"} ${Number(client.transferTx)}\n`;
 | 
			
		||||
      wireguardReceivedBytes += `wireguard_received_bytes{interface="wg0",enabled="${client.enabled}",address="${client.address}",name="${client.name}"} ${Number(client.transferRx)}\n`;
 | 
			
		||||
      wireguardLatestHandshakeSeconds += `wireguard_latest_handshake_seconds{interface="wg0",enabled="${client.enabled}",address="${client.address}",name="${client.name}"} ${client.latestHandshakeAt ? (new Date().getTime() - new Date(client.latestHandshakeAt).getTime()) / 1000 : 0}\n`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let returnText = '# HELP wg-easy and wireguard metrics\n';
 | 
			
		||||
 | 
			
		||||
    returnText += '\n# HELP wireguard_configured_peers\n';
 | 
			
		||||
    returnText += '# TYPE wireguard_configured_peers gauge\n';
 | 
			
		||||
    returnText += `wireguard_configured_peers{interface="wg0"} ${Number(wireguardPeerCount)}\n`;
 | 
			
		||||
 | 
			
		||||
    returnText += '\n# HELP wireguard_enabled_peers\n';
 | 
			
		||||
    returnText += '# TYPE wireguard_enabled_peers gauge\n';
 | 
			
		||||
    returnText += `wireguard_enabled_peers{interface="wg0"} ${Number(wireguardEnabledPeersCount)}\n`;
 | 
			
		||||
 | 
			
		||||
    returnText += '\n# HELP wireguard_connected_peers\n';
 | 
			
		||||
    returnText += '# TYPE wireguard_connected_peers gauge\n';
 | 
			
		||||
    returnText += `wireguard_connected_peers{interface="wg0"} ${Number(wireguardConnectedPeersCount)}\n`;
 | 
			
		||||
 | 
			
		||||
    returnText += '\n# HELP wireguard_sent_bytes Bytes sent to the peer\n';
 | 
			
		||||
    returnText += '# TYPE wireguard_sent_bytes counter\n';
 | 
			
		||||
    returnText += `${wireguardSentBytes}`;
 | 
			
		||||
 | 
			
		||||
    returnText += '\n# HELP wireguard_received_bytes Bytes received from the peer\n';
 | 
			
		||||
    returnText += '# TYPE wireguard_received_bytes counter\n';
 | 
			
		||||
    returnText += `${wireguardReceivedBytes}`;
 | 
			
		||||
 | 
			
		||||
    returnText += '\n# HELP wireguard_latest_handshake_seconds UNIX timestamp seconds of the last handshake\n';
 | 
			
		||||
    returnText += '# TYPE wireguard_latest_handshake_seconds gauge\n';
 | 
			
		||||
    returnText += `${wireguardLatestHandshakeSeconds}`;
 | 
			
		||||
 | 
			
		||||
    return returnText;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getMetricsJSON() {
 | 
			
		||||
    const clients = await this.getClients();
 | 
			
		||||
    let wireguardPeerCount = 0;
 | 
			
		||||
    let wireguardEnabledPeersCount = 0;
 | 
			
		||||
    let wireguardConnectedPeersCount = 0;
 | 
			
		||||
    for (const client of Object.values(clients)) {
 | 
			
		||||
      wireguardPeerCount++;
 | 
			
		||||
      if (client.enabled === true) {
 | 
			
		||||
        wireguardEnabledPeersCount++;
 | 
			
		||||
      }
 | 
			
		||||
      if (client.endpoint !== null) {
 | 
			
		||||
        wireguardConnectedPeersCount++;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return {
 | 
			
		||||
      wireguard_configured_peers: Number(wireguardPeerCount),
 | 
			
		||||
      wireguard_enabled_peers: Number(wireguardEnabledPeersCount),
 | 
			
		||||
      wireguard_connected_peers: Number(wireguardConnectedPeersCount),
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										19
									
								
								src/package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										19
									
								
								src/package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -9,6 +9,7 @@
 | 
			
		|||
      "version": "1.0.1",
 | 
			
		||||
      "license": "CC BY-NC-SA 4.0",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "basic-auth": "^2.0.1",
 | 
			
		||||
        "bcryptjs": "^2.4.3",
 | 
			
		||||
        "crc-32": "^1.2.2",
 | 
			
		||||
        "debug": "^4.3.6",
 | 
			
		||||
| 
						 | 
				
			
			@ -992,6 +993,24 @@
 | 
			
		|||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/basic-auth": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "safe-buffer": "5.1.2"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/basic-auth/node_modules/safe-buffer": {
 | 
			
		||||
      "version": "5.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/bcryptjs": {
 | 
			
		||||
      "version": "2.4.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,7 @@
 | 
			
		|||
  "author": "Emile Nijssen",
 | 
			
		||||
  "license": "CC BY-NC-SA 4.0",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "basic-auth": "^2.0.1",
 | 
			
		||||
    "bcryptjs": "^2.4.3",
 | 
			
		||||
    "crc-32": "^1.2.2",
 | 
			
		||||
    "debug": "^4.3.6",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue