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