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 |