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

mendersoftware / mender / 1056318808

31 Oct 2023 02:09PM UTC coverage: 80.209% (+0.04%) from 80.167%
1056318808

push

gitlab-ci

lluiscampos
fix: Serialize identity keys as single strings when possible

Use similar logic than for inventory keys: if the vector has one
element serialize as string, else submit as a list of strings.

Changelog: None
Ticket: MEN-681

Signed-off-by: Lluis Campos <lluis.campos@northern.tech>
(cherry picked from commit dd09332c7)

22 of 22 new or added lines in 2 files covered. (100.0%)

6906 of 8610 relevant lines covered (80.21%)

9345.51 hits per line

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

73.61
/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

119
        auto identity_data_json = identity_parser::DumpIdentityData(expected_identity_data.value());
12✔
120
        mlog::Debug("Got identity data: " + identity_data_json);
12✔
121

122
        // Create the request body
123
        unordered_map<string, string> request_body_map {
124
                {"id_data", identity_data_json},
125
        };
18✔
126

127
        if (tenant_token.size() > 0) {
6✔
128
                request_body_map.insert({"tenant_token", tenant_token});
4✔
129
        }
130

131
        auto expected_public_key = crypto::ExtractPublicKey(private_key_path);
6✔
132
        if (!expected_public_key) {
6✔
133
                return expected_public_key.error();
×
134
        }
135
        request_body_map.insert({"pubkey", expected_public_key.value()});
12✔
136

137
        auto expected_request_body = json::Dump(request_body_map);
12✔
138
        if (!expected_request_body) {
6✔
139
                return expected_request_body.error();
×
140
        }
141
        auto request_body = expected_request_body.value();
6✔
142

143
        // Sign the body
144
        auto expected_signature =
145
                crypto::SignRawData(private_key_path, common::ByteVectorFromString(request_body));
12✔
146
        if (!expected_signature) {
6✔
147
                return expected_signature.error();
×
148
        }
149
        auto signature = expected_signature.value();
6✔
150

151
        // TryAuthenticate() calls the handler on any potential further errors, we
152
        // are done here with no errors.
153
        TryAuthenticate(servers.cbegin(), servers.cend(), client, request_body, signature, api_handler);
12✔
154
        return error::NoError;
6✔
155
}
156

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

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

180
        req->SetBodyGenerator([request_body]() -> io::ExpectedReaderPtr {
45✔
181
                return make_shared<io::StringReader>(request_body);
7✔
182
        });
18✔
183

184
        auto received_body = make_shared<vector<uint8_t>>();
9✔
185

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

200
                        auto body_writer = make_shared<io::ByteWriter>(received_body);
7✔
201
                        body_writer->SetUnlimited(true);
7✔
202
                        resp->SetBodyWriter(body_writer);
7✔
203

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

220
                        string response_body = common::StringFromByteVector(*received_body);
7✔
221

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

260
error::Error FetchJWTToken(
6✔
261
        mender::http::Client &client,
262
        const vector<string> &servers,
263
        const string &private_key_path,
264
        const string &device_identity_script_path,
265
        APIResponseHandler api_handler,
266
        const string &tenant_token) {
267
        return http::FetchJWTToken(
268
                client, servers, private_key_path, device_identity_script_path, api_handler, tenant_token);
12✔
269
}
270

271
void Authenticator::ExpireToken() {
27✔
272
        token_ = nullopt;
273
        server_url_ = nullopt;
274

275
        if (!token_fetch_in_progress_) {
27✔
276
                RequestNewToken(nullopt);
54✔
277
        }
278
}
27✔
279

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

302
        watching_token_signal_ = (err == error::NoError);
11✔
303
        return err;
11✔
304
}
305

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

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

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

357
        token_fetch_in_progress_ = true;
6✔
358

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

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

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

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

401
        if (token_ && server_url_) {
20✔
402
                AuthData auth_data {*server_url_, *token_};
6✔
403
                action(ExpectedAuthData(std::move(auth_data)));
3✔
404
                return error::NoError;
3✔
405
        }
406
        // else => no token
407

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

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

455
        return error::NoError;
11✔
456
}
457

458
} // namespace auth
459
} // namespace api
460
} // 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