GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/authz/authz_fetch.cc
Date: 2026-05-03 02:36:16
Exec Total Coverage
Lines: 275 336 81.8%
Branches: 225 457 49.2%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #include "authz_fetch.h"
6
7 #include <errno.h>
8 #include <signal.h>
9 #include <sys/wait.h>
10 #include <syslog.h>
11 #include <unistd.h>
12
13 #include <algorithm>
14 #include <cassert>
15 #include <cstring>
16 #include <vector>
17
18 #include "monitor.h"
19 #include "options.h"
20 #include "sanitizer.h"
21 #include "util/capabilities.h"
22 #include "util/concurrency.h"
23 #include "util/logging.h"
24 #include "util/platform.h"
25 #include "util/pointer.h"
26 #include "util/posix.h"
27 #include "util/smalloc.h"
28 #include "util/string.h"
29
30 using namespace std; // NOLINT
31
32
33 const int AuthzExternalFetcher::kMinTtl = 0;
34 const uint32_t AuthzExternalFetcher::kProtocolVersion = 1;
35
36
37 1329 AuthzExternalFetcher::AuthzExternalFetcher(const string &fqrn,
38 const string &progname,
39 const string &search_path,
40 1329 OptionsManager *options_manager)
41 1329 : fqrn_(fqrn)
42
1/2
✓ Branch 1 taken 1329 times.
✗ Branch 2 not taken.
1329 , progname_(progname)
43
1/2
✓ Branch 1 taken 1329 times.
✗ Branch 2 not taken.
1329 , search_path_(search_path)
44 1329 , fd_send_(-1)
45 1329 , fd_recv_(-1)
46 1329 , pid_(-1)
47 1329 , fail_state_(false)
48 1329 , options_manager_(options_manager)
49
1/2
✓ Branch 3 taken 1329 times.
✗ Branch 4 not taken.
1329 , next_start_(-1) {
50 1329 InitLock();
51 1329 }
52
53 15 AuthzExternalFetcher::AuthzExternalFetcher(const string &fqrn,
54 int fd_send,
55 15 int fd_recv)
56 15 : fqrn_(fqrn)
57 15 , fd_send_(fd_send)
58 15 , fd_recv_(fd_recv)
59 15 , pid_(-1)
60 15 , fail_state_(false)
61 15 , options_manager_(NULL)
62
1/2
✓ Branch 3 taken 15 times.
✗ Branch 4 not taken.
15 , next_start_(-1) {
63 15 InitLock();
64 15 }
65
66
67 5376 AuthzExternalFetcher::~AuthzExternalFetcher() {
68 2688 const int retval = pthread_mutex_destroy(&lock_);
69
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1344 times.
2688 assert(retval == 0);
70
71 // Allow helper to gracefully terminate
72
3/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1332 times.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
2688 if ((fd_send_ >= 0) && !fail_state_) {
73 24 LogCvmfs(kLogAuthz, kLogDebug, "shutting down authz helper");
74 72 Send(string("{\"cvmfs_authz_v1\":{") + "\"msgid\":"
75 96 + StringifyInt(kAuthzMsgQuit) + "," + "\"revision\":0}}");
76 }
77
78 2688 ReapHelper();
79 5376 }
80
81 1407 void AuthzExternalFetcher::ReapHelper() {
82 // If we are reaping the helper, we don't try to shut it down again.
83
84
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 1383 times.
1407 if (fd_send_ >= 0)
85 24 close(fd_send_);
86 1407 fd_send_ = -1;
87
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 1383 times.
1407 if (fd_recv_ >= 0)
88 24 close(fd_recv_);
89 1407 fd_recv_ = -1;
90
91
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1398 times.
1407 if (pid_ > 0) {
92 int retval;
93 9 const uint64_t now = platform_monotonic_time();
94 int statloc;
95 do {
96
1/2
✓ Branch 1 taken 34529640 times.
✗ Branch 2 not taken.
34529640 retval = waitpid(pid_, &statloc, WNOHANG);
97
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 34529637 times.
34529640 if (platform_monotonic_time() > (now + kChildTimeout)) {
98
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 LogCvmfs(kLogAuthz, kLogSyslogWarn | kLogDebug,
99 "authz helper %s unresponsive, killing", progname_.c_str());
100 3 retval = kill(pid_, SIGKILL);
101
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (retval == 0) {
102 // Pick up client return status
103
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 (void)waitpid(pid_, &statloc, 0);
104 } else {
105 // Process might have been terminated just before the kill() call
106 (void)waitpid(pid_, &statloc, WNOHANG);
107 }
108 3 break;
109 }
110
2/2
✓ Branch 0 taken 34529631 times.
✓ Branch 1 taken 6 times.
34529637 } while (retval == 0);
111 9 pid_ = -1;
112 }
113 1407 }
114
115
116 63 void AuthzExternalFetcher::EnterFailState() {
117 63 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
118 "authz helper %s enters fail state, no more authorization",
119 progname_.c_str());
120
121 63 ReapHelper();
122 63 next_start_ = platform_monotonic_time() + kChildTimeout;
123 63 fail_state_ = true;
124 63 }
125
126
127 /**
128 * Uses execve to start progname_. The started program has stdin and stdout
129 * connected to fd_send_ and fd_recv_ and the CVMFS_... environment variables
130 * set. Special care must be taken when we call fork here in an unknown state
131 * of the client. Therefore we can't use ManagedExec (we can't use malloc).
132 *
133 * A failed execve is not caught by this routine. It will be caught in the
134 * next step, when mother and child start talking.
135 */
136 12 void AuthzExternalFetcher::ExecHelper() {
137 int pipe_send[2];
138 int pipe_recv[2];
139
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 MakePipe(pipe_send);
140
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 MakePipe(pipe_recv);
141 12 char *argv0 = strdupa(progname_.c_str());
142 12 char *argv[] = {argv0, NULL};
143
144 12 const bool strip_prefix = true;
145 12 vector<string> authz_env = options_manager_->GetEnvironmentSubset(
146
2/4
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
24 "CVMFS_AUTHZ_", strip_prefix);
147 12 vector<char *> envp;
148
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
12 for (unsigned i = 0; i < authz_env.size(); ++i)
149 envp.push_back(strdupa(authz_env[i].c_str()));
150
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 envp.push_back(strdupa("CVMFS_AUTHZ_HELPER=yes"));
151
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 envp.push_back(NULL);
152
153 #ifdef __APPLE__
154 int max_fd = sysconf(_SC_OPEN_MAX);
155 assert(max_fd > 0);
156 #else
157 12 std::vector<int> open_fds;
158
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 DIR *dirp = opendir("/proc/self/fd");
159
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 assert(dirp);
160 platform_dirent64 *dirent;
161
3/4
✓ Branch 1 taken 420 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 408 times.
✓ Branch 4 taken 12 times.
420 while ((dirent = platform_readdir(dirp))) {
162
1/2
✓ Branch 2 taken 408 times.
✗ Branch 3 not taken.
408 const std::string name(dirent->d_name);
163 uint64_t name_uint64;
164 // Make sure the dir name is digits only (skips ".", ".." and similar).
165
3/4
✓ Branch 1 taken 408 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 24 times.
✓ Branch 4 taken 384 times.
408 if (!String2Uint64Parse(name, &name_uint64))
166 24 continue;
167
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 360 times.
384 if (name_uint64 < 2)
168 24 continue;
169
1/2
✓ Branch 1 taken 360 times.
✗ Branch 2 not taken.
360 open_fds.push_back(static_cast<int>(name_uint64));
170
2/2
✓ Branch 1 taken 360 times.
✓ Branch 2 taken 48 times.
408 }
171
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 closedir(dirp);
172 #endif
173
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 LogCvmfs(kLogAuthz, kLogDebug | kLogSyslog, "starting authz helper %s",
174 argv0);
175
176 12 const pid_t pid = fork();
177
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 12 times.
15 if (pid == 0) {
178 // Child process, close file descriptors and run the helper
179 3 int retval = dup2(pipe_send[0], 0);
180
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 assert(retval == 0);
181 3 retval = dup2(pipe_recv[1], 1);
182
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 assert(retval == 1);
183 #ifdef __APPLE__
184 for (int fd = 2; fd < max_fd; fd++)
185 close(fd);
186 #else
187
2/2
✓ Branch 1 taken 90 times.
✓ Branch 2 taken 3 times.
93 for (unsigned i = 0; i < open_fds.size(); ++i)
188
1/2
✓ Branch 2 taken 90 times.
✗ Branch 3 not taken.
90 close(open_fds[i]);
189 #endif
190
191
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 3 times.
42 for (size_t i = 0; i < sizeof(Watchdog::g_suppressed_signals) / sizeof(int);
192 i++) {
193 struct sigaction signal_handler;
194 39 signal_handler.sa_handler = SIG_DFL;
195 39 sigaction(Watchdog::g_suppressed_signals[i], &signal_handler, NULL);
196 }
197
198
4/8
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 3 times.
3 if ((getuid() != 0) && SetuidCapabilityPermitted()) {
199 // The helper expects traditional real uid root credentials
200 const uid_t uid = geteuid();
201 const gid_t gid = getegid();
202 if (!SwitchCredentials(0, 0, false) ||
203 !SwitchCredentials(uid, gid, true)) {
204 syslog(LOG_USER | LOG_ERR,
205 "failed to switch back to real uid 0 for authz helper");
206 _exit(1);
207 }
208 }
209
210 3 execve(argv0, argv, &envp[0]);
211 3 syslog(LOG_USER | LOG_ERR, "failed to start authz helper %s (%d)", argv0,
212
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
3 errno);
213 _exit(1);
214 }
215
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 assert(pid > 0);
216
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 close(pipe_send[0]);
217
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 close(pipe_recv[1]);
218
219 // Don't receive a signal if the helper terminates
220 12 signal(SIGPIPE, SIG_IGN);
221 12 pid_ = pid;
222 12 fd_send_ = pipe_send[1];
223 12 fd_recv_ = pipe_recv[0];
224 12 }
225
226
227 AuthzStatus AuthzExternalFetcher::CheckHelper(
228 const std::string &membership)
229 {
230 if ((fd_send_ < 0) && (membership != "")) {
231 if (progname_.empty())
232 progname_ = FindHelper(membership);
233 ExecHelper();
234 if (!Handshake())
235 return kAuthzNoHelper;
236 }
237 return kAuthzOk;
238 }
239
240
241 3 AuthzStatus AuthzExternalFetcher::Fetch(
242 const QueryInfo &query_info,
243 AuthzToken *authz_token,
244 unsigned *ttl)
245 {
246 3 *ttl = kDefaultTtl;
247
248 3 const MutexLockGuard lock_guard(lock_);
249
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (fail_state_) {
250 const uint64_t now = platform_monotonic_time();
251 if (now > next_start_) {
252 fail_state_ = false;
253 } else {
254 return kAuthzNoHelper;
255 }
256 }
257
258 bool retval;
259
260
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (fd_send_ < 0) {
261 return kAuthzNoHelper;
262 }
263
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 assert((fd_send_ >= 0) && (fd_recv_ >= 0));
264
265 3 string authz_schema;
266 3 string pure_membership;
267
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 StripAuthzSchema(query_info.membership, &authz_schema, &pure_membership);
268
2/4
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
6 string json_msg = string("{\"cvmfs_authz_v1\":{") + "\"msgid\":"
269
4/8
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
12 + StringifyInt(kAuthzMsgVerify) + "," + "\"revision\":0,"
270
4/8
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
12 + "\"uid\":" + StringifyInt(query_info.uid) + ","
271
4/8
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
12 + "\"gid\":" + StringifyInt(query_info.gid) + ","
272
4/8
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
12 + "\"pid\":" + StringifyInt(query_info.pid) + ","
273
4/8
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
9 + "\"membership\":\"" + Base64(pure_membership) + "\"}}";
274
4/8
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3 times.
✗ Branch 9 not taken.
3 retval = Send(json_msg) && Recv(&json_msg);
275
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!retval)
276 return kAuthzNoHelper;
277 3 AuthzExternalMsg binary_msg;
278
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 retval = ParseMsg(json_msg, kAuthzMsgPermit, &binary_msg);
279
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!retval)
280 return kAuthzNoHelper;
281
282 3 *ttl = binary_msg.permit.ttl;
283
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (binary_msg.permit.status == kAuthzOk) {
284 3 *authz_token = binary_msg.permit.token;
285 3 LogCvmfs(kLogAuthz, kLogDebug, "got token of type %d and size %u",
286
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 binary_msg.permit.token.type, binary_msg.permit.token.size);
287 }
288
289 3 return binary_msg.permit.status;
290 3 }
291
292
293 string AuthzExternalFetcher::FindHelper(const string &membership) {
294 string authz_schema;
295 string pure_membership;
296 StripAuthzSchema(membership, &authz_schema, &pure_membership);
297 const sanitizer::AuthzSchemaSanitizer sanitizer;
298 if (!sanitizer.IsValid(authz_schema)) {
299 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, "invalid authz schema: %s",
300 authz_schema.c_str());
301 return "";
302 }
303
304 string exe_path = search_path_ + "/cvmfs_" + authz_schema + "_helper";
305 if (!FileExists(exe_path)) {
306 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, "authz helper %s missing",
307 exe_path.c_str());
308 }
309 return exe_path;
310 }
311
312
313 /**
314 * Establish communication link with a forked authz helper.
315 */
316 6 bool AuthzExternalFetcher::Handshake() {
317
1/3
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
6 const string debug_log = GetLogDebugFile();
318 6 string json_debug_log;
319
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
6 if (debug_log != "")
320 json_debug_log = ",\"debug_log\":\"" + debug_log + "\"";
321
2/4
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
12 string json_msg = string("{") + "\"cvmfs_authz_v1\":{"
322
4/8
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
18 + "\"msgid\":" + StringifyInt(0) + "," + "\"revision\":0,"
323
5/10
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 6 times.
✗ Branch 14 not taken.
18 + "\"fqrn\":\"" + fqrn_ + "\"," + "\"syslog_facility\":"
324
4/8
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
24 + StringifyInt(GetLogSyslogFacility()) + ","
325
4/8
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
18 + "\"syslog_level\":" + StringifyInt(GetLogSyslogLevel())
326
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
12 + json_debug_log + "}}";
327
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 bool retval = Send(json_msg);
328
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (!retval)
329 3 return false;
330
331
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 retval = Recv(&json_msg);
332
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!retval)
333 return false;
334 3 AuthzExternalMsg binary_msg;
335
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 retval = ParseMsg(json_msg, kAuthzMsgReady, &binary_msg);
336
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!retval)
337 return false;
338
339 3 return true;
340 6 }
341
342
343 1344 void AuthzExternalFetcher::InitLock() {
344 1344 const int retval = pthread_mutex_init(&lock_, NULL);
345
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1344 times.
1344 assert(retval == 0);
346 1344 }
347
348
349 30 bool AuthzExternalFetcher::Send(const string &msg) {
350 // Line format: 4 byte protocol version, 4 byte length, message
351 struct {
352 uint32_t version;
353 uint32_t length;
354 } header;
355 30 header.version = kProtocolVersion;
356 30 header.length = msg.length();
357 30 const unsigned raw_length = sizeof(header) + msg.length();
358 unsigned char *raw_msg = reinterpret_cast<unsigned char *>(
359 30 alloca(raw_length));
360 30 memcpy(raw_msg, &header, sizeof(header));
361 30 memcpy(raw_msg + sizeof(header), msg.data(), header.length);
362
363
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 const bool retval = SafeWrite(fd_send_, raw_msg, raw_length);
364
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 24 times.
30 if (!retval)
365
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 EnterFailState();
366 30 return retval;
367 }
368
369
370 /**
371 * We want to see valid JSON in the form
372 * { "cvmfs_authz_v1" : {
373 * "msgid":
374 * "revision":
375 * ...
376 * }
377 * ...
378 * }
379 *
380 * The contents of "cvmfs_authz_v1" depends on the msgid. Additional fields
381 * are ignored. The protocol revision should indicate changes in the fields.
382 */
383 57 bool AuthzExternalFetcher::ParseMsg(const std::string &json_msg,
384 const AuthzExternalMsgIds expected_msgid,
385 AuthzExternalMsg *binary_msg) {
386
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 57 times.
57 assert(binary_msg != NULL);
387
388
2/4
✓ Branch 1 taken 57 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 57 times.
✗ Branch 5 not taken.
57 const UniquePtr<JsonDocument> json_document(JsonDocument::Create(json_msg));
389
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 51 times.
57 if (!json_document.IsValid()) {
390
1/2
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
6 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
391 "invalid json from authz helper %s: %s", progname_.c_str(),
392 json_msg.c_str());
393
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 EnterFailState();
394 6 return false;
395 }
396
397
2/4
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 51 times.
✗ Branch 8 not taken.
51 const JSON *json_authz = JsonDocument::SearchInObject(
398 json_document->root(), "cvmfs_authz_v1", JSON_OBJECT);
399
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 48 times.
51 if (json_authz == NULL) {
400
1/2
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
3 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
401 "\"cvmfs_authz_v1\" not found in json from authz helper %s: %s",
402 progname_.c_str(), json_msg.c_str());
403
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 EnterFailState();
404 3 return false;
405 }
406
407
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 if (!ParseMsgId(json_authz, binary_msg)
408
6/6
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 30 times.
✓ Branch 4 taken 18 times.
✓ Branch 5 taken 30 times.
48 || (binary_msg->msgid != expected_msgid)) {
409
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 EnterFailState();
410 18 return false;
411 }
412
3/4
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 27 times.
30 if (!ParseRevision(json_authz, binary_msg)) {
413
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 EnterFailState();
414 3 return false;
415 }
416
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 9 times.
27 if (binary_msg->msgid == kAuthzMsgPermit) {
417
3/4
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 15 times.
18 if (!ParsePermit(json_authz, binary_msg)) {
418
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 EnterFailState();
419 3 return false;
420 }
421 }
422 24 return true;
423 57 }
424
425
426 48 bool AuthzExternalFetcher::ParseMsgId(const JSON *json_authz,
427 AuthzExternalMsg *binary_msg) {
428
2/4
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 48 times.
✗ Branch 6 not taken.
48 const JSON *json_msgid = JsonDocument::SearchInObject(json_authz, "msgid",
429 JSON_INT);
430
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 42 times.
48 if (json_msgid == NULL) {
431 6 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
432 "\"msgid\" not found in json from authz helper %s",
433 progname_.c_str());
434 6 EnterFailState();
435 6 return false;
436 }
437
438 42 if ((json_msgid->get<int>() < 0)
439
6/6
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 36 times.
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 36 times.
42 || (json_msgid->get<int>() >= kAuthzMsgInvalid)) {
440 6 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
441 "invalid \"msgid\" in json from authz helper %s: %d",
442 progname_.c_str(), json_msgid->get<int>());
443 6 EnterFailState();
444 6 return false;
445 }
446
447 36 binary_msg->msgid = static_cast<AuthzExternalMsgIds>(json_msgid->get<int>());
448 36 return true;
449 }
450
451
452 /**
453 * A permit must contain the authorization status. Optionally it can come with
454 * a "time to live" of the answer and a token (e.g. X.509 proxy certificate).
455 */
456 18 bool AuthzExternalFetcher::ParsePermit(const JSON *json_authz,
457 AuthzExternalMsg *binary_msg) {
458
2/4
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 18 times.
✗ Branch 6 not taken.
18 const JSON *json_status = JsonDocument::SearchInObject(json_authz, "status",
459 JSON_INT);
460
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 15 times.
18 if (json_status == NULL) {
461 3 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
462 "\"status\" not found in json from authz helper %s",
463 progname_.c_str());
464 3 EnterFailState();
465 3 return false;
466 }
467 15 if ((json_status->get<int>() < 0)
468
3/6
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 15 times.
15 || (json_status->get<int>() > kAuthzUnknown)) {
469 binary_msg->permit.status = kAuthzUnknown;
470 } else {
471 15 binary_msg->permit.status = static_cast<AuthzStatus>(
472 15 json_status->get<int>());
473 }
474
475
2/4
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 15 times.
✗ Branch 6 not taken.
15 const JSON *json_ttl = JsonDocument::SearchInObject(json_authz, "ttl",
476 JSON_INT);
477
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 12 times.
15 if (json_ttl == NULL) {
478 3 LogCvmfs(kLogAuthz, kLogDebug, "no ttl, using default");
479 3 binary_msg->permit.ttl = kDefaultTtl;
480 } else {
481 12 binary_msg->permit.ttl = std::max(kMinTtl, json_ttl->get<int>());
482 }
483
484
2/4
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 15 times.
✗ Branch 6 not taken.
15 const JSON *json_token = JsonDocument::SearchInObject(
485 json_authz, "x509_proxy", JSON_STRING);
486
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 9 times.
15 if (json_token != NULL) {
487 6 binary_msg->permit.token.type = kTokenX509;
488 6 string token_binary;
489
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 const bool valid_base64 = Debase64(json_token->get<string>(),
490 &token_binary);
491
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (!valid_base64) {
492 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
493 "invalid Base64 in 'x509_proxy' from authz helper %s",
494 progname_.c_str());
495 EnterFailState();
496 return false;
497 }
498 6 const unsigned size = token_binary.size();
499 6 binary_msg->permit.token.size = size;
500
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (size > 0) {
501 // The token is passed to the AuthzSessionManager, which takes care of
502 // freeing the memory
503 6 binary_msg->permit.token.data = smalloc(size);
504 6 memcpy(binary_msg->permit.token.data, token_binary.data(), size);
505 }
506
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 }
507
508
2/4
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 15 times.
✗ Branch 6 not taken.
15 json_token = JsonDocument::SearchInObject(json_authz, "bearer_token",
509 JSON_STRING);
510
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (json_token != NULL) {
511 binary_msg->permit.token.type = kTokenBearer;
512 const unsigned size = json_token->get<string>().size();
513 binary_msg->permit.token.size = size;
514 if (size > 0) {
515 // The token is passed to the AuthzSessionManager, which takes care of
516 // freeing the memory
517 binary_msg->permit.token.data = smalloc(size);
518 memcpy(binary_msg->permit.token.data, json_token->get<string>().data(),
519 size);
520
521 LogCvmfs(kLogAuthz, kLogDebug,
522 "Got a bearer_token from authz_helper. "
523 "Setting token type to kTokenBearer");
524 } else {
525 // We got a bearer_token, but a size 0 (or negative?) string
526 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
527 "bearer_token was in returned JSON from Authz helper,"
528 " but of size 0 from authz helper %s",
529 progname_.c_str());
530 }
531 }
532
533
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 9 times.
15 if (binary_msg->permit.token.type == kTokenUnknown) {
534 // No auth token returned, so authz should do... what exactly?
535 // Log error message
536 6 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
537 "No auth token found in returned JSON from Authz helper %s",
538 progname_.c_str());
539 }
540
541 15 return true;
542 }
543
544
545 30 bool AuthzExternalFetcher::ParseRevision(const JSON *json_authz,
546 AuthzExternalMsg *binary_msg) {
547
2/4
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 30 times.
✗ Branch 6 not taken.
30 const JSON *json_revision = JsonDocument::SearchInObject(
548 json_authz, "revision", JSON_INT);
549
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
30 if (json_revision == NULL) {
550 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
551 "\"revision\" not found in json from authz helper %s",
552 progname_.c_str());
553 EnterFailState();
554 return false;
555 }
556
557
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 27 times.
30 if (json_revision->get<int>() < 0) {
558 3 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
559 "invalid \"revision\" in json from authz helper %s: %d",
560 progname_.c_str(), json_revision->get<int>());
561 3 EnterFailState();
562 3 return false;
563 }
564
565 27 binary_msg->protocol_revision = json_revision->get<int>();
566 27 return true;
567 }
568
569
570 12 bool AuthzExternalFetcher::Recv(string *msg) {
571 uint32_t version;
572
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 ssize_t retval = SafeRead(fd_recv_, &version, sizeof(version));
573
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 9 times.
12 if (retval != static_cast<int>(sizeof(version))) {
574
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 EnterFailState();
575 3 return false;
576 }
577
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
9 if (version != kProtocolVersion) {
578
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
579 "authz helper uses unknown protocol version %u", version);
580
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 EnterFailState();
581 3 return false;
582 }
583
584 uint32_t length;
585
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 retval = SafeRead(fd_recv_, &length, sizeof(length));
586
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (retval != static_cast<int>(sizeof(length))) {
587 EnterFailState();
588 return false;
589 }
590
591 6 msg->clear();
592 char buf[kPageSize];
593 6 unsigned nbytes = 0;
594
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 6 times.
18 while (nbytes < length) {
595 12 const unsigned remaining = length - nbytes;
596
1/2
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 retval = SafeRead(fd_recv_, buf, std::min(kPageSize, remaining));
597
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (retval < 0) {
598 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
599 "read failure from authz helper %s", progname_.c_str());
600 EnterFailState();
601 return false;
602 }
603 12 nbytes += retval;
604
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 msg->append(buf, retval);
605 }
606
607 6 return true;
608 }
609
610
611 3 void AuthzExternalFetcher::StripAuthzSchema(const string &membership,
612 string *authz_schema,
613 string *pure_membership) {
614
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 vector<string> components = SplitString(membership, '%');
615
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 *authz_schema = components[0];
616
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 if (components.size() < 2) {
617
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 LogCvmfs(kLogAuthz, kLogDebug, "invalid membership: %s",
618 membership.c_str());
619
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 *pure_membership = "";
620 3 return;
621 }
622
623 components.erase(components.begin());
624 *pure_membership = JoinStrings(components, "%");
625
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 }
626