| Directory: | cvmfs/ |
|---|---|
| File: | cvmfs/authz/authz_fetch.cc |
| Date: | 2025-10-26 02:35:25 |
| 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 | 1073 | AuthzExternalFetcher::AuthzExternalFetcher(const string &fqrn, | |
| 38 | const string &progname, | ||
| 39 | const string &search_path, | ||
| 40 | 1073 | OptionsManager *options_manager) | |
| 41 | 1073 | : fqrn_(fqrn) | |
| 42 |
1/2✓ Branch 1 taken 1073 times.
✗ Branch 2 not taken.
|
1073 | , progname_(progname) |
| 43 |
1/2✓ Branch 1 taken 1073 times.
✗ Branch 2 not taken.
|
1073 | , search_path_(search_path) |
| 44 | 1073 | , fd_send_(-1) | |
| 45 | 1073 | , fd_recv_(-1) | |
| 46 | 1073 | , pid_(-1) | |
| 47 | 1073 | , fail_state_(false) | |
| 48 | 1073 | , options_manager_(options_manager) | |
| 49 |
1/2✓ Branch 3 taken 1073 times.
✗ Branch 4 not taken.
|
1073 | , next_start_(-1) { |
| 50 | 1073 | InitLock(); | |
| 51 | 1073 | } | |
| 52 | |||
| 53 | 200 | AuthzExternalFetcher::AuthzExternalFetcher(const string &fqrn, | |
| 54 | int fd_send, | ||
| 55 | 200 | int fd_recv) | |
| 56 | 200 | : fqrn_(fqrn) | |
| 57 | 200 | , fd_send_(fd_send) | |
| 58 | 200 | , fd_recv_(fd_recv) | |
| 59 | 200 | , pid_(-1) | |
| 60 | 200 | , fail_state_(false) | |
| 61 | 200 | , options_manager_(NULL) | |
| 62 |
1/2✓ Branch 3 taken 200 times.
✗ Branch 4 not taken.
|
200 | , next_start_(-1) { |
| 63 | 200 | InitLock(); | |
| 64 | 200 | } | |
| 65 | |||
| 66 | |||
| 67 | 5092 | AuthzExternalFetcher::~AuthzExternalFetcher() { | |
| 68 | 2546 | const int retval = pthread_mutex_destroy(&lock_); | |
| 69 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1273 times.
|
2546 | assert(retval == 0); |
| 70 | |||
| 71 | // Allow helper to gracefully terminate | ||
| 72 |
3/4✓ Branch 0 taken 160 times.
✓ Branch 1 taken 1113 times.
✓ Branch 2 taken 160 times.
✗ Branch 3 not taken.
|
2546 | if ((fd_send_ >= 0) && !fail_state_) { |
| 73 | 320 | LogCvmfs(kLogAuthz, kLogDebug, "shutting down authz helper"); | |
| 74 | 960 | Send(string("{\"cvmfs_authz_v1\":{") + "\"msgid\":" | |
| 75 | 1280 | + StringifyInt(kAuthzMsgQuit) + "," + "\"revision\":0}}"); | |
| 76 | } | ||
| 77 | |||
| 78 | 2546 | ReapHelper(); | |
| 79 | 5092 | } | |
| 80 | |||
| 81 | 2113 | 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 320 times.
✓ Branch 1 taken 1793 times.
|
2113 | if (fd_send_ >= 0) |
| 85 | 320 | close(fd_send_); | |
| 86 | 2113 | fd_send_ = -1; | |
| 87 |
2/2✓ Branch 0 taken 320 times.
✓ Branch 1 taken 1793 times.
|
2113 | if (fd_recv_ >= 0) |
| 88 | 320 | close(fd_recv_); | |
| 89 | 2113 | fd_recv_ = -1; | |
| 90 | |||
| 91 |
2/2✓ Branch 0 taken 120 times.
✓ Branch 1 taken 1993 times.
|
2113 | if (pid_ > 0) { |
| 92 | int retval; | ||
| 93 | 120 | const uint64_t now = platform_monotonic_time(); | |
| 94 | int statloc; | ||
| 95 | do { | ||
| 96 |
1/2✓ Branch 1 taken 494008760 times.
✗ Branch 2 not taken.
|
494008760 | retval = waitpid(pid_, &statloc, WNOHANG); |
| 97 |
2/2✓ Branch 1 taken 40 times.
✓ Branch 2 taken 494008720 times.
|
494008760 | if (platform_monotonic_time() > (now + kChildTimeout)) { |
| 98 |
1/2✓ Branch 2 taken 40 times.
✗ Branch 3 not taken.
|
40 | LogCvmfs(kLogAuthz, kLogSyslogWarn | kLogDebug, |
| 99 | "authz helper %s unresponsive, killing", progname_.c_str()); | ||
| 100 | 40 | retval = kill(pid_, SIGKILL); | |
| 101 |
1/2✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
|
40 | if (retval == 0) { |
| 102 | // Pick up client return status | ||
| 103 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | (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 | 40 | break; | |
| 109 | } | ||
| 110 |
2/2✓ Branch 0 taken 494008640 times.
✓ Branch 1 taken 80 times.
|
494008720 | } while (retval == 0); |
| 111 | 120 | pid_ = -1; | |
| 112 | } | ||
| 113 | 2113 | } | |
| 114 | |||
| 115 | |||
| 116 | 840 | void AuthzExternalFetcher::EnterFailState() { | |
| 117 | 840 | LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, | |
| 118 | "authz helper %s enters fail state, no more authorization", | ||
| 119 | progname_.c_str()); | ||
| 120 | |||
| 121 | 840 | ReapHelper(); | |
| 122 | 840 | next_start_ = platform_monotonic_time() + kChildTimeout; | |
| 123 | 840 | fail_state_ = true; | |
| 124 | 840 | } | |
| 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 | 160 | void AuthzExternalFetcher::ExecHelper() { | |
| 137 | int pipe_send[2]; | ||
| 138 | int pipe_recv[2]; | ||
| 139 |
1/2✓ Branch 1 taken 160 times.
✗ Branch 2 not taken.
|
160 | MakePipe(pipe_send); |
| 140 |
1/2✓ Branch 1 taken 160 times.
✗ Branch 2 not taken.
|
160 | MakePipe(pipe_recv); |
| 141 | 160 | char *argv0 = strdupa(progname_.c_str()); | |
| 142 | 160 | char *argv[] = {argv0, NULL}; | |
| 143 | |||
| 144 | 160 | const bool strip_prefix = true; | |
| 145 | 160 | vector<string> authz_env = options_manager_->GetEnvironmentSubset( | |
| 146 |
2/4✓ Branch 2 taken 160 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 160 times.
✗ Branch 6 not taken.
|
320 | "CVMFS_AUTHZ_", strip_prefix); |
| 147 | 160 | vector<char *> envp; | |
| 148 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 160 times.
|
160 | 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 160 times.
✗ Branch 2 not taken.
|
160 | envp.push_back(strdupa("CVMFS_AUTHZ_HELPER=yes")); |
| 151 |
1/2✓ Branch 1 taken 160 times.
✗ Branch 2 not taken.
|
160 | envp.push_back(NULL); |
| 152 | |||
| 153 | #ifdef __APPLE__ | ||
| 154 | int max_fd = sysconf(_SC_OPEN_MAX); | ||
| 155 | assert(max_fd > 0); | ||
| 156 | #else | ||
| 157 | 160 | std::vector<int> open_fds; | |
| 158 |
1/2✓ Branch 1 taken 160 times.
✗ Branch 2 not taken.
|
160 | DIR *dirp = opendir("/proc/self/fd"); |
| 159 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 160 times.
|
160 | assert(dirp); |
| 160 | platform_dirent64 *dirent; | ||
| 161 |
3/4✓ Branch 1 taken 3040 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2880 times.
✓ Branch 4 taken 160 times.
|
3040 | while ((dirent = platform_readdir(dirp))) { |
| 162 |
1/2✓ Branch 2 taken 2880 times.
✗ Branch 3 not taken.
|
2880 | 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 2880 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 320 times.
✓ Branch 4 taken 2560 times.
|
2880 | if (!String2Uint64Parse(name, &name_uint64)) |
| 166 | 320 | continue; | |
| 167 |
2/2✓ Branch 0 taken 320 times.
✓ Branch 1 taken 2240 times.
|
2560 | if (name_uint64 < 2) |
| 168 | 320 | continue; | |
| 169 |
1/2✓ Branch 1 taken 2240 times.
✗ Branch 2 not taken.
|
2240 | open_fds.push_back(static_cast<int>(name_uint64)); |
| 170 |
2/2✓ Branch 1 taken 2240 times.
✓ Branch 2 taken 640 times.
|
2880 | } |
| 171 |
1/2✓ Branch 1 taken 160 times.
✗ Branch 2 not taken.
|
160 | closedir(dirp); |
| 172 | #endif | ||
| 173 |
1/2✓ Branch 1 taken 160 times.
✗ Branch 2 not taken.
|
160 | LogCvmfs(kLogAuthz, kLogDebug | kLogSyslog, "starting authz helper %s", |
| 174 | argv0); | ||
| 175 | |||
| 176 | 160 | const pid_t pid = fork(); | |
| 177 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 160 times.
|
163 | 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 42 times.
✓ Branch 2 taken 3 times.
|
45 | for (unsigned i = 0; i < open_fds.size(); ++i) |
| 188 |
1/2✓ Branch 2 taken 42 times.
✗ Branch 3 not taken.
|
42 | 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 160 times.
|
160 | assert(pid > 0); |
| 204 |
1/2✓ Branch 1 taken 160 times.
✗ Branch 2 not taken.
|
160 | close(pipe_send[0]); |
| 205 |
1/2✓ Branch 1 taken 160 times.
✗ Branch 2 not taken.
|
160 | close(pipe_recv[1]); |
| 206 | |||
| 207 | // Don't receive a signal if the helper terminates | ||
| 208 | 160 | signal(SIGPIPE, SIG_IGN); | |
| 209 | 160 | pid_ = pid; | |
| 210 | 160 | fd_send_ = pipe_send[1]; | |
| 211 | 160 | fd_recv_ = pipe_recv[0]; | |
| 212 | 160 | } | |
| 213 | |||
| 214 | |||
| 215 | 40 | AuthzStatus AuthzExternalFetcher::Fetch(const QueryInfo &query_info, | |
| 216 | AuthzToken *authz_token, | ||
| 217 | unsigned *ttl) { | ||
| 218 | 40 | *ttl = kDefaultTtl; | |
| 219 | |||
| 220 | 40 | const MutexLockGuard lock_guard(lock_); | |
| 221 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
|
40 | 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 40 times.
|
40 | 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 40 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 40 times.
✗ Branch 3 not taken.
|
40 | assert((fd_send_ >= 0) && (fd_recv_ >= 0)); |
| 241 | |||
| 242 | 40 | string authz_schema; | |
| 243 | 40 | string pure_membership; | |
| 244 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | StripAuthzSchema(query_info.membership, &authz_schema, &pure_membership); |
| 245 |
2/4✓ Branch 2 taken 40 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 40 times.
✗ Branch 6 not taken.
|
80 | string json_msg = string("{\"cvmfs_authz_v1\":{") + "\"msgid\":" |
| 246 |
4/8✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 40 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 40 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 40 times.
✗ Branch 11 not taken.
|
160 | + StringifyInt(kAuthzMsgVerify) + "," + "\"revision\":0," |
| 247 |
4/8✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 40 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 40 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 40 times.
✗ Branch 11 not taken.
|
160 | + "\"uid\":" + StringifyInt(query_info.uid) + "," |
| 248 |
4/8✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 40 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 40 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 40 times.
✗ Branch 11 not taken.
|
160 | + "\"gid\":" + StringifyInt(query_info.gid) + "," |
| 249 |
4/8✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 40 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 40 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 40 times.
✗ Branch 11 not taken.
|
160 | + "\"pid\":" + StringifyInt(query_info.pid) + "," |
| 250 |
4/8✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 40 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 40 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 40 times.
✗ Branch 11 not taken.
|
120 | + "\"membership\":\"" + Base64(pure_membership) + "\"}}"; |
| 251 |
4/8✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 40 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 40 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 40 times.
✗ Branch 9 not taken.
|
40 | retval = Send(json_msg) && Recv(&json_msg); |
| 252 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
|
40 | if (!retval) |
| 253 | ✗ | return kAuthzNoHelper; | |
| 254 | 40 | AuthzExternalMsg binary_msg; | |
| 255 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | retval = ParseMsg(json_msg, kAuthzMsgPermit, &binary_msg); |
| 256 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
|
40 | if (!retval) |
| 257 | ✗ | return kAuthzNoHelper; | |
| 258 | |||
| 259 | 40 | *ttl = binary_msg.permit.ttl; | |
| 260 |
1/2✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
|
40 | if (binary_msg.permit.status == kAuthzOk) { |
| 261 | 40 | *authz_token = binary_msg.permit.token; | |
| 262 | 40 | LogCvmfs(kLogAuthz, kLogDebug, "got token of type %d and size %u", | |
| 263 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | binary_msg.permit.token.type, binary_msg.permit.token.size); |
| 264 | } | ||
| 265 | |||
| 266 | 40 | return binary_msg.permit.status; | |
| 267 | 40 | } | |
| 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 | 80 | bool AuthzExternalFetcher::Handshake() { | |
| 294 |
1/3✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
80 | const string debug_log = GetLogDebugFile(); |
| 295 | 80 | string json_debug_log; | |
| 296 |
2/4✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 80 times.
|
80 | if (debug_log != "") |
| 297 | ✗ | json_debug_log = ",\"debug_log\":\"" + debug_log + "\""; | |
| 298 |
2/4✓ Branch 2 taken 80 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 80 times.
✗ Branch 6 not taken.
|
160 | string json_msg = string("{") + "\"cvmfs_authz_v1\":{" |
| 299 |
4/8✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 80 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 80 times.
✗ Branch 11 not taken.
|
240 | + "\"msgid\":" + StringifyInt(0) + "," + "\"revision\":0," |
| 300 |
5/10✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 80 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 80 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 80 times.
✗ Branch 14 not taken.
|
240 | + "\"fqrn\":\"" + fqrn_ + "\"," + "\"syslog_facility\":" |
| 301 |
4/8✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 80 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 80 times.
✗ Branch 11 not taken.
|
320 | + StringifyInt(GetLogSyslogFacility()) + "," |
| 302 |
4/8✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 80 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 80 times.
✗ Branch 11 not taken.
|
240 | + "\"syslog_level\":" + StringifyInt(GetLogSyslogLevel()) |
| 303 |
2/4✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
|
160 | + json_debug_log + "}}"; |
| 304 |
1/2✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
|
80 | bool retval = Send(json_msg); |
| 305 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 40 times.
|
80 | if (!retval) |
| 306 | 40 | return false; | |
| 307 | |||
| 308 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | retval = Recv(&json_msg); |
| 309 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
|
40 | if (!retval) |
| 310 | ✗ | return false; | |
| 311 | 40 | AuthzExternalMsg binary_msg; | |
| 312 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | retval = ParseMsg(json_msg, kAuthzMsgReady, &binary_msg); |
| 313 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
|
40 | if (!retval) |
| 314 | ✗ | return false; | |
| 315 | |||
| 316 | 40 | return true; | |
| 317 | 80 | } | |
| 318 | |||
| 319 | |||
| 320 | 1273 | void AuthzExternalFetcher::InitLock() { | |
| 321 | 1273 | const int retval = pthread_mutex_init(&lock_, NULL); | |
| 322 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1273 times.
|
1273 | assert(retval == 0); |
| 323 | 1273 | } | |
| 324 | |||
| 325 | |||
| 326 | 400 | 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 | 400 | header.version = kProtocolVersion; | |
| 333 | 400 | header.length = msg.length(); | |
| 334 | 400 | const unsigned raw_length = sizeof(header) + msg.length(); | |
| 335 | unsigned char *raw_msg = reinterpret_cast<unsigned char *>( | ||
| 336 | 400 | alloca(raw_length)); | |
| 337 | 400 | memcpy(raw_msg, &header, sizeof(header)); | |
| 338 | 400 | memcpy(raw_msg + sizeof(header), msg.data(), header.length); | |
| 339 | |||
| 340 |
1/2✓ Branch 1 taken 400 times.
✗ Branch 2 not taken.
|
400 | const bool retval = SafeWrite(fd_send_, raw_msg, raw_length); |
| 341 |
2/2✓ Branch 0 taken 80 times.
✓ Branch 1 taken 320 times.
|
400 | if (!retval) |
| 342 |
1/2✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
|
80 | EnterFailState(); |
| 343 | 400 | 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 | 760 | 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 760 times.
|
760 | assert(binary_msg != NULL); |
| 364 | |||
| 365 |
2/4✓ Branch 1 taken 760 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 760 times.
✗ Branch 5 not taken.
|
760 | const UniquePtr<JsonDocument> json_document(JsonDocument::Create(json_msg)); |
| 366 |
2/2✓ Branch 1 taken 80 times.
✓ Branch 2 taken 680 times.
|
760 | if (!json_document.IsValid()) { |
| 367 |
1/2✓ Branch 3 taken 80 times.
✗ Branch 4 not taken.
|
80 | 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 80 times.
✗ Branch 2 not taken.
|
80 | EnterFailState(); |
| 371 | 80 | return false; | |
| 372 | } | ||
| 373 | |||
| 374 |
2/4✓ Branch 2 taken 680 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 680 times.
✗ Branch 8 not taken.
|
680 | JSON *json_authz = JsonDocument::SearchInObject( |
| 375 | json_document->root(), "cvmfs_authz_v1", JSON_OBJECT); | ||
| 376 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 640 times.
|
680 | if (json_authz == NULL) { |
| 377 |
1/2✓ Branch 3 taken 40 times.
✗ Branch 4 not taken.
|
40 | 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 40 times.
✗ Branch 2 not taken.
|
40 | EnterFailState(); |
| 381 | 40 | return false; | |
| 382 | } | ||
| 383 | |||
| 384 |
1/2✓ Branch 1 taken 640 times.
✗ Branch 2 not taken.
|
640 | if (!ParseMsgId(json_authz, binary_msg) |
| 385 |
6/6✓ Branch 0 taken 480 times.
✓ Branch 1 taken 160 times.
✓ Branch 2 taken 80 times.
✓ Branch 3 taken 400 times.
✓ Branch 4 taken 240 times.
✓ Branch 5 taken 400 times.
|
640 | || (binary_msg->msgid != expected_msgid)) { |
| 386 |
1/2✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
|
240 | EnterFailState(); |
| 387 | 240 | return false; | |
| 388 | } | ||
| 389 |
3/4✓ Branch 1 taken 400 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 40 times.
✓ Branch 4 taken 360 times.
|
400 | if (!ParseRevision(json_authz, binary_msg)) { |
| 390 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | EnterFailState(); |
| 391 | 40 | return false; | |
| 392 | } | ||
| 393 |
2/2✓ Branch 0 taken 240 times.
✓ Branch 1 taken 120 times.
|
360 | if (binary_msg->msgid == kAuthzMsgPermit) { |
| 394 |
3/4✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 40 times.
✓ Branch 4 taken 200 times.
|
240 | if (!ParsePermit(json_authz, binary_msg)) { |
| 395 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | EnterFailState(); |
| 396 | 40 | return false; | |
| 397 | } | ||
| 398 | } | ||
| 399 | 320 | return true; | |
| 400 | 760 | } | |
| 401 | |||
| 402 | |||
| 403 | 640 | bool AuthzExternalFetcher::ParseMsgId(JSON *json_authz, | |
| 404 | AuthzExternalMsg *binary_msg) { | ||
| 405 |
2/4✓ Branch 2 taken 640 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 640 times.
✗ Branch 6 not taken.
|
640 | JSON *json_msgid = JsonDocument::SearchInObject(json_authz, "msgid", |
| 406 | JSON_INT); | ||
| 407 |
2/2✓ Branch 0 taken 80 times.
✓ Branch 1 taken 560 times.
|
640 | if (json_msgid == NULL) { |
| 408 | 80 | LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, | |
| 409 | "\"msgid\" not found in json from authz helper %s", | ||
| 410 | progname_.c_str()); | ||
| 411 | 80 | EnterFailState(); | |
| 412 | 80 | return false; | |
| 413 | } | ||
| 414 | |||
| 415 |
2/2✓ Branch 0 taken 520 times.
✓ Branch 1 taken 40 times.
|
560 | if ((json_msgid->int_value < 0) |
| 416 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 480 times.
|
520 | || (json_msgid->int_value >= kAuthzMsgInvalid)) { |
| 417 | 80 | LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, | |
| 418 | "invalid \"msgid\" in json from authz helper %s: %d", | ||
| 419 | progname_.c_str(), json_msgid->int_value); | ||
| 420 | 80 | EnterFailState(); | |
| 421 | 80 | return false; | |
| 422 | } | ||
| 423 | |||
| 424 | 480 | binary_msg->msgid = static_cast<AuthzExternalMsgIds>(json_msgid->int_value); | |
| 425 | 480 | 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 | 240 | bool AuthzExternalFetcher::ParsePermit(JSON *json_authz, | |
| 434 | AuthzExternalMsg *binary_msg) { | ||
| 435 |
2/4✓ Branch 2 taken 240 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 240 times.
✗ Branch 6 not taken.
|
240 | JSON *json_status = JsonDocument::SearchInObject(json_authz, "status", |
| 436 | JSON_INT); | ||
| 437 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 200 times.
|
240 | if (json_status == NULL) { |
| 438 | 40 | LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, | |
| 439 | "\"status\" not found in json from authz helper %s", | ||
| 440 | progname_.c_str()); | ||
| 441 | 40 | EnterFailState(); | |
| 442 | 40 | return false; | |
| 443 | } | ||
| 444 |
1/2✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
|
200 | if ((json_status->int_value < 0) |
| 445 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 200 times.
|
200 | || (json_status->int_value > kAuthzUnknown)) { |
| 446 | ✗ | binary_msg->permit.status = kAuthzUnknown; | |
| 447 | } else { | ||
| 448 | 200 | binary_msg->permit.status = static_cast<AuthzStatus>( | |
| 449 | 200 | json_status->int_value); | |
| 450 | } | ||
| 451 | |||
| 452 |
2/4✓ Branch 2 taken 200 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 200 times.
✗ Branch 6 not taken.
|
200 | JSON *json_ttl = JsonDocument::SearchInObject(json_authz, "ttl", JSON_INT); |
| 453 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 160 times.
|
200 | if (json_ttl == NULL) { |
| 454 | 40 | LogCvmfs(kLogAuthz, kLogDebug, "no ttl, using default"); | |
| 455 | 40 | binary_msg->permit.ttl = kDefaultTtl; | |
| 456 | } else { | ||
| 457 | 160 | binary_msg->permit.ttl = std::max(kMinTtl, json_ttl->int_value); | |
| 458 | } | ||
| 459 | |||
| 460 |
2/4✓ Branch 2 taken 200 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 200 times.
✗ Branch 6 not taken.
|
200 | JSON *json_token = JsonDocument::SearchInObject(json_authz, "x509_proxy", |
| 461 | JSON_STRING); | ||
| 462 |
2/2✓ Branch 0 taken 80 times.
✓ Branch 1 taken 120 times.
|
200 | if (json_token != NULL) { |
| 463 | 80 | binary_msg->permit.token.type = kTokenX509; | |
| 464 | 80 | string token_binary; | |
| 465 |
2/4✓ Branch 2 taken 80 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 80 times.
✗ Branch 6 not taken.
|
80 | const bool valid_base64 = Debase64(json_token->string_value, &token_binary); |
| 466 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 80 times.
|
80 | 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 | 80 | const unsigned size = token_binary.size(); | |
| 474 | 80 | binary_msg->permit.token.size = size; | |
| 475 |
1/2✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
|
80 | if (size > 0) { |
| 476 | // The token is passed to the AuthzSessionManager, which takes care of | ||
| 477 | // freeing the memory | ||
| 478 | 80 | binary_msg->permit.token.data = smalloc(size); | |
| 479 | 80 | memcpy(binary_msg->permit.token.data, token_binary.data(), size); | |
| 480 | } | ||
| 481 |
1/2✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
|
80 | } |
| 482 | |||
| 483 |
2/4✓ Branch 2 taken 200 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 200 times.
✗ Branch 6 not taken.
|
200 | json_token = JsonDocument::SearchInObject(json_authz, "bearer_token", |
| 484 | JSON_STRING); | ||
| 485 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 200 times.
|
200 | 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 80 times.
✓ Branch 1 taken 120 times.
|
200 | if (binary_msg->permit.token.type == kTokenUnknown) { |
| 508 | // No auth token returned, so authz should do... what exactly? | ||
| 509 | // Log error message | ||
| 510 | 80 | LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, | |
| 511 | "No auth token found in returned JSON from Authz helper %s", | ||
| 512 | progname_.c_str()); | ||
| 513 | } | ||
| 514 | |||
| 515 | 200 | return true; | |
| 516 | } | ||
| 517 | |||
| 518 | |||
| 519 | 400 | bool AuthzExternalFetcher::ParseRevision(JSON *json_authz, | |
| 520 | AuthzExternalMsg *binary_msg) { | ||
| 521 |
2/4✓ Branch 2 taken 400 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 400 times.
✗ Branch 6 not taken.
|
400 | JSON *json_revision = JsonDocument::SearchInObject(json_authz, "revision", |
| 522 | JSON_INT); | ||
| 523 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 400 times.
|
400 | 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 40 times.
✓ Branch 1 taken 360 times.
|
400 | if (json_revision->int_value < 0) { |
| 532 | 40 | LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, | |
| 533 | "invalid \"revision\" in json from authz helper %s: %d", | ||
| 534 | progname_.c_str(), json_revision->int_value); | ||
| 535 | 40 | EnterFailState(); | |
| 536 | 40 | return false; | |
| 537 | } | ||
| 538 | |||
| 539 | 360 | binary_msg->protocol_revision = json_revision->int_value; | |
| 540 | 360 | return true; | |
| 541 | } | ||
| 542 | |||
| 543 | |||
| 544 | 160 | bool AuthzExternalFetcher::Recv(string *msg) { | |
| 545 | uint32_t version; | ||
| 546 |
1/2✓ Branch 1 taken 160 times.
✗ Branch 2 not taken.
|
160 | ssize_t retval = SafeRead(fd_recv_, &version, sizeof(version)); |
| 547 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 120 times.
|
160 | if (retval != static_cast<int>(sizeof(version))) { |
| 548 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | EnterFailState(); |
| 549 | 40 | return false; | |
| 550 | } | ||
| 551 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 80 times.
|
120 | if (version != kProtocolVersion) { |
| 552 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, |
| 553 | "authz helper uses unknown protocol version %u", version); | ||
| 554 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | EnterFailState(); |
| 555 | 40 | return false; | |
| 556 | } | ||
| 557 | |||
| 558 | uint32_t length; | ||
| 559 |
1/2✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
|
80 | retval = SafeRead(fd_recv_, &length, sizeof(length)); |
| 560 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 80 times.
|
80 | if (retval != static_cast<int>(sizeof(length))) { |
| 561 | ✗ | EnterFailState(); | |
| 562 | ✗ | return false; | |
| 563 | } | ||
| 564 | |||
| 565 | 80 | msg->clear(); | |
| 566 | char buf[kPageSize]; | ||
| 567 | 80 | unsigned nbytes = 0; | |
| 568 |
2/2✓ Branch 0 taken 160 times.
✓ Branch 1 taken 80 times.
|
240 | while (nbytes < length) { |
| 569 | 160 | const unsigned remaining = length - nbytes; | |
| 570 |
1/2✓ Branch 2 taken 160 times.
✗ Branch 3 not taken.
|
160 | retval = SafeRead(fd_recv_, buf, std::min(kPageSize, remaining)); |
| 571 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 160 times.
|
160 | 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 | 160 | nbytes += retval; | |
| 578 |
1/2✓ Branch 1 taken 160 times.
✗ Branch 2 not taken.
|
160 | msg->append(buf, retval); |
| 579 | } | ||
| 580 | |||
| 581 | 80 | return true; | |
| 582 | } | ||
| 583 | |||
| 584 | |||
| 585 | 40 | void AuthzExternalFetcher::StripAuthzSchema(const string &membership, | |
| 586 | string *authz_schema, | ||
| 587 | string *pure_membership) { | ||
| 588 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | vector<string> components = SplitString(membership, '%'); |
| 589 |
1/2✓ Branch 2 taken 40 times.
✗ Branch 3 not taken.
|
40 | *authz_schema = components[0]; |
| 590 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | if (components.size() < 2) { |
| 591 |
1/2✓ Branch 2 taken 40 times.
✗ Branch 3 not taken.
|
40 | LogCvmfs(kLogAuthz, kLogDebug, "invalid membership: %s", |
| 592 | membership.c_str()); | ||
| 593 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | *pure_membership = ""; |
| 594 | 40 | return; | |
| 595 | } | ||
| 596 | |||
| 597 | ✗ | components.erase(components.begin()); | |
| 598 | ✗ | *pure_membership = JoinStrings(components, "%"); | |
| 599 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 40 times.
|
40 | } |
| 600 |