]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: add support for HTTP Referer-based ACLs of Swift API.
authorRadoslaw Zarzynski <rzarzynski@mirantis.com>
Thu, 28 Jan 2016 17:43:56 +0000 (18:43 +0100)
committerRadoslaw Zarzynski <rzarzynski@mirantis.com>
Thu, 2 Jun 2016 13:37:05 +0000 (15:37 +0200)
Signed-off-by: Radoslaw Zarzynski <rzarzynski@mirantis.com>
src/rgw/rgw_acl.cc
src/rgw/rgw_acl.h
src/rgw/rgw_acl_swift.cc
src/rgw/rgw_common.cc
src/rgw/rgw_json_enc.cc
src/rgw/rgw_rest_swift.cc

index 51f18e96d977989107efd09081f060cf4986d530..bbd19a71781fbab87957cf72b32d3ef3fe8d7e90 100644 (file)
@@ -22,6 +22,9 @@ void RGWAccessControlList::_add_grant(ACLGrant *grant)
   ACLPermission& perm = grant->get_permission();
   ACLGranteeType& type = grant->get_type();
   switch (type.get_type()) {
+  case ACL_TYPE_REFERER:
+    referer_list.emplace_back(grant->get_referer(), perm.get_permissions());
+    break;
   case ACL_TYPE_GROUP:
     acl_group_map[grant->get_group()] |= perm.get_permissions();
     break;
@@ -53,8 +56,11 @@ int RGWAccessControlList::get_perm(const RGWIdentityApplier& auth_identity,
   return perm_mask & auth_identity.get_perms_from_aclspec(acl_user_map);
 }
 
-int RGWAccessControlList::get_group_perm(ACLGroupTypeEnum group, int perm_mask) {
-  ldout(cct, 5) << "Searching permissions for group=" << (int)group << " mask=" << perm_mask << dendl;
+int RGWAccessControlList::get_group_perm(ACLGroupTypeEnum group, int perm_mask)
+{
+  ldout(cct, 5) << "Searching permissions for group=" << (int)group
+                << " mask=" << perm_mask << dendl;
+
   map<uint32_t, int>::iterator iter = acl_group_map.find((uint32_t)group);
   if (iter != acl_group_map.end()) {
     ldout(cct, 5) << "Found permission: " << iter->second << dendl;
@@ -64,8 +70,32 @@ int RGWAccessControlList::get_group_perm(ACLGroupTypeEnum group, int perm_mask)
   return 0;
 }
 
+int RGWAccessControlList::get_referer_perm(const std::string http_referer,
+                                           const int perm_mask)
+{
+  ldout(cct, 5) << "Searching permissions for referer=" << http_referer
+                << " mask=" << perm_mask << dendl;
+
+  /* FIXME: C++11 doesn't have std::rbegin nor std::rend. We would like to
+   * switch when C++14 becomes available. */
+  const auto iter = std::find_if(referer_list.crbegin(), referer_list.crend(),
+    [http_referer](const ACLReferer& r) -> bool {
+      return r.is_match(http_referer);
+    }
+  );
+
+  if (referer_list.crend() == iter) {
+    ldout(cct, 5) << "Permissions for referer not found" << dendl;
+    return 0;
+  } else {
+    ldout(cct, 5) << "Found referer permission=" << iter->perm << dendl;
+    return iter->perm & perm_mask;
+  }
+}
+
 int RGWAccessControlPolicy::get_perm(const RGWIdentityApplier& auth_identity,
-                                     const int perm_mask)
+                                     const int perm_mask,
+                                     const char * const http_referer)
 {
   int perm = acl.get_perm(auth_identity, perm_mask);
 
@@ -73,8 +103,9 @@ int RGWAccessControlPolicy::get_perm(const RGWIdentityApplier& auth_identity,
     perm |= perm_mask & (RGW_PERM_READ_ACP | RGW_PERM_WRITE_ACP);
   }
 
-  if (perm == perm_mask)
+  if (perm == perm_mask) {
     return perm;
+  }
 
   /* should we continue looking up? */
   if ((perm & perm_mask) != perm_mask) {
@@ -86,6 +117,11 @@ int RGWAccessControlPolicy::get_perm(const RGWIdentityApplier& auth_identity,
     }
   }
 
+  /* Should we continue looking up even deeper? */
+  if (nullptr != http_referer && (perm & perm_mask) != perm_mask) {
+    perm |= acl.get_referer_perm(http_referer, perm_mask);
+  }
+
   ldout(cct, 5) << "Getting permissions identity=" << auth_identity
                 << " owner=" << owner.get_id()
                 << " perm=" << perm << dendl;
@@ -95,11 +131,12 @@ int RGWAccessControlPolicy::get_perm(const RGWIdentityApplier& auth_identity,
 
 bool RGWAccessControlPolicy::verify_permission(const RGWIdentityApplier& auth_identity,
                                                const int user_perm_mask,
-                                               const int perm)
+                                               const int perm,
+                                               const char * const http_referer)
 {
   int test_perm = perm | RGW_PERM_READ_OBJS | RGW_PERM_WRITE_OBJS;
 
-  int policy_perm = get_perm(auth_identity, test_perm);
+  int policy_perm = get_perm(auth_identity, test_perm, http_referer);
 
   /* the swift WRITE_OBJS perm is equivalent to the WRITE obj, just
      convert those bits. Note that these bits will only be set on
index ec073ba3ed8f186543ef250631a709e0f65876d4..6ae2cbd0621eea60915689600bdd0f5e40a72e16 100644 (file)
@@ -32,6 +32,7 @@ enum ACLGranteeTypeEnum {
   ACL_TYPE_EMAIL_USER = 1,
   ACL_TYPE_GROUP      = 2,
   ACL_TYPE_UNKNOWN    = 3,
+  ACL_TYPE_REFERER    = 4,
 };
 
 enum ACLGroupTypeEnum {
@@ -109,6 +110,7 @@ protected:
   ACLPermission permission;
   string name;
   ACLGroupTypeEnum group;
+  string url_spec;
 
 public:
   ACLGrant() : group(ACL_GROUP_NONE) {}
@@ -122,6 +124,7 @@ public:
       _id = email; // implies from_str() that parses the 't:u' syntax
       return true;
     case ACL_TYPE_GROUP:
+    case ACL_TYPE_REFERER:
       return false;
     default:
       _id = id;
@@ -133,9 +136,10 @@ public:
   ACLPermission& get_permission() { return permission; }
   const ACLPermission& get_permission() const { return permission; }
   ACLGroupTypeEnum get_group() const { return group; }
+  const string& get_referer() const { return url_spec; }
 
   void encode(bufferlist& bl) const {
-    ENCODE_START(4, 3, bl);
+    ENCODE_START(5, 3, bl);
     ::encode(type, bl);
     string s;
     id.to_str(s);
@@ -147,10 +151,11 @@ public:
     ::encode(name, bl);
     __u32 g = (__u32)group;
     ::encode(g, bl);
+    ::encode(url_spec, bl);
     ENCODE_FINISH(bl);
   }
   void decode(bufferlist::iterator& bl) {
-    DECODE_START_LEGACY_COMPAT_LEN(4, 3, 3, bl);
+    DECODE_START_LEGACY_COMPAT_LEN(5, 3, 3, bl);
     ::decode(type, bl);
     string s;
     ::decode(s, bl);
@@ -167,6 +172,11 @@ public:
     } else {
       group = uri_to_group(uri);
     }
+    if (struct_v >= 5) {
+      ::decode(url_spec, bl);
+    } else {
+      url_spec.clear();
+    }
     DECODE_FINISH(bl);
   }
   void dump(Formatter *f) const;
@@ -185,9 +195,60 @@ public:
     group = _group;
     permission.set_permissions(perm);
   }
+  void set_referer(const std::string& _url_spec, int perm) {
+    type.set(ACL_TYPE_REFERER);
+    url_spec = _url_spec;
+    permission.set_permissions(perm);
+  }
 };
 WRITE_CLASS_ENCODER(ACLGrant)
 
+struct ACLReferer {
+  std::string url_spec;
+  int perm;
+
+  ACLReferer() : perm(0) {}
+  ACLReferer(const std::string& url_spec,
+             const int perm)
+    : url_spec(url_spec),
+      perm(perm) {
+  }
+
+  bool is_match(std::string http_referer) const {
+    if (http_referer == url_spec) {
+      return true;
+    }
+
+    if (http_referer.length() < url_spec.length()) {
+      return false;
+    }
+
+    if ('.' == url_spec[0]) {
+      /* Wildcard support: a referer matches the spec when its last char are
+       * perfectly equal to spec. */
+      return !http_referer.compare(http_referer.length() - url_spec.length(),
+                                   url_spec.length(), url_spec);
+    }
+
+    return false;
+  }
+
+  void encode(bufferlist& bl) const {
+    ENCODE_START(1, 1, bl);
+    ::encode(url_spec, bl);
+    ::encode(perm, bl);
+    ENCODE_FINISH(bl);
+  }
+  void decode(bufferlist::iterator& bl) {
+    DECODE_START_LEGACY_COMPAT_LEN(1, 1, 1, bl);
+    ::decode(url_spec, bl);
+    ::decode(perm, bl);
+    DECODE_FINISH(bl);
+  }
+  void dump(Formatter *f) const;
+};
+WRITE_CLASS_ENCODER(ACLReferer)
+
 class RGWIdentityApplier;
 
 class RGWAccessControlList
@@ -196,6 +257,7 @@ protected:
   CephContext *cct;
   map<string, int> acl_user_map;
   map<uint32_t, int> acl_group_map;
+  list<ACLReferer> referer_list;
   multimap<string, ACLGrant> grant_map;
   void _add_grant(ACLGrant *grant);
 public:
@@ -211,17 +273,19 @@ public:
   int get_perm(const RGWIdentityApplier& auth_identity,
                int perm_mask);
   int get_group_perm(ACLGroupTypeEnum group, int perm_mask);
+  int get_referer_perm(const std::string http_referer, int perm_mask);
   void encode(bufferlist& bl) const {
-    ENCODE_START(3, 3, bl);
+    ENCODE_START(4, 3, bl);
     bool maps_initialized = true;
     ::encode(maps_initialized, bl);
     ::encode(acl_user_map, bl);
     ::encode(grant_map, bl);
     ::encode(acl_group_map, bl);
+    ::encode(referer_list, bl);
     ENCODE_FINISH(bl);
   }
   void decode(bufferlist::iterator& bl) {
-    DECODE_START_LEGACY_COMPAT_LEN(3, 3, 3, bl);
+    DECODE_START_LEGACY_COMPAT_LEN(4, 3, 3, bl);
     bool maps_initialized;
     ::decode(maps_initialized, bl);
     ::decode(acl_user_map, bl);
@@ -235,6 +299,9 @@ public:
         _add_grant(&grant);
       }
     }
