GCC Code Coverage Report


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