// RootHandler returns the handler that routes all the paths from / for the // server. func RootHandler(ac auth.AccessController, ctx context.Context, trust signed.CryptoService) http.Handler { hand := utils.RootHandlerFactory(ac, ctx, trust) r := mux.NewRouter() r.Methods("GET").Path("/v2/").Handler(hand(handlers.MainHandler)) r.Methods("POST").Path("/v2/{imageName:.*}/_trust/tuf/").Handler( prometheus.InstrumentHandlerWithOpts( prometheusOpts("UpdateTuf"), hand(handlers.AtomicUpdateHandler, "push", "pull"))) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:root|targets(?:/[^/\\s]+)*|snapshot|timestamp}.{checksum:[a-fA-F0-9]{64}|[a-fA-F0-9]{96}|[a-fA-F0-9]{128}}.json").Handler( prometheus.InstrumentHandlerWithOpts( prometheusOpts("GetRoleByHash"), hand(handlers.GetHandler, "pull"))) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:root|targets(?:/[^/\\s]+)*|snapshot|timestamp}.json").Handler( prometheus.InstrumentHandlerWithOpts( prometheusOpts("GetRole"), hand(handlers.GetHandler, "pull"))) r.Methods("GET").Path( "/v2/{imageName:.*}/_trust/tuf/{tufRole:snapshot|timestamp}.key").Handler( prometheus.InstrumentHandlerWithOpts( prometheusOpts("GetKey"), hand(handlers.GetKeyHandler, "push", "pull"))) r.Methods("DELETE").Path("/v2/{imageName:.*}/_trust/tuf/").Handler( prometheus.InstrumentHandlerWithOpts( prometheusOpts("DeleteTuf"), hand(handlers.DeleteHandler, "push", "pull"))) r.Methods("GET").Path("/_notary_server/health").HandlerFunc(health.StatusHandler) r.Methods("GET").Path("/metrics").Handler(prometheus.Handler()) r.Methods("GET", "POST", "PUT", "HEAD", "DELETE").Path("/{other:.*}").Handler( hand(handlers.NotFoundHandler)) return r }
// RootHandler returns the handler that routes all the paths from / for the // server. func RootHandler(ac auth.AccessController, ctx context.Context, trust signed.CryptoService) http.Handler { hand := utils.RootHandlerFactory(ac, ctx, trust) r := mux.NewRouter() r.Methods("GET").Path("/v2/").Handler(hand(handlers.MainHandler)) r.Methods("POST").Path("/v2/{imageName:.*}/_trust/tuf/").Handler( prometheus.InstrumentHandlerWithOpts( prometheusOpts("UpdateTuf"), hand(handlers.AtomicUpdateHandler, "push", "pull"))) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:(root|targets|snapshot)}.json").Handler( prometheus.InstrumentHandlerWithOpts( prometheusOpts("GetRole"), hand(handlers.GetHandler, "pull"))) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/timestamp.json").Handler( prometheus.InstrumentHandlerWithOpts( prometheusOpts("GetTimestamp"), hand(handlers.GetTimestampHandler, "pull"))) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/timestamp.key").Handler( prometheus.InstrumentHandlerWithOpts( prometheusOpts("GetTimestampKey"), hand(handlers.GetTimestampKeyHandler, "push", "pull"))) r.Methods("DELETE").Path("/v2/{imageName:.*}/_trust/tuf/").Handler( prometheus.InstrumentHandlerWithOpts( prometheusOpts("DeleteTuf"), hand(handlers.DeleteHandler, "push", "pull"))) r.Methods("GET").Path("/_notary_server/health").Handler(hand( func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { health.StatusHandler(w, r) return nil })) r.Methods("GET").Path("/_notary_server/metrics").Handler(prometheus.Handler()) r.Methods("GET", "POST", "PUT", "HEAD", "DELETE").Path("/{other:.*}").Handler(hand(utils.NotFoundHandler)) return r }
func TestMainHandlerGet(t *testing.T) { hand := utils.RootHandlerFactory(nil, context.Background(), &signed.Ed25519{}) handler := hand(MainHandler) ts := httptest.NewServer(handler) defer ts.Close() _, err := http.Get(ts.URL) if err != nil { t.Fatalf("Received error on GET /: %s", err.Error()) } }
func TestMainHandlerNotGet(t *testing.T) { hand := utils.RootHandlerFactory(nil, context.Background(), &signed.Ed25519{}) handler := hand(MainHandler) ts := httptest.NewServer(handler) defer ts.Close() res, err := http.Head(ts.URL) if err != nil { t.Fatalf("Received error on GET /: %s", err.Error()) } if res.StatusCode != http.StatusNotFound { t.Fatalf("Expected 404, received %d", res.StatusCode) } }
func testPublish(t *testing.T, rootType data.KeyAlgorithm) { // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) assert.NoError(t, err, "failed to create a temporary directory: %s", err) gun := "docker.com/notary" // Set up server ctx := context.WithValue(context.Background(), "metaStore", storage.NewMemStorage()) // Do not pass one of the const KeyAlgorithms here as the value! Passing a // string is in itself good test that we are handling it correctly as we will // be receiving a string from the configuration. ctx = context.WithValue(ctx, "keyAlgorithm", "ecdsa") hand := utils.RootHandlerFactory(nil, ctx, cryptoservice.NewCryptoService("", trustmanager.NewKeyMemoryStore(passphraseRetriever))) r := mux.NewRouter() r.Methods("POST").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/").Handler(hand(handlers.AtomicUpdateHandler, "push", "pull")) r.Methods("GET").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/{tufRole:(root|targets|snapshot)}.json").Handler(hand(handlers.GetHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/timestamp.json").Handler(hand(handlers.GetTimestampHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/timestamp.key").Handler(hand(handlers.GetTimestampKeyHandler, "push", "pull")) //r.Methods("POST").Path("/v2/{imageName:" + server.RepositoryNameRegexp + "}/_trust/tuf/{tufRole:(root|targets|timestamp|snapshot)}.json").Handler(hand(handlers.UpdateHandler, "push", "pull")) r.Methods("DELETE").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/").Handler(hand(handlers.DeleteHandler, "push", "pull")) ts := httptest.NewServer(r) repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever) assert.NoError(t, err, "error creating repository: %s", err) rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String()) assert.NoError(t, err, "error generating root key: %s", err) rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID) assert.NoError(t, err, "error retreiving root key: %s", err) err = repo.Initialize(rootCryptoService) assert.NoError(t, err, "error creating repository: %s", err) // Add fixtures/intermediate-ca.crt as a target. There's no particular reason // for using this file except that it happens to be available as // a fixture. latestTarget, err := NewTarget("latest", "../fixtures/intermediate-ca.crt") assert.NoError(t, err, "error creating target") err = repo.AddTarget(latestTarget) assert.NoError(t, err, "error adding target") // Look for the changelist file changelistDirPath := filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "changelist") changelistDir, err := os.Open(changelistDirPath) assert.NoError(t, err, "could not open changelist directory") fileInfos, err := changelistDir.Readdir(0) assert.NoError(t, err, "could not read changelist directory") // Should only be one file in the directory assert.Len(t, fileInfos, 1, "wrong number of changelist files found") clName := fileInfos[0].Name() raw, err := ioutil.ReadFile(filepath.Join(changelistDirPath, clName)) assert.NoError(t, err, "could not read changelist file %s", clName) c := &changelist.TufChange{} err = json.Unmarshal(raw, c) assert.NoError(t, err, "could not unmarshal changelist file %s", clName) assert.EqualValues(t, changelist.ActionCreate, c.Actn) assert.Equal(t, "targets", c.Role) assert.Equal(t, "target", c.ChangeType) assert.Equal(t, "latest", c.ChangePath) assert.NotEmpty(t, c.Data) changelistDir.Close() // Create a second target currentTarget, err := NewTarget("current", "../fixtures/intermediate-ca.crt") assert.NoError(t, err, "error creating target") err = repo.AddTarget(currentTarget) assert.NoError(t, err, "error adding target") changelistDir, err = os.Open(changelistDirPath) assert.NoError(t, err, "could not open changelist directory") // There should now be a second file in the directory fileInfos, err = changelistDir.Readdir(0) assert.NoError(t, err, "could not read changelist directory") assert.Len(t, fileInfos, 2, "wrong number of changelist files found") newFileFound := false for _, fileInfo := range fileInfos { if fileInfo.Name() != clName { clName2 := fileInfo.Name() raw, err := ioutil.ReadFile(filepath.Join(changelistDirPath, clName2)) assert.NoError(t, err, "could not read changelist file %s", clName2) c := &changelist.TufChange{} err = json.Unmarshal(raw, c) assert.NoError(t, err, "could not unmarshal changelist file %s", clName2) assert.EqualValues(t, changelist.ActionCreate, c.Actn) assert.Equal(t, "targets", c.Role) assert.Equal(t, "target", c.ChangeType) assert.Equal(t, "current", c.ChangePath) assert.NotEmpty(t, c.Data) newFileFound = true break } } assert.True(t, newFileFound, "second changelist file not found") changelistDir.Close() // Now test Publish err = repo.Publish() assert.NoError(t, err) changelistDir, err = os.Open(changelistDirPath) assert.NoError(t, err, "could not open changelist directory") fileInfos, err = changelistDir.Readdir(0) assert.NoError(t, err, "could not read changelist directory") // Should only be one file in the directory assert.Len(t, fileInfos, 0, "wrong number of changelist files found") // Create a new repo and pull from the server tempBaseDir2, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir2) assert.NoError(t, err, "failed to create a temporary directory: %s", err) repo2, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever) assert.NoError(t, err, "error creating repository: %s", err) targets, err := repo2.ListTargets() assert.NoError(t, err) // Should be two targets assert.Len(t, targets, 2, "unexpected number of targets returned by ListTargets") if targets[0].Name == "latest" { assert.Equal(t, latestTarget, targets[0], "latest target does not match") assert.Equal(t, currentTarget, targets[1], "current target does not match") } else if targets[0].Name == "current" { assert.Equal(t, currentTarget, targets[0], "current target does not match") assert.Equal(t, latestTarget, targets[1], "latest target does not match") } else { t.Fatalf("unexpected target name: %s", targets[0].Name) } // Also test GetTargetByName newLatestTarget, err := repo2.GetTargetByName("latest") assert.NoError(t, err) assert.Equal(t, latestTarget, newLatestTarget, "latest target does not match") newCurrentTarget, err := repo2.GetTargetByName("current") assert.NoError(t, err) assert.Equal(t, currentTarget, newCurrentTarget, "current target does not match") }
// Run sets up and starts a TLS server that can be cancelled using the // given configuration. The context it is passed is the context it should // use directly for the TLS server, and generate children off for requests func Run(ctx context.Context, addr, tlsCertFile, tlsKeyFile string, trust signed.CryptoService, authMethod string, authOpts interface{}) error { tcpAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { return err } var lsnr net.Listener lsnr, err = net.ListenTCP("tcp", tcpAddr) if err != nil { return err } if tlsCertFile != "" && tlsKeyFile != "" { keypair, err := tls.LoadX509KeyPair(tlsCertFile, tlsKeyFile) if err != nil { return err } tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, PreferServerCipherSuites: true, CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, }, Certificates: []tls.Certificate{keypair}, Rand: rand.Reader, } logrus.Info("Enabling TLS") lsnr = tls.NewListener(lsnr, tlsConfig) } else if tlsCertFile != "" || tlsKeyFile != "" { return fmt.Errorf("Partial TLS configuration found. Either include both a cert and key file in the configuration, or include neither to disable TLS.") } var ac auth.AccessController if authMethod == "token" { authOptions, ok := authOpts.(map[string]interface{}) if !ok { return fmt.Errorf("auth.options must be a map[string]interface{}") } ac, err = auth.GetAccessController(authMethod, authOptions) if err != nil { return err } } hand := utils.RootHandlerFactory(ac, ctx, trust) r := mux.NewRouter() r.Methods("GET").Path("/v2/").Handler(hand(handlers.MainHandler)) r.Methods("POST").Path("/v2/{imageName:.*}/_trust/tuf/").Handler(hand(handlers.AtomicUpdateHandler, "push", "pull")) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:(root|targets|snapshot)}.json").Handler(hand(handlers.GetHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/timestamp.json").Handler(hand(handlers.GetTimestampHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/timestamp.key").Handler(hand(handlers.GetTimestampKeyHandler, "push", "pull")) r.Methods("DELETE").Path("/v2/{imageName:.*}/_trust/tuf/").Handler(hand(handlers.DeleteHandler, "push", "pull")) r.Methods("GET", "POST", "PUT", "HEAD", "DELETE").Path("/{other:.*}").Handler(hand(utils.NotFoundHandler)) svr := http.Server{ Addr: addr, Handler: r, } logrus.Info("Starting on ", addr) err = svr.Serve(lsnr) return err }
// Run sets up and starts a TLS server that can be cancelled using the // given configuration. The context it is passed is the context it should // use directly for the TLS server, and generate children off for requests func Run(ctx context.Context, addr, tlsCertFile, tlsKeyFile string, trust signed.CryptoService, authMethod string, authOpts interface{}) error { tcpAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { return err } var lsnr net.Listener lsnr, err = net.ListenTCP("tcp", tcpAddr) if err != nil { return err } keypair, err := tls.LoadX509KeyPair(tlsCertFile, tlsKeyFile) if err != nil { // must be able to run without certs. In prod, users may // want load balancer to terminate TLS logrus.Errorf("[Notary Server] Error loading TLS keys %s", err) } else { tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, PreferServerCipherSuites: true, CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, }, Certificates: []tls.Certificate{keypair}, Rand: rand.Reader, } lsnr = tls.NewListener(lsnr, tlsConfig) } var ac auth.AccessController if authMethod == "" { ac = nil } else { authOptions, ok := authOpts.(map[string]interface{}) if !ok { return fmt.Errorf("auth.options must be a map[string]interface{}") } ac, err = auth.GetAccessController(authMethod, authOptions) if err != nil { return err } } hand := utils.RootHandlerFactory(ac, ctx, trust) r := mux.NewRouter() r.Methods("GET").Path("/v2/").Handler(hand(handlers.MainHandler)) r.Methods("POST").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/").Handler(hand(handlers.AtomicUpdateHandler, "push", "pull")) r.Methods("GET").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/{tufRole:(root|targets|snapshot)}.json").Handler(hand(handlers.GetHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/timestamp.json").Handler(hand(handlers.GetTimestampHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/timestamp.key").Handler(hand(handlers.GetTimestampKeyHandler, "push", "pull")) r.Methods("DELETE").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/").Handler(hand(handlers.DeleteHandler, "push", "pull")) svr := http.Server{ Addr: addr, Handler: r, } logrus.Info("[Notary Server] : Starting on ", addr) err = svr.Serve(lsnr) return err }
func run(ctx context.Context, addr, tlsCertFile, tlsKeyFile string, trust signed.CryptoService) error { keypair, err := tls.LoadX509KeyPair(tlsCertFile, tlsKeyFile) if err != nil { logrus.Errorf("[Notary Server] Error loading keys %s", err) return err } tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, PreferServerCipherSuites: true, CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, }, Certificates: []tls.Certificate{keypair}, Rand: rand.Reader, } tcpAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { return err } lsnr, err := net.ListenTCP("tcp", tcpAddr) if err != nil { return err } tlsLsnr := tls.NewListener(lsnr, tlsConfig) var ac auth.AccessController = nil //ac, err := auth.GetAccessController("token", map[string]interface{}{}) //if err != nil { // return err //} hand := utils.RootHandlerFactory(ac, ctx, trust) r := mux.NewRouter() // TODO (endophage): use correct regexes for image and tag names r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:(root|targets|timestamp|snapshot)}.json").Handler(hand(handlers.GetHandler, "pull")) r.Methods("POST").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:(root|targets|timestamp|snapshot)}.json").Handler(hand(handlers.UpdateHandler, "push", "pull")) r.Methods("DELETE").Path("/v2/{imageName:.*}/_trust/tuf/").Handler(hand(handlers.DeleteHandler, "push", "pull")) svr := NewHTTPServer( http.Server{ Addr: addr, Handler: r, }, ) logrus.Info("[Notary Server] : Listening on", addr) go stopWatcher(ctx, svr, lsnr, tlsLsnr) err = svr.Serve(tlsLsnr) return err }
// RootHandler returns the handler that routes all the paths from / for the // server. func RootHandler(ctx context.Context, ac auth.AccessController, trust signed.CryptoService, consistent, current utils.CacheControlConfig, repoPrefixes []string) http.Handler { authWrapper := utils.RootHandlerFactory(ctx, ac, trust) createHandler := func(opts _serverEndpoint) http.Handler { var wrapped http.Handler wrapped = authWrapper(opts.ServerHandler, opts.PermissionsRequired...) if opts.IncludeCacheHeaders { wrapped = utils.WrapWithCacheHandler(opts.CacheControlConfig, wrapped) } wrapped = filterImagePrefixes(repoPrefixes, opts.ErrorIfGUNInvalid, wrapped) return prometheus.InstrumentHandlerWithOpts(prometheusOpts(opts.OperationName), wrapped) } invalidGUNErr := errors.ErrInvalidGUN.WithDetail(fmt.Sprintf("Require GUNs with prefix: %v", repoPrefixes)) notFoundError := errors.ErrMetadataNotFound.WithDetail(nil) r := mux.NewRouter() r.Methods("GET").Path("/v2/").Handler(authWrapper(handlers.MainHandler)) r.Methods("POST").Path("/v2/{imageName:[^*]+}/_trust/tuf/").Handler(createHandler(_serverEndpoint{ OperationName: "UpdateTUF", ErrorIfGUNInvalid: invalidGUNErr, ServerHandler: handlers.AtomicUpdateHandler, PermissionsRequired: []string{"push", "pull"}, })) r.Methods("GET").Path("/v2/{imageName:[^*]+}/_trust/tuf/{tufRole:root|targets(?:/[^/\\s]+)*|snapshot|timestamp}.{checksum:[a-fA-F0-9]{64}|[a-fA-F0-9]{96}|[a-fA-F0-9]{128}}.json").Handler(createHandler(_serverEndpoint{ OperationName: "GetRoleByHash", ErrorIfGUNInvalid: notFoundError, IncludeCacheHeaders: true, CacheControlConfig: consistent, ServerHandler: handlers.GetHandler, PermissionsRequired: []string{"pull"}, })) r.Methods("GET").Path("/v2/{imageName:[^*]+}/_trust/tuf/{tufRole:root|targets(?:/[^/\\s]+)*|snapshot|timestamp}.json").Handler(createHandler(_serverEndpoint{ OperationName: "GetRole", ErrorIfGUNInvalid: notFoundError, IncludeCacheHeaders: true, CacheControlConfig: current, ServerHandler: handlers.GetHandler, PermissionsRequired: []string{"pull"}, })) r.Methods("GET").Path( "/v2/{imageName:[^*]+}/_trust/tuf/{tufRole:snapshot|timestamp}.key").Handler(createHandler(_serverEndpoint{ OperationName: "GetKey", ErrorIfGUNInvalid: notFoundError, ServerHandler: handlers.GetKeyHandler, PermissionsRequired: []string{"push", "pull"}, })) r.Methods("POST").Path( "/v2/{imageName:[^*]+}/_trust/tuf/{tufRole:snapshot|timestamp}.key").Handler(createHandler(_serverEndpoint{ OperationName: "RotateKey", ErrorIfGUNInvalid: notFoundError, ServerHandler: handlers.RotateKeyHandler, PermissionsRequired: []string{"*"}, })) r.Methods("DELETE").Path("/v2/{imageName:[^*]+}/_trust/tuf/").Handler(createHandler(_serverEndpoint{ OperationName: "DeleteTUF", ErrorIfGUNInvalid: notFoundError, ServerHandler: handlers.DeleteHandler, PermissionsRequired: []string{"*"}, })) r.Methods("GET").Path("/v2/{imageName:[^*]+}/_trust/changefeed").Handler(createHandler(_serverEndpoint{ OperationName: "Changefeed", ErrorIfGUNInvalid: notFoundError, ServerHandler: handlers.Changefeed, PermissionsRequired: []string{"pull"}, })) r.Methods("GET").Path("/v2/_trust/changefeed").Handler(createHandler(_serverEndpoint{ OperationName: "Changefeed", ServerHandler: handlers.Changefeed, PermissionsRequired: []string{"*"}, })) r.Methods("GET").Path("/_notary_server/health").HandlerFunc(health.StatusHandler) r.Methods("GET").Path("/metrics").Handler(prometheus.Handler()) r.Methods("GET", "POST", "PUT", "HEAD", "DELETE").Path("/{other:.*}").Handler( authWrapper(handlers.NotFoundHandler)) return r }
func TestRotate(t *testing.T) { // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) assert.NoError(t, err, "failed to create a temporary directory: %s", err) gun := "docker.com/notary" // Set up server ctx := context.WithValue(context.Background(), "metaStore", storage.NewMemStorage()) // Do not pass one of the const KeyAlgorithms here as the value! Passing a // string is in itself good test that we are handling it correctly as we will // be receiving a string from the configuration. ctx = context.WithValue(ctx, "keyAlgorithm", "ecdsa") hand := utils.RootHandlerFactory(nil, ctx, cryptoservice.NewCryptoService("", trustmanager.NewKeyMemoryStore(passphraseRetriever))) r := mux.NewRouter() r.Methods("POST").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/").Handler(hand(handlers.AtomicUpdateHandler, "push", "pull")) r.Methods("GET").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/{tufRole:(root|targets|snapshot)}.json").Handler(hand(handlers.GetHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/timestamp.json").Handler(hand(handlers.GetTimestampHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/timestamp.key").Handler(hand(handlers.GetTimestampKeyHandler, "push", "pull")) //r.Methods("POST").Path("/v2/{imageName:" + server.RepositoryNameRegexp + "}/_trust/tuf/{tufRole:(root|targets|timestamp|snapshot)}.json").Handler(hand(handlers.UpdateHandler, "push", "pull")) r.Methods("DELETE").Path("/v2/{imageName:" + v2.RepositoryNameRegexp.String() + "}/_trust/tuf/").Handler(hand(handlers.DeleteHandler, "push", "pull")) ts := httptest.NewServer(r) repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever) assert.NoError(t, err, "error creating repository: %s", err) rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey) assert.NoError(t, err, "error generating root key: %s", err) err = repo.Initialize(rootKeyID) assert.NoError(t, err, "error creating repository: %s", err) // Add fixtures/intermediate-ca.crt as a target. There's no particular reason // for using this file except that it happens to be available as // a fixture. // Adding a target will allow us to confirm the repository is still valid after // rotating the keys. latestTarget, err := NewTarget("latest", "../fixtures/intermediate-ca.crt") assert.NoError(t, err, "error creating target") err = repo.AddTarget(latestTarget) assert.NoError(t, err, "error adding target") // Publish err = repo.Publish() assert.NoError(t, err) // Get root.json and capture targets + snapshot key IDs repo.GetTargetByName("latest") // force a pull targetsKeyIDs := repo.tufRepo.Root.Signed.Roles["targets"].KeyIDs snapshotKeyIDs := repo.tufRepo.Root.Signed.Roles["snapshot"].KeyIDs assert.Len(t, targetsKeyIDs, 1) assert.Len(t, snapshotKeyIDs, 1) // Do rotation repo.RotateKeys() // Publish err = repo.Publish() assert.NoError(t, err) // Get root.json. Check targets + snapshot keys have changed // and that they match those found in the changelist. _, err = repo.GetTargetByName("latest") // force a pull assert.NoError(t, err) newTargetsKeyIDs := repo.tufRepo.Root.Signed.Roles["targets"].KeyIDs newSnapshotKeyIDs := repo.tufRepo.Root.Signed.Roles["snapshot"].KeyIDs assert.Len(t, newTargetsKeyIDs, 1) assert.Len(t, newSnapshotKeyIDs, 1) assert.NotEqual(t, targetsKeyIDs[0], newTargetsKeyIDs[0]) assert.NotEqual(t, snapshotKeyIDs[0], newSnapshotKeyIDs[0]) // Confirm changelist dir empty after publishing changes // Look for the changelist file changelistDirPath := filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "changelist") changelistDir, err := os.Open(changelistDirPath) assert.NoError(t, err, "could not open changelist directory") fileInfos, err := changelistDir.Readdir(0) assert.NoError(t, err, "could not read changelist directory") // Should only be one file in the directory assert.Len(t, fileInfos, 0, "wrong number of changelist files found") }
// Run sets up and starts a TLS server that can be cancelled using the // given configuration. The context it is passed is the context it should // use directly for the TLS server, and generate children off for requests func Run(ctx context.Context, addr, tlsCertFile, tlsKeyFile string, trust signed.CryptoService) error { tcpAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { return err } var lsnr net.Listener lsnr, err = net.ListenTCP("tcp", tcpAddr) if err != nil { return err } keypair, err := tls.LoadX509KeyPair(tlsCertFile, tlsKeyFile) if err != nil { // must be able to run without certs. In prod, users may // want load balancer to terminate TLS logrus.Errorf("[Notary Server] Error loading TLS keys %s", err) } else { tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, PreferServerCipherSuites: true, CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, }, Certificates: []tls.Certificate{keypair}, Rand: rand.Reader, } lsnr = tls.NewListener(lsnr, tlsConfig) } var ac auth.AccessController = nil hand := utils.RootHandlerFactory(ac, ctx, trust) r := mux.NewRouter() // TODO (endophage): use correct regexes for image and tag names r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:(root|targets|snapshot)}.json").Handler(hand(handlers.GetHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/timestamp.json").Handler(hand(handlers.GetTimestampHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/timestamp.key").Handler(hand(handlers.GetTimestampKeyHandler, "push", "pull")) r.Methods("POST").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:(root|targets|timestamp|snapshot)}.json").Handler(hand(handlers.UpdateHandler, "push", "pull")) r.Methods("DELETE").Path("/v2/{imageName:.*}/_trust/tuf/").Handler(hand(handlers.DeleteHandler, "push", "pull")) svr := http.Server{ Addr: addr, Handler: r, } logrus.Error("[Notary Server] : Starting on ", addr) err = svr.Serve(lsnr) return err }