From ea613655592f8f110c9c0f44e50729d7b0498b69 Mon Sep 17 00:00:00 2001 From: Tejaswi Nadahalli Date: Wed, 17 Jun 2026 18:28:10 +0200 Subject: [PATCH 1/3] core/workflows: gate confidential workflows on ConfidentialWorkflows.Enabled Bump chainlink-common to pick up PerWorkflow.ConfidentialWorkflows.Enabled and enforce it in the v2 ConfidentialModule: when the gate is disabled, confidential workflow executions are rejected. Scoped per workflow/owner/org/global via the settings registry. --- core/services/workflows/syncer/v2/handler.go | 2 +- .../workflows/v2/confidential_module.go | 11 ++++++- .../workflows/v2/confidential_module_test.go | 30 ++++++++++--------- core/services/workflows/v2/config.go | 9 +++++- go.mod | 4 +-- go.sum | 8 ++--- 6 files changed, 41 insertions(+), 23 deletions(-) diff --git a/core/services/workflows/syncer/v2/handler.go b/core/services/workflows/syncer/v2/handler.go index 8d38502915b..2949650ebcb 100644 --- a/core/services/workflows/syncer/v2/handler.go +++ b/core/services/workflows/syncer/v2/handler.go @@ -766,7 +766,7 @@ func (h *eventHandler) engineFactoryFn(ctx context.Context, workflowID string, o binaryHash := v2.ComputeBinaryHash(binary) confLggr := logger.Named(h.lggr, "WorkflowEngine.ConfidentialModule") confLggr = logger.With(confLggr, "workflowID", workflowID, "workflowName", name, "workflowOwner", owner) - confidential := v2.NewConfidentialModule(h.capRegistry, binaryURL, binaryHash, workflowID, owner, name.String(), tag, confLggr) + confidential := v2.NewConfidentialModule(h.capRegistry, binaryURL, binaryHash, workflowID, owner, name.String(), tag, h.engineLimiters.ConfidentialWorkflowsEnabled, confLggr) engineModule := h.createEngineModule(ctx, workflowID, binary, moduleConfig, module) selectingModule := generichost.NewRequirementSelectingModule( generichost.ModuleAndHandler{Module: engineModule}, diff --git a/core/services/workflows/v2/confidential_module.go b/core/services/workflows/v2/confidential_module.go index 6844128c625..7e0b83f9db7 100644 --- a/core/services/workflows/v2/confidential_module.go +++ b/core/services/workflows/v2/confidential_module.go @@ -15,6 +15,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/contexts" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/settings/limits" "github.com/smartcontractkit/chainlink-common/pkg/types/core" "github.com/smartcontractkit/chainlink-common/pkg/workflows/host" @@ -64,11 +65,12 @@ type ConfidentialModule struct { requirements sync.Map infoOnce sync.Once provider func(tee *sdkpb.Tee) bool + enabledGate limits.GateLimiter } var _ host.RequirementEnforcingModule = (*ConfidentialModule)(nil) -func NewConfidentialModule(capRegistry core.CapabilitiesRegistry, binaryURL string, binaryHash []byte, workflowID, workflowOwner, workflowName, workflowTag string, lggr logger.Logger) *ConfidentialModule { +func NewConfidentialModule(capRegistry core.CapabilitiesRegistry, binaryURL string, binaryHash []byte, workflowID, workflowOwner, workflowName, workflowTag string, enabledGate limits.GateLimiter, lggr logger.Logger) *ConfidentialModule { return &ConfidentialModule{ capRegistry: capRegistry, binaryURL: binaryURL, @@ -77,6 +79,7 @@ func NewConfidentialModule(capRegistry core.CapabilitiesRegistry, binaryURL stri workflowOwner: workflowOwner, workflowName: workflowName, workflowTag: workflowTag, + enabledGate: enabledGate, lggr: lggr, } } @@ -90,6 +93,12 @@ func (m *ConfidentialModule) Execute( request *sdkpb.ExecuteRequest, helper host.ExecutionHelper, ) (*sdkpb.ExecutionResult, error) { + if m.enabledGate != nil { + if err := m.enabledGate.AllowErr(ctx); err != nil { + return nil, fmt.Errorf("confidential-workflows capability is disabled by settings: %w", err) + } + } + var requirements *sdkpb.Requirements rawRequirements, loaded := m.requirements.LoadAndDelete(helper.GetWorkflowExecutionID()) if loaded { diff --git a/core/services/workflows/v2/confidential_module_test.go b/core/services/workflows/v2/confidential_module_test.go index 24033965969..a6e775938e8 100644 --- a/core/services/workflows/v2/confidential_module_test.go +++ b/core/services/workflows/v2/confidential_module_test.go @@ -15,6 +15,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/settings/limits" regmocks "github.com/smartcontractkit/chainlink-common/pkg/types/core/mocks" "github.com/smartcontractkit/chainlink-common/pkg/workflows/host" @@ -140,6 +141,7 @@ func TestConfidentialModule_Execute(t *testing.T) { "owner-abc", "my-workflow", "v1", + limits.NewGateLimiter(true), lggr, ) @@ -157,7 +159,7 @@ func TestConfidentialModule_Execute(t *testing.T) { capReg.EXPECT().GetExecutable(matches.AnyContext, confidentialWorkflowsCapabilityID). Return(nil, errors.New("capability not found")).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr) + mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) _, err := mod.Execute(ctx, execReq, &stubExecutionHelper{}) require.Error(t, err) @@ -173,7 +175,7 @@ func TestConfidentialModule_Execute(t *testing.T) { execCap.EXPECT().Execute(matches.AnyContext, mock.Anything). Return(capabilities.CapabilityResponse{}, errors.New("enclave unavailable")).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr) + mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) _, err := mod.Execute(ctx, execReq, &stubExecutionHelper{}) require.Error(t, err) @@ -189,7 +191,7 @@ func TestConfidentialModule_Execute(t *testing.T) { execCap.EXPECT().Execute(matches.AnyContext, mock.Anything). Return(capabilities.CapabilityResponse{Payload: nil}, nil).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr) + mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) _, err := mod.Execute(ctx, execReq, &stubExecutionHelper{}) require.Error(t, err) @@ -211,7 +213,7 @@ func TestConfidentialModule_Execute(t *testing.T) { Return(capabilities.CapabilityResponse{Payload: respPayload}, nil).Once() binaryHash := ComputeBinaryHash([]byte("some-binary")) - mod := NewConfidentialModule(capReg, "https://example.com/wasm", binaryHash, "wf-abc", "0xowner", "my-workflow", "v2", lggr) + mod := NewConfidentialModule(capReg, "https://example.com/wasm", binaryHash, "wf-abc", "0xowner", "my-workflow", "v2", limits.NewGateLimiter(true), lggr) _, err := mod.Execute(ctx, execReq, &stubExecutionHelper{executionID: "exec-xyz"}) require.NoError(t, err) @@ -268,7 +270,7 @@ func TestConfidentialModule_Tee(t *testing.T) { {Type: sdkpb.TeeType_TEE_TYPE_AWS_NITRO, Regions: []string{"us-east-1", "eu-west-1"}}, })}, nil).Once() - mod := NewConfidentialModule(capReg, "https://example.com/binary.wasm", []byte("fakehash"), "wf-123", "owner-abc", "my-workflow", "v1", lggr) + mod := NewConfidentialModule(capReg, "https://example.com/binary.wasm", []byte("fakehash"), "wf-123", "owner-abc", "my-workflow", "v1", limits.NewGateLimiter(true), lggr) assert.True(t, mod.Tee(ctx, anyRegionsTee("us-east-1"))) }) @@ -284,7 +286,7 @@ func TestConfidentialModule_Tee(t *testing.T) { {Type: sdkpb.TeeType_TEE_TYPE_AWS_NITRO, Regions: []string{"us-east-1"}}, })}, nil).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr) + mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) assert.False(t, mod.Tee(ctx, anyRegionsTee("ap-southeast-1"))) }) @@ -297,7 +299,7 @@ func TestConfidentialModule_Tee(t *testing.T) { execCap.EXPECT().Execute(matches.AnyContext, mock.Anything). Return(capabilities.CapabilityResponse{Payload: buildRespPayload(t, nil)}, nil).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr) + mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) assert.False(t, mod.Tee(ctx, anyRegionsTee("us-east-1"))) }) @@ -306,7 +308,7 @@ func TestConfidentialModule_Tee(t *testing.T) { capReg.EXPECT().GetExecutable(matches.AnyContext, confidentialWorkflowsCapabilityID). Return(nil, errors.New("capability not found")).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr) + mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) assert.False(t, mod.Tee(ctx, anyRegionsTee("us-east-1"))) }) @@ -319,7 +321,7 @@ func TestConfidentialModule_Tee(t *testing.T) { execCap.EXPECT().Execute(matches.AnyContext, mock.Anything). Return(capabilities.CapabilityResponse{}, errors.New("enclave unavailable")).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr) + mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) assert.False(t, mod.Tee(ctx, anyRegionsTee("us-east-1"))) }) @@ -332,7 +334,7 @@ func TestConfidentialModule_Tee(t *testing.T) { execCap.EXPECT().Execute(matches.AnyContext, mock.Anything). Return(capabilities.CapabilityResponse{Payload: nil}, nil).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr) + mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) assert.False(t, mod.Tee(ctx, anyRegionsTee("us-east-1"))) }) @@ -350,7 +352,7 @@ func TestConfidentialModule_Tee(t *testing.T) { }). Return(capabilities.CapabilityResponse{Payload: buildRespPayload(t, nil)}, nil).Once() - mod := NewConfidentialModule(capReg, "https://example.com/wasm", []byte("hash"), "wf-xyz", "0xowner", "my-workflow", "v3", lggr) + mod := NewConfidentialModule(capReg, "https://example.com/wasm", []byte("hash"), "wf-xyz", "0xowner", "my-workflow", "v3", limits.NewGateLimiter(true), lggr) _ = mod.Tee(ctx, anyRegionsTee("us-east-1")) assert.Equal(t, "ProvidedTees", capturedReq.Method) @@ -376,7 +378,7 @@ func TestConfidentialModule_Tee(t *testing.T) { {Type: sdkpb.TeeType_TEE_TYPE_AWS_NITRO, Regions: []string{"us-east-1"}}, })}, nil).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr) + mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) assert.True(t, mod.Tee(ctx, anyRegionsTee("us-east-1"))) assert.True(t, mod.Tee(ctx, anyRegionsTee("us-east-1"))) @@ -416,7 +418,7 @@ func TestConfidentialModule_SetRequirements(t *testing.T) { }). Return(capabilities.CapabilityResponse{Payload: respPayload}, nil).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr) + mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) requirements := &sdkpb.Requirements{ Tee: &sdkpb.Tee{ @@ -453,7 +455,7 @@ func TestConfidentialModule_SetRequirements(t *testing.T) { }). Return(capabilities.CapabilityResponse{Payload: respPayload}, nil).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", lggr) + mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) mod.SetRequirements("exec-789", &sdkpb.Requirements{}) _, err := mod.Execute(ctx, execReq, &stubExecutionHelper{executionID: "exec-789"}) diff --git a/core/services/workflows/v2/config.go b/core/services/workflows/v2/config.go index f6775125268..f75621089b1 100644 --- a/core/services/workflows/v2/config.go +++ b/core/services/workflows/v2/config.go @@ -116,7 +116,8 @@ type EngineLimiters struct { UserMetricLabelsPerMetric limits.BoundLimiter[int] UserMetricLabelValueLength limits.BoundLimiter[int] - ExecutionTimestampsEnabled limits.GateLimiter + ExecutionTimestampsEnabled limits.GateLimiter + ConfidentialWorkflowsEnabled limits.GateLimiter } // NewLimiters returns a new set of EngineLimiters based on the default configuration, and optionally modified by cfgFn. @@ -264,6 +265,10 @@ func (l *EngineLimiters) init(lf limits.Factory, cfgFn func(*cresettings.Workflo if err != nil { return } + l.ConfidentialWorkflowsEnabled, err = limits.MakeGateLimiter(lf, cfg.ConfidentialWorkflows.Enabled) + if err != nil { + return + } return } @@ -302,6 +307,7 @@ func (l *EngineLimiters) EvictWorkflow(workflowID string) error { l.ConfidentialHTTPCalls, l.SecretsCalls, l.ExecutionTimestampsEnabled, + l.ConfidentialWorkflowsEnabled, } var errs error for _, e := range evictables { @@ -343,6 +349,7 @@ func (l *EngineLimiters) Close() error { l.ConfidentialHTTPCalls, l.SecretsCalls, l.ExecutionTimestampsEnabled, + l.ConfidentialWorkflowsEnabled, ) } diff --git a/go.mod b/go.mod index d51c4df4597..62e110b67a9 100644 --- a/go.mod +++ b/go.mod @@ -85,7 +85,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260415165642-49f23e4d76cc github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260611143717-66b6ab499fd0 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260611160104-87b1e75d2c33 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260617161620-6c25e6db90be github.com/smartcontractkit/chainlink-common/keystore v1.2.0 github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0 github.com/smartcontractkit/chainlink-data-streams v0.1.15-0.20260522094612-5f9f748bd87a @@ -97,7 +97,7 @@ require ( github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260423135514-5b1a7565a99c github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260521164805-26d78d5e1243 github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4 - github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260611123141-db97012a6c32 + github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260616114954-8b52f3f386f2 github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260512230622-65f10f4cd305 diff --git a/go.sum b/go.sum index 607ea319c0e..e4d051d454d 100644 --- a/go.sum +++ b/go.sum @@ -1165,8 +1165,8 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260415165642-49f23e4d76cc/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260611143717-66b6ab499fd0 h1:8p6HmvdE1IpmcNBN5uvHC+Ene033aRvci3nQE8QAbyQ= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260611143717-66b6ab499fd0/go.mod h1:hhF1KVXQcx7bA0gJsBSGMpDK6wMYXZdrgAhICpcWgdA= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260611160104-87b1e75d2c33 h1:osxv7PghidWEYULnJu0YNBbMym3z23YS0CqMZqFYemw= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260611160104-87b1e75d2c33/go.mod h1:BCYSFxZTxZEGbyeodKCmfC1MydCEmIf+O29xrLBLnJI= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260617161620-6c25e6db90be h1:DxPnpU1rqM+AVR1ioD+3lAuJr/22E+IlA7aAD0QESEk= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260617161620-6c25e6db90be/go.mod h1:I4awFDjjNWJO5pEW0lTllxO2BZdtAHgzXJXSmjlQExU= github.com/smartcontractkit/chainlink-common/keystore v1.2.0 h1:1BH/b14CkGjArfzznlioQpIJiynECWVT48JUP9E277U= github.com/smartcontractkit/chainlink-common/keystore v1.2.0/go.mod h1:9R/74vN+bJ5PbkOyM/pUy/AeAZaRwYb/k4XPeXcbDio= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0 h1:NExKM/D0HneOq/N5LGTbkV4VOa0UHCvfTNEb4GqYpto= @@ -1201,8 +1201,8 @@ github.com/smartcontractkit/chainlink-protos/chainlink-ccv/message-rules v0.0.0- github.com/smartcontractkit/chainlink-protos/chainlink-ccv/message-rules v0.0.0-20260505131349-78e491b80735/go.mod h1:zAJq6Tpkx5AdFUwW67dIYnW+Bdf50drCCpMR81Qxb4E= github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251211142334-5c3421fe2c8d h1:AJy55QJ/pBhXkZjc7N+ATnWfxrcjq9BI9DmdtdjwDUQ= github.com/smartcontractkit/chainlink-protos/chainlink-ccv/verifier v0.0.0-20251211142334-5c3421fe2c8d/go.mod h1:5JdppgngCOUS76p61zCinSCgOhPeYQ+OcDUuome5THQ= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260611123141-db97012a6c32 h1:GNl+lLK0QCakqA1J1i7FoOai2JrOGOzNzSniMijaCjA= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260611123141-db97012a6c32/go.mod h1:vTFHTCbLui4Vn8fTmAadfE3rdnvfrDwOmMujmW857D0= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260616114954-8b52f3f386f2 h1:pW8BIAwXJM1uh5L7BGmaRc1M7i1CtsfPM4MnP+kd8kc= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260616114954-8b52f3f386f2/go.mod h1:vTFHTCbLui4Vn8fTmAadfE3rdnvfrDwOmMujmW857D0= github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36 h1:SG+wAsNyAcA6Kk19ljuxi3HK9Ll2lpHik8OKoY4x7A0= github.com/smartcontractkit/chainlink-protos/data-feeds v0.1.1-0.20260501174546-2e8846986b36/go.mod h1:vL1bDgPSJjV0EqHYs4dDlR+EEE0cJchgvGLYXhwIjXY= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 h1:NJdGFhzT6zMaTod4QkBqVD2sg0I25iw1boOYtTpEwRo= From bbdff7189f775b8fb149453412f1e5527cdb5484 Mon Sep 17 00:00:00 2001 From: Tejaswi Nadahalli Date: Thu, 18 Jun 2026 13:24:14 +0200 Subject: [PATCH 2/3] core/workflows: error from NewConfidentialModule on nil gate Validate enabledGate in the constructor instead of nil-checking inside Execute, per review. Route test construction through a helper and add a nil-gate test. --- core/services/workflows/syncer/v2/handler.go | 5 ++- .../workflows/v2/confidential_module.go | 13 +++--- .../workflows/v2/confidential_module_test.go | 43 ++++++++++++------- 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/core/services/workflows/syncer/v2/handler.go b/core/services/workflows/syncer/v2/handler.go index 2949650ebcb..30254d5e703 100644 --- a/core/services/workflows/syncer/v2/handler.go +++ b/core/services/workflows/syncer/v2/handler.go @@ -766,7 +766,10 @@ func (h *eventHandler) engineFactoryFn(ctx context.Context, workflowID string, o binaryHash := v2.ComputeBinaryHash(binary) confLggr := logger.Named(h.lggr, "WorkflowEngine.ConfidentialModule") confLggr = logger.With(confLggr, "workflowID", workflowID, "workflowName", name, "workflowOwner", owner) - confidential := v2.NewConfidentialModule(h.capRegistry, binaryURL, binaryHash, workflowID, owner, name.String(), tag, h.engineLimiters.ConfidentialWorkflowsEnabled, confLggr) + confidential, err := v2.NewConfidentialModule(h.capRegistry, binaryURL, binaryHash, workflowID, owner, name.String(), tag, h.engineLimiters.ConfidentialWorkflowsEnabled, confLggr) + if err != nil { + return nil, fmt.Errorf("failed to create confidential module: %w", err) + } engineModule := h.createEngineModule(ctx, workflowID, binary, moduleConfig, module) selectingModule := generichost.NewRequirementSelectingModule( generichost.ModuleAndHandler{Module: engineModule}, diff --git a/core/services/workflows/v2/confidential_module.go b/core/services/workflows/v2/confidential_module.go index 7e0b83f9db7..8865408b9e3 100644 --- a/core/services/workflows/v2/confidential_module.go +++ b/core/services/workflows/v2/confidential_module.go @@ -70,7 +70,10 @@ type ConfidentialModule struct { var _ host.RequirementEnforcingModule = (*ConfidentialModule)(nil) -func NewConfidentialModule(capRegistry core.CapabilitiesRegistry, binaryURL string, binaryHash []byte, workflowID, workflowOwner, workflowName, workflowTag string, enabledGate limits.GateLimiter, lggr logger.Logger) *ConfidentialModule { +func NewConfidentialModule(capRegistry core.CapabilitiesRegistry, binaryURL string, binaryHash []byte, workflowID, workflowOwner, workflowName, workflowTag string, enabledGate limits.GateLimiter, lggr logger.Logger) (*ConfidentialModule, error) { + if enabledGate == nil { + return nil, errors.New("enabledGate must not be nil") + } return &ConfidentialModule{ capRegistry: capRegistry, binaryURL: binaryURL, @@ -81,7 +84,7 @@ func NewConfidentialModule(capRegistry core.CapabilitiesRegistry, binaryURL stri workflowTag: workflowTag, enabledGate: enabledGate, lggr: lggr, - } + }, nil } func (m *ConfidentialModule) Start() {} @@ -93,10 +96,8 @@ func (m *ConfidentialModule) Execute( request *sdkpb.ExecuteRequest, helper host.ExecutionHelper, ) (*sdkpb.ExecutionResult, error) { - if m.enabledGate != nil { - if err := m.enabledGate.AllowErr(ctx); err != nil { - return nil, fmt.Errorf("confidential-workflows capability is disabled by settings: %w", err) - } + if err := m.enabledGate.AllowErr(ctx); err != nil { + return nil, fmt.Errorf("confidential-workflows capability is disabled by settings: %w", err) } var requirements *sdkpb.Requirements diff --git a/core/services/workflows/v2/confidential_module_test.go b/core/services/workflows/v2/confidential_module_test.go index a6e775938e8..b5ce0568057 100644 --- a/core/services/workflows/v2/confidential_module_test.go +++ b/core/services/workflows/v2/confidential_module_test.go @@ -133,7 +133,7 @@ func TestConfidentialModule_Execute(t *testing.T) { req.Payload != nil })).Return(capabilities.CapabilityResponse{Payload: respPayload}, nil).Once() - mod := NewConfidentialModule( + mod := mustNewConfidentialModule(t, capReg, "https://example.com/binary.wasm", []byte("fakehash"), @@ -159,7 +159,7 @@ func TestConfidentialModule_Execute(t *testing.T) { capReg.EXPECT().GetExecutable(matches.AnyContext, confidentialWorkflowsCapabilityID). Return(nil, errors.New("capability not found")).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) + mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) _, err := mod.Execute(ctx, execReq, &stubExecutionHelper{}) require.Error(t, err) @@ -175,7 +175,7 @@ func TestConfidentialModule_Execute(t *testing.T) { execCap.EXPECT().Execute(matches.AnyContext, mock.Anything). Return(capabilities.CapabilityResponse{}, errors.New("enclave unavailable")).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) + mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) _, err := mod.Execute(ctx, execReq, &stubExecutionHelper{}) require.Error(t, err) @@ -191,7 +191,7 @@ func TestConfidentialModule_Execute(t *testing.T) { execCap.EXPECT().Execute(matches.AnyContext, mock.Anything). Return(capabilities.CapabilityResponse{Payload: nil}, nil).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) + mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) _, err := mod.Execute(ctx, execReq, &stubExecutionHelper{}) require.Error(t, err) @@ -213,7 +213,7 @@ func TestConfidentialModule_Execute(t *testing.T) { Return(capabilities.CapabilityResponse{Payload: respPayload}, nil).Once() binaryHash := ComputeBinaryHash([]byte("some-binary")) - mod := NewConfidentialModule(capReg, "https://example.com/wasm", binaryHash, "wf-abc", "0xowner", "my-workflow", "v2", limits.NewGateLimiter(true), lggr) + mod := mustNewConfidentialModule(t, capReg, "https://example.com/wasm", binaryHash, "wf-abc", "0xowner", "my-workflow", "v2", limits.NewGateLimiter(true), lggr) _, err := mod.Execute(ctx, execReq, &stubExecutionHelper{executionID: "exec-xyz"}) require.NoError(t, err) @@ -270,7 +270,7 @@ func TestConfidentialModule_Tee(t *testing.T) { {Type: sdkpb.TeeType_TEE_TYPE_AWS_NITRO, Regions: []string{"us-east-1", "eu-west-1"}}, })}, nil).Once() - mod := NewConfidentialModule(capReg, "https://example.com/binary.wasm", []byte("fakehash"), "wf-123", "owner-abc", "my-workflow", "v1", limits.NewGateLimiter(true), lggr) + mod := mustNewConfidentialModule(t, capReg, "https://example.com/binary.wasm", []byte("fakehash"), "wf-123", "owner-abc", "my-workflow", "v1", limits.NewGateLimiter(true), lggr) assert.True(t, mod.Tee(ctx, anyRegionsTee("us-east-1"))) }) @@ -286,7 +286,7 @@ func TestConfidentialModule_Tee(t *testing.T) { {Type: sdkpb.TeeType_TEE_TYPE_AWS_NITRO, Regions: []string{"us-east-1"}}, })}, nil).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) + mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) assert.False(t, mod.Tee(ctx, anyRegionsTee("ap-southeast-1"))) }) @@ -299,7 +299,7 @@ func TestConfidentialModule_Tee(t *testing.T) { execCap.EXPECT().Execute(matches.AnyContext, mock.Anything). Return(capabilities.CapabilityResponse{Payload: buildRespPayload(t, nil)}, nil).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) + mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) assert.False(t, mod.Tee(ctx, anyRegionsTee("us-east-1"))) }) @@ -308,7 +308,7 @@ func TestConfidentialModule_Tee(t *testing.T) { capReg.EXPECT().GetExecutable(matches.AnyContext, confidentialWorkflowsCapabilityID). Return(nil, errors.New("capability not found")).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) + mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) assert.False(t, mod.Tee(ctx, anyRegionsTee("us-east-1"))) }) @@ -321,7 +321,7 @@ func TestConfidentialModule_Tee(t *testing.T) { execCap.EXPECT().Execute(matches.AnyContext, mock.Anything). Return(capabilities.CapabilityResponse{}, errors.New("enclave unavailable")).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) + mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) assert.False(t, mod.Tee(ctx, anyRegionsTee("us-east-1"))) }) @@ -334,7 +334,7 @@ func TestConfidentialModule_Tee(t *testing.T) { execCap.EXPECT().Execute(matches.AnyContext, mock.Anything). Return(capabilities.CapabilityResponse{Payload: nil}, nil).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) + mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) assert.False(t, mod.Tee(ctx, anyRegionsTee("us-east-1"))) }) @@ -352,7 +352,7 @@ func TestConfidentialModule_Tee(t *testing.T) { }). Return(capabilities.CapabilityResponse{Payload: buildRespPayload(t, nil)}, nil).Once() - mod := NewConfidentialModule(capReg, "https://example.com/wasm", []byte("hash"), "wf-xyz", "0xowner", "my-workflow", "v3", limits.NewGateLimiter(true), lggr) + mod := mustNewConfidentialModule(t, capReg, "https://example.com/wasm", []byte("hash"), "wf-xyz", "0xowner", "my-workflow", "v3", limits.NewGateLimiter(true), lggr) _ = mod.Tee(ctx, anyRegionsTee("us-east-1")) assert.Equal(t, "ProvidedTees", capturedReq.Method) @@ -378,7 +378,7 @@ func TestConfidentialModule_Tee(t *testing.T) { {Type: sdkpb.TeeType_TEE_TYPE_AWS_NITRO, Regions: []string{"us-east-1"}}, })}, nil).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) + mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) assert.True(t, mod.Tee(ctx, anyRegionsTee("us-east-1"))) assert.True(t, mod.Tee(ctx, anyRegionsTee("us-east-1"))) @@ -418,7 +418,7 @@ func TestConfidentialModule_SetRequirements(t *testing.T) { }). Return(capabilities.CapabilityResponse{Payload: respPayload}, nil).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) + mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) requirements := &sdkpb.Requirements{ Tee: &sdkpb.Tee{ @@ -455,7 +455,7 @@ func TestConfidentialModule_SetRequirements(t *testing.T) { }). Return(capabilities.CapabilityResponse{Payload: respPayload}, nil).Once() - mod := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) + mod := mustNewConfidentialModule(t, capReg, "", nil, "wf", "owner", "name", "tag", limits.NewGateLimiter(true), lggr) mod.SetRequirements("exec-789", &sdkpb.Requirements{}) _, err := mod.Execute(ctx, execReq, &stubExecutionHelper{executionID: "exec-789"}) @@ -478,3 +478,16 @@ func TestConfidentialModule_InterfaceMethods(t *testing.T) { mod.Close() assert.False(t, mod.IsLegacyDAG()) } + +func mustNewConfidentialModule(t *testing.T, capRegistry *regmocks.CapabilitiesRegistry, binaryURL string, binaryHash []byte, workflowID, workflowOwner, workflowName, workflowTag string, enabledGate limits.GateLimiter, lggr logger.Logger) *ConfidentialModule { + t.Helper() + m, err := NewConfidentialModule(capRegistry, binaryURL, binaryHash, workflowID, workflowOwner, workflowName, workflowTag, enabledGate, lggr) + require.NoError(t, err) + return m +} + +func TestNewConfidentialModule_NilGate(t *testing.T) { + capReg := regmocks.NewCapabilitiesRegistry(t) + _, err := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", nil, logger.Test(t)) + require.Error(t, err) +} From 9bdddda38e76209db2f475c82f2501f566ba7c2a Mon Sep 17 00:00:00 2001 From: Tejaswi Nadahalli Date: Thu, 18 Jun 2026 13:35:28 +0200 Subject: [PATCH 3/3] core/workflows: add changeset and t.Parallel for confidential gate Changeset for the ConfidentialWorkflows.Enabled gate, and satisfy the paralleltest linter on the nil-gate test. --- .changeset/cw-enabled-gate.md | 5 +++++ core/services/workflows/v2/confidential_module_test.go | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/cw-enabled-gate.md diff --git a/.changeset/cw-enabled-gate.md b/.changeset/cw-enabled-gate.md new file mode 100644 index 00000000000..ea10cf86231 --- /dev/null +++ b/.changeset/cw-enabled-gate.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +Add the ConfidentialWorkflows.Enabled feature gate to the v2 workflow engine. Confidential workflow execution can now be toggled per workflow/owner/org/global via the settings registry; when disabled, ConfidentialModule.Execute rejects the request. diff --git a/core/services/workflows/v2/confidential_module_test.go b/core/services/workflows/v2/confidential_module_test.go index b5ce0568057..196693eb74a 100644 --- a/core/services/workflows/v2/confidential_module_test.go +++ b/core/services/workflows/v2/confidential_module_test.go @@ -487,6 +487,7 @@ func mustNewConfidentialModule(t *testing.T, capRegistry *regmocks.CapabilitiesR } func TestNewConfidentialModule_NilGate(t *testing.T) { + t.Parallel() capReg := regmocks.NewCapabilitiesRegistry(t) _, err := NewConfidentialModule(capReg, "", nil, "wf", "owner", "name", "tag", nil, logger.Test(t)) require.Error(t, err)