Skip to content
Permalink
Browse files

Allow k8s namespace with run-as-requestor (#745)

  • Loading branch information...
DaoWen authored and shamsimam committed May 14, 2019
1 parent 33cb9d2 commit 4de273cfec6b1c5dad20a74ef476203bfab4eaca
@@ -42,7 +42,7 @@ Additional (optional) parameters that can be set:
|`X-Waiter-Metadata-*`|n/a|any string|A metadata field. Metadata fields allow you to store arbitrary strings along with your service description.|Allows additional information to be stored with a service to help identify the service.|
|`X-Waiter-Metric-Group`|"other"|[A-Za-z0-9-_]+|Metric groups allow services to be grouped together for the purpose of metric collection.|If you have `:statsd` enabled in your [config file](../config-full.edn), then specify a metric group. If you have multiple services, choose the same metric group if you'd like combined metrics, otherwise choose independent metric groups.|
|`X-Waiter-Min-Instances`|1|1 or 2|The minimum number of instances that Waiter should keep running at all times.|Go with the default.|
|`X-Waiter-Namespace`|empty|user-name|The namespace in which to create back-end scheduler objects. Must match the run-as-user when specified.|Leave this field blank unless you need direct access to resources Waiter creates on your behalf.|
|`X-Waiter-Namespace`|empty|user-name|The namespace in which to create back-end scheduler objects. When set, must match the run-as-user value.|Leave this field blank unless you need direct access to resources Waiter creates on your behalf.|
|`X-Waiter-Param-*`|n/a|any string|A parameter field. Parameter fields allow you to pass arbitrary string value to the environment executing your command. The variable name is formed by trimming the `X-Waiter-Param-` prefix and then uppercasing, e.g. `X-Waiter-Param-Foo_Bar2` is converted to the variable `FOO_BAR2`. Any passed param (e.g. `FOO_BAR2`) must be present in the `Allowed-Params` parameter value of the service.|Use as needed.|
|`X-Waiter-Permitted-User`|user who is making the request|any user|The user that is authorized to make requests to your service|Use as needed.|
|`X-Waiter-Ports`|1|[1-10]|The number of ports needed by the service.|Use as needed, only restriction is that the first port ($PORT0) must always be used for a web server responding to incoming web requests using the specified (configured by backend-proto) HTTP/1.1 or HTTP/2 protocol.|
@@ -162,15 +162,13 @@
(log/warn "test-basic-logs did not verify file link:" stdout-file-link)))))))))

(defn- check-pod-namespace
[waiter-url namespace-arg expected-namespace]
[waiter-url headers expected-namespace]
(let [cookies (all-cookies waiter-url)
router-url (-> waiter-url routers first val)
testing-suffix (if namespace-arg "custom" "default")
testing-suffix (str (:x-waiter-run-as-user headers "nil") "-" (:x-waiter-namespace headers "nil"))
{:keys [body error service-id status]}
(make-request-with-debug-info
(cond-> {:x-waiter-name (str (rand-name) "-" testing-suffix)}
namespace-arg
(assoc :x-waiter-namespace namespace-arg))
(merge {:x-waiter-name (str (rand-name) "-" testing-suffix)} headers)
#(make-kitchen-request waiter-url % :path "/hello"))]
(when-not (= 200 status)
(throw (ex-info "Failed to create service"
@@ -189,17 +187,46 @@
(is (= expected-namespace pod-namespace))))))

(deftest ^:parallel ^:integration-fast test-pod-namespace
"Expected behavior for services with namespaces:
Run-As-User Namespace Validation
Missing Missing OK
Missing * OK
Missing foo OK
Missing bar FAIL
foo Missing OK
foo * OK
foo foo OK
foo bar FAIL
* Missing OK
* * OK
* foo FAIL
* bar FAIL"
(testing-using-waiter-url
(when (using-k8s? waiter-url)
(let [current-user (retrieve-username)
default-namespace (-> waiter-url default-scheduler-settings :replicaset-spec-builder :default-namespace)]
(testing "Default namespace"
(check-pod-namespace waiter-url nil default-namespace))
(testing "Custom namespace"
(check-pod-namespace waiter-url current-user current-user))
(testing "Invalid namespace"
default-namespace (-> waiter-url default-scheduler-settings :replicaset-spec-builder :default-namespace)
star-user-header {:x-waiter-run-as-user "*"}
current-user-header {:x-waiter-run-as-user current-user}
not-current-user "not-current-user"]
(testing "namespaces for current user (implicit)"
(check-pod-namespace waiter-url {} default-namespace)
(check-pod-namespace waiter-url {:x-waiter-namespace "*"} current-user)
(check-pod-namespace waiter-url {:x-waiter-namespace current-user} current-user)
(is (thrown? Exception #"Service namespace must either be omitted or match the run-as-user"
(check-pod-namespace waiter-url {:x-waiter-namespace not-current-user} current-user))))
(testing "namespaces for current user (explicit)"
(check-pod-namespace waiter-url current-user-header default-namespace)
(check-pod-namespace waiter-url (assoc current-user-header :x-waiter-namespace "*") current-user)
(check-pod-namespace waiter-url (assoc current-user-header :x-waiter-namespace current-user) current-user)
(is (thrown? Exception #"Service namespace must either be omitted or match the run-as-user"
(check-pod-namespace waiter-url "not-current-user" current-user))))))))
(check-pod-namespace waiter-url (assoc current-user-header :x-waiter-namespace not-current-user) current-user))))
(testing "namespaces for run-as-requester"
(check-pod-namespace waiter-url star-user-header default-namespace)
(check-pod-namespace waiter-url (assoc star-user-header :x-waiter-namespace "*") current-user)
(is (thrown? Exception #"Cannot use run-as-requester with a specific namespace"
(check-pod-namespace waiter-url (assoc star-user-header :x-waiter-namespace current-user) current-user)))
(is (thrown? Exception #"Cannot use run-as-requester with a specific namespace"
(check-pod-namespace waiter-url (assoc star-user-header :x-waiter-namespace not-current-user) current-user))))))))

(defn- get-pod-service-account
[waiter-url namespace-arg user]
@@ -837,32 +837,33 @@
(let [service-id-prefix (rand-name)
target-user (retrieve-username)
token (create-token-name waiter-url service-id-prefix)]
(try
(log/info "creating configuration using token" token)
(let [token-definition (assoc
(kitchen-request-headers :prefix "")
:name service-id-prefix
:namespace target-user
:run-as-user target-user
:token token)
{:keys [body status]} (post-token waiter-url token-definition)]
(when (not= 200 status)
(log/info "error:" body)
(is (not body))))
(log/info "created configuration using token" token)
(log/info "retrieving configuration for token" token)
(let [token-response (get-token waiter-url token)
response-body (str (:body token-response))]
(when (not (str/includes? response-body service-id-prefix))
(log/info response-body))
(assert-response-status token-response 200)
(is (str/includes? response-body service-id-prefix))
(let [{:strs [namespace run-as-user] :as token-json} (try-parse-json response-body)]
(is (= target-user run-as-user))
(is (= target-user namespace))))
(log/info "asserted retrieval of configuration for token" token)
(finally
(delete-token-and-assert waiter-url token))))
(for [token-user [target-user "*"]]
(try
(log/info "creating configuration using token" token)
(let [token-definition (assoc
(kitchen-request-headers :prefix "")
:name service-id-prefix
:namespace token-user
:run-as-user token-user
:token token)
{:keys [body status]} (post-token waiter-url token-definition)]
(when (not= 200 status)
(log/info "error:" body)
(is (not body))))
(log/info "created configuration using token" token)
(log/info "retrieving configuration for token" token)
(let [token-response (get-token waiter-url token)
response-body (str (:body token-response))]
(when (not (str/includes? response-body service-id-prefix))
(log/info response-body))
(assert-response-status token-response 200)
(is (str/includes? response-body service-id-prefix))
(let [{:strs [namespace run-as-user] :as token-json} (try-parse-json response-body)]
(is (= target-user run-as-user))
(is (= target-user namespace))))
(log/info "asserted retrieval of configuration for token" token)
(finally
(delete-token-and-assert waiter-url token)))))

