Skip to content

Commit

Permalink
STORM-446: Adding doAsUser support to REST APIs.
Browse files Browse the repository at this point in the history
  • Loading branch information
Parth-Brahmbhatt committed Mar 1, 2015
1 parent 807e5cf commit e353fe0
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 28 deletions.
5 changes: 5 additions & 0 deletions STORM-UI-REST-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ You can use a tool such as `curl` to talk to the REST API:
# Note: We assume ui.port is configured to the default value of 8080.
$ curl http://<ui-host>:8080/api/v1/cluster/configuration

##Impersonating a user in secure environment
In a secure environment an authenticated user can impersonate another user. To impersonate a user the caller must pass
`doAsUser` param or header with value set to the user that the request needs to be performed as. Please see SECURITY.MD
to learn more about how to setup impersonation ACLs and authorization. The rest API uses the same configs and acls that
are used by nimbus.

## GET Operations

Expand Down
17 changes: 13 additions & 4 deletions storm-core/src/clj/backtype/storm/thrift.clj
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,15 @@
(if-not (.is_set_parallelism_hint component-common) 1 phint)))

(defn nimbus-client-and-conn
[host port]
(log-message "Connecting to Nimbus at " host ":" port)
([host port]
(nimbus-client-and-conn host port nil))
([host port as-user]
(log-message "Connecting to Nimbus at " host ":" port " as user: " as-user)
(let [conf (read-storm-config)
nimbusClient (NimbusClient. conf host port nil)
nimbusClient (NimbusClient. conf host port nil as-user)
client (.getClient nimbusClient)
transport (.transport nimbusClient)]
[client transport] ))
[client transport] )))

