fix: plugin server temporal TLS config (#40718)

This commit is contained in:
Carlos Marchal
2025-10-31 11:32:37 +01:00
committed by GitHub
parent dde612d95c
commit 2dbf18cadf
4 changed files with 62 additions and 90 deletions

View File

@@ -48,6 +48,7 @@
"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",
@@ -58,7 +59,6 @@
"@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,7 +74,6 @@
"@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,4 +1,3 @@
import * as grpc from '@grpc/grpc-js'
import { Client, Connection } from '@temporalio/client'
import { Hub } from '~/types'
@@ -7,8 +6,6 @@ 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
@@ -37,16 +34,6 @@ 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)
@@ -73,33 +60,18 @@ 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',
credentials: mockCredentials,
tls: {
serverRootCACertificate: expect.any(Buffer),
clientCertPair: {
crt: expect.any(Buffer),
key: expect.any(Buffer),
},
},
})
})

View File

@@ -1,7 +1,6 @@
import * as grpc from '@grpc/grpc-js'
import { Client, Connection, WorkflowHandle } from '@temporalio/client'
import { Client, Connection, TLSConfig, WorkflowHandle } from '@temporalio/client'
import fs from 'fs/promises'
import { Counter } from 'prom-client'
import * as tls from 'tls'
import { Hub } from '../../types'
import { logger } from '../../utils/logger'
@@ -36,37 +35,58 @@ export class TemporalService {
return this.client
}
private async buildTLSConfig(): Promise<TLSConfig | false> {
const { TEMPORAL_CLIENT_ROOT_CA, TEMPORAL_CLIENT_CERT, TEMPORAL_CLIENT_KEY } = this.hub
if (!(TEMPORAL_CLIENT_ROOT_CA && TEMPORAL_CLIENT_CERT && TEMPORAL_CLIENT_KEY)) {
return false
}
let systemCAs = Buffer.alloc(0)
try {
const fileBuffer = await fs.readFile('/etc/ssl/certs/ca-certificates.crt')
systemCAs = Buffer.from(fileBuffer)
} catch (err: any) {
if (err.code !== 'ENOENT') {
logger.warn('⚠️ Failed to load system CA bundle', { err })
} else {
logger.debug(' System CA bundle not found — using only provided root CA')
}
}
const combinedCA = Buffer.concat([systemCAs, Buffer.from(TEMPORAL_CLIENT_ROOT_CA)])
logger.debug('🔐 TLS configuration built', {
systemCABundle: systemCAs.length > 0,
combinedCABytes: combinedCA.length,
})
return {
serverRootCACertificate: combinedCA,
clientCertPair: {
crt: Buffer.from(TEMPORAL_CLIENT_CERT),
key: Buffer.from(TEMPORAL_CLIENT_KEY),
},
}
}
private async createClient(): Promise<Client> {
const tls = await this.buildTLSConfig()
const port = this.hub.TEMPORAL_PORT || '7233'
const address = `${this.hub.TEMPORAL_HOST}:${port}`
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 }),
})
const connection = await Connection.connect({ address, tls })
const client = new Client({
connection,
namespace: this.hub.TEMPORAL_NAMESPACE || 'default',
})
const tlsEnabled: boolean = credentials !== undefined
logger.info('✅ Connected to Temporal', {
address,
namespace: this.hub.TEMPORAL_NAMESPACE,
tlsEnabled,
tlsEnabled: tls !== false,
})
return client

45
pnpm-lock.yaml generated
View File

@@ -1223,9 +1223,6 @@ 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
@@ -4869,8 +4866,8 @@ packages:
resolution: {integrity: sha512-lOs/dCyveVF8TkVFnFSF7IGd0CJrTm91qiK6JLu+Z8qiT+7Ag0RyVhxZIWkhiACqwABo7kSHDm8FdH8p2wxSSw==}
engines: {node: '>=10'}
'@grpc/grpc-js@1.14.0':
resolution: {integrity: sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==}
'@grpc/grpc-js@1.13.4':
resolution: {integrity: sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==}
engines: {node: '>=12.10.0'}
'@grpc/proto-loader@0.7.15':
@@ -4878,11 +4875,6 @@ 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==}
@@ -12352,7 +12344,6 @@ 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==}
@@ -13933,7 +13924,6 @@ 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
@@ -13951,7 +13941,6 @@ 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==}
@@ -14823,7 +14812,6 @@ 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==}
@@ -23266,9 +23254,9 @@ snapshots:
- encoding
- supports-color
'@grpc/grpc-js@1.14.0':
'@grpc/grpc-js@1.13.4':
dependencies:
'@grpc/proto-loader': 0.8.0
'@grpc/proto-loader': 0.7.15
'@js-sdsl/ordered-map': 4.4.2
'@grpc/proto-loader@0.7.15':
@@ -23278,13 +23266,6 @@ 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':
@@ -24203,7 +24184,7 @@ snapshots:
'@opentelemetry/exporter-logs-otlp-grpc@0.203.0(@opentelemetry/api@1.9.0)':
dependencies:
'@grpc/grpc-js': 1.14.0
'@grpc/grpc-js': 1.13.4
'@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)
@@ -24233,7 +24214,7 @@ snapshots:
'@opentelemetry/exporter-metrics-otlp-grpc@0.203.0(@opentelemetry/api@1.9.0)':
dependencies:
'@grpc/grpc-js': 1.14.0
'@grpc/grpc-js': 1.13.4
'@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)
@@ -24271,7 +24252,7 @@ snapshots:
'@opentelemetry/exporter-trace-otlp-grpc@0.203.0(@opentelemetry/api@1.9.0)':
dependencies:
'@grpc/grpc-js': 1.14.0
'@grpc/grpc-js': 1.13.4
'@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)
@@ -24664,7 +24645,7 @@ snapshots:
'@opentelemetry/otlp-grpc-exporter-base@0.203.0(@opentelemetry/api@1.9.0)':
dependencies:
'@grpc/grpc-js': 1.14.0
'@grpc/grpc-js': 1.13.4
'@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)
@@ -28379,7 +28360,7 @@ snapshots:
commander: 9.4.1
expect-playwright: 0.8.0
glob: 10.4.5
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: 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-circus: 29.7.0
jest-environment-node: 29.7.0
jest-junit: 16.0.0
@@ -28677,7 +28658,7 @@ snapshots:
'@temporalio/client@1.13.1':
dependencies:
'@grpc/grpc-js': 1.14.0
'@grpc/grpc-js': 1.13.4
'@temporalio/common': 1.13.1
'@temporalio/proto': 1.13.1
abort-controller: 3.0.0
@@ -33679,7 +33660,7 @@ snapshots:
google-gax@4.6.1(encoding@0.1.13):
dependencies:
'@grpc/grpc-js': 1.14.0
'@grpc/grpc-js': 1.13.4
'@grpc/proto-loader': 0.7.15
'@types/long': 4.0.2
abort-controller: 3.0.0
@@ -35016,7 +34997,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.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: 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-circus: 29.7.0
jest-environment-node: 29.7.0
jest-process-manager: 0.4.0
@@ -35296,7 +35277,7 @@ snapshots:
dependencies:
ansi-escapes: 6.0.0
chalk: 5.4.1
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: 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-regex-util: 29.6.3
jest-watcher: 29.7.0
slash: 5.1.0