8000 Revive startup parameter `--server.session-timeout` (#14118) · arangodb/arangodb@e9c6ee9 · GitHub
[go: up one dir, main page]

Skip to content

Commit e9c6ee9

Browse files
jsteemanngoedderz
andauthored
Revive startup parameter --server.session-timeout (#14118)
Co-authored-by: Tobias Gödderz <tobias@arangodb.com>
1 parent 705b35e commit e9c6ee9

File tree

7 files changed

+155
-43
lines changed

7 files changed

+155
-43
lines changed

CHANGELOG

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
devel
22
-----
33

4+
* Revive startup parameter `--server.session-timeout` to control the timeout
5+
for web interface sessions and other sessions that are based on JWTs created
6+
by the `/_open/auth` API.
7+
8+
This PR also changes the default session timeout for web interface sessions
9+
to one hour. Older versions of ArangoDB had longer session timeouts.
10+
411
* Add prefix parameter to LEVENSHTEIN_MATCH function in ArangoSearch
512

613
* Removed redirects from /_admin/cluster* to /_admin/cluster/*. Adjusted

arangod/GeneralServer/AuthenticationFeature.cpp

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ AuthenticationFeature::AuthenticationFeature(application_features::ApplicationSe
5757
_authenticationSystemOnly(true),
5858
_localAuthentication(true),
5959
_active(true),
60-
_authenticationTimeout(0.0) {
60+
_authenticationTimeout(0.0),
61+
_sessionTimeout(static_cast<double>(1 * std::chrono::hours(1) / std::chrono::seconds(1))) { // 1 hour
6162
setOptional(false);
6263
startsAfter<application_features::BasicFeaturePhaseServer>();
6364

@@ -89,10 +90,23 @@ void AuthenticationFeature::collectOptions(std::shared_ptr<ProgramOptions> optio
8990
"--server.authentication-timeout",
9091
"timeout for the authentication cache in seconds (0 = indefinitely)",
9192
new DoubleParameter(&_authenticationTimeout));
93+
94+
options->addOption("--server.session-timeout",
95+
"timeout in seconds for web interface JWT sessions",
96+
new DoubleParameter(&_sessionTimeout),
97+
arangodb::options::makeFlags(
98+
arangodb::options::Flags::DefaultNoComponents,
99+
arangodb::options::Flags::OnCoordinator,
100+
arangodb::options::Flags::OnSingle))
101+
.setIntroducedIn(30900);
92102

93103
options->addOption("--server.local-authentication",
94104
"enable authentication using the local user database",
95-
new BooleanParameter(&_localAuthentication));
105+
new BooleanParameter(&_localAuthentication),
106+
arangodb::options::makeFlags(
107+
arangodb::options::Flags::DefaultNoComponents,
108+
arangodb::options::Flags::OnCoordinator,
109+
arangodb::options::Flags::OnSingle));
96110

97111
options->addOption(
98112
"--server.authentication-system-only",
@@ -102,10 +116,13 @@ void AuthenticationFeature::collectOptions(std::shared_ptr<ProgramOptions> optio
102116
#ifdef ARANGODB_HAVE_DOMAIN_SOCKETS
103117
options->addOption("--server.authentication-unix-sockets",
104118
"authentication for requests via UNIX domain sockets",
105-
new BooleanParameter(&_authenticationUnixSockets));
119+
new BooleanParameter(&_authenticationUnixSockets),
120+
arangodb::options::makeFlags(
121+
arangodb::options::Flags::DefaultNoOs,
122+
arangodb::options::Flags::OsLinux,
123+
arangodb::options::Flags::OsMac));
106124
#endif
107125

108-
// Maybe deprecate this option in devel
109126
options
110127
->addOption("--server.jwt-secret",
111128
"secret to use when doing jwt authentication",
@@ -151,6 +168,12 @@ void AuthenticationFeature::validateOptions(std::shared_ptr<ProgramOptions> opti
151168
FATAL_ERROR_EXIT();
152169
}
153170
}
171+
172+
if (_sessionTimeout <= 1.0) {
173+
LOG_TOPIC("85046", FATAL, arangodb::Logger::AUTHENTICATION)
174+
<< "--server.session-timeout has an invalid value: " << _sessionTimeout;
175+
FATAL_ERROR_EXIT();
176+
}
154177

155178
if (options->processingResult().touched("server.jwt-secret")) {
156179
LOG_TOPIC("1aaae", WARN, arangodb::Logger::AUTHENTICATION)

arangod/GeneralServer/AuthenticationFeature.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ class AuthenticationFeature final : public application_features::ApplicationFeat
7272
/// verification only secrets
7373
std::pair<std::string, std::vector<std::string>> jwtSecrets() const;
7474
#endif
75+
76+
double sessionTimeout() const { return _sessionTimeout; }
7577

7678
// load secrets from file(s)
7779
[[nodiscard]] Result loadJwtSecretsFromFile();
@@ -91,6 +93,7 @@ class AuthenticationFeature final : public application_features::ApplicationFeat
9193
bool _localAuthentication;
9294
bool _active;
9395
double _authenticationTimeout;
96+
double _sessionTimeout;
9497

9598
mutable std::mutex _jwtSecretsLock;
9699

arangod/RestHandler/RestAuthHandler.cpp

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@
2727
#include <velocypack/Builder.h>
2828
#include <velocypack/velocypack-aliases.h>
2929

30+
#include "Basics/ScopeGuard.h"
3031
#include "Basics/StringUtils.h"
3132
#include "GeneralServer/AuthenticationFeature.h"
3233
#include "Logger/LogMacros.h"
3334
#include "Logger/Logger.h"
3435
#include "Logger/LoggerStream.h"
35-
#include "Ssl/SslInterface.h"
3636
#include "Utils/Events.h"
3737

3838
using namespace arangodb;
@@ -41,15 +41,7 @@ using namespace arangodb::rest;
4141

4242
RestAuthHandler::RestAuthHandler(application_features::ApplicationServer& server,
4343
GeneralRequest* request, GeneralResponse* response)
44-
: RestVocbaseBaseHandler(server, request, response),
45-
_validFor(60 * 60 * 24 * 30) {}
46-
47-
std::string RestAuthHandler::generateJwt(std::string const& username,
48-
std::string const& password) {
49-
AuthenticationFeature* af = AuthenticationFeature::instance();
50-
TRI_ASSERT(af != nullptr);
51-
return fuerte::jwt::generateUserToken(af->tokenCache().jwtSecret(), username, _validFor);
52-
}
44+
: RestVocbaseBaseHandler(server, request, response) {}
5345

5446
RestStatus RestAuthHandler::execute() {
5547
auto const type = _request->requestType();
@@ -75,23 +67,36 @@ RestStatus RestAuthHandler::execute() {
7567
return badRequest();
7668
}
7769

78-
_username = usernameSlice.copyString();
70+
std::string const username = usernameSlice.copyString();
7971
std::string const password = passwordSlice.copyString();
8072

73+
bool isValid = false;
74+
75+
auto guard = scopeGuard([&]() {
76+
try {
77+
if (isValid) {
78+
events::LoggedIn(*_request, username);
79+
} else {
80+
events::CredentialsBad(*_request, username);
81+
}
82+
} catch (...) {
83+
// nothing we can do
84+
}
85+
});
86+
8187
auth::UserManager* um = AuthenticationFeature::instance()->userManager();
8288
if (um == nullptr) {
8389
std::string msg = "This server does not support users";
8490
LOG_TOPIC("2e7d4", ERR, Logger::AUTHENTICATION) << msg;
8591
generateError(rest::ResponseCode::UNAUTHORIZED, TRI_ERROR_HTTP_UNAUTHORIZED, msg);
86-
} else if (um->checkPassword(_username, password)) {
92+
} else if (um->checkPassword(username, password)) {
8793
VPackBuilder resultBuilder;
8894
{
8995
VPackObjectBuilder b(&resultBuilder);
90-
std::string jwt = generateJwt(_username, password);
91-
resultBuilder.add("jwt", VPackValue(jwt));
96+
resultBuilder.add("jwt", VPackValue(generateJwt(username)));
9297
}
9398

94-
_isValid = true;
99+
isValid = true;
95100
generateDocument(resultBuilder.slice(), true, &VPackOptions::Defaults);
96101
} else {
97102
// mop: rfc 2616 10.4.2 (if credentials wrong 401)
@@ -101,20 +106,17 @@ RestStatus RestAuthHandler::execute() {
101106
return RestStatus::DONE;
102107
}
103108

109+
std::string RestAuthHandler::generateJwt(std::string const& username) const {
110+
AuthenticationFeature* af = AuthenticationFeature::instance();
111+
TRI_ASSERT(af != nullptr);
112+
return fuerte::jwt::generateUserToken(
113+
af->tokenCache().jwtSecret(),
114+
username,
115+
std::chrono::seconds(uint64_t(af->sessionTimeout())));
116+
}
117+
104118
RestStatus RestAuthHandler::badRequest() {
105119
generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER,
106120
"invalid JSON");
107121
return RestStatus::DONE;
108122
}
109-
110-
void RestAuthHandler::shutdownExecute(bool isFinalized) noexcept {
111-
try {
112-
if (_isValid) {
113-
events::LoggedIn(*_request, _username);
114-
} else {
115-
events::CredentialsBad(*_request, _username);
116-
}
117-
} catch (...) {
118-
}
119-
RestVocbaseBaseHandler::shutdownExecute(isFinalized);
120-
}

arangod/RestHandler/RestAuthHandler.h

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,14 @@ class RestAuthHandler : public RestVocbaseBaseHandler {
3333
public:
3434
RestAuthHandler(application_features::ApplicationServer&, GeneralRequest*, GeneralResponse*);
3535

36-
std::string generateJwt(std::string const&, std::string const&);
37-
3836
public:
3937
char const* name() const override final { return "RestAuthHandler"; }
4038
RequestLane lane() const override final { return RequestLane::CLIENT_SLOW; }
4139
RestStatus execute() override;
42-
void shutdownExecute(bool isFinalized) noexcept override;
4340

4441
private:
42+
std::string generateJwt(std::string const& username) const;
4543
RestStatus badRequest();
46-
47-
private:
48-
std::string _username;
49-
bool _isValid = false;
50-
std::chrono::seconds _validFor;
5144
};
5245
} // namespace arangodb
5346

arangod/RestServer/ServerFeature.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,6 @@ void ServerFeature::collectOptions(std::shared_ptr<ProgramOptions> options) {
103103
options->addObsoleteOption("--vst.maxsize", "maximal size (in bytes) "
104104
"for a VelocyPack chunk", true);
105105

106-
options->addObsoleteOption(
107-
"--server.session-timeout",
108-
"timeout of web interface server sessions (in seconds)", true);
109-
110106
// add obsolete MMFiles WAL options (obsoleted in 3.7)
111107
options->addSection("wal", "WAL of the MMFiles engine", "", true, true);
112108
options->addObsoleteOption("--wal.allow-oversize-entries",
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*jshint globalstrict:false, strict:false */
2+
/* global getOptions, assertEqual, arango */
3+
4+
////////////////////////////////////////////////////////////////////////////////
5+
/// @brief test for server parameters
6+
///
7+
/// DISCLAIMER
8+
///
9+
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany
10+
///
11+
/// Licensed under the Apache License, Version 2.0 (the "License");
12+
/// you may not use this file except in compliance with the License.
13+
/// You may obtain a copy of the License at
14+
///
15+
/// http://www.apache.org/licenses/LICENSE-2.0
16+
///
17+
/// Unless required by applicable law or agreed to in writing, software
18+
/// distributed under the License is distributed on an "AS IS" BASIS,
19+
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20+
/// See the License for the specific language governing permissions and
21+
/// limitations under the License.
22+
///
23+
/// Copyright holder is ArangoDB Inc, Cologne, Germany
24+
///
25+
/// @author Jan Steemann
26+
/// @author Copyright 2019, ArangoDB Inc, Cologne, Germany
27+
////////////////////////////////////////////////////////////////////////////////
28+
29+
if (getOptions === true) {
30+
return {
31+
'server.session-timeout': '5',
32+
'server.authentication': 'true',
33+
'server.jwt-secret': 'haxxmann',
34+
};
35+
}
36+
const jsunity = require('jsunity');
37+
const request = require('@arangodb/request');
38+
39+
function testSuite() {
40+
let baseUrl = function () {
41+
return arango.getEndpoint().replace(/^tcp:/, 'http:').replace(/^ssl:/, 'https:');
42+
};
43+
44+
return {
45+
testSessionTimeout: function() {
46+
let result = request.get(baseUrl() + "/_api/version");
47+
// no access
48+
assertEqual(401, result.statusCode);
49+
50+
result = request.post({
51+
url: baseUrl() + "/_open/auth",
52+
body: {
53+
username: "root",
54+
< 10000 span class=pl-c1>password: ""
55+
},
56+
json: true
57+
});
58+
59+
assertEqual(200, result.statusCode);
60+
const jwt = result.json.jwt;
61+
62+
result = request.get({
63+
url: baseUrl() + "/_api/version",
64+
auth: {
65+
bearer: jwt,
66+
}
67+
});
68+
69+
// access granted
70+
assertEqual(200, result.statusCode);
71+
72+
require("internal").sleep(7);
73+
74+
result = request.get({
75+
url: baseUrl() + "/_api/version",
76+
auth: {
77+
bearer: jwt,
78+
}
79+
});
80+
81+
// JWT expired
82+
assertEqual(401, result.statusCode);
83+
},
84+
};
85+
}
86+
87+
jsunity.run(testSuite);
88+
return jsunity.done();

0 commit comments

Comments
 (0)
0