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

mendersoftware / mender / 1039476627

17 Oct 2023 10:49AM UTC coverage: 79.701% (-0.6%) from 80.278%
1039476627

push

gitlab-ci

oleorhagen
fix(mender-auth): Remember to pass in the needed params

Just add the missing identity script, and private key params.

Ticket: MEN-6671
Changelog: None

Signed-off-by: Ole Petter <ole.orhagen@northern.tech>

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

6557 of 8227 relevant lines covered (79.7%)

9764.5 hits per line

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

66.67
/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 {
×
61
        switch (code) {
×
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";
×
72
        case UnauthorizedError:
73
                return "Unauthorized error";
×
74
        case AuthenticationError:
75
                return "Authentication error";
×
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);
2✔
83
}
84

85
namespace http {
86

87
error::Error MakeHTTPResponseError(
×
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),
×
94
                "Authentication error(" + resp->GetStatusMessage() + "): " + msg + "(" + response_body
×
95
                        + ")");
×
96
}
97

98
error::Error FetchJWTToken(
3✔
99
        mender::http::Client &client,
100
        const string &server_url,
101
        const string &private_key_path,
102
        const string &device_identity_script_path,
103
        APIResponseHandler api_handler,
104
        const string &tenant_token) {
105
        key_value_parser::ExpectedKeyValuesMap expected_identity_data =
106
                identity_parser::GetIdentityData(device_identity_script_path);
3✔
107
        if (!expected_identity_data) {
3✔
108
                return expected_identity_data.error();
×
109
        }
110
        expected::ExpectedString expected_identity_data_json =
111
                json::Dump(expected_identity_data.value());
9✔
112
        if (!expected_identity_data_json) {
3✔
113
                mlog::Error("Failed to dump the identity data to JSON");
×
114
                return expected_identity_data_json.error();
×
115
        }
116
        auto identity_data_json = expected_identity_data_json.value();
3✔
117
        mlog::Debug("Got identity data: " + identity_data_json);
6✔
118

119
        // Create the request body
120
        unordered_map<string, string> request_body_map {
121
                {"id_data", identity_data_json},
122
        };
9✔
123

124
        if (tenant_token.size() > 0) {
3✔
125
                request_body_map.insert({"tenant_token", tenant_token});
2✔
126
        }
127

128
        auto expected_public_key = crypto::ExtractPublicKey(private_key_path);
3✔
129
        if (!expected_public_key) {
3✔
130
                return expected_public_key.error();
×
131
        }
132
        request_body_map.insert({"pubkey", expected_public_key.value()});
6✔
133

134
        auto expected_request_body = json::Dump(request_body_map);
6✔
135
        if (!expected_request_body) {
3✔
136
                return expected_request_body.error();
×
137
        }
138
        auto request_body = expected_request_body.value();
3✔
139

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

148
        auto whole_url = mender::http::JoinUrl(server_url, request_uri);
3✔
149

150
        auto req = make_shared<mender::http::OutgoingRequest>();
3✔
151
        req->SetMethod(mender::http::Method::POST);
3✔
152
        req->SetAddress(whole_url);
6✔
153
        req->SetHeader("Content-Type", "application/json");
6✔
154
        req->SetHeader("Content-Length", to_string(request_body.size()));
6✔
155
        req->SetHeader("Accept", "application/json");
6✔
156
        req->SetHeader("X-MEN-Signature", signature);
6✔
157
        req->SetHeader("Authorization", "API_KEY");
6✔
158

159
        req->SetBodyGenerator([request_body]() -> io::ExpectedReaderPtr {
15✔
160
                return make_shared<io::StringReader>(request_body);
3✔
161
        });
6✔
162

163
        auto received_body = make_shared<vector<uint8_t>>();
3✔
164

165
        return client.AsyncCall(
166
                req,
167
                [received_body, api_handler](mender::http::ExpectedIncomingResponsePtr exp_resp) {
3✔
168
                        if (!exp_resp) {
3✔
169
                                mlog::Error("Request failed: " + exp_resp.error().message);
×
170
                                api_handler(expected::unexpected(exp_resp.error()));
×
171
                                return;
×
172
                        }
173
                        auto resp = exp_resp.value();
3✔
174

175
                        auto body_writer = make_shared<io::ByteWriter>(received_body);
3✔
176
                        body_writer->SetUnlimited(true);
3✔
177
                        resp->SetBodyWriter(body_writer);
3✔
178

179
                        mlog::Debug("Received response header value:");
6✔
180
                        mlog::Debug("Status code:" + to_string(resp->GetStatusCode()));
6✔
181
                        mlog::Debug("Status message: " + resp->GetStatusMessage());
6✔
182
                },
183
                [received_body, api_handler](mender::http::ExpectedIncomingResponsePtr exp_resp) {
3✔
184
                        if (!exp_resp) {
3✔
185
                                mlog::Error("Request failed: " + exp_resp.error().message);
×
186
                                api_handler(expected::unexpected(exp_resp.error()));
×
187
                                return;
×
188
                        }
189
                        auto resp = exp_resp.value();
3✔
190

191
                        string response_body = common::StringFromByteVector(*received_body);
3✔
192

193
                        switch (resp->GetStatusCode()) {
3✔
194
                        case mender::http::StatusOK:
3✔
195
                                api_handler(response_body);
3✔
196
                                return;
3✔
197
                        case mender::http::StatusUnauthorized:
×
198
                                api_handler(expected::unexpected(MakeHTTPResponseError(
×
199
                                        UnauthorizedError,
200
                                        resp,
201
                                        response_body,
202
                                        "Failed to authorize with the server.")));
×
203
                                return;
×
204
                        case mender::http::StatusBadRequest:
×
205
                        case mender::http::StatusInternalServerError:
206
                                api_handler(expected::unexpected(MakeHTTPResponseError(
×
207
                                        APIError, resp, response_body, "Failed to authorize with the server.")));
×
208
                                return;
×
209
                        default:
210
                                mlog::Error("Unexpected error code " + resp->GetStatusMessage());
×
211
                                api_handler(expected::unexpected(MakeError(
×
212
                                        ResponseError, "Unexpected error code: " + resp->GetStatusMessage())));
×
213
                                return;
×
214
                        }
215
                });
12✔
216
}
217
} // namespace http
218

