Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve tracing for permissions checks #156

Merged
merged 5 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .devcontainer/.env
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ IDENTITYAPI_TRACING_JAEGER_ENDPOINT=http://localhost:14268/api/traces
IDENTITYAPI_CRDB_URI="postgresql://root@crdb:26257/identityapi_dev?sslmode=disable"

PERMISSIONSAPI_TRACING_ENABLED=true
PERMISSIONSAPI_TRACING_PROVIDER=jaeger
PERMISSIONSAPI_TRACING_JAEGER_ENDPOINT=http://localhost:14268/api/traces
PERMISSIONSAPI_TRACING_PROVIDER=otlpgrpc
PERMISSIONSAPI_TRACING_OTLP_ENDPOINT=jaeger:4317
PERMISSIONSAPI_TRACING_OTLP_INSECURE=true
PERMISSIONSAPI_SPICEDB_ENDPOINT=spicedb:50051
PERMISSIONSAPI_SPICEDB_KEY=$SPICEDB_GRPC_PRESHARED_KEY
PERMISSIONSAPI_SPICEDB_KEY=infradev
PERMISSIONSAPI_SPICEDB_INSECURE=true

PERMISSIONSAPI_PUBSUB_NAME=permissionsapi
Expand Down
7 changes: 6 additions & 1 deletion .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,12 @@ services:

jaeger:
image: jaegertracing/all-in-one:1.47.0
network_mode: service:app
environment:
- COLLECTOR_OTLP_ENABLED=true
ports:
- 16688:16686
networks:
- infradev

mock-oauth2-server:
image: ghcr.io/navikt/mock-oauth2-server:1.0.0
Expand Down
6 changes: 5 additions & 1 deletion internal/api/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"go.infratographer.com/permissions-api/internal/query"
"go.infratographer.com/permissions-api/internal/types"
"go.infratographer.com/x/gidx"
"go.opentelemetry.io/otel/codes"
"go.uber.org/multierr"
)

Expand Down Expand Up @@ -246,7 +247,10 @@ func (r *Router) checkAllActions(c echo.Context) error {
}

if internalErrors != 0 {
return echo.NewHTTPError(http.StatusInternalServerError, "an error occurred checking permissions").SetInternal(multierr.Combine(allErrors...))
combined := multierr.Combine(allErrors...)
span.SetStatus(codes.Error, combined.Error())

return echo.NewHTTPError(http.StatusInternalServerError, "an error occurred checking permissions").SetInternal(combined)
}

