Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for serving on Unix domain socket #478

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

hsartoris-bard
Copy link

Summary

These changes take advantage of jetty-unixsocket to allow serving on a socket, as in the following example:

(run-jetty handler {:http? false, :socket "/run/someapp.sock"})
## hostname is still required for making requests
curl --unix-socket /run/someapp.sock http://my-expected-hostname/url-path

Rationale

Unix sockets are simpler to implement access controls to than TCP sockets, as you can simply use file permissions. They are supported by common reverse proxies such as Apache and NGINX, and are thus reasonable for exposing a service through a proxy that may be handling authentication, without making that service available to all users on the host machine.

Possible enhancements:

  1. Options for file ownership and access controls
  2. A secure-by-default approach might see :http? default false when :socket is set, unless :host or :port are explicitly configured.
  3. To avoid the additional dependency, socket support could be conditional on manually including the jetty-unixsocket library. It is not an overwhelmingly heavy dependency, though.

Note: it has proved nontrivial to get a test working, as clj-http does not support sockets, and the Clojure implementations that I've found do not seem to work as intended out of the box. I have verified that this implementation is operational manually, and I am willing to work through the process of getting a test working as long as you're interested in incorporating this functionality.

@weavejester
Copy link
Member

I think this is reasonable in theory. Perhaps :unix-socket instead of :socket as an option, so it's not confused with other types of sockets. Also, have you tested to ensure that the inclusion of this library doesn't cause issues when using it under operating systems that don't have sockets (e.g. Windows)? I'd also suggest not automatically deleting the socket file on startup - that feels potentially dangerous. Instead, raise an error.

@hsartoris-bard
Copy link
Author

I was under the impression that there was now unix socket support on Windows, but indeed it appears not to work.

Changes

  • check for windows
  • rename :socket to :unix-socket
  • make :http? default false when :unix-socket is set, except when :host or :port are set
  • throw error on extant file instead of deleting
  • tests for basic functionality and errant attempts to use extant file

Let me know if there's anything else you'd like to see!

@hsartoris-bard
Copy link
Author

Whoops, wasn't my intention to check in the *warn-on-reflection* line - I can remove that, or resolve the singular reflection warning.

Copy link
Member

@weavejester weavejester left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your hard work on this, and apologies for taking a while to get around to reviewing it.

[org.eclipse.jetty.util BlockingArrayQueue]
[org.eclipse.jetty.util.thread ThreadPool QueuedThreadPool]
[org.eclipse.jetty.util.ssl SslContextFactory$Server KeyStoreScanner]
[javax.servlet AsyncContext DispatcherType AsyncEvent AsyncListener]
[javax.servlet.http HttpServletRequest HttpServletResponse]))
(set! *warn-on-reflection* true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove this line, please.

@@ -130,6 +133,25 @@
(.setHost (options :host))
(.setIdleTimeout (options :max-idle-time 200000)))))

(defn- socket-connector
^UnixSocketConnector [^Server server {:keys [unix-socket] :as options}]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use a consistent format in this file, so:

(defn- ^UnixSocketConnector unix-socket-connector
  [^Server server {:keys [unix-socket] :as options}]

Also I think unix-socket-connector would help differentiate it from TCP sockets.

Comment on lines +138 to +142
(when (->> (System/getProperty "os.name")
(re-find #"(?i)^windows"))
(throw (ex-info "Unix sockets not supported on windows"
{:os (System/getProperty "os.name")
:unix-socket unix-socket})))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's factor this out into its own function (and also capitalize Windows):

(defn- check-os-socket-compatibility []
  (when (->> (System/getProperty "os.name")
             (re-find #"(?i)^windows"))
    (throw (ex-info "Unix sockets not supported on Windows"
                    {:os (System/getProperty "os.name")
                     :unix-socket unix-socket}))))

(.deleteOnExit socket)
(doto (UnixSocketConnector.
server
#^"[Lorg.eclipse.jetty.server.ConnectionFactory;" (into-array ConnectionFactory [http-factory]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you split the line over 80 characters into two lines? Also can you fix the indentation so it's according to the style guide:

(doto (UnixSocketConnector.
       server
       ^"[Lorg.eclipse.jetty.server.ConnectionFactory;"
       (into-array ConnectionFactory [http-factory]))

Comment on lines +175 to +178
(when (:http? options (or ;; default is enabled when no socket specified
(not (:unix-socket options))
;; enabled with socket when host/port set
(some options [:host :port])))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels too complex, and isn't what the documentation says. I think let's keep it simple and just leave it defaulting to true. Let's add a note to the :unix-socket option instead to tell people they may want to set :http? to false.

:ssl? - allow connections over HTTPS
:ssl-port - the SSL port to listen on (defaults to 443, implies
:ssl? is true)
:unix-socket - File to be used as a Unix domain socket; will be
passed to [io/file]. Ensure there is no file at
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think [io/file] should be a link, as there's nothing to link it to.

Comment on lines +15 to +16
IOException
File]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two lines should be one.

(with-server hello-world {:unix-socket sock}
(is (= (-> (HttpClient/create)
(.remoteAddress
(reify Supplier
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation looks off here.

"Hello World")
"able to serve basic request")
(is (thrown-with-msg?
clojure.lang.ExceptionInfo #"File already exists"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation looks off. Please see Clojure style guide.

Comment on lines +145 to +147
(when (.exists socket)
(throw (ex-info "File already exists at socket path; should be deleted before starting server"
{:unix-socket unix-socket})))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's factor this out as well:

(defn- check-socket-file-does-not-exist [socket]
  (when (.exists socket)
    (throw (ex-info "Could not create socket file: file already exists"
                    {:socket-file (str socket)}))))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants