fix: plugin server temporal certificate handling (#40676)

This commit is contained in:
Carlos Marchal
2025-10-30 18:32:48 +01:00
committed by GitHub
parent 2a3ff2a877
commit 683d44c041
4 changed files with 89 additions and 38 deletions

View File

@@ -48,7 +48,6 @@
"license": "MIT",
"dependencies": {
"@amplitude/ua-parser-js": "^0.7.33",
"@temporalio/client": "^1.12.0",
"@aws-sdk/client-s3": "^3.709.0",
"@aws-sdk/lib-storage": "^3.709.0",
"@babel/core": "^7.22.10",
@@ -59,6 +58,7 @@
"@clickhouse/client": "^1.12.0",
"@google-cloud/pubsub": "4.11.0",
"@google-cloud/storage": "^5.8.5",
"@grpc/grpc-js": "^1.14.0",
"@maxmind/geoip2-node": "^3.4.0",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/auto-instrumentations-node": "^0.62.1",
@@ -74,6 +74,7 @@
"@posthog/plugin-scaffold": "1.4.4",
"@posthog/siphash": "1.1.1",
"@segment/action-destinations": "^3.383.0",
"@temporalio/client": "^1.12.0",
"@types/lru-cache": "^5.1.0",
"@types/node": "^22.13.14",
"@types/tail": "^2.2.1",

View File

@@ -1,3 +1,4 @@
import * as grpc from '@grpc/grpc-js'
import { Client, Connection } from '@temporalio/client'
import { Hub } from '~/types'
@@ -6,6 +7,8 @@ import { closeHub, createHub } from '~/utils/db/hub'
import { TemporalService } from './temporal.service'
jest.mock('@temporalio/client')
jest.mock('@grpc/grpc-js')
jest.mock('tls')
describe('TemporalService', () => {
let hub: Hub
@@ -34,6 +37,16 @@ describe('TemporalService', () => {
},
connection: mockConnection,
} as any
// Mock tls.createSecureContext
const tls = require('tls')
tls.createSecureContext = jest.fn().mockReturnValue({})
// Mock grpc.credentials.createFromSecureContext
const mockCredentials = {}
;(grpc.credentials as any) = {
createFromSecureContext: jest.fn().mockReturnValue(mockCredentials),
}
;(Connection.connect as jest.Mock) = jest.fn().mockResolvedValue(mockConnection)
;(Client as unknown as jest.Mock) = jest.fn().mockReturnValue(mockClient)
@@ -60,18 +73,33 @@ describe('TemporalService', () => {
hub.TEMPORAL_CLIENT_CERT = 'client-cert'
hub.TEMPORAL_CLIENT_KEY = 'client-key'
const tls = require('tls')
const mockSecureContext = {}
tls.createSecureContext = jest.fn().mockReturnValue(mockSecureContext)
const mockCredentials = {}
;(grpc.credentials as any) = {
createFromSecureContext: jest.fn().mockReturnValue(mockCredentials),
}
const newService = new TemporalService(hub)
await newService.startEvaluationRunWorkflow('test', 'test')
// Verify SecureContext was created with allowPartialTrustChain
expect(tls.createSecureContext).toHaveBeenCalledWith({
ca: expect.any(Buffer),
cert: expect.any(Buffer),
key: expect.any(Buffer),
allowPartialTrustChain: true,
})
// Verify gRPC credentials were created from SecureContext
expect(grpc.credentials.createFromSecureContext).toHaveBeenCalledWith(mockSecureContext)
// Verify Connection.connect was called with credentials
expect(Connection.connect).toHaveBeenCalledWith({
address: 'localhost:7233',
tls: {
serverRootCACertificate: expect.any(Buffer),
clientCertPair: {
crt: expect.any(Buffer),
key: expect.any(Buffer),
},
},
credentials: mockCredentials,
})
})

View File

@@ -1,5 +1,7 @@
import { Client, Connection, TLSConfig, WorkflowHandle } from '@temporalio/client'
import * as grpc from '@grpc/grpc-js'
import { Client, Connection, WorkflowHandle } from '@temporalio/client'
import { Counter } from 'prom-client'
import * as tls from 'tls'
import { Hub } from '../../types'
import { logger } from '../../utils/logger'
@@ -35,25 +37,26 @@ export class TemporalService {
}
private async createClient(): Promise<Client> {
// Configure TLS if certificates are provided (for production)
let tls: TLSConfig | boolean = false
if (this.hub.TEMPORAL_CLIENT_ROOT_CA && this.hub.TEMPORAL_CLIENT_CERT && this.hub.TEMPORAL_CLIENT_KEY) {
tls = {
serverRootCACertificate: Buffer.from(this.hub.TEMPORAL_CLIENT_ROOT_CA),
clientCertPair: {
crt: Buffer.from(this.hub.TEMPORAL_CLIENT_CERT),
key: Buffer.from(this.hub.TEMPORAL_CLIENT_KEY),
},
}
}
const port = this.hub.TEMPORAL_PORT || '7233'
const address = `${this.hub.TEMPORAL_HOST}:${port}`
// Create connection first
const connection = await Connection.connect({ address, tls })
let credentials: grpc.ChannelCredentials | undefined
if (this.hub.TEMPORAL_CLIENT_ROOT_CA && this.hub.TEMPORAL_CLIENT_CERT && this.hub.TEMPORAL_CLIENT_KEY) {
const secureContext = tls.createSecureContext({
ca: Buffer.from(this.hub.TEMPORAL_CLIENT_ROOT_CA),
cert: Buffer.from(this.hub.TEMPORAL_CLIENT_CERT),
key: Buffer.from(this.hub.TEMPORAL_CLIENT_KEY),
allowPartialTrustChain: true,
})
credentials = grpc.credentials.createFromSecureContext(secureContext)
}
const connection = await Connection.connect({
address,
...(credentials ? { credentials } : { tls: false }),
})
// Then create client with connection
const client = new Client({
connection,
namespace: this.hub.TEMPORAL_NAMESPACE || 'default',
@@ -62,7 +65,7 @@ export class TemporalService {
logger.info('✅ Connected to Temporal', {
address,
namespace: this.hub.TEMPORAL_NAMESPACE,
tlsEnabled: tls !== false,
tlsEnabled: credentials !== undefined,
})
return client

45
pnpm-lock.yaml generated
View File

@@ -1220,6 +1220,9 @@ importers:
'@google-cloud/storage':
specifier: ^5.8.5
version: 5.20.5(encoding@0.1.13)
'@grpc/grpc-js':
specifier: ^1.14.0
version: 1.14.0
'@maxmind/geoip2-node':
specifier: ^3.4.0
version: 3.5.0
@@ -4826,8 +4829,8 @@ packages:
resolution: {integrity: sha512-lOs/dCyveVF8TkVFnFSF7IGd0CJrTm91qiK6JLu+Z8qiT+7Ag0RyVhxZIWkhiACqwABo7kSHDm8FdH8p2wxSSw==}
engines: {node: '>=10'}
'@grpc/grpc-js@1.13.4':
resolution: {integrity: sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==}
'@grpc/grpc-js@1.14.0':
resolution: {integrity: sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==}
engines: {node: '>=12.10.0'}
'@grpc/proto-loader@0.7.15':
@@ -4835,6 +4838,11 @@ packages:
engines: {node: '>=6'}
hasBin: true
'@grpc/proto-loader@0.8.0':
resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==}
engines: {node: '>=6'}
hasBin: true
'@hapi/hoek@9.3.0':
resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==}
@@ -12304,6 +12312,7 @@ packages:
expect-playwright@0.8.0:
resolution: {integrity: sha512-+kn8561vHAY+dt+0gMqqj1oY+g5xWrsuGMk4QGxotT2WS545nVqqjs37z6hrYfIuucwqthzwJfCJUEYqixyljg==}
deprecated: ⚠️ The 'expect-playwright' package is deprecated. The Playwright core assertions (via @playwright/test) now cover the same functionality. Please migrate to built-in expect. See https://playwright.dev/docs/test-assertions for migration.
expect-type@1.2.2:
resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==}
@@ -13884,6 +13893,7 @@ packages:
jest-playwright-preset@4.0.0:
resolution: {integrity: sha512-+dGZ1X2KqtwXaabVjTGxy0a3VzYfvYsWaRcuO8vMhyclHSOpGSI1+5cmlqzzCwQ3+fv0EjkTc7I5aV9lo08dYw==}
deprecated: ⚠️ The 'jest-playwright-preset' package is deprecated. Please migrate to Playwright's built-in test runner (@playwright/test) which now includes full Jest-style features and parallel testing. See https://playwright.dev/docs/intro for details.
peerDependencies:
jest: ^29.3.1
jest-circus: ^29.3.1
@@ -13901,6 +13911,7 @@ packages:
jest-process-manager@0.4.0:
resolution: {integrity: sha512-80Y6snDyb0p8GG83pDxGI/kQzwVTkCxc7ep5FPe/F6JYdvRDhwr6RzRmPSP7SEwuLhxo80lBS/NqOdUIbHIfhw==}
deprecated: ⚠️ The 'jest-process-manager' package is deprecated. Please migrate to Playwright's built-in test runner (@playwright/test) which now includes full Jest-style features and parallel testing. See https://playwright.dev/docs/intro for details.
jest-regex-util@29.6.3:
resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==}
@@ -14772,6 +14783,7 @@ packages:
mathjax-full@3.2.2:
resolution: {integrity: sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==}
deprecated: Version 4 replaces this package with the scoped package @mathjax/src
mathml-tag-names@2.1.3:
resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==}
@@ -23214,9 +23226,9 @@ snapshots:
- encoding
- supports-color
'@grpc/grpc-js@1.13.4':
'@grpc/grpc-js@1.14.0':
dependencies:
'@grpc/proto-loader': 0.7.15
'@grpc/proto-loader': 0.8.0
'@js-sdsl/ordered-map': 4.4.2
'@grpc/proto-loader@0.7.15':
@@ -23226,6 +23238,13 @@ snapshots:
protobufjs: 7.5.3
yargs: 17.7.2
'@grpc/proto-loader@0.8.0':
dependencies:
lodash.camelcase: 4.3.0
long: 5.2.4
protobufjs: 7.5.3
yargs: 17.7.2
'@hapi/hoek@9.3.0': {}
'@hapi/topo@5.1.0':
@@ -24144,7 +24163,7 @@ snapshots:
'@opentelemetry/exporter-logs-otlp-grpc@0.203.0(@opentelemetry/api@1.9.0)':
dependencies:
'@grpc/grpc-js': 1.13.4
'@grpc/grpc-js': 1.14.0
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
'@opentelemetry/otlp-exporter-base': 0.203.0(@opentelemetry/api@1.9.0)
@@ -24174,7 +24193,7 @@ snapshots:
'@opentelemetry/exporter-metrics-otlp-grpc@0.203.0(@opentelemetry/api@1.9.0)':
dependencies:
'@grpc/grpc-js': 1.13.4
'@grpc/grpc-js': 1.14.0
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
'@opentelemetry/exporter-metrics-otlp-http': 0.203.0(@opentelemetry/api@1.9.0)
@@ -24212,7 +24231,7 @@ snapshots:
'@opentelemetry/exporter-trace-otlp-grpc@0.203.0(@opentelemetry/api@1.9.0)':
dependencies:
'@grpc/grpc-js': 1.13.4
'@grpc/grpc-js': 1.14.0
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
'@opentelemetry/otlp-exporter-base': 0.203.0(@opentelemetry/api@1.9.0)
@@ -24605,7 +24624,7 @@ snapshots:
'@opentelemetry/otlp-grpc-exporter-base@0.203.0(@opentelemetry/api@1.9.0)':
dependencies:
'@grpc/grpc-js': 1.13.4
'@grpc/grpc-js': 1.14.0
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
'@opentelemetry/otlp-exporter-base': 0.203.0(@opentelemetry/api@1.9.0)
@@ -28320,7 +28339,7 @@ snapshots:
commander: 9.4.1
expect-playwright: 0.8.0
glob: 10.4.5
jest: 29.7.0(@types/node@22.15.17)(ts-node@10.9.1(@swc/core@1.11.4(@swc/helpers@0.5.15))(@types/node@22.15.17)(typescript@5.2.2))
jest: 29.7.0(@types/node@22.18.8)(ts-node@10.9.1(@swc/core@1.11.4(@swc/helpers@0.5.15))(@types/node@22.18.8)(typescript@5.2.2))
jest-circus: 29.7.0
jest-environment-node: 29.7.0
jest-junit: 16.0.0
@@ -28618,7 +28637,7 @@ snapshots:
'@temporalio/client@1.13.1':
dependencies:
'@grpc/grpc-js': 1.13.4
'@grpc/grpc-js': 1.14.0
'@temporalio/common': 1.13.1
'@temporalio/proto': 1.13.1
abort-controller: 3.0.0
@@ -33618,7 +33637,7 @@ snapshots:
google-gax@4.6.1(encoding@0.1.13):
dependencies:
'@grpc/grpc-js': 1.13.4
'@grpc/grpc-js': 1.14.0
'@grpc/proto-loader': 0.7.15
'@types/long': 4.0.2
abort-controller: 3.0.0
@@ -34955,7 +34974,7 @@ snapshots:
jest-playwright-preset@4.0.0(jest-circus@29.7.0)(jest-environment-node@29.7.0)(jest-runner@29.7.0)(jest@29.7.0):
dependencies:
expect-playwright: 0.8.0
jest: 29.7.0(@types/node@22.15.17)(ts-node@10.9.1(@swc/core@1.11.4(@swc/helpers@0.5.15))(@types/node@22.15.17)(typescript@5.2.2))
jest: 29.7.0(@types/node@22.18.8)(ts-node@10.9.1(@swc/core@1.11.4(@swc/helpers@0.5.15))(@types/node@22.18.8)(typescript@5.2.2))
jest-circus: 29.7.0
jest-environment-node: 29.7.0
jest-process-manager: 0.4.0
@@ -35235,7 +35254,7 @@ snapshots:
dependencies:
ansi-escapes: 6.0.0
chalk: 5.4.1
jest: 29.7.0(@types/node@22.15.17)(ts-node@10.9.1(@swc/core@1.11.4(@swc/helpers@0.5.15))(@types/node@22.15.17)(typescript@5.2.2))
jest: 29.7.0(@types/node@22.18.8)(ts-node@10.9.1(@swc/core@1.11.4(@swc/helpers@0.5.15))(@types/node@22.18.8)(typescript@5.2.2))
jest-regex-util: 29.6.3
jest-watcher: 29.7.0
slash: 5.1.0