Directory: | cvmfs/ |
---|---|
File: | cvmfs/authz/authz_fetch.cc |
Date: | 2025-02-09 02:34:19 |
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 11521412 times.
✗ Branch 2 not taken.
|
11521412 | retval = waitpid(pid_, &statloc, WNOHANG); |
102 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 11521411 times.
|
11521412 | 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 11521409 times.
✓ Branch 1 taken 2 times.
|
11521411 | } 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 156 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 152 times.
✓ Branch 4 taken 4 times.
|
156 | while ((dirent = platform_readdir(dirp))) { |
167 |
1/2✓ Branch 2 taken 152 times.
✗ Branch 3 not taken.
|
152 | 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 152 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 144 times.
|
152 | if (!String2Uint64Parse(name, &name_uint64)) |
171 | 8 | continue; | |
172 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 136 times.
|
144 | if (name_uint64 < 2) |
173 | 8 | continue; | |
174 |
1/2✓ Branch 1 taken 136 times.
✗ Branch 2 not taken.
|
136 | open_fds.push_back(static_cast<int>(name_uint64)); |
175 |
2/2✓ Branch 1 taken 136 times.
✓ Branch 2 taken 16 times.
|
152 | } |
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 102 times.
✓ Branch 2 taken 3 times.
|
105 | for (unsigned i = 0; i < open_fds.size(); ++i) |
193 |
1/2✓ Branch 2 taken 102 times.
✗ Branch 3 not taken.
|
102 | 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 |