if unauthorizedErrors != 0 {
Expand Down
49 changes: 46 additions & 3 deletions internal/query/relations.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package query

import (
"context"
"errors"
"fmt"
"io"
"strings"
Expand Down Expand Up @@ -65,6 +66,27 @@ func resourceToSpiceDBRef(namespace string, r types.Resource) *pb.ObjectReferenc

// SubjectHasPermission checks if the given subject can do the given action on the given resource
func (e *engine) SubjectHasPermission(ctx context.Context, subject types.Resource, action string, resource types.Resource) error {
ctx, span := e.tracer.Start(
ctx,
"SubjectHasPermission",
trace.WithAttributes(
attribute.Stringer(
"permissions.actor",
subject.ID,
),
attribute.String(
"permissions.action",
action,
),
attribute.Stringer(
"permissions.resource",
resource.ID,
),
),
)

defer span.End()

req := &pb.CheckPermissionRequest{
Consistency: &pb.Consistency{
Requirement: &pb.Consistency_FullyConsistent{
Expand All @@ -78,7 +100,28 @@ func (e *engine) SubjectHasPermission(ctx context.Context, subject types.Resourc
},
}

return e.checkPermission(ctx, req)
err := e.checkPermission(ctx, req)

switch {
case err == nil:
span.SetAttributes(
attribute.String(
"permissions.outcome",
outcomeAllowed,
),
)
case errors.Is(err, ErrActionNotAssigned):
span.SetAttributes(
attribute.String(
"permissions.outcome",
outcomeDenied,
),
)
default:
span.SetStatus(codes.Error, err.Error())
}

return err
}

// AssignSubjectRole assigns the given role to the given subject.
Expand Down Expand Up @@ -194,7 +237,7 @@ func (e *engine) checkPermission(ctx context.Context, req *pb.CheckPermissionReq

// CreateRelationships atomically creates the given relationships in SpiceDB.
func (e *engine) CreateRelationships(ctx context.Context, rels []types.Relationship) (string, error) {
ctx, span := tracer.Start(ctx, "engine.CreateRelationships", trace.WithAttributes(attribute.Int("relationships", len(rels))))
ctx, span := e.tracer.Start(ctx, "engine.CreateRelationships", trace.WithAttributes(attribute.Int("relationships", len(rels))))

defer span.End()

Expand Down Expand Up @@ -347,7 +390,7 @@ func (e *engine) readRelationships(ctx context.Context, filter *pb.RelationshipF
// DeleteRelationships removes the specified relationships.
// If any relationships fails to be deleted, all completed deletions are re-created.
func (e *engine) DeleteRelationships(ctx context.Context, relationships ...types.Relationship) (string, error) {
ctx, span := tracer.Start(ctx, "engine.DeleteRelationships", trace.WithAttributes(attribute.Int("relationships", len(relationships))))
ctx, span := e.tracer.Start(ctx, "engine.DeleteRelationships", trace.WithAttributes(attribute.Int("relationships", len(relationships))))

defer span.End()

Expand Down
10 changes: 8 additions & 2 deletions internal/query/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import (
"github.com/authzed/authzed-go/v1"
"go.infratographer.com/x/gidx"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"

"go.infratographer.com/permissions-api/internal/iapl"
"go.infratographer.com/permissions-api/internal/types"
)

var (
tracer = otel.GetTracerProvider().Tracer("go.infratographer.com/permissions-api/internal/query")
const (
outcomeAllowed = "allowed"
outcomeDenied = "denied"
)

// Engine represents a client for making permissions queries.
Expand All @@ -37,6 +39,7 @@ type Engine interface {
}

type engine struct {
tracer trace.Tracer
logger *zap.SugaredLogger
namespace string
client *authzed.Client
Expand Down Expand Up @@ -87,10 +90,13 @@ func resourceHasRoleBindings(resType types.ResourceType) bool {

// NewEngine returns a new client for making permissions queries.
func NewEngine(namespace string, client *authzed.Client, options ...Option) Engine {
tracer := otel.GetTracerProvider().Tracer("go.infratographer.com/permissions-api/internal/query")

e := &engine{
logger: zap.NewNop().Sugar(),
namespace: namespace,
client: client,
tracer: tracer,
}

for _, fn := range options {
Expand Down
2 changes: 1 addition & 1 deletion permissions-api.example.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
oidc:
issuer: http://mock-oauth2-server:8081/default
audience: permissions-api

15 changes: 15 additions & 0 deletions pkg/permissions/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const (
bearerPrefix = "Bearer "

defaultClientTimeout = 5 * time.Second

outcomeAllowed = "allowed"
outcomeDenied = "denied"
)

var (
Expand Down Expand Up @@ -153,6 +156,12 @@ func (p *Permissions) checker(c echo.Context, actor, token string) Checker {
case errors.Is(err, ErrPermissionDenied):
logger.Warnw("unauthorized access to resource")
span.AddEvent("permission denied")
span.SetAttributes(
attribute.String(
"permissions.outcome",
outcomeDenied,
),
)
case errors.Is(err, ErrBadResponse):
logger.Errorw("bad response from server", "error", err, "response.status_code", resp.StatusCode, "response.body", string(body))
span.SetStatus(codes.Error, errors.WithStack(err).Error())
Expand All @@ -161,6 +170,12 @@ func (p *Permissions) checker(c echo.Context, actor, token string) Checker {
return err
}

span.SetAttributes(
attribute.String(
"permissions.outcome",
outcomeAllowed,
),
)
logger.Debug("access granted to resource")

return nil
Expand Down