Skip to content

Commit 59ec54e

Browse files
committed
fix: merge global agent CA certificates with cluster-specific ones
When system certificates are configured through the global https.agent, they were being ignored by the kubernetes-client because it provides its own dispatcher to undici. This fix merges the global agent's CA certificates with any cluster-specific CA certificates, ensuring that custom CAs configured via the global agent are respected. Fixes #2546
1 parent 1b6a84a commit 59ec54e

2 files changed

Lines changed: 77 additions & 1 deletion

File tree

src/config.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,25 @@ export class KubeConfig implements SecurityAuthentication {
648648
agentOptions: https.AgentOptions,
649649
): DispatcherOptions {
650650
const tlsOptions: tls.ConnectionOptions = {};
651-
if (agentOptions.ca !== undefined) tlsOptions.ca = agentOptions.ca;
651+
// Merge global agent CA certificates with cluster-specific ones
652+
const globalCA = (https.globalAgent as https.Agent).options?.ca;
653+
if (globalCA !== undefined) {
654+
tlsOptions.ca = globalCA;
655+
}
656+
if (agentOptions.ca !== undefined) {
657+
// If both global and cluster CA exist, concatenate them
658+
if (tlsOptions.ca !== undefined) {
659+
const globalCAs = Array.isArray(tlsOptions.ca) ? tlsOptions.ca : [tlsOptions.ca];
660+
const clusterCAs = Array.isArray(agentOptions.ca) ? agentOptions.ca : [agentOptions.ca];
661+
tlsOptions.ca = Buffer.concat(
662+
[...globalCAs, ...clusterCAs].map((ca) =>
663+
Buffer.isBuffer(ca) ? ca : Buffer.from(ca),
664+
),
665+
);
666+
} else {
667+
tlsOptions.ca = agentOptions.ca;
668+
}
669+
}
652670
if (agentOptions.cert !== undefined) tlsOptions.cert = agentOptions.cert;
653671
if (agentOptions.key !== undefined) tlsOptions.key = agentOptions.key;
654672
if (agentOptions.pfx !== undefined) tlsOptions.pfx = agentOptions.pfx;

src/config_test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,64 @@ describe('KubeConfig', () => {
565565
message: 'Unsupported proxy type',
566566
});
567567
});
568+
it('should merge global agent CA with cluster CA', async () => {
569+
const kc = new KubeConfig();
570+
kc.loadFromFile(kcFileName);
571+
572+
// Set a global CA
573+
const globalCA = Buffer.from('GLOBAL_CA_DATA');
574+
const originalGlobalCA = (https.globalAgent as https.Agent).options?.ca;
575+
(https.globalAgent as https.Agent).options.ca = globalCA;
576+
577+
try {
578+
const clusterCA = Buffer.from('CADATA2', 'utf-8');
579+
const dispatcherOpts = kc.createDispatcherOptions(kc.getCurrentCluster(), {
580+
ca: clusterCA,
581+
cert: undefined,
582+
key: undefined,
583+
});
584+
585+
strictEqual(dispatcherOpts.type, 'agent');
586+
// The CA should be a Buffer containing both global and cluster CA data
587+
const combinedCA = dispatcherOpts.connect.ca as Buffer;
588+
strictEqual(combinedCA.includes('GLOBAL_CA_DATA'), true);
589+
strictEqual(combinedCA.includes('CADATA2'), true);
590+
} finally {
591+
// Restore original global CA
592+
if (originalGlobalCA !== undefined) {
593+
(https.globalAgent as https.Agent).options.ca = originalGlobalCA;
594+
} else {
595+
delete (https.globalAgent as https.Agent).options.ca;
596+
}
597+
}
598+
});
599+
it('should use global CA when no cluster CA is provided', async () => {
600+
const kc = new KubeConfig();
601+
kc.loadFromFile(kcFileName);
602+
603+
// Set a global CA
604+
const globalCA = Buffer.from('GLOBAL_CA_DATA_ONLY');
605+
const originalGlobalCA = (https.globalAgent as https.Agent).options?.ca;
606+
(https.globalAgent as https.Agent).options.ca = globalCA;
607+
608+
try {
609+
const dispatcherOpts = kc.createDispatcherOptions(kc.getCurrentCluster(), {
610+
cert: undefined,
611+
key: undefined,
612+
});
613+
614+
strictEqual(dispatcherOpts.type, 'agent');
615+
// The CA should be the global CA only
616+
strictEqual(dispatcherOpts.connect.ca?.toString(), 'GLOBAL_CA_DATA_ONLY');
617+
} finally {
618+
// Restore original global CA
619+
if (originalGlobalCA !== undefined) {
620+
(https.globalAgent as https.Agent).options.ca = originalGlobalCA;
621+
} else {
622+
delete (https.globalAgent as https.Agent).options.ca;
623+
}
624+
}
625+
});
568626
it('should throw if cluster.server starts with http and skipTLSVerify is not set via applyToHTTPSOptions', async () => {
569627
const kc = new KubeConfig();
570628
kc.loadFromFile(kcProxyUrl);

0 commit comments

Comments
 (0)