From ad80cbe1ea36db3dc55250d72a2dc6b36c84f7e2 Mon Sep 17 00:00:00 2001 From: Anton Tiurin Date: Tue, 5 May 2015 11:25:42 +0300 Subject: [PATCH] [Server] Listen and serve on a unix socket Allow to use a unix socket as a listener. To specify an endpoint type we use an optional configuration field 'net', as there's no way to distinguish a relative socket path from a hostname. Signed-off-by: Anton Tiurin --- cmd/registry/main.go | 43 +++++++++----- configuration/configuration.go | 3 + configuration/configuration_test.go | 1 + docs/configuration.md | 91 ++++++++++++++++------------- registry/listener/listener.go | 74 +++++++++++++++++++++++ 5 files changed, 157 insertions(+), 55 deletions(-) create mode 100644 registry/listener/listener.go diff --git a/cmd/registry/main.go b/cmd/registry/main.go index 52eecf8f..df135917 100644 --- a/cmd/registry/main.go +++ b/cmd/registry/main.go @@ -21,6 +21,7 @@ import ( _ "github.com/docker/distribution/registry/auth/silly" _ "github.com/docker/distribution/registry/auth/token" "github.com/docker/distribution/registry/handlers" + "github.com/docker/distribution/registry/listener" _ "github.com/docker/distribution/registry/storage/driver/azure" _ "github.com/docker/distribution/registry/storage/driver/filesystem" _ "github.com/docker/distribution/registry/storage/driver/inmemory" @@ -67,14 +68,26 @@ func main() { go debugServer(config.HTTP.Debug.Addr) } - if config.HTTP.TLS.Certificate == "" { - context.GetLogger(app).Infof("listening on %v", config.HTTP.Addr) - if err := http.ListenAndServe(config.HTTP.Addr, handler); err != nil { - context.GetLogger(app).Fatalln(err) - } - } else { + server := &http.Server{ + Handler: handler, + } + + ln, err := listener.NewListener(config.HTTP.Net, config.HTTP.Addr) + if err != nil { + context.GetLogger(app).Fatalln(err) + } + defer ln.Close() + + if config.HTTP.TLS.Certificate != "" { tlsConf := &tls.Config{ - ClientAuth: tls.NoClientCert, + ClientAuth: tls.NoClientCert, + NextProtos: []string{"http/1.1"}, + Certificates: make([]tls.Certificate, 1), + } + + tlsConf.Certificates[0], err = tls.LoadX509KeyPair(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key) + if err != nil { + context.GetLogger(app).Fatalln(err) } if len(config.HTTP.TLS.ClientCAs) != 0 { @@ -99,16 +112,14 @@ func main() { tlsConf.ClientCAs = pool } - context.GetLogger(app).Infof("listening on %v, tls", config.HTTP.Addr) - server := &http.Server{ - Addr: config.HTTP.Addr, - Handler: handler, - TLSConfig: tlsConf, - } + ln = tls.NewListener(ln, tlsConf) + context.GetLogger(app).Infof("listening on %v, tls", ln.Addr()) + } else { + context.GetLogger(app).Infof("listening on %v", ln.Addr()) + } - if err := server.ListenAndServeTLS(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key); err != nil { - context.GetLogger(app).Fatalln(err) - } + if err := server.Serve(ln); err != nil { + context.GetLogger(app).Fatalln(err) } } diff --git a/configuration/configuration.go b/configuration/configuration.go index 074471b4..31496e6e 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -54,6 +54,9 @@ type Configuration struct { // Addr specifies the bind address for the registry instance. Addr string `yaml:"addr,omitempty"` + // Net specifies the net portion of the bind address. A default empty value means tcp. + Net string `yaml:"net,omitempty"` + Prefix string `yaml:"prefix,omitempty"` // Secret specifies the secret key which HMAC tokens are created with. diff --git a/configuration/configuration_test.go b/configuration/configuration_test.go index 5c5d68b3..cc7427dc 100644 --- a/configuration/configuration_test.go +++ b/configuration/configuration_test.go @@ -61,6 +61,7 @@ var configStruct = Configuration{ }, HTTP: struct { Addr string `yaml:"addr,omitempty"` + Net string `yaml:"net,omitempty"` Prefix string `yaml:"prefix,omitempty"` Secret string `yaml:"secret,omitempty"` TLS struct { diff --git a/docs/configuration.md b/docs/configuration.md index 29707fde..d7bd15d6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,6 +1,6 @@ @@ -98,7 +98,7 @@ http: debug: addr: localhost:5001 notifications: - endpoints: + endpoints: - name: alistener disabled: false url: https://my.listener.com/event @@ -158,7 +158,7 @@ directory. >configuration. -## version +## version ```yaml version: 0.1 @@ -166,7 +166,7 @@ version: 0.1 The `version` option is **required**. It specifies the configuration's version. It is expected to remain a top-level field, to allow for a consistent version -check before parsing the remainder of the configuration file. +check before parsing the remainder of the configuration file. ## log @@ -278,7 +278,7 @@ You must configure one backend; if you configure more, the registry returns an e Use the `cache` subsection to enable caching of data accessed in the storage backend. Currently, the only available cache provides fast access to layer -metadata. This, if configured, uses the `layerinfo` field. +metadata. This, if configured, uses the `layerinfo` field. You can set `layerinfo` field to `redis` or `inmemory`. The `redis` value uses a Redis pool to cache layer metadata. The `inmemory` value uses an in memory @@ -296,7 +296,7 @@ here so make sure there is adequate space available. ### azure -This storage backend uses Microsoft's Azure Storage platform. +This storage backend uses Microsoft's Azure Storage platform. @@ -336,7 +336,7 @@ This storage backend uses Microsoft's Azure Storage platform. - +
Name of the Azure container into which to store data.
@@ -455,7 +455,7 @@ This storage backend uses Amazon's Simple Storage Service (S3). This is a prefix that will be applied to all S3 keys to allow you to segment data in your bucket if necessary. - + ### Maintenance @@ -466,7 +466,7 @@ maintenance functions which are related to storage can be configured under the m ### Upload Purging Upload purging is a background process that periodically removes orphaned files from the upload -directories of the registry. Upload purging is enabled by default. To +directories of the registry. Upload purging is enabled by default. To configure upload directory purging, the following parameters must be set. @@ -475,10 +475,10 @@ must be set. --------- | -------- | ----------- `enabled` | yes | Set to true to enable upload purging. Default=true. | `age` | yes | Upload directories which are older than this age will be deleted. Default=168h (1 week) -`interval` | yes | The interval between upload directory purging. Default=24h. +`interval` | yes | The interval between upload directory purging. Default=24h. `dryrun` | yes | dryrun can be set to true to obtain a summary of what directories will be deleted. Default=false. -Note: `age` and `interval` are strings containing a number with optional fraction and a unit suffix: e.g. 45m, 2h10m, 168h (1 week). +Note: `age` and `interval` are strings containing a number with optional fraction and a unit suffix: e.g. 45m, 2h10m, 168h (1 week). ## auth @@ -505,7 +505,7 @@ The `silly` auth is only for development purposes. It simply checks for the existence of the `Authorization` header in the HTTP request. It has no regard for the header's value. If the header does not exist, the `silly` auth responds with a challenge response, echoing back the realm, service, and scope that access was -denied for. +denied for. The following values are used to configure the response: @@ -545,7 +545,7 @@ The following values are used to configure the response: Token based authentication allows the authentication system to be decoupled from the registry. It is a well established authentication paradigm with a high -degree of security. +degree of security. @@ -592,14 +592,14 @@ the token so it must match the value configured for the issuer. rootcertbundle -
- yes + yes The absolute path to the root certificate bundle. This bundle contains the public part of the certificates that is used to sign authentication tokens.
+ For more information about Token based authentication configuration, see the [specification.] @@ -613,7 +613,7 @@ object they're wrapping. This means a registry middleware must implement the `driver.StorageDriver`. Currently only one middleware, `cloudfront`, a storage middleware, is supported -in the registry implementation. +in the registry implementation. ```yaml middleware: @@ -747,7 +747,7 @@ configuration may contain both. production,staging, or development. - + endpoint @@ -756,9 +756,9 @@ configuration may contain both. no - Specify the enterprise Bugsnag endpoint. + Specify the enterprise Bugsnag endpoint. - + @@ -791,7 +791,7 @@ configuration may contain both. New Relic application name. - + verbose @@ -802,7 +802,7 @@ configuration may contain both. Enable New Relic debugging output on stdout. - + ## http @@ -810,6 +810,7 @@ configuration may contain both. ```yaml http: addr: localhost:5000 + net: tcp prefix: /my/nested/registry/ secret: asecretforlocaldevelopment tls: @@ -838,7 +839,20 @@ The `http` option details the configuration for the HTTP server that hosts the r yes - The HOST:PORT for which the server should accept connections. + The address for which the server should accept connections. The form depends on a network type (see net option): + HOST:PORT for tcp and FILE for a unix socket. + + + + + net + + + no + + + The network which is used to create a listening socket. Known networks are unix and tcp. + The default empty value means tcp. @@ -915,7 +929,7 @@ and proxy connections to the registry server. An array of absolute paths to a x509 CA file - + @@ -934,7 +948,7 @@ specifies the `HOST:PORT` on which the debug server should accept connections. ```yaml notifications: - endpoints: + endpoints: - name: alistener disabled: false url: https://my.listener.com/event @@ -965,7 +979,7 @@ Endpoints is a list of named services (URLs) that can accept event notifications yes -A human readable name for the service. +A human readable name for the service. @@ -989,7 +1003,7 @@ A boolean to enable/disable notifications for a service. The URL to which events should be published. - + headers @@ -1000,7 +1014,7 @@ The URL to which events should be published. Static headers to add to each request. - + timeout @@ -1021,7 +1035,7 @@ The URL to which events should be published. If you omit the suffix, the system interprets the value as nanoseconds. - + threshold @@ -1032,7 +1046,7 @@ The URL to which events should be published. An integer specifying how long to wait before backing off a failure. - + backoff @@ -1054,7 +1068,7 @@ The URL to which events should be published. If you omit the suffix, the system interprets the value as nanoseconds. - + @@ -1129,7 +1143,7 @@ with the [pool](#pool) subsection. Timeout for connecting to a redis instance. - + readtimeout @@ -1140,7 +1154,7 @@ with the [pool](#pool) subsection. Timeout for reading from redis connections. - + writetimeout @@ -1151,7 +1165,7 @@ with the [pool](#pool) subsection. Timeout for writing to redis connections. - + @@ -1206,7 +1220,7 @@ Configure the behavior of the Redis connection pool. sets the amount time to wait before closing inactive connections. - + @@ -1216,7 +1230,7 @@ The following is a simple example you can use for local development: ```yaml version: 0.1 -log: +log: level: debug storage: filesystem: @@ -1229,7 +1243,7 @@ http: ``` The above configures the registry instance to run on port `5000`, binding to -`localhost`, with the `debug` server enabled. Registry data storage is in the +`localhost`, with the `debug` server enabled. Registry data storage is in the `/tmp/registry-dev` directory. Logging is in `debug` mode, which is the most verbose. @@ -1242,7 +1256,7 @@ Both are generally useful for local development. This example illustrates how to configure storage middleware in a registry. Middleware allows the registry to serve layers via a content delivery network -(CDN). This is useful for reducing requests to the storage layer. +(CDN). This is useful for reducing requests to the storage layer. Currently, the registry supports [Amazon Cloudfront](http://aws.amazon.com/cloudfront/). You can only use Cloudfront in @@ -1263,7 +1277,7 @@ conjunction with the S3 storage driver. options: - + A set of key/value options to configure the middleware.
  • baseurl: The Cloudfront base URL.
  • @@ -1293,4 +1307,3 @@ middleware: >**Note**: Cloudfront keys exist separately to other AWS keys. See >[the documentation on AWS credentials](http://docs.aws.amazon.com/AWSSecurityCredentials/1.0/AboutAWSCredentials.html#KeyPairs) >for more information. - diff --git a/registry/listener/listener.go b/registry/listener/listener.go new file mode 100644 index 00000000..b93a7a63 --- /dev/null +++ b/registry/listener/listener.go @@ -0,0 +1,74 @@ +package listener + +import ( + "fmt" + "net" + "os" + "time" +) + +// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted +// connections. It's used by ListenAndServe and ListenAndServeTLS so +// dead TCP connections (e.g. closing laptop mid-download) eventually +// go away. +// it is a plain copy-paste from net/http/server.go +type tcpKeepAliveListener struct { + *net.TCPListener +} + +func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { + tc, err := ln.AcceptTCP() + if err != nil { + return + } + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(3 * time.Minute) + return tc, nil +} + +// NewListener announces on laddr and net. Accepted values of the net are +// 'unix' and 'tcp' +func NewListener(net, laddr string) (net.Listener, error) { + switch net { + case "unix": + return newUnixListener(laddr) + case "tcp", "": // an empty net means tcp + return newTCPListener(laddr) + default: + return nil, fmt.Errorf("unknown address type %s", net) + } +} + +func newUnixListener(laddr string) (net.Listener, error) { + fi, err := os.Stat(laddr) + if err == nil { + // the file exists. + // try to remove it if it's a socket + if !isSocket(fi.Mode()) { + return nil, fmt.Errorf("file %s exists and is not a socket", laddr) + } + + if err := os.Remove(laddr); err != nil { + return nil, err + } + } else if !os.IsNotExist(err) { + // we can't do stat on the file. + // it means we can not remove it + return nil, err + } + + return net.Listen("unix", laddr) +} + +func isSocket(m os.FileMode) bool { + return m&os.ModeSocket != 0 +} + +func newTCPListener(laddr string) (net.Listener, error) { + ln, err := net.Listen("tcp", laddr) + if err != nil { + return nil, err + } + + return tcpKeepAliveListener{ln.(*net.TCPListener)}, nil +}