map<int, string> temp_url_keys;
RGWQuotaInfo user_quota;
uint32_t type;
+ map<string, string> mfa_devices;
RGWUserInfo()
: auid(0),
}
void encode(bufferlist& bl) const {
- ENCODE_START(19, 9, bl);
+ ENCODE_START(20, 9, bl);
encode(auid, bl);
string access_key;
string secret_key;
encode(user_id.tenant, bl);
encode(admin, bl);
encode(type, bl);
+ encode(mfa_devices, bl);
ENCODE_FINISH(bl);
}
void decode(bufferlist::iterator& bl) {
- DECODE_START_LEGACY_COMPAT_LEN_32(19, 9, 9, bl);
+ DECODE_START_LEGACY_COMPAT_LEN_32(20, 9, 9, bl);
if (struct_v >= 2) decode(auid, bl);
else auid = CEPH_AUTH_UID_DEFAULT;
string access_key;
if (struct_v >= 19) {
decode(type, bl);
}
+ if (struct_v >= 20) {
+ ::decode(mfa_devices, bl);
+ }
DECODE_FINISH(bl);
}
void dump(Formatter *f) const;
BUCKET_VERSIONED = 0x2,
BUCKET_VERSIONS_SUSPENDED = 0x4,
BUCKET_DATASYNC_DISABLED = 0X8,
+ BUCKET_MFA_ENABLED = 0X10,
};
enum RGWBucketIndexType {
void decode_json(JSONObj *obj);
bool versioned() const { return (flags & BUCKET_VERSIONED) != 0; }
- int versioning_status() { return flags & (BUCKET_VERSIONED | BUCKET_VERSIONS_SUSPENDED); }
+ int versioning_status() { return flags & (BUCKET_VERSIONED | BUCKET_VERSIONS_SUSPENDED | BUCKET_MFA_ENABLED); }
bool versioning_enabled() { return versioning_status() == BUCKET_VERSIONED; }
+ bool mfa_enabled() { return versioning_status() == BUCKET_MFA_ENABLED; }
bool datasync_flag_enabled() const { return (flags & BUCKET_DATASYNC_DISABLED) == 0; }
bool has_swift_versioning() const {
string req_id;
string trans_id;
+ bool mfa_verified;
+
req_state(CephContext* _cct, RGWEnv* e, RGWUserInfo* u);
~req_state();
#include <boost/algorithm/string/replace.hpp>
#include <boost/utility/string_view.hpp>
+#include <liboath/oath.h>
+
#include "rgw_rest.h"
#include "rgw_rest_s3.h"
#include "rgw_rest_s3website.h"
rgw_flush_formatter_and_reset(s, s->formatter);
}
-class RGWSetBucketVersioningParser : public RGWXMLParser
-{
- XMLObj *alloc_obj(const char *el) override {
- return new XMLObj;
- }
-
-public:
- RGWSetBucketVersioningParser() {}
- ~RGWSetBucketVersioningParser() override {}
-
- int get_versioning_status(int *status) {
- XMLObj *config = find_first("VersioningConfiguration");
- if (!config)
- return -EINVAL;
-
- *status = VersioningNotChanged;
-
- XMLObj *field = config->find_first("Status");
- if (!field)
- return 0;
+struct ver_config_status {
+ int status{VersioningSuspended};
+ int mfa_status{0};
+
+ void decode_xml(XMLObj *obj) {
+dout(0) << __FILE__ << ":" << __LINE__ << dendl;
+ string status_str;
+ string mfa_str;
+ RGWXMLDecoder::decode_xml("Status", status_str, obj);
+dout(0) << __FILE__ << ":" << __LINE__ << dendl;
+ if (status_str == "Enabled") {
+ status = VersioningEnabled;
+ } else if (status_str != "Suspended") {
+ status = VersioningStatusInvalid;
+ }
- *status = VersioningSuspended;
- string& s = field->get_data();
+dout(0) << __FILE__ << ":" << __LINE__ << " status_str=" << status_str << dendl;
- if (stringcasecmp(s, "Enabled") == 0) {
- *status = VersioningEnabled;
- } else if (stringcasecmp(s, "Suspended") != 0) {
- return -EINVAL;
+ RGWXMLDecoder::decode_xml("MfaDelete", mfa_str, obj);
+dout(0) << __FILE__ << ":" << __LINE__ << " mfa_str=" << mfa_str << dendl;
+ if (mfa_str == "Enabled") {
+ mfa_status = 1;
+ } else if (mfa_str == "Disabled") {
+ mfa_status = 0;
+ } else {
+ mfa_status = -EINVAL;
}
-
- return 0;
}
};
return r;
}
- RGWSetBucketVersioningParser parser;
-
+ RGWXMLDecoder::XMLParser parser;
if (!parser.init()) {
ldout(s->cct, 0) << "ERROR: failed to initialize parser" << dendl;
- r = -EIO;
- return r;
+ return -EIO;
}
if (!parser.parse(data, len, 1)) {
return r;
}
+ ver_config_status status_conf;
+
+ RGWXMLDecoder::decode_xml("VersioningConfiguration", status_conf, &parser);
+
if (!store->is_meta_master()) {
/* only need to keep this data around if we're not meta master */
in_data.append(data, len);
}
- r = parser.get_versioning_status(&versioning_status);
-
+ versioning_status = status_conf.status;
+ if (versioning_status == VersioningStatusInvalid) {
+ r = -EINVAL;
+ }
+
+ if (status_conf.mfa_status >= 0) {
+ mfa_status = (bool)status_conf.mfa_status;
+ } else {
+ r = -EINVAL;
+ }
return r;
}
return 0;
}
+static int verify_mfa(RGWRados *store, RGWUserInfo *user, const string& mfa_str, bool *verified)
+{
+ vector<string> params;
+ get_str_vec(mfa_str, " ", params);
+
+ if (params.size() != 2) {
+ ldout(store->ctx(), 5) << "NOTICE: invalid mfa string provided: " << mfa_str << dendl;
+ return -EINVAL;
+ }
+
+ string& serial = params[0];
+ string& otp = params[1];
+
+ auto i = user->mfa_devices.find(serial);
+ if (i == user->mfa_devices.end()) {
+ ldout(store->ctx(), 5) << "NOTICE: user does not have mfa device with serial=" << serial << dendl;
+ return -EACCES;
+ }
+
+ string& seed = i->second;
+
+ utime_t now = ceph_clock_now();
+
+ int result = oath_totp_validate3(seed.c_str(), seed.size(), now.sec(),
+ 30 /* step size */, 0, 2 /* window */,
+ NULL, NULL, otp.c_str());
+ if (result == OATH_INVALID_OTP) {
+ ldout(cct, 5) << "NOTICE: totp token failed to validate" << dendl;
+ return -EACCES;
+ }
+
+ *verified = true;
+
+ return 0;
+}
+
int RGWHandler_REST_S3::postauth_init()
{
struct req_init_state *t = &s->init_state;
if (ret)
return ret;
}
+
+ const char *mfa = s->info.env->get("HTTP_X_AMZ_MFA");
+ if (mfa) {
+ ret = verify_mfa(store, s->user, string(mfa), &s->mfa_verified);
+ }
+
return 0;
}