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