]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
Initial work for OPA-Ceph integration 22624/head
authorAshutosh Narkar <anarkar4387@gmail.com>
Wed, 30 May 2018 23:49:30 +0000 (16:49 -0700)
committerAshutosh Narkar <anarkar4387@gmail.com>
Wed, 25 Jul 2018 08:20:34 +0000 (01:20 -0700)
Signed-off-by: Ashutosh Narkar <anarkar4387@gmail.com>
doc/radosgw/index.rst
doc/radosgw/opa.rst [new file with mode: 0644]
src/common/legacy_config_opts.h
src/common/options.cc
src/rgw/CMakeLists.txt
src/rgw/rgw_opa.cc [new file with mode: 0644]
src/rgw/rgw_opa.h [new file with mode: 0644]
src/rgw/rgw_process.cc
src/rgw/rgw_rest_s3.cc
src/rgw/rgw_rest_s3.h

index fa9915c48191ba8c9225730e6d7eed1ad1667669..37ff9305de78bdc835562866965ced23e2b551de 100644 (file)
@@ -49,6 +49,7 @@ you may write data with one API and retrieve it with the other.
    Export over NFS <nfs>
    OpenStack Keystone Integration <keystone>
    OpenStack Barbican Integration <barbican>
+   Open Policy Agent Integration <opa>
    Multi-tenancy <multitenancy>
    Compression <compression>
    LDAP Authentication <ldap-auth>
diff --git a/doc/radosgw/opa.rst b/doc/radosgw/opa.rst
new file mode 100644 (file)
index 0000000..18d7f6d
--- /dev/null
@@ -0,0 +1,71 @@
+==============================
+Open Policy Agent Integration
+==============================
+
+Open Policy Agent (OPA) is a lightweight general-purpose policy engine
+that can be co-located with a service. OPA can be integrated as a
+sidecar, host-level daemon, or library.
+
+Services can offload policy decisions to OPA by executing queries. Hence,
+policy enforcement can be decoupled from policy decisions.
+
+Configure OPA
+=============
+
+To configure OPA, load custom polices into OPA that control the resources users
+are allowed to access. Relevant data or context can also be loaded into OPA to make decisions.
+
+Polices and data can be loaded into OPA in the following ways::
+  * OPA's RESTful APIs
+  * OPA's *bundle* feature that downloads polcies and data from remote HTTP servers
+  * Filesystem
+
+Configure the Ceph Object Gateway
+=================================
+
+The following configuration options are available for OPA integration::
+
+     rgw use opa authz = {use opa server to authorize client requests}
+     rgw opa url = {opa server url:opa server port}
+     rgw opa token = {opa bearer token}
+     rgw opa verify ssl = {verify opa server ssl certificate}
+
+How does the RGW-OPA integration work
+=====================================
+
+After a user is authenticated, OPA can be used to check if the user is authorized
+to perform the given action on the resource. OPA responds with an allow or deny
+decision which is sent back to the RGW which enforces the decision.
+
+Example request::
+
+   POST /v1/data/ceph/authz HTTP/1.1
+   Host: opa.example.com:8181
+   Content-Type: application/json
+   
+   {
+       "input": {
+           "method": "GET",
+           "user_info": {
+               "used_id": "john",
+               "display_name": "John"  
+           },
+           "bucket_info": {
+               "bucket": {
+                   "name": "Testbucket",
+                   "bucket_id": "testbucket" 
+               },
+               "owner": "john" 
+           }             
+       }
+   }
+
+Response::
+
+   {"result": true}
+
+The above is a sample request sent to OPA which contains information about the
+user, resource and the action to be performed on the resource. Based on the polices
+and data loaded into OPA, it will verify whether the request should be allowed or denied.
+In the sample request, RGW makes a POST request to the endpoint */v1/data/ceph/authz*,
+where *ceph* is the package name and *authz* is the rule name.
index 6a38549b33bd4850c23e953efb3784e27fa260a0..1fe4525722f77ffddddebfa841acdec5d31e63ea 100644 (file)
@@ -1338,6 +1338,10 @@ OPTION(rgw_s3_auth_use_rados, OPT_BOOL)  // should we try to use the internal cr
 OPTION(rgw_s3_auth_use_keystone, OPT_BOOL)  // should we try to use keystone for s3?
 OPTION(rgw_s3_auth_order, OPT_STR) // s3 authentication order to try
 OPTION(rgw_barbican_url, OPT_STR)  // url for barbican server