+    if (struct_v >= 4) {
+      ::decode(referer_list, bl);
+    }
     DECODE_FINISH(bl);
   }
   void dump(Formatter *f) const;
@@ -248,6 +315,7 @@ public:
   void create_default(const rgw_user& id, string name) {
     acl_user_map.clear();
     acl_group_map.clear();
+    referer_list.clear();
 
     ACLGrant grant;
     grant.set_canon(id, name, RGW_PERM_FULL_CONTROL);
@@ -309,11 +377,13 @@ public:
   }
 
   int get_perm(const RGWIdentityApplier& auth_identity,
-               int perm_mask);
+               int perm_mask,
+               const char * http_referer);
   int get_group_perm(ACLGroupTypeEnum group, int perm_mask);
   bool verify_permission(const RGWIdentityApplier& auth_identity,
                          int user_perm_mask,
-                         int perm);
+                         int perm,
+                         const char * http_referer = nullptr);
 
   void encode(bufferlist& bl) const {
     ENCODE_START(2, 2, bl);
index d75f699122af0c2583d1bdfbffd17a76c7376360..96c5d90e1d049a95be536b4bbed7afa49bc70143 100644 (file)
@@ -62,20 +62,81 @@ static bool uid_is_public(const string& uid)
          sub.compare(".referrer") == 0;
 }
 
