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