mirror of
https://github.com/anthropic-experimental/sandbox-runtime.git
synced 2026-05-06 21:52:30 +08:00
* Add opt-in configuration for providing CA cert and key * Wire tlsTerminate CA loader into SandboxManager.initialize() When network.tlsTerminate is set, initialize() loads and validates the CA (throws on unreadable/non-PEM). reset() clears the cache. No behavior change when tlsTerminate is unset. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Add tests for tlsTerminate config and loadMitmCA - test/fixtures/tls-terminate/: committed test-only RSA-2048 self-signed CA (CN=srt-test-ca DO NOT TRUST, valid to 2126). README documents the generating openssl command. - test/sandbox/mitm-ca.test.ts: load/cache/reset plus all throw paths (missing file, non-PEM, swapped cert/key) against the fixture CA. - test/config-validation.test.ts: schema cases for network.tlsTerminate (optional, both paths required, non-empty). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
90 lines
3.1 KiB
TypeScript
90 lines
3.1 KiB
TypeScript
import { describe, test, expect, beforeEach, afterAll } from 'bun:test'
|
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import {
|
|
loadMitmCA,
|
|
getMitmCA,
|
|
resetMitmCA,
|
|
} from '../../src/sandbox/mitm-ca.js'
|
|
|
|
// Committed test-only CA — see test/fixtures/tls-terminate/README.md.
|
|
const FIXTURE_DIR = join(import.meta.dir, '..', 'fixtures', 'tls-terminate')
|
|
const certPath = join(FIXTURE_DIR, 'ca.crt')
|
|
const keyPath = join(FIXTURE_DIR, 'ca.key')
|
|
const certPem = readFileSync(certPath, 'utf8')
|
|
const keyPem = readFileSync(keyPath, 'utf8')
|
|
|
|
describe('mitm-ca: loadMitmCA', () => {
|
|
const scratch = mkdtempSync(join(tmpdir(), 'srt-mitm-ca-'))
|
|
const junkPath = join(scratch, 'junk.txt')
|
|
writeFileSync(junkPath, 'not pem\n')
|
|
|
|
beforeEach(() => {
|
|
resetMitmCA()
|
|
})
|
|
|
|
afterAll(() => {
|
|
rmSync(scratch, { recursive: true, force: true })
|
|
})
|
|
|
|
test('loads a real cert+key pair and exposes it via getMitmCA', () => {
|
|
const ca = loadMitmCA({ caCertPath: certPath, caKeyPath: keyPath })
|
|
expect(ca.certPath).toBe(certPath)
|
|
expect(ca.keyPath).toBe(keyPath)
|
|
expect(ca.certPem).toBe(certPem)
|
|
expect(ca.keyPem).toBe(keyPem)
|
|
expect(ca.certPem).toContain('-----BEGIN CERTIFICATE-----')
|
|
// openssl req -nodes emits PKCS8 ("PRIVATE KEY"); the loader's regex also
|
|
// accepts PKCS1 "RSA PRIVATE KEY" / "EC PRIVATE KEY".
|
|
expect(ca.keyPem).toMatch(/-----BEGIN (RSA |EC )?PRIVATE KEY-----/)
|
|
expect(getMitmCA()).toBe(ca)
|
|
})
|
|
|
|
test('caches: second call returns the same instance', () => {
|
|
const a = loadMitmCA({ caCertPath: certPath, caKeyPath: keyPath })
|
|
const b = loadMitmCA({ caCertPath: certPath, caKeyPath: keyPath })
|
|
expect(b).toBe(a)
|
|
})
|
|
|
|
test('resetMitmCA clears the cache', () => {
|
|
loadMitmCA({ caCertPath: certPath, caKeyPath: keyPath })
|
|
expect(getMitmCA()).toBeDefined()
|
|
resetMitmCA()
|
|
expect(getMitmCA()).toBeUndefined()
|
|
})
|
|
|
|
test('throws with field+path+code when cert path is missing', () => {
|
|
const missing = join(scratch, 'nope.crt')
|
|
expect(() =>
|
|
loadMitmCA({ caCertPath: missing, caKeyPath: keyPath }),
|
|
).toThrow(/tlsTerminate\.caCertPath: cannot read .*nope\.crt \(ENOENT\)/)
|
|
expect(getMitmCA()).toBeUndefined()
|
|
})
|
|
|
|
test('throws with field+path+code when key path is missing', () => {
|
|
const missing = join(scratch, 'nope.key')
|
|
expect(() =>
|
|
loadMitmCA({ caCertPath: certPath, caKeyPath: missing }),
|
|
).toThrow(/tlsTerminate\.caKeyPath: cannot read .*nope\.key \(ENOENT\)/)
|
|
})
|
|
|
|
test('throws when cert file is not PEM', () => {
|
|
expect(() =>
|
|
loadMitmCA({ caCertPath: junkPath, caKeyPath: keyPath }),
|
|
).toThrow(/tlsTerminate\.caCertPath: .* is not a PEM CERTIFICATE/)
|
|
})
|
|
|
|
test('throws when key file is not PEM', () => {
|
|
expect(() =>
|
|
loadMitmCA({ caCertPath: certPath, caKeyPath: junkPath }),
|
|
).toThrow(/tlsTerminate\.caKeyPath: .* is not a PEM PRIVATE KEY/)
|
|
})
|
|
|
|
test('throws when cert and key are swapped', () => {
|
|
expect(() =>
|
|
loadMitmCA({ caCertPath: keyPath, caKeyPath: certPath }),
|
|
).toThrow(/is not a PEM CERTIFICATE/)
|
|
})
|
|
})
|