From 7f9fc3fc4b0527fc746827baf50056d384bcb335 Mon Sep 17 00:00:00 2001 From: Jeremy Barlow Date: Thu, 24 Sep 2015 10:29:16 -0700 Subject: [PATCH 1/4] (SERVER-763) Read client auth info from tk-authz when configured This commit changes the Ruby request handler to read client authentication info from trapperkeeper-authorization when tk-authz is enabled via the `use-legacy-auth-conf: false` setting. Support for the pre-existing method of getting this info - via X-Client headers or an SSL certificate, depending upon how the `master.allow-header-cert-info` setting is configured - is preserved in the case that no information can be derived from tk-authz. tk-authz is presumed to provide client authentication info via an `authorization` map within the Ring request map. --- dev/puppet-server.conf.sample | 4 - project.clj | 2 +- .../ca/certificate_authority_service.clj | 10 +- .../services/jruby/jruby_puppet_service.clj | 12 +- .../puppet_admin/puppet_admin_service.clj | 10 +- .../request_handler/request_handler_core.clj | 153 ++++++++++-------- .../request_handler_service.clj | 24 ++- .../puppetserver/auth_conf_test.clj | 84 +++++++++- .../request_handler_core_test.clj | 43 +++-- 9 files changed, 243 insertions(+), 99 deletions(-) diff --git a/dev/puppet-server.conf.sample b/dev/puppet-server.conf.sample index 25dcd1b864..c1c92e080f 100644 --- a/dev/puppet-server.conf.sample +++ b/dev/puppet-server.conf.sample @@ -2,10 +2,6 @@ global: { logging-config: ./dev/logback-dev.xml } -master: { - allow-header-cert-info: false -} - product: { update-server-url: "http://localhost/" name: {group-id: puppetlabs.dev diff --git a/project.clj b/project.clj index f9863dca14..71f8c44dd0 100644 --- a/project.clj +++ b/project.clj @@ -21,7 +21,7 @@ ;; end version conflict resolution dependencies [puppetlabs/trapperkeeper ~tk-version] - [puppetlabs/trapperkeeper-authorization "0.1.3"] + [puppetlabs/trapperkeeper-authorization "0.1.3-SNAPSHOT"] [puppetlabs/kitchensink ~ks-version] [puppetlabs/ssl-utils "0.8.1"] [puppetlabs/dujour-version-check "0.1.2" :exclusions [org.clojure/tools.logging]] diff --git a/src/clj/puppetlabs/services/ca/certificate_authority_service.clj b/src/clj/puppetlabs/services/ca/certificate_authority_service.clj index bddaa52b4a..9fe7babf9a 100644 --- a/src/clj/puppetlabs/services/ca/certificate_authority_service.clj +++ b/src/clj/puppetlabs/services/ca/certificate_authority_service.clj @@ -18,11 +18,11 @@ puppet-version (get-in-config [:puppet-server :puppet-version])] (if (not-empty (:access-control settings)) (log/warn - (str "The 'client-whitelist' and 'authorization-required' settings in " - "the 'certificate-authority.certificate-status' section are " - "deprecated and will be removed in a future release. Remove these " - "settings and instead create an appropriate authorization rule in " - "the /etc/puppetlabs/puppetserver/conf.d/auth.conf file."))) + "The 'client-whitelist' and 'authorization-required' settings in the" + "'certificate-authority.certificate-status' section are deprecated and" + "will be removed in a future release. Remove these settings and create" + "an appropriate authorization rule in the" + "/etc/puppetlabs/puppetserver/conf.d/auth.conf file.")) (ca/initialize! settings) (log/info "CA Service adding a ring handler") (add-ring-handler diff --git a/src/clj/puppetlabs/services/jruby/jruby_puppet_service.clj b/src/clj/puppetlabs/services/jruby/jruby_puppet_service.clj index 9cbd64399b..7de84f1035 100644 --- a/src/clj/puppetlabs/services/jruby/jruby_puppet_service.clj +++ b/src/clj/puppetlabs/services/jruby/jruby_puppet_service.clj @@ -29,12 +29,12 @@ (core/verify-config-found! config) (log/info "Initializing the JRuby service") (if (:use-legacy-auth-conf config) - (log/warn - (str "The 'use-legacy-auth-conf' setting in the 'jruby-puppet' section " - "is deprecated and will be removed in a future release. Change " - "the value of this setting to 'false' and migrate your authorization " - "rule definitions in the /etc/puppetlabs/puppet/auth.conf file to " - "the /etc/puppetlabs/puppetserver/conf.d/auth.conf file."))) + (log/warn "The 'jruby-puppet.use-legacy-auth-conf' setting is set to" + "'true'. Support for the legacy Puppet auth.conf file is" + "deprecated and will be removed in a future release. Change" + "this setting to 'false' and migrate your authorization rule" + "definitions in the /etc/puppetlabs/puppet/auth.conf file to" + "the /etc/puppetlabs/puppetserver/conf.d/auth.conf file.")) (core/add-facter-jar-to-system-classloader (:ruby-load-path config)) (let [pool-context (core/create-pool-context config profiler agent-shutdown-fn)] (jruby-agents/send-prime-pool! pool-context) diff --git a/src/clj/puppetlabs/services/puppet_admin/puppet_admin_service.clj b/src/clj/puppetlabs/services/puppet_admin/puppet_admin_service.clj index 052ccd36e2..57603c729a 100644 --- a/src/clj/puppetlabs/services/puppet_admin/puppet_admin_service.clj +++ b/src/clj/puppetlabs/services/puppet_admin/puppet_admin_service.clj @@ -19,11 +19,11 @@ settings)] (if (not-empty whitelist-settings) (log/warn - (str "The 'client-whitelist' and 'authorization-required' settings in " - "the 'puppet-admin' section are deprecated and will be removed " - "in a future release. Remove these settings and instead create an " - "appropriate authorization rule in the " - "/etc/puppetlabs/puppetserver/conf.d file."))) + "The 'client-whitelist' and 'authorization-required' settings in" + "the 'puppet-admin' section are deprecated and will be removed" + "in a future release. Remove these settings and create an" + "appropriate authorization rule in the" + "/etc/puppetlabs/puppetserver/conf.d/auth.conf file.")) (add-ring-handler this (core/build-ring-handler route diff --git a/src/clj/puppetlabs/services/request_handler/request_handler_core.clj b/src/clj/puppetlabs/services/request_handler/request_handler_core.clj index 9efab258b0..cdbf102fb8 100644 --- a/src/clj/puppetlabs/services/request_handler/request_handler_core.clj +++ b/src/clj/puppetlabs/services/request_handler/request_handler_core.clj @@ -39,17 +39,6 @@ :ssl-client-header (unmunge-http-header-name (:ssl-client-header puppet-server))}) -(defn get-cert-common-name - "Given a request, return the Common Name from the client certificate subject." - [ssl-client-cert] - (if-let [cert ssl-client-cert] - (if-let [cert-dn (-> cert .getSubjectX500Principal .getName)] - (if-let [cert-cn (ks/cn-for-dn cert-dn)] - cert-cn - (log/errorf "cn not found in client certificate dn: %s" - cert-dn)) - (log/error "dn not found for client certificate subject")))) - (defn response->map "Converts a JRubyPuppetResponse instance to a map." [response] @@ -113,13 +102,27 @@ (defn header-auth-info "Return a map with authentication info based on header content" - [header-dn-name header-dn-val header-auth-val] + [header-dn-name header-dn-val header-auth-name header-auth-val] (if (ssl-utils/valid-x500-name? header-dn-val) - {:client-cert-cn (ssl-utils/x500-name->CN header-dn-val) - :authenticated (= "SUCCESS" header-auth-val)} (do - (if-not (nil? header-dn-val) - (log/errorf "The DN '%s' provided by the HTTP header '%s' is malformed." + (let [cn (ssl-utils/x500-name->CN header-dn-val) + authenticated (= "SUCCESS" header-auth-val)] + (log/debugf "CN '%s' provided by HTTP header '%s'" + cn header-dn-val) + (log/debugf (str "Verification of client '%s' provided by HTTP " + "header '%s': '%s'. Authenticated: %s.") + cn + header-auth-name + header-auth-val + authenticated) + {:client-cert-cn cn + :authenticated authenticated})) + (do + (if (nil? header-dn-val) + (log/debugf (str "No DN provided by the HTTP header '%s'. Treating " + "client as unauthenticated.") header-dn-name) + (log/errorf (str "DN '%s' provided by the HTTP header '%s' is " + "malformed. Treating client as unauthenticated.") header-dn-val header-dn-name)) unauthenticated-client-info))) @@ -173,40 +176,68 @@ cert-count " found")))))) -(defn auth-maybe-with-client-header-info - "Return authentication info based on client headers" - [config headers] - (let [header-dn-name (:ssl-client-header config) - header-dn-val (get headers header-dn-name) - header-auth-name (:ssl-client-verify-header config) - header-auth-val (get headers header-auth-name) - header-cert-val (get headers header-client-cert-name)] - (if (:allow-header-cert-info config) - (-> (header-auth-info header-dn-name - header-dn-val - header-auth-val) - (assoc :client-cert (header-cert header-cert-val))) - (do - (doseq [[header-name header-val] {header-dn-name header-dn-val - header-auth-name header-auth-val - header-client-cert-name header-cert-val}] - (if header-val - (log/warn "The HTTP header" header-name "was specified," - "but the master config option allow-header-cert-info" - "was either not set, or was set to false." - "This header will be ignored."))) - unauthenticated-client-info)))) +(defn ssl-auth-info + "Get map of client authentication info from the supplied + `java.security.cert.X509Certificate` object. If the supplied object is nil, + the information returned would represent an 'unauthenticated' client." + [ssl-client-cert] + (if ssl-client-cert + (let [cn (ssl-utils/get-cn-from-x509-certificate ssl-client-cert) + authenticated (not (empty? cn))] + (log/debugf "CN '%s' provided by SSL certificate. Authenticated: %s." + cn authenticated) + {:client-cert-cn cn + :authenticated authenticated}) + (do + (log/debugf "No SSL client certificate provided. " + "Treating client as unauthenticated.") + unauthenticated-client-info))) + +(defn client-auth-info + "Get map of client authentication info for the client. Map has the following + keys: + + * :client-cert - A `java.security.cert.X509Certificate` object or nil + * :client-cert-cn - The CN (Common Name) of the client, typically associated + with the CN attribute from the Distinguished Name + in an X.509 certificate's Subject. + * :authenticated - A boolean representing whether or not the client is + considered to have been successfully authenticated. -(defn auth-maybe-with-ssl-info - "Merge information from the SSL client cert into the jruby request if - available and information was not expected to be provided via client headers" - [config ssl-client-cert request] - (if (:allow-header-cert-info config) - request - (let [cn (get-cert-common-name ssl-client-cert)] - (merge request {:client-cert ssl-client-cert - :client-cert-cn cn - :authenticated (not (nil? cn))})))) + Parameters: + + * config - Map of configuration data + * request - Ring request containing client data" + [config request] + (if-let [authorization (:authorization request)] + {:client-cert (:certificate authorization) + :client-cert-cn (:name authorization) + :authenticated (true? (:authentic? authorization))} + (let [headers (:headers request) + header-dn-name (:ssl-client-header config) + header-dn-val (get headers header-dn-name) + header-auth-name (:ssl-client-verify-header config) + header-auth-val (get headers header-auth-name) + header-cert-val (get headers header-client-cert-name)] + (if (:allow-header-cert-info config) + (-> (header-auth-info header-dn-name + header-dn-val + header-auth-name + header-auth-val) + (assoc :client-cert (header-cert header-cert-val))) + (do + (doseq [[header-name header-val] + {header-dn-name header-dn-val + header-auth-name header-auth-val + header-client-cert-name header-cert-val}] + (if header-val + (log/warn "The HTTP header" header-name "was specified," + "but the master config option allow-header-cert-info" + "was either not set, or was set to false." + "This header will be ignored."))) + (let [ssl-cert (:ssl-client-cert request)] + (-> (ssl-auth-info ssl-cert) + (assoc :client-cert ssl-cert)))))))) (defn as-jruby-request "Given a ring HTTP request, return a new map that contains all of the data @@ -221,20 +252,16 @@ Server. If so, then the DN, authentication status, and, optionally, the certificate will be provided by HTTP headers." [config request] - (let [headers (:headers request) - jruby-req {:uri (:uri request) - :params (:params request) - :remote-addr (:remote-addr request) - :headers headers - :body (:body request) - :request-method (-> (:request-method request) - name - string/upper-case)}] - (merge jruby-req - (->> (auth-maybe-with-client-header-info config - headers) - (auth-maybe-with-ssl-info config - (:ssl-client-cert request)))))) + (merge + {:uri (:uri request) + :params (:params request) + :remote-addr (:remote-addr request) + :headers (:headers request) + :body (:body request) + :request-method (-> (:request-method request) + name + string/upper-case)} + (client-auth-info config request))) (defn make-request-mutable [request] diff --git a/src/clj/puppetlabs/services/request_handler/request_handler_service.clj b/src/clj/puppetlabs/services/request_handler/request_handler_service.clj index 3dc07402ea..e00007c31b 100644 --- a/src/clj/puppetlabs/services/request_handler/request_handler_service.clj +++ b/src/clj/puppetlabs/services/request_handler/request_handler_service.clj @@ -2,15 +2,33 @@ (:require [puppetlabs.trapperkeeper.core :as tk] [puppetlabs.services.protocols.request-handler :as handler] [puppetlabs.services.request-handler.request-handler-core :as request-handler-core] - [puppetlabs.trapperkeeper.services :as tk-services])) + [puppetlabs.trapperkeeper.services :as tk-services] + [clojure.tools.logging :as log])) (tk/defservice request-handler-service handler/RequestHandlerService [[:PuppetServerConfigService get-config]] (init [this context] (let [jruby-service (tk-services/get-service this :JRubyPuppetService) - config (request-handler-core/config->request-handler-settings (get-config))] - (assoc context :request-handler (request-handler-core/build-request-handler jruby-service config)))) + config (get-config)] + (when (contains? (:master config) :allow-header-cert-info) + (if (true? (get-in config [:jruby-puppet :use-legacy-auth-conf])) + (log/warn "The 'master.allow-header-cert-info' setting is deprecated. " + "Remove it, set 'jruby-puppet.use-legacy-auth-conf' to" + "'false', migrate your authorization rule definitions in" + "the /etc/puppetlabs/puppet/auth.conf file to the" + "/etc/puppetlabs/puppetserver/conf.d/auth.conf file, and set" + "'authorization.allow-header-cert-info' to the desired value.") + (log/warn "The 'master.allow-header-cert-info' setting is deprecated" + "and will be ignored in favor of the" + "'authorization.allow-header-cert-info' setting because" + "the 'jruby-puppet.use-legacy-auth-conf' setting is 'false'. " + "Remove the 'master.allow-header-cert-info' setting."))) + (assoc context :request-handler + (request-handler-core/build-request-handler + jruby-service + (request-handler-core/config->request-handler-settings + config))))) (handle-request [this request] (let [handler (:request-handler (tk-services/service-context this))] diff --git a/test/integration/puppetlabs/puppetserver/auth_conf_test.clj b/test/integration/puppetlabs/puppetserver/auth_conf_test.clj index 007dd58b26..4c3ef44cd1 100644 --- a/test/integration/puppetlabs/puppetserver/auth_conf_test.clj +++ b/test/integration/puppetlabs/puppetserver/auth_conf_test.clj @@ -1,12 +1,17 @@ (ns puppetlabs.puppetserver.auth-conf-test (:require [clojure.test :refer :all] + [clojure.string :as str] [puppetlabs.puppetserver.bootstrap-testutils :as bootstrap] [puppetlabs.http.client.sync :as http-client] [puppetlabs.kitchensink.core :as ks] + [puppetlabs.trapperkeeper.testutils.logging :as logutils] + [puppetlabs.ssl-utils.simple :as ssl-simple] + [puppetlabs.ssl-utils.core :as ssl-utils] [schema.test :as schema-test] [puppetlabs.services.jruby.jruby-testutils :as jruby-testutils] [me.raynes.fs :as fs] - [puppetlabs.trapperkeeper.testutils.logging :as logutils])) + [ring.util.codec :as ring-codec]) + (:import (java.io StringWriter))) (def test-resources-dir "./dev-resources/puppetlabs/puppetserver/auth_conf_test") @@ -55,13 +60,15 @@ (is (= 403 (:status response)) (ks/pprint-to-string response))))))))) -(deftest ^:integration tk-auth-used-when-legacy-auth-conf-false - (testing "Authorization is done per trapperkeeper auth.conf when :use-legacy-auth-conf false" +(deftest ^:integration request-with-ssl-cert-handled-via-tk-auth + (testing (str "Request with SSL certificate via trapperkeeper-authorization " + "handled") (logutils/with-test-logging (bootstrap/with-puppetserver-running app {:jruby-puppet {:use-legacy-auth-conf false} :authorization {:version 1 + :allow-header-cert-info false :rules [{:match-request {:path "^/puppet/v3/catalog/private$" :type "regex"} @@ -100,3 +107,74 @@ (let [response (http-get "production/catalog/private")] (is (= 200 (:status response)) (ks/pprint-to-string response))))))))) + +(deftest ^:integration request-with-x-client-headers-handled-via-tk-auth + (testing (str "Request with X-Client headers via trapperkeeper-authorization " + "handled") + (let [extension-value "UUUU-IIIII-DDD" + cert (:cert (ssl-simple/gen-self-signed-cert + "ssl-client" + 1 + {:keylength 512 + :extensions [{:oid "1.3.6.1.4.1.34380.1.1.1" + :critical false + :value extension-value}]})) + url-encoded-cert (let [cert-writer (StringWriter.) + _ (ssl-utils/cert->pem! cert cert-writer)] + (ring-codec/url-encode cert-writer)) + http-get-no-ssl (fn [path] + (http-client/get + (str "http://localhost:8080/" path) + {:headers {"Accept" "pson" + "X-Client-Cert" url-encoded-cert + "X-Client-DN" "CN=private" + "X-Client-Verify" "SUCCESS"} + :as :text}))] + (logutils/with-test-logging + (bootstrap/with-puppetserver-running + app + {:jruby-puppet {:use-legacy-auth-conf false} + :authorization {:version 1 + :allow-header-cert-info true + :rules + [{:match-request {:path "^/puppet/v3/catalog/private$" + :type "regex"} + :allow ["private" "localhost"] + :sort-order 1 + :name "catalog"}]} + :webserver {:host "localhost" + :port 8080}} + (testing "as 403 for unauthorized user" + (logutils/with-test-logging + (let [response (http-get-no-ssl + "puppet/v3/catalog/public?environment=production")] + (is (= 403 (:status response)) + (ks/pprint-to-string response))))) + (testing "for certificate when provided" + (let [environment-dir (fs/file jruby-testutils/code-dir + "environments") + manifest-dir (fs/file environment-dir + "production" + "manifests")] + (try + (fs/mkdirs manifest-dir) + (spit (fs/file manifest-dir "site.pp") + (str/join "\n" + ["notify {'trusty_info':" + " message => $trusted[extensions][pp_uuid]" + "}\n"])) + (let [response + (http-get-no-ssl + "puppet/v3/catalog/private?environment=production") + expected-content-in-catalog + (str + "\"parameters\":{\"message\":\"" + extension-value + "\"}")] + (is (= 200 (:status response)) + (ks/pprint-to-string response)) + (is (.contains (:body response) expected-content-in-catalog) + (str "Did not find '" expected-content-in-catalog + "' in full response body: " (:body response)))) + (finally + (fs/delete-dir environment-dir)))))))))) diff --git a/test/unit/puppetlabs/services/request_handler/request_handler_core_test.clj b/test/unit/puppetlabs/services/request_handler/request_handler_core_test.clj index b028ecc7a3..7c08a72e97 100644 --- a/test/unit/puppetlabs/services/request_handler/request_handler_core_test.clj +++ b/test/unit/puppetlabs/services/request_handler/request_handler_core_test.clj @@ -2,6 +2,7 @@ (:import (java.io StringReader ByteArrayInputStream)) (:require [puppetlabs.services.request-handler.request-handler-core :as core] [puppetlabs.ssl-utils.core :as ssl-utils] + [puppetlabs.ssl-utils.simple :as ssl-simple] [puppetlabs.puppetserver.certificate-authority :as cert-authority] [puppetlabs.trapperkeeper.testutils.logging :as logutils] [clojure.test :refer :all] @@ -35,15 +36,6 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Tests -(deftest get-cert-common-name-test - (testing (str "expected common name can be extracted from the certificate on " - "a request") - (let [cert (ssl-utils/pem->cert - (str test-resources-dir "/localhost.pem"))] - (is (= "localhost" (core/get-cert-common-name cert))))) - (testing "nil returned for cn when certificate on request is nil" - (is (nil? (core/get-cert-common-name nil))))) - (deftest wrap-params-for-jruby-test (testing "get with no query parameters returns empty params" (let [wrapped-request (core/wrap-params-for-jruby @@ -121,6 +113,39 @@ (is (= (core/unmunge-http-header-name "HTTP_X_CLIENT_DN") "x-client-dn")))) +(deftest cert-info-from-authorization + (testing "authorization section in request" + (let [url-encoded-cert (-> (str test-resources-dir "/localhost.pem") + slurp + ring-codec/url-encode) + cert-from-authorization (:cert (ssl-simple/gen-self-signed-cert + "authorization-client" + 1 + {:keylength 512})) + cert-from-ssl (:cert (ssl-simple/gen-self-signed-cert + "ssl-client" + 1 + {:keylength 512}))] + (doseq [allow-header-cert-info [false true]] + (testing (str "for allow-header-cert-info " allow-header-cert-info) + (let [req (core/as-jruby-request + (puppet-server-config allow-header-cert-info) + {:request-method :GET + :authorization {:name "authorization-client" + :authentic? true + :certificate cert-from-authorization} + :headers {"x-client-verify" "SUCCESS" + "x-client-dn" "CN=x-client" + "x-client-cert" url-encoded-cert} + :ssl-client-cert cert-from-ssl})] + (testing "has proper authenticated value" + (is (true? (get req :authenticated)))) + (testing "has proper name" + (is (= "authorization-client" (get req :client-cert-cn)))) + (testing "has proper cert" + (is (identical? cert-from-authorization + (get req :client-cert)))))))))) + (deftest cert-info-in-headers "In the case where Puppet Server is running under HTTP with an upstream HTTPS terminator, the cert's CN, cert, and authenticated status will be provided as From 1a732ce22818df09f99912570508360b7e2e3ec5 Mon Sep 17 00:00:00 2001 From: Jeremy Barlow Date: Mon, 28 Sep 2015 09:07:52 -0700 Subject: [PATCH 2/4] (SERVER-763) tk-authz doc updates to config and external SSL termination This commit contains some updates to the `configuration.markdown` and `external_ssl_termination.markdown` docs. The updates pertain to new / deprecated settings in Puppet Server per the integration of support for trapperkeeper-authorization. --- documentation/configuration.markdown | 262 +++++++++++++++--- .../external_ssl_termination.markdown | 45 ++- 2 files changed, 267 insertions(+), 40 deletions(-) diff --git a/documentation/configuration.markdown b/documentation/configuration.markdown index 3cde1609a3..c61e83f9a4 100644 --- a/documentation/configuration.markdown +++ b/documentation/configuration.markdown @@ -92,17 +92,45 @@ This file contains the settings for Puppet Server itself. value will give the best performance. * `borrow-timeout`: Optionally, set the timeout when attempting to borrow an instance from the JRuby pool in milliseconds. Defaults to 1200000. + * `use-legacy-auth-conf`: Optionally, set the method to be used for + authorizing access to the HTTP endpoints served by the "master" service. + The applicable endpoints include those listed in the + [Puppet V3 HTTP API](https://docs.puppetlabs.com/puppet/4.2/reference/http_api/http_api_index.html#puppet-v3-http-api). + For a value of `true`, also the default value if not specified, + authorization will be done in core Ruby puppet-agent code via the legacy + [`auth.conf`](https://docs.puppetlabs.com/puppet/4.2/reference/config_file_auth.html) + file. For a value of `false`, authorization will be done by + `trapperkeeper-authorization` via rules specified in an `authorization` + configuration. See the [`auth.conf`](#authconf) section on this page for + more information. Note that the legacy `auth.conf` is now deprecated in + Puppet Server, so it is recommended to use `trapperkeeper-authorization` + instead. * The `profiler` settings configure profiling: * `enabled`: if this is set to `true`, it enables profiling for the Puppet Ruby code. Defaults to `false`. * The `puppet-admin` section configures the Puppet Server's administrative API. (This is a new API, which isn't provided by Rack or WEBrick Puppet masters.) + With the introduction of `trapperkeeper-authorization` for authorizing + requests made to Puppet Server, the settings in this section are now + deprecated. You should consider removing these settings and configuring + the desired authorization behavior through `trapperkeeper-authorization` + instead. See the [`auth.conf`](#authconf) section for more details. * `authorization-required` determines whether a client certificate is required to access the endpoints in this API. If set to - `false`, the client-whitelist will be ignored. Defaults to `true`. + `false`, all requests will be permitted to access this API. If set to + `true`, only the clients whose certnames are included in the + `client-whitelist` setting are allowed access to the admin API. If this + setting is not specified but the `client-whitelist` setting is specified, + the default value for this setting is `true`. * `client-whitelist` contains a list of client certnames that are whitelisted to access the admin API. Any requests made to this endpoint that do not - present a valid client cert mentioned in this list will be denied access. + present a valid client certificate mentioned in this list will be denied + access. + + If neither the `authorization-required` nor the `client-whitelist` setting + is specified, authorization to the admin API endpoints is controlled by + `trapperkeeper-authorization`, through settings specified in the + [`auth.conf`](#authconf) file. ~~~ # configuration for the JRuby interpreters @@ -117,6 +145,7 @@ jruby-puppet: { master-log-dir: /var/log/puppetlabs/puppetserver max-active-instances: 1 max-requests-per-instance: 0 + use-legacy-auth-conf: false } # settings related to HTTP client requests made by Puppet Server @@ -150,67 +179,230 @@ profiler: { enabled: true } -# Settings related to the puppet-admin HTTP API -puppet-admin: { - client-whitelist: [] -} +# Settings related to the puppet-admin HTTP API - deprecated in favor +# of "auth.conf" +# puppet-admin: { +# client-whitelist: [] +# } +~~~ + +### `auth.conf` + +This file contains rules for authorizing access to the HTTP endpoints that +Puppet Server hosts. The file looks something like this: + +~~~ +authorization: { + version: 1 + # allow-header-cert-info: false + rules: [ + { + # Allow nodes to retrieve their own catalog + match-request: { + path: "^/puppet/v3/catalog/([^/]+)$" + type: regex + method: [get, post] + } + allow: "$1" + sort-order: 500 + name: "puppetlabs catalog" + }, +... +~~~ + +Puppet Server uses `trapperkeeper-authorization` for authorization control. +For more detailed information on the format of this file, see +[Configuring the Authorization Service](https://github.com/puppetlabs/trapperkeeper-authorization/blob/master/doc/authorization-config.md). + +If you need to customize the authorization rules for Puppet Server, it is +recommended that you create new rules rather than customizing the default +"puppetlabs" rules which appear in this file. In order for your rules to +"override" any corresponding "puppetlabs" rules, you should use a +`sort-order` for those rules which is in the range of 1 to 399 (inclusive). +Note that default rules from Puppet occupy the range from 400 to 600 (inclusive). + +For example, if you wanted to customize the behavior of the default "catalog" +rule from above to not only allow nodes to retrieve their own catalog but also +allow an "administrative" node to retrieve any node's catalog, you could add +a rule like this: + +~~~ +authorization: { + version: 1 + # allow-header-cert-info: false + rules: [ + { + # Allow nodes to retrieve their own catalog + # and admin nodes to retrieve any catalogs + match-request: { + path: "^/puppet/v3/catalog/([^/]+)$" + type: regex + method: [get, post] + } + allow: ["$1", "myadmin.host.com"] + sort-order: 200 + name: "my catalog" + }, + { + # Allow nodes to retrieve their own catalog + match-request: { + path: "^/puppet/v3/catalog/([^/]+)$" + type: regex + method: [get, post] + } + allow: "$1" + sort-order: 500 + name: "puppetlabs catalog" + }, +... ~~~ +If you want to add a rule but let the default rules from Puppet take +precedence over your new rule, you should use a `sort-order` for the rule +which is in the range from 601 to 998 (inclusive). + +Note that, for backward compatibility, the values of other configuration +settings control the specific endpoints for which `trapperkeeper-authorization` +is usedr: + +* [`jruby-puppet.use-legacy-auth-conf`](#puppetserverconf) - Controls the + method to be used for authorizing access to the HTTP endpoints served by the + "master" service. The applicable endpoints include those listed in the + [Puppet V3 HTTP API](https://docs.puppetlabs.com/puppet/4.2/reference/http_api/http_api_index.html#puppet-v3-http-api). + + For a value of `true`, also the default value if not specified, authorization + will be done in core Ruby puppet-agent code via the legacy + [`auth.conf`](https://docs.puppetlabs.com/puppet/4.2/reference/config_file_auth.html) + file. For a value of `false`, authorization will be done through via + `trapperkeeper-authorization`. + +* `puppet-admin.authorization-required` and `puppet-admin.client-whitelist` - + If either of these settings is present in the configuration, requests made to + Puppet Server's administrative API will be performed per the values for these + settings. See the [`puppetserver.conf/puppet-admin`](#puppetserverconf) + section for more information on these settings. If neither the + `puppet-admin.authorization-required` nor the `puppet-admin.client-whitelist` + setting is specified, requests to Puppet Server's administrative API will be + done via `trapperkeeper-authorization`. + + * `certificate-authority.certificate-status.authorization-required` and + `certificate-authority.certificate-status.client-whitelist` - + If either of these settings is present in the configuration, requests made + to Puppet Server's + [Certificate Status](https://github.com/puppetlabs/puppet/blob/master/api/docs/http_certificate_status.md) + API will be performed per the values for these settings. See the + [`ca.conf`](#caconf) section for more information on these settings. If + neither the `certificate-authority.certificate-status.authorization-required` + nor the `certificate-authority.certificate-status.client-whitelist` setting is + specified, requests to Puppet Server's administrative API will be done via + `trapperkeeper-authorization`. + +Support for the use of the legacy `auth.conf` for the "master" endpoints and +for the client whitelists for the Puppet admin and certificate status endpoints +is deprecated. You should consider configuring the above settings such that +only `trapperkeeper-authorization` is used for authorizing requests. + ### `master.conf` -This file contains settings for Puppet master features, such as node identification and authorization. +This file contains settings for Puppet master features, such as node +identification and authorization. The only setting that this file supports is +`allow-header-cert-info`. That setting is now deprecated in favor of the +`authorization.allow-header-cert-info` setting in the `auth.conf` file that +`trapperkeeper-authorization` uses. For more information on the `auth.conf` +file in general, see the [`auth.conf`](#authconf) section. For more information +on the `authorization.allow-header-cert-info` setting, see the +[`Configuring the Authorization Service`](https://github.com/puppetlabs/trapperkeeper-authorization/blob/master/doc/authorization-config.md#allow-header-cert-info) +page. -In a default installation, this file doesn't exist. You'll need to create it if you want to set non-default values for these settings. +In a default installation, this file doesn't exist. * `allow-header-cert-info` determines whether Puppet Server should use authorization info from the `X-Client-Verify`, `X-Client-CN`, and `X-Client-Cert` HTTP headers. Defaults to `false`. This setting is used to enable [external SSL termination.](./external_ssl_termination.markdown) If enabled, Puppet Server will ignore any actual certificate presented to the Jetty webserver, and will rely completely on header data to authorize requests. This is very dangerous unless you've secured your network to prevent any untrusted access to Puppet Server. - You can change Puppet's `ssl_client_verify_header` setting to use another header name instead of `X-Client-Verify`; the `ssl_client_header` setting can rename `X-Client-CN`. The `X-Client-Cert` header can't be renamed. + When the `master.allow-header-cert-info` setting is being used, you can + change Puppet's `ssl_client_verify_header` setting to use another header + name instead of `X-Client-Verify`; the `ssl_client_header` setting can + rename `X-Client-CN`. The `X-Client-Cert` header can't be renamed. When + the `authorization.allow-header-cert-info` setting is being used, however, + none of the `X-Client` headers can be renamed; identity must be specified + through the `X-Client-Verify`, `X-Client-CN`, and `X-Client-Cert` headers. - Note that this setting only applies to HTTP endpoints served by the "master" - service. The applicable endpoints include those listed in the + Note that the `master.allow-header-cert-info` setting only applies to HTTP + endpoints served by the "master" service. The applicable endpoints include + those listed in the [Puppet V3 HTTP API](https://docs.puppetlabs.com/puppet/4.2/reference/http_api/http_api_index.html#puppet-v3-http-api). - Note that this setting does not apply to the endpoints listed in the + The `master.allow-header-cert-info` setting does not apply to the endpoints + listed in the [CA V1 HTTP API](https://docs.puppetlabs.com/puppet/4.2/reference/http_api/http_api_index.html#ca-v1-http-api) or to any of the [Puppet Admin API](#puppetserverconf) endpoints. + The `authorization.allow-header-cert-info` setting, however, applies to all + HTTP endpoints that Puppet Server handles - including ones served by the + "master" service and the CA and Puppet Admin APIs. + + If `trapperkeeper-authorization` is enabled for authorizing requests to the + "master" HTTP endpoints - via the + [`jruby-puppet.use-legacy-auth-conf`](#puppetserverconf) setting being set + to `false` - the value of the `authorization.allow-header-cert-info` + setting controls how the user's identity is derived for authorization + purposes. In this case, the value of the `master.allow-header-cert-info` + setting would be ignored. + ~~~ -master: { - # allow-header-cert-info: false -} +# Deprecated in favor of `authorization.allow-header-cert-info` in "auth.conf" +# master: { +# allow-header-cert-info: false +# } ~~~ ### `ca.conf` -This file contains settings for the Certificate Authority service. - -* `certificate-status` contains settings for the certificate_status HTTP endpoint. -This endpoint allows certs to be signed, revoked, and deleted via HTTP requests. -This provides full control over Puppet's security, and access should almost -always be heavily restricted. Puppet Enterprise uses this endpoint to provide -a cert signing interface in the PE console. For full documentation, see the -[Certificate Status](https://github.com/puppetlabs/puppet/blob/master/api/docs/http_certificate_status.md) page. +This file contains settings for the Certificate Authority service. The only +settings that this file supports are `authorization-required` and +`client-whitelist`. These settings are now deprecated in favor of the +authorization settings in the `auth.conf` file that +`trapperkeeper-authorization` uses. For more information, see the +[`auth.conf`](#authconf) section. Since these settings are now deprecated, +the `ca.conf` file no longer appears in a Puppet Server package. + +* `certificate-status` contains settings for the `certificate_status` and + `certificate_statuses` HTTP endpoints. These endpoints allow certs to be + signed, revoked, and deleted via HTTP requests. This provides full control + over Puppet's security, and access should almost always be heavily restricted. + Puppet Enterprise uses these endpoints to provide a cert signing interface in + the PE console. For full documentation, see the + [Certificate Status](https://github.com/puppetlabs/puppet/blob/master/api/docs/http_certificate_status.md) page. * `authorization-required` determines whether a client certificate - is required to access the certificate status endpoints. If set to 'false' the - whitelist will be ignored. Defaults to `true`. + is required to access the certificate status(es) endpoints. If set to + `false`, all requests will be permitted to access this API. If set to + `true`, only the clients whose certnames are included in the + `client-whitelist` setting are allowed access to the admin API. If this + setting is not specified but the `client-whitelist` setting is specified, + the default value for this setting is `true`. * `client-whitelist` contains a list of client certnames that are whitelisted - to access the certificate_status endpoint. Any requests made to this - endpoint that do not present a valid client cert mentioned in this list will - be denied access. + to access the certificate_status(es) endpoints. Any requests made to this + endpoint that do not present a valid client certificate mentioned in this + list will be denied access. + + If neither the `authorization-required` nor the `client-whitelist` setting + is specified, authorization to the certificate status(es) endpoints is + controlled by `trapperkeeper-authorization`, through settings specified in + the [`auth.conf`](#authconf) file. ~~~ -# CA-related settings -certificate-authority: { - certificate-status: { - authorization-required: true - client-whitelist: [] - } -} +# CA-related settings - deprecated in favor of "auth.conf" +# certificate-authority: { +# certificate-status: { +# authorization-required: true +# client-whitelist: [] +# } +# } ~~~ + ## Logging All of Puppet Server's logging is routed through the JVM [Logback](http://logback.qos.ch/) library. By default, it logs to `/var/log/puppetserver/puppetserver.log` (open source releases) or `/var/log/pe-puppetserver/puppetserver.log` (Puppet Enterprise). The default log level is 'INFO'. By default, Puppet Server sends nothing to syslog. diff --git a/documentation/external_ssl_termination.markdown b/documentation/external_ssl_termination.markdown index dbe8e22627..98aa31ed8d 100644 --- a/documentation/external_ssl_termination.markdown +++ b/documentation/external_ssl_termination.markdown @@ -15,11 +15,33 @@ You'll need to turn off SSL and have Puppet Server use the HTTP protocol instead When using external SSL termination, Puppet Server expects to receive client certificate information via some HTTP headers. -By default, reading this data from headers is disabled. To allow Puppet Server to recognize it, edit (or create) `config.d/master.conf` and add `allow-header-cert-info: true` to the `master` config section. See [Puppet Server Configuration](./configuration.markdown) for more information on the `master.conf` file. +By default, reading this data from headers is disabled. To allow Puppet Server +to recognize it, you'll need to set the `allow-header-cert-info` setting to `true`. + +Prior to the inclusion of `trapperkeeper-authorization` and an `auth.conf` file +specific to Puppet Server, you would need to edit (or create) `conf.d/master.conf` +and add `allow-header-cert-info: true` to the `master` config section. See +[Puppet Server Configuration](./configuration.markdown) for more information on +the `master.conf` file. This approach, however, is now deprecated. + +Instead, it is preferred to enable `trapperkeeper-authorization` and +set the `allow-header-cert-info` setting via the `authorization` config +section. This involves the following steps: + +* Migrate any custom authorization rule definitions that you may have made to core Puppet's + `/etc/puppetlabs/puppet/auth.conf` file over to the + `/etc/puppetlabs/puppetserver/conf.d/auth.conf` file. +* Set the `jruby-puppet.use-legacy-auth-conf` setting in the + `conf.d/puppetserver.conf` file to `false`. +* Add `allow-header-cert-info: true` to the `authorization` config section in + the `/etc/puppetlabs/puppetserver/conf.d/auth.conf` file. + +See [Puppet Server Configuration](./configuration.markdown) for more information +on the `puppetserver.conf` and `auth.conf` files. > **WARNING**: Setting `allow-header-cert-info` to 'true' puts Puppet Server in an incredibly vulnerable state. Take extra caution to ensure it is **absolutely not reachable** by an untrusted network. > -> With `allow-header-cert-info` set to 'true', core Ruby Puppet application code will use only the client HTTP header values---not an SSL-layer client certificate---to determine the client subject name, authentication status, and trusted facts. This is true even if the web server is hosting an HTTPS connection. This applies to validation of the client via rules in the [auth.conf](https://docs.puppetlabs.com/guides/rest_auth_conf.html) file and any [trusted facts][trusted] extracted from certificate extensions. +> With `allow-header-cert-info` set to 'true', authorization code will use only the client HTTP header values---not an SSL-layer client certificate---to determine the client subject name, authentication status, and trusted facts. This is true even if the web server is hosting an HTTPS connection. This applies to validation of the client via rules in the [auth.conf](https://docs.puppetlabs.com/guides/rest_auth_conf.html) file and any [trusted facts][trusted] extracted from certificate extensions. > > If the `client-auth` setting in the `webserver` config block is set to `need` or `want`, the Jetty web server will still validate the client certificate against a certificate authority store, but it will only verify the SSL-layer client certificate---not a certificate in an `X-Client-Cert` header. @@ -38,19 +60,32 @@ The headers you'll need to set are `X-Client-Verify`, `X-Client-DN`, and `X-Clie Mandatory. Must be either `SUCCESS` if the certificate was validated, or something else if not. (The convention seems to be to use `NONE` for when a certificate wasn't presented, and `FAILED:reason` for other validation failures.) Puppet Server uses this to authorize requests; only requests with a value of `SUCCESS` will be considered authenticated. -You can change this header name with [the `ssl_client_verify_header` setting.](https://docs.puppetlabs.com/references/latest/configuration.html#sslclientverifyheader) +When using the `master.allow-header-cert-info: true` setting, you can change this header name with [the `ssl_client_verify_header` setting.](https://docs.puppetlabs.com/references/latest/configuration.html#sslclientverifyheader) This setting (and its twin, `ssl_client_header`) is a bit odd: its value should be the result of transforming the desired HTTP header name into a CGI-style environment variable name. That is, to change the HTTP header to `X-SSL-Client-Verify`, you would set the setting to `HTTP_X_SSL_CLIENT_VERIFY`. (Add `HTTP_` to the front, change hyphens to underscores, and uppercase everything.) (Puppet Server will eventually UN-munge the CGI variable name to get a valid HTTP header name, and use that name to interact directly with an HTTP request. This is a legacy quirk to ensure that the setting works the same for both Puppet Server and a Rack Puppet master; note that Rack actually does use CGI environment variables.) +Note that if you are using the `authorization.allow-header-cert-info: true` +setting, the name of the client verify header in the request must be +`X-Client-Verify`; the `ssl_client_verify_header` setting in the `puppet.conf` +file has no effect on how the client verify header is processed. + ### `X-Client-DN` Mandatory. Must be the [Subject DN][] of the agent's certificate, if a certificate was presented. Puppet Server uses this to authorize requests. -> **Note:** Currently, the DN must be in RFC-2253 format (comma-delimited). Due to a bug ([SERVER-213](https://tickets.puppetlabs.com/browse/SERVER-213)), Puppet Server can't decode OpenSSL-style DNs (slash-delimited). Note that Apache's `mod_ssl` `SSL_CLIENT_S_DN` variable uses OpenSSL-style DNs. +> **Note:** Currently, when using `master.allow-header-cert-info: true`, the DN must be in RFC-2253 format (comma-delimited). Due to a bug ([SERVER-213](https://tickets.puppetlabs.com/browse/SERVER-213)), Puppet Server can't decode OpenSSL-style DNs (slash-delimited). Note that Apache's `mod_ssl` `SSL_CLIENT_S_DN` variable uses OpenSSL-style DNs. Note + that the bug does not apply when `authorization.allow-header-cert-info` is set + to true. `trapperkeeper-authorization` supports decoding both the RFC-2253 + and OpenSSL "slash-delimited" DN formats. + +When using the `master.allow-header-cert-info: true` setting, you can change this header name with [the `ssl_client_header` setting.](https://docs.puppetlabs.com/references/latest/configuration.html#sslclientheader) See the note above for more info about this setting's expected values. -You can change this header name with [the `ssl_client_header` setting.](https://docs.puppetlabs.com/references/latest/configuration.html#sslclientheader) See the note above for more info about this setting's expected values. +Note that if you are using the `authorization.allow-header-cert-info: true` +setting, the name of the client DN header in the request must be +`X-Client-DN`; the `ssl_client_header` setting in the `puppet.conf` file has no +effect on how the client DN header is processed. [subject dn]: https://docs.puppetlabs.com/background/ssl/cert_anatomy.html#the-subject-dn-cn-certname-etc From d12449b6006b77b94a4eb9cff8be0def2099f2d6 Mon Sep 17 00:00:00 2001 From: Jeremy Barlow Date: Tue, 29 Sep 2015 11:05:53 -0700 Subject: [PATCH 3/4] (SERVER-763) Update tk-authz dependency to 0.1.4 and use tk-auth Ring helpers This commit updates Puppet Server's trapperkeeper-authorization dependency to 0.1.4 and updates some code to use some Ring helpers for extracting authenticated client info. --- project.clj | 2 +- .../services/request_handler/request_handler_core.clj | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/project.clj b/project.clj index 71f8c44dd0..dc9f92f047 100644 --- a/project.clj +++ b/project.clj @@ -21,7 +21,7 @@ ;; end version conflict resolution dependencies [puppetlabs/trapperkeeper ~tk-version] - [puppetlabs/trapperkeeper-authorization "0.1.3-SNAPSHOT"] + [puppetlabs/trapperkeeper-authorization "0.1.4"] [puppetlabs/kitchensink ~ks-version] [puppetlabs/ssl-utils "0.8.1"] [puppetlabs/dujour-version-check "0.1.2" :exclusions [org.clojure/tools.logging]] diff --git a/src/clj/puppetlabs/services/request_handler/request_handler_core.clj b/src/clj/puppetlabs/services/request_handler/request_handler_core.clj index cdbf102fb8..38ca9693a4 100644 --- a/src/clj/puppetlabs/services/request_handler/request_handler_core.clj +++ b/src/clj/puppetlabs/services/request_handler/request_handler_core.clj @@ -4,11 +4,11 @@ (com.puppetlabs.puppetserver JRubyPuppetResponse)) (:require [clojure.tools.logging :as log] [clojure.string :as string] - [puppetlabs.kitchensink.core :as ks] [puppetlabs.ssl-utils.core :as ssl-utils] [ring.util.codec :as ring-codec] [ring.util.response :as ring-response] [slingshot.slingshot :as sling] + [puppetlabs.trapperkeeper.authorization.ring :as ring-auth] [puppetlabs.puppetserver.ring.middleware.params :as pl-ring-params] [puppetlabs.services.jruby.jruby-puppet-service :as jruby])) @@ -210,9 +210,9 @@ * request - Ring request containing client data" [config request] (if-let [authorization (:authorization request)] - {:client-cert (:certificate authorization) - :client-cert-cn (:name authorization) - :authenticated (true? (:authentic? authorization))} + {:client-cert (ring-auth/authorized-certificate request) + :client-cert-cn (ring-auth/authorized-name request) + :authenticated (true? (ring-auth/authorized-authentic? request))} (let [headers (:headers request) header-dn-name (:ssl-client-header config) header-dn-val (get headers header-dn-name) From 9f796994a1f06e949e1573bda5f053b5d426bab9 Mon Sep 17 00:00:00 2001 From: Jeremy Barlow Date: Wed, 30 Sep 2015 08:33:32 -0700 Subject: [PATCH 4/4] (SERVER-763) Fixed a couple of typos and inlined a when in a doseq This commit addresses some feedback on the associated PR: * Fixes a couple of typos * Pulls a when conditional up from a child form into its parent doseq --- documentation/configuration.markdown | 4 ++-- .../request_handler/request_handler_core.clj | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/documentation/configuration.markdown b/documentation/configuration.markdown index c61e83f9a4..4e44ee4e4b 100644 --- a/documentation/configuration.markdown +++ b/documentation/configuration.markdown @@ -263,7 +263,7 @@ which is in the range from 601 to 998 (inclusive). Note that, for backward compatibility, the values of other configuration settings control the specific endpoints for which `trapperkeeper-authorization` -is usedr: +is used: * [`jruby-puppet.use-legacy-auth-conf`](#puppetserverconf) - Controls the method to be used for authorizing access to the HTTP endpoints served by the @@ -273,7 +273,7 @@ is usedr: For a value of `true`, also the default value if not specified, authorization will be done in core Ruby puppet-agent code via the legacy [`auth.conf`](https://docs.puppetlabs.com/puppet/4.2/reference/config_file_auth.html) - file. For a value of `false`, authorization will be done through via + file. For a value of `false`, authorization will be done via `trapperkeeper-authorization`. * `puppet-admin.authorization-required` and `puppet-admin.client-whitelist` - diff --git a/src/clj/puppetlabs/services/request_handler/request_handler_core.clj b/src/clj/puppetlabs/services/request_handler/request_handler_core.clj index 38ca9693a4..bd82e34166 100644 --- a/src/clj/puppetlabs/services/request_handler/request_handler_core.clj +++ b/src/clj/puppetlabs/services/request_handler/request_handler_core.clj @@ -229,12 +229,12 @@ (doseq [[header-name header-val] {header-dn-name header-dn-val header-auth-name header-auth-val - header-client-cert-name header-cert-val}] - (if header-val - (log/warn "The HTTP header" header-name "was specified," - "but the master config option allow-header-cert-info" - "was either not set, or was set to false." - "This header will be ignored."))) + header-client-cert-name header-cert-val} + :when header-val] + (log/warn "The HTTP header" header-name "was specified," + "but the master config option allow-header-cert-info" + "was either not set, or was set to false." + "This header will be ignored.")) (let [ssl-cert (:ssl-client-cert request)] (-> (ssl-auth-info ssl-cert) (assoc :client-cert ssl-cert))))))))