• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender / 1057366047

01 Nov 2023 08:50AM UTC coverage: 80.121% (-0.07%) from 80.195%
1057366047

push

gitlab-ci

kacf
fix: Don't cache server URL and token locally in process.

The problem if we do is that is that we may then connect to a
forwarder address which doesn't exist anymore. This produces
"connection refused", not "Not authorized", and therefore it does not
trigger an authentication request by itself. So always ask.

This can be easily reproduced by bringing up both mender-auth and
mender-update, and then restarting mender-auth after the first auth
cycle.

This makes the whole `AuthenticatorExternalTokenUpdateTest` test
irrelevant, since we no longer need to test the caching capability of
the client side authenticator. So just remove it.

Changelog: None
Ticket: None

Signed-off-by: Kristian Amlie <kristian.amlie@northern.tech>

6 of 6 new or added lines in 1 file covered. (100.0%)

6884 of 8592 relevant lines covered (80.12%)

9363.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.69
/src/api/auth/auth.cpp
1
// Copyright 2023 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
#include <api/auth.hpp>
16

17
#include <mutex>
18
#include <string>
19
#include <vector>
20

21
#include <common/common.hpp>
22
#include <common/crypto.hpp>
23
#include <common/json.hpp>
24
#include <common/error.hpp>
25
#include <common/log.hpp>
26
#include <common/path.hpp>
27
#include <common/expected.hpp>
28
#include <common/identity_parser.hpp>
29
#include <common/optional.hpp>
30

