CernVM-FS  2.12.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
authz_fetch.cc
Go to the documentation of this file.
1 
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;
35 
36 
38  const string &fqrn,
39  const string &progname,
40  const string &search_path,
41  OptionsManager *options_manager)
42  : fqrn_(fqrn)
43  , progname_(progname)
44  , search_path_(search_path)
45  , fd_send_(-1)
46  , fd_recv_(-1)
47  , pid_(-1)
48  , fail_state_(false)
49  , options_manager_(options_manager)
50  , next_start_(-1)
51 {
52  InitLock();
53 }
54 
56  const string &fqrn,
57  int fd_send,
58  int fd_recv)
59  : fqrn_(fqrn)
60  , fd_send_(fd_send)
61  , fd_recv_(fd_recv)
62  , pid_(-1)
63  , fail_state_(false)
64  , options_manager_(NULL)
65  , next_start_(-1)
66 {
67  InitLock();
68 }
69 
70 
72  int retval = pthread_mutex_destroy(&lock_);
73  assert(retval == 0);
74 
75  // Allow helper to gracefully terminate
76  if ((fd_send_ >= 0) && !fail_state_) {
77  LogCvmfs(kLogAuthz, kLogDebug, "shutting down authz helper");
78  Send(string("{\"cvmfs_authz_v1\":{") +
79  "\"msgid\":" + StringifyInt(kAuthzMsgQuit) + "," +
80  "\"revision\":0}}");
81  }
82 
83  ReapHelper();
84 }
85 
87  // If we are reaping the helper, we don't try to shut it down again.
88 
89  if (fd_send_ >= 0)
90  close(fd_send_);
91  fd_send_ = -1;
92  if (fd_recv_ >= 0)
93  close(fd_recv_);
94  fd_recv_ = -1;
95 
96  if (pid_ > 0) {
97  int retval;
98  uint64_t now = platform_monotonic_time();
99  int statloc;
100  do {
101  retval = waitpid(pid_, &statloc, WNOHANG);
102  if (platform_monotonic_time() > (now + kChildTimeout)) {
104  "authz helper %s unresponsive, killing", progname_.c_str());
105  retval = kill(pid_, SIGKILL);
106  if (retval == 0) {
107  // Pick up client return status
108  (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  break;
114  }
115  } while (retval == 0);
116  pid_ = -1;
117  }
118 }
119 
120 
123  "authz helper %s enters fail state, no more authorization",
124  progname_.c_str());
125 
126  ReapHelper();
128  fail_state_ = true;
129 }
130 
131 
142  int pipe_send[2];
143  int pipe_recv[2];
144  MakePipe(pipe_send);
145  MakePipe(pipe_recv);
146  char *argv0 = strdupa(progname_.c_str());
147  char *argv[] = {argv0, NULL};
148 
149  const bool strip_prefix = true;
150  vector<string> authz_env =
151  options_manager_->GetEnvironmentSubset("CVMFS_AUTHZ_", strip_prefix);
152  vector<char *> envp;
153  for (unsigned i = 0; i < authz_env.size(); ++i)
154  envp.push_back(strdupa(authz_env[i].c_str()));
155  envp.push_back(strdupa("CVMFS_AUTHZ_HELPER=yes"));
156  envp.push_back(NULL);
157 
158 #ifdef __APPLE__
159  int max_fd = sysconf(_SC_OPEN_MAX);
160  assert(max_fd > 0);
161 #else
162  std::vector<int> open_fds;
163  DIR *dirp = opendir("/proc/self/fd");
164  assert(dirp);
165  platform_dirent64 *dirent;
166  while ((dirent = platform_readdir(dirp))) {
167  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  if (!String2Uint64Parse(name, &name_uint64))
171  continue;
172  if (name_uint64 < 2)
173  continue;
174  open_fds.push_back(static_cast<int>(name_uint64));
175  }
176  closedir(dirp);
177 #endif
178  LogCvmfs(kLogAuthz, kLogDebug | kLogSyslog, "starting authz helper %s",
179  argv0);
180 
181  pid_t pid = fork();
182  if (pid == 0) {
183  // Child process, close file descriptors and run the helper
184  int retval = dup2(pipe_send[0], 0);
185  assert(retval == 0);
186  retval = dup2(pipe_recv[1], 1);
187  assert(retval == 1);
188 #ifdef __APPLE__
189  for (int fd = 2; fd < max_fd; fd++)
190  close(fd);
191 #else
192  for (unsigned i = 0; i < open_fds.size(); ++i)
193  close(open_fds[i]);
194 #endif
195 
196  for (size_t i = 0; i < sizeof(Watchdog::g_suppressed_signals)/sizeof(int);
197  i++) {
198  struct sigaction signal_handler;
199  signal_handler.sa_handler = SIG_DFL;
200  sigaction(Watchdog::g_suppressed_signals[i], &signal_handler, NULL);
201  }
202 
203  execve(argv0, argv, &envp[0]);
204  syslog(LOG_USER | LOG_ERR, "failed to start authz helper %s (%d)",
205  argv0, errno);
206  _exit(1);
207  }
208  assert(pid > 0);
209  close(pipe_send[0]);
210  close(pipe_recv[1]);
211 
212  // Don't receive a signal if the helper terminates
213  signal(SIGPIPE, SIG_IGN);
214  pid_ = pid;
215  fd_send_ = pipe_send[1];
216  fd_recv_ = pipe_recv[0];
217 }
218 
219 
221  const QueryInfo &query_info,
222  AuthzToken *authz_token,
223  unsigned *ttl)
224 {
225  *ttl = kDefaultTtl;
226 
227  MutexLockGuard lock_guard(lock_);
228  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  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  assert((fd_send_ >= 0) && (fd_recv_ >= 0));
248 
249  string authz_schema;
250  string pure_membership;
251  StripAuthzSchema(query_info.membership, &authz_schema, &pure_membership);
252  string json_msg = string("{\"cvmfs_authz_v1\":{") +
253  "\"msgid\":" + StringifyInt(kAuthzMsgVerify) + "," +
254  "\"revision\":0," +
255  "\"uid\":" + StringifyInt(query_info.uid) + "," +
256  "\"gid\":" + StringifyInt(query_info.gid) + "," +
257  "\"pid\":" + StringifyInt(query_info.pid) + "," +
258  "\"membership\":\"" + Base64(pure_membership) +
259  "\"}}";
260  retval = Send(json_msg) && Recv(&json_msg);
261  if (!retval)
262  return kAuthzNoHelper;
263  AuthzExternalMsg binary_msg;
264  retval = ParseMsg(json_msg, kAuthzMsgPermit, &binary_msg);
265  if (!retval)
266  return kAuthzNoHelper;
267 
268  *ttl = binary_msg.permit.ttl;
269  if (binary_msg.permit.status == kAuthzOk) {
270  *authz_token = binary_msg.permit.token;
271  LogCvmfs(kLogAuthz, kLogDebug, "got token of type %d and size %u",
272  binary_msg.permit.token.type, binary_msg.permit.token.size);
273  }
274 
275  return binary_msg.permit.status;
276 }
277 
278 
279 string AuthzExternalFetcher::FindHelper(const string &membership) {
280  string authz_schema;
281  string pure_membership;
282  StripAuthzSchema(membership, &authz_schema, &pure_membership);
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 
303  string debug_log = GetLogDebugFile();
304  string json_debug_log;
305  if (debug_log != "")
306  json_debug_log = ",\"debug_log\":\"" + debug_log + "\"";
307  string json_msg = string("{") +
308  "\"cvmfs_authz_v1\":{" +
309  "\"msgid\":" + StringifyInt(0) + "," +
310  "\"revision\":0," +
311  "\"fqrn\":\"" + fqrn_ + "\"," +
312  "\"syslog_facility\":" + StringifyInt(GetLogSyslogFacility()) + "," +
313  "\"syslog_level\":" + StringifyInt(GetLogSyslogLevel()) +
314  json_debug_log +
315  "}}";
316  bool retval = Send(json_msg);
317  if (!retval)
318  return false;
319 
320  retval = Recv(&json_msg);
321  if (!retval)
322  return false;
323  AuthzExternalMsg binary_msg;
324  retval = ParseMsg(json_msg, kAuthzMsgReady, &binary_msg);
325  if (!retval)
326  return false;
327 
328  return true;
329 }
330 
331 
333  int retval = pthread_mutex_init(&lock_, NULL);
334  assert(retval == 0);
335 }
336 
337 
338 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  header.version = kProtocolVersion;
345  header.length = msg.length();
346  unsigned raw_length = sizeof(header) + msg.length();
347  unsigned char *raw_msg = reinterpret_cast<unsigned char *>(
348  alloca(raw_length));
349  memcpy(raw_msg, &header, sizeof(header));
350  memcpy(raw_msg + sizeof(header), msg.data(), header.length);
351 
352  bool retval = SafeWrite(fd_send_, raw_msg, raw_length);
353  if (!retval)
354  EnterFailState();
355  return retval;
356 }
357 
358 
373  const std::string &json_msg,
374  const AuthzExternalMsgIds expected_msgid,
375  AuthzExternalMsg *binary_msg)
376 {
377  assert(binary_msg != NULL);
378 
379  UniquePtr<JsonDocument> json_document(JsonDocument::Create(json_msg));
380  if (!json_document.IsValid()) {
382  "invalid json from authz helper %s: %s",
383  progname_.c_str(), json_msg.c_str());
384  EnterFailState();
385  return false;
386  }
387 
388  JSON *json_authz = JsonDocument::SearchInObject(
389  json_document->root(), "cvmfs_authz_v1", JSON_OBJECT);
390  if (json_authz == NULL) {
392  "\"cvmfs_authz_v1\" not found in json from authz helper %s: %s",
393  progname_.c_str(), json_msg.c_str());
394  EnterFailState();
395  return false;
396  }
397 
398  if (!ParseMsgId(json_authz, binary_msg) ||
399  (binary_msg->msgid != expected_msgid))
400  {
401  EnterFailState();
402  return false;
403  }
404  if (!ParseRevision(json_authz, binary_msg)) {
405  EnterFailState();
406  return false;
407  }
408  if (binary_msg->msgid == kAuthzMsgPermit) {
409  if (!ParsePermit(json_authz, binary_msg)) {
410  EnterFailState();
411  return false;
412  }
413  }
414  return true;
415 }
416 
417 
419  JSON *json_authz,
420  AuthzExternalMsg *binary_msg)
421 {
422  JSON *json_msgid = JsonDocument::SearchInObject(
423  json_authz, "msgid", JSON_INT);
424  if (json_msgid == NULL) {
426  "\"msgid\" not found in json from authz helper %s",
427  progname_.c_str());
428  EnterFailState();
429  return false;
430  }
431 
432  if ((json_msgid->int_value < 0) ||
433  (json_msgid->int_value >= kAuthzMsgInvalid))
434  {
436  "invalid \"msgid\" in json from authz helper %s: %d",
437  progname_.c_str(), json_msgid->int_value);
438  EnterFailState();
439  return false;
440  }
441 
442  binary_msg->msgid = static_cast<AuthzExternalMsgIds>(json_msgid->int_value);
443  return true;
444 }
445 
446 
452  JSON *json_authz,
453  AuthzExternalMsg *binary_msg)
454 {
455  JSON *json_status =
456  JsonDocument::SearchInObject(json_authz, "status", JSON_INT);
457  if (json_status == NULL) {
459  "\"status\" not found in json from authz helper %s",
460  progname_.c_str());
461  EnterFailState();
462  return false;
463  }
464  if ((json_status->int_value < 0) || (json_status->int_value > kAuthzUnknown))
465  {
466  binary_msg->permit.status = kAuthzUnknown;
467  } else {
468  binary_msg->permit.status = static_cast<AuthzStatus>(
469  json_status->int_value);
470  }
471 
472  JSON *json_ttl = JsonDocument::SearchInObject(json_authz, "ttl", JSON_INT);
473  if (json_ttl == NULL) {
474  LogCvmfs(kLogAuthz, kLogDebug, "no ttl, using default");
475  binary_msg->permit.ttl = kDefaultTtl;
476  } else {
477  binary_msg->permit.ttl = std::max(kMinTtl, json_ttl->int_value);
478  }
479 
480  JSON *json_token =
481  JsonDocument::SearchInObject(json_authz, "x509_proxy", JSON_STRING);
482  if (json_token != NULL) {
483  binary_msg->permit.token.type = kTokenX509;
484  string token_binary;
485  bool valid_base64 = Debase64(json_token->string_value, &token_binary);
486  if (!valid_base64) {
488  "invalid Base64 in 'x509_proxy' from authz helper %s",
489  progname_.c_str());
490  EnterFailState();
491  return false;
492  }
493  unsigned size = token_binary.size();
494  binary_msg->permit.token.size = size;
495  if (size > 0) {
496  // The token is passed to the AuthzSessionManager, which takes care of
497  // freeing the memory
498  binary_msg->permit.token.data = smalloc(size);
499  memcpy(binary_msg->permit.token.data, token_binary.data(), size);
500  }
501  }
502 
503  json_token = JsonDocument::SearchInObject(json_authz, "bearer_token",
504  JSON_STRING);
505  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 
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
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  if (binary_msg->permit.token.type == kTokenUnknown) {
528  // No auth token returned, so authz should do... what exactly?
529  // Log error message
531  "No auth token found in returned JSON from Authz helper %s",
532  progname_.c_str());
533  }
534 
535  return true;
536 }
537 
538 
540  JSON *json_authz,
541  AuthzExternalMsg *binary_msg)
542 {
543  JSON *json_revision = JsonDocument::SearchInObject(
544  json_authz, "revision", JSON_INT);
545  if (json_revision == NULL) {
547  "\"revision\" not found in json from authz helper %s",
548  progname_.c_str());
549  EnterFailState();
550  return false;
551  }
552 
553  if (json_revision->int_value < 0) {
555  "invalid \"revision\" in json from authz helper %s: %d",
556  progname_.c_str(), json_revision->int_value);
557  EnterFailState();
558  return false;
559  }
560 
561  binary_msg->protocol_revision = json_revision->int_value;
562  return true;
563 }
564 
565 
566 bool AuthzExternalFetcher::Recv(string *msg) {
567  uint32_t version;
568  ssize_t retval = SafeRead(fd_recv_, &version, sizeof(version));
569  if (retval != static_cast<int>(sizeof(version))) {
570  EnterFailState();
571  return false;
572  }
573  if (version != kProtocolVersion) {
575  "authz helper uses unknown protocol version %u", version);
576  EnterFailState();
577  return false;
578  }
579 
580  uint32_t length;
581  retval = SafeRead(fd_recv_, &length, sizeof(length));
582  if (retval != static_cast<int>(sizeof(length))) {
583  EnterFailState();
584  return false;
585  }
586 
587  msg->clear();
588  char buf[kPageSize];
589  unsigned nbytes = 0;
590  while (nbytes < length) {
591  const unsigned remaining = length - nbytes;
592  retval = SafeRead(fd_recv_, buf, std::min(kPageSize, remaining));
593  if (retval < 0) {
595  "read failure from authz helper %s", progname_.c_str());
596  EnterFailState();
597  return false;
598  }
599  nbytes += retval;
600  msg->append(buf, retval);
601  }
602 
603  return true;
604 }
605 
606 
608  const string &membership,
609  string *authz_schema,
610  string *pure_membership)
611 {
612  vector<string> components = SplitString(membership, '%');
613  *authz_schema = components[0];
614  if (components.size() < 2) {
615  LogCvmfs(kLogAuthz, kLogDebug, "invalid membership: %s",
616  membership.c_str());
617  *pure_membership = "";
618  return;
619  }
620 
621  components.erase(components.begin());
622  *pure_membership = JoinStrings(components, "%");
623 }
AuthzExternalMsgIds msgid
Definition: authz_fetch.h:84
std::string search_path_
Definition: authz_fetch.h:175
AuthzExternalFetcher(const std::string &fqrn, const std::string &progname, const std::string &search_path, OptionsManager *options_manager)
std::string membership
Definition: authz_fetch.h:29
static JSON * SearchInObject(const JSON *json_object, const std::string &name, const json_type type)
Definition: authz.h:39
static const unsigned kChildTimeout
Definition: authz_fetch.h:129
string JoinStrings(const vector< string > &strings, const string &joint)
Definition: string.cc:325
Helper: &quot;I verified, cvmfs, here&#39;s the result&quot;.
Definition: authz_fetch.h:73
void * data
Definition: authz.h:33
void StripAuthzSchema(const std::string &membership, std::string *authz_schema, std::string *pure_membership)
Definition: authz_fetch.cc:607
std::vector< std::string > GetEnvironmentSubset(const std::string &key_prefix, bool strip_prefix)
Definition: options.cc:434
pthread_mutex_t lock_
Definition: authz_fetch.h:206
bool SafeWrite(int fd, const void *buf, size_t nbyte)
Definition: posix.cc:1986
assert((mem||(size==0))&&"Out Of Memory")
bool Debase64(const string &data, string *decoded)
Definition: string.cc:564
void MakePipe(int pipe_fd[2])
Definition: posix.cc:492
bool ParseMsgId(JSON *json_authz, AuthzExternalMsg *binary_msg)
Definition: authz_fetch.cc:418
static const uint32_t kProtocolVersion
Definition: authz_fetch.h:112
Helper: &quot;Yes, cvmfs, I&#39;m here&quot;.
Definition: authz_fetch.h:71
std::string progname_
Definition: authz_fetch.h:170
AuthzToken token
Definition: authz_fetch.h:88
bool String2Uint64Parse(const std::string &value, uint64_t *result)
Definition: string.cc:245
OptionsManager * options_manager_
Definition: authz_fetch.h:201
bool IsValid(const std::string &input) const
Definition: sanitizer.cc:114
bool ParsePermit(JSON *json_authz, AuthzExternalMsg *binary_msg)
Definition: authz_fetch.cc:451
bool FileExists(const std::string &path)
Definition: posix.cc:791
pid_t pid_
Definition: cvmfs.cc:153
int GetLogSyslogLevel()
Definition: logging.cc:168
bool Recv(std::string *msg)
Definition: authz_fetch.cc:566
#define strdupa(s)
Definition: platform_osx.h:286
#define GetLogDebugFile()
ssize_t SafeRead(int fd, void *buf, size_t nbyte)
Definition: posix.cc:2045
static JsonDocument * Create(const std::string &text)
vector< string > SplitString(const string &str, char delim)
Definition: string.cc:290
AuthzTokenType type
Definition: authz.h:32
static int g_suppressed_signals[13]
Definition: monitor.h:48
std::string FindHelper(const std::string &membership)
Definition: authz_fetch.cc:279
Cvmfs: &quot;Please verify, helper&quot;.
Definition: authz_fetch.h:72
string StringifyInt(const int64_t value)
Definition: string.cc:78
AuthzStatus status
Definition: authz_fetch.h:87
uint64_t platform_monotonic_time()
struct AuthzExternalMsg::@0 permit
bool ParseRevision(JSON *json_authz, AuthzExternalMsg *binary_msg)
Definition: authz_fetch.cc:539
unsigned size
Definition: authz.h:34
virtual ~AuthzExternalFetcher()
Definition: authz_fetch.cc:71
static const int kMinTtl
Definition: authz_fetch.h:134
virtual AuthzStatus Fetch(const QueryInfo &query_info, AuthzToken *authz_token, unsigned *ttl)
Definition: authz_fetch.cc:220
bool ParseMsg(const std::string &json_msg, const AuthzExternalMsgIds expected_msgid, AuthzExternalMsg *binary_msg)
Definition: authz_fetch.cc:372
bool Send(const std::string &msg)
Definition: authz_fetch.cc:338
string Base64(const string &data)
Definition: string.cc:504
BashOptionsManager options_manager_
Definition: mount.cvmfs.cc:34
Cvmfs: &quot;Please shutdown, helper&quot;.
Definition: authz_fetch.h:74
const unsigned kPageSize
Definition: posix.h:30
Definition: mutex.h:42
platform_dirent64 * platform_readdir(DIR *dirp)
AuthzExternalMsgIds
Definition: authz_fetch.h:69
static const unsigned kDefaultTtl
Definition: authz_fetch.h:139
First invalid message id.
Definition: authz_fetch.h:75
struct json_value JSON
Definition: helper_allow.cc:11
static void size_t size
Definition: smalloc.h:54
AuthzStatus
Definition: authz.h:38
const JSON * root() const
Definition: json_document.h:25
struct dirent64 platform_dirent64
int GetLogSyslogFacility()
Definition: logging.cc:214
CVMFS_EXPORT void LogCvmfs(const LogSource source, const int mask, const char *format,...)
Definition: logging.cc:528