219
error::Error FetchJWTToken(
3✔
220
        mender::http::Client &client,
221
        const string &server_url,
222
        const string &private_key_path,
223
        const string &device_identity_script_path,
224
        APIResponseHandler api_handler,
225
        const string &tenant_token) {
226
        return http::FetchJWTToken(
227
                client,
228
                server_url,
229
                private_key_path,
230
                device_identity_script_path,
231
                api_handler,
232
                tenant_token);
6✔
233
}
234

235
void Authenticator::ExpireToken() {
27✔
236
        token_ = nullopt;
237
        server_url_ = nullopt;
238

239
        if (!token_fetch_in_progress_) {
27✔
240
                RequestNewToken(nullopt);
54✔
241
        }
242
}
27✔
243

244
error::Error Authenticator::StartWatchingTokenSignal() {
11✔
245
        auto err = dbus_client_.RegisterSignalHandler<dbus::ExpectedStringPair>(
246
                "io.mender.Authentication1",
247
                "JwtTokenStateChange",
248
                [this](dbus::ExpectedStringPair ex_auth_dbus_data) {
4✔
249
                        auth_timeout_timer_.Cancel();
4✔
250
                        token_fetch_in_progress_ = false;
4✔
251
                        ExpectedAuthData ex_auth_data;
252
                        if (!ex_auth_dbus_data) {
4✔
253
                                mlog::Error(
×
254
                                        "Error from the JwtTokenStateChange DBus signal: "
255
                                        + ex_auth_dbus_data.error().String());
×
256
                                ex_auth_data = ExpectedAuthData(expected::unexpected(ex_auth_dbus_data.error()));
×
257
                        } else {
258
                                token_ = ex_auth_dbus_data.value().first;
4✔
259
                                server_url_ = ex_auth_dbus_data.value().second;
4✔
260
                                AuthData auth_data {*server_url_, *token_};
4✔
261
                                ex_auth_data = ExpectedAuthData(std::move(auth_data));
4✔
262
                        }
263
                        PostPendingActions(ex_auth_data);
4✔
264
                });
26✔
265

266
        watching_token_signal_ = (err == error::NoError);
11✔
267
        return err;
11✔
268
}
269