31
namespace mender {
32
namespace api {
33
namespace auth {
34

35

36
using namespace std;
37
namespace error = mender::common::error;
38
namespace common = mender::common;
39
namespace conf = mender::common::conf;
40

41

42
namespace identity_parser = mender::common::identity_parser;
43
namespace key_value_parser = mender::common::key_value_parser;
44
namespace path = mender::common::path;
45
namespace mlog = mender::common::log;
46
namespace expected = mender::common::expected;
47
namespace io = mender::common::io;
48
namespace json = mender::common::json;
49
namespace crypto = mender::common::crypto;
50

51

52
const string request_uri = "/api/devices/v1/authentication/auth_requests";
53

54
const AuthClientErrorCategoryClass AuthClientErrorCategory;
55

56
const char *AuthClientErrorCategoryClass::name() const noexcept {
×
57
        return "AuthClientErrorCategory";
×
58
}
59

60
string AuthClientErrorCategoryClass::message(int code) const {
4✔
61
        switch (code) {
4✔
62
        case NoError:
63
                return "Success";
×
64
        case SetupError:
65
                return "Error during setup";
×
66
        case RequestError:
67
                return "HTTP client request error";
×
68
        case ResponseError:
69
                return "HTTP client response error";
×
70
        case APIError:
71
                return "API error";
2✔
72
        case UnauthorizedError:
73
                return "Unauthorized error";
×
74
        case AuthenticationError:
75
                return "Authentication error";
2✔
76
        default:
77
                return "Unknown";
×
78
        }
79
}
80

81
error::Error MakeError(AuthClientErrorCode code, const string &msg) {
×
82
        return error::Error(error_condition(code, AuthClientErrorCategory), msg);
3✔
83
}
84

85
namespace http {
86

87
error::Error MakeHTTPResponseError(
2✔
88
        const AuthClientErrorCode code,
89
        const mender::http::ResponsePtr resp,
90
        const string &response_body,
91
        const string &msg) {
92
        return error::Error(
93
                error_condition(code, AuthClientErrorCategory),
2✔
94
                "Authentication error(" + resp->GetStatusMessage() + "): " + msg + "(" + response_body
4✔
95
                        + ")");
6✔
96
}
97

98
static void TryAuthenticate(
99
        vector<string>::const_iterator server_it,
100
        vector<string>::const_iterator end,
101
        mender::http::Client &client,
102
        const string request_body,
103
        const string signature,
104
        APIResponseHandler api_handler);
105

106
error::Error FetchJWTToken(
6✔
107
        mender::http::Client &client,
108
        const vector<string> &servers,
109
        const string &private_key_path,
110
        const string &device_identity_script_path,
111
        APIResponseHandler api_handler,
112
        const string &tenant_token) {
113
        key_value_parser::ExpectedKeyValuesMap expected_identity_data =
114
                identity_parser::GetIdentityData(device_identity_script_path);
6✔
115
        if (!expected_identity_data) {
6✔
116
                return expected_identity_data.error();
×
117
        }
118
        expected::ExpectedString expected_identity_data_json =
119
                json::Dump(expected_identity_data.value());
18✔
120
        if (!expected_identity_data_json) {
6✔
121
                mlog::Error("Failed to dump the identity data to JSON");
×
122
                return expected_identity_data_json.error();
×
123
        }
124
        auto identity_data_json = expected_identity_data_json.value();
6✔
125
        mlog::Debug("Got identity data: " + identity_data_json);
12✔
126

127
        // Create the request body
128
        unordered_map<string, string> request_body_map {
129
                {"id_data", identity_data_json},
130
        };
18✔
131

132
        if (tenant_token.size() > 0) {
6✔
133
                request_body_map.insert({"tenant_token", tenant_token});
4✔
134
        }
135

136
        auto expected_public_key = crypto::ExtractPublicKey(private_key_path);
6✔
137
        if (!expected_public_key) {
6✔
138
                return expected_public_key.error();
×
139
        }
140
        request_body_map.insert({"pubkey", expected_public_key.value()});
12✔
141

142
        auto expected_request_body = json::Dump(request_body_map);
12✔
143
        if (!expected_request_body) {
6✔
144
                return expected_request_body.error();
×
145
        }
146
        auto request_body = expected_request_body.value();
6✔
147

148
        // Sign the body
149
        auto expected_signature =
150
                crypto::SignRawData(private_key_path, common::ByteVectorFromString(request_body));
12✔
151
        if (!expected_signature) {
6✔
152
                return expected_signature.error();
×
153
        }
154
        auto signature = expected_signature.value();
6✔
155

156
        // TryAuthenticate() calls the handler on any potential further errors, we
157
        // are done here with no errors.
158
        TryAuthenticate(servers.cbegin(), servers.cend(), client, request_body, signature, api_handler);
12✔
159
        return error::NoError;
6✔
160
}
161

162
static void TryAuthenticate(
10✔
163
        vector<string>::const_iterator server_it,
164
        vector<string>::const_iterator end,
165
        mender::http::Client &client,
166
        const string request_body,
167
        const string signature,
168
        APIResponseHandler api_handler) {
169
        if (server_it == end) {
10✔
170
                auto err = MakeError(AuthenticationError, "No more servers to try for authentication");
2✔
171
                api_handler(expected::unexpected(err));
2✔
172
                return;
173
        }
174

175
        auto whole_url = mender::http::JoinUrl(*server_it, request_uri);
9✔
176
        auto req = make_shared<mender::http::OutgoingRequest>();
9✔
177
        req->SetMethod(mender::http::Method::POST);
9✔
178
        req->SetAddress(whole_url);
18✔
179
        req->SetHeader("Content-Type", "application/json");
18✔
180
        req->SetHeader("Content-Length", to_string(request_body.size()));
18✔
181
        req->SetHeader("Accept", "application/json");
18✔
182
        req->SetHeader("X-MEN-Signature", signature);
18✔
183
        req->SetHeader("Authorization", "API_KEY");
18✔
184

185
        req->SetBodyGenerator([request_body]() -> io::ExpectedReaderPtr {
45✔
186
                return make_shared<io::StringReader>(request_body);
7✔
187
        });
18✔
188

189
        auto received_body = make_shared<vector<uint8_t>>();
9✔
190

191
        auto err = client.AsyncCall(
192
                req,
193
                [received_body, server_it, end, &client, request_body, signature, api_handler](
9✔
194
                        mender::http::ExpectedIncomingResponsePtr exp_resp) {
2✔
195
                        if (!exp_resp) {
9✔
196
                                mlog::Info(
2✔
197
                                        "Authentication error trying server '" + *server_it
4✔
198
                                        + "': " + exp_resp.error().String());
6✔
199
                                TryAuthenticate(
2✔
200
                                        std::next(server_it), end, client, request_body, signature, api_handler);
4✔
201
                                return;
2✔
202
                        }
203
                        auto resp = exp_resp.value();
7✔
204

205
                        auto body_writer = make_shared<io::ByteWriter>(received_body);
7✔
206
                        body_writer->SetUnlimited(true);
7✔
207
                        resp->SetBodyWriter(body_writer);
7✔
208

209
                        mlog::Debug("Received response header value:");
14✔
210
                        mlog::Debug("Status code:" + to_string(resp->GetStatusCode()));
14✔
211
                        mlog::Debug("Status message: " + resp->GetStatusMessage());
14✔
212
                },
213
                [received_body, server_it, end, &client, request_body, signature, api_handler](
7✔
214
                        mender::http::ExpectedIncomingResponsePtr exp_resp) {
2✔
215
                        if (!exp_resp) {
7✔
216
                                mlog::Info(
×
217
                                        "Authentication error trying server '" + *server_it
×
218
                                        + "': " + exp_resp.error().String());
×
219
                                TryAuthenticate(
×
220
                                        std::next(server_it), end, client, request_body, signature, api_handler);
×
221
                                return;
×
222
                        }
223
                        auto resp = exp_resp.value();
7✔
224

225
                        string response_body = common::StringFromByteVector(*received_body);
7✔
226

227
                        error::Error err;
7✔
228
                        switch (resp->GetStatusCode()) {
7✔
229
                        case mender::http::StatusOK:
5✔
230
                                api_handler(AuthData {*server_it, response_body});
15✔
231
                                return;
5✔
232
                        case mender::http::StatusUnauthorized:
233
                                err = MakeHTTPResponseError(
×
234
                                        UnauthorizedError, resp, response_body, "Failed to authorize with the server.");
×
235
                                mlog::Info(
×
236
                                        "Authentication error trying server '" + *server_it + "': " + err.String());
×
237
                                TryAuthenticate(
×
238
                                        std::next(server_it), end, client, request_body, signature, api_handler);
×
239
                                return;
×
240
                        case mender::http::StatusBadRequest:
241
                        case mender::http::StatusInternalServerError:
242
                                err = MakeHTTPResponseError(
2✔
243
                                        APIError, resp, response_body, "Failed to authorize with the server.");
6✔
244
                                mlog::Info(
2✔
245
                                        "Authentication error trying server '" + *server_it + "': " + err.String());
4✔
246
                                TryAuthenticate(
2✔
247
                                        std::next(server_it), end, client, request_body, signature, api_handler);
4✔
248
                                return;
2✔
249
                        default:
250
                                err =
251
                                        MakeError(ResponseError, "Unexpected error code: " + resp->GetStatusMessage());
×
252
                                mlog::Info(
×
253
                                        "Authentication error trying server '" + *server_it + "': " + err.String());
×
254
                                TryAuthenticate(
×
255
                                        std::next(server_it), end, client, request_body, signature, api_handler);
×
256
                                return;
×
257
                        }
258
                });
36✔
259
        if (err != error::NoError) {
9✔
260
                api_handler(expected::unexpected(err));
×
261
        }
262
}
263
} // namespace http
264

265
error::Error FetchJWTToken(
6✔
266
        mender::http::Client &client,
267
        const vector<string> &servers,
268
        const string &private_key_path,
269
        const string &device_identity_script_path,
270
        APIResponseHandler api_handler,
271
        const string &tenant_token) {
272
        return http::FetchJWTToken(
273
                client, servers, private_key_path, device_identity_script_path, api_handler, tenant_token);
12✔
274
}
275

276
void Authenticator::ExpireToken() {
27✔
277
        if (!token_fetch_in_progress_) {
27✔
278
                RequestNewToken(nullopt);
54✔
279
        }
280
}
27✔
281

282
error::Error Authenticator::StartWatchingTokenSignal() {
10✔
283
        auto err = dbus_client_.RegisterSignalHandler<dbus::ExpectedStringPair>(
284
                "io.mender.Authentication1",
285
                "JwtTokenStateChange",
286
                [this](dbus::ExpectedStringPair ex_auth_dbus_data) {
9✔
287
                        auth_timeout_timer_.Cancel();
3✔
288
                        token_fetch_in_progress_ = false;
3✔
289
                        ExpectedAuthData ex_auth_data;
290
                        if (!ex_auth_dbus_data) {
3✔
291
                                mlog::Error(
×
292
                                        "Error from the JwtTokenStateChange DBus signal: "
293
                                        + ex_auth_dbus_data.error().String());
×
294
                                ex_auth_data = ExpectedAuthData(expected::unexpected(ex_auth_dbus_data.error()));
×
295
                        } else {
296
                                auto &token = ex_auth_dbus_data.value().first;
3✔
297
                                auto &server_url = ex_auth_dbus_data.value().second;
3✔
298
                                AuthData auth_data {server_url, token};
3✔
299
                                ex_auth_data = ExpectedAuthData(std::move(auth_data));
3✔
300
                        }
301
                        PostPendingActions(ex_auth_data);
3✔
302
                });
23✔
303

304
        watching_token_signal_ = (err == error::NoError);
10✔
305
        return err;
10✔
306
}
307

308
void Authenticator::PostPendingActions(ExpectedAuthData &ex_auth_data) {
39✔
309
        for (auto action : pending_actions_) {
47✔
310
                loop_.Post([action, ex_auth_data]() { action(ex_auth_data); });
40✔
311
        }
312
        pending_actions_.clear();
39✔
313
}
39✔
314

315
error::Error Authenticator::RequestNewToken(optional<AuthenticatedAction> opt_action) {
29✔
316
        if (token_fetch_in_progress_) {
29✔
317
                // Just make sure the action (if any) is called once the token is
318
                // obtained.
319
                if (opt_action) {
×
320
                        pending_actions_.push_back(*opt_action);
×
321
                }
322
                return error::NoError;
×
323
        }
324

325
        auto err = dbus_client_.CallMethod<expected::ExpectedBool>(
326
                "io.mender.AuthenticationManager",
327
                "/io/mender/AuthenticationManager",
328
                "io.mender.Authentication1",
329
                "FetchJwtToken",
330
                [this](expected::ExpectedBool ex_value) {
6✔
331
                        if (!ex_value) {
6✔
332
                                token_fetch_in_progress_ = false;
1✔
333
                                mlog::Error("Failed to request new token fetching: " + ex_value.error().String());
2✔
334
                                ExpectedAuthData ex_auth_data = expected::unexpected(ex_value.error());
1✔
335
                                PostPendingActions(ex_auth_data);
1✔
336
                        } else if (!ex_value.value()) {
5✔
337
                                // mender-auth encountered an error not sent over DBus (should never happen)
338
                                token_fetch_in_progress_ = false;
×
339
                                mlog::Error(
×
340
                                        "Failed to request new token fetching (see mender-auth logs for details)");
×
341
                                ExpectedAuthData ex_auth_data = expected::unexpected(MakeError(
×
342
                                        AuthenticationError, "Failed to request new token fetching from mender-auth"));
×
343
                                PostPendingActions(ex_auth_data);
×
344
                        }
345
                });
64✔
346
        if (err != error::NoError) {
29✔
347
                // A sync DBus error.
348
                mlog::Error("Failed to request new token fetching: " + err.String());
46✔
349
                token_fetch_in_progress_ = false;
23✔
350
                ExpectedAuthData ex_auth_data = expected::unexpected(err);
23✔
351
                PostPendingActions(ex_auth_data);
23✔
352
                if (opt_action) {
23✔
353
                        (*opt_action)(ex_auth_data);
×
354
                }
355
                return err;
23✔
356
        }
357
        // else everything went OK
358

359
        token_fetch_in_progress_ = true;
6✔
360

361
        // Make sure the action (if any) is called once the token is
362
        // obtained.
363
        if (opt_action) {
6✔
364
                pending_actions_.push_back(*opt_action);
2✔
365
        }
366

367
        // Make sure we don't wait for the token forever.
368
        auth_timeout_timer_.AsyncWait(auth_timeout_, [this](error::Error err) {
6✔
369
                if (err.code == make_error_condition(errc::operation_canceled)) {
5✔
370
                        return;
371
                } else if (err == error::NoError) {
2✔
372
                        mlog::Warning("Timed-out waiting for a new token");
4✔
373
                        token_fetch_in_progress_ = false;
2✔
374
                        ExpectedAuthData ex_auth_data = expected::unexpected(
2✔
375
                                MakeError(AuthenticationError, "Timed-out waiting for a new token"));
6✔
376
                        PostPendingActions(ex_auth_data);
2✔
377
                } else {
378
                        // should never happen
379
                        assert(false);
380
                        mlog::Error("Authentication timer error: " + err.String());
×
381

382
                        // In case it did happen, run the stacked up actions and unset the
383
                        // in_progress_ flag or things may got stuck.
384
                        token_fetch_in_progress_ = false;
×
385
                        ExpectedAuthData ex_auth_data = expected::unexpected(err);
×
386
                        PostPendingActions(ex_auth_data);
×
387
                }
388
        });
×
389
        return error::NoError;
6✔
390
}
391

392
error::Error Authenticator::WithToken(AuthenticatedAction action) {
18✔
393
        if (!watching_token_signal_) {
18✔
394
                auto err = StartWatchingTokenSignal();
10✔
395
                if (err != error::NoError) {
10✔
396
                        // Should never fail. We rely on the signal heavily so let's fail
397
                        // hard if it does.
398
                        action(expected::unexpected(err));
×
399
                        return err;
×
400
                }
401
        }
402

403
        if (token_fetch_in_progress_) {
18✔
404
                // Already waiting for a new token, just make sure the action is called
405
                // once it arrives (or once the wait times out).
406
                pending_actions_.push_back(action);
6✔
407
                return error::NoError;
6✔
408
        }
409
        // else => should fetch the token, cache it and call all pending actions
410
        // Try to get token from mender-auth
411
        auto err = dbus_client_.CallMethod<dbus::ExpectedStringPair>(
412
                "io.mender.AuthenticationManager",
413
                "/io/mender/AuthenticationManager",
414
                "io.mender.Authentication1",
415
                "GetJwtToken",
416
                [this, action](dbus::ExpectedStringPair ex_auth_dbus_data) {
24✔
417
                        token_fetch_in_progress_ = false;
12✔
418
                        auth_timeout_timer_.Cancel();
12✔
419
                        if (ex_auth_dbus_data && (ex_auth_dbus_data.value().first != "")
11✔
420
                                && (ex_auth_dbus_data.value().second != "")) {
22✔
421
                                // Got a valid token, let's save it and then call action and any
422
                                // previously-pending actions (if any) with it.
423
                                auto &token = ex_auth_dbus_data.value().first;
10✔
424
                                auto &server_url = ex_auth_dbus_data.value().second;
10✔
425
                                AuthData auth_data {server_url, token};
20✔
426

427
                                // Post/schedule pending actions before running the given action
428
                                // because the action can actually add more actions or even
429
                                // expire the token, etc. So post actions stacked up before we
430
                                // got here with the current token and only then give action a
431
                                // chance to mess with things.
432
                                ExpectedAuthData ex_auth_data {std::move(auth_data)};
433
                                PostPendingActions(ex_auth_data);
10✔
434
                                action(ex_auth_data);
20✔
435
                        } else {
436
                                // No valid token, let's request fetching of a new one
437
                                RequestNewToken(action);
4✔
438
                        }
439
                });
36✔
440
        if (err != error::NoError) {
12✔
441
                // No token and failed to try to get one (should never happen).
442
                ExpectedAuthData ex_auth_data = expected::unexpected(err);
×
443
                PostPendingActions(ex_auth_data);
×
444
                action(ex_auth_data);
×
445
                return err;
×
446
        }
447
        // else record that token is already being fetched (by GetJwtToken()).
448
        token_fetch_in_progress_ = true;
12✔
449

450
        return error::NoError;
12✔
451
}
452

453
} // namespace auth
454
} // namespace api
455
} // namespace mender
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc