1 /*
2   Copyright 2021 Northern.tech AS
3 
4   This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6   This program is free software; you can redistribute it and/or modify it
7   under the terms of the GNU General Public License as published by the
8   Free Software Foundation; version 3.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
18 
19   To the extent this program is licensed as part of the Enterprise
20   versions of CFEngine, the applicable Commercial Open Source License
21   (COSL) may apply to this file if you as a licensee so wish it. See
22   included file COSL.txt.
23 */
24 
25 #include <bootstrap.h>
26 
27 #include <policy_server.h>
28 #include <eval_context.h>
29 #include <files_names.h>
30 #include <scope.h>
31 #include <files_interfaces.h>
32 #include <exec_tools.h>
33 #include <generic_agent.h> // PrintVersionBanner
34 #include <audit.h>
35 #include <logging.h>
36 #include <string_lib.h>
37 #include <files_lib.h>
38 #include <known_dirs.h>
39 #include <addr_lib.h>
40 #include <communication.h>
41 #include <client_code.h>
42 #include <assert.h>
43 #include <crypto.h>
44 #include <openssl/rand.h>
45 #include <encode.h>
46 
47 /*
48 Bootstrapping is a tricky sequence of fragile events. We need to map shakey/IP
49 and identify policy hub IP in a special order to bootstrap the license and agents.
50 
51 During commercial bootstrap:
52 
53  - InitGA (generic-agent) loads the public key
54  - The verifylicense function sets the policy hub but fails to verify license yet
55    as there is no key/IP binding
56  - Policy server gets set in workdir/state/am_policy_hub
57  - The agents gets run and start this all over again, but this time
58    the am_policy_hub is defined and caches the key/IP binding
59  - Now the license has a binding, resolves the policy hub's key and succeeds
60 
61 */
62 
63 #if defined(__CYGWIN__) || defined(__ANDROID__)
64 
BootstrapAllowed(void)65 bool BootstrapAllowed(void)
66 {
67     return true;
68 }
69 
70 #elif !defined(__MINGW32__)
71 
BootstrapAllowed(void)72 bool BootstrapAllowed(void)
73 {
74     return IsPrivileged();
75 }
76 
77 #endif
78 
79 
80 /**
81  * @brief Sets both internal C variables as well as policy sys variables.
82  *
83  * Called at bootstrap and after reading policy_server.dat.
84  * Changes sys.policy_hub and sys.policy_hub_port.
85  * NULL is an acceptable value for new_policy_server. Could happen when an
86  * already bootstrapped server re-parses its policies, and the
87  * policy_server.dat file has been removed. Then this function will be called
88  * with NULL as new_policy_server, and cf-serverd will keep running even
89  * without a policy server set.
90  *
91  * @param ctx EvalContext is used to set related variables
92  * @param new_policy_server can be 'host:port', same as policy_server.dat
93  */
EvalContextSetPolicyServer(EvalContext * ctx,const char * new_policy_server)94 void EvalContextSetPolicyServer(EvalContext *ctx, const char *new_policy_server)
95 {
96     // Remove variables if undefined policy server:
97     if ( NULL_OR_EMPTY(new_policy_server) )
98     {
99         EvalContextVariableRemoveSpecial(   ctx, SPECIAL_SCOPE_SYS,
100                                             "policy_hub" );
101         EvalContextVariableRemoveSpecial(   ctx, SPECIAL_SCOPE_SYS,
102                                             "policy_hub_port" );
103         return;
104     }
105 
106     PolicyServerSet(new_policy_server);
107     const char *ip = PolicyServerGetIP();
108 
109     // Set the sys.policy_hub variable:
110     if ( ip != NULL )
111     {
112         EvalContextVariablePutSpecial(  ctx,  SPECIAL_SCOPE_SYS,
113                                         "policy_hub", ip,
114                                         CF_DATA_TYPE_STRING,
115                                         "source=bootstrap" );
116     }
117     else
118     {
119         EvalContextVariableRemoveSpecial(   ctx, SPECIAL_SCOPE_SYS,
120                                             "policy_hub" );
121     }
122 
123     // Set the sys.policy_hub_port variable:
124     if (PolicyServerGetPort() != NULL)
125     {
126         EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS,
127                                       "policy_hub_port", PolicyServerGetPort(),
128                                       CF_DATA_TYPE_STRING,
129                                       "source=bootstrap" );
130     }
131     else // Default value (CFENGINE_PORT_STR = "5308") is set
132     {
133         EvalContextVariablePutSpecial( ctx, SPECIAL_SCOPE_SYS,
134                                        "policy_hub_port",
135                                        CFENGINE_PORT_STR,
136                                        CF_DATA_TYPE_STRING,
137                                        "source=bootstrap" );
138     }
139 }
140 
141 //*******************************************************************
142 // POLICY SERVER FILE FUNCTIONS:
143 //*******************************************************************
144 
145 /**
146  * @brief     Reads the policy_server.dat and sets the internal variables.
147  * @param[in] ctx EvalContext used by EvalContextSetPolicyServer()
148  * @param[in] workdir the directory of policy_server.dat usually GetWorkDir()
149  */
EvalContextSetPolicyServerFromFile(EvalContext * ctx,const char * workdir)150  void EvalContextSetPolicyServerFromFile(EvalContext *ctx, const char *workdir)
151  {
152      char *contents = PolicyServerReadFile(workdir);
153      EvalContextSetPolicyServer(ctx, contents);
154      free(contents);
155  }
156 
157 
158 
159 //*******************************************************************
160 // POLICY HUB FUNCTIONS:
161 //*******************************************************************
162 
163 /**
164  * @brief Updates sys.last_policy_update variable from $(sys.masterdir)/cf_promises_validated
165  * @param ctx EvalContext to put variable into
166  */
UpdateLastPolicyUpdateTime(EvalContext * ctx)167 void UpdateLastPolicyUpdateTime(EvalContext *ctx)
168 {
169     // Get the timestamp on policy update
170     struct stat sb;
171     {
172         char cf_promises_validated_filename[CF_MAXVARSIZE];
173         snprintf(cf_promises_validated_filename, CF_MAXVARSIZE, "%s/cf_promises_validated", GetMasterDir());
174         MapName(cf_promises_validated_filename);
175 
176         if ((stat(cf_promises_validated_filename, &sb)) != 0)
177         {
178             return;
179         }
180     }
181 
182     char timebuf[26] = { 0 };
183     cf_strtimestamp_local(sb.st_mtime, timebuf);
184 
185     EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "last_policy_update", timebuf, CF_DATA_TYPE_STRING, "source=agent");
186 }
187 
188 /**
189  * @return True if the file STATEDIR/am_policy_hub exists
190  */
GetAmPolicyHub(void)191  bool GetAmPolicyHub(void)
192  {
193      char path[CF_BUFSIZE] = { 0 };
194      snprintf(path, sizeof(path), "%s/am_policy_hub", GetStateDir());
195      MapName(path);
196 
197      struct stat sb;
198      return stat(path, &sb) == 0;
199  }
200 
201 /**
202  * @brief Set the STATEDIR/am_policy_hub marker file.
203  * @param am_policy_hub If true, create marker file. If false, delete it.
204  * @return True if successful
205  */
AmPolicyHubFilename(void)206 static char *AmPolicyHubFilename(void)
207 {
208     return StringFormat("%s%cam_policy_hub", GetStateDir(), FILE_SEPARATOR);
209 }
210 
WriteAmPolicyHubFile(bool am_policy_hub)211 bool WriteAmPolicyHubFile(bool am_policy_hub)
212 {
213     char *filename = AmPolicyHubFilename();
214     if (am_policy_hub)
215     {
216         if (!GetAmPolicyHub())
217         {
218             if (creat(filename, 0600) == -1)
219             {
220                 Log(LOG_LEVEL_ERR, "Error writing marker file '%s'", filename);
221                 free(filename);
222                 return false;
223             }
224         }
225     }
226     else
227     {
228         if (GetAmPolicyHub())
229         {
230             if (unlink(filename) != 0)
231             {
232                 Log(LOG_LEVEL_ERR, "Error removing marker file '%s'", filename);
233                 free(filename);
234                 return false;
235             }
236         }
237     }
238     free(filename);
239     return true;
240 }
241 
242 
243 //*******************************************************************
244 // FAILSAFE FUNCTIONS:
245 //*******************************************************************
246 
247 /**
248  * @brief Write the builtin failsafe policy to the default location
249  * @return True if successful
250  */
WriteBuiltinFailsafePolicy(const char * inputdir)251 bool WriteBuiltinFailsafePolicy(const char *inputdir)
252 {
253     char failsafe_path[CF_BUFSIZE];
254     snprintf(failsafe_path, CF_BUFSIZE - 1, "%s/failsafe.cf", inputdir);
255     MapName(failsafe_path);
256 
257     return WriteBuiltinFailsafePolicyToPath(failsafe_path);
258 }
259 
260 /**
261  * @brief Exposed for testing. Use WriteBuiltinFailsafePolicy.
262  */
WriteBuiltinFailsafePolicyToPath(const char * filename)263 bool WriteBuiltinFailsafePolicyToPath(const char *filename)
264 {
265     // The bootstrap.inc file is generated by "make bootstrap-inc"
266     const char *bootstrap_content =
267 #include "bootstrap.inc"
268 ;
269 
270     Log(LOG_LEVEL_INFO, "Writing built-in failsafe policy to '%s'", filename);
271 
272     FILE *fout = safe_fopen(filename, "w");
273     if (!fout)
274     {
275         Log(LOG_LEVEL_ERR, "Unable to write failsafe to '%s' (fopen: %s)", filename, GetErrorStr());
276         return false;
277     }
278 
279     fputs(bootstrap_content, fout);
280     fclose(fout);
281 
282     return true;
283 }
284 
285 
286 //*******************************************************************
287 // POLICY FILE FUNCTIONS:
288 //*******************************************************************
289 
290 /**
291  * @brief Removes all files in $(sys.inputdir)
292  * @param inputdir
293  * @return True if successful
294  */
RemoveAllExistingPolicyInInputs(const char * inputs_path)295 bool RemoveAllExistingPolicyInInputs(const char *inputs_path)
296 {
297     Log(LOG_LEVEL_INFO, "Removing all files in '%s'", inputs_path);
298 
299     struct stat sb;
300     if (stat(inputs_path, &sb) == -1)
301     {
302         if (errno == ENOENT)
303         {
304             return true;
305         }
306         else
307         {
308             Log(LOG_LEVEL_ERR, "Could not stat inputs directory at '%s'. (stat: %s)", inputs_path, GetErrorStr());
309             return false;
310         }
311     }
312 
313     if (!S_ISDIR(sb.st_mode))
314     {
315         Log(LOG_LEVEL_ERR, "Inputs path exists at '%s', but it is not a directory", inputs_path);
316         return false;
317     }
318 
319     return DeleteDirectoryTree(inputs_path);
320 }
321 
322 /**
323  * @return True if the file $(sys.masterdir)/promises.cf exists
324  */
MasterfileExists(const char * masterdir)325 bool MasterfileExists(const char *masterdir)
326 {
327     char filename[CF_BUFSIZE] = { 0 };
328     snprintf(filename, sizeof(filename), "%s/promises.cf", masterdir);
329     MapName(filename);
330 
331     struct stat sb;
332     if (stat(filename, &sb) == -1)
333     {
334         if (errno == ENOENT)
335         {
336             return false;
337         }
338         else
339         {
340             Log(LOG_LEVEL_ERR, "Could not stat file '%s'. (stat: %s)", filename, GetErrorStr());
341             return false;
342         }
343     }
344 
345     if (!S_ISREG(sb.st_mode))
346     {
347         Log(LOG_LEVEL_ERR, "Path exists at '%s', but it is not a regular file", filename);
348         return false;
349     }
350 
351     return true;
352 }
353 
BootstrapIDFilename(const char * workdir)354 static char *BootstrapIDFilename(const char *workdir)
355 {
356     assert(workdir != NULL);
357     return StringFormat("%s%cbootstrap_id.dat", workdir, FILE_SEPARATOR);
358 }
359 
CreateBootstrapIDFile(const char * workdir)360 char *CreateBootstrapIDFile(const char *workdir)
361 {
362     assert(workdir != NULL);
363     char *filename = BootstrapIDFilename(workdir);
364 
365     FILE *file = safe_fopen_create_perms(filename, "w", CF_PERMS_DEFAULT);
366     if (file == NULL)
367     {
368         Log(LOG_LEVEL_ERR, "Unable to write bootstrap id file '%s' (fopen: %s)", filename, GetErrorStr());
369         free(filename);
370         return NULL;
371     }
372     CryptoInitialize();
373     #define RANDOM_BYTES 240 / 8 // 240 avoids padding (divisible by 6)
374     #define BASE_64_LENGTH_NO_PADDING (4 * (RANDOM_BYTES / 3))
375     unsigned char buf[RANDOM_BYTES];
376     RAND_bytes(buf, RANDOM_BYTES);
377     char *b64_id = StringEncodeBase64(buf, RANDOM_BYTES);
378     fprintf(file, "%s\n", b64_id);
379     fclose(file);
380 
381     free(filename);
382     return b64_id;
383 }
384 
ReadBootstrapIDFile(const char * workdir)385 char *ReadBootstrapIDFile(const char *workdir)
386 {
387     assert(workdir != NULL);
388 
389     char *const path = BootstrapIDFilename(workdir);
390     Writer *writer = FileRead(path, BASE_64_LENGTH_NO_PADDING + 1, NULL);
391     if (writer == NULL)
392     {
393         // Not having a bootstrap id file is considered normal
394         Log(LOG_LEVEL_DEBUG,
395             "Could not read bootstrap ID from file: '%s'",
396             path);
397         free(path);
398         return NULL;
399     }
400     char *data = StringWriterClose(writer);
401 
402     size_t data_length = strlen(data);
403     assert(data_length == BASE_64_LENGTH_NO_PADDING + 1);
404     assert(data[data_length - 1] == '\n');
405     if (data_length != BASE_64_LENGTH_NO_PADDING + 1)
406     {
407         Log(LOG_LEVEL_ERR, "'%s' contains invalid data: '%s'", path, data);
408         free(path);
409         free(data);
410         return NULL;
411     }
412 
413     data[data_length - 1] = '\0';
414 
415     Log(LOG_LEVEL_VERBOSE,
416         "Successfully read bootstrap ID '%s' from file '%s'",
417         data,
418         path);
419     free(path);
420     return data;
421 }
422 
EvalContextSetBootstrapID(EvalContext * ctx,char * bootstrap_id)423 void EvalContextSetBootstrapID(EvalContext *ctx, char *bootstrap_id)
424 {
425     EvalContextVariablePutSpecial(
426         ctx,
427         SPECIAL_SCOPE_SYS,
428         "bootstrap_id",
429         bootstrap_id,
430         CF_DATA_TYPE_STRING,
431         "source=bootstrap");
432 }
433