Skip to content
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
1 change: 1 addition & 0 deletions router/core/graph_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,7 @@ func (s *graphServer) buildGraphMux(ctx context.Context,
Enabled: s.securityConfiguration.BlockNonPersistedOperations.Enabled,
Condition: s.securityConfiguration.BlockNonPersistedOperations.Condition,
},
PersistedOperationsDisabled: s.persistedOperationsConfig.Disabled,
SafelistEnabled: s.persistedOperationsConfig.Safelist.Enabled,
LogUnknownOperationsEnabled: s.persistedOperationsConfig.LogUnknown,
exprManager: exprManager,
Expand Down
17 changes: 12 additions & 5 deletions router/core/graphql_prehandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -479,14 +479,14 @@ func (h *PreHandler) shouldComputeOperationSha256(operationKit *OperationKit) bo
hasPersistedHash := operationKit.parsedOperation.GraphQLRequestExtensions.PersistedQuery != nil && operationKit.parsedOperation.GraphQLRequestExtensions.PersistedQuery.Sha256Hash != ""
// If it already has a persisted hash attached to the request, then there is no need for us to compute it anew
// Otherwise, we only want to compute the hash (an expensive operation) if we're safelisting or logging unknown persisted operations
return !hasPersistedHash && (h.operationBlocker.SafelistEnabled || h.operationBlocker.LogUnknownOperationsEnabled)
return !hasPersistedHash && (h.operationBlocker.safelistEnabled || h.operationBlocker.logUnknownOperationsEnabled)
}

// shouldFetchPersistedOperation determines if we should fetch a persisted operation. The most intuitive case is if the
// operation is a persisted operation. However, we also want to fetch persisted operations if we're enabling safelisting
// and if we're logging unknown operations. This is because we want to check if the operation is already persisted in the cache
func (h *PreHandler) shouldFetchPersistedOperation(operationKit *OperationKit) bool {
return operationKit.parsedOperation.IsPersistedOperation || h.operationBlocker.SafelistEnabled || h.operationBlocker.LogUnknownOperationsEnabled
return operationKit.parsedOperation.IsPersistedOperation || h.operationBlocker.safelistEnabled || h.operationBlocker.logUnknownOperationsEnabled
}

