// NewRegistry creates a new registry from a context and configuration struct. func NewRegistry(ctx context.Context, config *configuration.Configuration) (*Registry, error) { var err error ctx, err = configureLogging(ctx, config) if err != nil { return nil, fmt.Errorf("error configuring logger: %v", err) } // inject a logger into the uuid library. warns us if there is a problem // with uuid generation under low entropy. uuid.Loggerf = context.GetLogger(ctx).Warnf app := handlers.NewApp(ctx, config) // TODO(aaronl): The global scope of the health checks means NewRegistry // can only be called once per process. app.RegisterHealthChecks() handler := configureReporting(app) handler = alive("/", handler) handler = health.Handler(handler) handler = panicHandler(handler) handler = gorhandlers.CombinedLoggingHandler(os.Stdout, handler) server := &http.Server{ Handler: handler, } return &Registry{ app: app, config: config, server: server, }, nil }
// main is a modified version of the registry main function: // https://github.com/docker/distribution/blob/6ba799b/cmd/registry/main.go func main() { logrus.SetLevel(logrus.InfoLevel) ctx := context.Background() ctx = context.WithValue(ctx, "version", version.String()) ctx = context.WithLogger(ctx, context.GetLogger(ctx, "version")) client, err := controller.NewClient("", os.Getenv("CONTROLLER_KEY")) if err != nil { context.GetLogger(ctx).Fatalln(err) } release, err := client.GetRelease(os.Getenv("FLYNN_RELEASE_ID")) if err != nil { context.GetLogger(ctx).Fatalln(err) } artifact, err := client.GetArtifact(release.ArtifactIDs[0]) if err != nil { context.GetLogger(ctx).Fatalln(err) } authKey := os.Getenv("AUTH_KEY") middleware.Register("flynn", repositoryMiddleware(client, artifact, authKey)) config := configuration.Configuration{ Version: configuration.CurrentVersion, Storage: configuration.Storage{ blobstore.DriverName: configuration.Parameters{}, "delete": configuration.Parameters{"enabled": true}, }, Middleware: map[string][]configuration.Middleware{ "repository": { {Name: "flynn"}, }, }, Auth: configuration.Auth{ "flynn": configuration.Parameters{ "auth_key": authKey, }, }, } config.HTTP.Secret = os.Getenv("REGISTRY_HTTP_SECRET") status.AddHandler(status.HealthyHandler) app := handlers.NewApp(ctx, config) http.Handle("/", app) addr := ":" + os.Getenv("PORT") context.GetLogger(app).Infof("listening on %s", addr) if err := http.ListenAndServe(addr, nil); err != nil { context.GetLogger(app).Fatalln(err) } }
func main() { root := os.Args[1] config := configuration.Configuration{ Storage: configuration.Storage{ "filesystem": configuration.Parameters{ "rootdirectory": root, }, }, } app := handlers.NewApp(context.Background(), config) if err := http.ListenAndServe(":8080", app); err != nil { log.Fatal(err) } }
func main() { flag.Usage = usage flag.Parse() if showVersion { version.PrintVersion() return } ctx := context.Background() ctx = context.WithValue(ctx, "version", version.Version) config, err := resolveConfiguration() if err != nil { fatalf("configuration error: %v", err) } ctx, err = configureLogging(ctx, config) if err != nil { fatalf("error configuring logger: %v", err) } app := handlers.NewApp(ctx, *config) handler := configureReporting(app) handler = gorhandlers.CombinedLoggingHandler(os.Stdout, handler) if config.HTTP.Debug.Addr != "" { go debugServer(config.HTTP.Debug.Addr) } 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, 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 { pool := x509.NewCertPool() for _, ca := range config.HTTP.TLS.ClientCAs { caPem, err := ioutil.ReadFile(ca) if err != nil { context.GetLogger(app).Fatalln(err) } if ok := pool.AppendCertsFromPEM(caPem); !ok { context.GetLogger(app).Fatalln(fmt.Errorf("Could not add CA to pool")) } } for _, subj := range pool.Subjects() { context.GetLogger(app).Debugf("CA Subject: %s", string(subj)) } tlsConf.ClientAuth = tls.RequireAndVerifyClientCert tlsConf.ClientCAs = pool } 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.Serve(ln); err != nil { context.GetLogger(app).Fatalln(err) } }
func main() { flag.Usage = usage flag.Parse() if showVersion { version.PrintVersion() return } ctx := context.Background() ctx = context.WithValue(ctx, "version", version.Version) config, err := resolveConfiguration() if err != nil { fatalf("configuration error: %v", err) } ctx, err = configureLogging(ctx, config) if err != nil { fatalf("error configuring logger: %v", err) } // inject a logger into the uuid library. warns us if there is a problem // with uuid generation under low entropy. uuid.Loggerf = context.GetLogger(ctx).Warnf app := handlers.NewApp(ctx, *config) app.RegisterHealthChecks() handler := configureReporting(app) handler = panicHandler(handler) handler = health.Handler(handler) handler = gorhandlers.CombinedLoggingHandler(os.Stdout, handler) if config.HTTP.Debug.Addr != "" { go debugServer(config.HTTP.Debug.Addr) } 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, NextProtos: []string{"http/1.1"}, Certificates: make([]tls.Certificate, 1), MinVersion: tls.VersionTLS10, PreferServerCipherSuites: true, CipherSuites: []uint16{ tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, }, } 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 { pool := x509.NewCertPool() for _, ca := range config.HTTP.TLS.ClientCAs { caPem, err := ioutil.ReadFile(ca) if err != nil { context.GetLogger(app).Fatalln(err) } if ok := pool.AppendCertsFromPEM(caPem); !ok { context.GetLogger(app).Fatalln(fmt.Errorf("Could not add CA to pool")) } } for _, subj := range pool.Subjects() { context.GetLogger(app).Debugf("CA Subject: %s", string(subj)) } tlsConf.ClientAuth = tls.RequireAndVerifyClientCert tlsConf.ClientCAs = pool } 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.Serve(ln); err != nil { context.GetLogger(app).Fatalln(err) } }
func TestPullthroughServeBlob(t *testing.T) { ctx := context.Background() installFakeAccessController(t) testImage, err := registrytest.NewImageForManifest("user/app", registrytest.SampleImageManifestSchema1, false) if err != nil { t.Fatal(err) } client := &testclient.Fake{} client.AddReactor("get", "images", registrytest.GetFakeImageGetHandler(t, *testImage)) // TODO: get rid of those nasty global vars backupRegistryClient := DefaultRegistryClient DefaultRegistryClient = makeFakeRegistryClient(client, fake.NewSimpleClientset()) defer func() { // set it back once this test finishes to make other unit tests working again DefaultRegistryClient = backupRegistryClient }() // pullthrough middleware will attempt to pull from this registry instance remoteRegistryApp := handlers.NewApp(ctx, &configuration.Configuration{ Loglevel: "debug", Auth: map[string]configuration.Parameters{ fakeAuthorizerName: {"realm": fakeAuthorizerName}, }, Storage: configuration.Storage{ "inmemory": configuration.Parameters{}, "cache": configuration.Parameters{ "blobdescriptor": "inmemory", }, "delete": configuration.Parameters{ "enabled": true, }, }, Middleware: map[string][]configuration.Middleware{ "registry": {{Name: "openshift"}}, "repository": {{Name: "openshift", Options: configuration.Parameters{"pullthrough": false}}}, "storage": {{Name: "openshift"}}, }, }) remoteRegistryServer := httptest.NewServer(remoteRegistryApp) defer remoteRegistryServer.Close() serverURL, err := url.Parse(remoteRegistryServer.URL) if err != nil { t.Fatalf("error parsing server url: %v", err) } os.Setenv("DOCKER_REGISTRY_URL", serverURL.Host) testImage.DockerImageReference = fmt.Sprintf("%s/%s@%s", serverURL.Host, "user/app", testImage.Name) testImageStream := registrytest.TestNewImageStreamObject("user", "app", "latest", testImage.Name, testImage.DockerImageReference) if testImageStream.Annotations == nil { testImageStream.Annotations = make(map[string]string) } testImageStream.Annotations[imageapi.InsecureRepositoryAnnotation] = "true" client.AddReactor("get", "imagestreams", imagetest.GetFakeImageStreamGetHandler(t, *testImageStream)) blob1Desc, blob1Content, err := registrytest.UploadTestBlob(serverURL, nil, "user/app") if err != nil { t.Fatal(err) } blob2Desc, blob2Content, err := registrytest.UploadTestBlob(serverURL, nil, "user/app") if err != nil { t.Fatal(err) } blob1Storage := map[digest.Digest][]byte{blob1Desc.Digest: blob1Content} blob2Storage := map[digest.Digest][]byte{blob2Desc.Digest: blob2Content} for _, tc := range []struct { name string method string blobDigest digest.Digest localBlobs map[digest.Digest][]byte expectedStatError error expectedContentLength int64 expectedBytesServed int64 expectedBytesServedLocally int64 expectedLocalCalls map[string]int }{ { name: "stat local blob", method: "HEAD", blobDigest: blob1Desc.Digest, localBlobs: blob1Storage, expectedContentLength: int64(len(blob1Content)), expectedLocalCalls: map[string]int{ "Stat": 1, "ServeBlob": 1, }, }, { name: "serve local blob", method: "GET", blobDigest: blob1Desc.Digest, localBlobs: blob1Storage, expectedContentLength: int64(len(blob1Content)), expectedBytesServed: int64(len(blob1Content)), expectedBytesServedLocally: int64(len(blob1Content)), expectedLocalCalls: map[string]int{ "Stat": 1, "ServeBlob": 1, }, }, { name: "stat remote blob", method: "HEAD", blobDigest: blob1Desc.Digest, localBlobs: blob2Storage, expectedContentLength: int64(len(blob1Content)), expectedLocalCalls: map[string]int{"Stat": 1}, }, { name: "serve remote blob", method: "GET", blobDigest: blob1Desc.Digest, expectedContentLength: int64(len(blob1Content)), expectedBytesServed: int64(len(blob1Content)), expectedLocalCalls: map[string]int{"Stat": 1}, }, { name: "unknown blob digest", method: "GET", blobDigest: unknownBlobDigest, expectedStatError: distribution.ErrBlobUnknown, expectedLocalCalls: map[string]int{"Stat": 1}, }, } { localBlobStore := newTestBlobStore(tc.localBlobs) cachedLayers, err := newDigestToRepositoryCache(10) if err != nil { t.Fatal(err) } ptbs := &pullthroughBlobStore{ BlobStore: localBlobStore, repo: &repository{ ctx: ctx, namespace: "user", name: "app", pullthrough: true, cachedLayers: cachedLayers, registryOSClient: client, }, digestToStore: make(map[string]distribution.BlobStore), } req, err := http.NewRequest(tc.method, fmt.Sprintf("http://example.org/v2/user/app/blobs/%s", tc.blobDigest), nil) if err != nil { t.Fatalf("[%s] failed to create http request: %v", tc.name, err) } w := httptest.NewRecorder() dgst := digest.Digest(tc.blobDigest) _, err = ptbs.Stat(ctx, dgst) if err != tc.expectedStatError { t.Errorf("[%s] Stat returned unexpected error: %#+v != %#+v", tc.name, err, tc.expectedStatError) } if err != nil || tc.expectedStatError != nil { continue } err = ptbs.ServeBlob(ctx, w, req, dgst) if err != nil { t.Errorf("[%s] unexpected ServeBlob error: %v", tc.name, err) continue } clstr := w.Header().Get("Content-Length") if cl, err := strconv.ParseInt(clstr, 10, 64); err != nil { t.Errorf(`[%s] unexpected Content-Length: %q != "%d"`, tc.name, clstr, tc.expectedContentLength) } else { if cl != tc.expectedContentLength { t.Errorf("[%s] Content-Length does not match expected size: %d != %d", tc.name, cl, tc.expectedContentLength) } } if w.Header().Get("Content-Type") != "application/octet-stream" { t.Errorf("[%s] Content-Type does not match expected: %q != %q", tc.name, w.Header().Get("Content-Type"), "application/octet-stream") } body := w.Body.Bytes() if int64(len(body)) != tc.expectedBytesServed { t.Errorf("[%s] unexpected size of body: %d != %d", tc.name, len(body), tc.expectedBytesServed) } for name, expCount := range tc.expectedLocalCalls { count := localBlobStore.calls[name] if count != expCount { t.Errorf("[%s] expected %d calls to method %s of local blob store, not %d", tc.name, expCount, name, count) } } for name, count := range localBlobStore.calls { if _, exists := tc.expectedLocalCalls[name]; !exists { t.Errorf("[%s] expected no calls to method %s of local blob store, got %d", tc.name, name, count) } } if localBlobStore.bytesServed != tc.expectedBytesServedLocally { t.Errorf("[%s] unexpected number of bytes served locally: %d != %d", tc.name, localBlobStore.bytesServed, tc.expectedBytesServed) } } }
// TestBlobDescriptorServiceIsApplied ensures that blobDescriptorService middleware gets applied. // It relies on the fact that blobDescriptorService requires higher levels to set repository object on given // context. If the object isn't given, its method will err out. func TestBlobDescriptorServiceIsApplied(t *testing.T) { ctx := context.Background() // don't do any authorization check installFakeAccessController(t) m := fakeBlobDescriptorService(t) // to make other unit tests working defer m.changeUnsetRepository(false) testImage, err := registrytest.NewImageForManifest("user/app", registrytest.SampleImageManifestSchema1, true) if err != nil { t.Fatal(err) } testImageStream := registrytest.TestNewImageStreamObject("user", "app", "latest", testImage.Name, "") client := &testclient.Fake{} client.AddReactor("get", "imagestreams", imagetest.GetFakeImageStreamGetHandler(t, *testImageStream)) client.AddReactor("get", "images", registrytest.GetFakeImageGetHandler(t, *testImage)) // TODO: get rid of those nasty global vars backupRegistryClient := DefaultRegistryClient DefaultRegistryClient = makeFakeRegistryClient(client, fake.NewSimpleClientset()) defer func() { // set it back once this test finishes to make other unit tests working DefaultRegistryClient = backupRegistryClient }() app := handlers.NewApp(ctx, &configuration.Configuration{ Loglevel: "debug", Auth: map[string]configuration.Parameters{ fakeAuthorizerName: {"realm": fakeAuthorizerName}, }, Storage: configuration.Storage{ "inmemory": configuration.Parameters{}, "cache": configuration.Parameters{ "blobdescriptor": "inmemory", }, "delete": configuration.Parameters{ "enabled": true, }, }, Middleware: map[string][]configuration.Middleware{ "registry": {{Name: "openshift"}}, "repository": {{Name: "openshift"}}, "storage": {{Name: "openshift"}}, }, }) server := httptest.NewServer(app) router := v2.Router() serverURL, err := url.Parse(server.URL) if err != nil { t.Fatalf("error parsing server url: %v", err) } os.Setenv("DOCKER_REGISTRY_URL", serverURL.Host) desc, _, err := registrytest.UploadTestBlob(serverURL, nil, "user/app") if err != nil { t.Fatal(err) } for _, tc := range []struct { name string method string endpoint string vars []string unsetRepository bool expectedStatus int expectedMethodInvocations map[string]int }{ { name: "get blob with repository unset", method: http.MethodGet, endpoint: v2.RouteNameBlob, vars: []string{ "name", "user/app", "digest", desc.Digest.String(), }, unsetRepository: true, expectedStatus: http.StatusInternalServerError, expectedMethodInvocations: map[string]int{"Stat": 1}, }, { name: "get blob", method: http.MethodGet, endpoint: v2.RouteNameBlob, vars: []string{ "name", "user/app", "digest", desc.Digest.String(), }, expectedStatus: http.StatusOK, // 1st stat is invoked in (*distribution/registry/handlers.blobHandler).GetBlob() as a // check of blob existence // 2nd stat happens in (*errorBlobStore).ServeBlob() invoked by the same GetBlob handler // 3rd stat is done by (*blobServiceListener).ServeBlob once the blob serving is finished; // it may happen with a slight delay after the blob was served expectedMethodInvocations: map[string]int{"Stat": 3}, }, { name: "stat blob with repository unset", method: http.MethodHead, endpoint: v2.RouteNameBlob, vars: []string{ "name", "user/app", "digest", desc.Digest.String(), }, unsetRepository: true, expectedStatus: http.StatusInternalServerError, expectedMethodInvocations: map[string]int{"Stat": 1}, }, { name: "stat blob", method: http.MethodHead, endpoint: v2.RouteNameBlob, vars: []string{ "name", "user/app", "digest", desc.Digest.String(), }, expectedStatus: http.StatusOK, // 1st stat is invoked in (*distribution/registry/handlers.blobHandler).GetBlob() as a // check of blob existence // 2nd stat happens in (*errorBlobStore).ServeBlob() invoked by the same GetBlob handler // 3rd stat is done by (*blobServiceListener).ServeBlob once the blob serving is finished; // it may happen with a slight delay after the blob was served expectedMethodInvocations: map[string]int{"Stat": 3}, }, { name: "delete blob with repository unset", method: http.MethodDelete, endpoint: v2.RouteNameBlob, vars: []string{ "name", "user/app", "digest", desc.Digest.String(), }, unsetRepository: true, expectedStatus: http.StatusInternalServerError, expectedMethodInvocations: map[string]int{"Stat": 1}, }, { name: "delete blob", method: http.MethodDelete, endpoint: v2.RouteNameBlob, vars: []string{ "name", "user/app", "digest", desc.Digest.String(), }, expectedStatus: http.StatusAccepted, expectedMethodInvocations: map[string]int{"Stat": 1, "Clear": 1}, }, { name: "get manifest with repository unset", method: http.MethodGet, endpoint: v2.RouteNameManifest, vars: []string{ "name", "user/app", "reference", "latest", }, unsetRepository: true, // failed because we trying to get manifest from storage driver first. expectedStatus: http.StatusNotFound, // manifest can't be retrieved from etcd expectedMethodInvocations: map[string]int{"Stat": 1}, }, { name: "get manifest", method: http.MethodGet, endpoint: v2.RouteNameManifest, vars: []string{ "name", "user/app", "reference", "latest", }, expectedStatus: http.StatusOK, // manifest is retrieved from etcd expectedMethodInvocations: map[string]int{"Stat": 3}, }, { name: "delete manifest with repository unset", method: http.MethodDelete, endpoint: v2.RouteNameManifest, vars: []string{ "name", "user/app", "reference", testImage.Name, }, unsetRepository: true, expectedStatus: http.StatusInternalServerError, // we don't allow to delete manifests from etcd; in this case, we attempt to delete layer link expectedMethodInvocations: map[string]int{"Stat": 1}, }, { name: "delete manifest", method: http.MethodDelete, endpoint: v2.RouteNameManifest, vars: []string{ "name", "user/app", "reference", testImage.Name, }, expectedStatus: http.StatusNotFound, // we don't allow to delete manifests from etcd; in this case, we attempt to delete layer link expectedMethodInvocations: map[string]int{"Stat": 1}, }, } { m.clearStats() m.changeUnsetRepository(tc.unsetRepository) route := router.GetRoute(tc.endpoint).Host(serverURL.Host) u, err := route.URL(tc.vars...) if err != nil { t.Errorf("[%s] failed to build route: %v", tc.name, err) continue } req, err := http.NewRequest(tc.method, u.String(), nil) if err != nil { t.Errorf("[%s] failed to make request: %v", tc.name, err) } client := &http.Client{} resp, err := client.Do(req) if err != nil { t.Errorf("[%s] failed to do the request: %v", tc.name, err) continue } defer resp.Body.Close() if resp.StatusCode != tc.expectedStatus { t.Errorf("[%s] unexpected status code: %v != %v", tc.name, resp.StatusCode, tc.expectedStatus) } if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted { content, err := ioutil.ReadAll(resp.Body) if err != nil { t.Errorf("[%s] failed to read body: %v", tc.name, err) } else if len(content) > 0 { errs := errcode.Errors{} err := errs.UnmarshalJSON(content) if err != nil { t.Logf("[%s] failed to parse body as error: %v", tc.name, err) t.Logf("[%s] received body: %v", tc.name, string(content)) } else { t.Logf("[%s] received errors: %#+v", tc.name, errs) } } } stats, err := m.getStats(tc.expectedMethodInvocations, time.Second*5) if err != nil { t.Errorf("[%s] failed to get stats: %v", tc.name, err) } for method, exp := range tc.expectedMethodInvocations { invoked := stats[method] if invoked != exp { t.Errorf("[%s] unexpected number of invocations of method %q: %v != %v", tc.name, method, invoked, exp) } } for method, invoked := range stats { if _, ok := tc.expectedMethodInvocations[method]; !ok { t.Errorf("[%s] unexpected method %q invoked %d times", tc.name, method, invoked) } } } }
// Execute runs the Docker registry. func Execute(configFile io.Reader) { config, err := configuration.Parse(configFile) if err != nil { log.Fatalf("Error parsing configuration file: %s", err) } logLevel, err := log.ParseLevel(string(config.Log.Level)) if err != nil { log.Errorf("Error parsing log level %q: %s", config.Log.Level, err) logLevel = log.InfoLevel } log.SetLevel(logLevel) log.Infof("version=%s", version.Version) ctx := context.Background() app := handlers.NewApp(ctx, *config) // register OpenShift routes // TODO: change this to an anonymous Access record app.RegisterRoute(app.NewRoute().Path("/healthz"), server.HealthzHandler, handlers.NameNotRequired, handlers.NoCustomAccessRecords) // TODO add https scheme adminRouter := app.NewRoute().PathPrefix("/admin/").Subrouter() pruneAccessRecords := func(*http.Request) []auth.Access { return []auth.Access{ { Resource: auth.Resource{ Type: "admin", }, Action: "prune", }, } } app.RegisterRoute( // DELETE /admin/blobs/<digest> adminRouter.Path("/blobs/{digest:"+digest.DigestRegexp.String()+"}").Methods("DELETE"), // handler server.BlobDispatcher, // repo name not required in url handlers.NameNotRequired, // custom access records pruneAccessRecords, ) app.RegisterRoute( // DELETE /admin/<repo>/manifests/<digest> adminRouter.Path("/{name:"+v2.RepositoryNameRegexp.String()+"}/manifests/{digest:"+digest.DigestRegexp.String()+"}").Methods("DELETE"), // handler server.ManifestDispatcher, // repo name required in url handlers.NameRequired, // custom access records pruneAccessRecords, ) app.RegisterRoute( // DELETE /admin/<repo>/layers/<digest> adminRouter.Path("/{name:"+v2.RepositoryNameRegexp.String()+"}/layers/{digest:"+digest.DigestRegexp.String()+"}").Methods("DELETE"), // handler server.LayerDispatcher, // repo name required in url handlers.NameRequired, // custom access records pruneAccessRecords, ) handler := gorillahandlers.CombinedLoggingHandler(os.Stdout, app) 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 { tlsConf := &tls.Config{ ClientAuth: tls.NoClientCert, } if len(config.HTTP.TLS.ClientCAs) != 0 { pool := x509.NewCertPool() for _, ca := range config.HTTP.TLS.ClientCAs { caPem, err := ioutil.ReadFile(ca) if err != nil { context.GetLogger(app).Fatalln(err) } if ok := pool.AppendCertsFromPEM(caPem); !ok { context.GetLogger(app).Fatalln(fmt.Errorf("Could not add CA to pool")) } } for _, subj := range pool.Subjects() { context.GetLogger(app).Debugf("CA Subject: %s", string(subj)) } tlsConf.ClientAuth = tls.RequireAndVerifyClientCert 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, } if err := server.ListenAndServeTLS(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key); err != nil { context.GetLogger(app).Fatalln(err) } } }
func TestDocker(t *testing.T) { if os.Getuid() != 0 { t.Skip("pinkerton: must be root to create AUFS mounts") } // start Docker registry using test files cwd, err := os.Getwd() if err != nil { t.Fatal(err) } root := filepath.Join(cwd, "test", "files") config := configuration.Configuration{ Storage: configuration.Storage{ "filesystem": configuration.Parameters{ "rootdirectory": root, }, }, } logrus.SetLevel(logrus.ErrorLevel) app := handlers.NewApp(context.Background(), config) srv := httptest.NewServer(app) defer srv.Close() // create context tmp, err := ioutil.TempDir("", "pinkerton-test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmp) ctx, err := BuildContext("aufs", tmp) if err != nil { t.Fatal(err) } // pull image using digest imageID, err := ctx.PullDocker(srv.URL+"?name=pinkerton-test&id="+testImageDigest, ioutil.Discard) if err != nil { t.Fatal(err) } if imageID != testImageID { t.Fatalf("expected image to have ID %q, got %q", testImageID, imageID) } // checkout image name := random.String(8) path, err := ctx.Checkout(name, imageID) if err != nil { t.Fatal(err) } defer ctx.Cleanup(name) // check foo.txt exists and has correct data f, err := os.Open(filepath.Join(path, "foo.txt")) if err != nil { t.Fatal(err) } data, err := ioutil.ReadAll(f) f.Close() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(data, []byte(testImageData)) { t.Fatalf("expected foo.txt to contain %q, got %q", testImageData, string(data)) } }
// Execute runs the Docker registry. func Execute(configFile io.Reader) { config, err := configuration.Parse(configFile) if err != nil { log.Fatalf("Error parsing configuration file: %s", err) } tokenPath := "/openshift/token" // If needed, generate and populate the token realm URL in the config. // Must be done prior to instantiating the app, so our auth provider has the config available. _, usingOpenShiftAuth := config.Auth[server.OpenShiftAuth] _, hasTokenRealm := config.Auth[server.OpenShiftAuth][server.TokenRealmKey].(string) if usingOpenShiftAuth && !hasTokenRealm { registryHost := os.Getenv(server.DockerRegistryURLEnvVar) if len(registryHost) == 0 { log.Fatalf("%s is required", server.DockerRegistryURLEnvVar) } tokenURL := &url.URL{Scheme: "https", Host: registryHost, Path: tokenPath} if len(config.HTTP.TLS.Certificate) == 0 { tokenURL.Scheme = "http" } if config.Auth[server.OpenShiftAuth] == nil { config.Auth[server.OpenShiftAuth] = configuration.Parameters{} } config.Auth[server.OpenShiftAuth][server.TokenRealmKey] = tokenURL.String() } ctx := context.Background() ctx, err = configureLogging(ctx, config) if err != nil { log.Fatalf("error configuring logger: %v", err) } log.Infof("version=%s", version.Version) // inject a logger into the uuid library. warns us if there is a problem // with uuid generation under low entropy. uuid.Loggerf = context.GetLogger(ctx).Warnf app := handlers.NewApp(ctx, config) // Add a token handling endpoint if usingOpenShiftAuth { app.NewRoute().Methods("GET").PathPrefix(tokenPath).Handler(server.NewTokenHandler(ctx, server.DefaultRegistryClient)) } // TODO add https scheme adminRouter := app.NewRoute().PathPrefix("/admin/").Subrouter() pruneAccessRecords := func(*http.Request) []auth.Access { return []auth.Access{ { Resource: auth.Resource{ Type: "admin", }, Action: "prune", }, } } app.RegisterRoute( // DELETE /admin/blobs/<digest> adminRouter.Path("/blobs/{digest:"+reference.DigestRegexp.String()+"}").Methods("DELETE"), // handler server.BlobDispatcher, // repo name not required in url handlers.NameNotRequired, // custom access records pruneAccessRecords, ) app.RegisterHealthChecks() handler := alive("/", app) // TODO: temporarily keep for backwards compatibility; remove in the future handler = alive("/healthz", handler) handler = health.Handler(handler) handler = panicHandler(handler) handler = gorillahandlers.CombinedLoggingHandler(os.Stdout, handler) 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 { tlsConf := crypto.SecureTLSConfig(&tls.Config{ClientAuth: tls.NoClientCert}) if len(config.HTTP.TLS.ClientCAs) != 0 { pool := x509.NewCertPool() for _, ca := range config.HTTP.TLS.ClientCAs { caPem, err := ioutil.ReadFile(ca) if err != nil { context.GetLogger(app).Fatalln(err) } if ok := pool.AppendCertsFromPEM(caPem); !ok { context.GetLogger(app).Fatalln(fmt.Errorf("Could not add CA to pool")) } } for _, subj := range pool.Subjects() { context.GetLogger(app).Debugf("CA Subject: %s", string(subj)) } tlsConf.ClientAuth = tls.RequireAndVerifyClientCert 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, } if err := server.ListenAndServeTLS(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key); err != nil { context.GetLogger(app).Fatalln(err) } } }
// Execute runs the Docker registry. func Execute(configFile io.Reader) { config, err := configuration.Parse(configFile) if err != nil { log.Fatalf("Error parsing configuration file: %s", err) } ctx := context.Background() ctx, err = configureLogging(ctx, config) if err != nil { log.Fatalf("error configuring logger: %v", err) } log.Infof("version=%s", version.Version) // inject a logger into the uuid library. warns us if there is a problem // with uuid generation under low entropy. uuid.Loggerf = context.GetLogger(ctx).Warnf app := handlers.NewApp(ctx, config) // TODO add https scheme adminRouter := app.NewRoute().PathPrefix("/admin/").Subrouter() pruneAccessRecords := func(*http.Request) []auth.Access { return []auth.Access{ { Resource: auth.Resource{ Type: "admin", }, Action: "prune", }, } } app.RegisterRoute( // DELETE /admin/blobs/<digest> adminRouter.Path("/blobs/{digest:"+reference.DigestRegexp.String()+"}").Methods("DELETE"), // handler server.BlobDispatcher, // repo name not required in url handlers.NameNotRequired, // custom access records pruneAccessRecords, ) app.RegisterHealthChecks() handler := alive("/", app) // TODO: temporarily keep for backwards compatibility; remove in the future handler = alive("/healthz", handler) handler = health.Handler(handler) handler = panicHandler(handler) handler = gorillahandlers.CombinedLoggingHandler(os.Stdout, handler) 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 { tlsConf := crypto.SecureTLSConfig(&tls.Config{ClientAuth: tls.NoClientCert}) if len(config.HTTP.TLS.ClientCAs) != 0 { pool := x509.NewCertPool() for _, ca := range config.HTTP.TLS.ClientCAs { caPem, err := ioutil.ReadFile(ca) if err != nil { context.GetLogger(app).Fatalln(err) } if ok := pool.AppendCertsFromPEM(caPem); !ok { context.GetLogger(app).Fatalln(fmt.Errorf("Could not add CA to pool")) } } for _, subj := range pool.Subjects() { context.GetLogger(app).Debugf("CA Subject: %s", string(subj)) } tlsConf.ClientAuth = tls.RequireAndVerifyClientCert 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, } if err := server.ListenAndServeTLS(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key); err != nil { context.GetLogger(app).Fatalln(err) } } }
func main() { flag.Usage = usage flag.Parse() if showVersion { version.PrintVersion() return } ctx := context.Background() ctx = context.WithValue(ctx, "version", version.Version) config, err := resolveConfiguration() if err != nil { fatalf("configuration error: %v", err) } ctx, err = configureLogging(ctx, config) if err != nil { fatalf("error configuring logger: %v", err) } app := handlers.NewApp(ctx, *config) handler := configureReporting(app) handler = gorhandlers.CombinedLoggingHandler(os.Stdout, handler) if config.HTTP.Debug.Addr != "" { 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 { tlsConf := &tls.Config{ ClientAuth: tls.NoClientCert, } if len(config.HTTP.TLS.ClientCAs) != 0 { pool := x509.NewCertPool() for _, ca := range config.HTTP.TLS.ClientCAs { caPem, err := ioutil.ReadFile(ca) if err != nil { context.GetLogger(app).Fatalln(err) } if ok := pool.AppendCertsFromPEM(caPem); !ok { context.GetLogger(app).Fatalln(fmt.Errorf("Could not add CA to pool")) } } for _, subj := range pool.Subjects() { context.GetLogger(app).Debugf("CA Subject: %s", string(subj)) } tlsConf.ClientAuth = tls.RequireAndVerifyClientCert 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, } if err := server.ListenAndServeTLS(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key); err != nil { context.GetLogger(app).Fatalln(err) } } }