diff options
Diffstat (limited to 'src/auth/cephx')
-rw-r--r-- | src/auth/cephx/CephxAuthorizeHandler.cc | 52 | ||||
-rw-r--r-- | src/auth/cephx/CephxAuthorizeHandler.h | 40 | ||||
-rw-r--r-- | src/auth/cephx/CephxClientHandler.cc | 312 | ||||
-rw-r--r-- | src/auth/cephx/CephxClientHandler.h | 78 | ||||
-rw-r--r-- | src/auth/cephx/CephxKeyServer.cc | 463 | ||||
-rw-r--r-- | src/auth/cephx/CephxKeyServer.h | 320 | ||||
-rw-r--r-- | src/auth/cephx/CephxProtocol.cc | 591 | ||||
-rw-r--r-- | src/auth/cephx/CephxProtocol.h | 524 | ||||
-rw-r--r-- | src/auth/cephx/CephxServiceHandler.cc | 403 | ||||
-rw-r--r-- | src/auth/cephx/CephxServiceHandler.h | 54 | ||||
-rw-r--r-- | src/auth/cephx/CephxSessionHandler.cc | 182 | ||||
-rw-r--r-- | src/auth/cephx/CephxSessionHandler.h | 44 |
12 files changed, 3063 insertions, 0 deletions
diff --git a/src/auth/cephx/CephxAuthorizeHandler.cc b/src/auth/cephx/CephxAuthorizeHandler.cc new file mode 100644 index 00000000..6684e164 --- /dev/null +++ b/src/auth/cephx/CephxAuthorizeHandler.cc @@ -0,0 +1,52 @@ +#include "CephxProtocol.h" +#include "CephxAuthorizeHandler.h" +#include "common/dout.h" + +#define dout_subsys ceph_subsys_auth + + + +bool CephxAuthorizeHandler::verify_authorizer( + CephContext *cct, + KeyStore *keys, + const bufferlist& authorizer_data, + size_t connection_secret_required_len, + bufferlist *authorizer_reply, + EntityName *entity_name, + uint64_t *global_id, + AuthCapsInfo *caps_info, + CryptoKey *session_key, + std::string *connection_secret, + std::unique_ptr<AuthAuthorizerChallenge> *challenge) +{ + auto iter = authorizer_data.cbegin(); + + if (!authorizer_data.length()) { + ldout(cct, 1) << "verify authorizer, authorizer_data.length()=0" << dendl; + return false; + } + + CephXServiceTicketInfo auth_ticket_info; + + bool isvalid = cephx_verify_authorizer(cct, keys, iter, + connection_secret_required_len, + auth_ticket_info, + challenge, connection_secret, + authorizer_reply); + + if (isvalid) { + *caps_info = auth_ticket_info.ticket.caps; + *entity_name = auth_ticket_info.ticket.name; + *global_id = auth_ticket_info.ticket.global_id; + *session_key = auth_ticket_info.session_key; + } + + return isvalid; +} + +// Return type of crypto used for this session's data; for cephx, symmetric authentication + +int CephxAuthorizeHandler::authorizer_session_crypto() +{ + return SESSION_SYMMETRIC_AUTHENTICATE; +} diff --git a/src/auth/cephx/CephxAuthorizeHandler.h b/src/auth/cephx/CephxAuthorizeHandler.h new file mode 100644 index 00000000..769426c4 --- /dev/null +++ b/src/auth/cephx/CephxAuthorizeHandler.h @@ -0,0 +1,40 @@ +// -*- 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. + * + */ + +#ifndef CEPH_CEPHXAUTHORIZEHANDLER_H +#define CEPH_CEPHXAUTHORIZEHANDLER_H + +#include "auth/AuthAuthorizeHandler.h" + +class CephContext; + +struct CephxAuthorizeHandler : public AuthAuthorizeHandler { + bool verify_authorizer( + CephContext *cct, + KeyStore *keys, + const bufferlist& authorizer_data, + size_t connection_secret_required_len, + bufferlist *authorizer_reply, + EntityName *entity_name, + uint64_t *global_id, + AuthCapsInfo *caps_info, + CryptoKey *session_key, + std::string *connection_secret, + std::unique_ptr<AuthAuthorizerChallenge> *challenge) override; + int authorizer_session_crypto() override; +}; + + + +#endif diff --git a/src/auth/cephx/CephxClientHandler.cc b/src/auth/cephx/CephxClientHandler.cc new file mode 100644 index 00000000..abdb2f2c --- /dev/null +++ b/src/auth/cephx/CephxClientHandler.cc @@ -0,0 +1,312 @@ +// -*- 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 <errno.h> + +#include "CephxClientHandler.h" +#include "CephxProtocol.h" + +#include "auth/KeyRing.h" +#include "include/random.h" +#include "common/ceph_context.h" +#include "common/config.h" +#include "common/dout.h" + +#define dout_subsys ceph_subsys_auth +#undef dout_prefix +#define dout_prefix *_dout << "cephx client: " + +void CephxClientHandler::reset() +{ + ldout(cct,10) << __func__ << dendl; + starting = true; + server_challenge = 0; +} + +int CephxClientHandler::build_request(bufferlist& bl) const +{ + ldout(cct, 10) << "build_request" << dendl; + + if (need & CEPH_ENTITY_TYPE_AUTH) { + /* authenticate */ + CephXRequestHeader header; + header.request_type = CEPHX_GET_AUTH_SESSION_KEY; + encode(header, bl); + + CryptoKey secret; + const bool got = keyring->get_secret(cct->_conf->name, secret); + if (!got) { + ldout(cct, 20) << "no secret found for entity: " << cct->_conf->name << dendl; + return -ENOENT; + } + + // is the key OK? + if (!secret.get_secret().length()) { + ldout(cct, 20) << "secret for entity " << cct->_conf->name << " is invalid" << dendl; + return -EINVAL; + } + + CephXAuthenticate req; + req.client_challenge = ceph::util::generate_random_number<uint64_t>(); + std::string error; + cephx_calc_client_server_challenge(cct, secret, server_challenge, + req.client_challenge, &req.key, error); + if (!error.empty()) { + ldout(cct, 20) << "cephx_calc_client_server_challenge error: " << error << dendl; + return -EIO; + } + + req.old_ticket = ticket_handler->ticket; + + // for nautilus+ servers: request other keys at the same time + req.other_keys = need; + + if (req.old_ticket.blob.length()) { + ldout(cct, 20) << "old ticket len=" << req.old_ticket.blob.length() << dendl; + } + + encode(req, bl); + + ldout(cct, 10) << "get auth session key: client_challenge " + << std::hex << req.client_challenge << std::dec << dendl; + return 0; + } + + if (_need_tickets()) { + /* get service tickets */ + ldout(cct, 10) << "get service keys: want=" << want << " need=" << need << " have=" << have << dendl; + + CephXRequestHeader header; + header.request_type = CEPHX_GET_PRINCIPAL_SESSION_KEY; + encode(header, bl); + + CephXAuthorizer *authorizer = ticket_handler->build_authorizer(global_id); + if (!authorizer) + return -EINVAL; + bl.claim_append(authorizer->bl); + delete authorizer; + + CephXServiceTicketRequest req; + req.keys = need; + encode(req, bl); + } + + return 0; +} + +bool CephxClientHandler::_need_tickets() const +{ + // do not bother (re)requesting tickets if we *only* need the MGR + // ticket; that can happen during an upgrade and we want to avoid a + // loop. we'll end up re-requesting it later when the secrets + // rotating. + return need && need != CEPH_ENTITY_TYPE_MGR; +} + +int CephxClientHandler::handle_response( + int ret, + bufferlist::const_iterator& indata, + CryptoKey *session_key, + std::string *connection_secret) +{ + ldout(cct, 10) << this << " handle_response ret = " << ret << dendl; + + if (ret < 0) + return ret; // hrm! + + if (starting) { + CephXServerChallenge ch; + try { + decode(ch, indata); + } catch (buffer::error& e) { + ldout(cct, 1) << __func__ << " failed to decode CephXServerChallenge: " + << e.what() << dendl; + return -EPERM; + } + server_challenge = ch.server_challenge; + ldout(cct, 10) << " got initial server challenge " + << std::hex << server_challenge << std::dec << dendl; + starting = false; + + tickets.invalidate_ticket(CEPH_ENTITY_TYPE_AUTH); + return -EAGAIN; + } + + struct CephXResponseHeader header; + try { + decode(header, indata); + } catch (buffer::error& e) { + ldout(cct, 1) << __func__ << " failed to decode CephXResponseHeader: " + << e.what() << dendl; + return -EPERM; + } + + switch (header.request_type) { + case CEPHX_GET_AUTH_SESSION_KEY: + { + ldout(cct, 10) << " get_auth_session_key" << dendl; + CryptoKey secret; + const bool got = keyring->get_secret(cct->_conf->name, secret); + if (!got) { + ldout(cct, 0) << "key not found for " << cct->_conf->name << dendl; + return -ENOENT; + } + + if (!tickets.verify_service_ticket_reply(secret, indata)) { + ldout(cct, 0) << "could not verify service_ticket reply" << dendl; + return -EPERM; + } + ldout(cct, 10) << " want=" << want << " need=" << need << " have=" << have << dendl; + if (!indata.end()) { + bufferlist cbl, extra_tickets; + try { + decode(cbl, indata); + decode(extra_tickets, indata); + } catch (buffer::error& e) { + ldout(cct, 1) << __func__ << " failed to decode tickets: " + << e.what() << dendl; + return -EPERM; + } + ldout(cct, 10) << " got connection bl " << cbl.length() + << " and extra tickets " << extra_tickets.length() + << dendl; + if (session_key && connection_secret) { + CephXTicketHandler& ticket_handler = + tickets.get_handler(CEPH_ENTITY_TYPE_AUTH); + if (session_key) { + *session_key = ticket_handler.session_key; + } + if (cbl.length() && connection_secret) { + auto p = cbl.cbegin(); + string err; + if (decode_decrypt(cct, *connection_secret, *session_key, p, + err)) { + lderr(cct) << __func__ << " failed to decrypt connection_secret" + << dendl; + } else { + ldout(cct, 10) << " got connection_secret " + << connection_secret->size() << " bytes" << dendl; + } + } + if (extra_tickets.length()) { + auto p = extra_tickets.cbegin(); + if (!tickets.verify_service_ticket_reply( + *session_key, p)) { + lderr(cct) << "could not verify extra service_tickets" << dendl; + } else { + ldout(cct, 10) << " got extra service_tickets" << dendl; + } + } + } + } + validate_tickets(); + if (_need_tickets()) + ret = -EAGAIN; + else + ret = 0; + } + break; + + case CEPHX_GET_PRINCIPAL_SESSION_KEY: + { + CephXTicketHandler& ticket_handler = tickets.get_handler(CEPH_ENTITY_TYPE_AUTH); + ldout(cct, 10) << " get_principal_session_key session_key " << ticket_handler.session_key << dendl; + + if (!tickets.verify_service_ticket_reply(ticket_handler.session_key, indata)) { + ldout(cct, 0) << "could not verify service_ticket reply" << dendl; + return -EPERM; + } + validate_tickets(); + if (!_need_tickets()) { + ret = 0; + } + } + break; + + case CEPHX_GET_ROTATING_KEY: + { + ldout(cct, 10) << " get_rotating_key" << dendl; + if (rotating_secrets) { + RotatingSecrets secrets; + CryptoKey secret_key; + const bool got = keyring->get_secret(cct->_conf->name, secret_key); + if (!got) { + ldout(cct, 0) << "key not found for " << cct->_conf->name << dendl; + return -ENOENT; + } + std::string error; + if (decode_decrypt(cct, secrets, secret_key, indata, error)) { + ldout(cct, 0) << "could not set rotating key: decode_decrypt failed. error:" + << error << dendl; + return -EINVAL; + } else { + rotating_secrets->set_secrets(std::move(secrets)); + } + } + } + break; + + default: + ldout(cct, 0) << " unknown request_type " << header.request_type << dendl; + ceph_abort(); + } + return ret; +} + + +AuthAuthorizer *CephxClientHandler::build_authorizer(uint32_t service_id) const +{ + ldout(cct, 10) << "build_authorizer for service " << ceph_entity_type_name(service_id) << dendl; + return tickets.build_authorizer(service_id); +} + + +bool CephxClientHandler::build_rotating_request(bufferlist& bl) const +{ + ldout(cct, 10) << "build_rotating_request" << dendl; + CephXRequestHeader header; + header.request_type = CEPHX_GET_ROTATING_KEY; + encode(header, bl); + return true; +} + +void CephxClientHandler::prepare_build_request() +{ + ldout(cct, 10) << "validate_tickets: want=" << want << " need=" << need + << " have=" << have << dendl; + validate_tickets(); + ldout(cct, 10) << "want=" << want << " need=" << need << " have=" << have + << dendl; + + ticket_handler = &(tickets.get_handler(CEPH_ENTITY_TYPE_AUTH)); +} + +void CephxClientHandler::validate_tickets() +{ + // lock should be held for write + tickets.validate_tickets(want, have, need); +} + +bool CephxClientHandler::need_tickets() +{ + validate_tickets(); + + ldout(cct, 20) << "need_tickets: want=" << want + << " have=" << have + << " need=" << need + << dendl; + + return _need_tickets(); +} diff --git a/src/auth/cephx/CephxClientHandler.h b/src/auth/cephx/CephxClientHandler.h new file mode 100644 index 00000000..b99691af --- /dev/null +++ b/src/auth/cephx/CephxClientHandler.h @@ -0,0 +1,78 @@ +// -*- 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. + * + */ + +#ifndef CEPH_CEPHXCLIENTHANDLER_H +#define CEPH_CEPHXCLIENTHANDLER_H + +#include "auth/AuthClientHandler.h" +#include "CephxProtocol.h" +#include "auth/RotatingKeyRing.h" + +class CephContext; +class KeyRing; + +class CephxClientHandler : public AuthClientHandler { + bool starting; + + /* envelope protocol parameters */ + uint64_t server_challenge; + + CephXTicketManager tickets; + CephXTicketHandler* ticket_handler; + + RotatingKeyRing* rotating_secrets; + KeyRing *keyring; + +public: + CephxClientHandler(CephContext *cct_, + RotatingKeyRing *rsecrets) + : AuthClientHandler(cct_), + starting(false), + server_challenge(0), + tickets(cct_), + ticket_handler(NULL), + rotating_secrets(rsecrets), + keyring(rsecrets->get_keyring()) + { + reset(); + } + + CephxClientHandler* clone() const override { + return new CephxClientHandler(*this); + } + + void reset() override; + void prepare_build_request() override; + int build_request(bufferlist& bl) const override; + int handle_response(int ret, bufferlist::const_iterator& iter, + CryptoKey *session_key, + std::string *connection_secret) override; + bool build_rotating_request(bufferlist& bl) const override; + + int get_protocol() const override { return CEPH_AUTH_CEPHX; } + + AuthAuthorizer *build_authorizer(uint32_t service_id) const override; + + bool need_tickets() override; + + void set_global_id(uint64_t id) override { + global_id = id; + tickets.global_id = id; + } +private: + void validate_tickets() override; + bool _need_tickets() const; +}; + +#endif diff --git a/src/auth/cephx/CephxKeyServer.cc b/src/auth/cephx/CephxKeyServer.cc new file mode 100644 index 00000000..0fcf4536 --- /dev/null +++ b/src/auth/cephx/CephxKeyServer.cc @@ -0,0 +1,463 @@ +// -*- 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 "common/config.h" +#include "CephxKeyServer.h" +#include "common/dout.h" +#include <sstream> + +#define dout_subsys ceph_subsys_auth +#undef dout_prefix +#define dout_prefix *_dout << "cephx keyserverdata: " + +bool KeyServerData::get_service_secret(CephContext *cct, uint32_t service_id, + CryptoKey& secret, uint64_t& secret_id, + double& ttl) const +{ + map<uint32_t, RotatingSecrets>::const_iterator iter = + rotating_secrets.find(service_id); + if (iter == rotating_secrets.end()) { + ldout(cct, 10) << "get_service_secret service " << ceph_entity_type_name(service_id) << " not found " << dendl; + return false; + } + + const RotatingSecrets& secrets = iter->second; + + // second to oldest, unless it's expired + map<uint64_t, ExpiringCryptoKey>::const_iterator riter = + secrets.secrets.begin(); + if (secrets.secrets.size() > 1) + ++riter; + + utime_t now = ceph_clock_now(); + if (riter->second.expiration < now) + ++riter; // "current" key has expired, use "next" key instead + + secret_id = riter->first; + secret = riter->second.key; + + // ttl may have just been increased by the user + // cap it by expiration of "next" key to prevent handing out a ticket + // with a bogus, possibly way into the future, validity + ttl = service_id == CEPH_ENTITY_TYPE_AUTH ? + cct->_conf->auth_mon_ticket_ttl : cct->_conf->auth_service_ticket_ttl; + ttl = min(ttl, static_cast<double>( + secrets.secrets.rbegin()->second.expiration - now)); + + ldout(cct, 30) << __func__ << " service " + << ceph_entity_type_name(service_id) << " secret_id " + << secret_id << " " << riter->second << " ttl " << ttl + << dendl; + return true; +} + +bool KeyServerData::get_service_secret(CephContext *cct, uint32_t service_id, + uint64_t secret_id, CryptoKey& secret) const +{ + map<uint32_t, RotatingSecrets>::const_iterator iter = + rotating_secrets.find(service_id); + if (iter == rotating_secrets.end()) + return false; + + const RotatingSecrets& secrets = iter->second; + map<uint64_t, ExpiringCryptoKey>::const_iterator riter = + secrets.secrets.find(secret_id); + + if (riter == secrets.secrets.end()) { + ldout(cct, 10) << "get_service_secret service " << ceph_entity_type_name(service_id) + << " secret " << secret_id << " not found" << dendl; + ldout(cct, 30) << " I have:" << dendl; + for (map<uint64_t, ExpiringCryptoKey>::const_iterator iter = + secrets.secrets.begin(); + iter != secrets.secrets.end(); + ++iter) + ldout(cct, 30) << " id " << iter->first << " " << iter->second << dendl; + return false; + } + + secret = riter->second.key; + + return true; +} +bool KeyServerData::get_auth(const EntityName& name, EntityAuth& auth) const { + map<EntityName, EntityAuth>::const_iterator iter = secrets.find(name); + if (iter != secrets.end()) { + auth = iter->second; + return true; + } + return extra_secrets->get_auth(name, auth); +} + +bool KeyServerData::get_secret(const EntityName& name, CryptoKey& secret) const { + map<EntityName, EntityAuth>::const_iterator iter = secrets.find(name); + if (iter != secrets.end()) { + secret = iter->second.key; + return true; + } + return extra_secrets->get_secret(name, secret); +} + +bool KeyServerData::get_caps(CephContext *cct, const EntityName& name, + const string& type, AuthCapsInfo& caps_info) const +{ + caps_info.allow_all = false; + + ldout(cct, 10) << "get_caps: name=" << name.to_str() << dendl; + map<EntityName, EntityAuth>::const_iterator iter = secrets.find(name); + if (iter != secrets.end()) { + ldout(cct, 10) << "get_secret: num of caps=" << iter->second.caps.size() << dendl; + map<string, bufferlist>::const_iterator capsiter = iter->second.caps.find(type); + if (capsiter != iter->second.caps.end()) { + caps_info.caps = capsiter->second; + } + return true; + } + + return extra_secrets->get_caps(name, type, caps_info); +} + + +#undef dout_prefix +#define dout_prefix *_dout << "cephx keyserver: " + + +KeyServer::KeyServer(CephContext *cct_, KeyRing *extra_secrets) + : cct(cct_), + data(extra_secrets), + lock{ceph::make_mutex("KeyServer::lock")} +{ +} + +int KeyServer::start_server() +{ + std::scoped_lock l{lock}; + + _check_rotating_secrets(); + _dump_rotating_secrets(); + return 0; +} + +bool KeyServer::_check_rotating_secrets() +{ + ldout(cct, 10) << "_check_rotating_secrets" << dendl; + + int added = 0; + added += _rotate_secret(CEPH_ENTITY_TYPE_AUTH); + added += _rotate_secret(CEPH_ENTITY_TYPE_MON); + added += _rotate_secret(CEPH_ENTITY_TYPE_OSD); + added += _rotate_secret(CEPH_ENTITY_TYPE_MDS); + added += _rotate_secret(CEPH_ENTITY_TYPE_MGR); + + if (added) { + ldout(cct, 10) << __func__ << " added " << added << dendl; + data.rotating_ver++; + //data.next_rotating_time = ceph_clock_now(cct); + //data.next_rotating_time += std::min(cct->_conf->auth_mon_ticket_ttl, cct->_conf->auth_service_ticket_ttl); + _dump_rotating_secrets(); + return true; + } + return false; +} + +void KeyServer::_dump_rotating_secrets() +{ + ldout(cct, 30) << "_dump_rotating_secrets" << dendl; + for (map<uint32_t, RotatingSecrets>::iterator iter = data.rotating_secrets.begin(); + iter != data.rotating_secrets.end(); + ++iter) { + RotatingSecrets& key = iter->second; + for (map<uint64_t, ExpiringCryptoKey>::iterator mapiter = key.secrets.begin(); + mapiter != key.secrets.end(); + ++mapiter) + ldout(cct, 30) << "service " << ceph_entity_type_name(iter->first) + << " id " << mapiter->first + << " key " << mapiter->second << dendl; + } +} + +int KeyServer::_rotate_secret(uint32_t service_id) +{ + RotatingSecrets& r = data.rotating_secrets[service_id]; + int added = 0; + utime_t now = ceph_clock_now(); + double ttl = service_id == CEPH_ENTITY_TYPE_AUTH ? cct->_conf->auth_mon_ticket_ttl : cct->_conf->auth_service_ticket_ttl; + + while (r.need_new_secrets(now)) { + ExpiringCryptoKey ek; + generate_secret(ek.key); + if (r.empty()) { + ek.expiration = now; + } else { + utime_t next_ttl = now; + next_ttl += ttl; + ek.expiration = std::max(next_ttl, r.next().expiration); + } + ek.expiration += ttl; + uint64_t secret_id = r.add(ek); + ldout(cct, 10) << "_rotate_secret adding " << ceph_entity_type_name(service_id) << dendl; + ldout(cct, 30) << "_rotate_secret adding " << ceph_entity_type_name(service_id) + << " id " << secret_id << " " << ek + << dendl; + added++; + } + return added; +} + +bool KeyServer::get_secret(const EntityName& name, CryptoKey& secret) const +{ + std::scoped_lock l{lock}; + return data.get_secret(name, secret); +} + +bool KeyServer::get_auth(const EntityName& name, EntityAuth& auth) const +{ + std::scoped_lock l{lock}; + return data.get_auth(name, auth); +} + +bool KeyServer::get_caps(const EntityName& name, const string& type, + AuthCapsInfo& caps_info) const +{ + std::scoped_lock l{lock}; + + return data.get_caps(cct, name, type, caps_info); +} + +bool KeyServer::get_service_secret(uint32_t service_id, CryptoKey& secret, + uint64_t& secret_id, double& ttl) const +{ + std::scoped_lock l{lock}; + + return data.get_service_secret(cct, service_id, secret, secret_id, ttl); +} + +bool KeyServer::get_service_secret(uint32_t service_id, + uint64_t secret_id, CryptoKey& secret) const +{ + std::scoped_lock l{lock}; + + return data.get_service_secret(cct, service_id, secret_id, secret); +} + +bool KeyServer::generate_secret(CryptoKey& secret) +{ + bufferptr bp; + CryptoHandler *crypto = cct->get_crypto_handler(CEPH_CRYPTO_AES); + if (!crypto) + return false; + + if (crypto->create(cct->random(), bp) < 0) + return false; + + secret.set_secret(CEPH_CRYPTO_AES, bp, ceph_clock_now()); + + return true; +} + +bool KeyServer::generate_secret(EntityName& name, CryptoKey& secret) +{ + if (!generate_secret(secret)) + return false; + + std::scoped_lock l{lock}; + + EntityAuth auth; + auth.key = secret; + + data.add_auth(name, auth); + + return true; +} + +bool KeyServer::contains(const EntityName& name) const +{ + std::scoped_lock l{lock}; + + return data.contains(name); +} + +int KeyServer::encode_secrets(Formatter *f, stringstream *ds) const +{ + std::scoped_lock l{lock}; + map<EntityName, EntityAuth>::const_iterator mapiter = data.secrets_begin(); + + if (mapiter == data.secrets_end()) + return -ENOENT; + + if (f) + f->open_array_section("auth_dump"); + + while (mapiter != data.secrets_end()) { + const EntityName& name = mapiter->first; + if (ds) { + *ds << name.to_str() << std::endl; + *ds << "\tkey: " << mapiter->second.key << std::endl; + } + if (f) { + f->open_object_section("auth_entities"); + f->dump_string("entity", name.to_str()); + f->dump_stream("key") << mapiter->second.key; + f->open_object_section("caps"); + } + + map<string, bufferlist>::const_iterator capsiter = + mapiter->second.caps.begin(); + for (; capsiter != mapiter->second.caps.end(); ++capsiter) { + // FIXME: need a const_iterator for bufferlist, but it doesn't exist yet. + bufferlist *bl = const_cast<bufferlist*>(&capsiter->second); + auto dataiter = bl->cbegin(); + string caps; + using ceph::decode; + decode(caps, dataiter); + if (ds) + *ds << "\tcaps: [" << capsiter->first << "] " << caps << std::endl; + if (f) + f->dump_string(capsiter->first.c_str(), caps); + } + if (f) { + f->close_section(); // caps + f->close_section(); // auth_entities + } + + ++mapiter; + } + + if (f) + f->close_section(); // auth_dump + return 0; +} + +void KeyServer::encode_formatted(string label, Formatter *f, bufferlist &bl) +{ + ceph_assert(f != NULL); + f->open_object_section(label.c_str()); + encode_secrets(f, NULL); + f->close_section(); + f->flush(bl); +} + +void KeyServer::encode_plaintext(bufferlist &bl) +{ + stringstream os; + encode_secrets(NULL, &os); + bl.append(os.str()); +} + +bool KeyServer::updated_rotating(bufferlist& rotating_bl, version_t& rotating_ver) +{ + std::scoped_lock l{lock}; + + _check_rotating_secrets(); + + if (data.rotating_ver <= rotating_ver) + return false; + + data.encode_rotating(rotating_bl); + + rotating_ver = data.rotating_ver; + + return true; +} + +bool KeyServer::get_rotating_encrypted(const EntityName& name, + bufferlist& enc_bl) const +{ + std::scoped_lock l{lock}; + + map<EntityName, EntityAuth>::const_iterator mapiter = data.find_name(name); + if (mapiter == data.secrets_end()) + return false; + + const CryptoKey& specific_key = mapiter->second.key; + + map<uint32_t, RotatingSecrets>::const_iterator rotate_iter = + data.rotating_secrets.find(name.get_type()); + if (rotate_iter == data.rotating_secrets.end()) + return false; + + RotatingSecrets secrets = rotate_iter->second; + + std::string error; + if (encode_encrypt(cct, secrets, specific_key, enc_bl, error)) + return false; + + return true; +} + +bool KeyServer::_get_service_caps(const EntityName& name, uint32_t service_id, + AuthCapsInfo& caps_info) const +{ + string s = ceph_entity_type_name(service_id); + + return data.get_caps(cct, name, s, caps_info); +} + +bool KeyServer::get_service_caps(const EntityName& name, uint32_t service_id, + AuthCapsInfo& caps_info) const +{ + std::scoped_lock l{lock}; + return _get_service_caps(name, service_id, caps_info); +} + + +int KeyServer::_build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, + CephXSessionAuthInfo& info, + double ttl) +{ + info.service_id = service_id; + info.ticket = parent_ticket; + info.ticket.init_timestamps(ceph_clock_now(), ttl); + info.validity.set_from_double(ttl); + + generate_secret(info.session_key); + + // mon keys are stored externally. and the caps are blank anyway. + if (service_id != CEPH_ENTITY_TYPE_MON) { + string s = ceph_entity_type_name(service_id); + if (!data.get_caps(cct, info.ticket.name, s, info.ticket.caps)) { + return -EINVAL; + } + } + return 0; +} + +int KeyServer::build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, + CephXSessionAuthInfo& info) +{ + double ttl; + if (!get_service_secret(service_id, info.service_secret, info.secret_id, + ttl)) { + return -EPERM; + } + + std::scoped_lock l{lock}; + return _build_session_auth_info(service_id, parent_ticket, info, ttl); +} + +int KeyServer::build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, + const CryptoKey& service_secret, + uint64_t secret_id, + CephXSessionAuthInfo& info) +{ + info.service_secret = service_secret; + info.secret_id = secret_id; + + std::scoped_lock l{lock}; + return _build_session_auth_info(service_id, parent_ticket, info, + cct->_conf->auth_service_ticket_ttl); +} + diff --git a/src/auth/cephx/CephxKeyServer.h b/src/auth/cephx/CephxKeyServer.h new file mode 100644 index 00000000..2662c64c --- /dev/null +++ b/src/auth/cephx/CephxKeyServer.h @@ -0,0 +1,320 @@ +// -*- 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. + * + */ + +#ifndef CEPH_KEYSSERVER_H +#define CEPH_KEYSSERVER_H + +#include "auth/KeyRing.h" +#include "CephxProtocol.h" +#include "CephxKeyServer.h" +#include "common/ceph_mutex.h" + +class CephContext; + +struct KeyServerData { + version_t version; + + /* for each entity */ + map<EntityName, EntityAuth> secrets; + KeyRing *extra_secrets; + + /* for each service type */ + version_t rotating_ver; + map<uint32_t, RotatingSecrets> rotating_secrets; + + explicit KeyServerData(KeyRing *extra) + : version(0), + extra_secrets(extra), + rotating_ver(0) {} + + void encode(bufferlist& bl) const { + __u8 struct_v = 1; + using ceph::encode; + encode(struct_v, bl); + encode(version, bl); + encode(rotating_ver, bl); + encode(secrets, bl); + encode(rotating_secrets, bl); + } + void decode(bufferlist::const_iterator& bl) { + using ceph::decode; + __u8 struct_v; + decode(struct_v, bl); + decode(version, bl); + decode(rotating_ver, bl); + decode(secrets, bl); + decode(rotating_secrets, bl); + } + + void encode_rotating(bufferlist& bl) const { + using ceph::encode; + __u8 struct_v = 1; + encode(struct_v, bl); + encode(rotating_ver, bl); + encode(rotating_secrets, bl); + } + void decode_rotating(bufferlist& rotating_bl) { + using ceph::decode; + auto iter = rotating_bl.cbegin(); + __u8 struct_v; + decode(struct_v, iter); + decode(rotating_ver, iter); + decode(rotating_secrets, iter); + } + + bool contains(const EntityName& name) const { + return (secrets.find(name) != secrets.end()); + } + + void clear_secrets() { + version = 0; + secrets.clear(); + rotating_ver = 0; + rotating_secrets.clear(); + } + + void add_auth(const EntityName& name, EntityAuth& auth) { + secrets[name] = auth; + } + + void remove_secret(const EntityName& name) { + map<EntityName, EntityAuth>::iterator iter = secrets.find(name); + if (iter == secrets.end()) + return; + secrets.erase(iter); + } + + bool get_service_secret(CephContext *cct, uint32_t service_id, + CryptoKey& secret, uint64_t& secret_id, + double& ttl) const; + bool get_service_secret(CephContext *cct, uint32_t service_id, + uint64_t secret_id, CryptoKey& secret) const; + bool get_auth(const EntityName& name, EntityAuth& auth) const; + bool get_secret(const EntityName& name, CryptoKey& secret) const; + bool get_caps(CephContext *cct, const EntityName& name, + const std::string& type, AuthCapsInfo& caps) const; + + map<EntityName, EntityAuth>::iterator secrets_begin() + { return secrets.begin(); } + map<EntityName, EntityAuth>::const_iterator secrets_begin() const + { return secrets.begin(); } + map<EntityName, EntityAuth>::iterator secrets_end() + { return secrets.end(); } + map<EntityName, EntityAuth>::const_iterator secrets_end() const + { return secrets.end(); } + map<EntityName, EntityAuth>::iterator find_name(const EntityName& name) + { return secrets.find(name); } + map<EntityName, EntityAuth>::const_iterator find_name(const EntityName& name) const + { return secrets.find(name); } + + + // -- incremental updates -- + typedef enum { + AUTH_INC_NOP, + AUTH_INC_ADD, + AUTH_INC_DEL, + AUTH_INC_SET_ROTATING, + } IncrementalOp; + + struct Incremental { + IncrementalOp op; + bufferlist rotating_bl; // if SET_ROTATING. otherwise, + EntityName name; + EntityAuth auth; + + void encode(bufferlist& bl) const { + using ceph::encode; + __u8 struct_v = 1; + encode(struct_v, bl); + __u32 _op = (__u32)op; + encode(_op, bl); + if (op == AUTH_INC_SET_ROTATING) { + encode(rotating_bl, bl); + } else { + encode(name, bl); + encode(auth, bl); + } + } + void decode(bufferlist::const_iterator& bl) { + using ceph::decode; + __u8 struct_v; + decode(struct_v, bl); + __u32 _op; + decode(_op, bl); + op = (IncrementalOp)_op; + ceph_assert(op >= AUTH_INC_NOP && op <= AUTH_INC_SET_ROTATING); + if (op == AUTH_INC_SET_ROTATING) { + decode(rotating_bl, bl); + } else { + decode(name, bl); + decode(auth, bl); + } + } + }; + + void apply_incremental(Incremental& inc) { + switch (inc.op) { + case AUTH_INC_ADD: + add_auth(inc.name, inc.auth); + break; + + case AUTH_INC_DEL: + remove_secret(inc.name); + break; + + case AUTH_INC_SET_ROTATING: + decode_rotating(inc.rotating_bl); + break; + + case AUTH_INC_NOP: + break; + + default: + ceph_abort(); + } + } + +}; +WRITE_CLASS_ENCODER(KeyServerData) +WRITE_CLASS_ENCODER(KeyServerData::Incremental) + + + + +class KeyServer : public KeyStore { + CephContext *cct; + KeyServerData data; + mutable ceph::mutex lock; + + int _rotate_secret(uint32_t service_id); + bool _check_rotating_secrets(); + void _dump_rotating_secrets(); + int _build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, + CephXSessionAuthInfo& info, + double ttl); + bool _get_service_caps(const EntityName& name, uint32_t service_id, + AuthCapsInfo& caps) const; +public: + KeyServer(CephContext *cct_, KeyRing *extra_secrets); + bool generate_secret(CryptoKey& secret); + + bool get_secret(const EntityName& name, CryptoKey& secret) const override; + bool get_auth(const EntityName& name, EntityAuth& auth) const; + bool get_caps(const EntityName& name, const string& type, AuthCapsInfo& caps) const; + bool get_active_rotating_secret(const EntityName& name, CryptoKey& secret) const; + int start_server(); + void rotate_timeout(double timeout); + + int build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, + CephXSessionAuthInfo& info); + int build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, + const CryptoKey& service_secret, + uint64_t secret_id, + CephXSessionAuthInfo& info); + + /* get current secret for specific service type */ + bool get_service_secret(uint32_t service_id, CryptoKey& secret, + uint64_t& secret_id, double& ttl) const; + bool get_service_secret(uint32_t service_id, uint64_t secret_id, + CryptoKey& secret) const override; + + bool generate_secret(EntityName& name, CryptoKey& secret); + + void encode(bufferlist& bl) const { + using ceph::encode; + encode(data, bl); + } + void decode(bufferlist::const_iterator& bl) { + std::scoped_lock l{lock}; + using ceph::decode; + decode(data, bl); + } + bool contains(const EntityName& name) const; + int encode_secrets(Formatter *f, stringstream *ds) const; + void encode_formatted(string label, Formatter *f, bufferlist &bl); + void encode_plaintext(bufferlist &bl); + int list_secrets(stringstream& ds) const { + return encode_secrets(NULL, &ds); + } + version_t get_ver() const { + std::scoped_lock l{lock}; + return data.version; + } + + void clear_secrets() { + std::scoped_lock l{lock}; + data.clear_secrets(); + } + + void apply_data_incremental(KeyServerData::Incremental& inc) { + std::scoped_lock l{lock}; + data.apply_incremental(inc); + } + void set_ver(version_t ver) { + std::scoped_lock l{lock}; + data.version = ver; + } + + void add_auth(const EntityName& name, EntityAuth& auth) { + std::scoped_lock l{lock}; + data.add_auth(name, auth); + } + + void remove_secret(const EntityName& name) { + std::scoped_lock l{lock}; + data.remove_secret(name); + } + + bool has_secrets() { + map<EntityName, EntityAuth>::const_iterator b = data.secrets_begin(); + return (b != data.secrets_end()); + } + int get_num_secrets() { + std::scoped_lock l{lock}; + return data.secrets.size(); + } + + void clone_to(KeyServerData& dst) const { + std::scoped_lock l{lock}; + dst = data; + } + void export_keyring(KeyRing& keyring) { + std::scoped_lock l{lock}; + for (map<EntityName, EntityAuth>::iterator p = data.secrets.begin(); + p != data.secrets.end(); + ++p) { + keyring.add(p->first, p->second); + } + } + + bool updated_rotating(bufferlist& rotating_bl, version_t& rotating_ver); + + bool get_rotating_encrypted(const EntityName& name, bufferlist& enc_bl) const; + + ceph::mutex& get_lock() const { return lock; } + bool get_service_caps(const EntityName& name, uint32_t service_id, + AuthCapsInfo& caps) const; + + map<EntityName, EntityAuth>::iterator secrets_begin() + { return data.secrets_begin(); } + map<EntityName, EntityAuth>::iterator secrets_end() + { return data.secrets_end(); } +}; +WRITE_CLASS_ENCODER(KeyServer) + + +#endif diff --git a/src/auth/cephx/CephxProtocol.cc b/src/auth/cephx/CephxProtocol.cc new file mode 100644 index 00000000..973b481f --- /dev/null +++ b/src/auth/cephx/CephxProtocol.cc @@ -0,0 +1,591 @@ +// -*- 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) 2009-2011 New Dream Network + * + * 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 "CephxProtocol.h" +#include "common/Clock.h" +#include "common/ceph_context.h" +#include "common/config.h" +#include "common/debug.h" +#include "include/buffer.h" + +#define dout_subsys ceph_subsys_auth +#undef dout_prefix +#define dout_prefix *_dout << "cephx: " + + + +void cephx_calc_client_server_challenge(CephContext *cct, CryptoKey& secret, uint64_t server_challenge, + uint64_t client_challenge, uint64_t *key, std::string &error) +{ + CephXChallengeBlob b; + b.server_challenge = server_challenge; + b.client_challenge = client_challenge; + + bufferlist enc; + if (encode_encrypt(cct, b, secret, enc, error)) + return; + + uint64_t k = 0; + const ceph_le64 *p = (const ceph_le64 *)enc.c_str(); + for (int pos = 0; pos + sizeof(k) <= enc.length(); pos+=sizeof(k), p++) + k ^= *p; + *key = k; +} + + +/* + * Authentication + */ + +bool cephx_build_service_ticket_blob(CephContext *cct, CephXSessionAuthInfo& info, + CephXTicketBlob& blob) +{ + CephXServiceTicketInfo ticket_info; + ticket_info.session_key = info.session_key; + ticket_info.ticket = info.ticket; + ticket_info.ticket.caps = info.ticket.caps; + + ldout(cct, 10) << "build_service_ticket service " + << ceph_entity_type_name(info.service_id) + << " secret_id " << info.secret_id + << " ticket_info.ticket.name=" + << ticket_info.ticket.name.to_str() + << " ticket.global_id " << info.ticket.global_id << dendl; + blob.secret_id = info.secret_id; + std::string error; + if (!info.service_secret.get_secret().length()) + error = "invalid key"; // Bad key? + else + encode_encrypt_enc_bl(cct, ticket_info, info.service_secret, blob.blob, error); + if (!error.empty()) { + ldout(cct, -1) << "cephx_build_service_ticket_blob failed with error " + << error << dendl; + return false; + } + return true; +} + +/* + * AUTH SERVER: authenticate + * + * Authenticate principal, respond with AuthServiceTicketInfo + * + * {session key, validity}^principal_secret + * {principal_ticket, session key}^service_secret ... "enc_ticket" + */ +bool cephx_build_service_ticket_reply(CephContext *cct, + CryptoKey& principal_secret, + vector<CephXSessionAuthInfo> ticket_info_vec, + bool should_encrypt_ticket, + CryptoKey& ticket_enc_key, + bufferlist& reply) +{ + __u8 service_ticket_reply_v = 1; + encode(service_ticket_reply_v, reply); + + uint32_t num = ticket_info_vec.size(); + encode(num, reply); + ldout(cct, 10) << "build_service_ticket_reply encoding " << num + << " tickets with secret " << principal_secret << dendl; + + for (vector<CephXSessionAuthInfo>::iterator ticket_iter = ticket_info_vec.begin(); + ticket_iter != ticket_info_vec.end(); + ++ticket_iter) { + CephXSessionAuthInfo& info = *ticket_iter; + encode(info.service_id, reply); + + __u8 service_ticket_v = 1; + encode(service_ticket_v, reply); + + CephXServiceTicket msg_a; + msg_a.session_key = info.session_key; + msg_a.validity = info.validity; + std::string error; + if (encode_encrypt(cct, msg_a, principal_secret, reply, error)) { + ldout(cct, -1) << "error encoding encrypted: " << error << dendl; + return false; + } + + bufferlist service_ticket_bl; + CephXTicketBlob blob; + if (!cephx_build_service_ticket_blob(cct, info, blob)) { + return false; + } + encode(blob, service_ticket_bl); + + ldout(cct, 30) << "service_ticket_blob is "; + service_ticket_bl.hexdump(*_dout); + *_dout << dendl; + + encode((__u8)should_encrypt_ticket, reply); + if (should_encrypt_ticket) { + if (encode_encrypt(cct, service_ticket_bl, ticket_enc_key, reply, error)) { + ldout(cct, -1) << "error encoding encrypted ticket: " << error << dendl; + return false; + } + } else { + encode(service_ticket_bl, reply); + } + } + return true; +} + +/* + * PRINCIPAL: verify our attempt to authenticate succeeded. fill out + * this ServiceTicket with the result. + */ +bool CephXTicketHandler::verify_service_ticket_reply( + CryptoKey& secret, + bufferlist::const_iterator& indata) +{ + try { + __u8 service_ticket_v; + decode(service_ticket_v, indata); + + CephXServiceTicket msg_a; + std::string error; + if (decode_decrypt(cct, msg_a, secret, indata, error)) { + ldout(cct, 0) << __func__ << " failed decode_decrypt, error is: " << error + << dendl; + return false; + } + + __u8 ticket_enc; + decode(ticket_enc, indata); + + bufferlist service_ticket_bl; + if (ticket_enc) { + ldout(cct, 10) << __func__ << " got encrypted ticket" << dendl; + std::string error; + if (decode_decrypt(cct, service_ticket_bl, session_key, indata, error)) { + ldout(cct, 10) << __func__ << " decode_decrypt failed " + << "with " << error << dendl; + return false; + } + } else { + decode(service_ticket_bl, indata); + } + auto iter = service_ticket_bl.cbegin(); + decode(ticket, iter); + ldout(cct, 10) << __func__ << " ticket.secret_id=" << ticket.secret_id + << dendl; + + ldout(cct, 10) << __func__ << " service " + << ceph_entity_type_name(service_id) + << " secret_id " << ticket.secret_id + << " session_key " << msg_a.session_key + << " validity=" << msg_a.validity << dendl; + session_key = msg_a.session_key; + if (!msg_a.validity.is_zero()) { + expires = ceph_clock_now(); + expires += msg_a.validity; + renew_after = expires; + renew_after -= ((double)msg_a.validity.sec() / 4); + ldout(cct, 10) << __func__ << " ticket expires=" << expires + << " renew_after=" << renew_after << dendl; + } + + have_key_flag = true; + return true; + } catch (buffer::error& e) { + ldout(cct, 1) << __func__ << " decode error: " << e.what() << dendl; + return false; + } +} + +bool CephXTicketHandler::have_key() +{ + if (have_key_flag) { + have_key_flag = ceph_clock_now() < expires; + } + + return have_key_flag; +} + +bool CephXTicketHandler::need_key() const +{ + if (have_key_flag) { + return (!expires.is_zero()) && (ceph_clock_now() >= renew_after); + } + + return true; +} + +bool CephXTicketManager::have_key(uint32_t service_id) +{ + map<uint32_t, CephXTicketHandler>::iterator iter = tickets_map.find(service_id); + if (iter == tickets_map.end()) + return false; + return iter->second.have_key(); +} + +bool CephXTicketManager::need_key(uint32_t service_id) const +{ + map<uint32_t, CephXTicketHandler>::const_iterator iter = tickets_map.find(service_id); + if (iter == tickets_map.end()) + return true; + return iter->second.need_key(); +} + +void CephXTicketManager::set_have_need_key(uint32_t service_id, uint32_t& have, uint32_t& need) +{ + map<uint32_t, CephXTicketHandler>::iterator iter = tickets_map.find(service_id); + if (iter == tickets_map.end()) { + have &= ~service_id; + need |= service_id; + ldout(cct, 10) << "set_have_need_key no handler for service " + << ceph_entity_type_name(service_id) << dendl; + return; + } + + //ldout(cct, 10) << "set_have_need_key service " << ceph_entity_type_name(service_id) + //<< " (" << service_id << ")" + //<< " need=" << iter->second.need_key() << " have=" << iter->second.have_key() << dendl; + if (iter->second.need_key()) + need |= service_id; + else + need &= ~service_id; + + if (iter->second.have_key()) + have |= service_id; + else + have &= ~service_id; +} + +void CephXTicketManager::invalidate_ticket(uint32_t service_id) +{ + map<uint32_t, CephXTicketHandler>::iterator iter = tickets_map.find(service_id); + if (iter != tickets_map.end()) + iter->second.invalidate_ticket(); +} + +/* + * PRINCIPAL: verify our attempt to authenticate succeeded. fill out + * this ServiceTicket with the result. + */ +bool CephXTicketManager::verify_service_ticket_reply(CryptoKey& secret, + bufferlist::const_iterator& indata) +{ + __u8 service_ticket_reply_v; + uint32_t num; + try { + decode(service_ticket_reply_v, indata); + decode(num, indata); + } catch (buffer::error& e) { + ldout(cct, 10) << __func__ << " failed to decode ticket v or count: " + << e.what() << dendl; + } + ldout(cct, 10) << "verify_service_ticket_reply got " << num << " keys" << dendl; + + for (int i=0; i<(int)num; i++) { + uint32_t type; + try { + decode(type, indata); + } catch (buffer::error& e) { + ldout(cct, 10) << __func__ << " failed to decode ticket type: " << e.what() + << dendl; + } + ldout(cct, 10) << "got key for service_id " << ceph_entity_type_name(type) << dendl; + CephXTicketHandler& handler = get_handler(type); + if (!handler.verify_service_ticket_reply(secret, indata)) { + return false; + } + handler.service_id = type; + } + + return true; +} + +/* + * PRINCIPAL: build authorizer to access the service. + * + * ticket, {timestamp}^session_key + */ +CephXAuthorizer *CephXTicketHandler::build_authorizer(uint64_t global_id) const +{ + CephXAuthorizer *a = new CephXAuthorizer(cct); + a->session_key = session_key; + cct->random()->get_bytes((char*)&a->nonce, sizeof(a->nonce)); + + __u8 authorizer_v = 1; // see AUTH_MODE_* in Auth.h + encode(authorizer_v, a->bl); + encode(global_id, a->bl); + encode(service_id, a->bl); + + encode(ticket, a->bl); + a->base_bl = a->bl; + + CephXAuthorize msg; + msg.nonce = a->nonce; + + std::string error; + if (encode_encrypt(cct, msg, session_key, a->bl, error)) { + ldout(cct, 0) << "failed to encrypt authorizer: " << error << dendl; + delete a; + return 0; + } + return a; +} + +/* + * PRINCIPAL: build authorizer to access the service. + * + * ticket, {timestamp}^session_key + */ +CephXAuthorizer *CephXTicketManager::build_authorizer(uint32_t service_id) const +{ + map<uint32_t, CephXTicketHandler>::const_iterator iter = tickets_map.find(service_id); + if (iter == tickets_map.end()) { + ldout(cct, 0) << "no TicketHandler for service " + << ceph_entity_type_name(service_id) << dendl; + return NULL; + } + + const CephXTicketHandler& handler = iter->second; + return handler.build_authorizer(global_id); +} + +void CephXTicketManager::validate_tickets(uint32_t mask, uint32_t& have, uint32_t& need) +{ + uint32_t i; + need = 0; + for (i = 1; i<=mask; i<<=1) { + if (mask & i) { + set_have_need_key(i, have, need); + } + } + ldout(cct, 10) << "validate_tickets want " << mask << " have " << have + << " need " << need << dendl; +} + +bool cephx_decode_ticket(CephContext *cct, KeyStore *keys, + uint32_t service_id, + const CephXTicketBlob& ticket_blob, + CephXServiceTicketInfo& ticket_info) +{ + uint64_t secret_id = ticket_blob.secret_id; + CryptoKey service_secret; + + if (!ticket_blob.blob.length()) { + return false; + } + + if (secret_id == (uint64_t)-1) { + if (!keys->get_secret(cct->_conf->name, service_secret)) { + ldout(cct, 0) << "ceph_decode_ticket could not get general service secret for service_id=" + << ceph_entity_type_name(service_id) << " secret_id=" << secret_id << dendl; + return false; + } + } else { + if (!keys->get_service_secret(service_id, secret_id, service_secret)) { + ldout(cct, 0) << "ceph_decode_ticket could not get service secret for service_id=" + << ceph_entity_type_name(service_id) << " secret_id=" << secret_id << dendl; + return false; + } + } + + std::string error; + decode_decrypt_enc_bl(cct, ticket_info, service_secret, ticket_blob.blob, error); + if (!error.empty()) { + ldout(cct, 0) << "ceph_decode_ticket could not decrypt ticket info. error:" + << error << dendl; + return false; + } + + return true; +} + +/* + * SERVICE: verify authorizer and generate reply authorizer + * + * {timestamp + 1}^session_key + */ +bool cephx_verify_authorizer(CephContext *cct, KeyStore *keys, + bufferlist::const_iterator& indata, + size_t connection_secret_required_len, + CephXServiceTicketInfo& ticket_info, + std::unique_ptr<AuthAuthorizerChallenge> *challenge, + std::string *connection_secret, + bufferlist *reply_bl) +{ + __u8 authorizer_v; + uint32_t service_id; + uint64_t global_id; + CryptoKey service_secret; + // ticket blob + CephXTicketBlob ticket; + + try { + decode(authorizer_v, indata); + decode(global_id, indata); + decode(service_id, indata); + decode(ticket, indata); + } catch (buffer::end_of_buffer &e) { + // Unable to decode! + return false; + } + ldout(cct, 10) << "verify_authorizer decrypted service " + << ceph_entity_type_name(service_id) + << " secret_id=" << ticket.secret_id << dendl; + + if (ticket.secret_id == (uint64_t)-1) { + EntityName name; + name.set_type(service_id); + if (!keys->get_secret(name, service_secret)) { + ldout(cct, 0) << "verify_authorizer could not get general service secret for service " + << ceph_entity_type_name(service_id) << " secret_id=" << ticket.secret_id << dendl; + return false; + } + } else { + if (!keys->get_service_secret(service_id, ticket.secret_id, service_secret)) { + ldout(cct, 0) << "verify_authorizer could not get service secret for service " + << ceph_entity_type_name(service_id) << " secret_id=" << ticket.secret_id << dendl; + if (cct->_conf->auth_debug && ticket.secret_id == 0) + ceph_abort_msg("got secret_id=0"); + return false; + } + } + std::string error; + if (!service_secret.get_secret().length()) + error = "invalid key"; // Bad key? + else + decode_decrypt_enc_bl(cct, ticket_info, service_secret, ticket.blob, error); + if (!error.empty()) { + ldout(cct, 0) << "verify_authorizer could not decrypt ticket info: error: " + << error << dendl; + return false; + } + + if (ticket_info.ticket.global_id != global_id) { + ldout(cct, 0) << "verify_authorizer global_id mismatch: declared id=" << global_id + << " ticket_id=" << ticket_info.ticket.global_id << dendl; + return false; + } + + ldout(cct, 10) << "verify_authorizer global_id=" << global_id << dendl; + + // CephXAuthorize + CephXAuthorize auth_msg; + if (decode_decrypt(cct, auth_msg, ticket_info.session_key, indata, error)) { + ldout(cct, 0) << "verify_authorizercould not decrypt authorize request with error: " + << error << dendl; + return false; + } + + if (challenge) { + auto *c = static_cast<CephXAuthorizeChallenge*>(challenge->get()); + if (!auth_msg.have_challenge || !c) { + c = new CephXAuthorizeChallenge; + challenge->reset(c); + cct->random()->get_bytes((char*)&c->server_challenge, sizeof(c->server_challenge)); + ldout(cct,10) << __func__ << " adding server_challenge " << c->server_challenge + << dendl; + + encode_encrypt_enc_bl(cct, *c, ticket_info.session_key, *reply_bl, error); + if (!error.empty()) { + ldout(cct, 10) << "verify_authorizer: encode_encrypt error: " << error << dendl; + return false; + } + return false; + } + ldout(cct, 10) << __func__ << " got server_challenge+1 " + << auth_msg.server_challenge_plus_one + << " expecting " << c->server_challenge + 1 << dendl; + if (c->server_challenge + 1 != auth_msg.server_challenge_plus_one) { + return false; + } + } + + /* + * Reply authorizer: + * {timestamp + 1}^session_key + */ + CephXAuthorizeReply reply; + // reply.trans_id = auth_msg.trans_id; + reply.nonce_plus_one = auth_msg.nonce + 1; +#ifndef WITH_SEASTAR + if (connection_secret) { + // generate a connection secret + connection_secret->resize(connection_secret_required_len); + if (connection_secret_required_len) { + cct->random()->get_bytes(connection_secret->data(), + connection_secret_required_len); + } + reply.connection_secret = *connection_secret; + } +#endif + if (encode_encrypt(cct, reply, ticket_info.session_key, *reply_bl, error)) { + ldout(cct, 10) << "verify_authorizer: encode_encrypt error: " << error << dendl; + return false; + } + + ldout(cct, 10) << "verify_authorizer ok nonce " << hex << auth_msg.nonce << dec + << " reply_bl.length()=" << reply_bl->length() << dendl; + return true; +} + +bool CephXAuthorizer::verify_reply(bufferlist::const_iterator& indata, + std::string *connection_secret) +{ + CephXAuthorizeReply reply; + + std::string error; + if (decode_decrypt(cct, reply, session_key, indata, error)) { + ldout(cct, 0) << "verify_reply couldn't decrypt with error: " << error << dendl; + return false; + } + + uint64_t expect = nonce + 1; + if (expect != reply.nonce_plus_one) { + ldout(cct, 0) << "verify_authorizer_reply bad nonce got " << reply.nonce_plus_one << " expected " << expect + << " sent " << nonce << dendl; + return false; + } + + if (connection_secret && + reply.connection_secret.size()) { + *connection_secret = reply.connection_secret; + } + return true; +} + +bool CephXAuthorizer::add_challenge(CephContext *cct, + const bufferlist& challenge) +{ + bl = base_bl; + + CephXAuthorize msg; + msg.nonce = nonce; + + auto p = challenge.begin(); + if (!p.end()) { + std::string error; + CephXAuthorizeChallenge ch; + decode_decrypt_enc_bl(cct, ch, session_key, challenge, error); + if (!error.empty()) { + ldout(cct, 0) << "failed to decrypt challenge (" << challenge.length() << " bytes): " + << error << dendl; + return false; + } + msg.have_challenge = true; + msg.server_challenge_plus_one = ch.server_challenge + 1; + } + + std::string error; + if (encode_encrypt(cct, msg, session_key, bl, error)) { + ldout(cct, 0) << __func__ << " failed to encrypt authorizer: " << error << dendl; + return false; + } + return true; +} diff --git a/src/auth/cephx/CephxProtocol.h b/src/auth/cephx/CephxProtocol.h new file mode 100644 index 00000000..de3871cc --- /dev/null +++ b/src/auth/cephx/CephxProtocol.h @@ -0,0 +1,524 @@ +// -*- 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. + * + */ + +#ifndef CEPH_CEPHXPROTOCOL_H +#define CEPH_CEPHXPROTOCOL_H + +/* + Ceph X protocol + + See doc/dev/cephx.rst + +*/ + +/* authenticate requests */ +#define CEPHX_GET_AUTH_SESSION_KEY 0x0100 +#define CEPHX_GET_PRINCIPAL_SESSION_KEY 0x0200 +#define CEPHX_GET_ROTATING_KEY 0x0400 + +#define CEPHX_REQUEST_TYPE_MASK 0x0F00 +#define CEPHX_CRYPT_ERR 1 + +#include "auth/Auth.h" +#include <errno.h> +#include <sstream> + +class CephContext; + +/* + * Authentication + */ + +// initial server -> client challenge +struct CephXServerChallenge { + uint64_t server_challenge; + + void encode(bufferlist& bl) const { + using ceph::encode; + __u8 struct_v = 1; + encode(struct_v, bl); + encode(server_challenge, bl); + } + void decode(bufferlist::const_iterator& bl) { + using ceph::decode; + __u8 struct_v; + decode(struct_v, bl); + decode(server_challenge, bl); + } +}; +WRITE_CLASS_ENCODER(CephXServerChallenge) + + +// request/reply headers, for subsequent exchanges. + +struct CephXRequestHeader { + __u16 request_type; + + void encode(bufferlist& bl) const { + using ceph::encode; + encode(request_type, bl); + } + void decode(bufferlist::const_iterator& bl) { + using ceph::decode; + decode(request_type, bl); + } +}; +WRITE_CLASS_ENCODER(CephXRequestHeader) + +struct CephXResponseHeader { + uint16_t request_type; + int32_t status; + + void encode(bufferlist& bl) const { + using ceph::encode; + encode(request_type, bl); + encode(status, bl); + } + void decode(bufferlist::const_iterator& bl) { + using ceph::decode; + decode(request_type, bl); + decode(status, bl); + } +}; +WRITE_CLASS_ENCODER(CephXResponseHeader) + +struct CephXTicketBlob { + uint64_t secret_id; + bufferlist blob; + + CephXTicketBlob() : secret_id(0) {} + + void encode(bufferlist& bl) const { + using ceph::encode; + __u8 struct_v = 1; + encode(struct_v, bl); + encode(secret_id, bl); + encode(blob, bl); + } + + void decode(bufferlist::const_iterator& bl) { + using ceph::decode; + __u8 struct_v; + decode(struct_v, bl); + decode(secret_id, bl); + decode(blob, bl); + } +}; +WRITE_CLASS_ENCODER(CephXTicketBlob) + +// client -> server response to challenge +struct CephXAuthenticate { + uint64_t client_challenge; + uint64_t key; + CephXTicketBlob old_ticket; + uint32_t other_keys = 0; // replaces CephXServiceTicketRequest + + bool old_ticket_may_be_omitted; + + void encode(bufferlist& bl) const { + using ceph::encode; + __u8 struct_v = 3; + encode(struct_v, bl); + encode(client_challenge, bl); + encode(key, bl); + encode(old_ticket, bl); + encode(other_keys, bl); + } + void decode(bufferlist::const_iterator& bl) { + using ceph::decode; + __u8 struct_v; + decode(struct_v, bl); + decode(client_challenge, bl); + decode(key, bl); + decode(old_ticket, bl); + if (struct_v >= 2) { + decode(other_keys, bl); + } + + // v2 and v3 encodings are the same, but: + // - some clients that send v1 or v2 don't populate old_ticket + // on reconnects (but do on renewals) + // - any client that sends v3 or later is expected to populate + // old_ticket both on reconnects and renewals + old_ticket_may_be_omitted = struct_v < 3; + } +}; +WRITE_CLASS_ENCODER(CephXAuthenticate) + +struct CephXChallengeBlob { + uint64_t server_challenge, client_challenge; + + void encode(bufferlist& bl) const { + using ceph::encode; + encode(server_challenge, bl); + encode(client_challenge, bl); + } + void decode(bufferlist::const_iterator& bl) { + using ceph::decode; + decode(server_challenge, bl); + decode(client_challenge, bl); + } +}; +WRITE_CLASS_ENCODER(CephXChallengeBlob) + +void cephx_calc_client_server_challenge(CephContext *cct, + CryptoKey& secret, uint64_t server_challenge, uint64_t client_challenge, + uint64_t *key, std::string &error); + + +/* + * getting service tickets + */ +struct CephXSessionAuthInfo { + uint32_t service_id; + uint64_t secret_id; + AuthTicket ticket; + CryptoKey session_key; + CryptoKey service_secret; + utime_t validity; +}; + + +extern bool cephx_build_service_ticket_blob(CephContext *cct, + CephXSessionAuthInfo& ticket_info, CephXTicketBlob& blob); + +extern void cephx_build_service_ticket_request(CephContext *cct, + uint32_t keys, + bufferlist& request); + +extern bool cephx_build_service_ticket_reply(CephContext *cct, + CryptoKey& principal_secret, + vector<CephXSessionAuthInfo> ticket_info, + bool should_encrypt_ticket, + CryptoKey& ticket_enc_key, + bufferlist& reply); + +struct CephXServiceTicketRequest { + uint32_t keys; + + void encode(bufferlist& bl) const { + using ceph::encode; + __u8 struct_v = 1; + encode(struct_v, bl); + encode(keys, bl); + } + void decode(bufferlist::const_iterator& bl) { + using ceph::decode; + __u8 struct_v; + decode(struct_v, bl); + decode(keys, bl); + } +}; +WRITE_CLASS_ENCODER(CephXServiceTicketRequest) + + +/* + * Authorize + */ + +struct CephXAuthorizeReply { + uint64_t nonce_plus_one; + std::string connection_secret; + void encode(bufferlist& bl) const { + using ceph::encode; + __u8 struct_v = 1; + if (connection_secret.size()) { + struct_v = 2; + } + encode(struct_v, bl); + encode(nonce_plus_one, bl); + if (struct_v >= 2) { + struct_v = 2; + encode(connection_secret, bl); + } + } + void decode(bufferlist::const_iterator& bl) { + using ceph::decode; + __u8 struct_v; + decode(struct_v, bl); + decode(nonce_plus_one, bl); + if (struct_v >= 2) { + decode(connection_secret, bl); + } + } +}; +WRITE_CLASS_ENCODER(CephXAuthorizeReply) + + +struct CephXAuthorizer : public AuthAuthorizer { +private: + CephContext *cct; +public: + uint64_t nonce; + bufferlist base_bl; + + explicit CephXAuthorizer(CephContext *cct_) + : AuthAuthorizer(CEPH_AUTH_CEPHX), cct(cct_), nonce(0) {} + + bool build_authorizer(); + bool verify_reply(bufferlist::const_iterator& reply, + std::string *connection_secret) override; + bool add_challenge(CephContext *cct, const bufferlist& challenge) override; +}; + + + +/* + * TicketHandler + */ +struct CephXTicketHandler { + uint32_t service_id; + CryptoKey session_key; + CephXTicketBlob ticket; // opaque to us + utime_t renew_after, expires; + bool have_key_flag; + + CephXTicketHandler(CephContext *cct_, uint32_t service_id_) + : service_id(service_id_), have_key_flag(false), cct(cct_) { } + + // to build our ServiceTicket + bool verify_service_ticket_reply(CryptoKey& principal_secret, + bufferlist::const_iterator& indata); + // to access the service + CephXAuthorizer *build_authorizer(uint64_t global_id) const; + + bool have_key(); + bool need_key() const; + + void invalidate_ticket() { + have_key_flag = 0; + } +private: + CephContext *cct; +}; + +struct CephXTicketManager { + typedef map<uint32_t, CephXTicketHandler> tickets_map_t; + tickets_map_t tickets_map; + uint64_t global_id; + + explicit CephXTicketManager(CephContext *cct_) : global_id(0), cct(cct_) {} + + bool verify_service_ticket_reply(CryptoKey& principal_secret, + bufferlist::const_iterator& indata); + + CephXTicketHandler& get_handler(uint32_t type) { + tickets_map_t::iterator i = tickets_map.find(type); + if (i != tickets_map.end()) + return i->second; + CephXTicketHandler newTicketHandler(cct, type); + std::pair < tickets_map_t::iterator, bool > res = + tickets_map.insert(std::make_pair(type, newTicketHandler)); + ceph_assert(res.second); + return res.first->second; + } + CephXAuthorizer *build_authorizer(uint32_t service_id) const; + bool have_key(uint32_t service_id); + bool need_key(uint32_t service_id) const; + void set_have_need_key(uint32_t service_id, uint32_t& have, uint32_t& need); + void validate_tickets(uint32_t mask, uint32_t& have, uint32_t& need); + void invalidate_ticket(uint32_t service_id); + +private: + CephContext *cct; +}; + + +/* A */ +struct CephXServiceTicket { + CryptoKey session_key; + utime_t validity; + + void encode(bufferlist& bl) const { + using ceph::encode; + __u8 struct_v = 1; + encode(struct_v, bl); + encode(session_key, bl); + encode(validity, bl); + } + void decode(bufferlist::const_iterator& bl) { + using ceph::decode; + __u8 struct_v; + decode(struct_v, bl); + decode(session_key, bl); + decode(validity, bl); + } +}; +WRITE_CLASS_ENCODER(CephXServiceTicket) + +/* B */ +struct CephXServiceTicketInfo { + AuthTicket ticket; + CryptoKey session_key; + + void encode(bufferlist& bl) const { + using ceph::encode; + __u8 struct_v = 1; + encode(struct_v, bl); + encode(ticket, bl); + encode(session_key, bl); + } + void decode(bufferlist::const_iterator& bl) { + using ceph::decode; + __u8 struct_v; + decode(struct_v, bl); + decode(ticket, bl); + decode(session_key, bl); + } +}; +WRITE_CLASS_ENCODER(CephXServiceTicketInfo) + +struct CephXAuthorizeChallenge : public AuthAuthorizerChallenge { + uint64_t server_challenge; + void encode(bufferlist& bl) const { + using ceph::encode; + __u8 struct_v = 1; + encode(struct_v, bl); + encode(server_challenge, bl); + } + void decode(bufferlist::const_iterator& bl) { + using ceph::decode; + __u8 struct_v; + decode(struct_v, bl); + decode(server_challenge, bl); + } +}; +WRITE_CLASS_ENCODER(CephXAuthorizeChallenge) + +struct CephXAuthorize { + uint64_t nonce; + bool have_challenge = false; + uint64_t server_challenge_plus_one = 0; + void encode(bufferlist& bl) const { + using ceph::encode; + __u8 struct_v = 2; + encode(struct_v, bl); + encode(nonce, bl); + encode(have_challenge, bl); + encode(server_challenge_plus_one, bl); + } + void decode(bufferlist::const_iterator& bl) { + using ceph::decode; + __u8 struct_v; + decode(struct_v, bl); + decode(nonce, bl); + if (struct_v >= 2) { + decode(have_challenge, bl); + decode(server_challenge_plus_one, bl); + } + } +}; +WRITE_CLASS_ENCODER(CephXAuthorize) + +/* + * Decode an extract ticket + */ +bool cephx_decode_ticket(CephContext *cct, KeyStore *keys, + uint32_t service_id, + const CephXTicketBlob& ticket_blob, + CephXServiceTicketInfo& ticket_info); + +/* + * Verify authorizer and generate reply authorizer + */ +extern bool cephx_verify_authorizer( + CephContext *cct, + KeyStore *keys, + bufferlist::const_iterator& indata, + size_t connection_secret_required_len, + CephXServiceTicketInfo& ticket_info, + std::unique_ptr<AuthAuthorizerChallenge> *challenge, + std::string *connection_secret, + bufferlist *reply_bl); + + + + + + +/* + * encode+encrypt macros + */ +static constexpr uint64_t AUTH_ENC_MAGIC = 0xff009cad8826aa55ull; + +template <typename T> +void decode_decrypt_enc_bl(CephContext *cct, T& t, CryptoKey key, + const bufferlist& bl_enc, + std::string &error) +{ + uint64_t magic; + bufferlist bl; + + if (key.decrypt(cct, bl_enc, bl, &error) < 0) + return; + + auto iter2 = bl.cbegin(); + __u8 struct_v; + decode(struct_v, iter2); + decode(magic, iter2); + if (magic != AUTH_ENC_MAGIC) { + ostringstream oss; + oss << "bad magic in decode_decrypt, " << magic << " != " << AUTH_ENC_MAGIC; + error = oss.str(); + return; + } + + decode(t, iter2); +} + +template <typename T> +void encode_encrypt_enc_bl(CephContext *cct, const T& t, const CryptoKey& key, + bufferlist& out, std::string &error) +{ + bufferlist bl; + __u8 struct_v = 1; + encode(struct_v, bl); + uint64_t magic = AUTH_ENC_MAGIC; + encode(magic, bl); + encode(t, bl); + + key.encrypt(cct, bl, out, &error); +} + +template <typename T> +int decode_decrypt(CephContext *cct, T& t, const CryptoKey& key, + bufferlist::const_iterator& iter, std::string &error) +{ + bufferlist bl_enc; + try { + decode(bl_enc, iter); + decode_decrypt_enc_bl(cct, t, key, bl_enc, error); + } + catch (buffer::error &e) { + error = "error decoding block for decryption"; + } + if (!error.empty()) + return CEPHX_CRYPT_ERR; + return 0; +} + +template <typename T> +int encode_encrypt(CephContext *cct, const T& t, const CryptoKey& key, + bufferlist& out, std::string &error) +{ + bufferlist bl_enc; + encode_encrypt_enc_bl(cct, t, key, bl_enc, error); + if (!error.empty()){ + return CEPHX_CRYPT_ERR; + } + encode(bl_enc, out); + return 0; +} + +#endif diff --git a/src/auth/cephx/CephxServiceHandler.cc b/src/auth/cephx/CephxServiceHandler.cc new file mode 100644 index 00000000..886014e8 --- /dev/null +++ b/src/auth/cephx/CephxServiceHandler.cc @@ -0,0 +1,403 @@ +// -*- 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 << ": " + +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 (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 (buffer::error& e) { + ldout(cct, 0) << __func__ << " failed to decode CephXAuthenticate: " + << e.what() << dendl; + ret = -EPERM; + break; + } + + CryptoKey secret; + if (!key_server->get_secret(entity_name, secret)) { + ldout(cct, 0) << "couldn't find entity name: " << entity_name << dendl; + ret = -EPERM; + break; + } + + if (!server_challenge) { + ret = -EPERM; + break; + } + + uint64_t expected_key; + std::string error; + cephx_calc_client_server_challenge(cct, secret, server_challenge, + req.client_challenge, &expected_key, error); + if (!error.empty()) { + ldout(cct, 0) << " cephx_calc_client_server_challenge error: " << error << dendl; + ret = -EPERM; + 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 = -EPERM; + break; + } + + CryptoKey session_key; + CephXSessionAuthInfo info; + bool should_enc_ticket = false; + + EntityAuth eauth; + if (! key_server->get_auth(entity_name, eauth)) { + ret = -EPERM; + break; + } + + 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, eauth.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 = -EPERM; + break; + } + + CephXServiceTicketRequest ticket_req; + try { + decode(ticket_req, indata); + } catch (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 = -EPERM; + 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); +} diff --git a/src/auth/cephx/CephxServiceHandler.h b/src/auth/cephx/CephxServiceHandler.h new file mode 100644 index 00000000..28d24f1e --- /dev/null +++ b/src/auth/cephx/CephxServiceHandler.h @@ -0,0 +1,54 @@ +// -*- 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. + * + */ + +#ifndef CEPH_CEPHXSERVICEHANDLER_H +#define CEPH_CEPHXSERVICEHANDLER_H + +#include "auth/AuthServiceHandler.h" +#include "auth/Auth.h" + +class KeyServer; +struct CephXAuthenticate; +struct CephXServiceTicketInfo; + +class CephxServiceHandler : public AuthServiceHandler { + KeyServer *key_server; + uint64_t server_challenge; + +public: + CephxServiceHandler(CephContext *cct_, KeyServer *ks) + : AuthServiceHandler(cct_), key_server(ks), server_challenge(0) {} + ~CephxServiceHandler() override {} + + int handle_request( + bufferlist::const_iterator& indata, + size_t connection_secret_required_length, + bufferlist *result_bl, + AuthCapsInfo *caps, + CryptoKey *session_key, + std::string *connection_secret) override; + +private: + int do_start_session(bool is_new_global_id, + bufferlist *result_bl, + AuthCapsInfo *caps) override; + + int verify_old_ticket(const CephXAuthenticate& req, + CephXServiceTicketInfo& old_ticket_info, + bool& should_enc_ticket); + void build_cephx_response_header(int request_type, int status, + bufferlist& bl); +}; + +#endif diff --git a/src/auth/cephx/CephxSessionHandler.cc b/src/auth/cephx/CephxSessionHandler.cc new file mode 100644 index 00000000..83069f28 --- /dev/null +++ b/src/auth/cephx/CephxSessionHandler.cc @@ -0,0 +1,182 @@ +// -*- 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 "CephxSessionHandler.h" +#include "CephxProtocol.h" + +#include <errno.h> +#include <sstream> + +#include "common/config.h" +#include "include/ceph_features.h" +#include "msg/Message.h" + +#define dout_subsys ceph_subsys_auth + +int CephxSessionHandler::_calc_signature(Message *m, uint64_t *psig) +{ + const ceph_msg_header& header = m->get_header(); + const ceph_msg_footer& footer = m->get_footer(); + + if (!HAVE_FEATURE(features, CEPHX_V2)) { + // legacy pre-mimic behavior for compatibility + + // optimized signature calculation + // - avoid temporary allocated buffers from encode_encrypt[_enc_bl] + // - skip the leading 4 byte wrapper from encode_encrypt + struct { + __u8 v; + ceph_le64 magic; + ceph_le32 len; + ceph_le32 header_crc; + ceph_le32 front_crc; + ceph_le32 middle_crc; + ceph_le32 data_crc; + } __attribute__ ((packed)) sigblock = { + 1, init_le64(AUTH_ENC_MAGIC), init_le32(4*4), + init_le32(header.crc), init_le32(footer.front_crc), + init_le32(footer.middle_crc), init_le32(footer.data_crc) + }; + + char exp_buf[CryptoKey::get_max_outbuf_size(sizeof(sigblock))]; + + try { + const CryptoKey::in_slice_t in { + sizeof(sigblock), + reinterpret_cast<const unsigned char*>(&sigblock) + }; + const CryptoKey::out_slice_t out { + sizeof(exp_buf), + reinterpret_cast<unsigned char*>(&exp_buf) + }; + key.encrypt(cct, in, out); + } catch (std::exception& e) { + lderr(cct) << __func__ << " failed to encrypt signature block" << dendl; + return -1; + } + + *psig = *reinterpret_cast<ceph_le64*>(exp_buf); + } else { + // newer mimic+ signatures + struct { + ceph_le32 header_crc; + ceph_le32 front_crc; + ceph_le32 front_len; + ceph_le32 middle_crc; + ceph_le32 middle_len; + ceph_le32 data_crc; + ceph_le32 data_len; + ceph_le32 seq_lower_word; + } __attribute__ ((packed)) sigblock = { + init_le32(header.crc), + init_le32(footer.front_crc), + init_le32(header.front_len), + init_le32(footer.middle_crc), + init_le32(header.middle_len), + init_le32(footer.data_crc), + init_le32(header.data_len), + init_le32(header.seq) + }; + + char exp_buf[CryptoKey::get_max_outbuf_size(sizeof(sigblock))]; + + try { + const CryptoKey::in_slice_t in { + sizeof(sigblock), + reinterpret_cast<const unsigned char*>(&sigblock) + }; + const CryptoKey::out_slice_t out { + sizeof(exp_buf), + reinterpret_cast<unsigned char*>(&exp_buf) + }; + key.encrypt(cct, in, out); + } catch (std::exception& e) { + lderr(cct) << __func__ << " failed to encrypt signature block" << dendl; + return -1; + } + + struct enc { + ceph_le64 a, b, c, d; + } *penc = reinterpret_cast<enc*>(exp_buf); + *psig = penc->a ^ penc->b ^ penc->c ^ penc->d; + } + + ldout(cct, 10) << __func__ << " seq " << m->get_seq() + << " front_crc_ = " << footer.front_crc + << " middle_crc = " << footer.middle_crc + << " data_crc = " << footer.data_crc + << " sig = " << *psig + << dendl; + return 0; +} + +int CephxSessionHandler::sign_message(Message *m) +{ + // If runtime signing option is off, just return success without signing. + if (!cct->_conf->cephx_sign_messages) { + return 0; + } + + uint64_t sig; + int r = _calc_signature(m, &sig); + if (r < 0) + return r; + + ceph_msg_footer& f = m->get_footer(); + f.sig = sig; + f.flags = (unsigned)f.flags | CEPH_MSG_FOOTER_SIGNED; + ldout(cct, 20) << "Putting signature in client message(seq # " << m->get_seq() + << "): sig = " << sig << dendl; + return 0; +} + +int CephxSessionHandler::check_message_signature(Message *m) +{ + // If runtime signing option is off, just return success without checking signature. + if (!cct->_conf->cephx_sign_messages) { + return 0; + } + if ((features & CEPH_FEATURE_MSG_AUTH) == 0) { + // it's fine, we didn't negotiate this feature. + return 0; + } + + uint64_t sig; + int r = _calc_signature(m, &sig); + if (r < 0) + return r; + + if (sig != m->get_footer().sig) { + // Should have been signed, but signature check failed. PLR + if (!(m->get_footer().flags & CEPH_MSG_FOOTER_SIGNED)) { + ldout(cct, 0) << "SIGN: MSG " << m->get_seq() << " Sender did not set CEPH_MSG_FOOTER_SIGNED." << dendl; + } + ldout(cct, 0) << "SIGN: MSG " << m->get_seq() << " Message signature does not match contents." << dendl; + ldout(cct, 0) << "SIGN: MSG " << m->get_seq() << "Signature on message:" << dendl; + ldout(cct, 0) << "SIGN: MSG " << m->get_seq() << " sig: " << m->get_footer().sig << dendl; + ldout(cct, 0) << "SIGN: MSG " << m->get_seq() << "Locally calculated signature:" << dendl; + ldout(cct, 0) << "SIGN: MSG " << m->get_seq() << " sig_check:" << sig << dendl; + + // For the moment, printing an error message to the log and + // returning failure is sufficient. In the long term, we should + // probably have code parsing the log looking for this kind of + // security failure, particularly when there are large numbers of + // them, since the latter is a potential sign of an attack. PLR + + ldout(cct, 0) << "Signature failed." << dendl; + return (SESSION_SIGNATURE_FAILURE); + } + + return 0; +} diff --git a/src/auth/cephx/CephxSessionHandler.h b/src/auth/cephx/CephxSessionHandler.h new file mode 100644 index 00000000..6a6a99f1 --- /dev/null +++ b/src/auth/cephx/CephxSessionHandler.h @@ -0,0 +1,44 @@ +// -*- 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 "auth/AuthSessionHandler.h" +#include "auth/Auth.h" + +class CephContext; +class Message; + +class CephxSessionHandler : public AuthSessionHandler { + CephContext *cct; + int protocol; + CryptoKey key; // per mon authentication + uint64_t features; + + int _calc_signature(Message *m, uint64_t *psig); + +public: + CephxSessionHandler(CephContext *cct, + const CryptoKey& session_key, + const uint64_t features) + : cct(cct), + protocol(CEPH_AUTH_CEPHX), + key(session_key), + features(features) { + } + ~CephxSessionHandler() override = default; + + int sign_message(Message *m) override; + int check_message_signature(Message *m) override ; +}; + |