func (h *PreHandler) handleOperation(req *http.Request, variablesParser *astjson.Parser, httpOperation *httpOperation) error {
Expand Down Expand Up @@ -539,7 +539,7 @@ func (h *PreHandler) handleOperation(req *http.Request, variablesParser *astjson
}
requestContext.operation.sha256Hash = operationKit.parsedOperation.Sha256Hash
requestContext.telemetry.addCustomMetricStringAttr(ContextFieldOperationSha256, requestContext.operation.sha256Hash)
if h.operationBlocker.SafelistEnabled || h.operationBlocker.LogUnknownOperationsEnabled {
if h.operationBlocker.safelistEnabled || h.operationBlocker.logUnknownOperationsEnabled {
// Set the request hash to the parsed hash, to see if it matches a persisted operation
operationKit.parsedOperation.GraphQLRequestExtensions.PersistedQuery = &GraphQLRequestExtensionsPersistedQuery{
Sha256Hash: operationKit.parsedOperation.Sha256Hash,
Expand All @@ -562,6 +562,13 @@ func (h *PreHandler) handleOperation(req *http.Request, variablesParser *astjson
)

if h.shouldFetchPersistedOperation(operationKit) {
if h.operationBlocker.persistedOperationsDisabled {
return &httpGraphqlError{
message: "persisted operations are disabled",
statusCode: http.StatusBadRequest,
}
}

ctx, span := h.tracer.Start(req.Context(), "Load Persisted Operation",
trace.WithSpanKind(trace.SpanKindClient),
trace.WithAttributes(requestContext.telemetry.traceAttrs...),
Expand All @@ -574,9 +581,9 @@ func (h *PreHandler) handleOperation(req *http.Request, variablesParser *astjson
span.SetStatus(codes.Error, err.Error())

var poNotFoundErr *persistedoperation.PersistentOperationNotFoundError
if h.operationBlocker.LogUnknownOperationsEnabled && errors.As(err, &poNotFoundErr) {
if h.operationBlocker.logUnknownOperationsEnabled && errors.As(err, &poNotFoundErr) {
requestContext.logger.Warn("Unknown persisted operation found", zap.String("query", operationKit.parsedOperation.Request.Query), zap.String("sha256Hash", poNotFoundErr.Sha256Hash))
if h.operationBlocker.SafelistEnabled {
if h.operationBlocker.safelistEnabled {
span.End()
return err
}
Expand Down
23 changes: 14 additions & 9 deletions router/core/operation_blocker.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package core
import (
"errors"
"fmt"
"reflect"

"github.com/expr-lang/expr/vm"
"github.com/wundergraph/cosmo/router/internal/expr"
"go.uber.org/zap"
"reflect"
)

var (
Expand All @@ -16,15 +17,16 @@ var (
)

type OperationBlocker struct {
SafelistEnabled bool
LogUnknownOperationsEnabled bool

blockMutations BlockMutationOptions
blockSubscriptions BlockSubscriptionOptions
blockNonPersisted BlockNonPersistedOptions
mutationExpr *vm.Program
subscriptionExpr *vm.Program
nonPersistedExpr *vm.Program

persistedOperationsDisabled bool
safelistEnabled bool
logUnknownOperationsEnabled bool
}

type BlockMutationOptions struct {
Expand All @@ -51,17 +53,20 @@ type OperationBlockerOptions struct {
BlockSubscriptions BlockSubscriptionOptions
BlockNonPersisted BlockNonPersistedOptions
SafelistEnabled bool
PersistedOperationsDisabled bool
LogUnknownOperationsEnabled bool
exprManager *expr.Manager
}

func NewOperationBlocker(opts *OperationBlockerOptions) (*OperationBlocker, error) {
ob := &OperationBlocker{
blockMutations: opts.BlockMutations,
blockSubscriptions: opts.BlockSubscriptions,
blockNonPersisted: opts.BlockNonPersisted,
SafelistEnabled: opts.SafelistEnabled,
LogUnknownOperationsEnabled: opts.LogUnknownOperationsEnabled,
blockMutations: opts.BlockMutations,
blockSubscriptions: opts.BlockSubscriptions,
blockNonPersisted: opts.BlockNonPersisted,

persistedOperationsDisabled: opts.PersistedOperationsDisabled,
safelistEnabled: opts.SafelistEnabled,
logUnknownOperationsEnabled: opts.LogUnknownOperationsEnabled,
}

if err := ob.compileExpressions(opts.exprManager); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion router/debug.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ events:
redis:
- id: my-redis
urls:
- 'redis://localhost:6379/2'
- 'redis://localhost:6379/2'
7 changes: 4 additions & 3 deletions router/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -799,8 +799,9 @@ type AutomaticPersistedQueriesCacheConfig struct {
}

type PersistedOperationsConfig struct {
LogUnknown bool `yaml:"log_unknown" env:"PERSISTED_OPERATIONS_LOG_UNKNOWN" envDefault:"false"`
Safelist SafelistConfiguration `yaml:"safelist" envPrefix:"PERSISTED_OPERATIONS_SAFELIST_"`
Disabled bool `yaml:"disabled" env:"DISABLED" envDefault:"false"`
LogUnknown bool `yaml:"log_unknown" env:"LOG_UNKNOWN" envDefault:"false"`
Safelist SafelistConfiguration `yaml:"safelist" envPrefix:"SAFELIST_"`
Cache PersistedOperationsCacheConfig `yaml:"cache"`
Storage PersistedOperationsStorageConfig `yaml:"storage"`
}
Expand Down Expand Up @@ -995,7 +996,7 @@ type Config struct {

StorageProviders StorageProviders `yaml:"storage_providers"`
ExecutionConfig ExecutionConfig `yaml:"execution_config"`
PersistedOperationsConfig PersistedOperationsConfig `yaml:"persisted_operations"`
PersistedOperationsConfig PersistedOperationsConfig `yaml:"persisted_operations" envPrefix:"PERSISTED_OPERATIONS_"`
AutomaticPersistedQueries AutomaticPersistedQueriesConfig `yaml:"automatic_persisted_queries"`
ApolloCompatibilityFlags ApolloCompatibilityFlags `yaml:"apollo_compatibility_flags"`
ApolloRouterCompatibilityFlags ApolloRouterCompatibilityFlags `yaml:"apollo_router_compatibility_flags"`
Expand Down
5 changes: 5 additions & 0 deletions router/pkg/config/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@
"additionalProperties": false,
"description": "The configuration for the persisted operations.",
"properties": {
"disabled": {
Comment thread
endigma marked this conversation as resolved.
"type": "boolean",
"description": "Disables persisted operations. If disabled, all operations sent with a persisted operation in the body are blocked.",
"default": false
},
"safelist": {
"type": "object",
"description": "The configuration for safelisting persisted operations.",
Expand Down
1 change: 1 addition & 0 deletions router/pkg/config/testdata/config_defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@
}
},
"PersistedOperationsConfig": {
"Disabled": false,
"LogUnknown": false,
"Safelist": {
"Enabled": false
Expand Down
1 change: 1 addition & 0 deletions router/pkg/config/testdata/config_full.json
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,7 @@
}
},
"PersistedOperationsConfig": {
"Disabled": false,
"LogUnknown": true,
"Safelist": {
"Enabled": true
Expand Down
Loading