diff --git a/plugin-server/package.json b/plugin-server/package.json index de1c015d3f..315f81132f 100644 --- a/plugin-server/package.json +++ b/plugin-server/package.json @@ -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", diff --git a/plugin-server/src/llm-analytics/services/temporal.service.test.ts b/plugin-server/src/llm-analytics/services/temporal.service.test.ts index 6107ec6fcb..b58925749c 100644 --- a/plugin-server/src/llm-analytics/services/temporal.service.test.ts +++ b/plugin-server/src/llm-analytics/services/temporal.service.test.ts @@ -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), + }, + }, }) }) diff --git a/plugin-server/src/llm-analytics/services/temporal.service.ts b/plugin-server/src/llm-analytics/services/temporal.service.ts index 9cce0bf74e..39b5527e74 100644 --- a/plugin-server/src/llm-analytics/services/temporal.service.ts +++ b/plugin-server/src/llm-analytics/services/temporal.service.ts @@ -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 { + 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 { + 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 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d7a2c5d9e4..1dabf22f24 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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