(defmacro with-nimbus-connection
[[client-sym host port] & body]
Expand All @@ -81,6 +83,13 @@
~@body
(finally (.close conn#)))))

(defmacro with-nimbus-connection-as-user
[[client-sym host port as-user] & body]
`(let [[^Nimbus$Client ~client-sym ^TTransport conn#] (nimbus-client-and-conn ~host ~port ~as-user)]
(try
~@body
(finally (.close conn#)))))

(defmacro with-configured-nimbus-connection
[client-sym & body]
`(let [conf# (read-storm-config)
Expand Down
50 changes: 34 additions & 16 deletions storm-core/src/clj/backtype/storm/ui/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,46 @@

(def ^:dynamic *STORM-CONF* (read-storm-config))
(def ^:dynamic *UI-ACL-HANDLER* (mk-authorization-handler (*STORM-CONF* NIMBUS-AUTHORIZER) *STORM-CONF*))
(def ^:dynamic *UI-IMPERSONATION-HANDLER* (mk-authorization-handler (*STORM-CONF* NIMBUS-IMPERSONATION-AUTHORIZER) *STORM-CONF*))

(def http-creds-handler (AuthUtils/GetUiHttpCredentialsPlugin *STORM-CONF*))

(defmacro with-nimbus
[nimbus-sym & body]
`(thrift/with-nimbus-connection
[~nimbus-sym (*STORM-CONF* NIMBUS-HOST) (*STORM-CONF* NIMBUS-THRIFT-PORT)]
~@body))
`(let [context# (ReqContext/context)
user# (if (.principal context#) (.getName (.principal context#)))]
(thrift/with-nimbus-connection-as-user
[~nimbus-sym (*STORM-CONF* NIMBUS-HOST) (*STORM-CONF* NIMBUS-THRIFT-PORT) user#]
~@body)))

(defn assert-authorized-user
([servlet-request op]
(assert-authorized-user servlet-request op nil))
([servlet-request op topology-conf]
(if http-creds-handler (.populateContext http-creds-handler (ReqContext/context) servlet-request))
(if *UI-ACL-HANDLER*
(let [context (ReqContext/context)]
(if-not (.permit *UI-ACL-HANDLER* context op topology-conf)
(let [principal (.principal context)
user (if principal (.getName principal) "unknown")]
(throw (AuthorizationException.
(str "UI request '" op "' for '"
user "' user is not authorized")))))))))
(let [context (ReqContext/context)]
(if http-creds-handler (.populateContext http-creds-handler context servlet-request))

(if (.isImpersonating context)
(if *UI-IMPERSONATION-HANDLER*
(if-not (.permit *UI-IMPERSONATION-HANDLER* context op topology-conf)
(let [principal (.principal context)
real-principal (.realPrincipal context)
user (if principal (.getName principal) "unknown")
real-user (if real-principal (.getName real-principal) "unknown")
remote-address (.remoteAddress context)]
(throw (AuthorizationException.
(str "user '" real-user "' is not authorized to impersonate user '" user "' from host '" remote-address "'. Please
see SECURITY.MD to learn how to configure impersonation ACL.")))))
(log-warn " principal " (.realPrincipal context) " is trying to impersonate " (.principal context) " but "
NIMBUS-IMPERSONATION-AUTHORIZER " has no authorizer configured. This is a potential security hole.
Please see SECURITY.MD to learn how to configure an impersonation authorizer.")))

(if *UI-ACL-HANDLER*
(if-not (.permit *UI-ACL-HANDLER* context op topology-conf)
(let [principal (.principal context)
user (if principal (.getName principal) "unknown")]
(throw (AuthorizationException.
(str "UI request '" op "' for '" user "' user is not authorized")))))))))

(defn get-filled-stats
[summs]
Expand Down Expand Up @@ -911,28 +929,29 @@
(GET "/api/v1/token" [ & m]
(json-response (format "{\"antiForgeryToken\": \"%s\"}" *anti-forgery-token*) (:callback m) :serialize-fn identity))
(POST "/api/v1/topology/:id/activate" [:as {:keys [cookies servlet-request]} id & m]
(assert-authorized-user servlet-request "activate" (topology-config id))
(with-nimbus nimbus
(let [tplg (->> (doto
(GetInfoOptions.)
(.set_num_err_choice NumErrorsChoice/NONE))
(.getTopologyInfoWithOpts ^Nimbus$Client nimbus id))
name (.get_name tplg)]
(assert-authorized-user servlet-request "activate" (topology-config id))
(.activate nimbus name)
(log-message "Activating topology '" name "'")))
(json-response (topology-op-response id "deactivate") (m "callback")))
(POST "/api/v1/topology/:id/deactivate" [:as {:keys [cookies servlet-request]} id & m]
(assert-authorized-user servlet-request "deactivate" (topology-config id))
(with-nimbus nimbus
(let [tplg (->> (doto
(GetInfoOptions.)
(.set_num_err_choice NumErrorsChoice/NONE))
(.getTopologyInfoWithOpts ^Nimbus$Client nimbus id))
name (.get_name tplg)]
(assert-authorized-user servlet-request "deactivate" (topology-config id))
(.deactivate nimbus name)
(log-message "Deactivating topology '" name "'")))
(json-response (topology-op-response id "deactivate") (m "callback")))
(POST "/api/v1/topology/:id/rebalance/:wait-time" [:as {:keys [cookies servlet-request]} id wait-time & m]
(assert-authorized-user servlet-request "rebalance" (topology-config id))
(with-nimbus nimbus
(let [tplg (->> (doto
(GetInfoOptions.)
Expand All @@ -941,7 +960,6 @@
name (.get_name tplg)
rebalance-options (m "rebalanceOptions")
options (RebalanceOptions.)]
(assert-authorized-user servlet-request "rebalance" (topology-config id))
(.set_wait_secs options (Integer/parseInt wait-time))
(if (and (not-nil? rebalance-options) (contains? rebalance-options "numWorkers"))
(.set_num_workers options (Integer/parseInt (.toString (rebalance-options "numWorkers")))))
Expand All @@ -952,14 +970,14 @@
(log-message "Rebalancing topology '" name "' with wait time: " wait-time " secs")))
(json-response (topology-op-response id "rebalance") (m "callback")))
(POST "/api/v1/topology/:id/kill/:wait-time" [:as {:keys [cookies servlet-request]} id wait-time & m]
(assert-authorized-user servlet-request "killTopology" (topology-config id))
(with-nimbus nimbus
(let [tplg (->> (doto
(GetInfoOptions.)
(.set_num_err_choice NumErrorsChoice/NONE))
(.getTopologyInfoWithOpts ^Nimbus$Client nimbus id))
name (.get_name tplg)
options (KillOptions.)]
(assert-authorized-user servlet-request "killTopology" (topology-config id))
(.set_wait_secs options (Integer/parseInt wait-time))
(.killTopologyWithOpts nimbus name options)
(log-message "Killing topology '" name "' with wait time: " wait-time " secs")))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,24 @@ public String getUserName(HttpServletRequest req) {
public ReqContext populateContext(ReqContext context,
HttpServletRequest req) {
String userName = getUserName(req);
Principal p = null;
if (userName != null) {
p = new SingleUserPrincipal(userName);

String doAsUser = req.getHeader("doAsUser");
if(doAsUser == null) {
doAsUser = req.getParameter("doAsUser");
}
Set<Principal> principals = new HashSet<Principal>(1);
if (p != null) {
principals.add(p);

if(doAsUser != null) {
context.setRealPrincipal(new SingleUserPrincipal(userName));
userName = doAsUser;
}
Subject s = new Subject(true, principals, new HashSet(), new HashSet());
context.setSubject(s);

if(userName != null) {
Subject s = new Subject();
Principal p = new SingleUserPrincipal(userName);
s.getPrincipals().add(p);
context.setSubject(s);
}

return context;
}
}

0 comments on commit e353fe0

Please sign in to comment.