summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSage Weil <sage@inktank.com>2012-11-13 14:50:42 -0800
committerSage Weil <sage@inktank.com>2012-11-13 14:50:42 -0800
commit6a8a59c5d00495be03db708953b7a8b5c55b8233 (patch)
treeb33d9740fa5df742b99c6e332961a0761dd48b8a
parent5d27f3da654b2d1b3f90a5758fc47b89f012a8d2 (diff)
parent1ba969b18000aa0150410860b8305db2d5679e7e (diff)
downloadceph-6a8a59c5d00495be03db708953b7a8b5c55b8233.tar.gz
Merge remote-tracking branch 'gh/wip-rgw-integration'
Conflicts: src/common/config_opts.h
-rw-r--r--debian/control2
-rw-r--r--doc/radosgw/config.rst43
-rw-r--r--src/.gitignore1
-rw-r--r--src/Makefile.am35
-rw-r--r--src/common/ceph_crypto.cc12
-rw-r--r--src/common/ceph_crypto.h4
-rw-r--r--src/common/ceph_crypto_cms.cc360
-rw-r--r--src/common/ceph_crypto_cms.h10
-rw-r--r--src/common/common_init.cc2
-rw-r--r--src/common/config_opts.h17
-rw-r--r--src/json_spirit/json_spirit_reader_template.h6
-rw-r--r--src/rgw/rgw_common.cc81
-rw-r--r--src/rgw/rgw_common.h47
-rw-r--r--src/rgw/rgw_html_errors.h2
-rw-r--r--src/rgw/rgw_http_client.cc76
-rw-r--r--src/rgw/rgw_http_client.h23
-rw-r--r--src/rgw/rgw_json.cc271
-rw-r--r--src/rgw/rgw_json.h94
-rw-r--r--src/rgw/rgw_jsonparser.cc80
-rw-r--r--src/rgw/rgw_main.cc10
-rw-r--r--src/rgw/rgw_op.cc348
-rw-r--r--src/rgw/rgw_op.h68
-rw-r--r--src/rgw/rgw_policy_s3.cc290
-rw-r--r--src/rgw/rgw_policy_s3.h56
-rw-r--r--src/rgw/rgw_rados.cc31
-rw-r--r--src/rgw/rgw_rados.h37
-rw-r--r--src/rgw/rgw_rest.cc95
-rw-r--r--src/rgw/rgw_rest.h17
-rw-r--r--src/rgw/rgw_rest_s3.cc777
-rw-r--r--src/rgw/rgw_rest_s3.h48
-rw-r--r--src/rgw/rgw_rest_swift.cc2
-rw-r--r--src/rgw/rgw_string.h94
-rw-r--r--src/rgw/rgw_swift.cc685
-rw-r--r--src/rgw/rgw_swift.h73
-rw-r--r--src/rgw/rgw_swift_auth.cc2
-rw-r--r--src/test/ceph_crypto.cc6
-rw-r--r--src/test/crypto.cc2
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);
}
};