270
void Authenticator::PostPendingActions(ExpectedAuthData &ex_auth_data) {
39✔
271
        for (auto action : pending_actions_) {
47✔
272
                loop_.Post([action, ex_auth_data]() { action(ex_auth_data); });
40✔
273
        }
274
        pending_actions_.clear();
39✔
275
}
39✔
276

277
error::Error Authenticator::RequestNewToken(optional<AuthenticatedAction> opt_action) {
29✔
278
        if (token_fetch_in_progress_) {
29✔
279
                // Just make sure the action (if any) is called once the token is
280
                // obtained.
281
                if (opt_action) {
×
282
                        pending_actions_.push_back(*opt_action);
×
283
                }
284
                return error::NoError;
×
285
        }
286

287
        auto err = dbus_client_.CallMethod<expected::ExpectedBool>(
288
                "io.mender.AuthenticationManager",
289
                "/io/mender/AuthenticationManager",
290
                "io.mender.Authentication1",
291
                "FetchJwtToken",
292
                [this](expected::ExpectedBool ex_value) {
6✔
293
                        if (!ex_value) {
6✔
294
                                token_fetch_in_progress_ = false;
1✔
295
                                mlog::Error("Failed to request new token fetching: " + ex_value.error().String());
2✔
296
                                ExpectedAuthData ex_auth_data = expected::unexpected(ex_value.error());
1✔
297
                                PostPendingActions(ex_auth_data);
1✔
298
                        } else if (!ex_value.value()) {
5✔
299
                                // mender-auth encountered an error not sent over DBus (should never happen)
300
                                token_fetch_in_progress_ = false;
×
301
                                mlog::Error(
×
302
                                        "Failed to request new token fetching (see mender-auth logs for details)");
×
303
                                ExpectedAuthData ex_auth_data = expected::unexpected(MakeError(
×
304
                                        AuthenticationError, "Failed to request new token fetching from mender-auth"));
×
305
                                PostPendingActions(ex_auth_data);
×
306
                        }
307
                });
64✔
308
        if (err != error::NoError) {
29✔
309
                // A sync DBus error.
310
                mlog::Error("Failed to request new token fetching: " + err.String());
46✔
311
                token_fetch_in_progress_ = false;
23✔
312
                ExpectedAuthData ex_auth_data = expected::unexpected(err);
23✔
313
                PostPendingActions(ex_auth_data);
23✔
314
                if (opt_action) {
23✔
315
                        (*opt_action)(ex_auth_data);
×
316
                }
317
                return err;
23✔
318
        }
319
        // else everything went OK
320

321
        token_fetch_in_progress_ = true;
6✔
322

323
        // Make sure the action (if any) is called once the token is
324
        // obtained.
325
        if (opt_action) {
6✔
326
                pending_actions_.push_back(*opt_action);
2✔
327
        }
328

329
        // Make sure we don't wait for the token forever.
330
        auth_timeout_timer_.AsyncWait(auth_timeout_, [this](error::Error err) {
6✔
331
                if (err.code == make_error_condition(errc::operation_canceled)) {
5✔
332
                        return;
333
                } else if (err == error::NoError) {
2✔
334
                        mlog::Warning("Timed-out waiting for a new token");
4✔
335
                        token_fetch_in_progress_ = false;
2✔
336
                        ExpectedAuthData ex_auth_data = expected::unexpected(
2✔
337
                                MakeError(AuthenticationError, "Timed-out waiting for a new token"));
6✔
338
                        PostPendingActions(ex_auth_data);
2✔
339
                } else {
340
                        // should never happen
341
                        assert(false);
342
                        mlog::Error("Authentication timer error: " + err.String());
×
343

344
                        // In case it did happen, run the stacked up actions and unset the
345
                        // in_progress_ flag or things may got stuck.
346
                        token_fetch_in_progress_ = false;
×
347
                        ExpectedAuthData ex_auth_data = expected::unexpected(err);
×
348
                        PostPendingActions(ex_auth_data);
×
349
                }
350
        });
×
351
        return error::NoError;
6✔
352
}
353

