1 // Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7 #include <config.h>
8
9 #include <cc/command_interpreter.h>
10 #include <exceptions/exceptions.h>
11 #include <process/testutils/d_test_stubs.h>
12 #include <process/d_cfg_mgr.h>
13 #include <process/redact_config.h>
14
15 #include <boost/foreach.hpp>
16 #include <boost/date_time/posix_time/posix_time.hpp>
17 #include <gtest/gtest.h>
18
19 #include <sstream>
20
21 using namespace std;
22 using namespace isc;
23 using namespace isc::config;
24 using namespace isc::process;
25 using namespace isc::data;
26 using namespace boost::posix_time;
27
28 namespace {
29
30 /// @brief Test Class for verifying that configuration context cannot be null
31 /// during construction.
32 class DCtorTestCfgMgr : public DCfgMgrBase {
33 public:
34 /// @brief Constructor - Note that is passes in an empty configuration
35 /// pointer to the base class constructor.
DCtorTestCfgMgr()36 DCtorTestCfgMgr() : DCfgMgrBase(ConfigPtr()) {
37 }
38
39 /// @brief Destructor
~DCtorTestCfgMgr()40 virtual ~DCtorTestCfgMgr() {
41 }
42
43 /// @brief Dummy implementation as this method is abstract.
createNewContext()44 virtual ConfigPtr createNewContext() {
45 return (ConfigPtr());
46 }
47
48 /// @brief Returns summary of configuration in the textual format.
getConfigSummary(const uint32_t)49 virtual std::string getConfigSummary(const uint32_t) {
50 return ("");
51 }
52 };
53
54 /// @brief Test fixture class for testing DCfgMgrBase class.
55 /// It maintains an member instance of DStubCfgMgr and derives from
56 /// ConfigParseTest fixture, thus providing methods for converting JSON
57 /// strings to configuration element sets, checking parse results,
58 /// accessing the configuration context and trying to unparse.
59 class DStubCfgMgrTest : public ConfigParseTest {
60 public:
61
62 /// @brief Constructor
DStubCfgMgrTest()63 DStubCfgMgrTest():cfg_mgr_(new DStubCfgMgr) {
64 }
65
66 /// @brief Destructor
~DStubCfgMgrTest()67 ~DStubCfgMgrTest() {
68 }
69
70 /// @brief Convenience method which returns a DStubContextPtr to the
71 /// configuration context.
72 ///
73 /// @return returns a DStubContextPtr.
getStubContext()74 DStubContextPtr getStubContext() {
75 return (boost::dynamic_pointer_cast<DStubContext>
76 (cfg_mgr_->getContext()));
77 }
78
79 /// @brief Configuration manager instance.
80 DStubCfgMgrPtr cfg_mgr_;
81 };
82
83 ///@brief Tests basic construction/destruction of configuration manager.
84 /// Verifies that:
85 /// 1. Proper construction succeeds.
86 /// 2. Configuration context is initialized by construction.
87 /// 3. Destruction works properly.
88 /// 4. Construction with a null context is not allowed.
TEST(DCfgMgrBase,construction)89 TEST(DCfgMgrBase, construction) {
90 DCfgMgrBasePtr cfg_mgr;
91
92 // Verify that configuration manager constructions without error.
93 ASSERT_NO_THROW(cfg_mgr.reset(new DStubCfgMgr()));
94
95 // Verify that the context can be retrieved and is not null.
96 ConfigPtr context = cfg_mgr->getContext();
97 EXPECT_TRUE(context);
98
99 // Verify that the manager can be destructed without error.
100 EXPECT_NO_THROW(cfg_mgr.reset());
101
102 // Verify that an attempt to construct a manger with a null context fails.
103 ASSERT_THROW(DCtorTestCfgMgr(), DCfgMgrBaseError);
104 }
105
106 ///@brief Tests fundamental aspects of configuration parsing.
107 /// Verifies that:
108 /// 1. A correctly formed simple configuration parses without error.
109 /// 2. An error building the element is handled.
110 /// 3. An error committing the element is handled.
111 /// 4. An unknown element error is handled.
TEST_F(DStubCfgMgrTest,basicParseTest)112 TEST_F(DStubCfgMgrTest, basicParseTest) {
113 // Create a simple configuration.
114 string config = "{ \"test-value\": [] } ";
115 ASSERT_TRUE(fromJSON(config));
116
117 // Verify that we can parse a simple configuration.
118 answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
119 EXPECT_TRUE(checkAnswer(0));
120
121 // Verify that we can check a simple configuration.
122 answer_ = cfg_mgr_->simpleParseConfig(config_set_, true);
123 EXPECT_TRUE(checkAnswer(0));
124 }
125
126 /// @brief Tests that element ids supported by the base class as well as those
127 /// added by the derived class function properly.
128 /// This test verifies that:
129 /// 1. Boolean parameters can be parsed and retrieved.
130 /// 2. Uint32 parameters can be parsed and retrieved.
131 /// 3. String parameters can be parsed and retrieved.
132 /// 4. Map elements can be parsed and retrieved.
133 /// 5. List elements can be parsed and retrieved.
134 /// 6. Parsing a second configuration, updates the existing context values
135 /// correctly.
TEST_F(DStubCfgMgrTest,simpleTypesTest)136 TEST_F(DStubCfgMgrTest, simpleTypesTest) {
137 // Create a configuration with all of the parameters.
138 string config = "{ \"bool_test\": true , "
139 " \"uint32_test\": 77 , "
140 " \"string_test\": \"hmmm chewy\" , "
141 " \"map_test\" : {} , "
142 " \"list_test\": [] }";
143 ASSERT_TRUE(fromJSON(config));
144
145 // Verify that the configuration parses without error.
146 answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
147 ASSERT_TRUE(checkAnswer(0));
148 DStubContextPtr context = getStubContext();
149 ASSERT_TRUE(context);
150
151 // Create a configuration which "updates" all of the parameter values.
152 string config2 = "{ \"bool_test\": false , "
153 " \"uint32_test\": 88 , "
154 " \"string_test\": \"ewww yuk!\" , "
155 " \"map_test2\" : {} , "
156 " \"list_test2\": [] }";
157 ASSERT_TRUE(fromJSON(config2));
158
159 // Verify that the configuration parses without error.
160 answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
161 EXPECT_TRUE(checkAnswer(0));
162 context = getStubContext();
163 ASSERT_TRUE(context);
164 }
165
166 /// @brief Tests that the configuration context is preserved after failure
167 /// during parsing causes a rollback.
168 /// 1. Verifies configuration context rollback.
TEST_F(DStubCfgMgrTest,rollBackTest)169 TEST_F(DStubCfgMgrTest, rollBackTest) {
170 // Create a configuration with all of the parameters.
171 string config = "{ \"bool_test\": true , "
172 " \"uint32_test\": 77 , "
173 " \"string_test\": \"hmmm chewy\" , "
174 " \"map_test\" : {} , "
175 " \"list_test\": [] }";
176 ASSERT_TRUE(fromJSON(config));
177
178 // Verify that the configuration parses without error.
179 answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
180 EXPECT_TRUE(checkAnswer(0));
181 DStubContextPtr context = getStubContext();
182 ASSERT_TRUE(context);
183
184 // Create a configuration which "updates" all of the parameter values
185 // plus one unknown at the end.
186 string config2 = "{ \"bool_test\": false , "
187 " \"uint32_test\": 88 , "
188 " \"string_test\": \"ewww yuk!\" , "
189 " \"map_test2\" : {} , "
190 " \"list_test2\": [] , "
191 " \"zeta_unknown\": 33 } ";
192 ASSERT_TRUE(fromJSON(config2));
193 }
194
195 /// @brief Tests that the configuration context is preserved during
196 /// check only parsing.
TEST_F(DStubCfgMgrTest,checkOnly)197 TEST_F(DStubCfgMgrTest, checkOnly) {
198 // Create a configuration with all of the parameters.
199 string config = "{ \"bool_test\": true , "
200 " \"uint32_test\": 77 , "
201 " \"string_test\": \"hmmm chewy\" , "
202 " \"map_test\" : {} , "
203 " \"list_test\": [] }";
204 ASSERT_TRUE(fromJSON(config));
205
206 // Verify that the configuration parses without error.
207 answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
208 EXPECT_TRUE(checkAnswer(0));
209 DStubContextPtr context = getStubContext();
210 ASSERT_TRUE(context);
211
212
213 // Create a configuration which "updates" all of the parameter values.
214 string config2 = "{ \"bool_test\": false , "
215 " \"uint32_test\": 88 , "
216 " \"string_test\": \"ewww yuk!\" , "
217 " \"map_test2\" : {} , "
218 " \"list_test2\": [] }";
219 ASSERT_TRUE(fromJSON(config2));
220
221 answer_ = cfg_mgr_->simpleParseConfig(config_set_, true);
222 EXPECT_TRUE(checkAnswer(0));
223 context = getStubContext();
224 ASSERT_TRUE(context);
225
226 }
227
228 // Tests that configuration element position is returned by getParam variants.
TEST_F(DStubCfgMgrTest,paramPosition)229 TEST_F(DStubCfgMgrTest, paramPosition) {
230 // Create a configuration with one of each scalar types. We end them
231 // with line feeds so we can test position value.
232 string config = "{ \"bool_test\": true , \n"
233 " \"uint32_test\": 77 , \n"
234 " \"string_test\": \"hmmm chewy\" }";
235 ASSERT_TRUE(fromJSON(config));
236
237 // Verify that the configuration parses without error.
238 answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
239 ASSERT_TRUE(checkAnswer(0));
240 DStubContextPtr context = getStubContext();
241 ASSERT_TRUE(context);
242
243 }
244
245 // This tests if some aspects of simpleParseConfig are behaving properly.
246 // Thorough testing is only possible for specific implementations. This
247 // is done for control agent (see CtrlAgentControllerTest tests in
248 // src/bin/agent/tests/ctrl_agent_controller_unittest.cc for example).
249 // Also, shell tests in src/bin/agent/ctrl_agent_process_tests.sh test
250 // the whole CA process that uses simpleParseConfig. The alternative
251 // would be to implement whole parser that would set the context
252 // properly. The ROI for this is not worth the effort.
TEST_F(DStubCfgMgrTest,simpleParseConfig)253 TEST_F(DStubCfgMgrTest, simpleParseConfig) {
254 using namespace isc::data;
255
256 // Passing just null pointer should result in error return code
257 answer_ = cfg_mgr_->simpleParseConfig(ConstElementPtr(), false);
258 EXPECT_TRUE(checkAnswer(1));
259
260 // Ok, now try with a dummy, but valid json code
261 string config = "{ \"bool_test\": true , \n"
262 " \"uint32_test\": 77 , \n"
263 " \"string_test\": \"hmmm chewy\" }";
264 ASSERT_NO_THROW(fromJSON(config));
265
266 answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
267 EXPECT_TRUE(checkAnswer(0));
268 }
269
270 // This test checks that the post configuration callback function is
271 // executed by the simpleParseConfig function.
TEST_F(DStubCfgMgrTest,simpleParseConfigWithCallback)272 TEST_F(DStubCfgMgrTest, simpleParseConfigWithCallback) {
273 string config = "{ \"bool_test\": true , \n"
274 " \"uint32_test\": 77 , \n"
275 " \"string_test\": \"hmmm chewy\" }";
276 ASSERT_NO_THROW(fromJSON(config));
277
278 answer_ = cfg_mgr_->simpleParseConfig(config_set_, false,
279 []() {
280 isc_throw(Unexpected, "unexpected configuration error");
281 });
282 EXPECT_TRUE(checkAnswer(1));
283 }
284
285 // This test checks that redactConfig works as expected.
TEST_F(DStubCfgMgrTest,redactConfig)286 TEST_F(DStubCfgMgrTest, redactConfig) {
287 // Basic case.
288 string config = "{ \"foo\": 1 }";
289 ConstElementPtr elem;
290 ASSERT_NO_THROW(elem = Element::fromJSON(config));
291 ConstElementPtr ret;
292 ASSERT_NO_THROW(ret = redactConfig(elem));
293 EXPECT_EQ(ret->str(), elem->str());
294
295 // Verify redaction.
296 config = "{ \"password\": \"foo\", \"secret\": \"bar\" }";
297 ASSERT_NO_THROW(elem = Element::fromJSON(config));
298 ASSERT_NO_THROW(ret = redactConfig(elem));
299 string expected = "{ \"password\": \"*****\", \"secret\": \"*****\" }";
300 EXPECT_EQ(expected, ret->str());
301
302 // Verify that user context are skipped.
303 config = "{ \"user-context\": { \"password\": \"foo\" } }";
304 ASSERT_NO_THROW(elem = Element::fromJSON(config));
305 ASSERT_NO_THROW(ret = redactConfig(elem));
306 EXPECT_EQ(ret->str(), elem->str());
307
308 // Verify that only given subtrees are handled.
309 list<string> keys = { "foo" };
310 config = "{ \"foo\": { \"password\": \"foo\" }, ";
311 config += "\"next\": { \"secret\": \"bar\" } }";
312 ASSERT_NO_THROW(elem = Element::fromJSON(config));
313 ASSERT_NO_THROW(ret = redactConfig(elem, keys));
314 expected = "{ \"foo\": { \"password\": \"*****\" }, ";
315 expected += "\"next\": { \"secret\": \"bar\" } }";
316 EXPECT_EQ(expected, ret->str());
317 }
318
319 // Test that user context is not touched when configuration is redacted.
TEST(RedactConfig,userContext)320 TEST(RedactConfig, userContext) {
321 ConstElementPtr const config(Element::fromJSON(R"(
322 {
323 "some-database": {
324 "password": "sensitive",
325 "secret": "sensitive",
326 "user": "keatest",
327 "nested-map": {
328 "password": "sensitive",
329 "secret": "sensitive",
330 "user": "keatest"
331 }
332 },
333 "user-context": {
334 "password": "keatest",
335 "secret": "keatest",
336 "user": "keatest",
337 "nested-map": {
338 "password": "keatest",
339 "secret": "keatest",
340 "user": "keatest"
341 }
342 }
343 }
344 )"));
345 ConstElementPtr const expected(Element::fromJSON(R"(
346 {
347 "some-database": {
348 "password": "*****",
349 "secret": "*****",
350 "user": "keatest",
351 "nested-map": {
352 "password": "*****",
353 "secret": "*****",
354 "user": "keatest"
355 }
356 },
357 "user-context": {
358 "password": "keatest",
359 "secret": "keatest",
360 "user": "keatest",
361 "nested-map": {
362 "password": "keatest",
363 "secret": "keatest",
364 "user": "keatest"
365 }
366 }
367 }
368 )"));
369 ConstElementPtr redacted(redactConfig(config));
370 EXPECT_TRUE(isEquivalent(redacted, expected))
371 << "Actual:\n" << prettyPrint(redacted) << "\n"
372 "Expected:\n" << prettyPrint(expected);
373 }
374
375 } // end of anonymous namespace
376