124 lines
4.0 KiB
Clojure
124 lines
4.0 KiB
Clojure
(ns ldap-nginx-plumber.main
|
|
(:require [clojure.spec.alpha :as spec]
|
|
[clojure.string :as str]
|
|
[org.httpkit.server :as http]
|
|
[clj-ldap.client :as ldap]
|
|
[babashka.cli :as cli]
|
|
[clojure.pprint :refer [pprint]])
|
|
(:import [java.util Base64]
|
|
[java.nio.charset StandardCharsets])
|
|
(:gen-class))
|
|
|
|
(defn- port? [x]
|
|
(and (nat-int? x)
|
|
(<= 0 x 65535)))
|
|
|
|
(def cli-spec
|
|
{:spec
|
|
{:port {:coerce :int
|
|
:desc "Port to listen on"
|
|
:alias :p
|
|
:validate port?
|
|
:require true}
|
|
:base-dn {:coerce :string
|
|
:desc "Base DN for LDAP searches"
|
|
:require true}
|
|
:ldap-host {:coerce :string
|
|
:require true}
|
|
:ldap-port {:coerce :int
|
|
:validate port?
|
|
:default 389}
|
|
:bind-dn {:coerce :string
|
|
:require true}
|
|
:bind-password {:coerce :string
|
|
:require true}}})
|
|
|
|
(def ^:dynamic *opts*)
|
|
|
|
(defonce ldap-connection-pool
|
|
(atom nil))
|
|
|
|
(defn- base64->utf8 [base64]
|
|
(try (-> (.decode (Base64/getDecoder) base64)
|
|
(String. StandardCharsets/UTF_8))
|
|
(catch java.lang.IllegalArgumentException _))
|
|
nil)
|
|
|
|
(defn- response [status & {:as more}]
|
|
(apply merge
|
|
{:status status
|
|
:headers {"Content-Type" "text/plain"}}
|
|
more))
|
|
|
|
(defn- consultant-app [req]
|
|
(printf "\n%s received request:\n" (.toString (java.util.Date.)))
|
|
(pprint req)
|
|
(try
|
|
(if-let [[_ user pass] (some->> (get-in [req :headers] "Authorization")
|
|
str/lower-case
|
|
(re-matches #"basic ([a-zA-Z0-9+/=]+)")
|
|
second
|
|
base64->utf8
|
|
(re-matches #"([^:]+):(.*)"))]
|
|
(response 200 :body "yay!")
|
|
(response
|
|
401
|
|
:headers {"WWW-Authenticate" "Basic realm=\"Restricted\""
|
|
"Cache-Control" "no-cache"}))
|
|
(catch Exception e
|
|
(println "`consultant-app` threw an error:")
|
|
(prn e)
|
|
(response 500))))
|
|
|
|
(defonce consultant-server (atom nil))
|
|
|
|
(defn- stop-consultant! []
|
|
(when @consultant-server
|
|
;; Graceful shutdown: wait 100ms for existing requests to be finished.
|
|
;; :timeout is optional, when no timeout, stop immediately.
|
|
(http/server-stop! @consultant-server {:timeout 100})
|
|
(reset! consultant-server nil)))
|
|
|
|
(defn- start-consultant [& {:keys [port] :as opts}]
|
|
(binding [*opts* opts]
|
|
(if @consultant-server
|
|
(throw (ex-info "Refusing to start the server whilst a previous lingers" {}))
|
|
(reset! consultant-server
|
|
(http/run-server #'consultant-app
|
|
{:port port
|
|
:legacy-return-value? false})))))
|
|
|
|
(defn- connect-to-ldap
|
|
[& {:keys [ldap-host ldap-port bind-dn bind-password]}]
|
|
(reset! ldap-connection-pool
|
|
(or @ldap-connection-pool
|
|
(ldap/connect {:host {:address ldap-host
|
|
:port ldap-port}
|
|
:max-connections 8
|
|
:bind-dn bind-dn
|
|
:password bind-password}))))
|
|
|
|
(defn- main* [& opts]
|
|
(and (apply connect-to-ldap opts)
|
|
(apply start-consultant opts)))
|
|
|
|
(comment
|
|
(let [ask (let [base-dn "dc=identify,dc=deertopia,dc=net"]
|
|
(consultant-app {:port 8080 :ldap-host "192.168.68.79" :ldap-port 3890
|
|
:base-dn base-dn
|
|
:bind-dn (str "uid=nginx-bind-user,ou=people," base-dn)
|
|
:bind-password "secret123"}))]
|
|
(ask {})))
|
|
|
|
(comment ; Start on :8080
|
|
(let [base-dn "dc=identify,dc=deertopia,dc=net"]
|
|
(main* :port 8080 :ldap-host "192.168.68.79" :ldap-port 3890 :base-dn base-dn
|
|
:bind-dn (str "uid=nginx-bind-user,ou=people," base-dn)
|
|
:bind-password "secret123")))
|
|
|
|
(comment ; Shutdown
|
|
(stop-consultant!))
|
|
|
|
(defn -main [& args]
|
|
(main* (cli/parse-opts args cli-spec)))
|