GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/authz/authz_fetch.cc Lines: 213 273 78.0 %
Date: 2019-02-03 02:48:13 Branches: 87 138 63.0 %

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 "logging.h"
20
#include "options.h"
21
#include "platform.h"
22
#include "sanitizer.h"
23
#include "smalloc.h"
24
#include "util/pointer.h"
25
#include "util/posix.h"
26
#include "util/string.h"
27
#include "util_concurrency.h"
28
29
using namespace std;  // NOLINT
30
31
32
const int AuthzExternalFetcher::kMinTtl = 0;
33
const uint32_t AuthzExternalFetcher::kProtocolVersion = 1;
34
35
36
136
AuthzExternalFetcher::AuthzExternalFetcher(
37
  const string &fqrn,
38
  const string &progname,
39
  const string &search_path,
40
  OptionsManager *options_manager)
41
  : fqrn_(fqrn)
42
  , progname_(progname)
43
  , search_path_(search_path)
44
  , fd_send_(-1)
45
  , fd_recv_(-1)
46
  , pid_(-1)
47
  , fail_state_(false)
48
  , options_manager_(options_manager)
49
136
  , next_start_(-1)
50
{
51
136
  InitLock();
52
136
}
53
54
75
AuthzExternalFetcher::AuthzExternalFetcher(
55
  const string &fqrn,
56
  int fd_send,
57
  int fd_recv)
58
  : fqrn_(fqrn)
59
  , fd_send_(fd_send)
60
  , fd_recv_(fd_recv)
61
  , pid_(-1)
62
  , fail_state_(false)
63
  , options_manager_(NULL)
64
75
  , next_start_(-1)
65
{
66
75
  InitLock();
67
75
}
68
69
70
422
AuthzExternalFetcher::~AuthzExternalFetcher() {
71
211
  int retval = pthread_mutex_destroy(&lock_);
72
211
  assert(retval == 0);
73
74
  // Allow helper to gracefully terminate
75

211
  if ((fd_send_ >= 0) && !fail_state_) {
76
60
    LogCvmfs(kLogAuthz, kLogDebug, "shutting down authz helper");
77
    Send(string("{\"cvmfs_authz_v1\":{") +
78
      "\"msgid\":" + StringifyInt(kAuthzMsgQuit) + "," +
79
60
      "\"revision\":0}}");
80
  }
81
82
211
  ReapHelper();
83
211
}
84
85
526
void AuthzExternalFetcher::ReapHelper() {
86
  // If we are reaping the helper, we don't try to shut it down again.
87
88
526
  if (fd_send_ >= 0)
89
120
    close(fd_send_);
90
526
  fd_send_ = -1;
91
526
  if (fd_recv_ >= 0)
92
120
    close(fd_recv_);
93
526
  fd_recv_ = -1;
94
95
526
  if (pid_ > 0) {
96
    int retval;
97
45
    uint64_t now = platform_monotonic_time();
98
    int statloc;
99
390591330
    do {
100
390591345
      retval = waitpid(pid_, &statloc, WNOHANG);
101
390591345
      if (platform_monotonic_time() > (now + kChildTimeout)) {
102
        LogCvmfs(kLogAuthz, kLogSyslogWarn | kLogDebug,
103
15
                 "authz helper %s unresponsive, killing", progname_.c_str());
104
15
        retval = kill(pid_, SIGKILL);
105
15
        if (retval == 0) {
106
          // Pick up client return status
107
15
          (void) waitpid(pid_, &statloc, 0);
108
        } else {
109
          // Process might have been terminated just before the kill() call
110
          (void) waitpid(pid_, &statloc, WNOHANG);
111
        }
112
15
        break;
113
      }
114
    } while (retval == 0);
115
45
    pid_ = -1;
116
  }
117
526
}
118
119
120
315
void AuthzExternalFetcher::EnterFailState() {
121
  LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
122
           "authz helper %s enters fail state, no more authorization",
123
315
           progname_.c_str());
124
125
315
  ReapHelper();
126
315
  next_start_ = platform_monotonic_time() + kChildTimeout;
127
315
  fail_state_ = true;
128
315
}
129
130
131
/**
132
 * Uses execve to start progname_.  The started program has stdin and stdout
133
 * connected to fd_send_ and fd_recv_ and the CVMFS_... environment variables
134
 * set.  Special care must be taken when we call fork here in an unknown state
135
 * of the client.  Therefore we can't use ManagedExec (we can't use malloc).
136
 *
137
 * A failed execve is not caught by this routine.  It will be caught in the
138
 * next step, when mother and child start talking.
139
 */
