Last active
February 9, 2019 07:50
-
-
Save candera/11310395 to your computer and use it in GitHub Desktop.
Revisions
-
candera revised this gist
May 12, 2014 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,5 +1,7 @@ * Embedding an SSH-accessible REPL in a Clojure process *N.B.* This is now [[https://github.com/mtnygard/ssh-repl][a library]], thanks to the efforts of the wonderful [[https://twitter.com/mtnygard][@mtnygard]]. And [[https://github.com/mtnygard/ssh-repl/blob/master/README.md][the README]] does a good job of making clear just how terrible an idea it is to actually do this. :) As any Clojurist knows, the REPL is an incredibly handy development tool. It can also be useful as a tool for debugging running programs. Of course, this raises the question of how to limit access to the -
candera revised this gist
Apr 26, 2014 . 1 changed file with 9 additions and 35 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -29,7 +29,7 @@ On to the code! We need to start by pulling in a few namespaces and classes: #+begin_src clojure :results silent (import '[org.apache.sshd SshServer] '[org.apache.sshd.server Command PasswordAuthenticator PublickeyAuthenticator] '[org.apache.sshd.common Factory] @@ -38,40 +38,28 @@ We need to start by pulling in a few namespaces and classes: (require '[clojure.java.io :as io]) #+end_src Then we can create a server object, using the defaults for things like hash algorithms and other ssh-y stuff: #+begin_src clojure :results silent (def sshd (SshServer/setUpDefaultServer)) #+end_src Tell the server what port to run on when it starts: #+begin_src clojure :results silent (.setPort sshd 2022) #+end_src Give a path where we can save our host keys, and generate them if they don't exist. This way, if the server gets restarted, its identity will remain the same. #+begin_src clojure :results silent (.setKeyPairProvider sshd (SimpleGeneratorHostKeyProvider. "hostkey.ser")) #+end_src Here's where the real magic is - we specify a "shell factory", which knows how to create an instance of =Command= that will start a REPL on a separate thread. Because this is Java, there's a whole bunch of @@ -80,7 +68,7 @@ state to keep track of, but we can do that in an atom. Note the use of and =*err*= despite the fact that we're running the REPL on its own thread. #+begin_src clojure :results silent (.setShellFactory sshd (reify Factory (create [this] @@ -119,32 +107,24 @@ thread. (future-call (bound-fn* clojure.main/repl)))))))))) #+end_src We need to configure security, or the sshd server won't start. For now, let's use a simple authenticator that just checks that the password is "foo": #+begin_src clojure :results silent (.setPasswordAuthenticator sshd (reify PasswordAuthenticator (authenticate [this username password session] (= password "foo")))) #+end_src Now we simply start the server: #+begin_src clojure :results silent (.start sshd) #+end_src At this point, we can do =ssh -T -p 2022 localhost=, provide the hardcoded password "foo" and we have a REPL! (Note: you will need to type =~.= to disconnect.) The =-T= option prevents the allocation of @@ -159,7 +139,7 @@ also provides support for public key authentication. It requires a bit of code to go from a SSH public key file to an instance of =java.security.PublicKey=, but it's not too bad: #+begin_src clojure :results silent (import '[java.math BigInteger] '[java.security KeyFactory PublicKey] '[java.security.spec DSAPublicKeySpec RSAPublicKeySpec] @@ -208,12 +188,9 @@ of =java.security.PublicKey=, but it's not too bad: :type type}))))) #+end_src But now that we have it, we can easily use it. #+begin_src clojure :results silent (let [allowed-key (read-ssh-key "/Users/candera/.ssh/id_rsa.pub")] (.setPublickeyAuthenticator sshd (reify PublickeyAuthenticator @@ -224,9 +201,6 @@ But now that we have it, we can easily use it. (= key allowed-key)))))) #+end_src And then we can connect with =ssh -T -p 2022 repl@localhost=, without using a password! Obviously, you will have to change the path to the public key to get this to work for you. -
candera revised this gist
Apr 26, 2014 . 1 changed file with 4 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -154,9 +154,10 @@ you see what you type. * Authentication via public key Passwords - especially passwords embedded in our programs - are less than desirable. Which is why it's great that SSHD also provides support for public key authentication. It requires a bit of code to go from a SSH public key file to an instance of =java.security.PublicKey=, but it's not too bad: #+begin_src clojure (import '[java.math BigInteger] -
candera created this gist
Apr 26, 2014 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,231 @@ * Embedding an SSH-accessible REPL in a Clojure process As any Clojurist knows, the REPL is an incredibly handy development tool. It can also be useful as a tool for debugging running programs. Of course, this raises the question of how to limit access to the REPL to authorized parties. With the [[http://mina.apache.org/sshd-project/index.html][Apache SSHD]] library, you can embed an SSH server in any JVM process. It takes only a little code to hook this up to a REPL, and to limit access either by public key or password. Start by including a reference to the Apache SSHD library. If you're using Leiningen, your dependency will look something like this: #+begin_src clojure [org.apache.sshd/sshd-core "0.11.0"] #+end_src The SSHD project is fairly active, so be sure to check for the latest version; version 0.11.0 was current as of April 2014. You can also use [[https://github.com/rkneufeld/lein-try][lein try]] to start an experimental REPL with the latest available SSHD library, like this: #+begin_src sh lein try org.apache.sshd/sshd-core org.slf4j/slf4j-simple #+end_src On to the code! We need to start by pulling in a few namespaces and classes: #+begin_src clojure (import '[org.apache.sshd SshServer] '[org.apache.sshd.server Command PasswordAuthenticator PublickeyAuthenticator] '[org.apache.sshd.common Factory] '[org.apache.sshd.server.keyprovider SimpleGeneratorHostKeyProvider]) (require '[clojure.java.io :as io]) #+end_src #+RESULTS: : nil Then we can create a server object, using the defaults for things like hash algorithms and other ssh-y stuff: #+begin_src clojure (def sshd (SshServer/setUpDefaultServer)) #+end_src #+RESULTS: : #'user/sshd Tell the server what port to run on when it starts: #+begin_src clojure (.setPort sshd 2022) #+end_src #+RESULTS: : nil Give a path where we can save our host keys, and generate them if they don't exist. This way, if the server gets restarted, its identity will remain the same. #+begin_src clojure (.setKeyPairProvider sshd (SimpleGeneratorHostKeyProvider. "hostkey.ser")) #+end_src #+RESULTS: : nil Here's where the real magic is - we specify a "shell factory", which knows how to create an instance of =Command= that will start a REPL on a separate thread. Because this is Java, there's a whole bunch of state to keep track of, but we can do that in an atom. Note the use of =bound-fn*=, which lets us preserve our bindings of =*in*=, =*out*=, and =*err*= despite the fact that we're running the REPL on its own thread. #+begin_src clojure (.setShellFactory sshd (reify Factory (create [this] (let [state (atom {})] (reify Command (destroy [this] (when-let [fut (:future @state)] (future-cancel fut))) (setErrorStream [this err] (.setNoDelay err true) (swap! state assoc-in [:streams :err] err)) (setExitCallback [this cb] (swap! state assoc :exit-callback cb)) (setInputStream [this in] (swap! state assoc-in [:streams :in] in)) (setOutputStream [this out] (.setNoDelay out true) (swap! state assoc-in [:streams :out] out)) (start [this env] (binding [*in* (-> @state :streams :in io/reader clojure.lang.LineNumberingPushbackReader.) *out* (-> @state :streams :out io/writer) *err* (-> @state :streams :err io/writer)] (swap! state assoc :future (future-call (bound-fn* clojure.main/repl)))))))))) #+end_src #+RESULTS: : nil We need to configure security, or the sshd server won't start. For now, let's use a simple authenticator that just checks that the password is "foo": #+begin_src clojure (.setPasswordAuthenticator sshd (reify PasswordAuthenticator (authenticate [this username password session] (= password "foo")))) #+end_src #+RESULTS: : nil Now we simply start the server: #+begin_src clojure (.start sshd) #+end_src #+RESULTS: : nil At this point, we can do =ssh -T -p 2022 localhost=, provide the hardcoded password "foo" and we have a REPL! (Note: you will need to type =~.= to disconnect.) The =-T= option prevents the allocation of a pseudo-tty. I have no idea what that means, other than that it lets you see what you type. * Authentication via public key Passwords - especially passwords embedded in our programs - are less than desirable. SSHD also provides support for public key authentication, though. It requires a bit of code to go from a SSH public key file to an instance of =java.security.PublicKey= though. #+begin_src clojure (import '[java.math BigInteger] '[java.security KeyFactory PublicKey] '[java.security.spec DSAPublicKeySpec RSAPublicKeySpec] '[java.util Scanner]) (defn decode-string "Decodes a string from a ByteBuffer." [bb] (let [len (.getInt bb) buf (byte-array len)] (.get bb buf) (String. buf))) (defn decode-bigint "Decodes a java.math.BigInteger from a ByteBuffer." [bb] (let [len (.getInt bb) buf (byte-array len)] (.get bb buf) (BigInteger. buf))) (defn read-ssh-key "Reads in the SSH key at `path`, returning an instance of `java.security.PublicKey`." [path] (let [contents (slurp path) parts (clojure.string/split contents #" ") bytes (->> parts (filter #(.startsWith % "AAAA")) first javax.xml.bind.DatatypeConverter/parseBase64Binary) bb (-> bytes alength java.nio.ByteBuffer/allocate (.put bytes) .flip)] (case (decode-string bb) "ssh-rsa" (.generatePublic (KeyFactory/getInstance "RSA") (let [[e m] (repeatedly 2 #(decode-bigint bb))] (RSAPublicKeySpec. m e))) "ssh-dss" (.generatePublic (KeyFactory/getInstance "DSA") (let [[p q g y] (repeatedly 4 #(decode-bigint bb))] (DSAPublicKeySpec. y p q g))) (throw (ex-info "Unknown key type" {:reason ::unknown-key-type :type type}))))) #+end_src #+RESULTS: : #'user/read-ssh-key But now that we have it, we can easily use it. #+begin_src clojure (let [allowed-key (read-ssh-key "/Users/candera/.ssh/id_rsa.pub")] (.setPublickeyAuthenticator sshd (reify PublickeyAuthenticator (authenticate [this username key session] (clojure.tools.logging/info :username username :key key) (and (= username "repl") (= key allowed-key)))))) #+end_src #+RESULTS: : nil And then we can connect with =ssh -T -p 2022 repl@localhost=, without using a password! Obviously, you will have to change the path to the public key to get this to work for you.