CernVM-FS  2.11.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 "options.h"
20 #include "sanitizer.h"
21 #include "util/concurrency.h"
22 #include "util/logging.h"
23 #include "util/platform.h"
24 #include "util/pointer.h"
25 #include "util/posix.h"
26 #include "util/smalloc.h"
27 #include "util/string.h"
28 
29 using namespace std; // NOLINT
30 
31 
32 const int AuthzExternalFetcher::kMinTtl = 0;
34 
35 
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  , next_start_(-1)
50 {
51  InitLock();
52 }
53 
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  , next_start_(-1)
65 {
66  InitLock();
67 }
68 
69 
71  int retval = pthread_mutex_destroy(&lock_);
72  assert(retval == 0);
73 
74  // Allow helper to gracefully terminate
75  if ((fd_send_ >= 0) && !fail_state_) {
76  LogCvmfs(kLogAuthz, kLogDebug, "shutting down authz helper");
77  Send(string("{\"cvmfs_authz_v1\":{") +
78  "\"msgid\":" + StringifyInt(kAuthzMsgQuit) + "," +
79  "\"revision\":0}}");
80  }
81 
82  ReapHelper();
83 }
84 
86  // If we are reaping the helper, we don't try to shut it down again.
87 
88  if (fd_send_ >= 0)
89  close(fd_send_);
90  fd_send_ = -1;
91  if (fd_recv_ >= 0)
92  close(fd_recv_);
93  fd_recv_ = -1;
94 
95  if (pid_ > 0) {
96  int retval;
97  uint64_t now = platform_monotonic_time();
98  int statloc;
99  do {
100  retval = waitpid(pid_, &statloc, WNOHANG);
101  if (platform_monotonic_time() > (now + kChildTimeout)) {
103  "authz helper %s unresponsive, killing", progname_.c_str());
104  retval = kill(pid_, SIGKILL);
105  if (retval == 0) {
106  // Pick up client return status
107  (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  break;
113  }
114  } while (retval == 0);
115  pid_ = -1;
116  }
117 }
118 
119 
122  "authz helper %s enters fail state, no more authorization",
123  progname_.c_str());
124 
125  ReapHelper();
127  fail_state_ = true;
128 }
129 
130 
141  int pipe_send[2];
142  int pipe_recv[2];
143  MakePipe(pipe_send);
144  MakePipe(pipe_recv);
145  char *argv0 = strdupa(progname_.c_str());
146  char *argv[] = {argv0, NULL};
147 
148  const bool strip_prefix = true;
149  vector<string> authz_env =
150  options_manager_->GetEnvironmentSubset("CVMFS_AUTHZ_", strip_prefix);
151  vector<char *> envp;
152  for (unsigned i = 0; i < authz_env.size(); ++i)
153  envp.push_back(strdupa(authz_env[i].c_str()));
154  envp.push_back(strdupa("CVMFS_AUTHZ_HELPER=yes"));
155  envp.push_back(NULL);
156 
157 #ifdef __APPLE__
158  int max_fd = sysconf(_SC_OPEN_MAX);
159  assert(max_fd > 0);
160 #else
161  std::vector<int> open_fds;
162  DIR *dirp = opendir("/proc/self/fd");
163  assert(dirp);
164  platform_dirent64 *dirent;
165  while ((dirent = platform_readdir(dirp))) {
166  const std::string name(dirent->d_name);
167  uint64_t name_uint64;
168  // Make sure the dir name is digits only (skips ".", ".." and similar).
169  if (!String2Uint64Parse(name, &name_uint64))
170  continue;
171  if (name_uint64 < 2)
172  continue;
173  open_fds.push_back(static_cast<int>(name_uint64));
174  }
175  closedir(dirp);
176 #endif
177  LogCvmfs(kLogAuthz, kLogDebug | kLogSyslog, "starting authz helper %s",
178  argv0);
179 
180  pid_t pid = fork();
181  if (pid == 0) {
182  // Child process, close file descriptors and run the helper
183  int retval = dup2(pipe_send[0], 0);
184  assert(retval == 0);
185  retval = dup2(pipe_recv[1], 1);
186  assert(retval == 1);
187 #ifdef __APPLE__
188  for (int fd = 2; fd < max_fd; fd++)
189  close(fd);
190 #else
191  for (unsigned i = 0; i < open_fds.size(); ++i)
192  close(open_fds[i]);
193 #endif
194 
195  execve(argv0, argv, &envp[0]);
196  syslog(LOG_USER | LOG_ERR, "failed to start authz helper %s (%d)",
197  argv0, errno);
198  abort();
199  }
200  assert(pid > 0);
201  close(pipe_send[0]);
202  close(pipe_recv[1]);
203 
204  // Don't receive a signal if the helper terminates
205  signal(SIGPIPE, SIG_IGN);
206  pid_ = pid;
207  fd_send_ = pipe_send[1];
208  fd_recv_ = pipe_recv[0];
209 }
210 
211 
213  const QueryInfo &query_info,
214  AuthzToken *authz_token,
215  unsigned *ttl)
216 {
217  *ttl = kDefaultTtl;
218 
219  MutexLockGuard lock_guard(lock_);
220  if (fail_state_) {
221  uint64_t now = platform_monotonic_time();
222  if (now > next_start_) {
223  fail_state_ = false;
224  } else {
225  return kAuthzNoHelper;
226  }
227  }
228 
229  bool retval;
230 
231  if (fd_send_ < 0) {
232  if (progname_.empty())
233  progname_ = FindHelper(query_info.membership);
234  ExecHelper();
235  retval = Handshake();
236  if (!retval)
237  return kAuthzNoHelper;
238  }
239  assert((fd_send_ >= 0) && (fd_recv_ >= 0));
240 
241  string authz_schema;
242  string pure_membership;
243  StripAuthzSchema(query_info.membership, &authz_schema, &pure_membership);
244  string json_msg = string("{\"cvmfs_authz_v1\":{") +
245  "\"msgid\":" + StringifyInt(kAuthzMsgVerify) + "," +
246  "\"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  "\"}}";
252  retval = Send(json_msg) && Recv(&json_msg);
253  if (!retval)
254  return kAuthzNoHelper;
255  AuthzExternalMsg binary_msg;
256  retval = ParseMsg(json_msg, kAuthzMsgPermit, &binary_msg);
257  if (!retval)
258  return kAuthzNoHelper;
259 
260  *ttl = binary_msg.permit.ttl;
261  if (binary_msg.permit.status == kAuthzOk) {
262  *authz_token = binary_msg.permit.token;
263  LogCvmfs(kLogAuthz, kLogDebug, "got token of type %d and size %u",
264  binary_msg.permit.token.type, binary_msg.permit.token.size);
265  }
266 
267  return binary_msg.permit.status;
268 }
269 
270 
271 string AuthzExternalFetcher::FindHelper(const string &membership) {
272  string authz_schema;
273  string pure_membership;
274  StripAuthzSchema(membership, &authz_schema, &pure_membership);
276  if (!sanitizer.IsValid(authz_schema)) {
277  LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, "invalid authz schema: %s",
278  authz_schema.c_str());
279  return "";
280  }
281 
282  string exe_path = search_path_ + "/cvmfs_" + authz_schema + "_helper";
283  if (!FileExists(exe_path)) {
284  LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, "authz helper %s missing",
285  exe_path.c_str());
286  }
287  return exe_path;
288 }
289 
290 
295  string debug_log = GetLogDebugFile();
296  string json_debug_log;
297  if (debug_log != "")
298  json_debug_log = ",\"debug_log\":\"" + debug_log + "\"";
299  string json_msg = string("{") +
300  "\"cvmfs_authz_v1\":{" +
301  "\"msgid\":" + StringifyInt(0) + "," +
302  "\"revision\":0," +
303  "\"fqrn\":\"" + fqrn_ + "\"," +
304  "\"syslog_facility\":" + StringifyInt(GetLogSyslogFacility()) + "," +
305  "\"syslog_level\":" + StringifyInt(GetLogSyslogLevel()) +
306  json_debug_log +
307  "}}";
308  bool retval = Send(json_msg);
309  if (!retval)
310  return false;
311 
312  retval = Recv(&json_msg);
313  if (!retval)
314  return false;
315  AuthzExternalMsg binary_msg;
316  retval = ParseMsg(json_msg, kAuthzMsgReady, &binary_msg);
317  if (!retval)
318  return false;
319 
320  return true;
321 }
322 
323 
325  int retval = pthread_mutex_init(&lock_, NULL);
326  assert(retval == 0);
327 }
328 
329 
330 bool AuthzExternalFetcher::Send(const string &msg) {
331  // Line format: 4 byte protocol version, 4 byte length, message
332  struct {
333  uint32_t version;
334  uint32_t length;
335  } header;
336  header.version = kProtocolVersion;
337  header.length = msg.length();
338  unsigned raw_length = sizeof(header) + msg.length();
339  unsigned char *raw_msg = reinterpret_cast<unsigned char *>(
340  alloca(raw_length));
341  memcpy(raw_msg, &header, sizeof(header));
342  memcpy(raw_msg + sizeof(header), msg.data(), header.length);
343 
344  bool retval = SafeWrite(fd_send_, raw_msg, raw_length);
345  if (!retval)
346  EnterFailState();
347  return retval;
348 }
349 
350 
365  const std::string &json_msg,
366  const AuthzExternalMsgIds expected_msgid,
367  AuthzExternalMsg *binary_msg)
368 {
369  assert(binary_msg != NULL);
370 
371  UniquePtr<JsonDocument> json_document(JsonDocument::Create(json_msg));
372  if (!json_document.IsValid()) {
374  "invalid json from authz helper %s: %s",
375  progname_.c_str(), json_msg.c_str());
376  EnterFailState();
377  return false;
378  }
379 
380  JSON *json_authz = JsonDocument::SearchInObject(
381  json_document->root(), "cvmfs_authz_v1", JSON_OBJECT);
382  if (json_authz == NULL) {
384  "\"cvmfs_authz_v1\" not found in json from authz helper %s: %s",
385  progname_.c_str(), json_msg.c_str());
386  EnterFailState();
387  return false;
388  }
389 
390  if (!ParseMsgId(json_authz, binary_msg) ||
391  (binary_msg->msgid != expected_msgid))
392  {
393  EnterFailState();
394  return false;
395  }
396  if (!ParseRevision(json_authz, binary_msg)) {
397  EnterFailState();
398  return false;
399  }
400  if (binary_msg->msgid == kAuthzMsgPermit) {
401  if (!ParsePermit(json_authz, binary_msg)) {
402  EnterFailState();
403  return false;
404  }
405  }
406  return true;
407 }
408 
409 
411  JSON *json_authz,
412  AuthzExternalMsg *binary_msg)
413 {
414  JSON *json_msgid = JsonDocument::SearchInObject(
415  json_authz, "msgid", JSON_INT);
416  if (json_msgid == NULL) {
418  "\"msgid\" not found in json from authz helper %s",
419  progname_.c_str());
420  EnterFailState();
421  return false;
422  }
423 
424  if ((json_msgid->int_value < 0) ||
425  (json_msgid->int_value >= kAuthzMsgInvalid))
426  {
428  "invalid \"msgid\" in json from authz helper %s: %d",
429  progname_.c_str(), json_msgid->int_value);
430  EnterFailState();
431  return false;
432  }
433 
434  binary_msg->msgid = static_cast<AuthzExternalMsgIds>(json_msgid->int_value);
435  return true;
436 }
437 
438 
444  JSON *json_authz,
445  AuthzExternalMsg *binary_msg)
446 {
447  JSON *json_status =
448  JsonDocument::SearchInObject(json_authz, "status", JSON_INT);
449  if (json_status == NULL) {
451  "\"status\" not found in json from authz helper %s",
452  progname_.c_str());
453  EnterFailState();
454  return false;
455  }
456  if ((json_status->int_value < 0) || (json_status->int_value > kAuthzUnknown))
457  {
458  binary_msg->permit.status = kAuthzUnknown;
459  } else {
460  binary_msg->permit.status = static_cast<AuthzStatus>(
461  json_status->int_value);
462  }
463 
464  JSON *json_ttl = JsonDocument::SearchInObject(json_authz, "ttl", JSON_INT);
465  if (json_ttl == NULL) {
466  LogCvmfs(kLogAuthz, kLogDebug, "no ttl, using default");
467  binary_msg->permit.ttl = kDefaultTtl;
468  } else {
469  binary_msg->permit.ttl = std::max(kMinTtl, json_ttl->int_value);
470  }
471 
472  JSON *json_token =
473  JsonDocument::SearchInObject(json_authz, "x509_proxy", JSON_STRING);
474  if (json_token != NULL) {
475  binary_msg->permit.token.type = kTokenX509;
476  string token_binary;
477  bool valid_base64 = Debase64(json_token->string_value, &token_binary);
478  if (!valid_base64) {
480  "invalid Base64 in 'x509_proxy' from authz helper %s",
481  progname_.c_str());
482  EnterFailState();
483  return false;
484  }
485  unsigned size = token_binary.size();
486  binary_msg->permit.token.size = size;
487  if (size > 0) {
488  // The token is passed to the AuthzSessionManager, which takes care of
489  // freeing the memory
490  binary_msg->permit.token.data = smalloc(size);
491  memcpy(binary_msg->permit.token.data, token_binary.data(), size);
492  }
493  }
494 
495  json_token = JsonDocument::SearchInObject(json_authz, "bearer_token",
496  JSON_STRING);
497  if (json_token != NULL) {
498  binary_msg->permit.token.type = kTokenBearer;
499  unsigned size = strlen(json_token->string_value);
500  binary_msg->permit.token.size = size;
501  if (size > 0) {
502  // The token is passed to the AuthzSessionManager, which takes care of
503  // freeing the memory
504  binary_msg->permit.token.data = smalloc(size);
505  memcpy(binary_msg->permit.token.data, json_token->string_value, size);
506 
508  "Got a bearer_token from authz_helper. "
509  "Setting token type to kTokenBearer");
510  } else {
511  // We got a bearer_token, but a size 0 (or negative?) string
513  "bearer_token was in returned JSON from Authz helper,"
514  " but of size 0 from authz helper %s",
515  progname_.c_str());
516  }
517  }
518 
519  if (binary_msg->permit.token.type == kTokenUnknown) {
520  // No auth token returned, so authz should do... what exactly?
521  // Log error message
523  "No auth token found in returned JSON from Authz helper %s",
524  progname_.c_str());
525  }
526 
527  return true;
528 }
529 
530 
532  JSON *json_authz,
533  AuthzExternalMsg *binary_msg)
534 {
535  JSON *json_revision = JsonDocument::SearchInObject(
536  json_authz, "revision", JSON_INT);
537  if (json_revision == NULL) {
539  "\"revision\" not found in json from authz helper %s",
540  progname_.c_str());
541  EnterFailState();
542  return false;
543  }
544 
545  if (json_revision->int_value < 0) {
547  "invalid \"revision\" in json from authz helper %s: %d",
548  progname_.c_str(), json_revision->int_value);
549  EnterFailState();
550  return false;
551  }
552 
553  binary_msg->protocol_revision = json_revision->int_value;
554  return true;
555 }
556 
557 
558 bool AuthzExternalFetcher::Recv(string *msg) {
559  uint32_t version;
560  ssize_t retval = SafeRead(fd_recv_, &version, sizeof(version));
561  if (retval != static_cast<int>(sizeof(version))) {
562  EnterFailState();
563  return false;
564  }
565  if (version != kProtocolVersion) {
567  "authz helper uses unknown protocol version %u", version);
568  EnterFailState();
569  return false;
570  }
571 
572  uint32_t length;
573  retval = SafeRead(fd_recv_, &length, sizeof(length));
574  if (retval != static_cast<int>(sizeof(length))) {
575  EnterFailState();
576  return false;
577  }
578 
579  msg->clear();
580  char buf[kPageSize];
581  unsigned nbytes = 0;
582  while (nbytes < length) {
583  const unsigned remaining = length - nbytes;
584  retval = SafeRead(fd_recv_, buf, std::min(kPageSize, remaining));
585  if (retval < 0) {
587  "read failure from authz helper %s", progname_.c_str());
588  EnterFailState();
589  return false;
590  }
591  nbytes += retval;
592  msg->append(buf, retval);
593  }
594 
595  return true;
596 }
597 
598 
600  const string &membership,
601  string *authz_schema,
602  string *pure_membership)
603 {
604  vector<string> components = SplitString(membership, '%');
605  *authz_schema = components[0];
606  if (components.size() < 2) {
607  LogCvmfs(kLogAuthz, kLogDebug, "invalid membership: %s",
608  membership.c_str());
609  *pure_membership = "";
610  return;
611  }
612 
613  components.erase(components.begin());
614  *pure_membership = JoinStrings(components, "%");
615 }
#define LogCvmfs(source, mask,...)
Definition: logging.h:25
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:599
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:1979
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:488
bool ParseMsgId(JSON *json_authz, AuthzExternalMsg *binary_msg)
Definition: authz_fetch.cc:410
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:443
bool FileExists(const std::string &path)
Definition: posix.cc:787
pid_t pid_
Definition: cvmfs.cc:149
int GetLogSyslogLevel()
Definition: logging.cc:168
bool Recv(std::string *msg)
Definition: authz_fetch.cc:558
#define strdupa(s)
Definition: platform_osx.h:286
#define GetLogDebugFile()
ssize_t SafeRead(int fd, void *buf, size_t nbyte)
Definition: posix.cc:2038
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
std::string FindHelper(const std::string &membership)
Definition: authz_fetch.cc:271
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:531
unsigned size
Definition: authz.h:34
virtual ~AuthzExternalFetcher()
Definition: authz_fetch.cc:70
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:212
bool ParseMsg(const std::string &json_msg, const AuthzExternalMsgIds expected_msgid, AuthzExternalMsg *binary_msg)
Definition: authz_fetch.cc:364
bool Send(const std::string &msg)
Definition: authz_fetch.cc:330
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:47
AuthzStatus
Definition: authz.h:38
const JSON * root() const
Definition: json_document.h:25
struct dirent64 platform_dirent64
int GetLogSyslogFacility()
Definition: logging.cc:214