GCC Code Coverage Report


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