diff --git a/README.md b/README.md index ce20e8267..8e15053e0 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,19 @@ UI For Docker listens on port 9000 by default. If you run UI For Docker inside a $ docker run -d -p 10.20.30.1:80:9000 --privileged -v /var/run/docker.sock:/var/run/docker.sock cloudinovasi/cloudinovasi-ui ``` +### Access a Docker engine protected via TLS + +Ensure that you have access to the CA, the cert and the public key used to access your Docker engine. + +These files will need to be named `ca.pem`, `cert.pem` and `key.pem` respectively. Store them somewhere on your disk and mount a volume containing these files inside the UI container: + +``` +# Note the access to the endpoint via https +$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -v /path/to/certs:/certs -e https://my-docker-host.domain:2376 +``` + +*Note*: Replace `/path/to/certs` to the path to the certificate files on your disk. + ### Hide containers with specific labels You can hide specific containers in the containers view by using the `-hide-label` or `-l` options and specifying a label. @@ -77,6 +90,7 @@ The following options are available for the `ui-for-docker` binary: * `--endpoint`, `-e`: Docker deamon endpoint (default: *"/var/run/docker.sock"*) * `--bind`, `-p`: Address and port to serve UI For Docker (default: *":9000"*) * `--data`, `-d`: Path to the data folder (default: *"."*) +* `--certs`, `-c`: Path to the certificates used for TLS (default: *"/certs"*) * `--assets`, `-a`: Path to the assets (default: *"."*) * `--swarm`, `-s`: Swarm cluster support (default: *false*) * `--hide-label`, `-l`: Hide containers with a specific label in the UI diff --git a/dockerui.go b/dockerui.go index b01a2f91a..9d3da31c6 100644 --- a/dockerui.go +++ b/dockerui.go @@ -15,6 +15,8 @@ import ( "fmt" "github.com/gorilla/securecookie" "gopkg.in/alecthomas/kingpin.v2" + "crypto/tls" + "crypto/x509" ) var ( @@ -22,6 +24,7 @@ var ( addr = kingpin.Flag("bind", "Address and port to serve UI For Docker").Default(":9000").Short('p').String() assets = kingpin.Flag("assets", "Path to the assets").Default(".").Short('a').String() data = kingpin.Flag("data", "Path to the data").Default(".").Short('d').String() + certs = kingpin.Flag("certs", "Path to the certs").Default("/certs").Short('c').String() swarm = kingpin.Flag("swarm", "Swarm cluster support").Default("false").Short('s').Bool() labels = LabelParser(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l')) authKey []byte @@ -114,18 +117,50 @@ func createTcpHandler(e string) http.Handler { return httputil.NewSingleHostReverseProxy(u) } +func createTlsConfig(c string) *tls.Config { + cert, err := tls.LoadX509KeyPair(c + "/" + "cert.pem", c + "/" + "key.pem") + if err != nil { + log.Fatal(err) + } + caCert, err := ioutil.ReadFile(c + "/" + "ca.pem") + if err != nil { + log.Fatal(err) + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + } + return tlsConfig; +} + +func createTcpHandlerWithTLS(e string, c string) http.Handler { + u, err := url.Parse(e) + if err != nil { + log.Fatal(err) + } + var tlsConfig = createTlsConfig(c) + proxy := httputil.NewSingleHostReverseProxy(u) + proxy.Transport = &http.Transport{ + TLSClientConfig: tlsConfig, + } + return proxy; +} + func createUnixHandler(e string) http.Handler { return &UnixHandler{e} } -func createHandler(dir string, d string, e string, c Config) http.Handler { +func createHandler(dir string, d string, certs string, e string, c Config) http.Handler { var ( mux = http.NewServeMux() fileHandler = http.FileServer(http.Dir(dir)) h http.Handler ) - - if strings.Contains(e, "http") { + if strings.Contains(e, "https") { + h = createTcpHandlerWithTLS(e, certs) + } else if strings.Contains(e, "http") { h = createTcpHandler(e) } else { if _, err := os.Stat(e); err != nil { @@ -181,7 +216,7 @@ func main() { HiddenLabels: *labels, } - handler := createHandler(*assets, *data, *endpoint, configuration) + handler := createHandler(*assets, *data, *certs, *endpoint, configuration) if err := http.ListenAndServe(*addr, handler); err != nil { log.Fatal(err) } diff --git a/gruntFile.js b/gruntFile.js index b5e1a6c98..b18674a1d 100644 --- a/gruntFile.js +++ b/gruntFile.js @@ -40,6 +40,7 @@ module.exports = function (grunt) { grunt.registerTask('run', ['if:binaryNotExist', 'build', 'shell:buildImage', 'shell:run']); grunt.registerTask('run-swarm', ['if:binaryNotExist', 'build', 'shell:buildImage', 'shell:runSwarm', 'watch:buildSwarm']); grunt.registerTask('run-dev', ['if:binaryNotExist', 'shell:buildImage', 'shell:run', 'watch:build']); + grunt.registerTask('run-ssl', ['if:binaryNotExist', 'shell:buildImage', 'shell:runSsl', 'watch:buildSsl']); grunt.registerTask('clear', ['clean:app']); // Print a timestamp (useful for when watching) @@ -224,6 +225,10 @@ module.exports = function (grunt) { buildSwarm: { files: ['<%= src.js %>', '<%= src.specs %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'], tasks: ['build', 'shell:buildImage', 'shell:runSwarm', 'shell:cleanImages'] + }, + buildSsl: { + files: ['<%= src.js %>', '<%= src.specs %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'], + tasks: ['build', 'shell:buildImage', 'shell:runSsl', 'shell:cleanImages'] } }, jshint: { @@ -267,7 +272,14 @@ module.exports = function (grunt) { command: [ 'docker stop ui-for-docker', 'docker rm ui-for-docker', - 'docker run --privileged -d -p 9000:9000 -v /tmp/docker-ui:/data --name ui-for-docker ui-for-docker -e http://10.0.7.10:4000 --swarm -d /data' + 'docker run -d -p 9000:9000 -v /tmp/docker-ui:/data --name ui-for-docker ui-for-docker -e http://10.0.7.10:4000 --swarm -d /data' + ].join(';') + }, + runSsl: { + command: [ + 'docker stop ui-for-docker', + 'docker rm ui-for-docker', + 'docker run -d -p 9000:9000 -v /tmp/docker-ui:/data -v /tmp/docker-ssl:/certs --name ui-for-docker ui-for-docker -e https://10.0.7.10:2376 -d /data' ].join(';') }, cleanImages: {