diff --git a/config.js b/config.js
index 6338dd7d9a..a2bf6fbfb3 100644
--- a/config.js
+++ b/config.js
@@ -1126,6 +1126,9 @@ config.DEFAULT_REGION = 'us-east-1';
config.VACCUM_ANALYZER_INTERVAL = 86400000;
+config.NOOBAA_METRICS_AUTH_ENABLED = process.env.NOOBAA_METRICS_AUTH_ENABLED === 'true';
+config.NOOBAA_VERSION_AUTH_ENABLED = process.env.NOOBAA_VERSION_AUTH_ENABLED === 'true';
+
/////////////////////
// //
// OVERRIDES //
diff --git a/docs/NooBaaNonContainerized/ConfigFileCustomizations.md b/docs/NooBaaNonContainerized/ConfigFileCustomizations.md
index 43e709e5bc..13723dd71e 100644
--- a/docs/NooBaaNonContainerized/ConfigFileCustomizations.md
+++ b/docs/NooBaaNonContainerized/ConfigFileCustomizations.md
@@ -449,7 +449,7 @@ Warning: After setting this configuration, NooBaa will skip schema validations a
"LOG_TO_STDERR_ENABLED": false
3. systemctl restart noobaa
```
-### 30. Notification log directory
+### 31. Notification log directory
* Key `NOTIFICATION_LOG_DIR`
* Type String
* Default empty
@@ -462,7 +462,7 @@ Warning: After setting this configuration, NooBaa will skip schema validations a
"NOTIFICATION_LOG_DIR": "/etc/notif"
3. systemctl restart noobaa
-### 31. Prometheus HTTP enable flag -
+### 32. Prometheus HTTP enable flag -
* Key: `ALLOW_HTTP_METRICS`
* Type: Boolean
* Default: true
@@ -476,7 +476,7 @@ Warning: After setting this configuration, NooBaa will skip schema validations a
3. systemctl restart noobaa
```
-### 32. Prometheus HTTPS enable flag -
+### 33. Prometheus HTTPS enable flag -
* Key: `ALLOW_HTTPS_METRICS`
* Type: Boolean
* Default: true
@@ -490,7 +490,7 @@ Warning: After setting this configuration, NooBaa will skip schema validations a
3. systemctl restart noobaa
```
-### 33. Notification space monitor frequency flag -
+### 34. Notification space monitor frequency flag -
* Key: `NOTIFICATION_REQ_PER_SPACE_CHECK`
* Type: Positive integer
* Default: 0
@@ -504,7 +504,7 @@ Warning: After setting this configuration, NooBaa will skip schema validations a
3. systemctl restart noobaa
```
-### 34. Notification space monitor threshold flag -
+### 35. Notification space monitor threshold flag -
* Key: `NOTIFICATION_SPACE_CHECK_THRESHOLD`
* Type: Number
* Default: 0.1
@@ -518,7 +518,7 @@ Warning: After setting this configuration, NooBaa will skip schema validations a
3. systemctl restart noobaa
```
-### 34. Dynamic supplemental groups allocation flag -
+### 36. Dynamic supplemental groups allocation flag -
* Key: `NSFS_ENABLE_DYNAMIC_SUPPLEMENTAL_GROUPS`
* Type: boolean
* Default: true
diff --git a/src/endpoint/endpoint.js b/src/endpoint/endpoint.js
index 86812eac8c..2f17d5c0e6 100755
--- a/src/endpoint/endpoint.js
+++ b/src/endpoint/endpoint.js
@@ -349,6 +349,9 @@ function create_endpoint_handler(server_type, init_request_sdk, { virtual_hosts,
* @param {import('http').ServerResponse} res
*/
function version_handler(req, res) {
+ if (config.NOOBAA_VERSION_AUTH_ENABLED) {
+ if (!http_utils.authorize_bearer(req, res)) return;
+ }
const noobaa_package_version = pkg.version;
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
diff --git a/src/endpoint/endpoint_utils.js b/src/endpoint/endpoint_utils.js
index 71f93254bc..9213af93ed 100644
--- a/src/endpoint/endpoint_utils.js
+++ b/src/endpoint/endpoint_utils.js
@@ -4,6 +4,7 @@
const querystring = require('querystring');
const http_utils = require('../util/http_utils');
const pkg = require('../../package.json');
+const config = require('../../config');
function prepare_rest_request(req) {
// generate request id, this is lighter than uuid
@@ -40,7 +41,9 @@ function parse_source_url(source_url) {
}
function set_noobaa_server_header(res) {
- res.setHeader('Server', `NooBaa/${pkg.version}`);
+ if (!config.NOOBAA_VERSION_AUTH_ENABLED) {
+ res.setHeader('Server', `NooBaa/${pkg.version}`);
+ }
}
exports.prepare_rest_request = prepare_rest_request;
diff --git a/src/server/analytic_services/prometheus_reporting.js b/src/server/analytic_services/prometheus_reporting.js
index 7d2e5a7e24..a3a6bced00 100644
--- a/src/server/analytic_services/prometheus_reporting.js
+++ b/src/server/analytic_services/prometheus_reporting.js
@@ -69,6 +69,12 @@ async function start_server(
return;
}
const metrics_request_handler = async (req, res) => {
+ if (config.NOOBAA_METRICS_AUTH_ENABLED) {
+ // Authorize bearer token metrics endpoint
+ // Role 'metrics' is used in operator RPC call,
+ // Update operator RPC call first before changing role
+ if (!http_utils.authorize_bearer(req, res, [ "metrics", "admin" ])) return;
+ }
// Serve all metrics on the root path for system that do have one or more fork running.
if (fork_enabled) {
// we would like this part to be first as clusterMetrics might fail.
diff --git a/src/server/web_server.js b/src/server/web_server.js
index c0efc683a7..5b751ed210 100755
--- a/src/server/web_server.js
+++ b/src/server/web_server.js
@@ -217,6 +217,10 @@ async function get_log_level_handler(req, res) {
}
async function get_version_handler(req, res) {
+ // Authorize bearer token version endpoint
+ if (config.NOOBAA_VERSION_AUTH_ENABLED) {
+ if (!http_utils.authorize_bearer(req, res)) return;
+ }
const { status, version } = await getVersion(req.url);
if (version) res.send(version);
if (status !== 200) res.status(status);
diff --git a/src/util/http_utils.js b/src/util/http_utils.js
index 733eaf46ba..55906cd555 100644
--- a/src/util/http_utils.js
+++ b/src/util/http_utils.js
@@ -946,6 +946,55 @@ function set_response_headers_from_request(req, res) {
if (req.query['response-expires']) res.setHeader('Expires', req.query['response-expires']);
}
+/**
+ * Authenticate JWT bearer token for metrics / version endpoints.
+ * Returns `true` on success, `false` after the function already sent an HTTP
+ * response (401/403) and the caller should terminate the handler early.
+ *
+ * @param {import('http').IncomingMessage} req
+ * @param {import('http').ServerResponse} res
+ * @param {string[]} roles
+ */
+function authorize_bearer(req, res, roles = undefined) {
+ const { authorization, ...rest_headers } = req.headers;
+ if (!authorization) {
+ dbg.error('Authentication required:', req.method, req.url, rest_headers);
+ // request lacks authentication, let the client know it's required with 401 Unauthorized
+ res.statusCode = 401;
+ res.setHeader('WWW-Authenticate', 'Bearer');
+ res.setHeader('Content-Type', 'text/plain');
+ res.end('Unauthorized');
+ return false;
+ }
+ if (!authorization.startsWith('Bearer ')) {
+ dbg.error('Authentication scheme must be Bearer:', req.method, req.url, rest_headers);
+ // authentication was provided but is invalid, return 403 Forbidden.
+ res.statusCode = 403;
+ res.setHeader('Content-Type', 'text/plain');
+ res.end('Forbidden');
+ return false;
+ }
+ const token = authorization.slice('Bearer '.length);
+ let auth;
+ try {
+ auth = jwt_utils.authorize_jwt_token(token);
+ } catch (err) {
+ dbg.error('Authentication failed to verify JWT token:', req.method, req.url, rest_headers, err);
+ res.statusCode = 403;
+ res.setHeader('Content-Type', 'text/plain');
+ res.end('Forbidden');
+ return false;
+ }
+ if (roles && !roles.includes(auth.role)) {
+ dbg.error('Authentication role is not allowed:', auth, roles, req.method, req.url, rest_headers);
+ res.statusCode = 403;
+ res.setHeader('Content-Type', 'text/plain');
+ res.end('Forbidden');
+ return false;
+ }
+ return true;
+}
+
exports.parse_url_query = parse_url_query;
exports.parse_client_ip = parse_client_ip;
exports.get_md_conditions = get_md_conditions;
@@ -984,3 +1033,4 @@ exports.CONTENT_TYPE_APP_JSON = CONTENT_TYPE_APP_JSON;
exports.CONTENT_TYPE_APP_XML = CONTENT_TYPE_APP_XML;
exports.CONTENT_TYPE_APP_FORM_URLENCODED = CONTENT_TYPE_APP_FORM_URLENCODED;
exports.set_response_headers_from_request = set_response_headers_from_request;
+exports.authorize_bearer = authorize_bearer;
diff --git a/src/util/jwt_utils.js b/src/util/jwt_utils.js
index 676fb16e53..854df2ae28 100644
--- a/src/util/jwt_utils.js
+++ b/src/util/jwt_utils.js
@@ -27,6 +27,10 @@ function make_internal_auth_token(object = {}, jwt_options = {}) {
return make_auth_token(object, jwt_options);
}
+/**
+ * authorize jwt token by verifying it against the jwt secret
+ * @param {string} token
+ */
function authorize_jwt_token(token) {
try {
return jwt.verify(token, get_jwt_secret());