+static bool extract_referer_urlspec(const string& uid, string& url_spec)
+{
+  const size_t pos = uid.find(':');
+  if (string::npos == pos) {
+    return false;
+  }
+
+  const auto sub = uid.substr(0, pos);
+  url_spec = uid.substr(pos + 1);
+
+  return sub.compare(".r") == 0 ||
+         sub.compare(".referer") == 0 ||
+         sub.compare(".referrer") == 0;
+}
+
+static bool normalize_referer_urlspec(string& url_spec, bool& is_negative)
+{
+  try {
+    if ('-' == url_spec[0]) {
+      is_negative = true;
+      url_spec = url_spec.substr(1);
+    } else {
+      is_negative = false;
+    }
+    if (url_spec != "*" && '*' == url_spec[0]) {
+      url_spec = url_spec.substr(1);
+    }
+
+    return !url_spec.empty() && url_spec != ".";
+  } catch (std::out_of_range) {
+    return false;
+  }
+}
+
 void RGWAccessControlPolicy_SWIFT::add_grants(RGWRados * const store,
                                               const list<string>& uids,
                                               const int perm)
 {
   for (const auto& uid : uids) {
+    ldout(cct, 20) << "trying to add grant for ACL uid=" << uid << dendl;
     ACLGrant grant;
-    RGWUserInfo grant_user;
+    string url_spec;
+
     if (uid_is_public(uid)) {
       grant.set_group(ACL_GROUP_ALL_USERS, perm);
       acl.add_grant(&grant);
-    } else  {
+    } else if (extract_referer_urlspec(uid, url_spec)) {
+      if (0 != (perm & SWIFT_PERM_WRITE)) {
+        ldout(cct, 10) << "cannot grant write access for referers" << dendl;
+        continue;
+      }
+
+      bool is_negative = false;
+      if (false == normalize_referer_urlspec(url_spec, is_negative)) {
+        ldout(cct, 10) << "cannot normalize referer: " << url_spec << dendl;
+        continue;
+      } else {
+        ldout(cct, 10) << "normalized referer to url_spec=" << url_spec
+                       << ", is_negative=" << is_negative << dendl;
+      }
+
+      if (is_negative) {
+        /* Forbid access. */
+        grant.set_referer(url_spec, 0);
+      } else {
+        grant.set_referer(url_spec, perm);
+      }
+
+      acl.add_grant(&grant);
+    } else {
       rgw_user user(uid);
+      RGWUserInfo grant_user;
+
       if (rgw_get_user_info_by_uid(store, user, grant_user) < 0) {
-        ldout(cct, 10) << "grant user does not exist:" << uid << dendl;
+        ldout(cct, 10) << "grant user does not exist: " << uid << dendl;
         /* skipping silently */
       } else {
         grant.set_canon(user, grant_user.display_name, perm);
index bce55ba08c68cdb12b8950058417ff99c34282d0..da4d897ed84277360539525159f6bf800b72d592 100644 (file)
@@ -902,7 +902,8 @@ bool verify_bucket_permission(struct req_state * const s,
   if (!verify_requester_payer_permission(s))
     return false;
 
-  if (bucket_acl->verify_permission(*s->auth_identity, perm, perm))
+  if (bucket_acl->verify_permission(*s->auth_identity, perm, perm,
+                                    s->info.env->get("HTTP_REFERER")))
     return true;
 
   if (!user_acl)
@@ -969,7 +970,8 @@ bool verify_object_permission(struct req_state * const s,
 
   /* we already verified the user mask above, so we pass swift_perm as the mask here,
      otherwise the mask might not cover the swift permissions bits */
-  if (bucket_acl->verify_permission(*s->auth_identity, swift_perm, swift_perm))
+  if (bucket_acl->verify_permission(*s->auth_identity, swift_perm, swift_perm,
+                                    s->info.env->get("HTTP_REFERER")))
     return true;
 
   if (!user_acl)
index 2fd2bb0f56279cc55845508cdecb8d8433066a23..85d691eac2fca2c8ae9ec850d6300d5945a8c894 100644 (file)
@@ -138,6 +138,7 @@ void ACLGrant::dump(Formatter *f) const
 
   f->dump_string("name", name);
   f->dump_int("group", (int)group);
+  f->dump_string("url_spec", url_spec);
 }
 
 void RGWAccessControlList::dump(Formatter *f) const
index b4430783425ff4ff233dcc6b73b358425b258776..996b247c6ad6caadd6f756ab29b9afe4c5cebd98 100644 (file)
@@ -472,16 +472,20 @@ void RGWStatBucket_ObjStore_SWIFT::send_response()
   dump_start(s);
 }
 
-static int get_swift_container_settings(req_state *s, RGWRados *store, RGWAccessControlPolicy *policy, bool *has_policy,
-                                        RGWCORSConfiguration *cors_config, bool *has_cors)
+static int get_swift_container_settings(req_state * const s,
+                                        RGWRados * const store,
+                                        RGWAccessControlPolicy * const policy,
+                                        bool * const has_policy,
+                                        RGWCORSConfiguration * const cors_config,
+                                        bool * const has_cors)
 {
   string read_list, write_list;
 
-  const char *read_attr = s->info.env->get("HTTP_X_CONTAINER_READ");
+  const char * const read_attr = s->info.env->get("HTTP_X_CONTAINER_READ");
   if (read_attr) {
     read_list = read_attr;
   }
-  const char *write_attr = s->info.env->get("HTTP_X_CONTAINER_WRITE");
+  const char * const write_attr = s->info.env->get("HTTP_X_CONTAINER_WRITE");
   if (write_attr) {
     write_list = write_attr;
   }
@@ -490,9 +494,14 @@ static int get_swift_container_settings(req_state *s, RGWRados *store, RGWAccess
 
   if (read_attr || write_attr) {
     RGWAccessControlPolicy_SWIFT swift_policy(s->cct);
-    int r = swift_policy.create(store, s->user->user_id, s->user->display_name, read_list, write_list);
-    if (r < 0)
+    int r = swift_policy.create(store,
+                                s->user->user_id,
+                                s->user->display_name,
+                                read_list,
+                                write_list);
+    if (r < 0) {
       return r;
+    }
 
     *policy = swift_policy;
     *has_policy = true;