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

mendersoftware / mender / 969241802

16 Aug 2023 08:29AM UTC coverage: 79.019% (+0.2%) from 78.825%
969241802

push

gitlab-ci

oleorhagen
chore(crypto): Make the PrivateKey constructor public

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

5314 of 6725 relevant lines covered (79.02%)

200.29 hits per line

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

82.61
/mender-update/context/context.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 <mender-update/context.hpp>
16

17
#include <cctype>
18

19
#include <algorithm>
20
#include <regex>
21
#include <set>
22

23
#include <artifact/artifact.hpp>
24
#include <common/common.hpp>
25
#include <common/conf/paths.hpp>
26
#include <common/error.hpp>
27
#include <common/expected.hpp>
28
#include <common/io.hpp>
29
#include <common/json.hpp>
30
#include <common/key_value_database.hpp>
31
#include <common/log.hpp>
32
#include <common/path.hpp>
33

34
namespace mender {
35
namespace update {
36
namespace context {
37

38
using namespace std;
39
namespace artifact = mender::artifact;
40
namespace common = mender::common;
41
namespace conf = mender::common::conf;
42
namespace error = mender::common::error;
43
namespace expected = mender::common::expected;
44
namespace io = mender::common::io;
45
namespace json = mender::common::json;
46
namespace kv_db = mender::common::key_value_database;
47
namespace log = mender::common::log;
48
namespace path = mender::common::path;
49

50
const string MenderContext::broken_artifact_name_suffix {"_INCONSISTENT"};
51

52
const string MenderContext::artifact_name_key {"artifact-name"};
53
const string MenderContext::artifact_group_key {"artifact-group"};
54
const string MenderContext::artifact_provides_key {"artifact-provides"};
55
const string MenderContext::standalone_state_key {"standalone-state"};
56
const string MenderContext::state_data_key {"state"};
57
const string MenderContext::state_data_key_uncommitted {"state-uncommitted"};
58
const string MenderContext::update_control_maps {"update-control-maps"};
59
const string MenderContext::auth_token_name {"authtoken"};
60
const string MenderContext::auth_token_cache_invalidator_name {"auth-token-cache-invalidator"};
61

62
const int MenderContext::standalone_data_version {1};
63

64
const MenderContextErrorCategoryClass MenderContextErrorCategory;
65

66
const char *MenderContextErrorCategoryClass::name() const noexcept {
×
67
        return "MenderContextErrorCategory";
×
68
}
69

70
string MenderContextErrorCategoryClass::message(int code) const {
3✔
71
        switch (code) {
3✔
72
        case NoError:
×
73
                return "Success";
×
74
        case ParseError:
×
75
                return "Parse error";
×
76
        case ValueError:
×
77
                return "Value error";
×
78
        case NoSuchUpdateModuleError:
×
79
                return "Update Module not found for given artifact type";
×
80
        case DatabaseValueError:
×
81
                return "Value in database is invalid or corrupted";
×
82
        case RebootRequiredError:
×
83
                return "Reboot required";
×
84
        case NoUpdateInProgressError:
2✔
85
                return "No update in progress";
2✔
86
        case ExitStatusOnlyError:
×
87
                return "ExitStatusOnlyError";
×
88
        case UnexpectedHttpResponse:
×
89
                return "Unexpected HTTP response";
×
90
        case StateDataStoreCountExceededError:
1✔
91
                return "State data store count exceeded";
1✔
92
        }
93
        assert(false);
×
94
        return "Unknown";
95
}
96

97
error::Error MakeError(MenderContextErrorCode code, const string &msg) {
207✔
98
        return error::Error(error_condition(code, MenderContextErrorCategory), msg);
414✔
99
}
100

101
error::Error MenderContext::Initialize() {
182✔
102
#if MENDER_USE_LMDB
103
        auto err = mender_store_.Open(path::Join(config_.data_store_dir, "mender-store"));
364✔
104
        if (error::NoError != err) {
182✔
105
                return err;
×
106
        }
107
        err = mender_store_.Remove(auth_token_name);
182✔
108
        if (error::NoError != err) {
182✔
109
                // key not existing in the DB is not treated as an error so this must be
110
                // a real error
111
                return err;
×
112
        }
113
        err = mender_store_.Remove(auth_token_cache_invalidator_name);
182✔
114
        if (error::NoError != err) {
182✔
115
                // same as above -- a real error
116
                return err;
×
117
        }
118

119
        return error::NoError;
182✔
120
#else
121
        return error::NoError;
122
#endif
123
}
124

125
kv_db::KeyValueDatabase &MenderContext::GetMenderStoreDB() {
451✔
126
        return mender_store_;
451✔
127
}
128

129
ExpectedProvidesData MenderContext::LoadProvides() {
172✔
130
        ExpectedProvidesData data;
344✔
131
        auto err = mender_store_.ReadTransaction([this, &data](kv_db::Transaction &txn) {
518✔
132
                data = LoadProvides(txn);
172✔
133
                if (!data) {
172✔
134
                        return data.error();
2✔
135
                }
136
                return error::NoError;
170✔
137
        });
344✔
138
        if (err != error::NoError) {
172✔
139
                return expected::unexpected(err);
4✔
140
        }
141
        return data;
170✔
142
}
143

144
ExpectedProvidesData MenderContext::LoadProvides(kv_db::Transaction &txn) {
218✔
145
        string artifact_name;
436✔
146
        string artifact_group;
436✔
147
        string artifact_provides_str;
436✔
148

149
        auto err = kv_db::ReadString(txn, artifact_name_key, artifact_name, true);
436✔
150
        if (err != error::NoError) {
218✔
151
                return expected::unexpected(err);
×
152
        }
153
        err = kv_db::ReadString(txn, artifact_group_key, artifact_group, true);
218✔
154
        if (err != error::NoError) {
218✔
155
                return expected::unexpected(err);
×
156
        }
157
        err = kv_db::ReadString(txn, artifact_provides_key, artifact_provides_str, true);
218✔
158
        if (err != error::NoError) {
218✔
159
                return expected::unexpected(err);
×
160
        }
161

162
        ProvidesData ret {};
436✔
163
        if (artifact_name != "") {
218✔
164
                ret["artifact_name"] = artifact_name;
167✔
165
        }
166
        if (artifact_group != "") {
218✔
167
                ret["artifact_group"] = artifact_group;
10✔
168
        }
169
        if (artifact_provides_str == "") {
218✔
170
                // nothing more to do
171
                return ret;
154✔
172
        }
173

174
        auto ex_j = json::Load(artifact_provides_str);
128✔
175
        if (!ex_j) {
64✔
176
                return expected::unexpected(ex_j.error());
2✔
177
        }
178
        auto ex_children = ex_j.value().GetChildren();
126✔
179
        if (!ex_children) {
63✔
180
                return expected::unexpected(ex_children.error());
×
181
        }
182

183
        auto children = ex_children.value();
126✔
184
        if (!all_of(children.cbegin(), children.cend(), [](const json::ChildrenMap::value_type &it) {
63✔
185
                        return it.second.IsString();
105✔
186
                })) {
187
                auto err = json::MakeError(json::TypeError, "Unexpected non-string data in provides");
2✔
188
                return expected::unexpected(err);
2✔
189
        }
190
        for (const auto &it : ex_children.value()) {
166✔
191
                ret[it.first] = it.second.GetString().value();
104✔
192
        }
193

194
        return ret;
62✔
195
}
196

197
expected::ExpectedString MenderContext::GetDeviceType() {
103✔
198
        string device_type_fpath = path::Join(config_.data_store_dir, "device_type");
206✔
199
        auto ex_is = io::OpenIfstream(device_type_fpath);
206✔
200
        if (!ex_is) {
103✔
201
                return expected::ExpectedString(expected::unexpected(ex_is.error()));
4✔
202
        }
203

204
        auto &is = ex_is.value();
101✔
205
        string line;
202✔
206
        errno = 0;
101✔
207
        getline(is, line);
101✔
208
        if (is.bad()) {
101✔
209
                int io_errno = errno;
×
210
                error::Error err {
211
                        generic_category().default_error_condition(io_errno),
×
212
                        "Failed to read device type from '" + device_type_fpath + "'"};
×
213
                return expected::ExpectedString(expected::unexpected(err));
×
214
        }
215

216
        const string::size_type eq_pos = 12;
101✔
217
        if (line.substr(0, eq_pos) != "device_type=") {
101✔
218
                auto err = MakeError(ParseError, "Failed to parse device_type data '" + line + "'");
6✔
219
                return expected::ExpectedString(expected::unexpected(err));
6✔
220
        }
221

222
        string ret = line.substr(eq_pos, string::npos);
196✔
223

224
        if (!is.eof()) {
98✔
225
                errno = 0;
97✔
226
                getline(is, line);
97✔
227
                if ((line != "") || (!is.eof())) {
97✔
228
                        auto err = MakeError(ValueError, "Trailing device_type data");
4✔
229
                        return expected::ExpectedString(expected::unexpected(err));
4✔
230
                }
231
        }
232

233
        return expected::ExpectedString(ret);
96✔
234
}
235

236
static error::Error FilterProvides(
40✔
237
        const ProvidesData &new_provides,
238
        const ClearsProvidesData &clears_provides,
239
        ProvidesData &to_modify) {
240
        // Use clears_provides to filter out unwanted provides.
241
        for (auto to_clear : clears_provides) {
127✔
242
                string escaped;
87✔
243
                // Potential to escape every character, though unlikely.
244
                escaped.reserve(to_clear.size() * 2);
87✔
245
                // Notable exception: '*', since it has special handling as a glob character.
246
                string meta_characters {".^$+()[]{}|?"};
87✔
247
                for (const auto chr : to_clear) {
1,639✔
248
                        if (chr == '*') {
1,552✔
249
                                // Turn every '*' glob wildcard into '.*' regex wildcard.
250
                                escaped.push_back('.');
39✔
251
                        } else if (any_of(meta_characters.begin(), meta_characters.end(), [chr](char c) {
1,513✔
252
                                                   return chr == c;
17,606✔
253
                                           })) {
254
                                // Escape every regex special character except '*'.
255
                                escaped.push_back('\\');
50✔
256
                        }
257
                        escaped.push_back(chr);
1,552✔
258
                }
259

260
                regex compiled;
87✔
261
                auto err = error::ExceptionToErrorOrAbort(
262
                        [&compiled, &escaped]() { compiled.assign(escaped, regex_constants::basic); });
174✔
263
                // Should not be possible, since the whole regex is escaped.
264
                AssertOrReturnError(err == error::NoError);
87✔
265

266
                set<string> keys;
174✔
267
                for (auto provide : to_modify) {
155✔
268
                        if (regex_match(provide.first.begin(), provide.first.end(), compiled)) {
68✔
269
                                keys.insert(provide.first);
12✔
270
                        }
271
                }
272
                for (auto key : keys) {
99✔
273
                        to_modify.erase(key);
12✔
274
                }
275
        }
276

277
        // Now add the provides from the new_provides set.
278
        for (auto provide : new_provides) {
103✔
279
                to_modify[provide.first] = provide.second;
63✔
280
        }
281

282
        return error::NoError;
40✔
283
}
284

285
error::Error MenderContext::CommitArtifactData(
46✔
286
        string artifact_name,
287
        string artifact_group,
288
        const optional::optional<ProvidesData> &new_provides,
289
        const optional::optional<ClearsProvidesData> &clears_provides,
290
        function<error::Error(kv_db::Transaction &)> txn_func) {
291
        return mender_store_.WriteTransaction([&](kv_db::Transaction &txn) {
46✔
292
                auto exp_existing = LoadProvides(txn);
92✔
293
                if (!exp_existing) {
46✔
294
                        return exp_existing.error();
×
295
                }
296
                auto modified_provides = exp_existing.value();
92✔
297

298
                error::Error err;
92✔
299
                if (!new_provides && !clears_provides) {
46✔
300
                        // Neither provides nor clear_provides came with the artifact. This means
301
                        // erase everything. `artifact_name` and `artifact_group` will still be
302
                        // preserved through special cases below.
303
                        modified_provides.clear();
3✔
304
                } else if (!new_provides) {
43✔
305
                        // No new provides came with the artifact. This means filter what we have,
306
                        // but don't add any new provides fields.
307
                        ProvidesData empty_provides;
2✔
308
                        err = FilterProvides(empty_provides, clears_provides.value(), modified_provides);
2✔
309
                } else if (!clears_provides) {
41✔
310
                        // Missing clears_provides is equivalent to `["*"]`, for historical reasons.
311
                        modified_provides = new_provides.value();
3✔
312
                } else {
313
                        // Standard case, filter existing provides using clears_provides, and then
314
                        // add new ones on top.
315
                        err = FilterProvides(new_provides.value(), clears_provides.value(), modified_provides);
38✔
316
                }
317
                if (err != error::NoError) {
46✔
318
                        return err;
×
319
                }
320

321
                if (artifact_name != "") {
46✔
322
                        modified_provides["artifact_name"] = artifact_name;
46✔
323
                }
324
                if (artifact_group != "") {
46✔
325
                        modified_provides["artifact_group"] = artifact_group;
4✔
326
                }
327

328
                string artifact_provides_str {"{"};
92✔
329
                for (const auto &it : modified_provides) {
165✔
330
                        if (it.first != "artifact_name" && it.first != "artifact_group") {
119✔
331
                                artifact_provides_str +=
332
                                        "\"" + it.first + "\":" + "\"" + json::EscapeString(it.second) + "\",";
68✔
333
                        }
334
                }
335

336
                // if some key-value pairs were added, replace the trailing comma with the
337
                // closing '}' to make a valid JSON
338
                if (artifact_provides_str != "{") {
46✔
339
                        artifact_provides_str[artifact_provides_str.length() - 1] = '}';
42✔
340
                } else {
341
                        // set to an empty value for consistency with the other two items
342
                        artifact_provides_str = "";
4✔
343
                }
344

345
                if (modified_provides["artifact_name"] != "") {
46✔
346
                        err = txn.Write(
46✔
347
                                artifact_name_key,
348
                                common::ByteVectorFromString(modified_provides["artifact_name"]));
92✔
349
                        if (err != error::NoError) {
46✔
350
                                return err;
×
351
                        }
352
                } else {
353
                        // This should not happen.
354
                        AssertOrReturnError(false);
×
355
                }
356

357
                if (modified_provides["artifact_group"] != "") {
46✔
358
                        err = txn.Write(
5✔
359
                                artifact_group_key,
360
                                common::ByteVectorFromString(modified_provides["artifact_group"]));
10✔
361
                } else {
362
                        err = txn.Remove(artifact_group_key);
41✔
363
                }
364
                if (err != error::NoError) {
46✔
365
                        return err;
×
366
                }
367

368
                if (artifact_provides_str != "") {
46✔
369
                        err = txn.Write(
42✔
370
                                artifact_provides_key, common::ByteVectorFromString(artifact_provides_str));
84✔
371
                        if (err != error::NoError) {
42✔
372
                                return err;
×
373
                        }
374
                }
375
                return txn_func(txn);
46✔
376
        });
46✔
377
}
378

379
expected::ExpectedBool MenderContext::MatchesArtifactDepends(const artifact::HeaderView &hdr_view) {
27✔
380
        auto ex_dev_type = GetDeviceType();
54✔
381
        if (!ex_dev_type) {
27✔
382
                return expected::unexpected(ex_dev_type.error());
×
383
        }
384
        auto ex_provides = LoadProvides();
54✔
385
        if (!ex_provides) {
27✔
386
                return expected::unexpected(ex_provides.error());
×
387
        }
388
        auto &provides = ex_provides.value();
27✔
389
        return ArtifactMatchesContext(
390
                provides, ex_dev_type.value(), hdr_view.header_info, hdr_view.type_info);
27✔
391
}
392

393
expected::ExpectedBool ArtifactMatchesContext(
39✔
394
        const ProvidesData &provides,
395
        const string &device_type,
396
        const artifact::HeaderInfo &hdr_info,
397
        const artifact::TypeInfo &type_info) {
398
        if (!common::MapContainsStringKey(provides, "artifact_name")) {
39✔
399
                return expected::unexpected(
×
400
                        MakeError(ValueError, "Missing artifact_name value in provides"));
×
401
        }
402

403
        const auto &hdr_depends = hdr_info.depends;
39✔
404
        AssertOrReturnUnexpected(hdr_depends.device_type.size() > 0);
39✔
405
        if (!common::VectorContainsString(hdr_depends.device_type, device_type)) {
39✔
406
                log::Debug("Artifact device type doesn't match");
2✔
407
                return false;
2✔
408
        }
409

410
        if (hdr_depends.artifact_name) {
37✔
411
                AssertOrReturnUnexpected(hdr_depends.artifact_name->size() > 0);
9✔
412
                if (!common::VectorContainsString(
9✔
413
                                *hdr_depends.artifact_name, provides.at("artifact_name"))) {
9✔
414
                        log::Debug("Artifact name doesn't match");
1✔
415
                        return false;
1✔
416
                }
417
        }
418

419
        if (hdr_depends.artifact_group) {
36✔
420
                AssertOrReturnUnexpected(hdr_depends.artifact_group->size() > 0);
8✔
421
                if (!common::MapContainsStringKey(provides, "artifact_group")) {
8✔
422
                        log::Debug(
1✔
423
                                "Missing artifact_group value in provides, required by artifact header info depends");
2✔
424
                        return false;
1✔
425
                }
426
                if (!common::VectorContainsString(
7✔
427
                                *hdr_depends.artifact_group, provides.at("artifact_group"))) {
7✔
428
                        log::Debug("Artifact group doesn't match");
1✔
429
                        return false;
1✔
430
                }
431
        }
432

433
        const auto &ti_depends = type_info.artifact_depends;
34✔
434
        if (!ti_depends) {
34✔
435
                // nothing more to check
436
                return true;
31✔
437
        }
438
        for (auto it : *ti_depends) {
4✔
439
                if (!common::MapContainsStringKey(provides, it.first)) {
3✔
440
                        log::Debug(
1✔
441
                                "Missing '" + it.first + "' in provides, required by artifact type info depends");
2✔
442
                        return false;
1✔
443
                }
444
                if (provides.at(it.first) != it.second) {
2✔
445
                        log::Debug(
1✔
446
                                "'" + it.first + "' artifact type info depends value '" + it.second
2✔
447
                                + "' doesn't match provides value '" + provides.at(it.first) + "'");
3✔
448
                        return false;
1✔
449
                }
450
        }
451

452
        return true;
1✔
453
}
454

455
} // namespace context
456
} // namespace update
457
} // 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