140
60
void AuthzExternalFetcher::ExecHelper() {
141
  int pipe_send[2];
142
  int pipe_recv[2];
143
60
  MakePipe(pipe_send);
144
60
  MakePipe(pipe_recv);
145
60
  char *argv0 = strdupa(progname_.c_str());
146
60
  char *argv[] = {argv0, NULL};
147
148
60
  const bool strip_prefix = true;
149
  vector<string> authz_env =
150
60
    options_manager_->GetEnvironmentSubset("CVMFS_AUTHZ_", strip_prefix);
151
60
  vector<char *> envp;
152
60
  for (unsigned i = 0; i < authz_env.size(); ++i)
153
    envp.push_back(strdupa(authz_env[i].c_str()));
154
60
  envp.push_back(strdupa("CVMFS_AUTHZ_HELPER=yes"));
155
60
  envp.push_back(NULL);
156
157
60
  int max_fd = sysconf(_SC_OPEN_MAX);
158
60
  assert(max_fd > 0);
159
  LogCvmfs(kLogAuthz, kLogDebug | kLogSyslog, "starting authz helper %s",
160
60
           argv0);
161
162
60
  pid_t pid = fork();
163
60
  if (pid == 0) {
164
    // Child process, close file descriptors and run the helper
165
    int retval = dup2(pipe_send[0], 0);
166
    assert(retval == 0);
167
    retval = dup2(pipe_recv[1], 1);
168
    assert(retval == 1);
169
    for (int fd = 2; fd < max_fd; fd++)
170
      close(fd);
171
172
    execve(argv0, argv, &envp[0]);
173
    syslog(LOG_USER | LOG_ERR, "failed to start authz helper %s (%d)",
174
           argv0, errno);
175
    abort();
176
  }
177
60
  assert(pid > 0);
178
60
  close(pipe_send[0]);
179
60
  close(pipe_recv[1]);
180
181
  // Don't receive a signal if the helper terminates
182
60
  signal(SIGPIPE, SIG_IGN);
183
60
  pid_ = pid;
184
60
  fd_send_ = pipe_send[1];
185
60
  fd_recv_ = pipe_recv[0];
186
60
}
187
188
189
15
AuthzStatus AuthzExternalFetcher::Fetch(
190
  const QueryInfo &query_info,
191
  AuthzToken *authz_token,
192
  unsigned *ttl)
193
{
194
15
  *ttl = kDefaultTtl;
195
196
15
  MutexLockGuard lock_guard(lock_);
197
15
  if (fail_state_) {
198
    uint64_t now = platform_monotonic_time();
199
    if (now > next_start_) {
200
      fail_state_ = false;
201
    } else {
202
      return kAuthzNoHelper;
203
    }
204
  }
205
206
  bool retval;
207
208
15
  if (fd_send_ < 0) {
209
    if (progname_.empty())
210
      progname_ = FindHelper(query_info.membership);
211
    ExecHelper();
212
    retval = Handshake();
213
    if (!retval)
214
      return kAuthzNoHelper;
215
  }
216

15
  assert((fd_send_ >= 0) && (fd_recv_ >= 0));
217
218
15
  string authz_schema;
219
15
  string pure_membership;
220
15
  StripAuthzSchema(query_info.membership, &authz_schema, &pure_membership);
221
  string json_msg = string("{\"cvmfs_authz_v1\":{") +
222
    "\"msgid\":" + StringifyInt(kAuthzMsgVerify) + "," +
223
    "\"revision\":0," +
224
    "\"uid\":" +  StringifyInt(query_info.uid) + "," +
225
    "\"gid\":" +  StringifyInt(query_info.gid) + "," +
226
    "\"pid\":" +  StringifyInt(query_info.pid) + "," +
227
    "\"membership\":\"" + Base64(pure_membership) +
228
15
      "\"}}";
229

15
  retval = Send(json_msg) && Recv(&json_msg);
230
15
  if (!retval)
231
    return kAuthzNoHelper;
232
15
  AuthzExternalMsg binary_msg;
233
15
  retval = ParseMsg(json_msg, kAuthzMsgPermit, &binary_msg);
234
15
  if (!retval)
235
    return kAuthzNoHelper;
236
237
15
  *ttl = binary_msg.permit.ttl;
238
15
  if (binary_msg.permit.status == kAuthzOk) {
239
15
    *authz_token = binary_msg.permit.token;
240
    LogCvmfs(kLogAuthz, kLogDebug, "got token of type %d and size %u",
241
15
             binary_msg.permit.token.type, binary_msg.permit.token.size);
242
  }
243
244
15
  return binary_msg.permit.status;
245
}
246
247
248
string AuthzExternalFetcher::FindHelper(const string &membership) {
249
  string authz_schema;
250
  string pure_membership;
251
  StripAuthzSchema(membership, &authz_schema, &pure_membership);
252
  sanitizer::AuthzSchemaSanitizer sanitizer;
253
  if (!sanitizer.IsValid(authz_schema)) {
254
    LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, "invalid authz schema: %s",
255
             authz_schema.c_str());
256
    return "";
257
  }