+OPTION(rgw_opa_url, OPT_STR)  // url for OPA server
+OPTION(rgw_opa_token, OPT_STR)  // Bearer token OPA uses to authenticate client requests
+OPTION(rgw_opa_verify_ssl, OPT_BOOL) // should we try to verify OPA's ssl
+OPTION(rgw_use_opa_authz, OPT_BOOL) // should we use OPA to authorize client requests?
 
 /* OpenLDAP-style LDAP parameter strings */
 /* rgw_ldap_uri  space-separated list of LDAP servers in URI format */
index 82c34ad88e5e35213e120edc24d61d1646d284e1..1226c54349a88c547e7aad67d2516c965cecdfc0 100644 (file)
@@ -5362,6 +5362,22 @@ std::vector<Option> get_rgw_options() {
     .set_default("")
     .set_description("LDAP search filter."),
 
+    Option("rgw_opa_url", Option::TYPE_STR, Option::LEVEL_ADVANCED)
+    .set_default("")
+    .set_description("URL to OPA server."),
+
+    Option("rgw_opa_token", Option::TYPE_STR, Option::LEVEL_ADVANCED)
+    .set_default("")
+    .set_description("The Bearer token OPA uses to authenticate client requests."),
+
+    Option("rgw_opa_verify_ssl", Option::TYPE_BOOL, Option::LEVEL_ADVANCED)
+    .set_default(true)
+    .set_description("Should RGW verify the OPA server SSL certificate."),
+
+    Option("rgw_use_opa_authz", Option::TYPE_BOOL, Option::LEVEL_ADVANCED)
+    .set_default(false)
+    .set_description("Should OPA be used to authorize client requests."),
+
     Option("rgw_admin_entry", Option::TYPE_STR, Option::LEVEL_ADVANCED)
     .set_default("admin")
     .set_description("Path prefix to be used for accessing RGW RESTful admin API."),
index a973270811fb5527001a05e4c6ee877632886772..f8aecfbf89314a530062bad8cb93b1c15242c8aa 100644 (file)
@@ -140,7 +140,8 @@ set(rgw_a_srcs
   rgw_rest_usage.cc
   rgw_rest_user.cc
   rgw_swift_auth.cc
-  rgw_usage.cc)
+  rgw_usage.cc
+  rgw_opa.cc)
 
 gperf_generate(${CMAKE_SOURCE_DIR}/src/rgw/rgw_iam_policy_keywords.gperf
   rgw_iam_policy_keywords.frag.cc)