(testing "can't create token with bad namespace"
(let [service-desc {:name (rand-name "notused")
@@ -891,17 +891,23 @@
service-description-based-on-headers)
; param headers need to update the environment
merge-params)
raw-run-as-user (get service-description-from-headers-and-token-sources "run-as-user")
raw-namespace (get service-description-from-headers-and-token-sources "namespace")
sanitized-service-description-from-sources (cond-> service-description-from-headers-and-token-sources
;; * run-as-user is the same as a missing run-as-user
(= "*" (get service-description-from-headers-and-token-sources "run-as-user"))
(dissoc service-description-from-headers-and-token-sources "run-as-user"))
(= "*" raw-run-as-user)
(dissoc "run-as-user")
;; * namespace means match the current user (for use with run-as-requester)
(= "*" raw-namespace)
(assoc "namespace" username))
sanitized-run-as-user (get sanitized-service-description-from-sources "run-as-user")
sanitized-metadata-description (sanitize-metadata sanitized-service-description-from-sources)
; run-as-user will not be set if description-from-headers or the token description contains it.
; else rely on presence of x-waiter headers to set the run-as-user
contains-waiter-header? (headers/contains-waiter-header waiter-headers on-the-fly-service-description-keys)
on-the-fly? (headers/contains-waiter-header waiter-headers on-the-fly-service-description-keys)
contains-service-parameter-header? (headers/contains-waiter-header waiter-headers service-parameter-keys)
user-service-description (cond-> sanitized-metadata-description
(and (not (contains? sanitized-metadata-description "run-as-user")) contains-waiter-header?)
(and (not (contains? sanitized-metadata-description "run-as-user")) on-the-fly?)
; can only set the run-as-user if some on-the-fly-service-description-keys waiter header was provided
(assoc-run-as-requester-fields username)
contains-service-parameter-header?
@@ -910,6 +916,9 @@
(when-not (seq user-service-description)
(throw (ex-info (utils/message :cannot-identify-service)
(error-message-map-fn passthrough-headers waiter-headers))))
(when (and (= "*" raw-run-as-user) raw-namespace (not= "*" raw-namespace))
(throw (ex-info "Cannot use run-as-requester with a specific namespace"
{:namespace raw-namespace :run-as-user raw-run-as-user :status 400})))
(sling/try+
(let [{:keys [core-service-description service-description service-id]}
(build service-description-builder user-service-description
@@ -922,7 +931,7 @@
service-preauthorized (and token-preauthorized (empty? service-description-based-on-headers))
service-authentication-disabled (and token-authentication-disabled (empty? service-description-based-on-headers))]
{:core-service-description core-service-description
:on-the-fly? contains-waiter-header?
:on-the-fly? on-the-fly?
:service-authentication-disabled service-authentication-disabled
:service-description service-description
:service-id service-id
@@ -2028,6 +2028,7 @@
"mem" {:max (* 32 1024)}}
builder (create-default-service-description-builder {:constraints constraints})
basic-service-description {"cpus" 1, "mem" 1, "cmd" "foo", "version" "bar", "run-as-user" "*"}
some-user-service-description (assoc basic-service-description "run-as-user" "some-user")
validation-settings {:allow-missing-required-fields? false}]

(testing "validate-service-description-within-limits"
@@ -2070,21 +2071,26 @@
:type :service-description-error}
(select-keys (ex-data ex) [:friendly-error-message :status :type])))))))

(testing "validate-service-description-namespace-default"
(is (nil? (validate builder basic-service-description validation-settings))))

(testing "validate-service-description-namespace-custom"
(testing "validate-service-description-namespace-run-as-requester"
(is (nil? (validate builder basic-service-description validation-settings)))
(is (nil? (validate builder
(assoc basic-service-description
"namespace" "some-user"
"run-as-user" "some-user")
validation-settings))))
(assoc basic-service-description "namespace" "*")
validation-settings)))
(is (thrown? Exception #"Cannot use run-as-requester with a specific namespace"
(validate builder
(assoc basic-service-description
"namespace" "some-user")
validation-settings))))

(testing "validate-service-description-namespace-invalid"
(testing "validate-service-description-namespace-some-user"
(is (nil? (validate builder some-user-service-description validation-settings)))
(is (nil? (validate builder
(assoc some-user-service-description "namespace" "some-user")
validation-settings)))
(is (thrown? Exception #"Service namespace must either be omitted or match the run-as-user"
(validate builder
(assoc basic-service-description
"namespace" "not-the-current-user")
"namespace" "some-other-user")
validation-settings))))))

(deftest test-retrieve-most-recently-modified-token

0 comments on commit 4de273c

Please sign in to comment.
You can’t perform that action at this time.