From 05bdde07f6c44532f08397a35558915dbd4e3e5e Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Sun, 31 Jul 2016 19:53:39 +1200 Subject: [PATCH 1/3] Split dockerui.go in separate files in api/ (#231) Split dockerui.go in separate files in api/ --- api/api.go | 28 +++++++ api/csrf.go | 48 +++++++++++ api/flags.go | 19 +++++ api/handler.go | 75 +++++++++++++++++ api/ssl.go | 27 +++++++ api/unix_handler.go | 47 +++++++++++ dockerui.go | 190 -------------------------------------------- gruntFile.js | 6 +- 8 files changed, 247 insertions(+), 193 deletions(-) create mode 100644 api/api.go create mode 100644 api/csrf.go create mode 100644 api/flags.go create mode 100644 api/handler.go create mode 100644 api/ssl.go create mode 100644 api/unix_handler.go delete mode 100644 dockerui.go diff --git a/api/api.go b/api/api.go new file mode 100644 index 0000000..f8d21a2 --- /dev/null +++ b/api/api.go @@ -0,0 +1,28 @@ +package main // import "github.com/kevana/ui-for-docker" + +import ( + "flag" + "log" + "net/http" +) + +func main() { + var ( + endpoint = flag.String("H", "unix:///var/run/docker.sock", "Dockerd endpoint") + addr = flag.String("p", ":9000", "Address and port to serve UI For Docker") + assets = flag.String("a", ".", "Path to the assets") + data = flag.String("d", ".", "Path to the data") + tlsverify = flag.Bool("tlsverify", false, "TLS support") + tlscacert = flag.String("tlscacert", "/certs/ca.pem", "Path to the CA") + tlscert = flag.String("tlscert", "/certs/cert.pem", "Path to the TLS certificate file") + tlskey = flag.String("tlskey", "/certs/key.pem", "Path to the TLS key") + ) + flag.Parse() + + tlsFlags := newTLSFlags(*tlsverify, *tlscacert, *tlscert, *tlskey) + + handler := newHandler(*assets, *data, *endpoint, tlsFlags) + if err := http.ListenAndServe(*addr, handler); err != nil { + log.Fatal(err) + } +} diff --git a/api/csrf.go b/api/csrf.go new file mode 100644 index 0000000..4377ee3 --- /dev/null +++ b/api/csrf.go @@ -0,0 +1,48 @@ +package main + +import ( + "github.com/gorilla/csrf" + "github.com/gorilla/securecookie" + "io/ioutil" + "log" + "net/http" +) + +const keyFile = "authKey.dat" + +// newAuthKey reuses an existing CSRF authkey if present or generates a new one +func newAuthKey(path string) []byte { + var authKey []byte + authKeyPath := path + "/" + keyFile + data, err := ioutil.ReadFile(authKeyPath) + if err != nil { + log.Print("Unable to find an existing CSRF auth key. Generating a new key.") + authKey = securecookie.GenerateRandomKey(32) + err := ioutil.WriteFile(authKeyPath, authKey, 0644) + if err != nil { + log.Fatal("Unable to persist CSRF auth key.") + log.Fatal(err) + } + } else { + authKey = data + } + return authKey +} + +// newCSRF initializes a new CSRF handler +func newCSRFHandler(keyPath string) func(h http.Handler) http.Handler { + authKey := newAuthKey(keyPath) + return csrf.Protect( + authKey, + csrf.HttpOnly(false), + csrf.Secure(false), + ) +} + +// newCSRFWrapper wraps a http.Handler to add the CSRF token +func newCSRFWrapper(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-CSRF-Token", csrf.Token(r)) + h.ServeHTTP(w, r) + }) +} diff --git a/api/flags.go b/api/flags.go new file mode 100644 index 0000000..9076e64 --- /dev/null +++ b/api/flags.go @@ -0,0 +1,19 @@ +package main + +// TLSFlags defines all the flags associated to the SSL configuration +type TLSFlags struct { + tls bool + caPath string + certPath string + keyPath string +} + +// newTLSFlags creates a new TLSFlags from command flags +func newTLSFlags(tls bool, cacert string, cert string, key string) TLSFlags { + return TLSFlags{ + tls: tls, + caPath: cacert, + certPath: cert, + keyPath: key, + } +} diff --git a/api/handler.go b/api/handler.go new file mode 100644 index 0000000..bf09fb6 --- /dev/null +++ b/api/handler.go @@ -0,0 +1,75 @@ +package main + +import ( + "log" + "net/http" + "net/http/httputil" + "net/url" + "os" +) + +// newHandler creates a new http.Handler with CSRF protection +func newHandler(dir string, d string, e string, tlsFlags TLSFlags) http.Handler { + var ( + mux = http.NewServeMux() + fileHandler = http.FileServer(http.Dir(dir)) + ) + + u, perr := url.Parse(e) + if perr != nil { + log.Fatal(perr) + } + + handler := newAPIHandler(u, tlsFlags) + CSRFHandler := newCSRFHandler(d) + + mux.Handle("/dockerapi/", http.StripPrefix("/dockerapi", handler)) + mux.Handle("/", fileHandler) + return CSRFHandler(newCSRFWrapper(mux)) +} + +// newAPIHandler initializes a new http.Handler based on the URL scheme +func newAPIHandler(u *url.URL, tlsFlags TLSFlags) http.Handler { + var handler http.Handler + if u.Scheme == "tcp" { + if tlsFlags.tls { + handler = newTCPHandlerWithTLS(u, tlsFlags) + } else { + handler = newTCPHandler(u) + } + } else if u.Scheme == "unix" { + socketPath := u.Path + if _, err := os.Stat(socketPath); err != nil { + if os.IsNotExist(err) { + log.Fatalf("Unix socket %s does not exist", socketPath) + } + log.Fatal(err) + } + handler = newUnixHandler(socketPath) + } else { + log.Fatalf("Bad Docker enpoint: %s. Only unix:// and tcp:// are supported.", u) + } + return handler +} + +// newUnixHandler initializes a new UnixHandler +func newUnixHandler(e string) http.Handler { + return &unixHandler{e} +} + +// newTCPHandler initializes a HTTP reverse proxy +func newTCPHandler(u *url.URL) http.Handler { + u.Scheme = "http" + return httputil.NewSingleHostReverseProxy(u) +} + +// newTCPHandlerWithL initializes a HTTPS reverse proxy with a TLS configuration +func newTCPHandlerWithTLS(u *url.URL, tlsFlags TLSFlags) http.Handler { + u.Scheme = "https" + var tlsConfig = newTLSConfig(tlsFlags) + proxy := httputil.NewSingleHostReverseProxy(u) + proxy.Transport = &http.Transport{ + TLSClientConfig: tlsConfig, + } + return proxy +} diff --git a/api/ssl.go b/api/ssl.go new file mode 100644 index 0000000..6d86db5 --- /dev/null +++ b/api/ssl.go @@ -0,0 +1,27 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "io/ioutil" + "log" +) + +// newTLSConfig initializes a tls.Config from the TLS flags +func newTLSConfig(tlsFlags TLSFlags) *tls.Config { + cert, err := tls.LoadX509KeyPair(tlsFlags.certPath, tlsFlags.keyPath) + if err != nil { + log.Fatal(err) + } + caCert, err := ioutil.ReadFile(tlsFlags.caPath) + if err != nil { + log.Fatal(err) + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + } + return tlsConfig +} diff --git a/api/unix_handler.go b/api/unix_handler.go new file mode 100644 index 0000000..15a5119 --- /dev/null +++ b/api/unix_handler.go @@ -0,0 +1,47 @@ +package main + +import ( + "io" + "log" + "net" + "net/http" + "net/http/httputil" +) + +// unixHandler defines a handler holding the path to a socket under UNIX +type unixHandler struct { + path string +} + +// ServeHTTP implementation for unixHandler +func (h *unixHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + conn, err := net.Dial("unix", h.path) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println(err) + return + } + c := httputil.NewClientConn(conn, nil) + defer c.Close() + + res, err := c.Do(r) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println(err) + return + } + defer res.Body.Close() + + copyHeader(w.Header(), res.Header) + if _, err := io.Copy(w, res.Body); err != nil { + log.Println(err) + } +} + +func copyHeader(dst, src http.Header) { + for k, vv := range src { + for _, v := range vv { + dst.Add(k, v) + } + } +} diff --git a/dockerui.go b/dockerui.go deleted file mode 100644 index 7cf0172..0000000 --- a/dockerui.go +++ /dev/null @@ -1,190 +0,0 @@ -package main // import "github.com/kevana/ui-for-docker" - -import ( - "flag" - "io" - "log" - "net" - "net/http" - "net/http/httputil" - "net/url" - "os" - "github.com/gorilla/csrf" - "io/ioutil" - "fmt" - "github.com/gorilla/securecookie" - "crypto/tls" - "crypto/x509" -) - -var ( - endpoint = flag.String("H", "unix:///var/run/docker.sock", "Dockerd endpoint") - addr = flag.String("p", ":9000", "Address and port to serve UI For Docker") - assets = flag.String("a", ".", "Path to the assets") - data = flag.String("d", ".", "Path to the data") - tlsverify = flag.Bool("tlsverify", false, "TLS support") - tlscacert = flag.String("tlscacert", "/certs/ca.pem", "Path to the CA") - tlscert = flag.String("tlscert", "/certs/cert.pem", "Path to the TLS certificate file") - tlskey = flag.String("tlskey", "/certs/key.pem", "Path to the TLS key") - authKey []byte - authKeyFile = "authKey.dat" -) - -type UnixHandler struct { - path string -} - -type TLSFlags struct { - tls bool - caPath string - certPath string - keyPath string -} - -func (h *UnixHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - conn, err := net.Dial("unix", h.path) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - log.Println(err) - return - } - c := httputil.NewClientConn(conn, nil) - defer c.Close() - - res, err := c.Do(r) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - log.Println(err) - return - } - defer res.Body.Close() - - copyHeader(w.Header(), res.Header) - if _, err := io.Copy(w, res.Body); err != nil { - log.Println(err) - } -} - -func copyHeader(dst, src http.Header) { - for k, vv := range src { - for _, v := range vv { - dst.Add(k, v) - } - } -} - -func createTLSConfig(flags TLSFlags) *tls.Config { - cert, err := tls.LoadX509KeyPair(flags.certPath, flags.keyPath) - if err != nil { - log.Fatal(err) - } - caCert, err := ioutil.ReadFile(flags.caPath) - if err != nil { - log.Fatal(err) - } - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(caCert) - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, - RootCAs: caCertPool, - } - return tlsConfig; -} - -func createTcpHandler(u *url.URL) http.Handler { - u.Scheme = "http"; - return httputil.NewSingleHostReverseProxy(u) -} - -func createTcpHandlerWithTLS(u *url.URL, flags TLSFlags) http.Handler { - u.Scheme = "https"; - var tlsConfig = createTLSConfig(flags) - 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, flags TLSFlags) http.Handler { - var ( - mux = http.NewServeMux() - fileHandler = http.FileServer(http.Dir(dir)) - h http.Handler - ) - - u, perr := url.Parse(e) - if perr != nil { - log.Fatal(perr) - } - - if u.Scheme == "tcp" { - if flags.tls { - h = createTcpHandlerWithTLS(u, flags) - } else { - h = createTcpHandler(u) - } - } else if u.Scheme == "unix" { - var socketPath = u.Path - if _, err := os.Stat(socketPath); err != nil { - if os.IsNotExist(err) { - log.Fatalf("unix socket %s does not exist", socketPath) - } - log.Fatal(err) - } - h = createUnixHandler(socketPath) - } else { - log.Fatalf("Bad Docker enpoint: %s. Only unix:// and tcp:// are supported.", e) - } - - // Use existing csrf authKey if present or generate a new one. - var authKeyPath = d + "/" + authKeyFile - dat, err := ioutil.ReadFile(authKeyPath) - if err != nil { - fmt.Println(err) - authKey = securecookie.GenerateRandomKey(32) - err := ioutil.WriteFile(authKeyPath, authKey, 0644) - if err != nil { - fmt.Println("unable to persist auth key", err) - } - } else { - authKey = dat - } - - CSRF := csrf.Protect( - authKey, - csrf.HttpOnly(false), - csrf.Secure(false), - ) - - mux.Handle("/dockerapi/", http.StripPrefix("/dockerapi", h)) - mux.Handle("/", fileHandler) - return CSRF(csrfWrapper(mux)) -} - -func csrfWrapper(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("X-CSRF-Token", csrf.Token(r)) - h.ServeHTTP(w, r) - }) -} - -func main() { - flag.Parse() - - tlsFlags := TLSFlags{ - tls: *tlsverify, - caPath: *tlscacert, - certPath: *tlscert, - keyPath: *tlskey, - } - - handler := createHandler(*assets, *data, *endpoint, tlsFlags) - if err := http.ListenAndServe(*addr, handler); err != nil { - log.Fatal(err) - } -} diff --git a/gruntFile.js b/gruntFile.js index 829c62f..de63db4 100644 --- a/gruntFile.js +++ b/gruntFile.js @@ -248,10 +248,10 @@ module.exports = function (grunt) { }, buildBinary: { command: [ - 'docker run --rm -v $(pwd):/src centurylink/golang-builder', - 'shasum ui-for-docker > ui-for-docker-checksum.txt', + 'docker run --rm -v $(pwd)/api:/src centurylink/golang-builder', + 'shasum api/ui-for-docker > ui-for-docker-checksum.txt', 'mkdir -p dist', - 'mv ui-for-docker dist/' + 'mv api/ui-for-docker dist/' ].join(' && ') }, run: { From 8007e0161b8699b0c669c1839dec16d1a63989f6 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 7 Sep 2016 10:20:47 +1200 Subject: [PATCH 2/3] Remove the HostConfig parameter from the Container.start request --- .../containers/containersController.js | 2 +- .../startContainer/startContainerController.js | 17 ++++------------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/app/components/containers/containersController.js b/app/components/containers/containersController.js index 02f7dbc..174b5d4 100644 --- a/app/components/containers/containersController.js +++ b/app/components/containers/containersController.js @@ -37,7 +37,7 @@ angular.module('containers', []) Container.get({id: c.Id}, function (d) { c = d; counter = counter + 1; - action({id: c.Id, HostConfig: c.HostConfig || {}}, function (d) { + action({id: c.Id}, {}, function (d) { Messages.send("Container " + msg, c.Id); var index = $scope.containers.indexOf(c); complete(); diff --git a/app/components/startContainer/startContainerController.js b/app/components/startContainer/startContainerController.js index bc7527b..79c1970 100644 --- a/app/components/startContainer/startContainerController.js +++ b/app/components/startContainer/startContainerController.js @@ -125,19 +125,10 @@ angular.module('startContainer', ['ui.bootstrap']) var s = $scope; Container.create(config, function (d) { if (d.Id) { - var reqBody = config.HostConfig || {}; - reqBody.id = d.Id; - ctor.start(reqBody, function (cd) { - if (cd.id) { - Messages.send('Container Started', d.Id); - $('#create-modal').modal('hide'); - loc.path('/containers/' + d.Id + '/'); - } else { - failedRequestHandler(cd, Messages); - ctor.remove({id: d.Id}, function () { - Messages.send('Container Removed', d.Id); - }); - } + ctor.start({id: d.Id}, {}, function (cd) { + Messages.send('Container Started', d.Id); + $('#create-modal').modal('hide'); + loc.path('/containers/' + d.Id + '/'); }, function (e) { failedRequestHandler(e, Messages); }); From db5dd0c356e088322742d9a77c13f1b1061884f3 Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Wed, 7 Sep 2016 00:14:24 -0500 Subject: [PATCH 3/3] Version bump to 0.11.0 --- app/app.js | 2 +- bower.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/app.js b/app/app.js index 67ae697..0fe4489 100644 --- a/app/app.js +++ b/app/app.js @@ -97,4 +97,4 @@ angular.module('uifordocker', [ // You need to set this to the api endpoint without the port i.e. http://192.168.1.9 .constant('DOCKER_ENDPOINT', 'dockerapi') .constant('DOCKER_PORT', '') // Docker port, leave as an empty string if no port is requred. If you have a port, prefix it with a ':' i.e. :4243 - .constant('UI_VERSION', 'v0.11.0-beta'); + .constant('UI_VERSION', 'v0.11.0'); diff --git a/bower.json b/bower.json index 9271a98..8b5258a 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "uifordocker", - "version": "0.11.0-beta", + "version": "0.11.0", "homepage": "https://github.com/kevana/ui-for-docker", "authors": [ "Michael Crosby ", diff --git a/package.json b/package.json index 7fb094c..97c264f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Michael Crosby & Kevan Ahlquist", "name": "uifordocker", "homepage": "https://github.com/kevana/ui-for-docker", - "version": "0.11.0-beta", + "version": "0.11.0", "repository": { "type": "git", "url": "git@github.com:kevana/ui-for-docker.git"