diff --git a/src/rgw/rgw_opa.cc b/src/rgw/rgw_opa.cc
new file mode 100644 (file)
index 0000000..08abf5a
--- /dev/null
@@ -0,0 +1,81 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "rgw_opa.h"
+
+#define dout_context g_ceph_context
+#define dout_subsys ceph_subsys_rgw
+
+int rgw_opa_authorize(RGWOp *& op,
+                      req_state * const s)
+{
+
+  ldpp_dout(op, 2) << "authorizing request using OPA" << dendl;
+
+  /* get OPA url */
+  const string& opa_url = s->cct->_conf->rgw_opa_url;
+  if (opa_url == "") {
+    ldpp_dout(op, 2) << "OPA_URL not provided" << dendl;
+    return -ERR_INVALID_REQUEST;
+  }
+  ldpp_dout(op, 2) << "OPA URL= " << opa_url.c_str() << dendl;
+
+  /* get authentication token for OPA */
+  const string& opa_token = s->cct->_conf->rgw_opa_token;
+
+  int ret;
+  bufferlist bl;
+  RGWHTTPTransceiver req(s->cct, "POST", opa_url.c_str(), &bl);
+
+  /* set required headers for OPA request */
+  req.append_header("X-Auth-Token", opa_token);
+  req.append_header("Content-Type", "application/json");
+
+  /* check if we want to verify OPA server SSL certificate */
+  req.set_verify_ssl(s->cct->_conf->rgw_opa_verify_ssl);
+
+  /* create json request body */
+  JSONFormatter jf;
+  jf.open_object_section("");
+  jf.open_object_section("input");
+  jf.dump_string("method", s->info.env->get("REQUEST_METHOD"));
+  jf.dump_string("relative_uri", s->relative_uri.c_str());
+  jf.dump_string("decoded_uri", s->decoded_uri.c_str());
+  jf.dump_string("params", s->info.request_params.c_str());
+  jf.dump_string("request_uri_aws4", s->info.request_uri_aws4.c_str());
+  jf.dump_string("object_name", s->object.name.c_str());
+  jf.dump_object("user_info", *s->user);
+  jf.dump_object("bucket_info", s->bucket_info);
+  jf.close_section();
+  jf.close_section();
+
+  std::stringstream ss;
+  jf.flush(ss);
+  req.set_post_data(ss.str());
+  req.set_send_length(ss.str().length());
+
+  /* send request */
+  ret = req.process();
+  if (ret < 0) {
+    ldpp_dout(op, 2) << "OPA process error:" << bl.c_str() << dendl;
+    return ret;
+  }
+
+  /* check OPA response */
+  JSONParser parser;
+  if (!parser.parse(bl.c_str(), bl.length())) {
+    ldpp_dout(op, 2) << "OPA parse error: malformed json" << dendl;
+    return -EINVAL;
+  }
+
+  bool opa_result;
+  JSONDecoder::decode_json("result", opa_result, &parser);
+
+  if (opa_result == false) {
+    ldpp_dout(op, 2) << "OPA rejecting request" << dendl;
+    return -EPERM;
+  }
+
+  ldpp_dout(op, 2) << "OPA accepting request" << dendl;
+  return 0;
+}
diff --git a/src/rgw/rgw_opa.h b/src/rgw/rgw_opa.h
new file mode 100644 (file)
index 0000000..2f87e45
--- /dev/null
@@ -0,0 +1,14 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef RGW_OPA_H
+#define RGW_OPA_H
+
+#include "rgw_common.h"
+#include "rgw_op.h"
+
+/* authorize request using OPA */
+int rgw_opa_authorize(RGWOp*& op,
+                      req_state* s);
+
+#endif /* RGW_OPA_H */
index 91914310f48dc28b902ebffba4ce75649caed253..c1cfbb02e62402d83904a3cfca0c93c6a80c8d2d 100644 (file)
@@ -12,6 +12,7 @@
 #include "rgw_process.h"
 #include "rgw_loadgen.h"
 #include "rgw_client_io.h"
+#include "rgw_opa.h"
 
 #define dout_subsys ceph_subsys_rgw
 
@@ -79,6 +80,14 @@ int rgw_process_authenticated(RGWHandler_REST * const handler,
     return ret;
   }
 
+  /* Check if OPA is used to authorize requests */
+  if (s->cct->_conf->rgw_use_opa_authz) {
+    ret = rgw_opa_authorize(op, s);
+    if (ret < 0) {
+      return ret;
+    }
+  }
+
   ldpp_dout(op, 2) << "verifying op permissions" << dendl;
   ret = op->verify_permission();
   if (ret < 0) {
index d0e0b8bb87665693bdc26d546ce57cd982cbf1e8..95ab37aec9b14e5e25e9dfcf2fdeb3681061d47f 100644 (file)
@@ -4215,7 +4215,7 @@ void rgw::auth::s3::LDAPEngine::shutdown() {
   }
 }
 
-/* LocalEndgine */
+/* LocalEngine */
 rgw::auth::Engine::result_t
 rgw::auth::s3::LocalEngine::authenticate(
   const boost::string_view& _access_key_id,
index 32cf79d0c871bf633f9339359fad98048ea79786..8cb7d5d00957f00a69b41af163c403e42152e7bd 100644 (file)
@@ -846,7 +846,6 @@ public:
   static void shutdown();
 };
 
-
 class LocalEngine : public AWSEngine {
   RGWRados* const store;
   const rgw::auth::LocalApplier::Factory* const apl_factory;