258
259
  string exe_path = search_path_ + "/cvmfs_" + authz_schema + "_helper";
260
  if (!FileExists(exe_path)) {
261
    LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, "authz helper %s missing",
262
             exe_path.c_str());
263
  }
264
  return exe_path;
265
}
266
267
268
/**
269
 * Establish communication link with a forked authz helper.
270
 */
271
30
bool AuthzExternalFetcher::Handshake() {
272
30
  string debug_log = GetLogDebugFile();
273
30
  string json_debug_log;
274
30
  if (debug_log != "")
275
    json_debug_log = ",\"debug_log\":\"" + debug_log + "\"";
276
  string json_msg = string("{") +
277
    "\"cvmfs_authz_v1\":{" +
278
    "\"msgid\":" + StringifyInt(0) + "," +
279
    "\"revision\":0," +
280
    "\"fqrn\":\"" + fqrn_ + "\"," +
281
    "\"syslog_facility\":" + StringifyInt(GetLogSyslogFacility()) + "," +
282
    "\"syslog_level\":" + StringifyInt(GetLogSyslogLevel()) +
283
    json_debug_log +
284
30
    "}}";
285
30
  bool retval = Send(json_msg);
286
30
  if (!retval)
287
15
    return false;
288
289
15
  retval = Recv(&json_msg);
290
15
  if (!retval)
291
    return false;
292
15
  AuthzExternalMsg binary_msg;
293
15
  retval = ParseMsg(json_msg, kAuthzMsgReady, &binary_msg);
294
15
  if (!retval)
295
    return false;
296
297
15
  return true;
298
}
299
300
301
211
void AuthzExternalFetcher::InitLock() {
302
211
  int retval = pthread_mutex_init(&lock_, NULL);
303
211
  assert(retval == 0);
304
211
}
305
306
307
150
bool AuthzExternalFetcher::Send(const string &msg) {
308
  // Line format: 4 byte protocol version, 4 byte length, message
309
  struct {
310
    uint32_t version;
311
    uint32_t length;
312
  } header;
313
150
  header.version = kProtocolVersion;
314
150
  header.length = msg.length();
315
150
  unsigned raw_length = sizeof(header) + msg.length();
316
  unsigned char *raw_msg = reinterpret_cast<unsigned char *>(
317
150
    alloca(raw_length));
318
150
  memcpy(raw_msg, &header, sizeof(header));
319
150
  memcpy(raw_msg + sizeof(header), msg.data(), header.length);
320
321
150
  bool retval = SafeWrite(fd_send_, raw_msg, raw_length);
322
150
  if (!retval)
323
30
    EnterFailState();
324
150
  return retval;
325
}
326
327
328
/**
329
 * We want to see valid JSON in the form
330
 * { "cvmfs_authz_v1" : {
331
 *     "msgid":
332
 *     "revision":
333
 *     ...
334
 *   }
335
 *   ...
336
 * }
337
 *
338
 * The contents of "cvmfs_authz_v1" depends on the msgid.  Additional fields
339
 * are ignored.  The protocol revision should indicate changes in the fields.
340
 */
341
285
bool AuthzExternalFetcher::ParseMsg(
342
  const std::string &json_msg,
343
  const AuthzExternalMsgIds expected_msgid,
344
  AuthzExternalMsg *binary_msg)
345
{
346
285
  assert(binary_msg != NULL);
347
348
285
  UniquePtr<JsonDocument> json_document(JsonDocument::Create(json_msg));
349
285
  if (!json_document.IsValid()) {
350
    LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
351
             "invalid json from authz helper %s: %s",
352
30
             progname_.c_str(), json_msg.c_str());
353
30
    EnterFailState();
354
30
    return false;
355
  }
356
357
  JSON *json_authz = JsonDocument::SearchInObject(
358
255
    json_document->root(), "cvmfs_authz_v1", JSON_OBJECT);
359
255
  if (json_authz == NULL) {
360
    LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
361
             "\"cvmfs_authz_v1\" not found in json from authz helper %s: %s",
362
15
             progname_.c_str(), json_msg.c_str());
363
15
    EnterFailState();
364
15
    return false;
365
  }
366
367

240
  if (!ParseMsgId(json_authz, binary_msg) ||
368
      (binary_msg->msgid != expected_msgid))
369
  {
370
90
    EnterFailState();
371
90
    return false;
372
  }
373
150
  if (!ParseRevision(json_authz, binary_msg)) {
374
15
    EnterFailState();
375
15
    return false;
376
  }
377
135
  if (binary_msg->msgid == kAuthzMsgPermit) {
378
90
    if (!ParsePermit(json_authz, binary_msg)) {
379
15
      EnterFailState();
380
15
      return false;
381
    }
382
  }
383
120
  return true;
384
}
385
386
387
240
bool AuthzExternalFetcher::ParseMsgId(
388
  JSON *json_authz,
389
  AuthzExternalMsg *binary_msg)
390
{
391
  JSON *json_msgid = JsonDocument::SearchInObject(
392
240
    json_authz, "msgid", JSON_INT);
393
240
  if (json_msgid == NULL) {
394
    LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
395
             "\"msgid\" not found in json from authz helper %s",
396
30
             progname_.c_str());
397
30
    EnterFailState();
398
30
    return false;
399
  }
400
401

210
  if ((json_msgid->int_value < 0) ||
402
      (json_msgid->int_value >= kAuthzMsgInvalid))
403
  {
404
    LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
405
             "invalid \"msgid\" in json from authz helper %s: %d",
406
30
             progname_.c_str(), json_msgid->int_value);
407
30
    EnterFailState();
408
30
    return false;
409
  }
410
411
180
  binary_msg->msgid = static_cast<AuthzExternalMsgIds>(json_msgid->int_value);
412
180
  return true;
413
}
414
415
416
/**
417
 * A permit must contain the authorization status.  Optionally it can come with
418
 * a "time to live" of the answer and a token (e.g. X.509 proxy certificate).
419
 */
420
90
bool AuthzExternalFetcher::ParsePermit(
421
  JSON *json_authz,
422
  AuthzExternalMsg *binary_msg)
423
{
424
  JSON *json_status =
425
90
    JsonDocument::SearchInObject(json_authz, "status", JSON_INT);
426
90
  if (json_status == NULL) {
427
    LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
428
             "\"status\" not found in json from authz helper %s",
429
15
             progname_.c_str());
430
15
    EnterFailState();
431
15
    return false;
432
  }
433

75
  if ((json_status->int_value < 0) || (json_status->int_value > kAuthzUnknown))
434
  {
435
    binary_msg->permit.status = kAuthzUnknown;
436
  } else {
437
    binary_msg->permit.status = static_cast<AuthzStatus>(
438
75
      json_status->int_value);
439
  }
440
441
75
  JSON *json_ttl = JsonDocument::SearchInObject(json_authz, "ttl", JSON_INT);
442
75
  if (json_ttl == NULL) {
443
15
    LogCvmfs(kLogAuthz, kLogDebug, "no ttl, using default");
444
15
    binary_msg->permit.ttl = kDefaultTtl;
445
  } else {
446
60
    binary_msg->permit.ttl = std::max(kMinTtl, json_ttl->int_value);
447
  }
448
449
  JSON *json_token =
450
75
    JsonDocument::SearchInObject(json_authz, "x509_proxy", JSON_STRING);
451
75
  if (json_token != NULL) {
452
30
    binary_msg->permit.token.type = kTokenX509;
453
30
    string token_binary;
454
30
    bool valid_base64 = Debase64(json_token->string_value, &token_binary);
455
30
    if (!valid_base64) {
456
      LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
457
               "invalid Base64 in 'x509_proxy' from authz helper %s",
458
               progname_.c_str());
459
      EnterFailState();
460
      return false;
461
    }
462
30
    unsigned size = token_binary.size();
463
30
    binary_msg->permit.token.size = size;
