GCC Code Coverage Report


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