diff options
author | Yehuda Sadeh <yehuda@inktank.com> | 2012-11-08 17:19:18 -0800 |
---|---|---|
committer | Yehuda Sadeh <yehuda@inktank.com> | 2012-11-08 17:19:18 -0800 |
commit | 2475d0143b17ad4470009b62682d0e5845673e8c (patch) | |
tree | 08f7bba903bd2c247b03b250296079eae607d78f | |
parent | e5124ced11e4150bae1b4b9b3fcc30b1deebbcac (diff) | |
parent | fc0d6d345320057182adfac85681241c976c944b (diff) | |
download | ceph-2475d0143b17ad4470009b62682d0e5845673e8c.tar.gz |
Merge branch 'wip-keystone' into wip-rgw-integration
Conflicts:
debian/control
Signed-off-by: Yehuda Sadeh <yehuda@inktank.com>
-rw-r--r-- | debian/control | 2 | ||||
-rw-r--r-- | doc/radosgw/config.rst | 43 | ||||
-rw-r--r-- | src/.gitignore | 1 | ||||
-rw-r--r-- | src/Makefile.am | 23 | ||||
-rw-r--r-- | src/common/ceph_crypto.cc | 12 | ||||
-rw-r--r-- | src/common/ceph_crypto.h | 4 | ||||
-rw-r--r-- | src/common/ceph_crypto_cms.cc | 360 | ||||
-rw-r--r-- | src/common/ceph_crypto_cms.h | 10 | ||||
-rw-r--r-- | src/common/common_init.cc | 2 | ||||
-rw-r--r-- | src/common/config_opts.h | 15 | ||||
-rw-r--r-- | src/json_spirit/json_spirit_reader_template.h | 2 | ||||
-rw-r--r-- | src/rgw/rgw_common.cc | 9 | ||||
-rw-r--r-- | src/rgw/rgw_common.h | 7 | ||||
-rw-r--r-- | src/rgw/rgw_http_client.cc | 76 | ||||
-rw-r--r-- | src/rgw/rgw_http_client.h | 23 | ||||
-rw-r--r-- | src/rgw/rgw_json.cc | 54 | ||||
-rw-r--r-- | src/rgw/rgw_json.h | 6 | ||||
-rw-r--r-- | src/rgw/rgw_main.cc | 10 | ||||
-rw-r--r-- | src/rgw/rgw_rest_swift.cc | 2 | ||||
-rw-r--r-- | src/rgw/rgw_swift.cc | 685 | ||||
-rw-r--r-- | src/rgw/rgw_swift.h | 73 | ||||
-rw-r--r-- | src/test/ceph_crypto.cc | 6 | ||||
-rw-r--r-- | src/test/crypto.cc | 2 |
23 files changed, 1339 insertions, 88 deletions
diff --git a/debian/control b/debian/control index dbf95fa944f..45030953a98 100644 --- a/debian/control +++ b/debian/control @@ -6,7 +6,7 @@ Vcs-Git: git://github.com/ceph/ceph.git Vcs-Browser: https://github.com/ceph/ceph Maintainer: Laszlo Boszormenyi (GCS) <gcs@debian.hu> Uploaders: Sage Weil <sage@newdream.net> -Build-Depends: debhelper (>= 6.0.7~), autotools-dev, autoconf, automake, libfuse-dev, libboost-dev (>= 1.34), libedit-dev, libnss3-dev, libtool, libexpat1-dev, libfcgi-dev, libatomic-ops-dev, libgoogle-perftools-dev [i386 amd64], pkg-config, libcurl4-gnutls-dev, libkeyutils-dev, uuid-dev, libaio-dev, python (>= 2.6.6-3~), libxml2-dev, javahelper, default-jdk +Build-Depends: debhelper (>= 6.0.7~), autotools-dev, autoconf, automake, libfuse-dev, libboost-dev (>= 1.34), libboost-thread-dev, libedit-dev, libnss3-dev, libtool, libexpat1-dev, libfcgi-dev, libatomic-ops-dev, libgoogle-perftools-dev [i386 amd64], pkg-config, libcurl4-gnutls-dev, libkeyutils-dev, uuid-dev, libaio-dev, python (>= 2.6.6-3~), libxml2-dev, javahelper, default-jdk Standards-Version: 3.9.3 Package: ceph diff --git a/doc/radosgw/config.rst b/doc/radosgw/config.rst index 3e14a20e1fe..c9605d06cef 100644 --- a/doc/radosgw/config.rst +++ b/doc/radosgw/config.rst @@ -291,3 +291,46 @@ RGW's ``user:subuser`` tuple maps to the ``tenant:user`` tuple expected by Swift built-in Swift authentication (``-V 1.0``) at this point. There is currently no way to make RGW authenticate users via OpenStack Identity Service (Keystone). + +Integrating with OpenStack Keystone +=================================== + +It is possible to integrate RGW with Keystone, the OpenStack identity service. This sets up RGW to accept Keystone +as the users authority. A user that Keystone authorizes to access RGW will also be automatically created on RGW +(if didn't exist beforehand). A token that Keystone validates will be considered as valid by RGW. + +The following config options are available for Keystone integration:: + + [client.radosgw.gateway] + rgw keystone url = {keystone server url} + rgw keystone admin token = {keystone admin token} + rgw keystone accepted roles = {accepted user roles} + rgw keystone token cache size = {number of tokens to cache} + rgw keystone revocation interval = {number of seconds before checking revoked tickets} + nss db path = {path to nss db} + +An RGW user is mapped into a Keystone ``tenant``. A Keystone user has different roles assigned to it on possibly more +than a single tenant. When RGW gets the ticket, it looks at the tenant, and the user roles that are assigned to +that ticket, and accepts/rejects the request according to the ``rgw keystone accepted roles`` configurable. + +Keystone itself needs to be configured to point to RGW as an object-storage endpoint:: + + keystone service-create --name swift --type-object-store + keystone endpoint-create --service-id <id> --public-url http://radosgw.example.com/swift/v1 + + +The keystone url is the Keystone admin RESTful api url. The admin token is the token that is configured internally +in Keystone for admin requests. + +RGW will query Keystone periodically for a list of revoked tokens. These requests are encoded and signed. Also, Keystone +may be configured to provide self signed tokens, which are also encoded and signed. RGW needs to be able to decode +and verify these signed messages, and it requires it to be set up appropriately. Currently, RGW will be able to do +it only if it was compiled with ``--with-nss``. It also requires converting the OpenSSL certificates that Keystone uses +for creating the requests to the nss db format, for example:: + + mkdir /var/ceph/nss + + openssl x509 -in /etc/keystone/ssl/certs/ca.pem -pubkey | \ + certutil -d /var/ceph/nss -A -n ca -t "TCu,Cu,Tuw" + openssl x509 -in /etc/keystone/ssl/certs/signing_cert.pem -pubkey | \ + certutil -d /var/ceph/nss -A -n signing_cert -t "TCu,Cu,Tuw" diff --git a/src/.gitignore b/src/.gitignore index 5a4216503ff..7548b5e47ae 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -23,6 +23,7 @@ /radosgw /radosgw-admin /rbdtool +/rgw_jsonparser /rgw_multiparser /streamtest /bench_log diff --git a/src/Makefile.am b/src/Makefile.am index 51abc35fa46..a42f02103f4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -43,7 +43,7 @@ check-local: $(srcdir)/test/encoding/check-generated.sh $(srcdir)/test/encoding/readable.sh ../ceph-object-corpus -EXTRALIBS = -luuid +EXTRALIBS = -luuid -lboost_thread if FREEBSD EXTRALIBS += -lexecinfo endif @@ -207,12 +207,15 @@ test_ioctls_SOURCES = client/test_ioctls.c bin_DEBUGPROGRAMS += test_ioctls dupstore_SOURCES = dupstore.cc +dupstore_CXXFLAGS= ${CRYPTO_CXXFLAGS} ${AM_CXXFLAGS} dupstore_LDADD = $(LIBOS_LDA) $(LIBGLOBAL_LDA) streamtest_SOURCES = streamtest.cc +streamtest_CXXFLAGS= ${CRYPTO_CXXFLAGS} ${AM_CXXFLAGS} streamtest_LDADD = $(LIBOS_LDA) $(LIBGLOBAL_LDA) bin_DEBUGPROGRAMS += dupstore streamtest test_trans_SOURCES = test_trans.cc +test_trans_CXXFLAGS= ${CRYPTO_CXXFLAGS} ${AM_CXXFLAGS} test_trans_LDADD = $(LIBOS_LDA) $(LIBGLOBAL_LDA) bin_DEBUGPROGRAMS += test_trans @@ -362,6 +365,7 @@ radosgw_SOURCES = \ rgw/rgw_rest_swift.cc \ rgw/rgw_rest_s3.cc \ rgw/rgw_rest_usage.cc \ + rgw/rgw_http_client.cc \ rgw/rgw_swift.cc \ rgw/rgw_swift_auth.cc \ rgw/rgw_main.cc @@ -920,7 +924,7 @@ bin_DEBUGPROGRAMS += test_libcephfs test_filestore_SOURCES = test/filestore/store_test.cc test_filestore_LDFLAGS = ${AM_LDFLAGS} test_filestore_LDADD = ${UNITTEST_STATIC_LDADD} $(LIBOS_LDA) $(LIBGLOBAL_LDA) -test_filestore_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} $(LEVELDB_INCLUDE) +test_filestore_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} $(LEVELDB_INCLUDE) ${CRYPTO_CXXFLAGS} bin_DEBUGPROGRAMS += test_filestore test_filestore_workloadgen_SOURCES = \ @@ -928,11 +932,12 @@ test_filestore_workloadgen_SOURCES = \ test/filestore/TestFileStoreState.cc test_filestore_workloadgen_LDFLAGS = ${AM_LDFLAGS} test_filestore_workloadgen_LDADD = $(LIBOS_LDA) $(LIBGLOBAL_LDA) +test_filestore_workloadgen_CXXFLAGS = ${CRYPTO_CXXFLAGS} ${AM_CXXFLAGS} bin_DEBUGPROGRAMS += test_filestore_workloadgen test_filestore_idempotent_SOURCES = test/filestore/test_idempotent.cc test/filestore/FileStoreTracker.cc test/common/ObjectContents.cc test_filestore_idempotent_LDADD = $(LIBOS_LDA) $(LIBGLOBAL_LDA) -test_filestore_idempotent_CXXFLAGS = $(LEVELDB_INCLUDE) +test_filestore_idempotent_CXXFLAGS = ${CRYPTO_CXXFLAGS} $(LEVELDB_INCLUDE) bin_DEBUGPROGRAMS += test_filestore_idempotent test_filestore_idempotent_sequence_SOURCES = \ @@ -940,13 +945,14 @@ test_filestore_idempotent_sequence_SOURCES = \ test/filestore/DeterministicOpSequence.cc \ test/filestore/TestFileStoreState.cc \ test/filestore/FileStoreDiff.cc +test_filestore_idempotent_sequence_CXXFLAGS = ${CRYPTO_CXXFLAGS} ${AM_CXXFLAGS} test_filestore_idempotent_sequence_LDADD = $(LIBOS_LDA) $(LIBGLOBAL_LDA) bin_DEBUGPROGRAMS += test_filestore_idempotent_sequence xattr_bench_SOURCES = test/xattr_bench.cc xattr_bench_LDFLAGS = ${AM_LDFLAGS} xattr_bench_LDADD = ${UNITTEST_STATIC_LDADD} $(LIBOS_LDA) $(LIBGLOBAL_LDA) -xattr_bench_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} $(LEVELDB_INCLUDE) +xattr_bench_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} $(LEVELDB_INCLUDE) ${CRYPTO_CXXFLAGS} bin_DEBUGPROGRAMS += xattr_bench test_filejournal_SOURCES = test/test_filejournal.cc @@ -964,13 +970,13 @@ bin_DEBUGPROGRAMS += test_stress_watch test_object_map_SOURCES = test/ObjectMap/test_object_map.cc test/ObjectMap/KeyValueDBMemory.cc os/DBObjectMap.cc os/LevelDBStore.cc test_object_map_LDFLAGS = ${AM_LDFLAGS} test_object_map_LDADD = ${UNITTEST_STATIC_LDADD} $(LIBOS_LDA) $(LIBGLOBAL_LDA) -test_object_map_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} $(LEVELDB_INCLUDE) +test_object_map_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} $(LEVELDB_INCLUDE) ${CRYPTO_CXXFLAGS} bin_DEBUGPROGRAMS += test_object_map test_keyvaluedb_atomicity_SOURCES = test/ObjectMap/test_keyvaluedb_atomicity.cc os/LevelDBStore.cc test_keyvaluedb_atomicity_LDFLAGS = ${AM_LDFLAGS} test_keyvaluedb_atomicity_LDADD = ${UNITTEST_STATIC_LDADD} $(LIBOS_LDA) $(LIBGLOBAL_LDA) -test_keyvaluedb_atomicity_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} $(LEVELDB_INCLUDE) +test_keyvaluedb_atomicity_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} $(LEVELDB_INCLUDE) ${CRYPTO_CXXFLAGS} bin_DEBUGPROGRAMS += test_keyvaluedb_atomicity test_keyvaluedb_iterators_SOURCES = test/ObjectMap/test_keyvaluedb_iterators.cc \ @@ -978,7 +984,7 @@ test_keyvaluedb_iterators_SOURCES = test/ObjectMap/test_keyvaluedb_iterators.cc os/LevelDBStore.cc test_keyvaluedb_iterators_LDFLAGS = ${AM_LDFLAGS} test_keyvaluedb_iterators_LDADD = ${UNITTEST_STATIC_LDADD} $(LIBOS_LDA) $(LIBGLOBAL_LDA) -test_keyvaluedb_iterators_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} $(LEVELDB_INCLUDE) +test_keyvaluedb_iterators_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} $(LEVELDB_INCLUDE) ${CRYPTO_CXXFLAGS} bin_DEBUGPROGRAMS += test_keyvaluedb_iterators test_cfuse_cache_invalidate_SOURCES = test/test_cfuse_cache_invalidate.cc @@ -1255,6 +1261,7 @@ libcommon_files = \ common/hex.cc \ common/entity_name.cc \ common/ceph_crypto.cc \ + common/ceph_crypto_cms.cc \ common/ipaddr.cc \ common/pick_address.cc \ include/addr_parsing.c \ @@ -1490,6 +1497,7 @@ noinst_HEADERS = \ common/config_obs.h\ common/config_opts.h\ common/ceph_crypto.h\ + common/ceph_crypto_cms.h\ common/utf8.h\ common/mime.h\ common/pick_address.h\ @@ -1829,6 +1837,7 @@ noinst_HEADERS = \ rgw/rgw_gc.h\ rgw/rgw_multi_del.h\ rgw/rgw_op.h\ + rgw/rgw_http_client.h\ rgw/rgw_swift.h\ rgw/rgw_swift_auth.h\ rgw/rgw_rados.h\ diff --git a/src/common/ceph_crypto.cc b/src/common/ceph_crypto.cc index 95909d07e74..3f04349c20b 100644 --- a/src/common/ceph_crypto.cc +++ b/src/common/ceph_crypto.cc @@ -12,6 +12,8 @@ * */ +#include "common/config.h" +#include "common/ceph_context.h" #include "ceph_crypto.h" #include "auth/Crypto.h" @@ -21,7 +23,7 @@ void ceph::crypto::shutdown(); #ifdef USE_CRYPTOPP -void ceph::crypto::init() +void ceph::crypto::init(CephContext *cct) { } @@ -36,10 +38,14 @@ ceph::crypto::HMACSHA1::~HMACSHA1() #elif USE_NSS -void ceph::crypto::init() +void ceph::crypto::init(CephContext *cct) { SECStatus s; - s = NSS_NoDB_Init(NULL); + if (cct->_conf->nss_db_path.empty()) { + s = NSS_NoDB_Init(NULL); + } else { + s = NSS_Init(cct->_conf->nss_db_path.c_str()); + } assert(s == SECSuccess); } diff --git a/src/common/ceph_crypto.h b/src/common/ceph_crypto.h index 52b98b83a63..c55359431d4 100644 --- a/src/common/ceph_crypto.h +++ b/src/common/ceph_crypto.h @@ -21,7 +21,7 @@ namespace ceph { namespace crypto { void assert_init(); - void init(); + void init(CephContext *cct); void shutdown(); using CryptoPP::Weak::MD5; @@ -56,7 +56,7 @@ typedef unsigned char byte; namespace ceph { namespace crypto { void assert_init(); - void init(); + void init(CephContext *cct); void shutdown(); class Digest { private: diff --git a/src/common/ceph_crypto_cms.cc b/src/common/ceph_crypto_cms.cc new file mode 100644 index 00000000000..4d7a4ef598b --- /dev/null +++ b/src/common/ceph_crypto_cms.cc @@ -0,0 +1,360 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1994-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + + +#include "common/config.h" + +#ifdef USE_NSS + +#include <nspr.h> +#include <cert.h> +#include <nss.h> +#include <smime.h> + +#endif + +#include <string.h> +#include <errno.h> + + +#include "include/buffer.h" + +#include "common/debug.h" + +#include "ceph_crypto_cms.h" + +#define dout_subsys ceph_subsys_crypto + + +#ifndef USE_NSS + +int ceph_decode_cms(CephContext *cct, bufferlist& cms_bl, bufferlist& decoded_bl) +{ + return -ENOTSUP; +} + +#else + + +static int cms_verbose = 0; + +static SECStatus +DigestFile(PLArenaPool *poolp, SECItem ***digests, SECItem *input, + SECAlgorithmID **algids) +{ + NSSCMSDigestContext *digcx; + SECStatus rv; + + digcx = NSS_CMSDigestContext_StartMultiple(algids); + if (digcx == NULL) + return SECFailure; + + NSS_CMSDigestContext_Update(digcx, input->data, input->len); + + rv = NSS_CMSDigestContext_FinishMultiple(digcx, poolp, digests); + return rv; +} + + +struct optionsStr { + SECCertUsage certUsage; + CERTCertDBHandle *certHandle; +}; + +struct decodeOptionsStr { + struct optionsStr *options; + SECItem content; + int headerLevel; + PRBool suppressContent; + NSSCMSGetDecryptKeyCallback dkcb; + PK11SymKey *bulkkey; + PRBool keepCerts; +}; + +static NSSCMSMessage * +decode(CephContext *cct, SECItem *input, const struct decodeOptionsStr *decodeOptions, bufferlist& out) +{ + NSSCMSDecoderContext *dcx; + SECStatus rv; + NSSCMSMessage *cmsg; + int nlevels, i; + SECItem sitem; + bufferptr bp; + SECItem *item; + + memset(&sitem, 0, sizeof(sitem)); + + PORT_SetError(0); + dcx = NSS_CMSDecoder_Start(NULL, + NULL, NULL, /* content callback */ + NULL, NULL, /* password callback */ + decodeOptions->dkcb, /* decrypt key callback */ + decodeOptions->bulkkey); + if (dcx == NULL) { + ldout(cct, 0) << "ERROR: failed to set up message decoder" << dendl; + return NULL; + } + rv = NSS_CMSDecoder_Update(dcx, (char *)input->data, input->len); + if (rv != SECSuccess) { + ldout(cct, 0) << "ERROR: failed to decode message" << dendl; + NSS_CMSDecoder_Cancel(dcx); + return NULL; + } + cmsg = NSS_CMSDecoder_Finish(dcx); + if (cmsg == NULL) { + ldout(cct, 0) << "ERROR: failed to decode message" << dendl; + return NULL; + } + + if (decodeOptions->headerLevel >= 0) { + ldout(cct, 20) << "SMIME: " << dendl; + } + + nlevels = NSS_CMSMessage_ContentLevelCount(cmsg); + for (i = 0; i < nlevels; i++) { + NSSCMSContentInfo *cinfo; + SECOidTag typetag; + + cinfo = NSS_CMSMessage_ContentLevel(cmsg, i); + typetag = NSS_CMSContentInfo_GetContentTypeTag(cinfo); + + ldout(cct, 20) << "level=" << decodeOptions->headerLevel << "." << nlevels - i << dendl; + + switch (typetag) { + case SEC_OID_PKCS7_SIGNED_DATA: + { + NSSCMSSignedData *sigd = NULL; + SECItem **digests; + int nsigners; + int j; + + if (decodeOptions->headerLevel >= 0) + ldout(cct, 20) << "type=signedData; " << dendl; + sigd = (NSSCMSSignedData *)NSS_CMSContentInfo_GetContent(cinfo); + if (sigd == NULL) { + ldout(cct, 0) << "ERROR: signedData component missing" << dendl; + goto loser; + } + + /* if we have a content file, but no digests for this signedData */ + if (decodeOptions->content.data != NULL && + !NSS_CMSSignedData_HasDigests(sigd)) { + PLArenaPool *poolp; + SECAlgorithmID **digestalgs; + + /* detached content: grab content file */ + sitem = decodeOptions->content; + + if ((poolp = PORT_NewArena(1024)) == NULL) { + ldout(cct, 0) << "ERROR: Out of memory" << dendl; + goto loser; + } + digestalgs = NSS_CMSSignedData_GetDigestAlgs(sigd); + if (DigestFile (poolp, &digests, &sitem, digestalgs) + != SECSuccess) { + ldout(cct, 0) << "ERROR: problem computing message digest" << dendl; + PORT_FreeArena(poolp, PR_FALSE); + goto loser; + } + if (NSS_CMSSignedData_SetDigests(sigd, digestalgs, digests) + != SECSuccess) { + ldout(cct, 0) << "ERROR: problem setting message digests" << dendl; + PORT_FreeArena(poolp, PR_FALSE); + goto loser; + } + PORT_FreeArena(poolp, PR_FALSE); + } + + /* import the certificates */ + if (NSS_CMSSignedData_ImportCerts(sigd, + decodeOptions->options->certHandle, + decodeOptions->options->certUsage, + decodeOptions->keepCerts) + != SECSuccess) { + ldout(cct, 0) << "ERROR: cert import failed" << dendl; + goto loser; + } + + /* find out about signers */ + nsigners = NSS_CMSSignedData_SignerInfoCount(sigd); + if (decodeOptions->headerLevel >= 0) + ldout(cct, 20) << "nsigners=" << nsigners << dendl; + if (nsigners == 0) { + /* Might be a cert transport message + ** or might be an invalid message, such as a QA test message + ** or a message from an attacker. + */ + SECStatus rv; + rv = NSS_CMSSignedData_VerifyCertsOnly(sigd, + decodeOptions->options->certHandle, + decodeOptions->options->certUsage); + if (rv != SECSuccess) { + ldout(cct, 0) << "ERROR: Verify certs-only failed!" << dendl; + goto loser; + } + return cmsg; + } + + /* still no digests? */ + if (!NSS_CMSSignedData_HasDigests(sigd)) { + ldout(cct, 0) << "ERROR: no message digests" << dendl; + goto loser; + } + + for (j = 0; j < nsigners; j++) { + const char * svs; + NSSCMSSignerInfo *si; + NSSCMSVerificationStatus vs; + SECStatus bad; + + si = NSS_CMSSignedData_GetSignerInfo(sigd, j); + if (decodeOptions->headerLevel >= 0) { + char *signercn; + static char empty[] = { "" }; + + signercn = NSS_CMSSignerInfo_GetSignerCommonName(si); + if (signercn == NULL) + signercn = empty; + ldout(cct, 20) << "\t\tsigner" << j << ".id=" << signercn << dendl; + if (signercn != empty) + PORT_Free(signercn); + } + bad = NSS_CMSSignedData_VerifySignerInfo(sigd, j, + decodeOptions->options->certHandle, + decodeOptions->options->certUsage); + vs = NSS_CMSSignerInfo_GetVerificationStatus(si); + svs = NSS_CMSUtil_VerificationStatusToString(vs); + if (decodeOptions->headerLevel >= 0) { + ldout(cct, 20) << "signer" << j << "status=" << svs << dendl; + /* goto loser ? */ + } else if (bad) { + ldout(cct, 0) << "ERROR: signer " << j << " status = " << svs << dendl; + goto loser; + } + } + } + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + { + NSSCMSEnvelopedData *envd; + if (decodeOptions->headerLevel >= 0) + ldout(cct, 20) << "type=envelopedData; " << dendl; + envd = (NSSCMSEnvelopedData *)NSS_CMSContentInfo_GetContent(cinfo); + if (envd == NULL) { + ldout(cct, 0) << "ERROR: envelopedData component missing" << dendl; + goto loser; + } + } + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + { + NSSCMSEncryptedData *encd; + if (decodeOptions->headerLevel >= 0) + ldout(cct, 20) << "type=encryptedData; " << dendl; + encd = (NSSCMSEncryptedData *)NSS_CMSContentInfo_GetContent(cinfo); + if (encd == NULL) { + ldout(cct, 0) << "ERROR: encryptedData component missing" << dendl; + goto loser; + } + } + break; + case SEC_OID_PKCS7_DATA: + if (decodeOptions->headerLevel >= 0) + ldout(cct, 20) << "type=data; " << dendl; + break; + default: + break; + } + } + + item = (sitem.data ? &sitem : NSS_CMSMessage_GetContent(cmsg)); + out.append((char *)item->data, item->len); + return cmsg; + +loser: + if (cmsg) + NSS_CMSMessage_Destroy(cmsg); + return NULL; +} + +int ceph_decode_cms(CephContext *cct, bufferlist& cms_bl, bufferlist& decoded_bl) +{ + NSSCMSMessage *cmsg = NULL; + struct decodeOptionsStr decodeOptions = { 0 }; + struct optionsStr options; + SECItem input; + + memset(&options, 0, sizeof(options)); + memset(&input, 0, sizeof(input)); + + input.data = (unsigned char *)cms_bl.c_str(); + input.len = cms_bl.length(); + + decodeOptions.content.data = NULL; + decodeOptions.content.len = 0; + decodeOptions.suppressContent = PR_FALSE; + decodeOptions.headerLevel = -1; + decodeOptions.keepCerts = PR_FALSE; + options.certUsage = certUsageEmailSigner; + + options.certHandle = CERT_GetDefaultCertDB(); + if (!options.certHandle) { + ldout(cct, 0) << "ERROR: No default cert DB" << dendl; + return -EIO; + } + if (cms_verbose) { + fprintf(stderr, "Got default certdb\n"); + } + + decodeOptions.options = &options; + + int ret = 0; + + cmsg = decode(cct, &input, &decodeOptions, decoded_bl); + if (!cmsg) { + ldout(cct, 0) << "ERROR: problem decoding" << dendl; + ret = -EINVAL; + } + + if (cmsg) + NSS_CMSMessage_Destroy(cmsg); + + SECITEM_FreeItem(&decodeOptions.content, PR_FALSE); + + return ret; +} + +#endif diff --git a/src/common/ceph_crypto_cms.h b/src/common/ceph_crypto_cms.h new file mode 100644 index 00000000000..5b0a7f5950f --- /dev/null +++ b/src/common/ceph_crypto_cms.h @@ -0,0 +1,10 @@ +#ifndef CEPH_CRYPTO_CMS_H +#define CEPH_CRYPTO_CMS_H + +#include "include/buffer.h" + +class CephContext; + +int ceph_decode_cms(CephContext *cct, bufferlist& cms_bl, bufferlist& decoded_bl); + +#endif diff --git a/src/common/common_init.cc b/src/common/common_init.cc index 76b50e714a5..3f7d501eb26 100644 --- a/src/common/common_init.cc +++ b/src/common/common_init.cc @@ -109,7 +109,7 @@ void complain_about_parse_errors(CephContext *cct, * same application. */ void common_init_finish(CephContext *cct) { - ceph::crypto::init(); + ceph::crypto::init(cct); cct->start_service_thread(); if (cct->_conf->lockdep) { diff --git a/src/common/config_opts.h b/src/common/config_opts.h index d2068fd11ce..5db0889dd20 100644 --- a/src/common/config_opts.h +++ b/src/common/config_opts.h @@ -77,6 +77,7 @@ SUBSYS(monc, 0, 5) SUBSYS(paxos, 0, 5) SUBSYS(tp, 0, 5) SUBSYS(auth, 1, 5) +SUBSYS(crypto, 1, 5) SUBSYS(finisher, 1, 5) SUBSYS(heartbeatmap, 1, 5) SUBSYS(perfcounter, 1, 5) @@ -417,15 +418,25 @@ OPTION(rbd_cache_size, OPT_LONGLONG, 32<<20) // cache size in bytes OPTION(rbd_cache_max_dirty, OPT_LONGLONG, 24<<20) // dirty limit in bytes - set to 0 for write-through caching OPTION(rbd_cache_target_dirty, OPT_LONGLONG, 16<<20) // target dirty limit in bytes OPTION(rbd_cache_max_dirty_age, OPT_FLOAT, 1.0) // seconds in cache before writeback starts + +OPTION(nss_db_path, OPT_STR, "") // path to nss db + OPTION(rgw_data, OPT_STR, "/var/lib/ceph/radosgw/$cluster-$id") OPTION(rgw_enable_apis, OPT_STR, "s3, swift, swift_auth, admin") OPTION(rgw_cache_enabled, OPT_BOOL, true) // rgw cache enabled OPTION(rgw_cache_lru_size, OPT_INT, 10000) // num of entries in rgw cache OPTION(rgw_socket_path, OPT_STR, "") // path to unix domain socket, if not specified, rgw will not run as external fcgi OPTION(rgw_dns_name, OPT_STR, "") -OPTION(rgw_swift_url, OPT_STR, "") // -OPTION(rgw_swift_url_prefix, OPT_STR, "swift") // +OPTION(rgw_swift_url, OPT_STR, "") // the swift url, being published by the internal swift auth +OPTION(rgw_swift_url_prefix, OPT_STR, "swift") // entry point for which a url is considered a swift url +OPTION(rgw_swift_auth_url, OPT_STR, "") // default URL to go and verify tokens for v1 auth (if not using internal swift auth) OPTION(rgw_swift_auth_entry, OPT_STR, "auth") // entry point for which a url is considered a swift auth url +OPTION(rgw_swift_use_keystone, OPT_BOOL, false) // should swift use keystone? +OPTION(rgw_keystone_url, OPT_STR, "") // url for keystone server +OPTION(rgw_keystone_admin_token, OPT_STR, "") // keystone admin token (shared secret) +OPTION(rgw_keystone_accepted_roles, OPT_STR, "Member, admin") // roles required to serve requests +OPTION(rgw_keystone_token_cache_size, OPT_INT, 10000) // max number of entries in keystone token cache +OPTION(rgw_keystone_revocation_interval, OPT_INT, 15 * 60) // seconds between tokens revocation check OPTION(rgw_admin_entry, OPT_STR, "admin") // entry point for which a url is considered an admin request OPTION(rgw_enforce_swift_acls, OPT_BOOL, true) OPTION(rgw_print_continue, OPT_BOOL, true) // enable if 100-Continue works diff --git a/src/json_spirit/json_spirit_reader_template.h b/src/json_spirit/json_spirit_reader_template.h index b814e916255..f87b59331b7 100644 --- a/src/json_spirit/json_spirit_reader_template.h +++ b/src/json_spirit/json_spirit_reader_template.h @@ -13,7 +13,7 @@ #include "json_spirit_value.h"
#include "json_spirit_error_position.h"
-//#define BOOST_SPIRIT_THREADSAFE // uncomment for multithreaded use, requires linking to boost.thread
+#define BOOST_SPIRIT_THREADSAFE // uncomment for multithreaded use, requires linking to boost.thread
#include <boost/bind.hpp>
#include <boost/function.hpp>
diff --git a/src/rgw/rgw_common.cc b/src/rgw/rgw_common.cc index c030e3466cd..62fda4b4d3c 100644 --- a/src/rgw/rgw_common.cc +++ b/src/rgw/rgw_common.cc @@ -39,6 +39,9 @@ int rgw_perf_start(CephContext *cct) plb.add_u64_counter(l_rgw_cache_hit, "cache_hit"); plb.add_u64_counter(l_rgw_cache_miss, "cache_miss"); + plb.add_u64_counter(l_rgw_keystone_token_cache_hit, "keystone_token_cache_hit"); + plb.add_u64_counter(l_rgw_keystone_token_cache_miss, "keystone_token_cache_miss"); + perfcounter = plb.create_perf_counters(); cct->get_perfcounters_collection()->add(perfcounter); return 0; @@ -88,7 +91,7 @@ is_err() const req_state::req_state(CephContext *_cct, struct RGWEnv *e) : cct(_cct), cio(NULL), op(OP_UNKNOWN), os_auth_token(NULL), - os_user(NULL), os_groups(NULL), env(e) + env(e) { enable_ops_log = env->conf->enable_ops_log; enable_usage_log = env->conf->enable_usage_log; @@ -109,8 +112,6 @@ req_state::req_state(CephContext *_cct, struct RGWEnv *e) : cct(_cct), cio(NULL) prot_flags = 0; os_auth_token = NULL; - os_user = NULL; - os_groups = NULL; time = ceph_clock_now(cct); perm_mask = 0; content_length = 0; @@ -130,8 +131,6 @@ req_state::~req_state() { delete formatter; delete bucket_acl; delete object_acl; - free(os_user); - free(os_groups); free((void *)object); free((void *)bucket_name); } diff --git a/src/rgw/rgw_common.h b/src/rgw/rgw_common.h index 0ba9e95c80b..c55907a38d3 100644 --- a/src/rgw/rgw_common.h +++ b/src/rgw/rgw_common.h @@ -138,6 +138,9 @@ enum { l_rgw_cache_hit, l_rgw_cache_miss, + l_rgw_keystone_token_cache_hit, + l_rgw_keystone_token_cache_miss, + l_rgw_last, }; @@ -611,8 +614,8 @@ struct req_state { int prot_flags; const char *os_auth_token; - char *os_user; - char *os_groups; + string swift_user; + string swift_groups; utime_t time; diff --git a/src/rgw/rgw_http_client.cc b/src/rgw/rgw_http_client.cc new file mode 100644 index 00000000000..4c7b99c17c3 --- /dev/null +++ b/src/rgw/rgw_http_client.cc @@ -0,0 +1,76 @@ +#include <curl/curl.h> +#include <curl/easy.h> + +#include "rgw_common.h" +#include "rgw_http_client.h" + +#define dout_subsys ceph_subsys_rgw + +static size_t read_http_header(void *ptr, size_t size, size_t nmemb, void *_info) +{ + RGWHTTPClient *client = (RGWHTTPClient *)_info; + size_t len = size * nmemb; + int ret = client->read_header(ptr, size * nmemb); + if (ret < 0) { + dout(0) << "WARNING: client->read_header() returned ret=" << ret << dendl; + } + + return len; +} + +static size_t read_http_data(void *ptr, size_t size, size_t nmemb, void *_info) +{ + RGWHTTPClient *client = (RGWHTTPClient *)_info; + size_t len = size * nmemb; + int ret = client->read_data(ptr, size * nmemb); + if (ret < 0) { + dout(0) << "WARNING: client->read_data() returned ret=" << ret << dendl; + } + + return len; +} + +int RGWHTTPClient::process(const string& url) +{ + int ret = 0; + CURL *curl_handle; + + char error_buf[CURL_ERROR_SIZE]; + + curl_handle = curl_easy_init(); + + dout(20) << "sending request to " << url << dendl; + + curl_slist *h = NULL; + + list<pair<string, string> >::iterator iter; + for (iter = headers.begin(); iter != headers.end(); ++iter) { + pair<string, string>& p = *iter; + string val = p.first; + val.append(": "); + val.append(p.second); + h = curl_slist_append(h, val.c_str()); + } + + curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, read_http_header); + curl_easy_setopt(curl_handle, CURLOPT_WRITEHEADER, (void *)this); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, read_http_data); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)this); + curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, (void *)error_buf); + if (h) { + curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, (void *)h); + } + CURLcode status = curl_easy_perform(curl_handle); + if (status) { + dout(0) << "curl_easy_performed returned error: " << error_buf << dendl; + ret = -EINVAL; + } + curl_easy_cleanup(curl_handle); + curl_slist_free_all(h); + + return ret; +} + + diff --git a/src/rgw/rgw_http_client.h b/src/rgw/rgw_http_client.h new file mode 100644 index 00000000000..944ea89e3f3 --- /dev/null +++ b/src/rgw/rgw_http_client.h @@ -0,0 +1,23 @@ +#ifndef CEPH_RGW_HTTP_CLIENT_H +#define CEPH_RGW_HTTP_CLIENT_H + +#include "rgw_common.h" + +class RGWHTTPClient +{ + list<pair<string, string> > headers; +public: + virtual ~RGWHTTPClient() {} + RGWHTTPClient() {} + + void append_header(const string& name, const string& val) { + headers.push_back(pair<string, string>(name, val)); + } + + virtual int read_header(void *ptr, size_t len) { return 0; } + virtual int read_data(void *ptr, size_t len) { return 0; } + + int process(const string& url); +}; + +#endif diff --git a/src/rgw/rgw_json.cc b/src/rgw/rgw_json.cc index 1a6fc32dea9..0f91c2279e1 100644 --- a/src/rgw/rgw_json.cc +++ b/src/rgw/rgw_json.cc @@ -2,6 +2,7 @@ #include <include/types.h> #include "rgw_json.h" +#include "rgw_common.h" // for testing DELETE ME #include <fstream> @@ -9,6 +10,8 @@ using namespace std; using namespace json_spirit; +#define dout_subsys ceph_subsys_rgw + JSONObjIter::JSONObjIter() { } @@ -52,7 +55,7 @@ JSONObj::~JSONObj() void JSONObj::add_child(string el, JSONObj *obj) { - cout << "add_child: " << name << " <- " << el << endl; + cout << "add_child: " << name << " <- " << el << std::endl; children.insert(pair<string, JSONObj *>(el, obj)); } @@ -65,14 +68,16 @@ bool JSONObj::get_attr(string name, string& attr) return true; } -JSONObjIter JSONObj::find(string name) +JSONObjIter JSONObj::find(const string& name) { JSONObjIter iter; map<string, JSONObj *>::iterator first; map<string, JSONObj *>::iterator last; first = children.find(name); - last = children.upper_bound(name); - iter.set(first, last); + if (first != children.end()) { + last = children.upper_bound(name); + iter.set(first, last); + } return iter; } @@ -80,14 +85,10 @@ JSONObjIter JSONObj::find_first() { JSONObjIter iter; iter.set(children.begin(), children.end()); - cout << "count=" << children.size() << endl; - for (map<string, JSONObj *>:: iterator i = children.begin(); i != children.end(); ++i) { - cout << "child: " << i->first << endl; - } return iter; } -JSONObjIter JSONObj::find_first(string name) +JSONObjIter JSONObj::find_first(const string& name) { JSONObjIter iter; map<string, JSONObj *>::iterator first; @@ -96,6 +97,26 @@ JSONObjIter JSONObj::find_first(string name) return iter; } +JSONObj *JSONObj::find_obj(const string& name) +{ + JSONObjIter iter = find(name); + if (iter.end()) + return NULL; + + return *iter; +} + +bool JSONObj::get_data(const string& key, string *dest) +{ + JSONObj *obj = find_obj(key); + if (!obj) + return false; + + *dest = obj->get_data(); + + return true; +} + /* accepts a JSON Array or JSON Object contained in * a JSON Spirit Value, v, and creates a JSONObj for each * child contained in v @@ -118,16 +139,11 @@ void JSONObj::handle_value(Value v) for (unsigned j = 0; j < temp_array.size(); j++) { Value cur = temp_array[j]; - - if (cur.type() == obj_type) { - handle_value(cur); - } else { - string temp_name; - - JSONObj *child = new JSONObj; - child->init(this, cur, temp_name); - add_child(child->get_name(), child); - } + string temp_name; + + JSONObj *child = new JSONObj; + child->init(this, cur, temp_name); + add_child(child->get_name(), child); } } } diff --git a/src/rgw/rgw_json.h b/src/rgw/rgw_json.h index e11ab25b405..d0dec397c28 100644 --- a/src/rgw/rgw_json.h +++ b/src/rgw/rgw_json.h @@ -55,12 +55,14 @@ public: string& get_name() { return name; } string& get_data() { return data_string; } + bool get_data(const string& key, string *dest); JSONObj *get_parent(); void add_child(string el, JSONObj *child); bool get_attr(string name, string& attr); - JSONObjIter find(string name); + JSONObjIter find(const string& name); JSONObjIter find_first(); - JSONObjIter find_first(string name); + JSONObjIter find_first(const string& name); + JSONObj *find_obj(const string& name); friend ostream& operator<<(ostream& out, JSONObj& obj); // does not work, FIXME diff --git a/src/rgw/rgw_main.cc b/src/rgw/rgw_main.cc index 5d9efee3cd4..944b59a5c8d 100644 --- a/src/rgw/rgw_main.cc +++ b/src/rgw/rgw_main.cc @@ -458,6 +458,7 @@ int main(int argc, const char **argv) RGWREST rest; list<string> apis; + bool do_swift = false; get_str_list(g_conf->rgw_enable_apis, apis); @@ -469,8 +470,11 @@ int main(int argc, const char **argv) if (apis_map.count("s3") > 0) rest.register_default_mgr(new RGWRESTMgr_S3); - if (apis_map.count("swift") > 0) + if (apis_map.count("swift") > 0) { + do_swift = true; + swift_init(g_ceph_context); rest.register_resource(g_conf->rgw_swift_url_prefix, new RGWRESTMgr_SWIFT); + } if (apis_map.count("swift_auth") > 0) rest.register_resource(g_conf->rgw_swift_auth_entry, new RGWRESTMgr_SWIFT_Auth); @@ -484,6 +488,10 @@ int main(int argc, const char **argv) RGWProcess process(g_ceph_context, store, g_conf->rgw_thread_pool_size, &rest); process.run(); + if (do_swift) { + swift_finalize(); + } + rgw_log_usage_finalize(); rgw_perf_stop(g_ceph_context); diff --git a/src/rgw/rgw_rest_swift.cc b/src/rgw/rgw_rest_swift.cc index 6fefb548e75..e64028c3caf 100644 --- a/src/rgw/rgw_rest_swift.cc +++ b/src/rgw/rgw_rest_swift.cc @@ -612,7 +612,7 @@ RGWOp *RGWHandler_ObjStore_Obj_SWIFT::op_copy() int RGWHandler_ObjStore_SWIFT::authorize() { - bool authorized = rgw_verify_swift_token(store, s); + bool authorized = rgw_swift->verify_swift_token(store, s); if (!authorized) return -EPERM; diff --git a/src/rgw/rgw_swift.cc b/src/rgw/rgw_swift.cc index e7f4035d56c..2ce04074a03 100644 --- a/src/rgw/rgw_swift.cc +++ b/src/rgw/rgw_swift.cc @@ -2,25 +2,45 @@ #include <stdlib.h> #include <unistd.h> -#include <curl/curl.h> -#include <curl/easy.h> - +#include "rgw_json.h" #include "rgw_common.h" #include "rgw_swift.h" #include "rgw_swift_auth.h" #include "rgw_user.h" +#include "rgw_http_client.h" + +#include "include/str_list.h" + +#include "common/ceph_crypto_cms.h" +#include "common/armor.h" #define dout_subsys ceph_subsys_rgw -static size_t read_http_header(void *ptr, size_t size, size_t nmemb, void *_info) +static list<string> roles_list; + +class RGWKeystoneTokenCache; + +class RGWValidateSwiftToken : public RGWHTTPClient { + CephContext *cct; + struct rgw_swift_auth_info *info; + +protected: + RGWValidateSwiftToken() : cct(NULL), info(NULL) {} +public: + RGWValidateSwiftToken(CephContext *_cct, struct rgw_swift_auth_info *_info) : cct(_cct), info(_info) {} + + int read_header(void *ptr, size_t len); + + friend class RGWKeystoneTokenCache; +}; + +int RGWValidateSwiftToken::read_header(void *ptr, size_t len) { - size_t len = size * nmemb; char line[len + 1]; - struct rgw_swift_auth_info *info = (struct rgw_swift_auth_info *)_info; char *s = (char *)ptr, *end = (char *)ptr + len; char *p = line; - dout(10) << "read_http_header" << dendl; + ldout(cct, 10) << "read_http_header" << dendl; while (s != end) { if (*s == '\r') { @@ -29,7 +49,7 @@ static size_t read_http_header(void *ptr, size_t size, size_t nmemb, void *_info } if (*s == '\n') { *p = '\0'; - dout(10) << "os_auth:" << line << dendl; + ldout(cct, 10) << "os_auth:" << line << dendl; // TODO: fill whatever data required here char *l = line; char *tok = strsep(&l, " \t:"); @@ -40,11 +60,11 @@ static size_t read_http_header(void *ptr, size_t size, size_t nmemb, void *_info if (strcmp(tok, "HTTP") == 0) { info->status = atoi(l); } else if (strcasecmp(tok, "X-Auth-Groups") == 0) { - info->auth_groups = strdup(l); + info->auth_groups = l; char *s = strchr(l, ','); if (s) { *s = '\0'; - info->user = strdup(l); + info->user = l; } } else if (strcasecmp(tok, "X-Auth-Ttl") == 0) { info->ttl = atoll(l); @@ -54,34 +74,558 @@ static size_t read_http_header(void *ptr, size_t size, size_t nmemb, void *_info if (s != end) *p++ = *s++; } - return len; + return 0; } -static int rgw_swift_validate_token(const char *token, struct rgw_swift_auth_info *info) +int RGWSwift::validate_token(const char *token, struct rgw_swift_auth_info *info) { - CURL *curl_handle; - string auth_url = "http://127.0.0.1:11000/token"; + if (g_conf->rgw_swift_auth_url.empty()) + return -EINVAL; + + string auth_url = g_conf->rgw_swift_auth_url; + if (auth_url[auth_url.size() - 1] != '/') + auth_url.append("/"); + auth_url.append("token"); char url_buf[auth_url.size() + 1 + strlen(token) + 1]; sprintf(url_buf, "%s/%s", auth_url.c_str(), token); - dout(10) << "rgw_swift_validate_token url=" << url_buf << dendl; + RGWValidateSwiftToken validate(cct, info); + + ldout(cct, 10) << "rgw_swift_validate_token url=" << url_buf << dendl; + + int ret = validate.process(url_buf); + if (ret < 0) + return ret; + + return 0; +} + +int KeystoneToken::parse(CephContext *cct, bufferlist& bl) +{ + RGWJSONParser parser; + + if (!parser.parse(bl.c_str(), bl.length())) { + ldout(cct, 0) << "malformed json" << dendl; + return -EINVAL; + } + + JSONObjIter iter = parser.find_first("access"); + if (iter.end()) { + ldout(cct, 0) << "token response is missing access section" << dendl; + return -EINVAL; + } + + JSONObj *access_obj = *iter; + JSONObj *user = access_obj->find_obj("user"); + if (!user) { + ldout(cct, 0) << "token response is missing user section" << dendl; + return -EINVAL; + } + + if (!user->get_data("username", &user_name)) { + ldout(cct, 0) << "token response is missing user username field" << dendl; + return -EINVAL; + } + + JSONObj *roles_obj = user->find_obj("roles"); + if (!roles_obj) { + ldout(cct, 0) << "token response is missing roles section, or section empty" << dendl; + return -EINVAL; + } + + JSONObjIter riter = roles_obj->find_first(); + if (riter.end()) { + ldout(cct, 0) << "token response has an empty roles list" << dendl; + return -EINVAL; + } + + for (; !riter.end(); ++riter) { + JSONObj *role_obj = *riter; + if (!role_obj) { + ldout(cct, 0) << "ERROR: role object is NULL" << dendl; + return -EINVAL; + } + + JSONObj *role_name = role_obj->find_obj("name"); + if (!role_name) { + ldout(cct, 0) << "token response is missing role name section" << dendl; + return -EINVAL; + } + string role = role_name->get_data(); + roles[role] = true; + } + + JSONObj *token = access_obj->find_obj("token"); + if (!token) { + ldout(cct, 0) << "missing token section in response" << dendl; + return -EINVAL; + } + + string expires; + + if (!token->get_data("expires", &expires)) { + ldout(cct, 0) << "token response is missing expiration field" << dendl; + return -EINVAL; + } + + struct tm t; + if (!parse_iso8601(expires.c_str(), &t)) { + ldout(cct, 0) << "failed to parse token expiration (" << expires << ")" << dendl; + return -EINVAL; + } + + expiration = timegm(&t); + + JSONObj *tenant = token->find_obj("tenant"); + if (!tenant) { + ldout(cct, 0) << "token response is missing tenant section" << dendl; + return -EINVAL; + } + + if (!tenant->get_data("id", &tenant_id)) { + ldout(cct, 0) << "tenant is missing id field" << dendl; + return -EINVAL; + } + + + if (!tenant->get_data("name", &tenant_name)) { + ldout(cct, 0) << "tenant is missing name field" << dendl; + return -EINVAL; + } + + return 0; +} + +struct token_entry { + KeystoneToken token; + list<string>::iterator lru_iter; +}; + +class RGWKeystoneTokenCache { + CephContext *cct; + + map<string, token_entry> tokens; + list<string> tokens_lru; + + Mutex lock; + + size_t max; + +public: + RGWKeystoneTokenCache(CephContext *_cct, int _max) : cct(_cct), lock("RGWKeystoneTokenCache"), max(_max) {} + + bool find(const string& token_id, KeystoneToken& token); + void add(const string& token_id, KeystoneToken& token); + void invalidate(const string& token_id); +}; + +bool RGWKeystoneTokenCache::find(const string& token_id, KeystoneToken& token) +{ + lock.Lock(); + map<string, token_entry>::iterator iter = tokens.find(token_id); + if (iter == tokens.end()) { + lock.Unlock(); + if (perfcounter) perfcounter->inc(l_rgw_keystone_token_cache_miss); + return false; + } + + token_entry& entry = iter->second; + tokens_lru.erase(entry.lru_iter); + + if (entry.token.expired()) { + tokens.erase(iter); + lock.Unlock(); + if (perfcounter) perfcounter->inc(l_rgw_keystone_token_cache_hit); + return false; + } + token = entry.token; + + tokens_lru.push_front(token_id); + entry.lru_iter = tokens_lru.begin(); + + lock.Unlock(); + if (perfcounter) perfcounter->inc(l_rgw_keystone_token_cache_hit); + + return true; +} + +void RGWKeystoneTokenCache::add(const string& token_id, KeystoneToken& token) +{ + lock.Lock(); + map<string, token_entry>::iterator iter = tokens.find(token_id); + if (iter != tokens.end()) { + token_entry& e = iter->second; + tokens_lru.erase(e.lru_iter); + } + + tokens_lru.push_front(token_id); + token_entry& entry = tokens[token_id]; + entry.token = token; + entry.lru_iter = tokens_lru.begin(); + + while (tokens_lru.size() > max) { + list<string>::reverse_iterator riter = tokens_lru.rbegin(); + iter = tokens.find(*riter); + assert(iter != tokens.end()); + tokens.erase(iter); + tokens_lru.pop_back(); + } + + lock.Unlock(); +} + +void RGWKeystoneTokenCache::invalidate(const string& token_id) +{ + Mutex::Locker l(lock); + map<string, token_entry>::iterator iter = tokens.find(token_id); + if (iter == tokens.end()) + return; + + ldout(cct, 20) << "invalidating revoked token id=" << token_id << dendl; + token_entry& e = iter->second; + tokens_lru.erase(e.lru_iter); + tokens.erase(iter); +} + +class RGWValidateKeystoneToken : public RGWHTTPClient { + bufferlist *bl; +public: + RGWValidateKeystoneToken(bufferlist *_bl) : bl(_bl) {} + + int read_data(void *ptr, size_t len) { + bl->append((char *)ptr, len); + return 0; + } +}; + +static RGWKeystoneTokenCache *keystone_token_cache = NULL; + +class RGWGetRevokedTokens : public RGWHTTPClient { + bufferlist *bl; +public: + RGWGetRevokedTokens(bufferlist *_bl) : bl(_bl) {} + + int read_data(void *ptr, size_t len) { + bl->append((char *)ptr, len); + return 0; + } +}; + +static int open_cms_envelope(CephContext *cct, string& src, string& dst) +{ +#define BEGIN_CMS "-----BEGIN CMS-----" +#define END_CMS "-----END CMS-----" + + int start = src.find(BEGIN_CMS); + if (start < 0) { + ldout(cct, 0) << "failed to find " << BEGIN_CMS << " in response" << dendl; + return -EINVAL; + } + start += sizeof(BEGIN_CMS) - 1; + + int end = src.find(END_CMS); + if (end < 0) { + ldout(cct, 0) << "failed to find " << END_CMS << " in response" << dendl; + return -EINVAL; + } + + string s = src.substr(start, end - start); + + int pos = 0; + + do { + int next = s.find('\n', pos); + if (next < 0) { + dst.append(s.substr(pos)); + break; + } else { + dst.append(s.substr(pos, next - pos)); + } + pos = next + 1; + } while (pos < (int)s.size()); + + return 0; +} + +static int decode_b64_cms(CephContext *cct, const string& signed_b64, bufferlist& bl) +{ + bufferptr signed_ber(signed_b64.size() * 2); + char *dest = signed_ber.c_str(); + const char *src = signed_b64.c_str(); + size_t len = signed_b64.size(); + char buf[len + 1]; + buf[len] = '\0'; + for (size_t i = 0; i < len; i++, src++) { + if (*src != '-') + buf[i] = *src; + else + buf[i] = '/'; + } + int ret = ceph_unarmor(dest, dest + signed_ber.length(), buf, buf + signed_b64.size()); + if (ret < 0) { + ldout(cct, 0) << "ceph_unarmor() failed, ret=" << ret << dendl; + return ret; + } + + bufferlist signed_ber_bl; + signed_ber_bl.append(signed_ber); + + ret = ceph_decode_cms(cct, signed_ber_bl, bl); + if (ret < 0) { + ldout(cct, 0) << "ceph_decode_cms returned " << ret << dendl; + return ret; + } + + return 0; +} + + +int RGWSwift::check_revoked() +{ + bufferlist bl; + RGWGetRevokedTokens req(&bl); + + string url = g_conf->rgw_keystone_url; + if (url.empty()) { + ldout(cct, 0) << "ERROR: keystone url is not configured" << dendl; + return -EINVAL; + } + if (url[url.size() - 1] != '/') + url.append("/"); + url.append("v2.0/tokens/revoked"); + + req.append_header("X-Auth-Token", g_conf->rgw_keystone_admin_token); + + int ret = req.process(url); + if (ret < 0) + return ret; + + bl.append((char)0); // NULL terminate for debug output + + ldout(cct, 10) << "request returned " << bl.c_str() << dendl; + + RGWJSONParser parser; + + if (!parser.parse(bl.c_str(), bl.length())) { + ldout(cct, 0) << "malformed json" << dendl; + return -EINVAL; + } + + JSONObjIter iter = parser.find_first("signed"); + if (iter.end()) { + ldout(cct, 0) << "revoked tokens response is missing signed section" << dendl; + return -EINVAL; + } + + JSONObj *signed_obj = *iter; + + string signed_str = signed_obj->get_data(); + + ldout(cct, 10) << "signed=" << signed_str << dendl; + + string signed_b64; + ret = open_cms_envelope(cct, signed_str, signed_b64); + if (ret < 0) + return ret; + + ldout(cct, 10) << "content=" << signed_b64 << dendl; + + bufferlist json; + ret = decode_b64_cms(cct, signed_b64, json); + if (ret < 0) { + return ret; + } + + ldout(cct, 10) << "ceph_decode_cms: decoded: " << json.c_str() << dendl; + + RGWJSONParser list_parser; + if (!list_parser.parse(json.c_str(), json.length())) { + ldout(cct, 0) << "malformed json" << dendl; + return -EINVAL; + } + + JSONObjIter revoked_iter = list_parser.find_first("revoked"); + if (revoked_iter.end()) { + ldout(cct, 0) << "no revoked section in json" << dendl; + return -EINVAL; + } + + JSONObj *revoked_obj = *revoked_iter; + + JSONObjIter tokens_iter = revoked_obj->find_first(); + for (; !tokens_iter.end(); ++tokens_iter) { + JSONObj *o = *tokens_iter; + + JSONObj *token = o->find_obj("id"); + if (!token) { + ldout(cct, 0) << "bad token in array, missing id" << dendl; + continue; + } + + string token_id = token->get_data(); + keystone_token_cache->invalidate(token_id); + } + + return 0; +} + +static void rgw_set_keystone_token_auth_info(KeystoneToken& token, struct rgw_swift_auth_info *info) +{ + info->user = token.tenant_id; + info->display_name = token.tenant_name; + info->status = 200; +} + +int RGWSwift::parse_keystone_token_response(const string& token, bufferlist& bl, struct rgw_swift_auth_info *info, KeystoneToken& t) +{ + int ret = t.parse(cct, bl); + if (ret < 0) + return ret; + + bool found = false; + list<string>::iterator iter; + for (iter = roles_list.begin(); iter != roles_list.end(); ++iter) { + const string& role = *iter; + if (t.roles.find(role) != t.roles.end()) { + found = true; + break; + } + } + + if (!found) { + ldout(cct, 0) << "user does not hold a matching role; required roles: " << g_conf->rgw_keystone_accepted_roles << dendl; + return -EPERM; + } + + ldout(cct, 0) << "validated token: " << t.tenant_name << ":" << t.user_name << " expires: " << t.expiration << dendl; + + rgw_set_keystone_token_auth_info(t, info); + + return 0; +} + +int RGWSwift::update_user_info(RGWRados *store, struct rgw_swift_auth_info *info, RGWUserInfo& user_info) +{ + if (rgw_get_user_info_by_uid(store, info->user, user_info) < 0) { + ldout(cct, 0) << "NOTICE: couldn't map swift user" << dendl; + user_info.user_id = info->user; + user_info.display_name = info->display_name; + + int ret = rgw_store_user_info(store, user_info, true); + if (ret < 0) { + ldout(cct, 0) << "ERROR: failed to store new user's info: ret=" << ret << dendl; + return ret; + } + } + return 0; +} + +#define PKI_ANS1_PREFIX "MII" + +static bool is_pki_token(const string& token) +{ + return token.compare(0, sizeof(PKI_ANS1_PREFIX) - 1, PKI_ANS1_PREFIX) == 0; +} + +static void get_token_id(const string& token, string& token_id) +{ + if (!is_pki_token(token)) { + token_id = token; + return; + } + + unsigned char m[CEPH_CRYPTO_MD5_DIGESTSIZE]; + + MD5 hash; + hash.Update((const byte *)token.c_str(), token.size()); + hash.Final(m); + + + char calc_md5[CEPH_CRYPTO_MD5_DIGESTSIZE * 2 + 1]; + buf_to_hex(m, CEPH_CRYPTO_MD5_DIGESTSIZE, calc_md5); + token_id = calc_md5; +} + +static bool decode_pki_token(CephContext *cct, const string& token, bufferlist& bl) +{ + if (!is_pki_token(token)) + return false; + + int ret = decode_b64_cms(cct, token, bl); + if (ret < 0) + return false; - curl_handle = curl_easy_init(); + ldout(cct, 20) << "successfully decoded pki token" << dendl; - curl_easy_setopt(curl_handle, CURLOPT_URL, url_buf); - curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1L); + return true; +} - curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, read_http_header); +int RGWSwift::validate_keystone_token(RGWRados *store, const string& token, struct rgw_swift_auth_info *info, + RGWUserInfo& rgw_user) +{ + KeystoneToken t; - curl_easy_setopt(curl_handle, CURLOPT_WRITEHEADER, info); + string token_id; + get_token_id(token, token_id); - curl_easy_perform(curl_handle); - curl_easy_cleanup(curl_handle); + ldout(cct, 20) << "token_id=" << token_id << dendl; + + /* check cache first */ + if (keystone_token_cache->find(token_id, t)) { + rgw_set_keystone_token_auth_info(t, info); + + ldout(cct, 20) << "cached token.tenant_id=" << t.tenant_id << dendl; + + int ret = update_user_info(store, info, rgw_user); + if (ret < 0) + return ret; + + return 0; + } + + bufferlist bl; + + /* check if that's a self signed token that we can decode */ + if (!decode_pki_token(cct, token, bl)) { + + /* can't decode, just go to the keystone server for validation */ + + RGWValidateKeystoneToken validate(&bl); + + string url = g_conf->rgw_keystone_url; + if (url.empty()) { + ldout(cct, 0) << "ERROR: keystone url is not configured" << dendl; + return -EINVAL; + } + if (url[url.size() - 1] != '/') + url.append("/"); + url.append("v2.0/tokens/"); + url.append(token); + + validate.append_header("X-Auth-Token", g_conf->rgw_keystone_admin_token); + + int ret = validate.process(url); + if (ret < 0) + return ret; + } + + bl.append((char)0); // NULL terminate for debug output + + ldout(cct, 20) << "received response: " << bl.c_str() << dendl; + + int ret = parse_keystone_token_response(token, bl, info, t); + if (ret < 0) + return ret; + + ret = update_user_info(store, info, rgw_user); + if (ret < 0) + return ret; return 0; } -bool rgw_verify_swift_token(RGWRados *store, req_state *s) + +bool RGWSwift::verify_swift_token(RGWRados *store, req_state *s) { if (!s->os_auth_token) return false; @@ -96,32 +640,105 @@ bool rgw_verify_swift_token(RGWRados *store, req_state *s) struct rgw_swift_auth_info info; - memset(&info, 0, sizeof(info)); - info.status = 401; // start with access denied, validate_token might change that - int ret = rgw_swift_validate_token(s->os_auth_token, &info); + int ret; + + if (g_conf->rgw_swift_use_keystone) { + ret = validate_keystone_token(store, s->os_auth_token, &info, s->user); + return (ret >= 0); + } + + ret = validate_token(s->os_auth_token, &info); if (ret < 0) return ret; - if (!info.user) { - dout(5) << "swift auth didn't authorize a user" << dendl; + if (info.user.empty()) { + ldout(cct, 5) << "swift auth didn't authorize a user" << dendl; return false; } - s->os_user = info.user; - s->os_groups = info.auth_groups; + s->swift_user = info.user; + s->swift_groups = info.auth_groups; - string swift_user = s->os_user; + string swift_user = s->swift_user; - dout(10) << "swift user=" << s->os_user << dendl; + ldout(cct, 10) << "swift user=" << s->swift_user << dendl; if (rgw_get_user_info_by_swift(store, swift_user, s->user) < 0) { - dout(0) << "NOTICE: couldn't map swift user" << dendl; + ldout(cct, 0) << "NOTICE: couldn't map swift user" << dendl; return false; } - dout(10) << "user_id=" << s->user.user_id << dendl; + ldout(cct, 10) << "user_id=" << s->user.user_id << dendl; return true; } + +void RGWSwift::init() +{ + get_str_list(cct->_conf->rgw_keystone_accepted_roles, roles_list); + + keystone_token_cache = new RGWKeystoneTokenCache(cct, cct->_conf->rgw_keystone_token_cache_size); + + keystone_revoke_thread = new KeystoneRevokeThread(cct, this); + keystone_revoke_thread->create(); +} + + +void RGWSwift::finalize() +{ + delete keystone_token_cache; + keystone_token_cache = NULL; + + down_flag.set(1); + if (keystone_revoke_thread) { + keystone_revoke_thread->stop(); + keystone_revoke_thread->join(); + } + delete keystone_revoke_thread; + keystone_revoke_thread = NULL; +} + +RGWSwift *rgw_swift = NULL; + +void swift_init(CephContext *cct) +{ + rgw_swift = new RGWSwift(cct); +} + +void swift_finalize() +{ + delete rgw_swift; +} + +bool RGWSwift::going_down() +{ + return (down_flag.read() != 0); +} + +void *RGWSwift::KeystoneRevokeThread::entry() { + do { + dout(2) << "keystone revoke thread: start" << dendl; + int r = swift->check_revoked(); + if (r < 0) { + dout(0) << "ERROR: keystone revocation processing returned error r=" << r << dendl; + } + + if (swift->going_down()) + break; + + lock.Lock(); + cond.WaitInterval(cct, lock, utime_t(cct->_conf->rgw_keystone_revocation_interval, 0)); + lock.Unlock(); + } while (!swift->going_down()); + + return NULL; +} + +void RGWSwift::KeystoneRevokeThread::stop() +{ + Mutex::Locker l(lock); + cond.Signal(); +} + diff --git a/src/rgw/rgw_swift.h b/src/rgw/rgw_swift.h index a678b22065a..bdca5b46283 100644 --- a/src/rgw/rgw_swift.h +++ b/src/rgw/rgw_swift.h @@ -3,18 +3,85 @@ #define CEPH_RGW_SWIFT_H #include "rgw_common.h" +#include "common/Cond.h" class RGWRados; struct rgw_swift_auth_info { int status; - char *auth_groups; - char *user; + string auth_groups; + string user; + string display_name; long long ttl; + + rgw_swift_auth_info() : status(0), ttl(0) {} +}; + +class KeystoneToken { +public: + string tenant_name; + string tenant_id; + string user_name; + time_t expiration; + + map<string, bool> roles; + + KeystoneToken() {} + + int parse(CephContext *cct, bufferlist& bl); + + bool expired() { + uint64_t now = ceph_clock_now(NULL).sec(); + return (now < (uint64_t)expiration); + } }; -bool rgw_verify_swift_token(RGWRados *store, req_state *s); +class RGWSwift { + CephContext *cct; + atomic_t down_flag; + + int validate_token(const char *token, struct rgw_swift_auth_info *info); + int validate_keystone_token(RGWRados *store, const string& token, struct rgw_swift_auth_info *info, + RGWUserInfo& rgw_user); + + int parse_keystone_token_response(const string& token, bufferlist& bl, struct rgw_swift_auth_info *info, + KeystoneToken& t); + int update_user_info(RGWRados *store, struct rgw_swift_auth_info *info, RGWUserInfo& user_info); + + class KeystoneRevokeThread : public Thread { + CephContext *cct; + RGWSwift *swift; + Mutex lock; + Cond cond; + + public: + KeystoneRevokeThread(CephContext *_cct, RGWSwift *_swift) : cct(_cct), swift(_swift), lock("KeystoneRevokeThread") {} + void *entry(); + void stop(); + }; + + KeystoneRevokeThread *keystone_revoke_thread; + + void init(); + void finalize(); +protected: + int check_revoked(); +public: + + RGWSwift(CephContext *_cct) : cct(_cct) { + init(); + } + ~RGWSwift() { + finalize(); + } + + bool verify_swift_token(RGWRados *store, req_state *s); + bool going_down(); +}; +extern RGWSwift *rgw_swift; +void swift_init(CephContext *cct); +void swift_finalize(); #endif diff --git a/src/test/ceph_crypto.cc b/src/test/ceph_crypto.cc index 403f6b5400b..2c934fa848c 100644 --- a/src/test/ceph_crypto.cc +++ b/src/test/ceph_crypto.cc @@ -5,7 +5,7 @@ class CryptoEnvironment: public ::testing::Environment { public: void SetUp() { - ceph::crypto::init(); + ceph::crypto::init(g_ceph_context); } }; @@ -117,7 +117,7 @@ class ForkDeathTest : public ::testing::Test { virtual void TearDown() { // undo the NSS shutdown we did in the parent process, after the // test is done - ceph::crypto::init(); + ceph::crypto::init(g_ceph_context); } }; @@ -127,7 +127,7 @@ void do_simple_crypto() { // fork, and if you comment out the ceph::crypto::init, or if the // trick were to fail, you would see this ending in an assert and // not exit status 0 - ceph::crypto::init(); + ceph::crypto::init(g_ceph_context); ceph::crypto::MD5 h; h.Update((const byte*)"foo", 3); unsigned char digest[CEPH_CRYPTO_MD5_DIGESTSIZE]; diff --git a/src/test/crypto.cc b/src/test/crypto.cc index 85150ef80a9..80a5495001d 100644 --- a/src/test/crypto.cc +++ b/src/test/crypto.cc @@ -10,7 +10,7 @@ class CryptoEnvironment: public ::testing::Environment { public: void SetUp() { - ceph::crypto::init(); + ceph::crypto::init(g_ceph_context); } }; |