diff --git a/router/core/graph_server.go b/router/core/graph_server.go index 0c5b7cd493..c622782691 100644 --- a/router/core/graph_server.go +++ b/router/core/graph_server.go @@ -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, diff --git a/router/core/graphql_prehandler.go b/router/core/graphql_prehandler.go index 9558af55b3..9bac030b0b 100644 --- a/router/core/graphql_prehandler.go +++ b/router/core/graphql_prehandler.go @@ -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 { @@ -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, @@ -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...), @@ -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 } diff --git a/router/core/operation_blocker.go b/router/core/operation_blocker.go index f1bb7adead..1a5ac26fc3 100644 --- a/router/core/operation_blocker.go +++ b/router/core/operation_blocker.go @@ -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 ( @@ -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 { @@ -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 { diff --git a/router/debug.config.yaml b/router/debug.config.yaml index bed83ed3b5..59cd928d6a 100644 --- a/router/debug.config.yaml +++ b/router/debug.config.yaml @@ -39,4 +39,4 @@ events: redis: - id: my-redis urls: - - 'redis://localhost:6379/2' \ No newline at end of file + - 'redis://localhost:6379/2' diff --git a/router/pkg/config/config.go b/router/pkg/config/config.go index b83f0360ac..2efc4f9206 100644 --- a/router/pkg/config/config.go +++ b/router/pkg/config/config.go @@ -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"` } @@ -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"` diff --git a/router/pkg/config/config.schema.json b/router/pkg/config/config.schema.json index 6fbe8851ec..3110bd4881 100644 --- a/router/pkg/config/config.schema.json +++ b/router/pkg/config/config.schema.json @@ -143,6 +143,11 @@ "additionalProperties": false, "description": "The configuration for the persisted operations.", "properties": { + "disabled": { + "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.", diff --git a/router/pkg/config/testdata/config_defaults.json b/router/pkg/config/testdata/config_defaults.json index 2501daddae..9a4ccea958 100644 --- a/router/pkg/config/testdata/config_defaults.json +++ b/router/pkg/config/testdata/config_defaults.json @@ -420,6 +420,7 @@ } }, "PersistedOperationsConfig": { + "Disabled": false, "LogUnknown": false, "Safelist": { "Enabled": false diff --git a/router/pkg/config/testdata/config_full.json b/router/pkg/config/testdata/config_full.json index 7869972aef..146e725e6a 100644 --- a/router/pkg/config/testdata/config_full.json +++ b/router/pkg/config/testdata/config_full.json @@ -779,6 +779,7 @@ } }, "PersistedOperationsConfig": { + "Disabled": false, "LogUnknown": true, "Safelist": { "Enabled": true