Skip to content

Commit

Permalink
Merge pull request #188 from jzelinskie/gateway
Browse files Browse the repository at this point in the history
Support HTTP/JSON via gRPC Gateway
  • Loading branch information
jzelinskie committed Oct 25, 2021
2 parents 5b5ace0 + e9b164a commit 4e70dde
Show file tree
Hide file tree
Showing 15 changed files with 372 additions and 88 deletions.
16 changes: 8 additions & 8 deletions cmd/spicedb/developer.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func developerServiceRun(cmd *cobra.Command, args []string) {
registerDeveloperGrpcServices(grpcServer, shareStore)

go func() {
addr := cobrautil.MustGetString(cmd, "grpc-addr")
addr := cobrautil.MustGetStringExpanded(cmd, "grpc-addr")
l, err := net.Listen("tcp", addr)
if err != nil {
log.Fatal().Str("addr", addr).Msg("failed to listen on addr for gRPC server")
Expand Down Expand Up @@ -99,8 +99,8 @@ func developerServiceRun(cmd *cobra.Command, args []string) {
}

func shareStoreFromCmd(cmd *cobra.Command) (v0svc.ShareStore, error) {
shareStoreSalt := cobrautil.MustGetString(cmd, "share-store-salt")
shareStoreKind := cobrautil.MustGetString(cmd, "share-store")
shareStoreSalt := cobrautil.MustGetStringExpanded(cmd, "share-store-salt")
shareStoreKind := cobrautil.MustGetStringExpanded(cmd, "share-store")
event := log.Info()

var shareStore v0svc.ShareStore
Expand All @@ -109,11 +109,11 @@ func shareStoreFromCmd(cmd *cobra.Command) (v0svc.ShareStore, error) {
shareStore = v0svc.NewInMemoryShareStore(shareStoreSalt)

case "s3":
bucketName := cobrautil.MustGetString(cmd, "s3-bucket")
accessKey := cobrautil.MustGetString(cmd, "s3-access-key")
secretKey := cobrautil.MustGetString(cmd, "s3-secret-key")
endpoint := cobrautil.MustGetString(cmd, "s3-endpoint")
region := stringz.DefaultEmpty(cobrautil.MustGetString(cmd, "s3-region"), "auto")
bucketName := cobrautil.MustGetStringExpanded(cmd, "s3-bucket")
accessKey := cobrautil.MustGetStringExpanded(cmd, "s3-access-key")
secretKey := cobrautil.MustGetStringExpanded(cmd, "s3-secret-key")
endpoint := cobrautil.MustGetStringExpanded(cmd, "s3-endpoint")
region := stringz.DefaultEmpty(cobrautil.MustGetStringExpanded(cmd, "s3-region"), "auto")

optsNames := []string{"s3-bucket", "s3-access-key", "s3-secret-key", "s3-endpoint"}
opts := []string{bucketName, accessKey, secretKey, endpoint}
Expand Down
6 changes: 3 additions & 3 deletions cmd/spicedb/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ func registerMigrateCmd(rootCmd *cobra.Command) {
}

func migrateRun(cmd *cobra.Command, args []string) {
datastoreEngine := cobrautil.MustGetString(cmd, "datastore-engine")
dbURL := cobrautil.MustGetString(cmd, "datastore-conn-uri")
datastoreEngine := cobrautil.MustGetStringExpanded(cmd, "datastore-engine")
dbURL := cobrautil.MustGetStringExpanded(cmd, "datastore-conn-uri")

if datastoreEngine == "cockroachdb" {
log.Info().Msg("migrating cockroachdb datastore")
Expand Down Expand Up @@ -81,7 +81,7 @@ func registerHeadCmd(rootCmd *cobra.Command) {
}

func headRevisionRun(cmd *cobra.Command, args []string) {
datastoreEngine := cobrautil.MustGetString(cmd, "datastore-engine")
datastoreEngine := cobrautil.MustGetStringExpanded(cmd, "datastore-engine")

var headRevision string
var err error
Expand Down
8 changes: 4 additions & 4 deletions cmd/spicedb/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ func newRootCmd() *cobra.Command {
PersistentPreRunE: persistentPreRunE,
TraverseChildren: true,
Example: fmt.Sprintf(` %s:
spicedb serve --grpc-preshared-key "somerandomkeyhere" --grpc-no-tls
spicedb serve --grpc-preshared-key "somerandomkeyhere" --grpc-no-tls --http-no-tls
%s:
spicedb serve --grpc-preshared-key "realkeyhere" --grpc-cert-path path/to/tls/cert
--grpc-key-path path/to/tls/key --datastore-engine postgres
--datastore-conn-uri "postgres-connection-string-here"
spicedb serve --grpc-preshared-key "realkeyhere" --grpc-cert-path path/to/tls/cert --grpc-key-path path/to/tls/key \
--http-cert-path path/to/tls/cert --http-key-path path/to/tls/key \
--datastore-engine postgres --datastore-conn-uri "postgres-connection-string-here"
%s:
spicedb serve-testing
`,
Expand Down
122 changes: 86 additions & 36 deletions cmd/spicedb/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"errors"
"fmt"
"net"
"net/http"
Expand Down Expand Up @@ -34,6 +35,7 @@ import (
"github.com/authzed/spicedb/internal/dispatch/client/consistentbackend"
"github.com/authzed/spicedb/internal/dispatch/graph"
"github.com/authzed/spicedb/internal/dispatch/remote"
"github.com/authzed/spicedb/internal/gateway"
"github.com/authzed/spicedb/internal/middleware/servicespecific"
"github.com/authzed/spicedb/internal/namespace"
"github.com/authzed/spicedb/internal/services"
Expand All @@ -50,12 +52,12 @@ func registerServeCmd(rootCmd *cobra.Command) {
PersistentPreRunE: persistentPreRunE,
Run: serveRun,
Example: fmt.Sprintf(` %s:
spicedb serve --grpc-preshared-key "somerandomkeyhere" --grpc-no-tls
spicedb serve --grpc-preshared-key "somerandomkeyhere" --grpc-no-tls --http-no-tls
%s:
spicedb serve --grpc-preshared-key "realkeyhere" --grpc-cert-path path/to/tls/cert
--grpc-key-path path/to/tls/key --datastore-engine postgres
--datastore-conn-uri "postgres-connection-string-here"
spicedb serve --grpc-preshared-key "realkeyhere" --grpc-cert-path path/to/tls/cert --grpc-key-path path/to/tls/key \
--http-cert-path path/to/tls/cert --http-key-path path/to/tls/key \
--datastore-engine postgres --datastore-conn-uri "postgres-connection-string-here"
`, color.YellowString("No TLS and in-memory"), color.GreenString("TLS and a real datastore")),
}

Expand Down Expand Up @@ -100,6 +102,12 @@ func registerServeCmd(rootCmd *cobra.Command) {
// Flags for internal dispatch API
serveCmd.Flags().String("internal-grpc-addr", ":50053", "address to listen for internal requests")

// Flags for HTTP gateway
serveCmd.Flags().String("http-addr", ":8443", "address to listen for HTTP API requests")
serveCmd.Flags().Bool("http-no-tls", false, "serve HTTP API requests unencrypted")
serveCmd.Flags().String("http-cert-path", "", "local path to the TLS certificate used to serve HTTP API requests")
serveCmd.Flags().String("http-key-path", "", "local path to the TLS key used to serve HTTP API requests")

// Flags for configuring dispatch behavior
serveCmd.Flags().Uint32("dispatch-max-depth", 50, "maximum recursion depth for nested calls")
serveCmd.Flags().String("dispatch-redispatch-dns-name", "", "dns SRV record name to resolve for remote redispatch, empty string disables redispatch")
Expand All @@ -122,21 +130,21 @@ func registerServeCmd(rootCmd *cobra.Command) {
}

func serveRun(cmd *cobra.Command, args []string) {
token := cobrautil.MustGetString(cmd, "grpc-preshared-key")
token := cobrautil.MustGetStringExpanded(cmd, "grpc-preshared-key")
if len(token) < 1 {
log.Fatal().Msg("a preshared key must be provided via --grpc-preshared-key to authenticate API requests")
}

datastoreEngine := cobrautil.MustGetString(cmd, "datastore-engine")
datastoreURI := cobrautil.MustGetString(cmd, "datastore-conn-uri")
datastoreEngine := cobrautil.MustGetStringExpanded(cmd, "datastore-engine")
datastoreURI := cobrautil.MustGetStringExpanded(cmd, "datastore-conn-uri")

revisionFuzzingTimedelta := cobrautil.MustGetDuration(cmd, "datastore-revision-fuzzing-duration")
gcWindow := cobrautil.MustGetDuration(cmd, "datastore-gc-window")
maxRetries := cobrautil.MustGetInt(cmd, "datastore-max-tx-retries")
overlapKey := cobrautil.MustGetString(cmd, "datastore-tx-overlap-key")
overlapStrategy := cobrautil.MustGetString(cmd, "datastore-tx-overlap-strategy")
overlapKey := cobrautil.MustGetStringExpanded(cmd, "datastore-tx-overlap-key")
overlapStrategy := cobrautil.MustGetStringExpanded(cmd, "datastore-tx-overlap-strategy")

splitQuerySize, err := units.ParseBase2Bytes(cobrautil.MustGetString(cmd, "datastore-query-split-size"))
splitQuerySize, err := units.ParseBase2Bytes(cobrautil.MustGetStringExpanded(cmd, "datastore-query-split-size"))
if err != nil {
log.Fatal().Err(err).Msg("failed to parse datastore-query-split-size")
}
Expand Down Expand Up @@ -271,13 +279,13 @@ func serveRun(cmd *cobra.Command, args []string) {
redispatch := graph.NewLocalOnlyDispatcher(nsm, ds)
redispatchClientCtx, redispatchClientCancel := context.WithCancel(context.Background())

redispatchTarget := cobrautil.MustGetString(cmd, "dispatch-redispatch-dns-name")
redispatchServiceName := cobrautil.MustGetString(cmd, "dispatch-redispatch-service-name")
redispatchTarget := cobrautil.MustGetStringExpanded(cmd, "dispatch-redispatch-dns-name")
redispatchServiceName := cobrautil.MustGetStringExpanded(cmd, "dispatch-redispatch-service-name")
if redispatchTarget != "" {
log.Info().Str("target", redispatchTarget).Msg("initializing remote redispatcher")

resolverAddr := cobrautil.MustGetString(cmd, "dispatch-peer-resolver-addr")
resolverCertPath := cobrautil.MustGetString(cmd, "dispatch-peer-resolver-cert-path")
resolverAddr := cobrautil.MustGetStringExpanded(cmd, "dispatch-peer-resolver-addr")
resolverCertPath := cobrautil.MustGetStringExpanded(cmd, "dispatch-peer-resolver-cert-path")
var resolverConfig *consistentbackend.EndpointResolverConfig
if resolverCertPath != "" {
log.Debug().Str("addr", resolverAddr).Str("cacert", resolverCertPath).Msg("using TLS protected peer resolver")
Expand All @@ -288,8 +296,8 @@ func serveRun(cmd *cobra.Command, args []string) {
}

peerCertPath := cobrautil.MustGetStringExpanded(cmd, "grpc-cert-path")
peerPSK := cobrautil.MustGetString(cmd, "grpc-preshared-key")
selfEndpoint := cobrautil.MustGetString(cmd, "internal-grpc-addr")
peerPSK := cobrautil.MustGetStringExpanded(cmd, "grpc-preshared-key")
selfEndpoint := cobrautil.MustGetStringExpanded(cmd, "internal-grpc-addr")

var endpointConfig *consistentbackend.EndpointConfig
var fallbackConfig *consistentbackend.FallbackEndpointConfig
Expand Down Expand Up @@ -321,6 +329,14 @@ func serveRun(cmd *cobra.Command, args []string) {
log.Fatal().Err(err).Msg("failed to initialize redispatcher cache")
}

internalDispatch := graph.NewDispatcher(cachingRedispatch, nsm, ds)
cachingInternalDispatch, err := caching.NewCachingDispatcher(internalDispatch, nil, "dispatch")
if err != nil {
log.Fatal().Err(err).Msg("failed to initialize internal dispatcher cache")
}

internaldispatch.RegisterGrpcServices(internalGrpcServer, cachingInternalDispatch)

prefixRequiredOption := v1alpha1svc.PrefixRequired
if !cobrautil.MustGetBool(cmd, "schema-prefixes-required") {
prefixRequiredOption = v1alpha1svc.PrefixNotRequired
Expand All @@ -331,19 +347,17 @@ func serveRun(cmd *cobra.Command, args []string) {
v1SchemaServiceOption = services.V1SchemaServiceDisabled
}

maxDepth := cobrautil.MustGetUint32(cmd, "dispatch-max-depth")
services.RegisterGrpcServices(grpcServer, ds, nsm, cachingRedispatch, maxDepth, prefixRequiredOption, v1SchemaServiceOption)

internalDispatch := graph.NewDispatcher(cachingRedispatch, nsm, ds)
cachingInternalDispatch, err := caching.NewCachingDispatcher(internalDispatch, nil, "dispatch")
if err != nil {
log.Fatal().Err(err).Msg("failed to initialize internal dispatcher cache")
}

internaldispatch.RegisterGrpcServices(internalGrpcServer, cachingInternalDispatch)

services.RegisterGrpcServices(
grpcServer,
ds,
nsm,
cachingRedispatch,
cobrautil.MustGetUint32(cmd, "dispatch-max-depth"),
prefixRequiredOption,
v1SchemaServiceOption,
)
go func() {
addr := cobrautil.MustGetString(cmd, "grpc-addr")
addr := cobrautil.MustGetStringExpanded(cmd, "grpc-addr")
l, err := net.Listen("tcp", addr)
if err != nil {
log.Fatal().Str("addr", addr).Msg("failed to listen on addr for gRPC server")
Expand All @@ -357,7 +371,7 @@ func serveRun(cmd *cobra.Command, args []string) {
}()

go func() {
addr := cobrautil.MustGetString(cmd, "internal-grpc-addr")
addr := cobrautil.MustGetStringExpanded(cmd, "internal-grpc-addr")
l, err := net.Listen("tcp", addr)
if err != nil {
log.Fatal().Str("addr", addr).Msg("failed to listen on addr for internal gRPC server")
Expand All @@ -370,28 +384,60 @@ func serveRun(cmd *cobra.Command, args []string) {
}
}()

// Start the REST gateway to serve HTTP/JSON.
gatewaySrv, err := gateway.NewHTTPServer(context.TODO(), gateway.Config{
Addr: cobrautil.MustGetStringExpanded(cmd, "http-addr"),
UpstreamAddr: cobrautil.MustGetStringExpanded(cmd, "grpc-addr"),
UpstreamTLSDisabled: cobrautil.MustGetBool(cmd, "grpc-no-tls"),
UpstreamTLSCertPath: cobrautil.MustGetStringExpanded(cmd, "grpc-cert-path"),
})
if err != nil {
log.Fatal().Err(err).Msg("failed to initialize rest gateway")
}
go func() {
log.Info().Str("addr", gatewaySrv.Addr).Msg("rest gateway server started listening")
if cobrautil.MustGetBool(cmd, "http-no-tls") {
if err := gatewaySrv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal().Err(err).Msg("failed while serving rest gateway")
}
} else {
certPath := cobrautil.MustGetStringExpanded(cmd, "http-cert-path")
keyPath := cobrautil.MustGetStringExpanded(cmd, "http-key-path")
if certPath == "" || keyPath == "" {
errStr := "failed to start http server: must provide either --http-no-tls or --http-cert-path and --http-key-path"
log.Fatal().Err(errors.New(errStr)).Msg("failed to create http server")
}

if err := gatewaySrv.ListenAndServeTLS(certPath, keyPath); err != http.ErrServerClosed {
log.Fatal().Err(err).Msg("failed while serving rest gateway")
}
}
}()

// Start the metrics endpoint.
metricsrv := cobrautil.MetricsServerFromFlags(cmd)
go func() {
addr := cobrautil.MustGetStringExpanded(cmd, "metrics-addr")
log.Info().Str("addr", addr).Msg("metrics server started listening")
if err := metricsrv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal().Err(err).Msg("failed while serving metrics")
}
}()

dashboardAddr := cobrautil.MustGetString(cmd, "dashboard-addr")
// Start a dashboard.
dashboardAddr := cobrautil.MustGetStringExpanded(cmd, "dashboard-addr")
dashboard := dashboard.NewDashboard(dashboardAddr, dashboard.Args{
GrpcNoTLS: cobrautil.MustGetBool(cmd, "grpc-no-tls"),
GrpcAddr: cobrautil.MustGetString(cmd, "grpc-addr"),
GrpcAddr: cobrautil.MustGetStringExpanded(cmd, "grpc-addr"),
DatastoreEngine: datastoreEngine,
}, ds)
if dashboardAddr != "" {
go func() {
log.Info().Str("addr", dashboardAddr).Msg("dashboard server started listening")
if err := dashboard.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal().Err(err).Msg("failed while serving dashboard")
}
}()

url := fmt.Sprintf("http://localhost%s", dashboardAddr)
log.Info().Str("url", url).Msg("dashboard running")
}

signalctx, _ := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
Expand All @@ -418,8 +464,8 @@ func serveRun(cmd *cobra.Command, args []string) {
internalGrpcServer.GracefulStop()
redispatchClientCancel()

if err := metricsrv.Close(); err != nil {
log.Fatal().Err(err).Msg("failed while shutting down metrics server")
if err := gatewaySrv.Close(); err != nil {
log.Fatal().Err(err).Msg("failed while shutting down rest gateway")
}

if err := nsm.Close(); err != nil {
Expand All @@ -430,6 +476,10 @@ func serveRun(cmd *cobra.Command, args []string) {
log.Fatal().Err(err).Msg("failed while shutting down datastore")
}

if err := metricsrv.Close(); err != nil {
log.Fatal().Err(err).Msg("failed while shutting down metrics server")
}

if dashboardAddr != "" {
if err := dashboard.Close(); err != nil {
log.Fatal().Err(err).Msg("failed while shutting down dashboard")
Expand Down
6 changes: 3 additions & 3 deletions cmd/spicedb/testserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func registerTestserverCmd(rootCmd *cobra.Command) {
}

func runTestServer(cmd *cobra.Command, args []string) {
configFilePaths := cobrautil.MustGetStringSlice(cmd, "load-configs")
configFilePaths := cobrautil.MustGetStringSliceExpanded(cmd, "load-configs")

backendMiddleware := &perTokenBackendMiddleware{
&sync.Map{},
Expand Down Expand Up @@ -87,7 +87,7 @@ func runTestServer(cmd *cobra.Command, args []string) {
}

go func() {
addr := cobrautil.MustGetString(cmd, "grpc-addr")
addr := cobrautil.MustGetStringExpanded(cmd, "grpc-addr")
l, err := net.Listen("tcp", addr)
if err != nil {
log.Fatal().Str("addr", addr).Msg("failed to listen on addr for gRPC server")
Expand All @@ -100,7 +100,7 @@ func runTestServer(cmd *cobra.Command, args []string) {
}()

go func() {
addr := cobrautil.MustGetString(cmd, "readonly-grpc-addr")
addr := cobrautil.MustGetStringExpanded(cmd, "readonly-grpc-addr")
l, err := net.Listen("tcp", addr)
if err != nil {
log.Fatal().Str("addr", addr).Msg("failed to listen on readonly addr for gRPC server")
Expand Down
9 changes: 5 additions & 4 deletions e2e/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ go 1.17

require (
github.com/authzed/authzed-go v0.1.1-0.20210923172306-b4b512e4d359
github.com/authzed/grpcutil v0.0.0-20210914195113-c0d8369e7e1f
github.com/authzed/grpcutil v0.0.0-20211020204402-aba1876830e6
github.com/authzed/spicedb v0.0.0
github.com/jackc/pgx/v4 v4.13.0
github.com/stretchr/testify v1.7.0
google.golang.org/grpc v1.41.0
)

require (
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/envoyproxy/protoc-gen-validate v0.6.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
Expand All @@ -28,10 +29,10 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect
golang.org/x/sys v0.0.0-20210921065528-437939a70204 // indirect
golang.org/x/net v0.0.0-20211020060615-d418f374d309 // indirect
golang.org/x/sys v0.0.0-20211020174200-9d6173849985 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af // indirect
google.golang.org/genproto v0.0.0-20211020151524-b7c3a969101a // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
Expand Down

0 comments on commit 4e70dde

Please sign in to comment.