CernVM-FS  2.13.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
repository_session.cc
Go to the documentation of this file.
1 
6 #include <fcntl.h>
7 #include <unistd.h>
8 
9 #include <cassert>
10 #include <string>
11 
12 #include "backoff.h"
13 #include "catalog_mgr_ro.h"
14 #include "crypto/hash.h"
15 #include "directory_entry.h"
16 #include "duplex_curl.h"
17 #include "gateway_util.h"
18 #include "json_document.h"
19 #include "publish/except.h"
20 #include "publish/repository.h"
21 #include "ssl.h"
22 #include "upload.h"
23 #include "util/logging.h"
24 #include "util/pointer.h"
25 #include "util/posix.h"
26 #include "util/string.h"
27 
28 namespace {
29 
30 struct CurlBuffer {
31  std::string data;
32 };
33 
34 enum LeaseReply {
38 };
39 
40 static CURL *PrepareCurl(const std::string &method) {
41  const char *user_agent_string = "cvmfs/" CVMFS_VERSION;
42 
43  CURL *h_curl = curl_easy_init();
44  assert(h_curl != NULL);
45 
46  curl_easy_setopt(h_curl, CURLOPT_NOPROGRESS, 1L);
47  curl_easy_setopt(h_curl, CURLOPT_USERAGENT, user_agent_string);
48  curl_easy_setopt(h_curl, CURLOPT_MAXREDIRS, 50L);
49  curl_easy_setopt(h_curl, CURLOPT_CUSTOMREQUEST, method.c_str());
50 
51  return h_curl;
52 }
53 
54 static size_t RecvCB(void *buffer, size_t size, size_t nmemb, void *userp) {
55  CurlBuffer *my_buffer = static_cast<CurlBuffer *>(userp);
56 
57  if (size * nmemb < 1) {
58  return 0;
59  }
60 
61  my_buffer->data = static_cast<char *>(buffer);
62 
63  return my_buffer->data.size();
64 }
65 
66 static void MakeAcquireRequest(const gateway::GatewayKey &key,
67  const std::string &repo_path,
68  const std::string &repo_service_url,
69  int llvl,
70  CurlBuffer *buffer) {
71  CURLcode ret = static_cast<CURLcode>(0);
72 
73  CURL *h_curl = PrepareCurl("POST");
74 
75  const std::string payload = "{\"path\" : \"" + repo_path
76  + "\", \"api_version\" : \""
77  + StringifyInt(gateway::APIVersion()) + "\", "
78  + "\"hostname\" : \"" + GetHostname() + "\"}";
79 
81  shash::HmacString(key.secret(), payload, &hmac);
84  cs.ApplySslCertificatePath(h_curl);
85 
86  const std::string header_str = std::string("Authorization: ") + key.id() + " "
87  + Base64(hmac.ToString(false));
88  struct curl_slist *auth_header = NULL;
89  auth_header = curl_slist_append(auth_header, header_str.c_str());
90  curl_easy_setopt(h_curl, CURLOPT_HTTPHEADER, auth_header);
91 
92  // Make request to acquire lease from repo services
93  curl_easy_setopt(h_curl, CURLOPT_URL, (repo_service_url + "/leases").c_str());
94  curl_easy_setopt(h_curl, CURLOPT_POSTFIELDSIZE_LARGE,
95  static_cast<curl_off_t>(payload.length()));
96  curl_easy_setopt(h_curl, CURLOPT_POSTFIELDS, payload.c_str());
97  curl_easy_setopt(h_curl, CURLOPT_WRITEFUNCTION, RecvCB);
98  curl_easy_setopt(h_curl, CURLOPT_WRITEDATA, buffer);
99 
100  ret = curl_easy_perform(h_curl);
101  curl_easy_cleanup(h_curl);
102  if (ret != CURLE_OK) {
104  "Make lease acquire request failed: %d. Reply: %s", ret,
105  buffer->data.c_str());
106  throw publish::EPublish("cannot acquire lease",
108  }
109 }
110 
111 // TODO(jblomer): This should eventually also handle the POST request for
112 // committing a transaction
113 static void MakeDropRequest(const gateway::GatewayKey &key,
114  const std::string &session_token,
115  const std::string &repo_service_url,
116  int llvl,
117  CurlBuffer *reply) {
118  CURLcode ret = static_cast<CURLcode>(0);
119 
120  CURL *h_curl = PrepareCurl("DELETE");
121 
122  shash::Any hmac(shash::kSha1);
123  shash::HmacString(key.secret(), session_token, &hmac);
126  cs.ApplySslCertificatePath(h_curl);
127 
128  const std::string header_str = std::string("Authorization: ") + key.id() + " "
129  + Base64(hmac.ToString(false));
130  struct curl_slist *auth_header = NULL;
131  auth_header = curl_slist_append(auth_header, header_str.c_str());
132  curl_easy_setopt(h_curl, CURLOPT_HTTPHEADER, auth_header);
133 
134  curl_easy_setopt(h_curl, CURLOPT_URL,
135  (repo_service_url + "/leases/" + session_token).c_str());
136  curl_easy_setopt(h_curl, CURLOPT_POSTFIELDSIZE_LARGE,
137  static_cast<curl_off_t>(0));
138  curl_easy_setopt(h_curl, CURLOPT_POSTFIELDS, NULL);
139  curl_easy_setopt(h_curl, CURLOPT_WRITEFUNCTION, RecvCB);
140  curl_easy_setopt(h_curl, CURLOPT_WRITEDATA, reply);
141 
142  ret = curl_easy_perform(h_curl);
143  curl_easy_cleanup(h_curl);
144  if (ret != CURLE_OK) {
146  "Make lease drop request failed: %d. Reply: '%s'", ret,
147  reply->data.c_str());
148  throw publish::EPublish("cannot drop lease",
150  }
151 }
152 
154  std::string *session_token,
155  int llvl) {
156  if (buffer.data.size() == 0 || session_token == NULL) {
157  return kLeaseReplyFailure;
158  }
159 
161  if (!reply.IsValid() || !reply->IsValid()) {
162  return kLeaseReplyFailure;
163  }
164 
165  const JSON *result = JsonDocument::SearchInObject(reply->root(), "status",
166  JSON_STRING);
167  if (result != NULL) {
168  const std::string status = result->string_value;
169  if (status == "ok") {
170  LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Gateway reply: ok");
171  const JSON *token = JsonDocument::SearchInObject(
172  reply->root(), "session_token", JSON_STRING);
173  if (token != NULL) {
174  LogCvmfs(kLogCvmfs, kLogDebug, "Session token: %s",
175  token->string_value);
176  *session_token = token->string_value;
177  return kLeaseReplySuccess;
178  }
179  } else if (status == "path_busy") {
180  const JSON *time_remaining = JsonDocument::SearchInObject(
181  reply->root(), "time_remaining", JSON_STRING);
182  LogCvmfs(
183  kLogCvmfs, llvl | kLogStdout, "Path busy. Time remaining = %s",
184  (time_remaining != NULL) ? time_remaining->string_value : "UNKNOWN");
185  return kLeaseReplyBusy;
186  } else if (status == "error") {
187  const JSON *reason = JsonDocument::SearchInObject(reply->root(), "reason",
188  JSON_STRING);
189  LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Error: '%s'",
190  (reason != NULL) ? reason->string_value : "");
191  } else {
192  LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Unknown reply. Status: %s",
193  status.c_str());
194  }
195  }
196 
197  return kLeaseReplyFailure;
198 }
199 
200 
201 static LeaseReply ParseDropReply(const CurlBuffer &buffer, int llvl) {
202  if (buffer.data.size() == 0) {
203  return kLeaseReplyFailure;
204  }
205 
207  if (!reply.IsValid() || !reply->IsValid()) {
208  return kLeaseReplyFailure;
209  }
210 
211  const JSON *result = JsonDocument::SearchInObject(reply->root(), "status",
212  JSON_STRING);
213  if (result != NULL) {
214  const std::string status = result->string_value;
215  if (status == "ok") {
216  LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Gateway reply: ok");
217  return kLeaseReplySuccess;
218  } else if (status == "invalid_token") {
219  LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Error: invalid session token");
220  } else if (status == "error") {
221  const JSON *reason = JsonDocument::SearchInObject(reply->root(), "reason",
222  JSON_STRING);
223  LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Error from gateway: '%s'",
224  (reason != NULL) ? reason->string_value : "");
225  } else {
226  LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Unknown reply. Status: %s",
227  status.c_str());
228  }
229  }
230 
231  return kLeaseReplyFailure;
232 }
233 
234 } // anonymous namespace
235 
236 namespace publish {
237 
238 Publisher::Session::Session(const Settings &settings_session)
239  : settings_(settings_session)
240  , keep_alive_(false)
241  // TODO(jblomer): it would be better to actually read & validate the token
242  , has_lease_(FileExists(settings_.token_path)) { }
243 
244 
245 Publisher::Session::Session(const SettingsPublisher &settings_publisher,
246  int llvl) {
247  keep_alive_ = false;
248  if (settings_publisher.storage().type()
250  has_lease_ = true;
251  return;
252  }
253 
254  settings_.service_endpoint = settings_publisher.storage().endpoint();
255  settings_.repo_path = settings_publisher.fqrn() + "/"
256  + settings_publisher.transaction().lease_path();
257  settings_.gw_key_path = settings_publisher.keychain().gw_key_path();
258  settings_.token_path = settings_publisher.transaction()
259  .spool_area()
260  .gw_session_token();
261  settings_.llvl = llvl;
262 
263  // TODO(jblomer): it would be better to actually read & validate the token
264  has_lease_ = FileExists(settings_.token_path);
265  // If a lease is already present, we don't want to remove it automatically
266  keep_alive_ = has_lease_;
267 }
268 
269 
270 void Publisher::Session::SetKeepAlive(bool value) { keep_alive_ = value; }
271 
272 
273 void Publisher::Session::Acquire() {
274  if (has_lease_)
275  return;
276 
278  if (!gw_key.IsValid()) {
279  throw EPublish("cannot read gateway key: " + settings_.gw_key_path,
280  EPublish::kFailGatewayKey);
281  }
282  CurlBuffer buffer;
283  MakeAcquireRequest(gw_key, settings_.repo_path, settings_.service_endpoint,
284  settings_.llvl, &buffer);
285 
286  std::string session_token;
287  LeaseReply rep = ParseAcquireReply(buffer, &session_token, settings_.llvl);
288  switch (rep) {
289  case kLeaseReplySuccess: {
290  has_lease_ = true;
291  bool rvb = SafeWriteToFile(session_token, settings_.token_path, 0600);
292  if (!rvb) {
293  throw EPublish("cannot write session token: " + settings_.token_path);
294  }
295  } break;
296  case kLeaseReplyBusy:
297  throw EPublish("lease path busy", EPublish::kFailLeaseBusy);
298  break;
299  case kLeaseReplyFailure:
300  default:
301  throw EPublish("cannot parse session token", EPublish::kFailLeaseBody);
302  }
303 }
304 
305 void Publisher::Session::Drop() {
306  if (!has_lease_)
307  return;
308  // TODO(jblomer): there might be a better way to distinguish between the
309  // nop-session and a real session
310  if (settings_.service_endpoint.empty())
311  return;
312 
313  std::string token;
314  int fd_token = open(settings_.token_path.c_str(), O_RDONLY);
315  bool rvb = SafeReadToString(fd_token, &token);
316  close(fd_token);
317  if (!rvb) {
318  throw EPublish("cannot read session token: " + settings_.token_path,
319  EPublish::kFailGatewayKey);
320  }
322  if (!gw_key.IsValid()) {
323  throw EPublish("cannot read gateway key: " + settings_.gw_key_path,
324  EPublish::kFailGatewayKey);
325  }
326 
327  CurlBuffer buffer;
328  MakeDropRequest(gw_key, token, settings_.service_endpoint, settings_.llvl,
329  &buffer);
330  LeaseReply rep = ParseDropReply(buffer, settings_.llvl);
331  int rvi = 0;
332  switch (rep) {
333  case kLeaseReplySuccess:
334  has_lease_ = false;
335  rvi = unlink(settings_.token_path.c_str());
336  if (rvi != 0)
337  throw EPublish("cannot delete session token " + settings_.token_path);
338  break;
339  case kLeaseReplyFailure:
340  default:
341  throw EPublish("gateway doesn't recognize the lease or cannot drop it",
342  EPublish::kFailLeaseBody);
343  }
344 }
345 
346 Publisher::Session::~Session() {
347  if (keep_alive_)
348  return;
349 
350  Drop();
351 }
352 
353 } // namespace publish
const SettingsRepository settings_
Definition: repository.h:136
int APIVersion()
Definition: gateway_util.cc:26
std::string id() const
Definition: gateway_util.h:19
static JSON * SearchInObject(const JSON *json_object, const std::string &name, const json_type type)
std::string gw_key_path() const
Definition: settings.h:331
std::string ToString(const bool with_suffix=false) const
Definition: hash.h:241
assert((mem||(size==0))&&"Out Of Memory")
bool SafeWriteToFile(const std::string &content, const std::string &path, int mode)
Definition: posix.cc:2136
static void MakeDropRequest(const gateway::GatewayKey &key, const std::string &session_token, const std::string &repo_service_url, int llvl, CurlBuffer *reply)
static CURL * PrepareCurl(const std::string &method)
std::string secret() const
Definition: gateway_util.h:20
bool FileExists(const std::string &path)
Definition: posix.cc:803
std::string GetHostname()
Definition: posix.cc:762
bool IsValid() const
Definition: json_document.h:26
static JsonDocument * Create(const std::string &text)
void UseSystemCertificatePath()
Definition: ssl.cc:71
size_t RecvCB(void *buffer, size_t size, size_t nmemb, void *userp)
bool IsValid() const
Definition: gateway_util.h:18
string StringifyInt(const int64_t value)
Definition: string.cc:77
bool SafeReadToString(int fd, std::string *final_result)
Definition: posix.cc:2116
const SettingsKeychain & keychain() const
Definition: settings.h:372
string Base64(const string &data)
Definition: string.cc:537
static void MakeAcquireRequest(const gateway::GatewayKey &key, const std::string &repo_path, const std::string &repo_service_url, int llvl, CurlBuffer *buffer)
void HmacString(const std::string &key, const std::string &content, Any *any_digest)
Definition: hash.h:515
GatewayKey ReadGatewayKey(const std::string &key_file_name)
Definition: gateway_util.cc:28
std::string fqrn() const
Definition: settings.h:366
static LeaseReply ParseDropReply(const CurlBuffer &buffer, int llvl)
struct json_value JSON
Definition: helper_allow.cc:11
static void size_t size
Definition: smalloc.h:54
static LeaseReply ParseAcquireReply(const CurlBuffer &buffer, std::string *session_token, int llvl)
const JSON * root() const
Definition: json_document.h:25
CVMFS_EXPORT void LogCvmfs(const LogSource source, const int mask, const char *format,...)
Definition: logging.cc:545