354
error::Error Authenticator::WithToken(AuthenticatedAction action) {
20✔
355
        if (!watching_token_signal_) {
20✔
356
                auto err = StartWatchingTokenSignal();
11✔
357
                if (err != error::NoError) {
11✔
358
                        // Should never fail. We rely on the signal heavily so let's fail
359
                        // hard if it does.
360
                        action(expected::unexpected(err));
×
361
                        return err;
×
362
                }
363
        }
364

365
        if (token_ && server_url_) {
20✔
366
                AuthData auth_data {*server_url_, *token_};
6✔
367
                action(ExpectedAuthData(std::move(auth_data)));
3✔
368
                return error::NoError;
3✔
369
        }
370
        // else => no token
371

372
        if (token_fetch_in_progress_) {
17✔
373
                // Already waiting for a new token, just make sure the action is called
374
                // once it arrives (or once the wait times out).
375
                pending_actions_.push_back(action);
6✔
376
                return error::NoError;
6✔
377
        }
378
        // else => should fetch the token, cache it and call all pending actions
379
        // Try to get token from mender-auth
380
        auto err = dbus_client_.CallMethod<dbus::ExpectedStringPair>(
381
                "io.mender.AuthenticationManager",
382
                "/io/mender/AuthenticationManager",
383
                "io.mender.Authentication1",
384
                "GetJwtToken",
385
                [this, action](dbus::ExpectedStringPair ex_auth_dbus_data) {
31✔
386
                        token_fetch_in_progress_ = false;
11✔
387
                        auth_timeout_timer_.Cancel();
11✔
388
                        if (ex_auth_dbus_data && (ex_auth_dbus_data.value().first != "")
10✔
389
                                && (ex_auth_dbus_data.value().second != "")) {
20✔
390
                                // Got a valid token, let's save it and then call action and any
391
                                // previously-pending actions (if any) with it.
392
                                token_ = ex_auth_dbus_data.value().first;
9✔
393
                                server_url_ = ex_auth_dbus_data.value().second;
9✔
394
                                AuthData auth_data {*server_url_, *token_};
18✔
395

396
                                // Post/schedule pending actions before running the given action
397
                                // because the action can actually add more actions or even
398
                                // expire the token, etc. So post actions stacked up before we
399
                                // got here with the current token and only then give action a
400
                                // chance to mess with things.
401
                                ExpectedAuthData ex_auth_data {std::move(auth_data)};
402
                                PostPendingActions(ex_auth_data);
9✔
403
                                action(ex_auth_data);
18✔
404
                        } else {
405
                                // No valid token, let's request fetching of a new one
406
                                RequestNewToken(action);
4✔
407
                        }
408
                });
33✔
409
        if (err != error::NoError) {
11✔
410
                // No token and failed to try to get one (should never happen).
411
                ExpectedAuthData ex_auth_data = expected::unexpected(err);
×
412
                PostPendingActions(ex_auth_data);
×
413
                action(ex_auth_data);
×
414
                return err;
×
415
        }
416
        // else record that token is already being fetched (by GetJwtToken()).
417
        token_fetch_in_progress_ = true;
11✔
418

419
        return error::NoError;
11✔
420
}
421

422
} // namespace auth
423
} // namespace api
424
} // 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