Skip to content

Commit bea018f

Browse files
authored
Merge pull request #9029 from naveenpaul1/metrics-auth-token
Authentication for metrics and version endpoint
2 parents d69f08a + 4a5ea9f commit bea018f

File tree

8 files changed

+81
-7
lines changed

8 files changed

+81
-7
lines changed

config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,9 @@ config.DEFAULT_REGION = 'us-east-1';
11261126

11271127
config.VACCUM_ANALYZER_INTERVAL = 86400000;
11281128

1129+
config.NOOBAA_METRICS_AUTH_ENABLED = process.env.NOOBAA_METRICS_AUTH_ENABLED === 'true';
1130+
config.NOOBAA_VERSION_AUTH_ENABLED = process.env.NOOBAA_VERSION_AUTH_ENABLED === 'true';
1131+
11291132
/////////////////////
11301133
// //
11311134
// OVERRIDES //

docs/NooBaaNonContainerized/ConfigFileCustomizations.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ Warning: After setting this configuration, NooBaa will skip schema validations a
449449
"LOG_TO_STDERR_ENABLED": false
450450
3. systemctl restart noobaa
451451
```
452-
### 30. Notification log directory
452+
### 31. Notification log directory
453453
* <u>Key</u> `NOTIFICATION_LOG_DIR`
454454
* <u>Type</u> String
455455
* <u>Default</u> empty
@@ -462,7 +462,7 @@ Warning: After setting this configuration, NooBaa will skip schema validations a
462462
"NOTIFICATION_LOG_DIR": "/etc/notif"
463463
3. systemctl restart noobaa
464464
465-
### 31. Prometheus HTTP enable flag -
465+
### 32. Prometheus HTTP enable flag -
466466
* <u>Key</u>: `ALLOW_HTTP_METRICS`
467467
* <u>Type</u>: Boolean
468468
* <u>Default</u>: true
@@ -476,7 +476,7 @@ Warning: After setting this configuration, NooBaa will skip schema validations a
476476
3. systemctl restart noobaa
477477
```
478478
479-
### 32. Prometheus HTTPS enable flag -
479+
### 33. Prometheus HTTPS enable flag -
480480
* <u>Key</u>: `ALLOW_HTTPS_METRICS`
481481
* <u>Type</u>: Boolean
482482
* <u>Default</u>: true
@@ -490,7 +490,7 @@ Warning: After setting this configuration, NooBaa will skip schema validations a
490490
3. systemctl restart noobaa
491491
```
492492
493-
### 33. Notification space monitor frequency flag -
493+
### 34. Notification space monitor frequency flag -
494494
* <u>Key</u>: `NOTIFICATION_REQ_PER_SPACE_CHECK`
495495
* <u>Type</u>: Positive integer
496496
* <u>Default</u>: 0
@@ -504,7 +504,7 @@ Warning: After setting this configuration, NooBaa will skip schema validations a
504504
3. systemctl restart noobaa
505505
```
506506
507-
### 34. Notification space monitor threshold flag -
507+
### 35. Notification space monitor threshold flag -
508508
* <u>Key</u>: `NOTIFICATION_SPACE_CHECK_THRESHOLD`
509509
* <u>Type</u>: Number
510510
* <u>Default</u>: 0.1
@@ -518,7 +518,7 @@ Warning: After setting this configuration, NooBaa will skip schema validations a
518518
3. systemctl restart noobaa
519519
```
520520
521-
### 34. Dynamic supplemental groups allocation flag -
521+
### 36. Dynamic supplemental groups allocation flag -
522522
* <u>Key</u>: `NSFS_ENABLE_DYNAMIC_SUPPLEMENTAL_GROUPS`
523523
* <u>Type</u>: boolean
524524
* <u>Default</u>: true

src/endpoint/endpoint.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ function create_endpoint_handler(server_type, init_request_sdk, { virtual_hosts,
349349
* @param {import('http').ServerResponse} res
350350
*/
351351
function version_handler(req, res) {
352+
if (config.NOOBAA_VERSION_AUTH_ENABLED && !http_utils.authorize_bearer(req, res)) return;
352353
const noobaa_package_version = pkg.version;
353354
res.statusCode = 200;
354355
res.setHeader('Content-Type', 'text/plain');

src/endpoint/endpoint_utils.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
const querystring = require('querystring');
55
const http_utils = require('../util/http_utils');
66
const pkg = require('../../package.json');
7+
const config = require('../../config');
78

89
function prepare_rest_request(req) {
910
// generate request id, this is lighter than uuid
@@ -40,7 +41,9 @@ function parse_source_url(source_url) {
4041
}
4142

4243
function set_noobaa_server_header(res) {
43-
res.setHeader('Server', `NooBaa/${pkg.version}`);
44+
if (!config.NOOBAA_VERSION_AUTH_ENABLED) {
45+
res.setHeader('Server', `NooBaa/${pkg.version}`);
46+
}
4447
}
4548

4649
exports.prepare_rest_request = prepare_rest_request;

src/server/analytic_services/prometheus_reporting.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ const reports = Object.seal({
2121
endpoint: null // optional
2222
});
2323

24+
const ROLE_METRICS = 'metrics';
25+
const ROLE_ADMIN = 'admin';
26+
2427
let io_stats_complete = {};
2528
let ops_stats_complete = {};
2629
let fs_worker_stats_complete = {};
@@ -69,6 +72,12 @@ async function start_server(
6972
return;
7073
}
7174
const metrics_request_handler = async (req, res) => {
75+
if (config.NOOBAA_METRICS_AUTH_ENABLED && !http_utils.authorize_bearer(req, res, [ ROLE_METRICS, ROLE_ADMIN ])) {
76+
// Authorize bearer token metrics endpoint
77+
// Role 'metrics' is used in operator RPC call,
78+
// Update operator RPC call first before changing role
79+
return;
80+
}
7281
// Serve all metrics on the root path for system that do have one or more fork running.
7382
if (fork_enabled) {
7483
// we would like this part to be first as clusterMetrics might fail.

src/server/web_server.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ async function get_log_level_handler(req, res) {
217217
}
218218

219219
async function get_version_handler(req, res) {
220+
// Authorize bearer token version endpoint
221+
if (config.NOOBAA_VERSION_AUTH_ENABLED && !http_utils.authorize_bearer(req, res)) return;
220222
const { status, version } = await getVersion(req.url);
221223
if (version) res.send(version);
222224
if (status !== 200) res.status(status);

src/util/http_utils.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,57 @@ function set_response_headers_from_request(req, res) {
946946
if (req.query['response-expires']) res.setHeader('Expires', req.query['response-expires']);
947947
}
948948

949+
/**
950+
* Authenticate JWT bearer token for metrics / version endpoints.
951+
* Returns `true` on success, `false` after the function already sent an HTTP
952+
* response (401/403) and the caller should terminate the handler early.
953+
*
954+
* @param {import('http').IncomingMessage} req
955+
* @param {import('http').ServerResponse} res
956+
* @param {string[]} [roles]
957+
*/
958+
function authorize_bearer(req, res, roles = undefined) {
959+
const { authorization, ...rest_headers } = req.headers;
960+
961+
const populate_response = () => {
962+
res.statusCode = 403;
963+
res.setHeader('Content-Type', 'text/plain');
964+
res.end('Forbidden');
965+
};
966+
967+
968+
if (!authorization) {
969+
dbg.error('Authentication required:', req.method, req.url, rest_headers);
970+
// request lacks authentication, let the client know it's required with 401 Unauthorized
971+
res.statusCode = 401;
972+
res.setHeader('WWW-Authenticate', 'Bearer');
973+
res.setHeader('Content-Type', 'text/plain');
974+
res.end('Unauthorized');
975+
return false;
976+
}
977+
if (!authorization.startsWith('Bearer ')) {
978+
dbg.error('Authentication scheme must be Bearer:', req.method, req.url, rest_headers);
979+
// authentication was provided but is invalid, return 403 Forbidden.
980+
populate_response();
981+
return false;
982+
}
983+
const token = authorization.slice('Bearer '.length);
984+
let auth;
985+
try {
986+
auth = jwt_utils.authorize_jwt_token(token);
987+
} catch (err) {
988+
dbg.error('Authentication failed to verify JWT token:', req.method, req.url, rest_headers, err);
989+
populate_response();
990+
return false;
991+
}
992+
if (roles && !roles.includes(auth.role)) {
993+
dbg.error('Authentication role is not allowed:', auth, roles, req.method, req.url, rest_headers);
994+
populate_response();
995+
return false;
996+
}
997+
return true;
998+
}
999+
9491000
exports.parse_url_query = parse_url_query;
9501001
exports.parse_client_ip = parse_client_ip;
9511002
exports.get_md_conditions = get_md_conditions;
@@ -984,3 +1035,4 @@ exports.CONTENT_TYPE_APP_JSON = CONTENT_TYPE_APP_JSON;
9841035
exports.CONTENT_TYPE_APP_XML = CONTENT_TYPE_APP_XML;
9851036
exports.CONTENT_TYPE_APP_FORM_URLENCODED = CONTENT_TYPE_APP_FORM_URLENCODED;
9861037
exports.set_response_headers_from_request = set_response_headers_from_request;
1038+
exports.authorize_bearer = authorize_bearer;

src/util/jwt_utils.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ function make_internal_auth_token(object = {}, jwt_options = {}) {
2727
return make_auth_token(object, jwt_options);
2828
}
2929

30+
/**
31+
* authorize jwt token by verifying it against the jwt secret
32+
* @param {string} token
33+
*/
3034
function authorize_jwt_token(token) {
3135
try {
3236
return jwt.verify(token, get_jwt_secret());

0 commit comments

Comments
 (0)