summaryrefslogtreecommitdiffstats
path: root/src/auth/cephx/CephxServiceHandler.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/auth/cephx/CephxServiceHandler.cc')
-rw-r--r--src/auth/cephx/CephxServiceHandler.cc421
1 files changed, 421 insertions, 0 deletions
diff --git a/src/auth/cephx/CephxServiceHandler.cc b/src/auth/cephx/CephxServiceHandler.cc
new file mode 100644
index 000000000..977a43ad6
--- /dev/null
+++ b/src/auth/cephx/CephxServiceHandler.cc
@@ -0,0 +1,421 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2004-2009 Sage Weil <sage@newdream.net>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+
+#include "CephxServiceHandler.h"
+#include "CephxProtocol.h"
+#include "CephxKeyServer.h"
+#include <errno.h>
+#include <sstream>
+
+#include "include/random.h"
+#include "common/config.h"
+#include "common/debug.h"
+
+#define dout_subsys ceph_subsys_auth
+#undef dout_prefix
+#define dout_prefix *_dout << "cephx server " << entity_name << ": "
+
+using std::dec;
+using std::hex;
+using std::vector;
+
+using ceph::bufferlist;
+using ceph::decode;
+using ceph::encode;
+
+int CephxServiceHandler::do_start_session(
+ bool is_new_global_id,
+ bufferlist *result_bl,
+ AuthCapsInfo *caps)
+{
+ global_id_status = is_new_global_id ? global_id_status_t::NEW_PENDING :
+ global_id_status_t::RECLAIM_PENDING;
+
+ uint64_t min = 1; // always non-zero
+ uint64_t max = std::numeric_limits<uint64_t>::max();
+ server_challenge = ceph::util::generate_random_number<uint64_t>(min, max);
+ ldout(cct, 10) << "start_session server_challenge "
+ << hex << server_challenge << dec << dendl;
+
+ CephXServerChallenge ch;
+ ch.server_challenge = server_challenge;
+ encode(ch, *result_bl);
+ return 0;
+}
+
+int CephxServiceHandler::verify_old_ticket(
+ const CephXAuthenticate& req,
+ CephXServiceTicketInfo& old_ticket_info,
+ bool& should_enc_ticket)
+{
+ ldout(cct, 20) << " checking old_ticket: secret_id="
+ << req.old_ticket.secret_id
+ << " len=" << req.old_ticket.blob.length()
+ << ", old_ticket_may_be_omitted="
+ << req.old_ticket_may_be_omitted << dendl;
+ ceph_assert(global_id_status != global_id_status_t::NONE);
+ if (global_id_status == global_id_status_t::NEW_PENDING) {
+ // old ticket is not needed
+ if (req.old_ticket.blob.length()) {
+ ldout(cct, 0) << " superfluous ticket presented" << dendl;
+ return -EINVAL;
+ }
+ if (req.old_ticket_may_be_omitted) {
+ ldout(cct, 10) << " new global_id " << global_id
+ << " (unexposed legacy client)" << dendl;
+ global_id_status = global_id_status_t::NEW_NOT_EXPOSED;
+ } else {
+ ldout(cct, 10) << " new global_id " << global_id << dendl;
+ global_id_status = global_id_status_t::NEW_OK;
+ }
+ return 0;
+ }
+
+ if (!req.old_ticket.blob.length()) {
+ // old ticket is needed but not presented
+ if (cct->_conf->auth_allow_insecure_global_id_reclaim &&
+ req.old_ticket_may_be_omitted) {
+ ldout(cct, 10) << " allowing reclaim of global_id " << global_id
+ << " with no ticket presented (legacy client, auth_allow_insecure_global_id_reclaim=true)"
+ << dendl;
+ global_id_status = global_id_status_t::RECLAIM_INSECURE;
+ return 0;
+ }
+ ldout(cct, 0) << " attempt to reclaim global_id " << global_id
+ << " without presenting ticket" << dendl;
+ return -EACCES;
+ }
+
+ if (!cephx_decode_ticket(cct, key_server, CEPH_ENTITY_TYPE_AUTH,
+ req.old_ticket, old_ticket_info)) {
+ if (cct->_conf->auth_allow_insecure_global_id_reclaim &&
+ req.old_ticket_may_be_omitted) {
+ ldout(cct, 10) << " allowing reclaim of global_id " << global_id
+ << " using bad ticket (legacy client, auth_allow_insecure_global_id_reclaim=true)"
+ << dendl;
+ global_id_status = global_id_status_t::RECLAIM_INSECURE;
+ return 0;
+ }
+ ldout(cct, 0) << " attempt to reclaim global_id " << global_id
+ << " using bad ticket" << dendl;
+ return -EACCES;
+ }
+ ldout(cct, 20) << " decoded old_ticket: global_id="
+ << old_ticket_info.ticket.global_id << dendl;
+ if (global_id != old_ticket_info.ticket.global_id) {
+ if (cct->_conf->auth_allow_insecure_global_id_reclaim &&
+ req.old_ticket_may_be_omitted) {
+ ldout(cct, 10) << " allowing reclaim of global_id " << global_id
+ << " using mismatching ticket (legacy client, auth_allow_insecure_global_id_reclaim=true)"
+ << dendl;
+ global_id_status = global_id_status_t::RECLAIM_INSECURE;
+ return 0;
+ }
+ ldout(cct, 0) << " attempt to reclaim global_id " << global_id
+ << " using mismatching ticket" << dendl;
+ return -EACCES;
+ }
+ ldout(cct, 10) << " allowing reclaim of global_id " << global_id
+ << " (valid ticket presented, will encrypt new ticket)"
+ << dendl;
+ global_id_status = global_id_status_t::RECLAIM_OK;
+ should_enc_ticket = true;
+ return 0;
+}
+
+int CephxServiceHandler::handle_request(
+ bufferlist::const_iterator& indata,
+ size_t connection_secret_required_len,
+ bufferlist *result_bl,
+ AuthCapsInfo *caps,
+ CryptoKey *psession_key,
+ std::string *pconnection_secret)
+{
+ int ret = 0;
+
+ struct CephXRequestHeader cephx_header;
+ try {
+ decode(cephx_header, indata);
+ } catch (ceph::buffer::error& e) {
+ ldout(cct, 0) << __func__ << " failed to decode CephXRequestHeader: "
+ << e.what() << dendl;
+ return -EPERM;
+ }
+
+ switch (cephx_header.request_type) {
+ case CEPHX_GET_AUTH_SESSION_KEY:
+ {
+ ldout(cct, 10) << "handle_request get_auth_session_key for "
+ << entity_name << dendl;
+
+ CephXAuthenticate req;
+ try {
+ decode(req, indata);
+ } catch (ceph::buffer::error& e) {
+ ldout(cct, 0) << __func__ << " failed to decode CephXAuthenticate: "
+ << e.what() << dendl;
+ ret = -EPERM;
+ break;
+ }
+
+ EntityAuth eauth;
+ if (!key_server->get_auth(entity_name, eauth)) {
+ ldout(cct, 0) << "couldn't find entity name: " << entity_name << dendl;
+ ret = -EACCES;
+ break;
+ }
+
+ if (!server_challenge) {
+ ret = -EACCES;
+ break;
+ }
+
+ uint64_t expected_key;
+ CryptoKey *used_key = &eauth.key;
+ std::string error;
+ cephx_calc_client_server_challenge(cct, eauth.key, server_challenge,
+ req.client_challenge, &expected_key, error);
+ if ((!error.empty() || req.key != expected_key) &&
+ !eauth.pending_key.empty()) {
+ ldout(cct, 10) << "normal key failed for " << entity_name
+ << ", trying pending_key" << dendl;
+ // try pending_key instead
+ error.clear();
+ cephx_calc_client_server_challenge(cct, eauth.pending_key,
+ server_challenge,
+ req.client_challenge, &expected_key,
+ error);
+ if (error.empty()) {
+ used_key = &eauth.pending_key;
+ key_server->note_used_pending_key(entity_name, eauth.pending_key);
+ }
+ }
+ if (!error.empty()) {
+ ldout(cct, 0) << " cephx_calc_client_server_challenge error: " << error << dendl;
+ ret = -EACCES;
+ break;
+ }
+
+ ldout(cct, 20) << " checking key: req.key=" << hex << req.key
+ << " expected_key=" << expected_key << dec << dendl;
+ if (req.key != expected_key) {
+ ldout(cct, 0) << " unexpected key: req.key=" << hex << req.key
+ << " expected_key=" << expected_key << dec << dendl;
+ ret = -EACCES;
+ break;
+ }
+
+ CryptoKey session_key;
+ CephXSessionAuthInfo info;
+ bool should_enc_ticket = false;
+
+ CephXServiceTicketInfo old_ticket_info;
+ ret = verify_old_ticket(req, old_ticket_info, should_enc_ticket);
+ if (ret) {
+ ldout(cct, 0) << " could not verify old ticket" << dendl;
+ break;
+ }
+
+ double ttl;
+ if (!key_server->get_service_secret(CEPH_ENTITY_TYPE_AUTH,
+ info.service_secret, info.secret_id,
+ ttl)) {
+ ldout(cct, 0) << " could not get service secret for auth subsystem" << dendl;
+ ret = -EIO;
+ break;
+ }
+
+ info.service_id = CEPH_ENTITY_TYPE_AUTH;
+ info.ticket.name = entity_name;
+ info.ticket.global_id = global_id;
+ info.ticket.init_timestamps(ceph_clock_now(), ttl);
+ info.validity.set_from_double(ttl);
+
+ key_server->generate_secret(session_key);
+
+ info.session_key = session_key;
+ if (psession_key) {
+ *psession_key = session_key;
+ }
+
+ vector<CephXSessionAuthInfo> info_vec;
+ info_vec.push_back(info);
+
+ build_cephx_response_header(cephx_header.request_type, 0, *result_bl);
+ if (!cephx_build_service_ticket_reply(
+ cct, *used_key, info_vec, should_enc_ticket,
+ old_ticket_info.session_key, *result_bl)) {
+ ret = -EIO;
+ break;
+ }
+
+ if (!key_server->get_service_caps(entity_name, CEPH_ENTITY_TYPE_MON,
+ *caps)) {
+ ldout(cct, 0) << " could not get mon caps for " << entity_name << dendl;
+ ret = -EACCES;
+ break;
+ } else {
+ char *caps_str = caps->caps.c_str();
+ if (!caps_str || !caps_str[0]) {
+ ldout(cct,0) << "mon caps null for " << entity_name << dendl;
+ ret = -EACCES;
+ break;
+ }
+
+ if (req.other_keys) {
+ // nautilus+ client
+ // generate a connection_secret
+ bufferlist cbl;
+ if (pconnection_secret) {
+ pconnection_secret->resize(connection_secret_required_len);
+ if (connection_secret_required_len) {
+ cct->random()->get_bytes(pconnection_secret->data(),
+ connection_secret_required_len);
+ }
+ std::string err;
+ if (encode_encrypt(cct, *pconnection_secret, session_key, cbl,
+ err)) {
+ lderr(cct) << __func__ << " failed to encrypt connection secret, "
+ << err << dendl;
+ ret = -EACCES;
+ break;
+ }
+ }
+ encode(cbl, *result_bl);
+ // provide requested service tickets at the same time
+ vector<CephXSessionAuthInfo> info_vec;
+ for (uint32_t service_id = 1; service_id <= req.other_keys;
+ service_id <<= 1) {
+ // skip CEPH_ENTITY_TYPE_AUTH: auth ticket is already encoded
+ // (possibly encrypted with the old session key)
+ if ((req.other_keys & service_id) &&
+ service_id != CEPH_ENTITY_TYPE_AUTH) {
+ ldout(cct, 10) << " adding key for service "
+ << ceph_entity_type_name(service_id) << dendl;
+ CephXSessionAuthInfo svc_info;
+ key_server->build_session_auth_info(
+ service_id,
+ info.ticket,
+ svc_info);
+ info_vec.push_back(svc_info);
+ }
+ }
+ bufferlist extra;
+ if (!info_vec.empty()) {
+ CryptoKey no_key;
+ cephx_build_service_ticket_reply(
+ cct, session_key, info_vec, false, no_key, extra);
+ }
+ encode(extra, *result_bl);
+ }
+
+ // caller should try to finish authentication
+ ret = 1;
+ }
+ }
+ break;
+
+ case CEPHX_GET_PRINCIPAL_SESSION_KEY:
+ {
+ ldout(cct, 10) << "handle_request get_principal_session_key" << dendl;
+
+ bufferlist tmp_bl;
+ CephXServiceTicketInfo auth_ticket_info;
+ // note: no challenge here.
+ if (!cephx_verify_authorizer(
+ cct, *key_server, indata, 0, auth_ticket_info, nullptr,
+ nullptr,
+ &tmp_bl)) {
+ ret = -EACCES;
+ break;
+ }
+
+ CephXServiceTicketRequest ticket_req;
+ try {
+ decode(ticket_req, indata);
+ } catch (ceph::buffer::error& e) {
+ ldout(cct, 0) << __func__
+ << " failed to decode CephXServiceTicketRequest: "
+ << e.what() << dendl;
+ ret = -EPERM;
+ break;
+ }
+ ldout(cct, 10) << " ticket_req.keys = " << ticket_req.keys << dendl;
+
+ ret = 0;
+ vector<CephXSessionAuthInfo> info_vec;
+ int found_services = 0;
+ int service_err = 0;
+ for (uint32_t service_id = 1; service_id <= ticket_req.keys;
+ service_id <<= 1) {
+ // skip CEPH_ENTITY_TYPE_AUTH: auth ticket must be obtained with
+ // CEPHX_GET_AUTH_SESSION_KEY
+ if ((ticket_req.keys & service_id) &&
+ service_id != CEPH_ENTITY_TYPE_AUTH) {
+ ldout(cct, 10) << " adding key for service "
+ << ceph_entity_type_name(service_id) << dendl;
+ CephXSessionAuthInfo info;
+ int r = key_server->build_session_auth_info(
+ service_id,
+ auth_ticket_info.ticket, // parent ticket (client's auth ticket)
+ info);
+ // tolerate missing MGR rotating key for the purposes of upgrades.
+ if (r < 0) {
+ ldout(cct, 10) << " missing key for service "
+ << ceph_entity_type_name(service_id) << dendl;
+ service_err = r;
+ continue;
+ }
+ info_vec.push_back(info);
+ ++found_services;
+ }
+ }
+ if (!found_services && service_err) {
+ ldout(cct, 10) << __func__ << " did not find any service keys" << dendl;
+ ret = service_err;
+ }
+ CryptoKey no_key;
+ build_cephx_response_header(cephx_header.request_type, ret, *result_bl);
+ cephx_build_service_ticket_reply(cct, auth_ticket_info.session_key,
+ info_vec, false, no_key, *result_bl);
+ }
+ break;
+
+ case CEPHX_GET_ROTATING_KEY:
+ {
+ ldout(cct, 10) << "handle_request getting rotating secret for "
+ << entity_name << dendl;
+ build_cephx_response_header(cephx_header.request_type, 0, *result_bl);
+ if (!key_server->get_rotating_encrypted(entity_name, *result_bl)) {
+ ret = -EACCES;
+ break;
+ }
+ }
+ break;
+
+ default:
+ ldout(cct, 10) << "handle_request unknown op " << cephx_header.request_type << dendl;
+ return -EINVAL;
+ }
+ return ret;
+}
+
+void CephxServiceHandler::build_cephx_response_header(int request_type, int status, bufferlist& bl)
+{
+ struct CephXResponseHeader header;
+ header.request_type = request_type;
+ header.status = status;
+ encode(header, bl);
+}