Skip to content

Commit 5d8945e

Browse files
Add encrypted basic credentials to mgt ui
consolidate cookies mgt to the server and
1 parent 4094ce9 commit 5d8945e

15 files changed

Lines changed: 407 additions & 211 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ rabbitmq-server-*.tar.xz
123123
rabbitmq-server-*.zip
124124
secondary_dist/
125125

126+
# RabbitMQ javascript files generated from EJS templates
127+
deps/*/priv/www/js/*-ejs.js
128+
126129
# Trace tools output.
127130
*-ttb
128131
*.ti

deps/rabbitmq_management/priv/schema/rabbitmq_management.schema

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,13 @@ end}.
490490
{mapping, "management.disable_basic_auth", "rabbitmq_management.disable_basic_auth",
491491
[{datatype, boolean}]}.
492492

493+
%% When set, the management UI stores an AES-256-GCM encrypted bearer token
494+
%% (prefixed "rmqe.") instead of a plaintext base64(username:password).
495+
%% The value must be identical on every node in the cluster.
496+
{mapping, "management.credentials_encryption_secret",
497+
"rabbitmq_management.credentials_encryption_secret",
498+
[{datatype, [tagged_binary, binary]}]}.
499+
493500
%% Management only
494501

495502
{mapping, "management.disable_stats", "rabbitmq_management.disable_management_stats", [

deps/rabbitmq_management/priv/www/js/main.js

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@ function start_app_login () {
8686
this.get('#/', function () {})
8787
if (!oauth.enabled || !oauth.oauth_disable_basic_auth) {
8888
this.put('#/login', function() {
89-
set_basic_auth(this.params['username'], this.params['password'])
90-
check_login()
89+
login(this.params['username'], this.params['password']);
9190
});
9291
}
9392
})
@@ -117,34 +116,69 @@ function check_login () {
117116
}
118117
return false;
119118
}
120-
check_version()
121-
hide_popup_warn()
122-
replace_content('outer', format('layout', {}))
123-
var user_login_session_timeout = parseInt(user.login_session_timeout)
124-
if (!isNaN(user_login_session_timeout)) {
125-
update_login_session_timeout(user_login_session_timeout)
119+
load_ui();
120+
return true;
121+
}
122+
123+
function do_login(username, password) {
124+
var result = null;
125+
$.ajax({
126+
async: false,
127+
type: 'POST',
128+
url: 'api/login',
129+
data: {username: username, password: password},
130+
success: function(resp) { result = resp; },
131+
error: function(xhr) {
132+
try { result = JSON.parse(xhr.responseText); } catch(e) {}
133+
if (!result) result = {error: 'login_failed', reason: 'Login failed'};
134+
}
135+
});
136+
return result;
137+
}
138+
139+
function login(username, password) {
140+
var result = do_login(username, password);
141+
if (!result || result.error) {
142+
replace_content('login-status', '<p>Login failed</p>');
143+
if (result && result.reason && typeof result.reason === 'string') {
144+
show_popup('warn', fmt_escape_html(result.reason));
145+
}
146+
return false;
147+
}
148+
var scheme = result.token.type === 'bearer' ? 'Bearer' : 'Basic';
149+
set_auth(scheme, result.token.value, default_hard_session_timeout());
150+
user = result.user;
151+
var sessionTimeout = parseInt(user.login_session_timeout);
152+
if (!isNaN(sessionTimeout)) {
153+
update_login_session_timeout(sessionTimeout);
126154
}
155+
load_ui();
156+
return true;
157+
}
158+
159+
function load_ui() {
160+
check_version();
161+
hide_popup_warn();
162+
replace_content('outer', format('layout', {}));
127163

128164
ui_data_model.vhosts = JSON.parse(sync_get('/vhosts'));
129-
ac.update(user, ui_data_model)
165+
ac.update(user, ui_data_model);
130166
if (ac.isMonitoringUser()) {
131-
ui_data_model.nodes = JSON.parse(sync_get('/nodes'))
167+
ui_data_model.nodes = JSON.parse(sync_get('/nodes'));
132168
}
133-
var overview = JSON.parse(sync_get('/overview'))
169+
var overview = JSON.parse(sync_get('/overview'));
134170

135-
display.update(overview, ui_data_model)
171+
display.update(overview, ui_data_model);
136172

137-
setup_global_vars(overview)
173+
setup_global_vars(overview);
138174

139-
setup_constant_events()
140-
update_vhosts()
141-
update_interval()
175+
setup_constant_events();
176+
update_vhosts();
177+
update_interval();
142178
setup_extensions(function onCompleted() {
143179
console.info("All extensions have been loaded. Starting application ..");
144180
start_app();
145181
});
146-
147-
return true
148182
}
149183

150184

Lines changed: 35 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
1-
function local_storage_available() {
2-
try {
3-
return 'localStorage' in window && window['localStorage'] !== null;
4-
} catch (e) {
5-
return false;
6-
}
7-
}
8-
91
/// Credential management
102

113
const CREDENTIALS = 'credentials'
124
const AUTH_SCHEME = "auth-scheme"
13-
const LOGGED_IN = 'loggedIn'
14-
const LOGIN_SESSION_TIMEOUT = "login_session_timeout"
5+
const SESSION_EXPIRY = 'session_expiry'
156
const AUTH_RESOURCE = 'auth_resource'
167

178
const BASIC_AUTH_SCHEME = "Basic"
@@ -30,9 +21,12 @@ function get_auth_resource() {
3021

3122
// When auth_scheme is undefined, matches any scheme for backwards compatibility.
3223
function has_auth_credentials(auth_scheme) {
33-
let authenticated =get_local_pref(CREDENTIALS) != undefined && get_local_pref(AUTH_SCHEME) != undefined &&
34-
get_cookie_value(LOGGED_IN) != undefined;
35-
return authenticated && (auth_scheme == undefined
24+
let expiry = get_local_pref(SESSION_EXPIRY);
25+
let authenticated = get_local_pref(CREDENTIALS) != undefined &&
26+
get_local_pref(AUTH_SCHEME) != undefined &&
27+
expiry != undefined &&
28+
Date.now() < parseInt(expiry, 10);
29+
return authenticated && (auth_scheme == undefined
3630
|| auth_scheme == get_auth_scheme());
3731
}
3832
function get_auth_credentials() {
@@ -44,13 +38,11 @@ function get_auth_scheme() {
4438
function clear_auth() {
4539
clear_local_pref(CREDENTIALS)
4640
clear_local_pref(AUTH_SCHEME)
47-
clear_local_pref(LOGIN_SESSION_TIMEOUT)
48-
clear_cookie_value(LOGGED_IN)
41+
clear_local_pref(SESSION_EXPIRY)
4942
clear_local_pref(AUTH_RESOURCE)
43+
$.ajax({ async: false, type: 'DELETE', url: 'api/login' })
5044
}
5145
function set_basic_auth(username, password) {
52-
// Clear the stored timeout so a fresh login re-applies the configured value.
53-
clear_local_pref(LOGIN_SESSION_TIMEOUT)
5446
set_auth("Basic", b64_encode_utf8(username + ":" + password), default_hard_session_timeout())
5547
}
5648
function set_token_auth(token) {
@@ -59,9 +51,10 @@ function set_token_auth(token) {
5951
function set_auth(auth_scheme, credentials, validUntil) {
6052
clear_local_pref(CREDENTIALS)
6153
clear_local_pref(AUTH_SCHEME)
54+
clear_local_pref(SESSION_EXPIRY)
6255
store_local_pref(CREDENTIALS, credentials)
6356
store_local_pref(AUTH_SCHEME, auth_scheme)
64-
store_cookie_value_with_expiration(LOGGED_IN, "true", validUntil) // session marker
57+
store_local_pref(SESSION_EXPIRY, validUntil.getTime())
6558
}
6659

6760
function authorization_header() {
@@ -78,15 +71,22 @@ function default_hard_session_timeout() {
7871
}
7972

8073
function update_login_session_timeout(login_session_timeout) {
81-
if (get_local_pref(LOGIN_SESSION_TIMEOUT) != undefined || !has_auth_credentials()) {
82-
return;
83-
}
74+
if (!has_auth_credentials()) return;
8475
var date = new Date();
8576
date.setMinutes(date.getMinutes() + login_session_timeout);
86-
store_local_pref(LOGIN_SESSION_TIMEOUT, login_session_timeout);
87-
store_cookie_value_with_expiration(LOGGED_IN, "true", date)
77+
store_local_pref(SESSION_EXPIRY, date.getTime())
78+
}
79+
80+
function print_logging_session_info (user_login_session_timeout) {
81+
let authenticated = has_auth_credentials()
82+
let session_expiry = get_local_pref(SESSION_EXPIRY)
83+
console.log('user_login_session_timeout: ' + user_login_session_timeout)
84+
console.log('has_auth_credentials: ' + authenticated)
85+
console.log('session_expiry: ' + session_expiry)
86+
console.log('isNaN(user_login_session_timeout): ' + isNaN(user_login_session_timeout))
8887
}
8988

89+
9090
/// End Credential Management
9191

9292
// Our base64 library takes a string that is really a byte sequence,
@@ -102,83 +102,33 @@ function encode_utf8(str) {
102102
return unescape(encodeURIComponent(str));
103103
}
104104

105-
function store_cookie_value(k, v) {
106-
var d = parse_cookie();
107-
d[short_key(k)] = v;
108-
store_cookie(d);
109-
}
105+
// All preferences and credentials are stored in localStorage.
106+
// The management UI requires localStorage; without it no part of the UI functions.
110107

111-
function store_cookie_value_with_expiration(k, v, expiration_date) {
112-
var d = parse_cookie();
113-
d[short_key(k)] = v;
114-
store_cookie_with_expiration(d, expiration_date);
108+
function store_local_pref(k, v) {
109+
window.localStorage.setItem('rabbitmq.' + k, v);
115110
}
116111

117-
function clear_cookie_value(k) {
118-
var d = parse_cookie();
119-
delete d[short_key(k)];
120-
store_cookie(d);
112+
function clear_local_pref(k) {
113+
window.localStorage.removeItem('rabbitmq.' + k);
121114
}
122115

123-
function get_cookie_value(k) {
124-
var r;
125-
r = parse_cookie()[short_key(k)];
126-
return r == undefined ? default_pref(k) : r;
127-
}
128-
function store_local_pref(k, v) {
129-
if (local_storage_available()) {
130-
window.localStorage.setItem('rabbitmq.' + k, v);
131-
}else {
132-
throw "Local Storage not available. RabbitMQ requires localStorage"
133-
}
134-
}
135-
function clear_local_pref(k) {
136-
if (local_storage_available()) {
137-
window.localStorage.removeItem('rabbitmq.' + k);
138-
}
116+
function get_local_pref(k) {
117+
return window.localStorage.getItem('rabbitmq.' + k);
139118
}
140119

141120
function store_pref(k, v) {
142-
if (local_storage_available()) {
143-
window.localStorage['rabbitmq.' + k] = v;
144-
}
145-
else {
146-
var d = parse_cookie();
147-
d[short_key(k)] = v;
148-
store_cookie(d);
149-
}
121+
store_local_pref(k, v);
150122
}
151123

152124
function clear_pref(k) {
153-
if (local_storage_available()) {
154-
window.localStorage.removeItem('rabbitmq.' + k);
155-
}
156-
else {
157-
var d = parse_cookie();
158-
delete d[short_key(k)];
159-
store_cookie(d);
160-
}
161-
}
162-
function get_local_pref(k) {
163-
if (local_storage_available()) {
164-
return window.localStorage.getItem('rabbitmq.' + k)
165-
}else {
166-
throw "Local Storage not available. RabbitMQ requires localStorage"
167-
}
125+
clear_local_pref(k);
168126
}
169127

170128
function get_pref(k, defaultValue = undefined) {
171-
var val;
172-
if (local_storage_available()) {
173-
val = window.localStorage['rabbitmq.' + k];
174-
}
175-
else {
176-
val = parse_cookie()[short_key(k)];
177-
178-
}
179-
var res = (val == undefined) ?
129+
var val = get_local_pref(k);
130+
return (val == undefined) ?
180131
(defaultValue != undefined ? defaultValue : default_pref(k)) : val;
181-
return res;
182132
}
183133

184134
function section_pref(template, name) {
@@ -218,57 +168,3 @@ function default_column_pref(key0) {
218168
return 'false';
219169
}
220170

221-
// ---------------------------------------------------------------------------
222-
223-
function parse_cookie() {
224-
var c = get_cookie('m');
225-
var items = c.length == 0 ? [] : c.split('|');
226-
227-
var start = 0;
228-
var dict = {};
229-
for (var i in items) {
230-
var kv = items[i].split(':');
231-
dict[kv[0]] = unescape(kv[1]);
232-
}
233-
return dict;
234-
}
235-
236-
function store_cookie(dict) {
237-
store_cookie_with_expiration(dict, default_hard_session_timeout());
238-
}
239-
240-
function store_cookie_with_expiration(dict, expiration_date) {
241-
var enc = [];
242-
for (var k in dict) {
243-
enc.push(k + ':' + escape(dict[k]));
244-
}
245-
document.cookie = 'm=' + enc.join('|') + '; expires=' + expiration_date.toUTCString() + "; path=/";
246-
// console.log("Cookie m expires at " + expiration_date);
247-
}
248-
249-
function get_cookie(key) {
250-
var cookies = document.cookie.split(';');
251-
for (var i in cookies) {
252-
var kv = cookies[i].trim().split('=');
253-
if (kv[0] == key) return kv[1];
254-
}
255-
return '';
256-
}
257-
258-
// Try to economise on space since cookies have limited length.
259-
function short_key(k) {
260-
var res = Math.abs(k.hashCode() << 16 >> 16);
261-
res = res.toString(16);
262-
return res;
263-
}
264-
265-
String.prototype.hashCode = function() {
266-
var hash = 0;
267-
if (this.length == 0) return code;
268-
for (i = 0; i < this.length; i++) {
269-
char = this.charCodeAt(i);
270-
hash = 31*hash+char;
271-
hash = hash & hash; // Convert to 32bit integer
272-
}
273-
return hash;
274-
}

0 commit comments

Comments
 (0)