GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/authz/authz_fetch.cc
Date: 2025-06-22 02:36:02
Exec Total Coverage
Lines: 274 326 84.0%
Branches: 218 433 50.3%

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/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 1902 AuthzExternalFetcher::AuthzExternalFetcher(const string &fqrn,
38 const string &progname,
39 const string &search_path,
40 1902 OptionsManager *options_manager)
41 1902 : fqrn_(fqrn)
42
1/2
✓ Branch 1 taken 1902 times.
✗ Branch 2 not taken.
1902 , progname_(progname)
43
1/2
✓ Branch 1 taken 1902 times.
✗ Branch 2 not taken.
1902 , search_path_(search_path)
44 1902 , fd_send_(-1)
45 1902 , fd_recv_(-1)
46 1902 , pid_(-1)
47 1902 , fail_state_(false)
48 1902 , options_manager_(options_manager)
49
1/2
✓ Branch 3 taken 1902 times.
✗ Branch 4 not taken.
1902 , next_start_(-1) {
50 1902 InitLock();
51 1902 }
52
53 240 AuthzExternalFetcher::AuthzExternalFetcher(const string &fqrn,
54 int fd_send,
55 240 int fd_recv)
56 240 : fqrn_(fqrn)
57 240 , fd_send_(fd_send)
58 240 , fd_recv_(fd_recv)
59 240 , pid_(-1)
60 240 , fail_state_(false)
61 240 , options_manager_(NULL)
62
1/2
✓ Branch 3 taken 240 times.
✗ Branch 4 not taken.
240 , next_start_(-1) {
63 240 InitLock();
64 240 }
65
66
67 8568 AuthzExternalFetcher::~AuthzExternalFetcher() {
68 4284 const int retval = pthread_mutex_destroy(&lock_);
69
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2142 times.
4284 assert(retval == 0);
70
71 // Allow helper to gracefully terminate
72
3/4
✓ Branch 0 taken 192 times.
✓ Branch 1 taken 1950 times.
✓ Branch 2 taken 192 times.
✗ Branch 3 not taken.
4284 if ((fd_send_ >= 0) && !fail_state_) {
73 384 LogCvmfs(kLogAuthz, kLogDebug, "shutting down authz helper");
74 1152 Send(string("{\"cvmfs_authz_v1\":{") + "\"msgid\":"
75 1536 + StringifyInt(kAuthzMsgQuit) + "," + "\"revision\":0}}");
76 }
77
78 4284 ReapHelper();
79 8568 }
80
81 3150 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 384 times.
✓ Branch 1 taken 2766 times.
3150 if (fd_send_ >= 0)
85 384 close(fd_send_);
86 3150 fd_send_ = -1;
87
2/2
✓ Branch 0 taken 384 times.
✓ Branch 1 taken 2766 times.
3150 if (fd_recv_ >= 0)
88 384 close(fd_recv_);
89 3150 fd_recv_ = -1;
90
91
2/2
✓ Branch 0 taken 144 times.
✓ Branch 1 taken 3006 times.
3150 if (pid_ > 0) {
92 int retval;
93 144 const uint64_t now = platform_monotonic_time();
94 int statloc;
95 do {
96
1/2
✓ Branch 1 taken 559054080 times.
✗ Branch 2 not taken.
559054080 retval = waitpid(pid_, &statloc, WNOHANG);
97
2/2
✓ Branch 1 taken 48 times.
✓ Branch 2 taken 559054032 times.
559054080 if (platform_monotonic_time() > (now + kChildTimeout)) {
98
1/2
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
48 LogCvmfs(kLogAuthz, kLogSyslogWarn | kLogDebug,
99 "authz helper %s unresponsive, killing", progname_.c_str());
100 48 retval = kill(pid_, SIGKILL);
101
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 if (retval == 0) {
102 // Pick up client return status
103
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 (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 48 break;
109 }
110
2/2
✓ Branch 0 taken 559053936 times.
✓ Branch 1 taken 96 times.
559054032 } while (retval == 0);
111 144 pid_ = -1;
112 }
113 3150 }
114
115
116 1008 void AuthzExternalFetcher::EnterFailState() {
117 1008 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
118 "authz helper %s enters fail state, no more authorization",
119 progname_.c_str());
120
121 1008 ReapHelper();
122 1008 next_start_ = platform_monotonic_time() + kChildTimeout;
123 1008 fail_state_ = true;
124 1008 }
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 192 void AuthzExternalFetcher::ExecHelper() {
137 int pipe_send[2];
138 int pipe_recv[2];
139
1/2
✓ Branch 1 taken 192 times.
✗ Branch 2 not taken.
192 MakePipe(pipe_send);
140
1/2
✓ Branch 1 taken 192 times.
✗ Branch 2 not taken.
192 MakePipe(pipe_recv);
141 192 char *argv0 = strdupa(progname_.c_str());
142 192 char *argv[] = {argv0, NULL};
143
144 192 const bool strip_prefix = true;
145 192 vector<string> authz_env = options_manager_->GetEnvironmentSubset(
146
2/4
✓ Branch 2 taken 192 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 192 times.
✗ Branch 6 not taken.
384 "CVMFS_AUTHZ_", strip_prefix);
147 192 vector<char *> envp;
148
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 192 times.
192 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 192 times.
✗ Branch 2 not taken.
192 envp.push_back(strdupa("CVMFS_AUTHZ_HELPER=yes"));
151
1/2
✓ Branch 1 taken 192 times.
✗ Branch 2 not taken.
192 envp.push_back(NULL);
152
153 #ifdef __APPLE__
154 int max_fd = sysconf(_SC_OPEN_MAX);
155 assert(max_fd > 0);
156 #else
157 192 std::vector<int> open_fds;
158
1/2
✓ Branch 1 taken 192 times.
✗ Branch 2 not taken.
192 DIR *dirp = opendir("/proc/self/fd");
159
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 192 times.
192 assert(dirp);
160 platform_dirent64 *dirent;
161
3/4
✓ Branch 1 taken 4608 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4416 times.
✓ Branch 4 taken 192 times.
4608 while ((dirent = platform_readdir(dirp))) {
162
1/2
✓ Branch 2 taken 4416 times.
✗ Branch 3 not taken.
4416 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 4416 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 384 times.
✓ Branch 4 taken 4032 times.
4416 if (!String2Uint64Parse(name, &name_uint64))
166 384 continue;
167
2/2
✓ Branch 0 taken 384 times.
✓ Branch 1 taken 3648 times.
4032 if (name_uint64 < 2)
168 384 continue;
169
1/2
✓ Branch 1 taken 3648 times.
✗ Branch 2 not taken.
3648 open_fds.push_back(static_cast<int>(name_uint64));
170
2/2
✓ Branch 1 taken 3648 times.
✓ Branch 2 taken 768 times.
4416 }
171
1/2
✓ Branch 1 taken 192 times.
✗ Branch 2 not taken.
192 closedir(dirp);
172 #endif
173
1/2
✓ Branch 1 taken 192 times.
✗ Branch 2 not taken.
192 LogCvmfs(kLogAuthz, kLogDebug | kLogSyslog, "starting authz helper %s",
174 argv0);
175
176 192 const pid_t pid = fork();
177
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 192 times.
195 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 57 times.
✓ Branch 2 taken 3 times.
60 for (unsigned i = 0; i < open_fds.size(); ++i)
188
1/2
✓ Branch 2 taken 57 times.
✗ Branch 3 not taken.
57 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 3 execve(argv0, argv, &envp[0]);
199 3 syslog(LOG_USER | LOG_ERR, "failed to start authz helper %s (%d)", argv0,
200
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
3 errno);
201 _exit(1);
202 }
203
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 192 times.
192 assert(pid > 0);
204
1/2
✓ Branch 1 taken 192 times.
✗ Branch 2 not taken.
192 close(pipe_send[0]);
205
1/2
✓ Branch 1 taken 192 times.
✗ Branch 2 not taken.
192 close(pipe_recv[1]);
206
207 // Don't receive a signal if the helper terminates
208 192 signal(SIGPIPE, SIG_IGN);
209 192 pid_ = pid;
210 192 fd_send_ = pipe_send[1];
211 192 fd_recv_ = pipe_recv[0];
212 192 }
213
214
215 48 AuthzStatus AuthzExternalFetcher::Fetch(const QueryInfo &query_info,
216 AuthzToken *authz_token,
217 unsigned *ttl) {
218 48 *ttl = kDefaultTtl;
219
220 48 const MutexLockGuard lock_guard(lock_);
221
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (fail_state_) {
222 const uint64_t now = platform_monotonic_time();
223 if (now > next_start_) {
224 fail_state_ = false;
225 } else {
226 return kAuthzNoHelper;
227 }
228 }
229
230 bool retval;
231
232
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (fd_send_ < 0) {
233 if (progname_.empty())
234 progname_ = FindHelper(query_info.membership);
235 ExecHelper();
236 retval = Handshake();
237 if (!retval)
238 return kAuthzNoHelper;
239 }
240
2/4
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
48 assert((fd_send_ >= 0) && (fd_recv_ >= 0));
241
242 48 string authz_schema;
243 48 string pure_membership;
244
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 StripAuthzSchema(query_info.membership, &authz_schema, &pure_membership);
245
2/4
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 48 times.
✗ Branch 6 not taken.
96 string json_msg = string("{\"cvmfs_authz_v1\":{") + "\"msgid\":"
246
4/8
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 48 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 48 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 48 times.
✗ Branch 11 not taken.
192 + StringifyInt(kAuthzMsgVerify) + "," + "\"revision\":0,"
247
4/8
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 48 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 48 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 48 times.
✗ Branch 11 not taken.
192 + "\"uid\":" + StringifyInt(query_info.uid) + ","
248
4/8
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 48 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 48 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 48 times.
✗ Branch 11 not taken.
192 + "\"gid\":" + StringifyInt(query_info.gid) + ","
249
4/8
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 48 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 48 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 48 times.
✗ Branch 11 not taken.
192 + "\"pid\":" + StringifyInt(query_info.pid) + ","
250
4/8
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 48 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 48 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 48 times.
✗ Branch 11 not taken.
144 + "\"membership\":\"" + Base64(pure_membership) + "\"}}";
251
4/8
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 48 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 48 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 48 times.
✗ Branch 9 not taken.
48 retval = Send(json_msg) && Recv(&json_msg);
252
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (!retval)
253 return kAuthzNoHelper;
254 48 AuthzExternalMsg binary_msg;
255
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 retval = ParseMsg(json_msg, kAuthzMsgPermit, &binary_msg);
256
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (!retval)
257 return kAuthzNoHelper;
258
259 48 *ttl = binary_msg.permit.ttl;
260
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 if (binary_msg.permit.status == kAuthzOk) {
261 48 *authz_token = binary_msg.permit.token;
262 48 LogCvmfs(kLogAuthz, kLogDebug, "got token of type %d and size %u",
263
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 binary_msg.permit.token.type, binary_msg.permit.token.size);
264 }
265
266 48 return binary_msg.permit.status;
267 48 }
268
269
270 string AuthzExternalFetcher::FindHelper(const string &membership) {
271 string authz_schema;
272 string pure_membership;
273 StripAuthzSchema(membership, &authz_schema, &pure_membership);
274 const sanitizer::AuthzSchemaSanitizer sanitizer;
275 if (!sanitizer.IsValid(authz_schema)) {
276 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, "invalid authz schema: %s",
277 authz_schema.c_str());
278 return "";
279 }
280
281 string exe_path = search_path_ + "/cvmfs_" + authz_schema + "_helper";
282 if (!FileExists(exe_path)) {
283 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, "authz helper %s missing",
284 exe_path.c_str());
285 }
286 return exe_path;
287 }
288
289
290 /**
291 * Establish communication link with a forked authz helper.
292 */
293 96 bool AuthzExternalFetcher::Handshake() {
294
1/3
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
96 const string debug_log = GetLogDebugFile();
295 96 string json_debug_log;
296
2/4
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 96 times.
96 if (debug_log != "")
297 json_debug_log = ",\"debug_log\":\"" + debug_log + "\"";
298
2/4
✓ Branch 2 taken 96 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 96 times.
✗ Branch 6 not taken.
192 string json_msg = string("{") + "\"cvmfs_authz_v1\":{"
299
4/8
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 96 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 96 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 96 times.
✗ Branch 11 not taken.
288 + "\"msgid\":" + StringifyInt(0) + "," + "\"revision\":0,"
300
5/10
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 96 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 96 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 96 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 96 times.
✗ Branch 14 not taken.
288 + "\"fqrn\":\"" + fqrn_ + "\"," + "\"syslog_facility\":"
301
4/8
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 96 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 96 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 96 times.
✗ Branch 11 not taken.
384 + StringifyInt(GetLogSyslogFacility()) + ","
302
4/8
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 96 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 96 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 96 times.
✗ Branch 11 not taken.
288 + "\"syslog_level\":" + StringifyInt(GetLogSyslogLevel())
303
2/4
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 96 times.
✗ Branch 5 not taken.
192 + json_debug_log + "}}";
304
1/2
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
96 bool retval = Send(json_msg);
305
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 48 times.
96 if (!retval)
306 48 return false;
307
308
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 retval = Recv(&json_msg);
309
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (!retval)
310 return false;
311 48 AuthzExternalMsg binary_msg;
312
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 retval = ParseMsg(json_msg, kAuthzMsgReady, &binary_msg);
313
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (!retval)
314 return false;
315
316 48 return true;
317 96 }
318
319
320 2142 void AuthzExternalFetcher::InitLock() {
321 2142 const int retval = pthread_mutex_init(&lock_, NULL);
322
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2142 times.
2142 assert(retval == 0);
323 2142 }
324
325
326 480 bool AuthzExternalFetcher::Send(const string &msg) {
327 // Line format: 4 byte protocol version, 4 byte length, message
328 struct {
329 uint32_t version;
330 uint32_t length;
331 } header;
332 480 header.version = kProtocolVersion;
333 480 header.length = msg.length();
334 480 const unsigned raw_length = sizeof(header) + msg.length();
335 unsigned char *raw_msg = reinterpret_cast<unsigned char *>(
336 480 alloca(raw_length));
337 480 memcpy(raw_msg, &header, sizeof(header));
338 480 memcpy(raw_msg + sizeof(header), msg.data(), header.length);
339
340
1/2
✓ Branch 1 taken 480 times.
✗ Branch 2 not taken.
480 const bool retval = SafeWrite(fd_send_, raw_msg, raw_length);
341
2/2
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 384 times.
480 if (!retval)
342
1/2
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
96 EnterFailState();
343 480 return retval;
344 }
345
346
347 /**
348 * We want to see valid JSON in the form
349 * { "cvmfs_authz_v1" : {
350 * "msgid":
351 * "revision":
352 * ...
353 * }
354 * ...
355 * }
356 *
357 * The contents of "cvmfs_authz_v1" depends on the msgid. Additional fields
358 * are ignored. The protocol revision should indicate changes in the fields.
359 */
360 912 bool AuthzExternalFetcher::ParseMsg(const std::string &json_msg,
361 const AuthzExternalMsgIds expected_msgid,
362 AuthzExternalMsg *binary_msg) {
363
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 912 times.
912 assert(binary_msg != NULL);
364
365
2/4
✓ Branch 1 taken 912 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 912 times.
✗ Branch 5 not taken.
912 const UniquePtr<JsonDocument> json_document(JsonDocument::Create(json_msg));
366
2/2
✓ Branch 1 taken 96 times.
✓ Branch 2 taken 816 times.
912 if (!json_document.IsValid()) {
367
1/2
✓ Branch 3 taken 96 times.
✗ Branch 4 not taken.
96 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
368 "invalid json from authz helper %s: %s", progname_.c_str(),
369 json_msg.c_str());
370
1/2
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
96 EnterFailState();
371 96 return false;
372 }
373
374
2/4
✓ Branch 2 taken 816 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 816 times.
✗ Branch 8 not taken.
816 JSON *json_authz = JsonDocument::SearchInObject(
375 json_document->root(), "cvmfs_authz_v1", JSON_OBJECT);
376
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 768 times.
816 if (json_authz == NULL) {
377
1/2
✓ Branch 3 taken 48 times.
✗ Branch 4 not taken.
48 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
378 "\"cvmfs_authz_v1\" not found in json from authz helper %s: %s",
379 progname_.c_str(), json_msg.c_str());
380
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 EnterFailState();
381 48 return false;
382 }
383
384
1/2
✓ Branch 1 taken 768 times.
✗ Branch 2 not taken.
768 if (!ParseMsgId(json_authz, binary_msg)
385
6/6
✓ Branch 0 taken 576 times.
✓ Branch 1 taken 192 times.
✓ Branch 2 taken 96 times.
✓ Branch 3 taken 480 times.
✓ Branch 4 taken 288 times.
✓ Branch 5 taken 480 times.
768 || (binary_msg->msgid != expected_msgid)) {
386
1/2
✓ Branch 1 taken 288 times.
✗ Branch 2 not taken.
288 EnterFailState();
387 288 return false;
388 }
389
3/4
✓ Branch 1 taken 480 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 48 times.
✓ Branch 4 taken 432 times.
480 if (!ParseRevision(json_authz, binary_msg)) {
390
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 EnterFailState();
391 48 return false;
392 }
393
2/2
✓ Branch 0 taken 288 times.
✓ Branch 1 taken 144 times.
432 if (binary_msg->msgid == kAuthzMsgPermit) {
394
3/4
✓ Branch 1 taken 288 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 48 times.
✓ Branch 4 taken 240 times.
288 if (!ParsePermit(json_authz, binary_msg)) {
395
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 EnterFailState();
396 48 return false;
397 }
398 }
399 384 return true;
400 912 }
401
402
403 768 bool AuthzExternalFetcher::ParseMsgId(JSON *json_authz,
404 AuthzExternalMsg *binary_msg) {
405
2/4
✓ Branch 2 taken 768 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 768 times.
✗ Branch 6 not taken.
768 JSON *json_msgid = JsonDocument::SearchInObject(json_authz, "msgid",
406 JSON_INT);
407
2/2
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 672 times.
768 if (json_msgid == NULL) {
408 96 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
409 "\"msgid\" not found in json from authz helper %s",
410 progname_.c_str());
411 96 EnterFailState();
412 96 return false;
413 }
414
415
2/2
✓ Branch 0 taken 624 times.
✓ Branch 1 taken 48 times.
672 if ((json_msgid->int_value < 0)
416
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 576 times.
624 || (json_msgid->int_value >= kAuthzMsgInvalid)) {
417 96 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
418 "invalid \"msgid\" in json from authz helper %s: %d",
419 progname_.c_str(), json_msgid->int_value);
420 96 EnterFailState();
421 96 return false;
422 }
423
424 576 binary_msg->msgid = static_cast<AuthzExternalMsgIds>(json_msgid->int_value);
425 576 return true;
426 }
427
428
429 /**
430 * A permit must contain the authorization status. Optionally it can come with
431 * a "time to live" of the answer and a token (e.g. X.509 proxy certificate).
432 */
433 288 bool AuthzExternalFetcher::ParsePermit(JSON *json_authz,
434 AuthzExternalMsg *binary_msg) {
435
2/4
✓ Branch 2 taken 288 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 288 times.
✗ Branch 6 not taken.
288 JSON *json_status = JsonDocument::SearchInObject(json_authz, "status",
436 JSON_INT);
437
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 240 times.
288 if (json_status == NULL) {
438 48 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
439 "\"status\" not found in json from authz helper %s",
440 progname_.c_str());
441 48 EnterFailState();
442 48 return false;
443 }
444
1/2
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
240 if ((json_status->int_value < 0)
445
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
240 || (json_status->int_value > kAuthzUnknown)) {
446 binary_msg->permit.status = kAuthzUnknown;
447 } else {
448 240 binary_msg->permit.status = static_cast<AuthzStatus>(
449 240 json_status->int_value);
450 }
451
452
2/4
✓ Branch 2 taken 240 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 240 times.
✗ Branch 6 not taken.
240 JSON *json_ttl = JsonDocument::SearchInObject(json_authz, "ttl", JSON_INT);
453
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 192 times.
240 if (json_ttl == NULL) {
454 48 LogCvmfs(kLogAuthz, kLogDebug, "no ttl, using default");
455 48 binary_msg->permit.ttl = kDefaultTtl;
456 } else {
457 192 binary_msg->permit.ttl = std::max(kMinTtl, json_ttl->int_value);
458 }
459
460
2/4
✓ Branch 2 taken 240 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 240 times.
✗ Branch 6 not taken.
240 JSON *json_token = JsonDocument::SearchInObject(json_authz, "x509_proxy",
461 JSON_STRING);
462
2/2
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 144 times.
240 if (json_token != NULL) {
463 96 binary_msg->permit.token.type = kTokenX509;
464 96 string token_binary;
465
2/4
✓ Branch 2 taken 96 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 96 times.
✗ Branch 6 not taken.
96 const bool valid_base64 = Debase64(json_token->string_value, &token_binary);
466
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 96 times.
96 if (!valid_base64) {
467 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
468 "invalid Base64 in 'x509_proxy' from authz helper %s",
469 progname_.c_str());
470 EnterFailState();
471 return false;
472 }
473 96 const unsigned size = token_binary.size();
474 96 binary_msg->permit.token.size = size;
475
1/2
✓ Branch 0 taken 96 times.
✗ Branch 1 not taken.
96 if (size > 0) {
476 // The token is passed to the AuthzSessionManager, which takes care of
477 // freeing the memory
478 96 binary_msg->permit.token.data = smalloc(size);
479 96 memcpy(binary_msg->permit.token.data, token_binary.data(), size);
480 }
481
1/2
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
96 }
482
483
2/4
✓ Branch 2 taken 240 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 240 times.
✗ Branch 6 not taken.
240 json_token = JsonDocument::SearchInObject(json_authz, "bearer_token",
484 JSON_STRING);
485
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
240 if (json_token != NULL) {
486 binary_msg->permit.token.type = kTokenBearer;
487 const unsigned size = strlen(json_token->string_value);
488 binary_msg->permit.token.size = size;
489 if (size > 0) {
490 // The token is passed to the AuthzSessionManager, which takes care of
491 // freeing the memory
492 binary_msg->permit.token.data = smalloc(size);
493 memcpy(binary_msg->permit.token.data, json_token->string_value, size);
494
495 LogCvmfs(kLogAuthz, kLogDebug,
496 "Got a bearer_token from authz_helper. "
497 "Setting token type to kTokenBearer");
498 } else {
499 // We got a bearer_token, but a size 0 (or negative?) string
500 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
501 "bearer_token was in returned JSON from Authz helper,"
502 " but of size 0 from authz helper %s",
503 progname_.c_str());
504 }
505 }
506
507
2/2
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 144 times.
240 if (binary_msg->permit.token.type == kTokenUnknown) {
508 // No auth token returned, so authz should do... what exactly?
509 // Log error message
510 96 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
511 "No auth token found in returned JSON from Authz helper %s",
512 progname_.c_str());
513 }
514
515 240 return true;
516 }
517
518
519 480 bool AuthzExternalFetcher::ParseRevision(JSON *json_authz,
520 AuthzExternalMsg *binary_msg) {
521
2/4
✓ Branch 2 taken 480 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 480 times.
✗ Branch 6 not taken.
480 JSON *json_revision = JsonDocument::SearchInObject(json_authz, "revision",
522 JSON_INT);
523
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 480 times.
480 if (json_revision == NULL) {
524 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
525 "\"revision\" not found in json from authz helper %s",
526 progname_.c_str());
527 EnterFailState();
528 return false;
529 }
530
531
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 432 times.
480 if (json_revision->int_value < 0) {
532 48 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
533 "invalid \"revision\" in json from authz helper %s: %d",
534 progname_.c_str(), json_revision->int_value);
535 48 EnterFailState();
536 48 return false;
537 }
538
539 432 binary_msg->protocol_revision = json_revision->int_value;
540 432 return true;
541 }
542
543
544 192 bool AuthzExternalFetcher::Recv(string *msg) {
545 uint32_t version;
546
1/2
✓ Branch 1 taken 192 times.
✗ Branch 2 not taken.
192 ssize_t retval = SafeRead(fd_recv_, &version, sizeof(version));
547
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 144 times.
192 if (retval != static_cast<int>(sizeof(version))) {
548
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 EnterFailState();
549 48 return false;
550 }
551
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 96 times.
144 if (version != kProtocolVersion) {
552
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
553 "authz helper uses unknown protocol version %u", version);
554
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 EnterFailState();
555 48 return false;
556 }
557
558 uint32_t length;
559
1/2
✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
96 retval = SafeRead(fd_recv_, &length, sizeof(length));
560
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 96 times.
96 if (retval != static_cast<int>(sizeof(length))) {
561 EnterFailState();
562 return false;
563 }
564
565 96 msg->clear();
566 char buf[kPageSize];
567 96 unsigned nbytes = 0;
568
2/2
✓ Branch 0 taken 192 times.
✓ Branch 1 taken 96 times.
288 while (nbytes < length) {
569 192 const unsigned remaining = length - nbytes;
570
1/2
✓ Branch 2 taken 192 times.
✗ Branch 3 not taken.
192 retval = SafeRead(fd_recv_, buf, std::min(kPageSize, remaining));
571
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 192 times.
192 if (retval < 0) {
572 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
573 "read failure from authz helper %s", progname_.c_str());
574 EnterFailState();
575 return false;
576 }
577 192 nbytes += retval;
578
1/2
✓ Branch 1 taken 192 times.
✗ Branch 2 not taken.
192 msg->append(buf, retval);
579 }
580
581 96 return true;
582 }
583
584
585 48 void AuthzExternalFetcher::StripAuthzSchema(const string &membership,
586 string *authz_schema,
587 string *pure_membership) {
588
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 vector<string> components = SplitString(membership, '%');
589
1/2
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
48 *authz_schema = components[0];
590
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 if (components.size() < 2) {
591
1/2
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
48 LogCvmfs(kLogAuthz, kLogDebug, "invalid membership: %s",
592 membership.c_str());
593
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 *pure_membership = "";
594 48 return;
595 }
596
597 components.erase(components.begin());
598 *pure_membership = JoinStrings(components, "%");
599
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
48 }
600