CernVM-FS  2.12.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\" : \"" +
80  StringifyInt(gateway::APIVersion()) + "\", " +
81  "\"hostname\" : \"" + GetHostname() + "\"}";
82 
84  shash::HmacString(key.secret(), payload, &hmac);
87  cs.ApplySslCertificatePath(h_curl);
88 
89  const std::string header_str =
90  std::string("Authorization: ") + key.id() + " " +
91  Base64(hmac.ToString(false));
92  struct curl_slist* auth_header = NULL;
93  auth_header = curl_slist_append(auth_header, header_str.c_str());
94  curl_easy_setopt(h_curl, CURLOPT_HTTPHEADER, auth_header);
95 
96  // Make request to acquire lease from repo services
97  curl_easy_setopt(h_curl, CURLOPT_URL, (repo_service_url + "/leases").c_str());
98  curl_easy_setopt(h_curl, CURLOPT_POSTFIELDSIZE_LARGE,
99  static_cast<curl_off_t>(payload.length()));
100  curl_easy_setopt(h_curl, CURLOPT_POSTFIELDS, payload.c_str());
101  curl_easy_setopt(h_curl, CURLOPT_WRITEFUNCTION, RecvCB);
102  curl_easy_setopt(h_curl, CURLOPT_WRITEDATA, buffer);
103 
104  ret = curl_easy_perform(h_curl);
105  curl_easy_cleanup(h_curl);
106  if (ret != CURLE_OK) {
108  "Make lease acquire request failed: %d. Reply: %s", ret,
109  buffer->data.c_str());
110  throw publish::EPublish("cannot acquire lease",
112  }
113 }
114 
115 // TODO(jblomer): This should eventually also handle the POST request for
116 // committing a transaction
117 static void MakeDropRequest(
118  const gateway::GatewayKey &key,
119  const std::string &session_token,
120  const std::string &repo_service_url,
121  int llvl,
122  CurlBuffer *reply)
123 {
124  CURLcode ret = static_cast<CURLcode>(0);
125 
126  CURL *h_curl = PrepareCurl("DELETE");
127 
128  shash::Any hmac(shash::kSha1);
129  shash::HmacString(key.secret(), session_token, &hmac);
132  cs.ApplySslCertificatePath(h_curl);
133 
134  const std::string header_str =
135  std::string("Authorization: ") + key.id() + " " +
136  Base64(hmac.ToString(false));
137  struct curl_slist *auth_header = NULL;
138  auth_header = curl_slist_append(auth_header, header_str.c_str());
139  curl_easy_setopt(h_curl, CURLOPT_HTTPHEADER, auth_header);
140 
141  curl_easy_setopt(h_curl, CURLOPT_URL,
142  (repo_service_url + "/leases/" + session_token).c_str());
143  curl_easy_setopt(h_curl, CURLOPT_POSTFIELDSIZE_LARGE,
144  static_cast<curl_off_t>(0));
145  curl_easy_setopt(h_curl, CURLOPT_POSTFIELDS, NULL);
146  curl_easy_setopt(h_curl, CURLOPT_WRITEFUNCTION, RecvCB);
147  curl_easy_setopt(h_curl, CURLOPT_WRITEDATA, reply);
148 
149  ret = curl_easy_perform(h_curl);
150  curl_easy_cleanup(h_curl);
151  if (ret != CURLE_OK) {
153  "Make lease drop request failed: %d. Reply: '%s'",
154  ret, reply->data.c_str());
155  throw publish::EPublish("cannot drop lease",
157  }
158 }
159 
161  const CurlBuffer &buffer,
162  std::string *session_token,
163  int llvl)
164 {
165  if (buffer.data.size() == 0 || session_token == NULL) {
166  return kLeaseReplyFailure;
167  }
168 
170  if (!reply.IsValid() || !reply->IsValid()) {
171  return kLeaseReplyFailure;
172  }
173 
174  const JSON *result =
175  JsonDocument::SearchInObject(reply->root(), "status", JSON_STRING);
176  if (result != NULL) {
177  const std::string status = result->string_value;
178  if (status == "ok") {
179  LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Gateway reply: ok");
180  const JSON *token = JsonDocument::SearchInObject(
181  reply->root(), "session_token", JSON_STRING);
182  if (token != NULL) {
183  LogCvmfs(kLogCvmfs, kLogDebug, "Session token: %s",
184  token->string_value);
185  *session_token = token->string_value;
186  return kLeaseReplySuccess;
187  }
188  } else if (status == "path_busy") {
189  const JSON *time_remaining = JsonDocument::SearchInObject(
190  reply->root(), "time_remaining", JSON_STRING);
191  LogCvmfs(kLogCvmfs, llvl | kLogStdout,
192  "Path busy. Time remaining = %s", (time_remaining != NULL) ?
193  time_remaining->string_value : "UNKNOWN");
194  return kLeaseReplyBusy;
195  } else if (status == "error") {
196  const JSON *reason =
197  JsonDocument::SearchInObject(reply->root(), "reason", JSON_STRING);
198  LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Error: '%s'",
199  (reason != NULL) ? reason->string_value : "");
200  } else {
201  LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Unknown reply. Status: %s",
202  status.c_str());
203  }
204  }
205 
206  return kLeaseReplyFailure;
207 }
208 
209 
210 static LeaseReply ParseDropReply(const CurlBuffer &buffer, int llvl) {
211  if (buffer.data.size() == 0) {
212  return kLeaseReplyFailure;
213  }
214 
216  if (!reply.IsValid() || !reply->IsValid()) {
217  return kLeaseReplyFailure;
218  }
219 
220  const JSON *result =
221  JsonDocument::SearchInObject(reply->root(), "status", JSON_STRING);
222  if (result != NULL) {
223  const std::string status = result->string_value;
224  if (status == "ok") {
225  LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Gateway reply: ok");
226  return kLeaseReplySuccess;
227  } else if (status == "invalid_token") {
228  LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Error: invalid session token");
229  } else if (status == "error") {
230  const JSON *reason =
231  JsonDocument::SearchInObject(reply->root(), "reason", JSON_STRING);
232  LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Error from gateway: '%s'",
233  (reason != NULL) ? reason->string_value : "");
234  } else {
235  LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Unknown reply. Status: %s",
236  status.c_str());
237  }
238  }
239 
240  return kLeaseReplyFailure;
241 }
242 
243 } // anonymous namespace
244 
245 namespace publish {
246 
247 Publisher::Session::Session(const Settings &settings_session)
248  : settings_(settings_session)
249  , keep_alive_(false)
250  // TODO(jblomer): it would be better to actually read & validate the token
251  , has_lease_(FileExists(settings_.token_path))
252 {
253 }
254 
255 
256 Publisher::Session::Session(const SettingsPublisher &settings_publisher,
257  int llvl)
258 {
259  keep_alive_ = false;
260  if (settings_publisher.storage().type() != upload::SpoolerDefinition::Gateway)
261  {
262  has_lease_ = true;
263  return;
264  }
265 
266  settings_.service_endpoint = settings_publisher.storage().endpoint();
267  settings_.repo_path = settings_publisher.fqrn() + "/" +
268  settings_publisher.transaction().lease_path();
269  settings_.gw_key_path = settings_publisher.keychain().gw_key_path();
270  settings_.token_path =
271  settings_publisher.transaction().spool_area().gw_session_token();
272  settings_.llvl = llvl;
273 
274  // TODO(jblomer): it would be better to actually read & validate the token
275  has_lease_ = FileExists(settings_.token_path);
276  // If a lease is already present, we don't want to remove it automatically
277  keep_alive_ = has_lease_;
278 }
279 
280 
281 void Publisher::Session::SetKeepAlive(bool value) {
282  keep_alive_ = value;
283 }
284 
285 
286 void Publisher::Session::Acquire() {
287  if (has_lease_)
288  return;
289 
291  if (!gw_key.IsValid()) {
292  throw EPublish("cannot read gateway key: " + settings_.gw_key_path,
293  EPublish::kFailGatewayKey);
294  }
295  CurlBuffer buffer;
296  MakeAcquireRequest(gw_key, settings_.repo_path, settings_.service_endpoint,
297  settings_.llvl, &buffer);
298 
299  std::string session_token;
300  LeaseReply rep = ParseAcquireReply(buffer, &session_token, settings_.llvl);
301  switch (rep) {
302  case kLeaseReplySuccess:
303  {
304  has_lease_ = true;
305  bool rvb = SafeWriteToFile(
306  session_token,
307  settings_.token_path,
308  0600);
309  if (!rvb) {
310  throw EPublish("cannot write session token: " + settings_.token_path);
311  }
312  }
313  break;
314  case kLeaseReplyBusy:
315  throw EPublish("lease path busy", EPublish::kFailLeaseBusy);
316  break;
317  case kLeaseReplyFailure:
318  default:
319  throw EPublish("cannot parse session token", EPublish::kFailLeaseBody);
320  }
321 }
322 
323 void Publisher::Session::Drop() {
324  if (!has_lease_)
325  return;
326  // TODO(jblomer): there might be a better way to distinguish between the
327  // nop-session and a real session
328  if (settings_.service_endpoint.empty())
329  return;
330 
331  std::string token;
332  int fd_token = open(settings_.token_path.c_str(), O_RDONLY);
333  bool rvb = SafeReadToString(fd_token, &token);
334  close(fd_token);
335  if (!rvb) {
336  throw EPublish("cannot read session token: " + settings_.token_path,
337  EPublish::kFailGatewayKey);
338  }
340  if (!gw_key.IsValid()) {
341  throw EPublish("cannot read gateway key: " + settings_.gw_key_path,
342  EPublish::kFailGatewayKey);
343  }
344 
345  CurlBuffer buffer;
346  MakeDropRequest(gw_key, token, settings_.service_endpoint, settings_.llvl,
347  &buffer);
348  LeaseReply rep = ParseDropReply(buffer, settings_.llvl);
349  int rvi = 0;
350  switch (rep) {
351  case kLeaseReplySuccess:
352  has_lease_ = false;
353  rvi = unlink(settings_.token_path.c_str());
354  if (rvi != 0)
355  throw EPublish("cannot delete session token " + settings_.token_path);
356  break;
357  case kLeaseReplyFailure:
358  default:
359  throw EPublish("gateway doesn't recognize the lease or cannot drop it",
360  EPublish::kFailLeaseBody);
361  }
362 }
363 
364 Publisher::Session::~Session() {
365  if (keep_alive_)
366  return;
367 
368  Drop();
369 }
370 
371 } // namespace publish
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:2084
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:791
std::string GetHostname()
Definition: posix.cc:750
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:2068
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: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:528