CernVM-FS  2.13.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 &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  , next_start_(-1) {
50  InitLock();
51 }
52 
54  int fd_send,
55  int fd_recv)
56  : fqrn_(fqrn)
57  , fd_send_(fd_send)
58  , fd_recv_(fd_recv)
59  , pid_(-1)
60  , fail_state_(false)
61  , options_manager_(NULL)
62  , next_start_(-1) {
63  InitLock();
64 }
65 
66 
68  const int retval = pthread_mutex_destroy(&lock_);
69  assert(retval == 0);
70 
71  // Allow helper to gracefully terminate
72  if ((fd_send_ >= 0) && !fail_state_) {
73  LogCvmfs(kLogAuthz, kLogDebug, "shutting down authz helper");
74  Send(string("{\"cvmfs_authz_v1\":{") + "\"msgid\":"
75  + StringifyInt(kAuthzMsgQuit) + "," + "\"revision\":0}}");
76  }
77 
78  ReapHelper();
79 }
80 
82  // If we are reaping the helper, we don't try to shut it down again.
83 
84  if (fd_send_ >= 0)
85  close(fd_send_);
86  fd_send_ = -1;
87  if (fd_recv_ >= 0)
88  close(fd_recv_);
89  fd_recv_ = -1;
90 
91  if (pid_ > 0) {
92  int retval;
93  const uint64_t now = platform_monotonic_time();
94  int statloc;
95  do {
96  retval = waitpid(pid_, &statloc, WNOHANG);
97  if (platform_monotonic_time() > (now + kChildTimeout)) {
99  "authz helper %s unresponsive, killing", progname_.c_str());
100  retval = kill(pid_, SIGKILL);
101  if (retval == 0) {
102  // Pick up client return status
103  (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  break;
109  }
110  } while (retval == 0);
111  pid_ = -1;
112  }
113 }
114 
115 
118  "authz helper %s enters fail state, no more authorization",
119  progname_.c_str());
120 
121  ReapHelper();
123  fail_state_ = true;
124 }
125 
126 
137  int pipe_send[2];
138  int pipe_recv[2];
139  MakePipe(pipe_send);
140  MakePipe(pipe_recv);
141  char *argv0 = strdupa(progname_.c_str());
142  char *argv[] = {argv0, NULL};
143 
144  const bool strip_prefix = true;
145  vector<string> authz_env = options_manager_->GetEnvironmentSubset(
146  "CVMFS_AUTHZ_", strip_prefix);
147  vector<char *> envp;
148  for (unsigned i = 0; i < authz_env.size(); ++i)
149  envp.push_back(strdupa(authz_env[i].c_str()));
150  envp.push_back(strdupa("CVMFS_AUTHZ_HELPER=yes"));
151  envp.push_back(NULL);
152 
153 #ifdef __APPLE__
154  int max_fd = sysconf(_SC_OPEN_MAX);
155  assert(max_fd > 0);
156 #else
157  std::vector<int> open_fds;
158  DIR *dirp = opendir("/proc/self/fd");
159  assert(dirp);
160  platform_dirent64 *dirent;
161  while ((dirent = platform_readdir(dirp))) {
162  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  if (!String2Uint64Parse(name, &name_uint64))
166  continue;
167  if (name_uint64 < 2)
168  continue;
169  open_fds.push_back(static_cast<int>(name_uint64));
170  }
171  closedir(dirp);
172 #endif
173  LogCvmfs(kLogAuthz, kLogDebug | kLogSyslog, "starting authz helper %s",
174  argv0);
175 
176  const pid_t pid = fork();
177  if (pid == 0) {
178  // Child process, close file descriptors and run the helper
179  int retval = dup2(pipe_send[0], 0);
180  assert(retval == 0);
181  retval = dup2(pipe_recv[1], 1);
182  assert(retval == 1);
183 #ifdef __APPLE__
184  for (int fd = 2; fd < max_fd; fd++)
185  close(fd);
186 #else
187  for (unsigned i = 0; i < open_fds.size(); ++i)
188  close(open_fds[i]);
189 #endif
190 
191  for (size_t i = 0; i < sizeof(Watchdog::g_suppressed_signals) / sizeof(int);
192  i++) {
193  struct sigaction signal_handler;
194  signal_handler.sa_handler = SIG_DFL;
195  sigaction(Watchdog::g_suppressed_signals[i], &signal_handler, NULL);
196  }
197 
198  execve(argv0, argv, &envp[0]);
199  syslog(LOG_USER | LOG_ERR, "failed to start authz helper %s (%d)", argv0,
200  errno);
201  _exit(1);
202  }
203  assert(pid > 0);
204  close(pipe_send[0]);
205  close(pipe_recv[1]);
206 
207  // Don't receive a signal if the helper terminates
208  signal(SIGPIPE, SIG_IGN);
209  pid_ = pid;
210  fd_send_ = pipe_send[1];
211  fd_recv_ = pipe_recv[0];
212 }
213 
214 
216  AuthzToken *authz_token,
217  unsigned *ttl) {
218  *ttl = kDefaultTtl;
219 
220  const MutexLockGuard lock_guard(lock_);
221  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  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  assert((fd_send_ >= 0) && (fd_recv_ >= 0));
241 
242  string authz_schema;
243  string pure_membership;
244  StripAuthzSchema(query_info.membership, &authz_schema, &pure_membership);
245  string json_msg = string("{\"cvmfs_authz_v1\":{") + "\"msgid\":"
246  + StringifyInt(kAuthzMsgVerify) + "," + "\"revision\":0,"
247  + "\"uid\":" + StringifyInt(query_info.uid) + ","
248  + "\"gid\":" + StringifyInt(query_info.gid) + ","
249  + "\"pid\":" + StringifyInt(query_info.pid) + ","
250  + "\"membership\":\"" + Base64(pure_membership) + "\"}}";
251  retval = Send(json_msg) && Recv(&json_msg);
252  if (!retval)
253  return kAuthzNoHelper;
254  AuthzExternalMsg binary_msg;
255  retval = ParseMsg(json_msg, kAuthzMsgPermit, &binary_msg);
256  if (!retval)
257  return kAuthzNoHelper;
258 
259  *ttl = binary_msg.permit.ttl;
260  if (binary_msg.permit.status == kAuthzOk) {
261  *authz_token = binary_msg.permit.token;
262  LogCvmfs(kLogAuthz, kLogDebug, "got token of type %d and size %u",
263  binary_msg.permit.token.type, binary_msg.permit.token.size);
264  }
265 
266  return binary_msg.permit.status;
267 }
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 
294  const string debug_log = GetLogDebugFile();
295  string json_debug_log;
296  if (debug_log != "")
297  json_debug_log = ",\"debug_log\":\"" + debug_log + "\"";
298  string json_msg = string("{") + "\"cvmfs_authz_v1\":{"
299  + "\"msgid\":" + StringifyInt(0) + "," + "\"revision\":0,"
300  + "\"fqrn\":\"" + fqrn_ + "\"," + "\"syslog_facility\":"
302  + "\"syslog_level\":" + StringifyInt(GetLogSyslogLevel())
303  + json_debug_log + "}}";
304  bool retval = Send(json_msg);
305  if (!retval)
306  return false;
307 
308  retval = Recv(&json_msg);
309  if (!retval)
310  return false;
311  AuthzExternalMsg binary_msg;
312  retval = ParseMsg(json_msg, kAuthzMsgReady, &binary_msg);
313  if (!retval)
314  return false;
315 
316  return true;
317 }
318 
319 
321  const int retval = pthread_mutex_init(&lock_, NULL);
322  assert(retval == 0);
323 }
324 
325 
326 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  header.version = kProtocolVersion;
333  header.length = msg.length();
334  const unsigned raw_length = sizeof(header) + msg.length();
335  unsigned char *raw_msg = reinterpret_cast<unsigned char *>(
336  alloca(raw_length));
337  memcpy(raw_msg, &header, sizeof(header));
338  memcpy(raw_msg + sizeof(header), msg.data(), header.length);
339 
340  const bool retval = SafeWrite(fd_send_, raw_msg, raw_length);
341  if (!retval)
342  EnterFailState();
343  return retval;
344 }
345 
346 
360 bool AuthzExternalFetcher::ParseMsg(const std::string &json_msg,
361  const AuthzExternalMsgIds expected_msgid,
362  AuthzExternalMsg *binary_msg) {
363  assert(binary_msg != NULL);
364 
365  const UniquePtr<JsonDocument> json_document(JsonDocument::Create(json_msg));
366  if (!json_document.IsValid()) {
368  "invalid json from authz helper %s: %s", progname_.c_str(),
369  json_msg.c_str());
370  EnterFailState();
371  return false;
372  }
373 
374  JSON *json_authz = JsonDocument::SearchInObject(
375  json_document->root(), "cvmfs_authz_v1", JSON_OBJECT);
376  if (json_authz == NULL) {
378  "\"cvmfs_authz_v1\" not found in json from authz helper %s: %s",
379  progname_.c_str(), json_msg.c_str());
380  EnterFailState();
381  return false;
382  }
383 
384  if (!ParseMsgId(json_authz, binary_msg)
385  || (binary_msg->msgid != expected_msgid)) {
386  EnterFailState();
387  return false;
388  }
389  if (!ParseRevision(json_authz, binary_msg)) {
390  EnterFailState();
391  return false;
392  }
393  if (binary_msg->msgid == kAuthzMsgPermit) {
394  if (!ParsePermit(json_authz, binary_msg)) {
395  EnterFailState();
396  return false;
397  }
398  }
399  return true;
400 }
401 
402 
404  AuthzExternalMsg *binary_msg) {
405  JSON *json_msgid = JsonDocument::SearchInObject(json_authz, "msgid",
406  JSON_INT);
407  if (json_msgid == NULL) {
409  "\"msgid\" not found in json from authz helper %s",
410  progname_.c_str());
411  EnterFailState();
412  return false;
413  }
414 
415  if ((json_msgid->int_value < 0)
416  || (json_msgid->int_value >= kAuthzMsgInvalid)) {
418  "invalid \"msgid\" in json from authz helper %s: %d",
419  progname_.c_str(), json_msgid->int_value);
420  EnterFailState();
421  return false;
422  }
423 
424  binary_msg->msgid = static_cast<AuthzExternalMsgIds>(json_msgid->int_value);
425  return true;
426 }
427 
428 
434  AuthzExternalMsg *binary_msg) {
435  JSON *json_status = JsonDocument::SearchInObject(json_authz, "status",
436  JSON_INT);
437  if (json_status == NULL) {
439  "\"status\" not found in json from authz helper %s",
440  progname_.c_str());
441  EnterFailState();
442  return false;
443  }
444  if ((json_status->int_value < 0)
445  || (json_status->int_value > kAuthzUnknown)) {
446  binary_msg->permit.status = kAuthzUnknown;
447  } else {
448  binary_msg->permit.status = static_cast<AuthzStatus>(
449  json_status->int_value);
450  }
451 
452  JSON *json_ttl = JsonDocument::SearchInObject(json_authz, "ttl", JSON_INT);
453  if (json_ttl == NULL) {
454  LogCvmfs(kLogAuthz, kLogDebug, "no ttl, using default");
455  binary_msg->permit.ttl = kDefaultTtl;
456  } else {
457  binary_msg->permit.ttl = std::max(kMinTtl, json_ttl->int_value);
458  }
459 
460  JSON *json_token = JsonDocument::SearchInObject(json_authz, "x509_proxy",
461  JSON_STRING);
462  if (json_token != NULL) {
463  binary_msg->permit.token.type = kTokenX509;
464  string token_binary;
465  const bool valid_base64 = Debase64(json_token->string_value, &token_binary);
466  if (!valid_base64) {
468  "invalid Base64 in 'x509_proxy' from authz helper %s",
469  progname_.c_str());
470  EnterFailState();
471  return false;
472  }
473  const unsigned size = token_binary.size();
474  binary_msg->permit.token.size = size;
475  if (size > 0) {
476  // The token is passed to the AuthzSessionManager, which takes care of
477  // freeing the memory
478  binary_msg->permit.token.data = smalloc(size);
479  memcpy(binary_msg->permit.token.data, token_binary.data(), size);
480  }
481  }
482 
483  json_token = JsonDocument::SearchInObject(json_authz, "bearer_token",
484  JSON_STRING);
485  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 
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
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  if (binary_msg->permit.token.type == kTokenUnknown) {
508  // No auth token returned, so authz should do... what exactly?
509  // Log error message
511  "No auth token found in returned JSON from Authz helper %s",
512  progname_.c_str());
513  }
514 
515  return true;
516 }
517 
518 
520  AuthzExternalMsg *binary_msg) {
521  JSON *json_revision = JsonDocument::SearchInObject(json_authz, "revision",
522  JSON_INT);
523  if (json_revision == NULL) {
525  "\"revision\" not found in json from authz helper %s",
526  progname_.c_str());
527  EnterFailState();
528  return false;
529  }
530 
531  if (json_revision->int_value < 0) {
533  "invalid \"revision\" in json from authz helper %s: %d",
534  progname_.c_str(), json_revision->int_value);
535  EnterFailState();
536  return false;
537  }
538 
539  binary_msg->protocol_revision = json_revision->int_value;
540  return true;
541 }
542 
543 
544 bool AuthzExternalFetcher::Recv(string *msg) {
545  uint32_t version;
546  ssize_t retval = SafeRead(fd_recv_, &version, sizeof(version));
547  if (retval != static_cast<int>(sizeof(version))) {
548  EnterFailState();
549  return false;
550  }
551  if (version != kProtocolVersion) {
553  "authz helper uses unknown protocol version %u", version);
554  EnterFailState();
555  return false;
556  }
557 
558  uint32_t length;
559  retval = SafeRead(fd_recv_, &length, sizeof(length));
560  if (retval != static_cast<int>(sizeof(length))) {
561  EnterFailState();
562  return false;
563  }
564 
565  msg->clear();
566  char buf[kPageSize];
567  unsigned nbytes = 0;
568  while (nbytes < length) {
569  const unsigned remaining = length - nbytes;
570  retval = SafeRead(fd_recv_, buf, std::min(kPageSize, remaining));
571  if (retval < 0) {
573  "read failure from authz helper %s", progname_.c_str());
574  EnterFailState();
575  return false;
576  }
577  nbytes += retval;
578  msg->append(buf, retval);
579  }
580 
581  return true;
582 }
583 
584 
585 void AuthzExternalFetcher::StripAuthzSchema(const string &membership,
586  string *authz_schema,
587  string *pure_membership) {
588  vector<string> components = SplitString(membership, '%');
589  *authz_schema = components[0];
590  if (components.size() < 2) {
591  LogCvmfs(kLogAuthz, kLogDebug, "invalid membership: %s",
592  membership.c_str());
593  *pure_membership = "";
594  return;
595  }
596 
597  components.erase(components.begin());
598  *pure_membership = JoinStrings(components, "%");
599 }
AuthzExternalMsgIds msgid
Definition: authz_fetch.h:83
std::string search_path_
Definition: authz_fetch.h:174
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:128
string JoinStrings(const vector< string > &strings, const string &joint)
Definition: string.cc:356
Helper: &quot;I verified, cvmfs, here&#39;s the result&quot;.
Definition: authz_fetch.h:72
void * data
Definition: authz.h:33
void StripAuthzSchema(const std::string &membership, std::string *authz_schema, std::string *pure_membership)
Definition: authz_fetch.cc:585
std::vector< std::string > GetEnvironmentSubset(const std::string &key_prefix, bool strip_prefix)
Definition: options.cc:428
pthread_mutex_t lock_
Definition: authz_fetch.h:205
bool SafeWrite(int fd, const void *buf, size_t nbyte)
Definition: posix.cc:2036
assert((mem||(size==0))&&"Out Of Memory")
bool Debase64(const string &data, string *decoded)
Definition: string.cc:598
void MakePipe(int pipe_fd[2])
Definition: posix.cc:487
bool ParseMsgId(JSON *json_authz, AuthzExternalMsg *binary_msg)
Definition: authz_fetch.cc:403
static const uint32_t kProtocolVersion
Definition: authz_fetch.h:111
Helper: &quot;Yes, cvmfs, I&#39;m here&quot;.
Definition: authz_fetch.h:70
std::string progname_
Definition: authz_fetch.h:169
AuthzToken token
Definition: authz_fetch.h:87
bool String2Uint64Parse(const std::string &value, uint64_t *result)
Definition: string.cc:257
OptionsManager * options_manager_
Definition: authz_fetch.h:200
bool IsValid(const std::string &input) const
Definition: sanitizer.cc:112
bool ParsePermit(JSON *json_authz, AuthzExternalMsg *binary_msg)
Definition: authz_fetch.cc:433
bool FileExists(const std::string &path)
Definition: posix.cc:803
pid_t pid_
Definition: cvmfs.cc:156
int GetLogSyslogLevel()
Definition: logging.cc:164
bool Recv(std::string *msg)
Definition: authz_fetch.cc:544
#define strdupa(s)
Definition: platform_osx.h:285
#define GetLogDebugFile()
ssize_t SafeRead(int fd, void *buf, size_t nbyte)
Definition: posix.cc:2094
static JsonDocument * Create(const std::string &text)
vector< string > SplitString(const string &str, char delim)
Definition: string.cc:306
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:270
Cvmfs: &quot;Please verify, helper&quot;.
Definition: authz_fetch.h:71
string StringifyInt(const int64_t value)
Definition: string.cc:77
AuthzStatus status
Definition: authz_fetch.h:86
uint64_t platform_monotonic_time()
struct AuthzExternalMsg::@0 permit
bool ParseRevision(JSON *json_authz, AuthzExternalMsg *binary_msg)
Definition: authz_fetch.cc:519
unsigned size
Definition: authz.h:34
virtual ~AuthzExternalFetcher()
Definition: authz_fetch.cc:67
static const int kMinTtl
Definition: authz_fetch.h:133
virtual AuthzStatus Fetch(const QueryInfo &query_info, AuthzToken *authz_token, unsigned *ttl)
Definition: authz_fetch.cc:215
bool ParseMsg(const std::string &json_msg, const AuthzExternalMsgIds expected_msgid, AuthzExternalMsg *binary_msg)
Definition: authz_fetch.cc:360
bool Send(const std::string &msg)
Definition: authz_fetch.cc:326
string Base64(const string &data)
Definition: string.cc:537
BashOptionsManager options_manager_
Definition: mount.cvmfs.cc:34
Cvmfs: &quot;Please shutdown, helper&quot;.
Definition: authz_fetch.h:73
const unsigned kPageSize
Definition: posix.h:30
Definition: mutex.h:42
platform_dirent64 * platform_readdir(DIR *dirp)
AuthzExternalMsgIds
Definition: authz_fetch.h:68
static const unsigned kDefaultTtl
Definition: authz_fetch.h:138
First invalid message id.
Definition: authz_fetch.h:74
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:210
CVMFS_EXPORT void LogCvmfs(const LogSource source, const int mask, const char *format,...)
Definition: logging.cc:545