464
30
    if (size > 0) {
465
      // The token is passed to the AuthzSessionManager, which takes care of
466
      // freeing the memory
467
30
      binary_msg->permit.token.data = smalloc(size);
468
30
      memcpy(binary_msg->permit.token.data, token_binary.data(), size);
469
    }
470
  }
471
472
  json_token = JsonDocument::SearchInObject(json_authz, "bearer_token",
473
75
                                            JSON_STRING);
474
75
  if (json_token != NULL) {
475
    binary_msg->permit.token.type = kTokenBearer;
476
    unsigned size = strlen(json_token->string_value);
477
    binary_msg->permit.token.size = size;
478
    if (size > 0) {
479
      // The token is passed to the AuthzSessionManager, which takes care of
480
      // freeing the memory
481
      binary_msg->permit.token.data = smalloc(size);
482
      memcpy(binary_msg->permit.token.data, json_token->string_value, size);
483
484
      LogCvmfs(kLogAuthz, kLogDebug,
485
               "Got a bearer_token from authz_helper. "
486
               "Setting token type to kTokenBearer");
487
    } else {
488
      // We got a bearer_token, but a size 0 (or negative?) string
489
      LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
490
               "bearer_token was in returned JSON from Authz helper,"
491
               " but of size 0 from authz helper %s",
492
               progname_.c_str());
493
    }
494
  }
495
496
75
  if (binary_msg->permit.token.type == kTokenUnknown) {
497
    // No auth token returned, so authz should do... what exactly?
498
    // Log error message
499
    LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
500
               "No auth token found in returned JSON from Authz helper %s",
501
30
               progname_.c_str());
502
  }
503
504
75
  return true;
505
}
506
507
508
150
bool AuthzExternalFetcher::ParseRevision(
509
  JSON *json_authz,
510
  AuthzExternalMsg *binary_msg)
511
{
512
  JSON *json_revision = JsonDocument::SearchInObject(
513
150
    json_authz, "revision", JSON_INT);
514
150
  if (json_revision == NULL) {
515
    LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
516
             "\"revision\" not found in json from authz helper %s",
517
             progname_.c_str());
518
    EnterFailState();
519
    return false;
520
  }
521
522
150
  if (json_revision->int_value < 0) {
523
    LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
524
             "invalid \"revision\" in json from authz helper %s: %d",
525
15
             progname_.c_str(), json_revision->int_value);
526
15
    EnterFailState();
527
15
    return false;
528
  }
529
530
135
  binary_msg->protocol_revision = json_revision->int_value;
531
135
  return true;
532
}
533
534
535
60
bool AuthzExternalFetcher::Recv(string *msg) {
536
  uint32_t version;
537
60
  ssize_t retval = SafeRead(fd_recv_, &version, sizeof(version));
538
60
  if (retval != static_cast<int>(sizeof(version))) {
539
15
    EnterFailState();
540
15
    return false;
541
  }
542
45
  if (version != kProtocolVersion) {
543
    LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
544
15
             "authz helper uses unknown protocol version %u", version);
545
15
    EnterFailState();
546
15
    return false;
547
  }
548
549
  uint32_t length;
550
30
  retval = SafeRead(fd_recv_, &length, sizeof(length));
551
30
  if (retval != static_cast<int>(sizeof(length))) {
552
    EnterFailState();
553
    return false;
554
  }
555
556
30
  msg->clear();
557
  char buf[kPageSize];
558
30
  unsigned nbytes = 0;
559
120
  while (nbytes < length) {
560
60
    const unsigned remaining = length - nbytes;
561
60
    retval = SafeRead(fd_recv_, buf, std::min(kPageSize, remaining));
562
60
    if (retval < 0) {
563
      LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
564
               "read failure from authz helper %s", progname_.c_str());
565
      EnterFailState();
566
      return false;
567
    }
568
60
    nbytes += retval;
569
60
    msg->append(buf, retval);
570
  }
571
572
30
  return true;
573
}
574
575
576
15
void AuthzExternalFetcher::StripAuthzSchema(
577
  const string &membership,
578
  string *authz_schema,
579
  string *pure_membership)
580
{
581
15
  vector<string> components = SplitString(membership, '%');
582
15
  *authz_schema = components[0];
583
15
  if (components.size() < 2) {
584
    LogCvmfs(kLogAuthz, kLogDebug, "invalid membership: %s",
585
15
             membership.c_str());
586
15
    *pure_membership = "";
587
    return;
588
  }
589
590
  components.erase(components.begin());
591
  *pure_membership = JoinStrings(components, "%");
592
}