diff options
author | Sage Weil <sage@inktank.com> | 2012-11-13 14:50:42 -0800 |
---|---|---|
committer | Sage Weil <sage@inktank.com> | 2012-11-13 14:50:42 -0800 |
commit | 6a8a59c5d00495be03db708953b7a8b5c55b8233 (patch) | |
tree | b33d9740fa5df742b99c6e332961a0761dd48b8a | |
parent | 5d27f3da654b2d1b3f90a5758fc47b89f012a8d2 (diff) | |
parent | 1ba969b18000aa0150410860b8305db2d5679e7e (diff) | |
download | ceph-6a8a59c5d00495be03db708953b7a8b5c55b8233.tar.gz |
Merge remote-tracking branch 'gh/wip-rgw-integration'
Conflicts:
src/common/config_opts.h
37 files changed, 3587 insertions, 220 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 36b55b9f115..c7e65264839 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 @@ -151,7 +151,7 @@ base: ceph-mon ceph-osd ceph-mds \ ceph cephfs \ ceph-syn \ rados radosgw librados-config \ - ceph-conf monmaptool osdmaptool crushtool ceph-authtool \ + ceph-conf monmaptool osdmaptool crushtool jsontest ceph-authtool \ init-ceph mkcephfs @@ -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 @@ -330,6 +333,7 @@ librgw_a_SOURCES = \ rgw/rgw_fcgi.cc \ rgw/rgw_xml.cc \ rgw/rgw_usage.cc \ + rgw/rgw_json.cc \ rgw/rgw_user.cc \ rgw/rgw_tools.cc \ rgw/rgw_rados.cc \ @@ -339,6 +343,7 @@ librgw_a_SOURCES = \ rgw/rgw_formats.cc \ rgw/rgw_log.cc \ rgw/rgw_multi.cc \ + rgw/rgw_policy_s3.cc \ rgw/rgw_gc.cc \ rgw/rgw_multi_del.cc \ rgw/rgw_env.cc @@ -357,6 +362,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 @@ -374,6 +380,11 @@ rgw_multiparser_CXXFLAGS = ${CRYPTO_CXXFLAGS} ${AM_CXXFLAGS} rgw_multiparser_LDADD = $(my_radosgw_ldadd) bin_DEBUGPROGRAMS += rgw_multiparser +rgw_jsonparser_SOURCES = rgw/rgw_jsonparser.cc +rgw_jsonparser_CXXFLAGS = ${CRYPTO_CXXFLAGS} ${AM_CXXFLAGS} +rgw_jsonparser_LDADD = $(my_radosgw_ldadd) +bin_DEBUGPROGRAMS += rgw_jsonparser + endif # librbd @@ -910,7 +921,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 = \ @@ -918,11 +929,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 = \ @@ -930,13 +942,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 @@ -954,13 +967,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 \ @@ -968,7 +981,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 @@ -1247,6 +1260,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 \ @@ -1486,6 +1500,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\ @@ -1813,15 +1828,19 @@ noinst_HEADERS = \ rgw/rgw_client_io.h\ rgw/rgw_fcgi.h\ rgw/rgw_xml.h\ + rgw/rgw_json.h\ rgw/rgw_cache.h\ rgw/rgw_common.h\ + rgw/rgw_string.h\ rgw/rgw_formats.h\ rgw/rgw_html_errors.h\ rgw/rgw_log.h\ rgw/rgw_multi.h\ + rgw/rgw_policy_s3.h\ 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 19efd0f1d4f..4d7a08b01b7 100644 --- a/src/common/config_opts.h +++ b/src/common/config_opts.h @@ -77,6 +77,7 @@ SUBSYS(monc, 0, 10) SUBSYS(paxos, 0, 5) SUBSYS(tp, 0, 5) SUBSYS(auth, 1, 5) +SUBSYS(crypto, 1, 5) SUBSYS(finisher, 1, 1) SUBSYS(heartbeatmap, 1, 5) SUBSYS(perfcounter, 1, 5) @@ -430,17 +431,28 @@ 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_swift_token_expiration, OPT_INT, 24 * 3600) // time in seconds for swift token expiration OPTION(rgw_print_continue, OPT_BOOL, true) // enable if 100-Continue works OPTION(rgw_remote_addr_param, OPT_STR, "REMOTE_ADDR") // e.g. X-Forwarded-For, if you have a reverse proxy OPTION(rgw_op_thread_timeout, OPT_INT, 10*60) @@ -469,6 +481,7 @@ OPTION(rgw_gc_obj_min_wait, OPT_INT, 2 * 3600) // wait time before object may OPTION(rgw_gc_processor_max_time, OPT_INT, 3600) // total run time for a single gc processor work OPTION(rgw_gc_processor_period, OPT_INT, 3600) // gc processor cycle time OPTION(rgw_resolve_cname, OPT_BOOL, false) // should rgw try to resolve hostname as a dns cname record +OPTION(rgw_obj_stripe_size, OPT_INT, 4 << 20) OPTION(mutex_perf_counter, OPT_BOOL, false) // enable/disable mutex perf counter diff --git a/src/json_spirit/json_spirit_reader_template.h b/src/json_spirit/json_spirit_reader_template.h index 93f64e49dc0..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>
@@ -468,7 +468,7 @@ namespace json_spirit ;
members_
- = pair_ >> *( ',' >> pair_ )
+ = pair_ >> *( ',' >> pair_ | ch_p(',') )
;
pair_
@@ -484,7 +484,7 @@ namespace json_spirit ;
elements_
- = value_ >> *( ',' >> value_ )
+ = value_ >> *( ',' >> value_ | ch_p(',') )
;
string_
diff --git a/src/rgw/rgw_common.cc b/src/rgw/rgw_common.cc index b0460929da8..62fda4b4d3c 100644 --- a/src/rgw/rgw_common.cc +++ b/src/rgw/rgw_common.cc @@ -2,6 +2,7 @@ #include "rgw_common.h" #include "rgw_acl.h" +#include "rgw_string.h" #include "common/ceph_crypto.h" #include "common/armor.h" @@ -38,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; @@ -81,13 +85,13 @@ is_clear() const bool rgw_err:: is_err() const { - return !(http_ret >= 200 && http_ret <= 299); + return !(http_ret >= 200 && http_ret <= 399); } 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; @@ -108,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; @@ -129,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); } @@ -141,6 +141,34 @@ std::ostream& operator<<(std::ostream& oss, const rgw_err &err) return oss; } +string rgw_string_unquote(const string& s) +{ + if (s[0] != '"' || s.size() < 2) + return s; + + int len; + for (len = s.size(); len > 2; --len) { + if (s[len - 1] != ' ') + break; + } + + if (s[len-1] != '"') + return s; + + return s.substr(1, len - 2); +} + +static void trim_whitespace(const string& src, string& dst) +{ + const char *spacestr = " \t\n\r\f\v"; + int start = src.find_first_not_of(spacestr); + if (start < 0) + return; + + int end = src.find_last_not_of(spacestr); + dst = src.substr(start, end - start + 1); +} + static bool check_str_end(const char *s) { if (!s) @@ -209,6 +237,34 @@ bool parse_rfc2616(const char *s, struct tm *t) return parse_rfc850(s, t) || parse_asctime(s, t) || parse_rfc1123(s, t) || parse_rfc1123_alt(s,t); } +bool parse_iso8601(const char *s, struct tm *t) +{ + memset(t, 0, sizeof(*t)); + const char *p = strptime(s, "%Y-%m-%dT%T", t); + if (!p) { + dout(0) << "parse_iso8601 failed" << dendl; + return false; + } + string str; + trim_whitespace(p, str); + if (str.size() == 1 && str[0] == 'Z') + return true; + + if (str.size() != 5) { + return false; + } + if (str[0] != '.' || + str[str.size() - 1] != 'Z') + return false; + + uint32_t ms; + int r = stringtoul(str.substr(1, 3), &ms); + if (r < 0) + return false; + + return true; +} + int parse_time(const char *time_str, time_t *time) { struct tm tm; @@ -221,7 +277,7 @@ int parse_time(const char *time_str, time_t *time) return 0; } -int parse_date(string& date, uint64_t *epoch, string *out_date, string *out_time) +int parse_date(const string& date, uint64_t *epoch, string *out_date, string *out_time) { struct tm tm; @@ -558,17 +614,6 @@ int RGWUserCaps::parse_cap_perm(const string& str, uint32_t *perm) return 0; } -static void trim_whitespace(const string& src, string& dst) -{ - const char *spacestr = " \t\n\r\f\v"; - int start = src.find_first_not_of(spacestr); - if (start < 0) - return; - - int end = src.find_last_not_of(spacestr); - dst = src.substr(start, end - start + 1); -} - int RGWUserCaps::get_cap(const string& cap, string& type, uint32_t *pperm) { int pos = cap.find('='); diff --git a/src/rgw/rgw_common.h b/src/rgw/rgw_common.h index f4a02eedcf4..c55907a38d3 100644 --- a/src/rgw/rgw_common.h +++ b/src/rgw/rgw_common.h @@ -39,10 +39,12 @@ using ceph::crypto::MD5; #define RGW_ATTR_PREFIX "user.rgw." +#define RGW_AMZ_META_PREFIX "x-amz-meta-" + #define RGW_ATTR_ACL RGW_ATTR_PREFIX "acl" #define RGW_ATTR_ETAG RGW_ATTR_PREFIX "etag" #define RGW_ATTR_BUCKETS RGW_ATTR_PREFIX "buckets" -#define RGW_ATTR_META_PREFIX RGW_ATTR_PREFIX "x-amz-meta-" +#define RGW_ATTR_META_PREFIX RGW_ATTR_PREFIX RGW_AMZ_META_PREFIX #define RGW_ATTR_CONTENT_TYPE RGW_ATTR_PREFIX "content_type" #define RGW_ATTR_CACHE_CONTROL RGW_ATTR_PREFIX "cache_control" #define RGW_ATTR_CONTENT_DISP RGW_ATTR_PREFIX "content_disposition" @@ -79,6 +81,7 @@ using ceph::crypto::MD5; #define STATUS_ACCEPTED 1901 #define STATUS_NO_CONTENT 1902 #define STATUS_PARTIAL_CONTENT 1903 +#define STATUS_REDIRECT 1904 #define ERR_INVALID_BUCKET_NAME 2000 #define ERR_INVALID_OBJECT_NAME 2001 @@ -102,6 +105,7 @@ using ceph::crypto::MD5; #define ERR_TOO_LARGE 2019 #define ERR_TOO_MANY_BUCKETS 2020 #define ERR_INVALID_REQUEST 2021 +#define ERR_TOO_SMALL 2022 #define ERR_USER_SUSPENDED 2100 #define ERR_INTERNAL_ERROR 2200 @@ -134,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, }; @@ -565,6 +572,7 @@ struct req_state { ceph::Formatter *formatter; string decoded_uri; string request_uri; + string script_uri; string request_params; const char *host; const char *method; @@ -606,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; @@ -696,35 +704,6 @@ struct RGWBucketEnt { }; WRITE_CLASS_ENCODER(RGWBucketEnt) -struct RGWUploadPartInfo { - uint32_t num; - uint64_t size; - string etag; - utime_t modified; - - RGWUploadPartInfo() : num(0), size(0) {} - - void encode(bufferlist& bl) const { - ENCODE_START(2, 2, bl); - ::encode(num, bl); - ::encode(size, bl); - ::encode(etag, bl); - ::encode(modified, bl); - ENCODE_FINISH(bl); - } - void decode(bufferlist::iterator& bl) { - DECODE_START_LEGACY_COMPAT_LEN(2, 2, 2, bl); - ::decode(num, bl); - ::decode(size, bl); - ::decode(etag, bl); - ::decode(modified, bl); - DECODE_FINISH(bl); - } - void dump(Formatter *f) const; - static void generate_test_instances(list<RGWUploadPartInfo*>& o); -}; -WRITE_CLASS_ENCODER(RGWUploadPartInfo) - class rgw_obj { std::string orig_obj; std::string orig_key; @@ -1018,10 +997,12 @@ static inline const char *rgw_obj_category_name(RGWObjCategory category) return "unknown"; } +extern string rgw_string_unquote(const string& s); /** time parsing */ extern int parse_time(const char *time_str, time_t *time); extern bool parse_rfc2616(const char *s, struct tm *t); -extern int parse_date(string& date, uint64_t *epoch, string *out_date = NULL, string *out_time = NULL); +extern bool parse_iso8601(const char *s, struct tm *t); +extern int parse_date(const string& date, uint64_t *epoch, string *out_date = NULL, string *out_time = NULL); /** Check if the req_state's user has the necessary permissions * to do the requested action */ diff --git a/src/rgw/rgw_html_errors.h b/src/rgw/rgw_html_errors.h index c4335bcaf0a..254af46e663 100644 --- a/src/rgw/rgw_html_errors.h +++ b/src/rgw/rgw_html_errors.h @@ -15,6 +15,7 @@ const static struct rgw_html_errors RGW_HTML_ERRORS[] = { { STATUS_ACCEPTED, 202, "Accepted" }, { STATUS_NO_CONTENT, 204, "NoContent" }, { STATUS_PARTIAL_CONTENT, 206, "" }, + { STATUS_REDIRECT, 303, "" }, { ERR_NOT_MODIFIED, 304, "NotModified" }, { EINVAL, 400, "InvalidArgument" }, { ERR_INVALID_REQUEST, 400, "InvalidRequest" }, @@ -27,6 +28,7 @@ const static struct rgw_html_errors RGW_HTML_ERRORS[] = { { ERR_INVALID_PART_ORDER, 400, "InvalidPartOrder" }, { ERR_REQUEST_TIMEOUT, 400, "RequestTimeout" }, { ERR_TOO_LARGE, 400, "EntityTooLarge" }, + { ERR_TOO_SMALL, 400, "EntityTooSmall" }, { ERR_TOO_MANY_BUCKETS, 400, "TooManyBuckets" }, { ERR_LENGTH_REQUIRED, 411, "MissingContentLength" }, { EACCES, 403, "AccessDenied" }, 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 new file mode 100644 index 00000000000..0f91c2279e1 --- /dev/null +++ b/src/rgw/rgw_json.cc @@ -0,0 +1,271 @@ +#include <iostream> +#include <include/types.h> + +#include "rgw_json.h" +#include "rgw_common.h" + +// for testing DELETE ME +#include <fstream> + +using namespace std; +using namespace json_spirit; + +#define dout_subsys ceph_subsys_rgw + +JSONObjIter::JSONObjIter() +{ +} + +JSONObjIter::~JSONObjIter() +{ +} + +void JSONObjIter::set(const JSONObjIter::map_iter_t &_cur, const JSONObjIter::map_iter_t &_last) +{ + cur = _cur; + last = _last; +} + +void JSONObjIter::operator++() +{ + if (cur != last) + ++cur; +}; + +JSONObj *JSONObjIter::operator*() +{ + return cur->second; +}; + +// does not work, FIXME +ostream& operator<<(ostream& out, JSONObj& obj) { + out << obj.name << ": " << obj.data_string; + return out; +} + +JSONObj::~JSONObj() +{ + multimap<string, JSONObj *>::iterator iter; + for (iter = children.begin(); iter != children.end(); ++iter) { + JSONObj *obj = iter->second; + delete obj; + } +} + + +void JSONObj::add_child(string el, JSONObj *obj) +{ + cout << "add_child: " << name << " <- " << el << std::endl; + children.insert(pair<string, JSONObj *>(el, obj)); +} + +bool JSONObj::get_attr(string name, string& attr) +{ + map<string, string>::iterator iter = attr_map.find(name); + if (iter == attr_map.end()) + return false; + attr = iter->second; + return true; +} + +JSONObjIter JSONObj::find(const string& name) +{ + JSONObjIter iter; + map<string, JSONObj *>::iterator first; + map<string, JSONObj *>::iterator last; + first = children.find(name); + if (first != children.end()) { + last = children.upper_bound(name); + iter.set(first, last); + } + return iter; +} + +JSONObjIter JSONObj::find_first() +{ + JSONObjIter iter; + iter.set(children.begin(), children.end()); + return iter; +} + +JSONObjIter JSONObj::find_first(const string& name) +{ + JSONObjIter iter; + map<string, JSONObj *>::iterator first; + first = children.find(name); + iter.set(first, children.end()); + 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 + */ +void JSONObj::handle_value(Value v) +{ + if (v.type() == obj_type) { + Object temp_obj = v.get_obj(); + for (Object::size_type i = 0; i < temp_obj.size(); i++) { + Pair temp_pair = temp_obj[i]; + string temp_name = temp_pair.name_; + Value temp_value = temp_pair.value_; + JSONObj *child = new JSONObj; + child->init(this, temp_value, temp_name); + add_child(temp_name, child); + } + } else if (v.type() == array_type) { + Array temp_array = v.get_array(); + Value value; + + for (unsigned j = 0; j < temp_array.size(); j++) { + Value cur = temp_array[j]; + string temp_name; + + JSONObj *child = new JSONObj; + child->init(this, cur, temp_name); + add_child(child->get_name(), child); + } + } +} + +void JSONObj::init(JSONObj *p, Value v, string n) +{ + name = n; + parent = p; + data = v; + + handle_value(v); + if (v.type() == str_type) + data_string = v.get_str(); + else + data_string = write(v, raw_utf8); + attr_map.insert(pair<string,string>(name, data_string)); +} + +JSONObj *JSONObj::get_parent() +{ + return parent; +} + +bool JSONObj::is_object() +{ + cout << data.type() << std::endl; + return (data.type() == obj_type); +} + +bool JSONObj::is_array() +{ + return (data.type() == array_type); +} + +vector<string> JSONObj::get_array_elements() +{ + vector<string> elements; + Array temp_array; + + if (data.type() == array_type) + temp_array = data.get_array(); + + int array_size = temp_array.size(); + if (array_size > 0) + for (int i = 0; i < array_size; i++) { + Value temp_value = temp_array[i]; + string temp_string; + temp_string = write(temp_value, raw_utf8); + elements.push_back(temp_string); + } + + return elements; +} + +RGWJSONParser::RGWJSONParser() : buf_len(0), success(true) +{ +} + +RGWJSONParser::~RGWJSONParser() +{ +} + + + +void RGWJSONParser::handle_data(const char *s, int len) +{ + json_buffer.append(s, len); // check for problems with null termination FIXME + buf_len += len; +} + +// parse a supplied JSON fragment +bool RGWJSONParser::parse(const char *buf_, int len) +{ + string json_string = buf_; + // make a substring to len + json_string = json_string.substr(0, len); + success = read(json_string, data); + if (success) + handle_value(data); + else + set_failure(); + + return success; +} + +// parse the internal json_buffer up to len +bool RGWJSONParser::parse(int len) +{ + string json_string = json_buffer.substr(0, len); + success = read(json_string, data); + if (success) + handle_value(data); + else + set_failure(); + + return success; +} + +// parse the complete internal json_buffer +bool RGWJSONParser::parse() +{ + success = read(json_buffer, data); + if (success) + handle_value(data); + else + set_failure(); + + return success; +} + +// parse a supplied ifstream, for testing. DELETE ME +bool RGWJSONParser::parse(const char *file_name) +{ + ifstream is(file_name); + success = read(is, data); + if (success) + handle_value(data); + else + set_failure(); + + return success; +} + + + diff --git a/src/rgw/rgw_json.h b/src/rgw/rgw_json.h new file mode 100644 index 00000000000..d0dec397c28 --- /dev/null +++ b/src/rgw/rgw_json.h @@ -0,0 +1,94 @@ +#ifndef RGW_JSON_H +#define RGW_JSON_H + +#include <iostream> +#include <include/types.h> + +// for testing DELETE ME +#include <fstream> + +#include "json_spirit/json_spirit.h" + + +using namespace std; +using namespace json_spirit; + + +class JSONObj; + +class JSONObjIter { + typedef map<string, JSONObj *>::iterator map_iter_t; + map_iter_t cur; + map_iter_t last; + +public: + JSONObjIter(); + ~JSONObjIter(); + void set(const JSONObjIter::map_iter_t &_cur, const JSONObjIter::map_iter_t &_end); + + void operator++(); + JSONObj *operator*(); + + bool end() { + return (cur == last); + } +}; + +class JSONObj +{ + JSONObj *parent; +protected: + string name; // corresponds to obj_type in XMLObj + Value data; + string data_string; + multimap<string, JSONObj *> children; + map<string, string> attr_map; + void handle_value(Value v); + +public: + + JSONObj() : parent(NULL){}; + + virtual ~JSONObj(); + + void init(JSONObj *p, Value v, string n); + + 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(const string& name); + JSONObjIter find_first(); + JSONObjIter find_first(const string& name); + JSONObj *find_obj(const string& name); + + friend ostream& operator<<(ostream& out, JSONObj& obj); // does not work, FIXME + + bool is_array(); + bool is_object(); + vector<string> get_array_elements(); +}; + +class RGWJSONParser : public JSONObj +{ + int buf_len; + string json_buffer; + bool success; +public: + RGWJSONParser(); + virtual ~RGWJSONParser(); + void handle_data(const char *s, int len); + + bool parse(const char *buf_, int len); + bool parse(int len); + bool parse(); + bool parse(const char *file_name); + + const char *get_json() { return json_buffer.c_str(); } + void set_failure() { success = false; } +}; + + +#endif diff --git a/src/rgw/rgw_jsonparser.cc b/src/rgw/rgw_jsonparser.cc new file mode 100644 index 00000000000..7a1f11c325e --- /dev/null +++ b/src/rgw/rgw_jsonparser.cc @@ -0,0 +1,80 @@ +#include <string.h> + +#include <iostream> +#include <map> + +#include "include/types.h" + +#include "rgw_json.h" + +#define dout_subsys ceph_subsys_rgw + +using namespace std; + +void dump_array(JSONObj *obj) +{ + + JSONObjIter iter = obj->find_first(); + + for (; !iter.end(); ++iter) { + JSONObj *o = *iter; + cout << "data=" << o->get_data() << endl; + } + +} + +int main(int argc, char **argv) { + RGWJSONParser parser; + + char buf[1024]; + + for (;;) { + int done; + int len; + + len = fread(buf, 1, sizeof(buf), stdin); + if (ferror(stdin)) { + cerr << "read error" << std::endl; + exit(-1); + } + done = feof(stdin); + + bool ret = parser.parse(buf, len); + if (!ret) + cerr << "parse error" << std::endl; + + if (done) + break; + } + + JSONObjIter iter = parser.find_first(); + + for (; !iter.end(); ++iter) { + JSONObj *obj = *iter; + cout << "is_object=" << obj->is_object() << endl; + cout << "is_array=" << obj->is_array() << endl; + cout << "name=" << obj->get_name() << endl; + cout << "data=" << obj->get_data() << endl; + } + + iter = parser.find_first("conditions"); + if (!iter.end()) { + JSONObj *obj = *iter; + + JSONObjIter iter2 = obj->find_first(); + for (; !iter2.end(); ++iter2) { + JSONObj *child = *iter2; + cout << "is_object=" << child->is_object() << endl; + cout << "is_array=" << child->is_array() << endl; + if (child->is_array()) { + dump_array(child); + } + cout << "name=" << child->get_name() << endl; + cout << "data=" << child->get_data() << endl; + } + } + + + exit(0); +} + 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_op.cc b/src/rgw/rgw_op.cc index 61e373425f7..3f550c2a978 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -283,7 +283,7 @@ static int read_policy(RGWRados *store, struct req_state *s, RGWBucketInfo& buck * only_bucket: If true, reads the bucket ACL rather than the object ACL. * Returns: 0 on success, -ERR# otherwise. */ -static int build_policies(RGWRados *store, struct req_state *s, bool only_bucket, bool prefetch_data) +int rgw_build_policies(RGWRados *store, struct req_state *s, bool only_bucket, bool prefetch_data) { int ret = 0; string obj_str; @@ -878,6 +878,34 @@ int RGWPutObj::verify_permission() return 0; } +int RGWPutObjProcessor::complete(string& etag, map<string, bufferlist>& attrs) +{ + int r = do_complete(etag, attrs); + if (r < 0) + return r; + + is_complete = true; + return 0; +} + +RGWPutObjProcessor::~RGWPutObjProcessor() +{ + if (is_complete) + return; + + if (!s) + return; + + list<rgw_obj>::iterator iter; + for (iter = objs.begin(); iter != objs.end(); ++iter) { + rgw_obj& obj = *iter; + int r = store->delete_obj(s->obj_ctx, obj); + if (r < 0 && r != -ENOENT) { + ldout(s->cct, 0) << "WARNING: failed to remove obj (" << obj << "), leaked" << dendl; + } + } +} + class RGWPutObjProcessor_Plain : public RGWPutObjProcessor { bufferlist data; @@ -888,7 +916,7 @@ protected: int prepare(RGWRados *store, struct req_state *s); int handle_data(bufferlist& bl, off_t ofs, void **phandle); int throttle_data(void *handle) { return 0; } - int complete(string& etag, map<string, bufferlist>& attrs); + int do_complete(string& etag, map<string, bufferlist>& attrs); public: RGWPutObjProcessor_Plain() : ofs(0) {} @@ -914,7 +942,7 @@ int RGWPutObjProcessor_Plain::handle_data(bufferlist& bl, off_t _ofs, void **pha return 0; } -int RGWPutObjProcessor_Plain::complete(string& etag, map<string, bufferlist>& attrs) +int RGWPutObjProcessor_Plain::do_complete(string& etag, map<string, bufferlist>& attrs) { int r = store->put_obj_meta(s->obj_ctx, obj, data.length(), NULL, attrs, RGW_OBJ_CATEGORY_MAIN, PUT_OBJ_CREATE, NULL, &data, NULL, NULL); @@ -933,10 +961,9 @@ class RGWPutObjProcessor_Aio : public RGWPutObjProcessor int drain_pending(); protected: - rgw_obj obj; uint64_t obj_len; - int handle_data(bufferlist& bl, off_t ofs, void **phandle); + int handle_data(rgw_obj& obj, bufferlist& bl, off_t ofs, off_t abs_ofs, void **phandle); int throttle_data(void *handle); RGWPutObjProcessor_Aio() : max_chunks(RGW_MAX_PENDING_CHUNKS), obj_len(0) {} @@ -945,10 +972,10 @@ protected: } }; -int RGWPutObjProcessor_Aio::handle_data(bufferlist& bl, off_t ofs, void **phandle) +int RGWPutObjProcessor_Aio::handle_data(rgw_obj& obj, bufferlist& bl, off_t ofs, off_t abs_ofs, void **phandle) { - if ((uint64_t)ofs + bl.length() > obj_len) - obj_len = ofs + bl.length(); + if ((uint64_t)abs_ofs + bl.length() > obj_len) + obj_len = abs_ofs + bl.length(); // For the first call pass -1 as the offset to // do a write_full. @@ -1025,22 +1052,41 @@ int RGWPutObjProcessor_Aio::throttle_data(void *handle) class RGWPutObjProcessor_Atomic : public RGWPutObjProcessor_Aio { bufferlist first_chunk; - rgw_obj head_obj; + uint64_t part_size; + off_t cur_part_ofs; + off_t next_part_ofs; + int cur_part_id; protected: + string oid_prefix; + rgw_obj head_obj; + rgw_obj cur_obj; + RGWObjManifest manifest; + + virtual bool immutable_head() { return false; } + int prepare(RGWRados *store, struct req_state *s); - int complete(string& etag, map<string, bufferlist>& attrs); + virtual int do_complete(string& etag, map<string, bufferlist>& attrs); + + void prepare_next_part(off_t ofs); + void complete_parts(); public: ~RGWPutObjProcessor_Atomic() {} - RGWPutObjProcessor_Atomic() {} + RGWPutObjProcessor_Atomic(uint64_t _p) : part_size(_p), + cur_part_ofs(0), + next_part_ofs(_p), + cur_part_id(0) {} int handle_data(bufferlist& bl, off_t ofs, void **phandle) { - if (!ofs) { + if (!ofs && !immutable_head()) { first_chunk.claim(bl); *phandle = NULL; obj_len = (uint64_t)first_chunk.length(); + prepare_next_part(first_chunk.length()); return 0; } - int r = RGWPutObjProcessor_Aio::handle_data(bl, ofs, phandle); + if (ofs >= next_part_ofs) + prepare_next_part(ofs); + int r = RGWPutObjProcessor_Aio::handle_data(cur_obj, bl, ofs - cur_part_ofs, ofs, phandle); return r; } @@ -1055,27 +1101,55 @@ int RGWPutObjProcessor_Atomic::prepare(RGWRados *store, struct req_state *s) char buf[33]; gen_rand_alphanumeric(s->cct, buf, sizeof(buf) - 1); - oid.append("_"); - oid.append(buf); - obj.init_ns(s->bucket, oid, shadow_ns); + oid_prefix.append("_"); + oid_prefix.append(buf); + oid_prefix.append("_"); return 0; } -int RGWPutObjProcessor_Atomic::complete(string& etag, map<string, bufferlist>& attrs) -{ - uint64_t head_chunk_len = first_chunk.length(); - RGWObjManifest manifest; - manifest.objs[0].loc = head_obj; - manifest.objs[0].loc_ofs = 0; - manifest.objs[0].size = head_chunk_len; - if (obj_len > RGW_MAX_CHUNK_SIZE) { - manifest.objs[RGW_MAX_CHUNK_SIZE].loc = obj; - manifest.objs[RGW_MAX_CHUNK_SIZE].loc_ofs = RGW_MAX_CHUNK_SIZE; - manifest.objs[RGW_MAX_CHUNK_SIZE].size = obj_len - head_chunk_len; +void RGWPutObjProcessor_Atomic::prepare_next_part(off_t ofs) { + int num_parts = manifest.objs.size(); + RGWObjManifestPart *part; + + /* first update manifest for written data */ + if (!num_parts) { + part = &manifest.objs[cur_part_ofs]; + part->loc = head_obj; + } else { + part = &manifest.objs[cur_part_ofs]; + part->loc = cur_obj; } + part->loc_ofs = 0; + part->size = ofs - cur_part_ofs; + + if ((uint64_t)ofs > manifest.obj_size) + manifest.obj_size = ofs; + + /* now update params for next part */ - manifest.obj_size = obj_len; + cur_part_ofs = ofs; + next_part_ofs = cur_part_ofs + part_size; + char buf[16]; + + cur_part_id++; + snprintf(buf, sizeof(buf), "%d", cur_part_id); + string cur_oid = oid_prefix; + cur_oid.append(buf); + cur_obj.init_ns(s->bucket, cur_oid, shadow_ns); + + add_obj(cur_obj); +}; + +void RGWPutObjProcessor_Atomic::complete_parts() +{ + if (obj_len > (uint64_t)cur_part_ofs) + prepare_next_part(obj_len); +} + +int RGWPutObjProcessor_Atomic::do_complete(string& etag, map<string, bufferlist>& attrs) +{ + complete_parts(); store->set_atomic(s->obj_ctx, head_obj); @@ -1085,16 +1159,17 @@ int RGWPutObjProcessor_Atomic::complete(string& etag, map<string, bufferlist>& a return r; } -class RGWPutObjProcessor_Multipart : public RGWPutObjProcessor_Aio +class RGWPutObjProcessor_Multipart : public RGWPutObjProcessor_Atomic { string part_num; RGWMPObj mp; protected: + bool immutable_head() { return true; } int prepare(RGWRados *store, struct req_state *s); - int complete(string& etag, map<string, bufferlist>& attrs); + int do_complete(string& etag, map<string, bufferlist>& attrs); public: - RGWPutObjProcessor_Multipart() {} + RGWPutObjProcessor_Multipart(uint64_t _p) : RGWPutObjProcessor_Atomic(_p) {} }; int RGWPutObjProcessor_Multipart::prepare(RGWRados *store, struct req_state *s) @@ -1112,13 +1187,19 @@ int RGWPutObjProcessor_Multipart::prepare(RGWRados *store, struct req_state *s) } oid = mp.get_part(part_num); - obj.init_ns(s->bucket, oid, mp_ns); + head_obj.init_ns(s->bucket, oid, mp_ns); + oid_prefix = oid; + oid_prefix.append("_"); + cur_obj = head_obj; + add_obj(head_obj); return 0; } -int RGWPutObjProcessor_Multipart::complete(string& etag, map<string, bufferlist>& attrs) +int RGWPutObjProcessor_Multipart::do_complete(string& etag, map<string, bufferlist>& attrs) { - int r = store->put_obj_meta(s->obj_ctx, obj, s->obj_size, NULL, attrs, RGW_OBJ_CATEGORY_MAIN, 0, NULL, NULL, NULL, NULL); + complete_parts(); + + int r = store->put_obj_meta(s->obj_ctx, head_obj, s->obj_size, NULL, attrs, RGW_OBJ_CATEGORY_MAIN, 0, NULL, NULL, NULL, NULL); if (r < 0) return r; @@ -1130,6 +1211,7 @@ int RGWPutObjProcessor_Multipart::complete(string& etag, map<string, bufferlist> info.etag = etag; info.size = s->obj_size; info.modified = ceph_clock_now(s->cct); + info.manifest = manifest; ::encode(info, bl); string multipart_meta_obj = mp.get_meta(); @@ -1149,13 +1231,16 @@ RGWPutObjProcessor *RGWPutObj::select_processor() bool multipart = s->args.exists("uploadId"); + uint64_t part_size = s->cct->_conf->rgw_obj_stripe_size; + if (!multipart) { - if (s->content_length <= RGW_MAX_CHUNK_SIZE && !chunked_upload) + if (s->content_length <= RGW_MAX_CHUNK_SIZE && !chunked_upload) { processor = new RGWPutObjProcessor_Plain(); - else - processor = new RGWPutObjProcessor_Atomic(); + } else { + processor = new RGWPutObjProcessor_Atomic(part_size); + } } else { - processor = new RGWPutObjProcessor_Multipart(); + processor = new RGWPutObjProcessor_Multipart(part_size); } return processor; @@ -1288,6 +1373,122 @@ done: (ceph_clock_now(s->cct) - s->time)); } +int RGWPostObj::verify_permission() +{ + return 0; +} + +RGWPutObjProcessor *RGWPostObj::select_processor() +{ + RGWPutObjProcessor *processor; + + uint64_t part_size = s->cct->_conf->rgw_obj_stripe_size; + + if (s->content_length <= RGW_MAX_CHUNK_SIZE) + processor = new RGWPutObjProcessor_Plain(); + else + processor = new RGWPutObjProcessor_Atomic(part_size); + + return processor; +} + +void RGWPostObj::dispose_processor(RGWPutObjProcessor *processor) +{ + delete processor; +} + +void RGWPostObj::execute() +{ + RGWPutObjProcessor *processor = NULL; + char calc_md5[CEPH_CRYPTO_MD5_DIGESTSIZE * 2 + 1]; + unsigned char m[CEPH_CRYPTO_MD5_DIGESTSIZE]; + MD5 hash; + bufferlist bl, aclbl; + int len = 0; + + // read in the data from the POST form + ret = get_params(); + if (ret < 0) + goto done; + + ret = verify_params(); + if (ret < 0) + goto done; + + if (!verify_bucket_permission(s, RGW_PERM_WRITE)) { + ret = -EACCES; + goto done; + } + + processor = select_processor(); + + ret = processor->prepare(store, s); + if (ret < 0) + goto done; + + while (data_pending) { + bufferlist data; + len = get_data(data); + + if (len < 0) { + ret = len; + goto done; + } + + if (!len) + break; + + void *handle; + const unsigned char *data_ptr = (const unsigned char *)data.c_str(); + + ret = processor->handle_data(data, ofs, &handle); + if (ret < 0) + goto done; + + hash.Update(data_ptr, len); + + ret = processor->throttle_data(handle); + if (ret < 0) + goto done; + + ofs += len; + + if (ofs > max_len) { + ret = -ERR_TOO_LARGE; + goto done; + } + } + + if (len < min_len) { + ret = -ERR_TOO_SMALL; + goto done; + } + + s->obj_size = ofs; + + hash.Final(m); + buf_to_hex(m, CEPH_CRYPTO_MD5_DIGESTSIZE, calc_md5); + + policy.encode(aclbl); + etag = calc_md5; + + bl.append(etag.c_str(), etag.size() + 1); + attrs[RGW_ATTR_ETAG] = bl; + attrs[RGW_ATTR_ACL] = aclbl; + + if (content_type.size()) { + bufferlist ct_bl; + ct_bl.append(content_type.c_str(), content_type.size() + 1); + attrs[RGW_ATTR_CONTENT_TYPE] = ct_bl; + } + + ret = processor->complete(etag, attrs); + +done: + dispose_processor(processor); +} + + int RGWPutMetadata::verify_permission() { if (!verify_object_permission(s, RGW_PERM_WRITE)) @@ -1706,7 +1907,8 @@ static int get_multiparts_info(RGWRados *store, struct req_state *s, string& met try { ::decode(info, bli); } catch (buffer::error& err) { - ldout(s->cct, 0) << "ERROR: could not decode policy, caught buffer::error" << dendl; + ldout(s->cct, 0) << "ERROR: could not part info, caught buffer::error" << dendl; + return -EIO; } parts[info.num] = info; } @@ -1721,23 +1923,6 @@ int RGWCompleteMultipart::verify_permission() return 0; } -static string string_unquote(const string& s) -{ - if (s[0] != '"' || s.size() < 2) - return s; - - int len; - for (len = s.size(); len > 2; --len) { - if (s[len - 1] != ' ') - break; - } - - if (s[len-1] != '"') - return s; - - return s.substr(1, len - 2); -} - void RGWCompleteMultipart::execute() { RGWMultiCompleteUpload *parts; @@ -1803,7 +1988,7 @@ void RGWCompleteMultipart::execute() ret = -ERR_INVALID_PART; return; } - string part_etag = string_unquote(iter->second); + string part_etag = rgw_string_unquote(iter->second); if (part_etag.compare(obj_iter->second.etag) != 0) { ldout(s->cct, 0) << "NOTICE: etag mismatch: part: " << iter->first << " etag: " << iter->second << dendl; ret = -ERR_INVALID_PART; @@ -1827,17 +2012,23 @@ void RGWCompleteMultipart::execute() target_obj.init(s->bucket, s->object_str); for (obj_iter = obj_parts.begin(); obj_iter != obj_parts.end(); ++obj_iter) { - string oid = mp.get_part(obj_iter->second.num); - rgw_obj src_obj; - src_obj.init_ns(s->bucket, oid, mp_ns); + RGWUploadPartInfo& obj_part = obj_iter->second; + + if (obj_part.manifest.empty()) { + string oid = mp.get_part(obj_iter->second.num); + rgw_obj src_obj; + src_obj.init_ns(s->bucket, oid, mp_ns); - RGWObjManifestPart& part = manifest.objs[ofs]; + RGWObjManifestPart& part = manifest.objs[ofs]; - part.loc = src_obj; - part.loc_ofs = 0; - part.size = obj_iter->second.size; + part.loc = src_obj; + part.loc_ofs = 0; + part.size = obj_iter->second.size; + } else { + manifest.append(obj_part.manifest); + } - ofs += part.size; + ofs += obj_part.size; } manifest.obj_size = ofs; @@ -1887,12 +2078,25 @@ void RGWAbortMultipart::execute() return; for (obj_iter = obj_parts.begin(); obj_iter != obj_parts.end(); ++obj_iter) { - string oid = mp.get_part(obj_iter->second.num); - rgw_obj obj; - obj.init_ns(s->bucket, oid, mp_ns); - ret = store->delete_obj(s->obj_ctx, obj); - if (ret < 0 && ret != -ENOENT) - return; + RGWUploadPartInfo& obj_part = obj_iter->second; + + if (obj_part.manifest.empty()) { + string oid = mp.get_part(obj_iter->second.num); + rgw_obj obj; + obj.init_ns(s->bucket, oid, mp_ns); + ret = store->delete_obj(s->obj_ctx, obj); + if (ret < 0 && ret != -ENOENT) + return; + } else { + RGWObjManifest& manifest = obj_part.manifest; + map<uint64_t, RGWObjManifestPart>::iterator oiter; + for (oiter = manifest.objs.begin(); oiter != manifest.objs.end(); ++oiter) { + RGWObjManifestPart& part = oiter->second; + ret = store->delete_obj(s->obj_ctx, part.loc); + if (ret < 0 && ret != -ENOENT) + return; + } + } } // and also remove the metadata obj meta_obj.init_ns(s->bucket, meta_oid, mp_ns); @@ -2073,7 +2277,7 @@ int RGWHandler::init(RGWRados *_store, struct req_state *_s, RGWClientIO *cio) int RGWHandler::do_read_permissions(RGWOp *op, bool only_bucket) { - int ret = build_policies(store, s, only_bucket, op->prefetch_data()); + int ret = rgw_build_policies(store, s, only_bucket, op->prefetch_data()); if (ret < 0) { ldout(s->cct, 10) << "read_permissions on " << s->bucket << ":" <<s->object_str << " only_bucket=" << only_bucket << " ret=" << ret << dendl; diff --git a/src/rgw/rgw_op.h b/src/rgw/rgw_op.h index 404c3384ab0..52676b92d71 100644 --- a/src/rgw/rgw_op.h +++ b/src/rgw/rgw_op.h @@ -9,7 +9,10 @@ #ifndef CEPH_RGW_OP_H #define CEPH_RGW_OP_H +#include <limits.h> + #include <string> +#include <map> #include "rgw_common.h" #include "rgw_rados.h" @@ -22,6 +25,8 @@ struct req_state; class RGWHandler; void rgw_get_request_metadata(struct req_state *s, map<string, bufferlist>& attrs); +int rgw_build_policies(RGWRados *store, struct req_state *s, bool only_bucket, bool prefetch_data); + /** * Provide the base class for all ops. @@ -249,8 +254,18 @@ class RGWPutObjProcessor protected: RGWRados *store; struct req_state *s; + bool is_complete; + + virtual int do_complete(string& etag, map<string, bufferlist>& attrs) = 0; + + list<rgw_obj> objs; + + void add_obj(rgw_obj& obj) { + objs.push_back(obj); + } public: - virtual ~RGWPutObjProcessor() {} + RGWPutObjProcessor() : store(NULL), s(NULL), is_complete(false) {} + virtual ~RGWPutObjProcessor(); virtual int prepare(RGWRados *_store, struct req_state *_s) { store = _store; s = _s; @@ -258,7 +273,7 @@ public: }; virtual int handle_data(bufferlist& bl, off_t ofs, void **phandle) = 0; virtual int throttle_data(void *handle) = 0; - virtual int complete(string& etag, map<string, bufferlist>& attrs) = 0; + virtual int complete(string& etag, map<string, bufferlist>& attrs); }; class RGWPutObj : public RGWOp { @@ -302,6 +317,55 @@ public: virtual const char *name() { return "put_obj"; } }; +class RGWPostObj : public RGWOp { + + friend class RGWPutObjProcessor; + +protected: + off_t min_len; + off_t max_len; + int ret; + int len; + off_t ofs; + const char *supplied_md5_b64; + const char *supplied_etag; + string etag; + string boundary; + bool data_pending; + string content_type; + RGWAccessControlPolicy policy; + map<string, bufferlist> attrs; + +public: + RGWPostObj() {} + + virtual void init(RGWRados *store, struct req_state *s, RGWHandler *h) { + RGWOp::init(store, s, h); + min_len = 0; + max_len = LLONG_MAX; + ret = 0; + len = 0; + ofs = 0; + supplied_md5_b64 = NULL; + supplied_etag = NULL; + etag = ""; + boundary = ""; + data_pending = false; + policy.set_ctx(s->cct); + } + + int verify_permission(); + void execute(); + + RGWPutObjProcessor *select_processor(); + void dispose_processor(RGWPutObjProcessor *processor); + + virtual int get_params() = 0; + virtual int get_data(bufferlist& bl) = 0; + virtual void send_response() = 0; + virtual const char *name() { return "post_obj"; } +}; + class RGWPutMetadata : public RGWOp { protected: int ret; diff --git a/src/rgw/rgw_policy_s3.cc b/src/rgw/rgw_policy_s3.cc new file mode 100644 index 00000000000..91adbe85233 --- /dev/null +++ b/src/rgw/rgw_policy_s3.cc @@ -0,0 +1,290 @@ + +#include <errno.h> + +#include "rgw_policy_s3.h" +#include "rgw_json.h" +#include "rgw_common.h" + + +#define dout_subsys ceph_subsys_rgw + +class RGWPolicyCondition { +protected: + string v1; + string v2; + + virtual bool check(const string& first, const string& second, string& err_msg) = 0; + +public: + virtual ~RGWPolicyCondition() {} + + void set_vals(const string& _v1, const string& _v2) { + v1 = _v1; + v2 = _v2; + } + + bool check(RGWPolicyEnv *env, map<string, bool, ltstr_nocase>& checked_vars, string& err_msg) { + string first, second; + env->get_value(v1, first, checked_vars); + env->get_value(v2, second, checked_vars); + + dout(1) << "policy condition check " << v1 << " [" << first << "] " << v2 << " [" << second << "]" << dendl; + bool ret = check(first, second, err_msg); + if (!ret) { + err_msg.append(": "); + err_msg.append(v1); + err_msg.append(", "); + err_msg.append(v2); + } + return ret; + } + +}; + + +class RGWPolicyCondition_StrEqual : public RGWPolicyCondition { +protected: + bool check(const string& first, const string& second, string& msg) { + bool ret = first.compare(second) == 0; + if (!ret) { + msg = "Policy condition failed: eq"; + } + return ret; + } +}; + +class RGWPolicyCondition_StrStartsWith : public RGWPolicyCondition { +protected: + bool check(const string& first, const string& second, string& msg) { + bool ret = first.compare(0, second.size(), second) == 0; + if (!ret) { + msg = "Policy condition failed: starts-with"; + } + return ret; + } +}; + +void RGWPolicyEnv::add_var(const string& name, const string& value) +{ + vars[name] = value; +} + +bool RGWPolicyEnv::get_var(const string& name, string& val) +{ + map<string, string, ltstr_nocase>::iterator iter = vars.find(name); + if (iter == vars.end()) + return false; + + val = iter->second; + + return true; +} + +bool RGWPolicyEnv::get_value(const string& s, string& val, map<string, bool, ltstr_nocase>& checked_vars) +{ + if (s.empty() || s[0] != '$') { + val = s; + return true; + } + + const string& var = s.substr(1); + checked_vars[var] = true; + + return get_var(var, val); +} + + +bool RGWPolicyEnv::match_policy_vars(map<string, bool, ltstr_nocase>& policy_vars, string& err_msg) +{ + map<string, string, ltstr_nocase>::iterator iter; + string ignore_prefix = "x-ignore-"; + for (iter = vars.begin(); iter != vars.end(); ++iter) { + const string& var = iter->first; + if (strncasecmp(ignore_prefix.c_str(), var.c_str(), ignore_prefix.size()) == 0) + continue; + if (policy_vars.count(var) == 0) { + err_msg = "Policy missing condition: "; + err_msg.append(iter->first); + dout(1) << "env var missing in policy: " << iter->first << dendl; + return false; + } + } + return true; +} + +RGWPolicy::~RGWPolicy() +{ + list<RGWPolicyCondition *>::iterator citer; + for (citer = conditions.begin(); citer != conditions.end(); ++citer) { + RGWPolicyCondition *cond = *citer; + delete cond; + } +} + +int RGWPolicy::set_expires(const string& e) +{ + struct tm t; + if (!parse_iso8601(e.c_str(), &t)) + return -EINVAL; + + expires = timegm(&t); + + return 0; +} + +int RGWPolicy::add_condition(const string& op, const string& first, const string& second, string& err_msg) +{ + RGWPolicyCondition *cond = NULL; + if (stringcasecmp(op, "eq") == 0) { + cond = new RGWPolicyCondition_StrEqual; + } else if (stringcasecmp(op, "starts-with") == 0) { + cond = new RGWPolicyCondition_StrStartsWith; + } else if (stringcasecmp(op, "content-length-range") == 0) { + off_t min, max; + int r = stringtoll(first, &min); + if (r < 0) { + err_msg = "Bad content-length-range param"; + dout(0) << "bad content-length-range param: " << first << dendl; + return r; + } + + r = stringtoll(second, &max); + if (r < 0) { + err_msg = "Bad content-length-range param"; + dout(0) << "bad content-length-range param: " << second << dendl; + return r; + } + + if (min > min_length) + min_length = min; + + if (max < max_length) + max_length = max; + + return 0; + } + + if (!cond) { + err_msg = "Invalid condition: "; + err_msg.append(op); + dout(0) << "invalid condition: " << op << dendl; + return -EINVAL; + } + + cond->set_vals(first, second); + + conditions.push_back(cond); + + return 0; +} + +int RGWPolicy::check(RGWPolicyEnv *env, string& err_msg) +{ + uint64_t now = ceph_clock_now(NULL).sec(); + if (expires <= now) { + dout(0) << "NOTICE: policy calculated as expired: " << expiration_str << dendl; + err_msg = "Policy expired"; + return -EACCES; // change to condition about expired policy following S3 + } + + list<pair<string, string> >::iterator viter; + for (viter = var_checks.begin(); viter != var_checks.end(); ++viter) { + pair<string, string>& p = *viter; + const string& name = p.first; + const string& check_val = p.second; + string val; + if (!env->get_var(name, val)) { + err_msg = "Policy check failed, variable not found: "; + err_msg.append(name); + return -EACCES; + } + + set_var_checked(name); + + dout(20) << "comparing " << name << " [" << val << "], " << check_val << dendl; + if (val.compare(check_val) != 0) { + err_msg = "Policy check failed, variable not met condition: "; + err_msg.append(name); + dout(1) << "policy check failed, val=" << val << " != " << check_val << dendl; + return -EACCES; + } + } + + list<RGWPolicyCondition *>::iterator citer; + for (citer = conditions.begin(); citer != conditions.end(); ++citer) { + RGWPolicyCondition *cond = *citer; + if (!cond->check(env, checked_vars, err_msg)) { + return -EACCES; + } + } + + if (!env->match_policy_vars(checked_vars, err_msg)) { + dout(1) << "missing policy condition" << dendl; + return -EACCES; + } + return 0; +} + + +int RGWPolicy::from_json(bufferlist& bl, string& err_msg) +{ + RGWJSONParser parser; + + if (!parser.parse(bl.c_str(), bl.length())) { + err_msg = "Malformed JSON"; + dout(0) << "malformed json" << dendl; + return -EINVAL; + } + + // as no time was included in the request, we hope that the user has included a short timeout + JSONObjIter iter = parser.find_first("expiration"); + if (iter.end()) { + err_msg = "Policy missing expiration"; + dout(0) << "expiration not found" << dendl; + return -EINVAL; // change to a "no expiration" error following S3 + } + + JSONObj *obj = *iter; + expiration_str = obj->get_data(); + int r = set_expires(expiration_str); + if (r < 0) { + err_msg = "Failed to parse policy expiration"; + return r; + } + + iter = parser.find_first("conditions"); + if (iter.end()) { + err_msg = "Policy missing conditions"; + dout(0) << "conditions not found" << dendl; + return -EINVAL; // change to a "no conditions" error following S3 + } + + obj = *iter; + + iter = obj->find_first(); + for (; !iter.end(); ++iter) { + JSONObj *child = *iter; + dout(20) << "is_object=" << child->is_object() << dendl; + dout(20) << "is_array=" << child->is_array() << dendl; + if (child->is_array()) { + JSONObjIter aiter = child->find_first(); + vector<string> v; + int i; + for (i = 0; !aiter.end() && i < 3; ++aiter, ++i) { + JSONObj *o = *aiter; + v.push_back(o->get_data()); + } + if (i != 3 || !aiter.end()) { /* we expect exactly 3 arguments here */ + err_msg = "Bad condition array, expecting 3 arguments"; + return -EINVAL; + } + + int r = add_condition(v[0], v[1], v[2], err_msg); + if (r < 0) + return r; + } else { + add_simple_check(child->get_name(), child->get_data()); + } + } + return 0; +} diff --git a/src/rgw/rgw_policy_s3.h b/src/rgw/rgw_policy_s3.h new file mode 100644 index 00000000000..84a2ee71751 --- /dev/null +++ b/src/rgw/rgw_policy_s3.h @@ -0,0 +1,56 @@ +#ifndef CEPH_RGW_POLICY_H +#define CEPH_RGW_POLICY_H + +#include <limits.h> + +#include <map> +#include <list> +#include <string> + +#include "include/utime.h" + +#include "rgw_string.h" + + +class RGWPolicyEnv { + std::map<std::string, std::string, ltstr_nocase> vars; + +public: + void add_var(const string& name, const string& value); + bool get_var(const string& name, string& val); + bool get_value(const string& s, string& val, std::map<std::string, bool, ltstr_nocase>& checked_vars); + bool match_policy_vars(map<string, bool, ltstr_nocase>& policy_vars, string& err_msg); +}; + +class RGWPolicyCondition; + + +class RGWPolicy { + uint64_t expires; + string expiration_str; + std::list<RGWPolicyCondition *> conditions; + std::list<pair<std::string, std::string> > var_checks; + std::map<std::string, bool, ltstr_nocase> checked_vars; + +public: + off_t min_length; + off_t max_length; + + RGWPolicy() : expires(0), min_length(0), max_length(LLONG_MAX) {} + ~RGWPolicy(); + + int set_expires(const string& e); + + void set_var_checked(const std::string& var) { + checked_vars[var] = true; + } + + int add_condition(const std::string& op, const std::string& first, const std::string& second, string& err_msg); + void add_simple_check(const std::string& var, const std::string& value) { + var_checks.push_back(pair<string, string>(var, value)); + } + + int check(RGWPolicyEnv *env, string& err_msg); + int from_json(bufferlist& bl, string& err_msg); +}; +#endif diff --git a/src/rgw/rgw_rados.cc b/src/rgw/rgw_rados.cc index b468b6f042f..6255010785a 100644 --- a/src/rgw/rgw_rados.cc +++ b/src/rgw/rgw_rados.cc @@ -116,6 +116,17 @@ int RGWRadosParams::init(CephContext *cct, RGWRados *store) return 0; } +void RGWObjManifest::append(RGWObjManifest& m) +{ + map<uint64_t, RGWObjManifestPart>::iterator iter; + uint64_t base = obj_size; + for (iter = m.objs.begin(); iter != m.objs.end(); ++iter) { + RGWObjManifestPart& part = iter->second; + objs[base + iter->first] = part; + } + obj_size += m.obj_size; +} + class RGWWatcher : public librados::WatchCtx { RGWRados *rados; public: @@ -2224,14 +2235,7 @@ int RGWRados::prepare_get_obj(void *ctx, rgw_obj& obj, /* Convert all times go GMT to make them compatible */ if (mod_ptr || unmod_ptr) { - struct tm mtm; - struct tm *gmtm = gmtime_r(&astate->mtime, &mtm); - if (!gmtm) { - ldout(cct, 0) << "NOTICE: could not get translate mtime for object" << dendl; - r = -EINVAL; - goto done_err; - } - ctime = mktime(gmtm); + ctime = astate->mtime; if (mod_ptr) { ldout(cct, 10) << "If-Modified-Since: " << *mod_ptr << " Last-Modified: " << ctime << dendl; @@ -2255,19 +2259,22 @@ int RGWRados::prepare_get_obj(void *ctx, rgw_obj& obj, goto done_err; if (if_match) { - ldout(cct, 10) << "ETag: " << etag.c_str() << " " << " If-Match: " << if_match << dendl; - if (strcmp(if_match, etag.c_str())) { + string if_match_str = rgw_string_unquote(if_match); + ldout(cct, 10) << "ETag: " << etag.c_str() << " " << " If-Match: " << if_match_str << dendl; + if (if_match_str.compare(etag.c_str()) != 0) { r = -ERR_PRECONDITION_FAILED; goto done_err; } } if (if_nomatch) { - ldout(cct, 10) << "ETag: " << etag.c_str() << " " << " If-NoMatch: " << if_nomatch << dendl; - if (strcmp(if_nomatch, etag.c_str()) == 0) { + string if_nomatch_str = rgw_string_unquote(if_nomatch); + ldout(cct, 10) << "ETag: " << etag.c_str() << " " << " If-NoMatch: " << if_nomatch_str << dendl; + if (if_nomatch_str.compare(etag.c_str()) == 0) { r = -ERR_NOT_MODIFIED; goto done_err; } + if_nomatch = if_nomatch_str.c_str(); } } diff --git a/src/rgw/rgw_rados.h b/src/rgw/rgw_rados.h index 101ce7db2a2..4e0b52d97b7 100644 --- a/src/rgw/rgw_rados.h +++ b/src/rgw/rgw_rados.h @@ -115,9 +115,46 @@ struct RGWObjManifest { void dump(Formatter *f) const; static void generate_test_instances(list<RGWObjManifest*>& o); + + void append(RGWObjManifest& m); + + bool empty() { return objs.empty(); } }; WRITE_CLASS_ENCODER(RGWObjManifest); +struct RGWUploadPartInfo { + uint32_t num; + uint64_t size; + string etag; + utime_t modified; + RGWObjManifest manifest; + + RGWUploadPartInfo() : num(0), size(0) {} + + void encode(bufferlist& bl) const { + ENCODE_START(3, 2, bl); + ::encode(num, bl); + ::encode(size, bl); + ::encode(etag, bl); + ::encode(modified, bl); + ::encode(manifest, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator& bl) { + DECODE_START_LEGACY_COMPAT_LEN(3, 2, 2, bl); + ::decode(num, bl); + ::decode(size, bl); + ::decode(etag, bl); + ::decode(modified, bl); + if (struct_v >= 3) + ::decode(manifest, bl); + DECODE_FINISH(bl); + } + void dump(Formatter *f) const; + static void generate_test_instances(list<RGWUploadPartInfo*>& o); +}; +WRITE_CLASS_ENCODER(RGWUploadPartInfo) + struct RGWObjState { bool is_atomic; bool has_attrs; diff --git a/src/rgw/rgw_rest.cc b/src/rgw/rgw_rest.cc index e30778c4c20..c2ea5253a54 100644 --- a/src/rgw/rgw_rest.cc +++ b/src/rgw/rgw_rest.cc @@ -163,11 +163,61 @@ void dump_etag(struct req_state *s, const char *etag) } } +void dump_pair(struct req_state *s, const char *key, const char *value) +{ + if ( (strlen(key) > 0) && (strlen(value) > 0)) + s->cio->print("%s: %s\n", key, value); +} + +void dump_bucket_from_state(struct req_state *s) +{ + if (!s->bucket_name_str.empty()) + s->cio->print("Bucket: \"%s\"\n", s->bucket_name_str.c_str()); +} + +void dump_object_from_state(struct req_state *s) +{ + if (!s->object_str.empty()) + s->cio->print("Key: \"%s\"\n", s->object_str.c_str()); +} + +void dump_uri_from_state(struct req_state *s) +{ + if (strcmp(s->request_uri.c_str(), "/") == 0) { + + string location = "http://"; + location += s->env->get("SERVER_NAME"); + if (!location.empty()) { + location += "/"; + if (!s->bucket_name_str.empty()) { + location += s->bucket_name_str; + location += "/"; + if (!s->object_str.empty()) { + location += s->object_str; + s->cio->print("Location: %s\n", location.c_str()); + } + } + } + } + else { + s->cio->print("Location: \"%s\"\n", s->request_uri.c_str()); + } +} + +void dump_redirect(struct req_state *s, const string& redirect) +{ + if (redirect.empty()) + return; + + s->cio->print("Location: %s\n", redirect.c_str()); +} + void dump_last_modified(struct req_state *s, time_t t) { char timestr[TIME_BUF_SIZE]; - struct tm *tmp = gmtime(&t); + struct tm result; + struct tm *tmp = gmtime_r(&t, &result); if (tmp == NULL) return; @@ -322,14 +372,9 @@ int RESTArgs::get_uint64(struct req_state *s, const string& name, uint64_t def_v return 0; } - char *end; - - *val = (uint64_t)strtoull(sval.c_str(), &end, 10); - if (*val == ULLONG_MAX) - return -EINVAL; - - if (*end) - return -EINVAL; + int r = stringtoull(sval, val); + if (r < 0) + return r; return 0; } @@ -347,14 +392,9 @@ int RESTArgs::get_int64(struct req_state *s, const string& name, int64_t def_val return 0; } - char *end; - - *val = (int64_t)strtoll(sval.c_str(), &end, 10); - if (*val == LLONG_MAX) - return -EINVAL; - - if (*end) - return -EINVAL; + int r = stringtoll(sval, val); + if (r < 0) + return r; return 0; } @@ -502,6 +542,22 @@ int RGWPutObj_ObjStore::get_data(bufferlist& bl) return len; } +int RGWPostObj_ObjStore::verify_params() +{ + /* check that we have enough memory to store the object + note that this test isn't exact and may fail unintentionally + for large requests is */ + if (!s->length) { + return -ERR_LENGTH_REQUIRED; + } + off_t len = atoll(s->length); + if (len > (off_t)RGW_MAX_PUT_SIZE) { + return -ERR_TOO_LARGE; + } + + return 0; +} + int RGWPutACLs_ObjStore::get_params() { size_t cl = 0; @@ -918,8 +974,10 @@ int RGWHandler_ObjStore::read_permissions(RGWOp *op_obj) break; } /* is it a 'create bucket' request? */ - if (s->object_str.size() == 0) + if ((s->op == OP_PUT) && s->object_str.size() == 0) return 0; + only_bucket = true; + break; case OP_DELETE: only_bucket = true; break; @@ -981,6 +1039,7 @@ RGWRESTMgr::~RGWRESTMgr() int RGWREST::preprocess(struct req_state *s, RGWClientIO *cio) { s->cio = cio; + s->script_uri = s->env->get("SCRIPT_URI"); s->request_uri = s->env->get("REQUEST_URI"); int pos = s->request_uri.find('?'); if (pos >= 0) { diff --git a/src/rgw/rgw_rest.h b/src/rgw/rgw_rest.h index 69056cb2a25..4ac5a5383e3 100644 --- a/src/rgw/rgw_rest.h +++ b/src/rgw/rgw_rest.h @@ -106,6 +106,15 @@ public: int get_data(bufferlist& bl); }; +class RGWPostObj_ObjStore : public RGWPostObj +{ +public: + RGWPostObj_ObjStore() {} + ~RGWPostObj_ObjStore() {} + + virtual int verify_params(); +}; + class RGWPutMetadata_ObjStore : public RGWPutMetadata { public: @@ -282,5 +291,13 @@ extern void dump_range(struct req_state *s, uint64_t ofs, uint64_t end, uint64_t extern void dump_continue(struct req_state *s); extern void list_all_buckets_end(struct req_state *s); extern void dump_time(struct req_state *s, const char *name, time_t *t); +extern void dump_bucket_from_state(struct req_state *s); +extern void dump_object_from_state(struct req_state *s); +extern void dump_uri_from_state(struct req_state *s); +extern void dump_redirect(struct req_state *s, const string& redirect); +extern void dump_pair(struct req_state *s, const char *key, const char *value); +extern bool is_valid_url(const char *url); + + #endif diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index f3a1675d6fb..4ae3258f67d 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -3,10 +3,12 @@ #include "common/ceph_crypto.h" #include "common/Formatter.h" +#include "common/utf8.h" #include "rgw_rest.h" #include "rgw_rest_s3.h" #include "rgw_acl.h" +#include "rgw_policy_s3.h" #include "common/armor.h" @@ -16,6 +18,34 @@ using namespace ceph::crypto; +void dump_common_s3_headers(struct req_state *s, const char *etag, + size_t content_len, const char *conn_status) +{ + // how many elements do we expect to include in the response + unsigned int expected_var_len = 4; + map<string, string> head_var; + + utime_t date = ceph_clock_now(s->cct); + if (!date.is_zero()) { + char buf[TIME_BUF_SIZE]; + date.sprintf(buf, TIME_BUF_SIZE); + head_var["date"] = buf; + } + + head_var["etag"] = etag; + head_var["conn_stat"] = conn_status; + head_var["server"] = s->env->get("HTTP_HOST"); + + // if we have all the variables we want go ahead and dump + if (head_var.size() == expected_var_len) { + dump_pair(s, "Date", head_var["date"].c_str()); + dump_etag(s, head_var["etag"].c_str()); + dump_content_length(s, content_len); + dump_pair(s, "Connection", head_var["conn_stat"].c_str()); + dump_pair(s, "Server", head_var["server"].c_str()); + } +} + void list_all_buckets_start(struct req_state *s) { s->formatter->open_array_section_in_ns("ListAllMyBucketsResult", @@ -334,6 +364,751 @@ void RGWPutObj_ObjStore_S3::send_response() end_header(s); } +string trim_whitespace(const string& src) +{ + if (src.empty()) { + return string(); + } + + int start = 0; + for (; start != (int)src.size(); start++) { + if (!isspace(src[start])) + break; + } + + int end = src.size() - 1; + if (end <= start) { + return string(); + } + + for (; end > start; end--) { + if (!isspace(src[end])) + break; + } + + return src.substr(start, end - start + 1); +} + +string trim_quotes(const string& val) +{ + string s = trim_whitespace(val); + if (s.size() < 2) + return s; + + int start = 0; + int end = s.size() - 1; + int quotes_count = 0; + + if (s[start] == '"') { + start++; + quotes_count++; + } + if (s[end] == '"') { + end--; + quotes_count++; + } + if (quotes_count == 2) { + return s.substr(start, end - start + 1); + } + return s; +} + +/* + * parses params in the format: 'first; param1=foo; param2=bar' + */ +static void parse_params(const string& params_str, string& first, map<string, string>& params) +{ + int pos = params_str.find(';'); + if (pos < 0) { + first = trim_whitespace(params_str); + return; + } + + first = trim_whitespace(params_str.substr(0, pos)); + + pos++; + + while (pos < (int)params_str.size()) { + ssize_t end = params_str.find(';', pos); + if (end < 0) + end = params_str.size(); + + string param = params_str.substr(pos, end - pos); + + int eqpos = param.find('='); + if (eqpos > 0) { + string param_name = trim_whitespace(param.substr(0, eqpos)); + string val = trim_quotes(param.substr(eqpos + 1)); + params[param_name] = val; + } else { + params[trim_whitespace(param)] = ""; + } + + pos = end + 1; + } +} + +static int parse_part_field(const string& line, string& field_name, struct post_part_field& field) +{ + int pos = line.find(':'); + if (pos < 0) + return -EINVAL; + + field_name = line.substr(0, pos); + if (pos >= (int)line.size() - 1) + return 0; + + parse_params(line.substr(pos + 1), field.val, field.params); + + return 0; +} + +bool is_crlf(const char *s) +{ + return (*s == '\r' && *(s + 1) == '\n'); +} + +/* + * find the index of the boundary, if exists, or optionally the next end of line + * also returns how many bytes to skip + */ +static int index_of(bufferlist& bl, int max_len, const string& str, bool check_crlf, + bool *reached_boundary, int *skip) +{ + *reached_boundary = false; + *skip = 0; + + if (str.size() < 2) // we assume boundary is at least 2 chars (makes it easier with crlf checks) + return -EINVAL; + + if (bl.length() < str.size()) + return -1; + + const char *buf = bl.c_str(); + const char *s = str.c_str(); + + if (max_len > (int)bl.length()) + max_len = bl.length(); + + int i; + for (i = 0; i < max_len; i++, buf++) { + if (check_crlf && + i >= 1 && + is_crlf(buf - 1)) { + return i + 1; // skip the crlf + } + if ((i < max_len - (int)str.size() + 1) && + (buf[0] == s[0] && buf[1] == s[1]) && + (strncmp(buf, s, str.size()) == 0)) { + *reached_boundary = true; + *skip = str.size(); + + /* oh, great, now we need to swallow the preceding crlf + * if exists + */ + if ((i >= 2) && + is_crlf(buf - 2)) { + i -= 2; + *skip += 2; + } + return i; + } + } + + return -1; +} + +int RGWPostObj_ObjStore_S3::read_with_boundary(bufferlist& bl, uint64_t max, bool check_crlf, + bool *reached_boundary, bool *done) +{ + uint64_t cl = max + 2 + boundary.size(); + + if (max > in_data.length()) { + uint64_t need_to_read = cl - in_data.length(); + + bufferptr bp(need_to_read); + + int read_len; + s->cio->read(bp.c_str(), need_to_read, &read_len); + + in_data.append(bp, 0, read_len); + } + + *done = false; + int skip; + int index = index_of(in_data, cl, boundary, check_crlf, reached_boundary, &skip); + if (index >= 0) + max = index; + + if (max > in_data.length()) + max = in_data.length(); + + bl.substr_of(in_data, 0, max); + + bufferlist new_read_data; + + /* + * now we need to skip boundary for next time, also skip any crlf, or + * check to see if it's the last final boundary (marked with "--" at the end + */ + if (*reached_boundary) { + int left = in_data.length() - max; + if (left < skip + 2) { + int need = skip + 2 - left; + bufferptr boundary_bp(need); + int actual; + s->cio->read(boundary_bp.c_str(), need, &actual); + in_data.append(boundary_bp); + } + max += skip; // skip boundary for next time + if (in_data.length() >= max + 2) { + const char *data = in_data.c_str(); + if (is_crlf(data + max)) { + max += 2; + } else { + if (*(data + max) == '-' && + *(data + max + 1) == '-') { + *done = true; + max += 2; + } + } + } + } + + new_read_data.substr_of(in_data, max, in_data.length() - max); + in_data = new_read_data; + + return 0; +} + +int RGWPostObj_ObjStore_S3::read_line(bufferlist& bl, uint64_t max, + bool *reached_boundary, bool *done) +{ + return read_with_boundary(bl, max, true, reached_boundary, done); +} + +int RGWPostObj_ObjStore_S3::read_data(bufferlist& bl, uint64_t max, + bool *reached_boundary, bool *done) +{ + return read_with_boundary(bl, max, false, reached_boundary, done); +} + + +int RGWPostObj_ObjStore_S3::read_form_part_header(struct post_form_part *part, + bool *done) +{ + bufferlist bl; + bool reached_boundary; + int r = read_line(bl, RGW_MAX_CHUNK_SIZE, &reached_boundary, done); + if (r < 0) + return r; + + if (*done) { + return 0; + } + + if (reached_boundary) { // skip the first boundary + r = read_line(bl, RGW_MAX_CHUNK_SIZE, &reached_boundary, done); + if (r < 0) + return r; + if (*done) + return 0; + } + + while (true) { + /* + * iterate through fields + */ + string line = trim_whitespace(string(bl.c_str(), bl.length())); + + if (line.empty()) + break; + + struct post_part_field field; + + string field_name; + r = parse_part_field(line, field_name, field); + if (r < 0) + return r; + + part->fields[field_name] = field; + + if (stringcasecmp(field_name, "Content-Disposition") == 0) { + part->name = field.params["name"]; + } + + if (reached_boundary) + break; + + r = read_line(bl, RGW_MAX_CHUNK_SIZE, &reached_boundary, done); + } + + return 0; +} + +bool RGWPostObj_ObjStore_S3::part_str(const string& name, string *val) +{ + map<string, struct post_form_part, ltstr_nocase>::iterator iter = parts.find(name); + if (iter == parts.end()) + return false; + + bufferlist& data = iter->second.data; + string str = string(data.c_str(), data.length()); + *val = trim_whitespace(str); + return true; +} + +bool RGWPostObj_ObjStore_S3::part_bl(const string& name, bufferlist *pbl) +{ + map<string, struct post_form_part, ltstr_nocase>::iterator iter = parts.find(name); + if (iter == parts.end()) + return false; + + *pbl = iter->second.data; + return true; +} + +void RGWPostObj_ObjStore_S3::rebuild_key(string& key) +{ + static string var = "${filename}"; + int pos = key.find(var); + if (pos < 0) + return; + + string new_key = key.substr(0, pos); + new_key.append(filename); + new_key.append(key.substr(pos + var.size())); + + key = new_key; +} + +int RGWPostObj_ObjStore_S3::get_params() +{ + string temp_line; + string param; + string old_param; + string param_value; + + string whitespaces (" \t\f\v\n\r"); + + // get the part boundary + string req_content_type_str = s->env->get("CONTENT_TYPE"); + string req_content_type; + map<string, string> params; + + if (s->expect_cont) { + /* ok, here it really gets ugly. With POST, the params are embedded in the + * request body, so we need to continue before being able to actually look + * at them. This diverts from the usual request flow. + */ + dump_continue(s); + s->expect_cont = false; + } + + parse_params(req_content_type_str, req_content_type, params); + + if (req_content_type.compare("multipart/form-data") != 0) { + err_msg = "Request Content-Type is not multipart/form-data"; + return -EINVAL; + } + + if (s->cct->_conf->subsys.should_gather(ceph_subsys_rgw, 20)) { + ldout(s->cct, 20) << "request content_type_str=" << req_content_type_str << dendl; + ldout(s->cct, 20) << "request content_type params:" << dendl; + map<string, string>::iterator iter; + for (iter = params.begin(); iter != params.end(); ++iter) { + ldout(s->cct, 20) << " " << iter->first << " -> " << iter->second << dendl; + } + } + + ldout(s->cct, 20) << "adding bucket to policy env: " << s->bucket.name << dendl; + env.add_var("bucket", s->bucket.name); + + map<string, string>::iterator iter = params.find("boundary"); + if (iter == params.end()) { + err_msg = "Missing multipart boundary specification"; + return -EINVAL; + } + + // create the boundary + boundary = "--"; + boundary.append(iter->second); + + bool done; + do { + struct post_form_part part; + int r = read_form_part_header(&part, &done); + if (r < 0) + return r; + + if (s->cct->_conf->subsys.should_gather(ceph_subsys_rgw, 20)) { + map<string, struct post_part_field, ltstr_nocase>::iterator piter; + for (piter = part.fields.begin(); piter != part.fields.end(); ++piter) { + ldout(s->cct, 20) << "read part header: name=" << part.name << " content_type=" << part.content_type << dendl; + ldout(s->cct, 20) << "name=" << piter->first << dendl; + ldout(s->cct, 20) << "val=" << piter->second.val << dendl; + ldout(s->cct, 20) << "params:" << dendl; + map<string, string>& params = piter->second.params; + for (iter = params.begin(); iter != params.end(); ++iter) { + ldout(s->cct, 20) << " " << iter->first << " -> " << iter->second << dendl; + } + } + } + + if (done) { /* unexpected here */ + err_msg = "Malformed request"; + return -EINVAL; + } + + if (stringcasecmp(part.name, "file") == 0) { /* beginning of data transfer */ + struct post_part_field& field = part.fields["Content-Disposition"]; + map<string, string>::iterator iter = field.params.find("filename"); + if (iter != field.params.end()) { + filename = iter->second; + } + parts[part.name] = part; + data_pending = true; + break; + } + + bool boundary; + r = read_data(part.data, RGW_MAX_CHUNK_SIZE, &boundary, &done); + if (!boundary) { + err_msg = "Couldn't find boundary"; + return -EINVAL; + } + parts[part.name] = part; + string part_str(part.data.c_str(), part.data.length()); + env.add_var(part.name, part_str); + } while (!done); + + if (!part_str("key", &s->object_str)) { + err_msg = "Key not specified"; + return -EINVAL; + } + + rebuild_key(s->object_str); + + env.add_var("key", s->object_str); + + part_str("Content-Type", &content_type); + env.add_var("Content-Type", content_type); + + map<string, struct post_form_part, ltstr_nocase>::iterator piter = parts.upper_bound(RGW_AMZ_META_PREFIX); + for (; piter != parts.end(); ++piter) { + string n = piter->first; + if (strncasecmp(n.c_str(), RGW_AMZ_META_PREFIX, sizeof(RGW_AMZ_META_PREFIX) - 1) != 0) + break; + + string attr_name = RGW_ATTR_PREFIX; + attr_name.append(n); + + /* need to null terminate it */ + bufferlist& data = piter->second.data; + string str = string(data.c_str(), data.length()); + + bufferlist attr_bl; + attr_bl.append(str.c_str(), str.size() + 1); + + attrs[attr_name] = attr_bl; + } + + int r = get_policy(); + if (r < 0) + return r; + + min_len = post_policy.min_length; + max_len = post_policy.max_length; + + return 0; +} + +int RGWPostObj_ObjStore_S3::get_policy() +{ + bufferlist encoded_policy; + string uid; + + if (part_bl("policy", &encoded_policy)) { + + // check that the signature matches the encoded policy + string s3_access_key; + if (!part_str("AWSAccessKeyId", &s3_access_key)) { + ldout(s->cct, 0) << "No S3 access key found!" << dendl; + err_msg = "Missing access key"; + return -EINVAL; + } + string signature_str; + if (!part_str("signature", &signature_str)) { + ldout(s->cct, 0) << "No signature found!" << dendl; + err_msg = "Missing signature"; + return -EINVAL; + } + + RGWUserInfo user_info; + + ret = rgw_get_user_info_by_access_key(store, s3_access_key, user_info); + if (ret < 0) { + ldout(s->cct, 0) << "User lookup failed!" << dendl; + err_msg = "Bad access key / signature"; + return -EACCES; + } + + map<string, RGWAccessKey> access_keys = user_info.access_keys; + + map<string, RGWAccessKey>::const_iterator iter = access_keys.begin(); + string s3_secret_key = (iter->second).key; + + char calc_signature[CEPH_CRYPTO_HMACSHA1_DIGESTSIZE]; + + calc_hmac_sha1(s3_secret_key.c_str(), s3_secret_key.size(), encoded_policy.c_str(), encoded_policy.length(), calc_signature); + bufferlist encoded_hmac; + bufferlist raw_hmac; + raw_hmac.append(calc_signature, CEPH_CRYPTO_HMACSHA1_DIGESTSIZE); + raw_hmac.encode_base64(encoded_hmac); + encoded_hmac.append((char)0); /* null terminate */ + + if (signature_str.compare(encoded_hmac.c_str()) != 0) { + ldout(s->cct, 0) << "Signature verification failed!" << dendl; + ldout(s->cct, 0) << "expected: " << signature_str.c_str() << dendl; + ldout(s->cct, 0) << "got: " << encoded_hmac.c_str() << dendl; + err_msg = "Bad access key / signature"; + return -EACCES; + } + ldout(s->cct, 0) << "Successful Signature Verification!" << dendl; + bufferlist decoded_policy; + try { + decoded_policy.decode_base64(encoded_policy); + } catch (buffer::error& err) { + ldout(s->cct, 0) << "failed to decode_base64 policy" << dendl; + err_msg = "Could not decode policy"; + return -EINVAL; + } + + decoded_policy.append('\0'); // NULL terminate + + ldout(s->cct, 0) << "POST policy: " << decoded_policy.c_str() << dendl; + + int r = post_policy.from_json(decoded_policy, err_msg); + if (r < 0) { + if (err_msg.empty()) { + err_msg = "Failed to parse policy"; + } + ldout(s->cct, 0) << "failed to parse policy" << dendl; + return -EINVAL; + } + + post_policy.set_var_checked("AWSAccessKeyId"); + post_policy.set_var_checked("policy"); + post_policy.set_var_checked("signature"); + + r = post_policy.check(&env, err_msg); + if (r < 0) { + if (err_msg.empty()) { + err_msg = "Policy check failed"; + } + ldout(s->cct, 0) << "policy check failed" << dendl; + return r; + } + + s->user = user_info; + } else { + ldout(s->cct, 0) << "No attached policy found!" << dendl; + } + + string canned_acl; + part_str("acl", &canned_acl); + + RGWAccessControlPolicy_S3 s3policy(s->cct); + ldout(s->cct, 20) << "canned_acl=" << canned_acl << dendl; + if (!s3policy.create_canned(s->user.user_id, "", canned_acl)) { + err_msg = "Bad canned ACLs"; + return -EINVAL; + } + + policy = s3policy; + + return 0; +} + +int RGWPostObj_ObjStore_S3::complete_get_params() +{ + bool done; + do { + struct post_form_part part; + int r = read_form_part_header(&part, &done); + if (r < 0) + return r; + + bufferlist part_data; + bool boundary; + r = read_data(part.data, RGW_MAX_CHUNK_SIZE, &boundary, &done); + if (!boundary) { + return -EINVAL; + } + + parts[part.name] = part; + } while (!done); + + return 0; +} + +int RGWPostObj_ObjStore_S3::get_data(bufferlist& bl) +{ + bool boundary; + bool done; + + int r = read_data(bl, RGW_MAX_CHUNK_SIZE, &boundary, &done); + if (r < 0) + return r; + + if (boundary) { + data_pending = false; + + if (!done) { /* reached end of data, let's drain the rest of the params */ + r = complete_get_params(); + if (r < 0) + return r; + } + } + + return bl.length(); +} + +static void escape_char(char c, string& dst) +{ + char buf[16]; + snprintf(buf, sizeof(buf), "%%%.2X", (unsigned int)c); + dst.append(buf); +} + +static bool char_needs_url_encoding(char c) +{ + if (c < 0x20 || c >= 0x7f) + return true; + + switch (c) { + case 0x20: + case 0x22: + case 0x23: + case 0x25: + case 0x26: + case 0x2B: + case 0x2C: + case 0x2F: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3E: + case 0x3D: + case 0x3F: + case 0x40: + case 0x5B: + case 0x5D: + case 0x5C: + case 0x5E: + case 0x60: + case 0x7B: + case 0x7D: + return true; + } + return false; +} + +static void url_escape(const string& src, string& dst) +{ + const char *p = src.c_str(); + for (unsigned i = 0; i < src.size(); i++, p++) { + if (char_needs_url_encoding(*p)) { + escape_char(*p, dst); + continue; + } + + dst.append(p, 1); + } +} + +void RGWPostObj_ObjStore_S3::send_response() +{ + if (ret == 0 && parts.count("success_action_redirect")) { + string redirect; + + part_str("success_action_redirect", &redirect); + + string bucket; + string key; + string etag_str = "\""; + + etag_str.append(etag); + etag_str.append("\""); + + string etag_url; + + url_escape(s->bucket_name, bucket); + url_escape(s->object_str, key); + url_escape(etag_str, etag_url); + + + redirect.append("?bucket="); + redirect.append(bucket); + redirect.append("&key="); + redirect.append(key); + redirect.append("&etag="); + redirect.append(etag_url); + + int r = check_utf8(redirect.c_str(), redirect.size()); + if (r < 0) { + ret = r; + goto done; + } + dump_redirect(s, redirect); + ret = STATUS_REDIRECT; + } else if (ret == 0 && parts.count("success_action_status")) { + string status_string; + uint32_t status_int; + + part_str("success_action_status", &status_string); + + int r = stringtoul(status_string, &status_int); + if (r < 0) { + ret = r; + goto done; + } + + switch (status_int) { + case 200: + break; + case 201: + ret = STATUS_CREATED; + break; + default: + ret = STATUS_NO_CONTENT; + break; + } + } else if (!ret) { + ret = STATUS_NO_CONTENT; + } + +done: + if (ret == STATUS_CREATED) { + s->formatter->open_object_section("PostResponse"); + if (g_conf->rgw_dns_name.length()) + s->formatter->dump_format("Location", "%s/%s", s->script_uri.c_str(), s->object_str.c_str()); + s->formatter->dump_string("Bucket", s->bucket_name); + s->formatter->dump_string("Key", s->object_str.c_str()); + s->formatter->close_section(); + } + s->err.message = err_msg; + set_req_state_err(s, ret); + dump_errno(s); + dump_content_length(s, s->formatter->get_len()); + end_header(s); + if (ret != STATUS_CREATED) + return; + + rgw_flush_formatter_and_reset(s, s->formatter); +} + + void RGWDeleteObj_ObjStore_S3::send_response() { int r = ret; @@ -745,7 +1520,7 @@ RGWOp *RGWHandler_ObjStore_Bucket_S3::op_post() return new RGWDeleteMultiObj_ObjStore_S3; } - return NULL; + return new RGWPostObj_ObjStore_S3; } RGWOp *RGWHandler_ObjStore_Obj_S3::get_obj_op(bool get_data) diff --git a/src/rgw/rgw_rest_s3.h b/src/rgw/rgw_rest_s3.h index 386f159b473..daa8037f065 100644 --- a/src/rgw/rgw_rest_s3.h +++ b/src/rgw/rgw_rest_s3.h @@ -5,6 +5,7 @@ #include "rgw_op.h" #include "rgw_html_errors.h" #include "rgw_acl_s3.h" +#include "rgw_policy_s3.h" #define RGW_AUTH_GRACE_MINS 15 @@ -81,6 +82,53 @@ public: void send_response(); }; +struct post_part_field { + string val; + map<string, string> params; +}; + +struct post_form_part { + string name; + string content_type; + map<string, struct post_part_field, ltstr_nocase> fields; + bufferlist data; +}; + +class RGWPostObj_ObjStore_S3 : public RGWPostObj_ObjStore { + string boundary; + string filename; + bufferlist in_data; + map<string, post_form_part, const ltstr_nocase> parts; + RGWPolicyEnv env; + RGWPolicy post_policy; + string err_msg; + + int read_with_boundary(bufferlist& bl, uint64_t max, bool check_eol, + bool *reached_boundary, + bool *done); + + int read_line(bufferlist& bl, uint64_t max, + bool *reached_boundary, bool *done); + + int read_data(bufferlist& bl, uint64_t max, bool *reached_boundary, bool *done); + + int read_form_part_header(struct post_form_part *part, + bool *done); + bool part_str(const string& name, string *val); + bool part_bl(const string& name, bufferlist *pbl); + + int get_policy(); + void rebuild_key(string& key); +public: + RGWPostObj_ObjStore_S3() {} + ~RGWPostObj_ObjStore_S3() {} + + int get_params(); + int complete_get_params(); + void send_response(); + int get_data(bufferlist& bl); +}; + class RGWDeleteObj_ObjStore_S3 : public RGWDeleteObj_ObjStore { public: RGWDeleteObj_ObjStore_S3() {} diff --git a/src/rgw/rgw_rest_swift.cc b/src/rgw/rgw_rest_swift.cc index 13c324a1bd6..ee55aed3254 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_string.h b/src/rgw/rgw_string.h new file mode 100644 index 00000000000..3c881a10a91 --- /dev/null +++ b/src/rgw/rgw_string.h @@ -0,0 +1,94 @@ +#ifndef CEPH_RGW_STRING_H +#define CEPH_RGW_STRING_H + +#include <stdlib.h> +#include <limits.h> + +struct ltstr_nocase +{ + bool operator()(const string& s1, const string& s2) const + { + return strcasecmp(s1.c_str(), s2.c_str()) < 0; + } +}; + +static inline int stringcasecmp(const string& s1, const string& s2) +{ + return strcasecmp(s1.c_str(), s2.c_str()); +} + +static inline int stringcasecmp(const string& s1, const char *s2) +{ + return strcasecmp(s1.c_str(), s2); +} + +static inline int stringcasecmp(const string& s1, int ofs, int size, const string& s2) +{ + return strncasecmp(s1.c_str() + ofs, s2.c_str(), size); +} + +static inline int stringtoll(const string& s, int64_t *val) +{ + char *end; + + long long result = strtoll(s.c_str(), &end, 10); + if (result == LLONG_MAX) + return -EINVAL; + + if (*end) + return -EINVAL; + + *val = (int64_t)result; + + return 0; +} + +static inline int stringtoull(const string& s, uint64_t *val) +{ + char *end; + + unsigned long long result = strtoull(s.c_str(), &end, 10); + if (result == ULLONG_MAX) + return -EINVAL; + + if (*end) + return -EINVAL; + + *val = (uint64_t)result; + + return 0; +} + +static inline int stringtol(const string& s, int32_t *val) +{ + char *end; + + long result = strtol(s.c_str(), &end, 10); + if (result == LONG_MAX) + return -EINVAL; + + if (*end) + return -EINVAL; + + *val = (int32_t)result; + + return 0; +} + +static inline int stringtoul(const string& s, uint32_t *val) +{ + char *end; + + unsigned long result = strtoul(s.c_str(), &end, 10); + if (result == ULONG_MAX) + return -EINVAL; + + if (*end) + return -EINVAL; + + *val = (uint32_t)result; + + return 0; +} + +#endif 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/rgw/rgw_swift_auth.cc b/src/rgw/rgw_swift_auth.cc index 47390fb48c1..b0be5d45938 100644 --- a/src/rgw/rgw_swift_auth.cc +++ b/src/rgw/rgw_swift_auth.cc @@ -49,7 +49,7 @@ static int encode_token(CephContext *cct, string& swift_user, string& key, buffe return ret; utime_t expiration = ceph_clock_now(cct); - expiration += RGW_SWIFT_TOKEN_EXPIRATION; // 15 minutes + expiration += cct->_conf->rgw_swift_token_expiration; ret = build_token(swift_user, key, nonce, expiration, bl); 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); } }; |