Skip to content

Commit

Permalink
feat: switch to gRPC gateway
Browse files Browse the repository at this point in the history
  • Loading branch information
hperl committed Feb 21, 2023
1 parent c20f53a commit 33496b2
Show file tree
Hide file tree
Showing 53 changed files with 2,045 additions and 1,340 deletions.
1 change: 1 addition & 0 deletions .schema/openapi/patches/replacements.sed
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
s/ory.keto.relation_tuples.v1alpha2.ErrorResponse/errorGeneric/g
s/ory.keto.relation_tuples.v1alpha2.ErrorResponse.Error/genericError/g
s/ory.keto.relation_tuples.v1alpha2.CreateRelationTupleRequest.Relationship/createRelationshipBody/g
s/ory.keto.relation_tuples.v1alpha2.SubjectSet/subjectSet/g
s/ory.keto.relation_tuples.v1alpha2.SubjectTree/expandedPermissionTree/g
Expand Down
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,22 @@ docker:
.PHONY: sdk
sdk: buf .bin/swagger .bin/ory node_modules
rm -rf internal/httpclient
cp proto/openapiv2/gateway.swagger.json spec/swagger.json
# cp proto/openapiv2/gateway.swagger.json spec/swagger.json
# swagger generate spec -m -o spec/swagger.json \
# -c github.com/ory/keto \
# -c github.com/ory/x/healthx \
# -x internal/httpclient \
# -x internal/e2e
.bin/ory dev swagger sanitize ./spec/swagger.json
sed -i -f ./.schema/openapi/patches/replacements.sed ./spec/swagger.json
swagger validate ./spec/swagger.json
.bin/ory dev swagger sanitize ./spec/api.swagger.json
sed -i -f ./.schema/openapi/patches/replacements.sed ./spec/api.swagger.json
swagger validate ./spec/api.swagger.json
CIRCLE_PROJECT_USERNAME=ory CIRCLE_PROJECT_REPONAME=keto \
.bin/ory dev openapi migrate \
--health-path-tags metadata \
-p file://.schema/openapi/patches/health.yaml \
-p file://.schema/openapi/patches/meta.yaml \
-p file://.schema/openapi/patches/checkServices.yaml \
spec/swagger.json spec/api.json
spec/api.swagger.json spec/api.json

mkdir -p internal/httpclient

Expand Down
8 changes: 6 additions & 2 deletions buf.gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ plugins:
out: proto
opt: paths=source_relative,require_unimplemented_servers=false

- plugin: buf.build/bufbuild/validate-go
out: proto
opt: paths=source_relative

- plugin: buf.build/protocolbuffers/js
out: proto
opt: import_style=commonjs,binary
Expand All @@ -29,13 +33,13 @@ plugins:
- remote: buf.build/grpc-ecosystem/plugins/openapiv2:v2.14.0-1
opt:
- allow_merge=true
- merge_file_name=gateway
- merge_file_name=api
- openapi_naming_strategy=fqn
- visibility_restriction_selectors=PUBLIC
- disable_service_tags=true
- disable_default_errors=true
- disable_default_responses=true
out: proto/openapiv2
out: spec

- remote: buf.build/sawadashota/plugins/protoc-gen-doc:v1.5.1
out: proto
Expand Down
9 changes: 5 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ require (
github.com/gobuffalo/pop/v6 v6.1.1
github.com/gofrs/uuid v4.4.0+incompatible
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0
github.com/julienschmidt/httprouter v1.3.0
github.com/luna-duclos/instrumentedsql v1.1.3
github.com/ory/analytics-go/v4 v4.0.3
github.com/ory/graceful v0.1.3
github.com/ory/herodot v0.9.13
github.com/ory/herodot v0.9.14-0.20230221151357-8771415caa58
github.com/ory/jsonschema/v3 v3.0.7
github.com/ory/keto/proto v0.10.0-alpha.0
github.com/ory/keto/proto v0.10.0-alpha.0.pre.1
github.com/ory/x v0.0.534
github.com/pelletier/go-toml v1.9.5
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
Expand All @@ -41,6 +42,7 @@ require (
golang.org/x/exp v0.0.0-20230131160201-f062dba9d201
golang.org/x/oauth2 v0.5.0
golang.org/x/sync v0.1.0
google.golang.org/genproto v0.0.0-20230131230820-1c016267d619
google.golang.org/grpc v1.53.0
google.golang.org/protobuf v1.28.1
)
Expand Down Expand Up @@ -68,6 +70,7 @@ require (
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/envoyproxy/protoc-gen-validate v0.9.1 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/fatih/color v1.14.1 // indirect
github.com/fatih/structs v1.1.0 // indirect
Expand Down Expand Up @@ -96,7 +99,6 @@ require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.4.0 // indirect
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
Expand Down Expand Up @@ -181,7 +183,6 @@ require (
golang.org/x/tools v0.5.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.9.1 h1:PS7VIOgmSVhWUEeZwTe7z7zouA22Cr590PzXKbZHOVY=
github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
Expand Down Expand Up @@ -639,6 +641,8 @@ github.com/ory/graceful v0.1.3 h1:FaeXcHZh168WzS+bqruqWEw/HgXWLdNv2nJ+fbhxbhc=
github.com/ory/graceful v0.1.3/go.mod h1:4zFz687IAF7oNHHiB586U4iL+/4aV09o/PYLE34t2bA=
github.com/ory/herodot v0.9.13 h1:cN/Z4eOkErl/9W7hDIDLb79IO/bfsH+8yscBjRpB4IU=
github.com/ory/herodot v0.9.13/go.mod h1:IWDs9kSvFQqw/cQ8zi5ksyYvITiUU4dI7glUrhZcJYo=
github.com/ory/herodot v0.9.14-0.20230221151357-8771415caa58 h1:bhqc664AI9K8ZT4ArUFUrs54Yy057WcO8ZzLzwtnqo4=
github.com/ory/herodot v0.9.14-0.20230221151357-8771415caa58/go.mod h1:pgbrEh43b0Gl1ZI8UC3qxkKWXH4RFX5wTX/RTydKqmM=
github.com/ory/jsonschema/v3 v3.0.7 h1:GQ9qfZDiJqs4l2d3p56dozCChvejQFZyLKGHYzDzOSo=
github.com/ory/jsonschema/v3 v3.0.7/go.mod h1:g8c8YOtN4TrR2wYeMdT02GDmzJDI0fEW2nI26BECafY=
github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM=
Expand Down
38 changes: 23 additions & 15 deletions internal/check/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/pkg/errors"
"google.golang.org/grpc/metadata"

"github.com/ory/herodot"

Expand Down Expand Up @@ -67,7 +68,7 @@ func (h *Handler) RegisterReadGRPCGateway(ctx context.Context, mux *runtime.Serv
return rts.RegisterCheckServiceHandlerFromEndpoint(ctx, mux, endpoint, opts)
}
func (h *Handler) RegisterReadGRPCGatewayConn(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return rts.RegisterReadServiceHandler(ctx, mux, conn)
return rts.RegisterCheckServiceHandler(ctx, mux, conn)
}

// Check Permission Result
Expand Down Expand Up @@ -305,31 +306,38 @@ func (h *Handler) postCheck(ctx context.Context, body io.Reader, query url.Value
return h.d.PermissionEngine().CheckIsMember(ctx, t[0], maxDepth)
}

func (h *Handler) Check(ctx context.Context, req *rts.CheckRequest) (*rts.CheckResponse, error) {
var src ketoapi.TupleData
if req.Tuple != nil {
src = req.Tuple
} else {
src = req
func (h *Handler) Check(ctx context.Context, req *rts.CheckRequest) (res *rts.CheckResponse, err error) {
tuple := (&ketoapi.RelationTuple{}).FromCheckRequest(req)

// Check if we should set the HTTP status code to 403 instead of 200 if the check fails.
if md, ok := metadata.FromIncomingContext(ctx); ok {
path := md["path"]
if len(path) > 0 && path[0] == RouteBase {
defer func() {
if res != nil && !res.Allowed {
_ = grpc.SetHeader(ctx, metadata.Pairs("x-http-code", "403"))
}
}()
}
}

tuple, err := (&ketoapi.RelationTuple{}).FromDataProvider(src)
if err != nil {
return nil, err
if tuple.SubjectID == nil && tuple.SubjectSet == nil {
return nil, ketoapi.ErrNilSubject
}

internalTuple, err := h.d.ReadOnlyMapper().FromTuple(ctx, tuple)
if errors.Is(err, herodot.ErrNotFound) {
res = &rts.CheckResponse{Allowed: false}
return res, nil
}
if err != nil {
return nil, err
}
allowed, err := h.d.PermissionEngine().CheckIsMember(ctx, internalTuple[0], int(req.MaxDepth))
// TODO add content change handling
if err != nil {
return nil, err
}

return &rts.CheckResponse{
Allowed: allowed,
Snaptoken: "not yet implemented",
}, nil
res = &rts.CheckResponse{Allowed: allowed}
return res, nil
}
14 changes: 4 additions & 10 deletions internal/check/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"context"
"io"
"net/http"
"net/http/httptest"
"net/url"
"testing"

Expand All @@ -17,7 +16,6 @@ import (

"github.com/ory/keto/internal/driver/config"

"github.com/julienschmidt/httprouter"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
Expand Down Expand Up @@ -60,19 +58,15 @@ func openAPIAssertDenied(t *testing.T, resp *http.Response) {
}

func TestRESTHandler(t *testing.T) {
nspaces := []*namespace.Namespace{{
Name: "check handler",
}}
nspaces := []*namespace.Namespace{{Name: "check handler"}}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
reg := driver.NewSqliteTestRegistry(t, false)
require.NoError(t, reg.Config(ctx).Set(config.KeyNamespaces, nspaces))
h := check.NewHandler(reg)
r := httprouter.New()
h.RegisterReadRoutes(&x.ReadRouter{Router: r})
ts := httptest.NewServer(r)
defer ts.Close()

endpoints := x.NewTestEndpoints(t, check.NewHandler(reg))
ts := endpoints.HTTP

for _, suite := range []struct {
name string
Expand Down
34 changes: 30 additions & 4 deletions internal/driver/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,16 @@ func (r *RegistryDefault) ReadRouter(ctx context.Context) http.Handler {
}
n.Use(reqlog.NewMiddlewareFromLogger(r.l, "read#Ory Keto").ExcludePaths(healthx.AliveCheckPath, healthx.ReadyCheckPath))

mux := runtime.NewServeMux(runtime.WithForwardResponseOption(x.HttpResponseModifier))
conn, err := grpc.DialContext(ctx, r.Config(ctx).ReadAPIListenOn(), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
healthClient := grpcHealthV1.NewHealthClient(conn)
mux := runtime.NewServeMux(append(
x.GRPCGatewayMuxOptions,
runtime.WithHealthEndpointAt(healthClient, healthx.ReadyCheckPath),
runtime.WithHealthEndpointAt(healthClient, healthx.AliveCheckPath),
)...)
for _, h := range r.allHandlers() {
if h, ok := h.(ReadHandler); ok {
if err := h.RegisterReadGRPCGateway(ctx, mux, r.Config(ctx).ReadAPIListenOn(), grpc.WithTransportCredentials(insecure.NewCredentials())); err != nil {
Expand All @@ -353,7 +362,6 @@ func (r *RegistryDefault) ReadRouter(ctx context.Context) http.Handler {
r.PrometheusManager().RegisterRouter(br.Router)
r.MetricsHandler().SetRoutes(br.Router)

r.HealthHandler().SetHealthRoutes(br.Router, false)
r.HealthHandler().SetVersionRoutes(br.Router)

n.UseHandler(&RouterOrGatewayHandler{Router: br.Router, ServeMux: mux})
Expand All @@ -379,7 +387,16 @@ func (r *RegistryDefault) WriteRouter(ctx context.Context) http.Handler {
}
n.Use(reqlog.NewMiddlewareFromLogger(r.l, "write#Ory Keto").ExcludePaths(healthx.AliveCheckPath, healthx.ReadyCheckPath))

mux := runtime.NewServeMux(runtime.WithForwardResponseOption(x.HttpResponseModifier))
conn, err := grpc.DialContext(ctx, r.Config(ctx).WriteAPIListenOn(), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
healthClient := grpcHealthV1.NewHealthClient(conn)
mux := runtime.NewServeMux(append(
x.GRPCGatewayMuxOptions,
runtime.WithHealthEndpointAt(healthClient, healthx.ReadyCheckPath),
runtime.WithHealthEndpointAt(healthClient, healthx.AliveCheckPath),
)...)
for _, h := range r.allHandlers() {
if h, ok := h.(WriteHandler); ok {
if err := h.RegisterWriteGRPCGateway(ctx, mux, r.Config(ctx).WriteAPIListenOn(), grpc.WithTransportCredentials(insecure.NewCredentials())); err != nil {
Expand Down Expand Up @@ -425,7 +442,16 @@ func (r *RegistryDefault) OPLSyntaxRouter(ctx context.Context) http.Handler {
r.HealthHandler().SetHealthRoutes(pr.Router, false)
r.HealthHandler().SetVersionRoutes(pr.Router)

mux := runtime.NewServeMux(runtime.WithForwardResponseOption(x.HttpResponseModifier))
conn, err := grpc.DialContext(ctx, r.Config(ctx).OPLSyntaxAPIListenOn(), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
healthClient := grpcHealthV1.NewHealthClient(conn)
mux := runtime.NewServeMux(append(
x.GRPCGatewayMuxOptions,
runtime.WithHealthEndpointAt(healthClient, healthx.ReadyCheckPath),
runtime.WithHealthEndpointAt(healthClient, healthx.AliveCheckPath),
)...)
for _, h := range r.allHandlers() {
if h, ok := h.(OPLSyntaxHandler); ok {
if err := h.RegisterSyntaxGRPCGateway(ctx, mux, r.Config(ctx).OPLSyntaxAPIListenOn(), grpc.WithTransportCredentials(insecure.NewCredentials())); err != nil {
Expand Down
5 changes: 3 additions & 2 deletions internal/e2e/full_suit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func Test(t *testing.T) {
}
}

func TestServeConfig(t *testing.T) {
func TestServeCORS(t *testing.T) {
t.Parallel()

ctx, reg, _ := newInitializedReg(t, dbx.GetSqlite(t, dbx.SQLiteMemory), map[string]interface{}{
Expand All @@ -150,8 +150,9 @@ func TestServeConfig(t *testing.T) {
req, err := http.NewRequest(http.MethodOptions, "http://"+reg.Config(ctx).ReadAPIListenOn()+relationtuple.ReadRouteBase, nil)
require.NoError(t, err)
req.Header.Set("Origin", "https://ory.sh")
req.Header.Set("Access-Control-Request-Method", http.MethodGet)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
require.Equal(t, http.StatusNoContent, resp.StatusCode)
assert.Equal(t, "https://ory.sh", resp.Header.Get("Access-Control-Allow-Origin"), "%+v", resp.Header)
}
2 changes: 1 addition & 1 deletion internal/e2e/grpc_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ func (g *grpcClient) transactTuples(t require.TestingT, ins []*ketoapi.RelationT
func (g *grpcClient) oplCheckSyntax(t require.TestingT, content []byte) (parseErrors []*ketoapi.ParseError) {
c := opl.NewSyntaxServiceClient(g.oplSyntaxConn(t))

res, err := c.Check(g.ctx, &opl.CheckRequest{Content: string(content)})
res, err := c.Check(g.ctx, &opl.CheckRequest{Content: content})
require.NoError(t, err)

raw, err := json.Marshal(res.Errors)
Expand Down
14 changes: 7 additions & 7 deletions internal/e2e/rest_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import (
"strconv"
"time"

"github.com/ory/x/healthx"

"github.com/ory/keto/internal/schema"
"github.com/ory/keto/ketoapi"

"github.com/ory/herodot"
"github.com/tidwall/gjson"

"github.com/ory/x/healthx"

"github.com/ory/keto/internal/x"

"github.com/stretchr/testify/assert"
Expand All @@ -45,7 +45,9 @@ func (rc *restClient) queryNamespaces(t require.TestingT) (res ketoapi.GetNamesp
}

func (rc *restClient) oplCheckSyntax(t require.TestingT, content []byte) []*ketoapi.ParseError {
body, code := rc.makeRequest(t, http.MethodPost, schema.RouteBase, string(content), rc.oplSyntaxURL)
enc, err := json.Marshal(content)
require.NoError(t, err)
body, code := rc.makeRequest(t, http.MethodPost, schema.RouteBase, string(enc), rc.oplSyntaxURL)
assert.Equal(t, http.StatusOK, code, body)
var response ketoapi.CheckOPLSyntaxResponse
require.NoError(t, json.Unmarshal([]byte(body), &response))
Expand Down Expand Up @@ -168,10 +170,8 @@ func (rc *restClient) expand(t require.TestingT, r *ketoapi.SubjectSet, depth in
return tree
}

func healthReady(t require.TestingT, readURL string) bool {
req, err := http.NewRequest("GET", readURL+healthx.ReadyCheckPath, nil)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
func healthReady(_ require.TestingT, readURL string) bool {
resp, err := http.Get(readURL + healthx.ReadyCheckPath)
if err != nil {
return false
}
Expand Down
6 changes: 4 additions & 2 deletions internal/e2e/sdk_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ func (c *sdkClient) requestCtx() context.Context {
}

func (c *sdkClient) oplCheckSyntax(t require.TestingT, content []byte) (parseErrors []*ketoapi.ParseError) {
body, err := json.Marshal(content)
require.NoError(t, err)
res, _, err := c.getOPLSyntaxClient().
RelationshipApi.
CheckOplSyntax(c.requestCtx()).
Body(string(content)).
Body(string(body)).
Execute()
require.NoError(t, err)

Expand Down Expand Up @@ -286,7 +288,7 @@ func (c *sdkClient) waitUntilLive(t require.TestingT) {
for err != nil {
resp, _, err = c.getReadClient().MetadataApi.IsReady(c.requestCtx()).Execute()
}
require.Equal(t, "ok", resp.Status)
require.Equal(t, "SERVING", resp.Status)
}

func (c *sdkClient) queryNamespaces(t require.TestingT) (response ketoapi.GetNamespacesResponse) {
Expand Down
Loading

0 comments on commit 33496b2

Please sign in to comment.