Skip to content

Commit 2e7d851

Browse files
authored
Merge pull request #2602 from 0x5457/fix/node-tls-reject-unauthorized-env-var
fix: respect NODE_TLS_REJECT_UNAUTHORIZED environment variable
2 parents 2220da3 + 5ab64ed commit 2e7d851

File tree

4 files changed

+150
-3
lines changed

4 files changed

+150
-3
lines changed

src/config.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,10 @@ export class KubeConfig implements SecurityAuthentication {
202202
agentOptions.key = opts.key;
203203
agentOptions.pfx = opts.pfx;
204204
agentOptions.passphrase = opts.passphrase;
205-
agentOptions.rejectUnauthorized = opts.rejectUnauthorized;
205+
// Only set rejectUnauthorized if explicitly configured. When not set, fetch will use NODE_TLS_REJECT_UNAUTHORIZED env var
206+
if (opts.rejectUnauthorized !== undefined) {
207+
agentOptions.rejectUnauthorized = opts.rejectUnauthorized;
208+
}
206209
// The ws docs say that it accepts anything that https.RequestOptions accepts,
207210
// but Typescript doesn't understand that idea (yet) probably could be fixed in
208211
// the typings, but for now just cast to any
@@ -259,7 +262,10 @@ export class KubeConfig implements SecurityAuthentication {
259262
agentOptions.key = httpsOptions.key;
260263
agentOptions.pfx = httpsOptions.pfx;
261264
agentOptions.passphrase = httpsOptions.passphrase;
262-
agentOptions.rejectUnauthorized = httpsOptions.rejectUnauthorized;
265+
// Only set rejectUnauthorized if explicitly configured. When not set, fetch will use NODE_TLS_REJECT_UNAUTHORIZED env var
266+
if (httpsOptions.rejectUnauthorized !== undefined) {
267+
agentOptions.rejectUnauthorized = httpsOptions.rejectUnauthorized;
268+
}
263269

264270
context.setAgent(this.createAgent(cluster, agentOptions));
265271
}

src/config_test.ts

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { fileURLToPath } from 'node:url';
1818
import mockfs from 'mock-fs';
1919

2020
import { Authenticator } from './auth.js';
21-
import { Headers } from 'node-fetch';
21+
import fetch, { Headers } from 'node-fetch';
2222
import { HttpMethod } from './index.js';
2323
import { assertRequestAgentsEqual, assertRequestOptionsEqual } from './test/match-buffer.js';
2424
import { CoreV1Api, RequestContext } from './api.js';
@@ -27,6 +27,7 @@ import { ActionOnInvalid, Cluster, newClusters, newContexts, newUsers, User } fr
2727
import { ExecAuth } from './exec_auth.js';
2828
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
2929
import { SocksProxyAgent } from 'socks-proxy-agent';
30+
import { AddressInfo } from 'node:net';
3031

3132
const kcFileName = 'testdata/kubeconfig.yaml';
3233
const kc2FileName = 'testdata/kubeconfig-2.yaml';
@@ -40,6 +41,9 @@ const kcInvalidContextFileName = 'testdata/empty-context-kubeconfig.yaml';
4041
const kcInvalidClusterFileName = 'testdata/empty-cluster-kubeconfig.yaml';
4142
const kcTlsServerNameFileName = 'testdata/tls-server-name-kubeconfig.yaml';
4243

44+
const testCertFileName = 'testdata/certs/test-cert.pem';
45+
const testKeyFileName = 'testdata/certs/test-key.pem';
46+
4347
const __dirname = dirname(fileURLToPath(import.meta.url));
4448

4549
describe('Config', () => {});
@@ -491,6 +495,61 @@ describe('KubeConfig', () => {
491495

492496
strictEqual(rc.getAgent() instanceof https.Agent, true);
493497
});
498+
499+
it('should apply NODE_TLS_REJECT_UNAUTHORIZED from environment to agent', async () => {
500+
const { server, host, port } = await createTestHttpsServer((req, res) => {
501+
res.setHeader('Content-Type', 'application/json');
502+
if (req.url?.includes('/api/v1/namespaces')) {
503+
res.writeHead(200);
504+
res.end(
505+
JSON.stringify({
506+
apiVersion: 'v1',
507+
kind: 'NamespaceList',
508+
items: [
509+
{
510+
apiVersion: 'v1',
511+
kind: 'Namespace',
512+
metadata: { name: 'default' },
513+
},
514+
],
515+
}),
516+
);
517+
} else {
518+
res.writeHead(200);
519+
res.end('ok');
520+
}
521+
});
522+
523+
const originalValue = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
524+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // lgtm[js/disabling-certificate-validation]
525+
after(() => {
526+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = originalValue;
527+
server.close();
528+
});
529+
530+
const kc = new KubeConfig();
531+
kc.loadFromClusterAndUser(
532+
{
533+
name: 'test-cluster',
534+
server: `https://${host}:${port}`,
535+
// ignore skipTLSVerify specified from environment variables
536+
} as Cluster,
537+
{
538+
name: 'test-user',
539+
token: 'test-token',
540+
},
541+
);
542+
const coreV1Api = kc.makeApiClient(CoreV1Api);
543+
const namespaceList = await coreV1Api.listNamespace();
544+
545+
strictEqual(namespaceList.kind, 'NamespaceList');
546+
strictEqual(namespaceList.items.length, 1);
547+
strictEqual(namespaceList.items[0].metadata?.name, 'default');
548+
549+
const res2 = await fetch(`https://${host}:${port}`, await kc.applyToFetchOptions({}));
550+
strictEqual(res2.status, 200);
551+
strictEqual(await res2.text(), 'ok');
552+
});
494553
});
495554

496555
describe('loadClusterConfigObjects', () => {
@@ -1827,3 +1886,38 @@ describe('KubeConfig', () => {
18271886
});
18281887
});
18291888
});
1889+
1890+
// create a self-signed HTTPS test server
1891+
async function createTestHttpsServer(
1892+
requestHandler?: (req: http.IncomingMessage, res: http.ServerResponse) => void,
1893+
): Promise<{
1894+
server: https.Server;
1895+
host: string;
1896+
port: number;
1897+
ca: string;
1898+
}> {
1899+
const host = 'localhost';
1900+
1901+
const cert = readFileSync(testCertFileName, 'utf8');
1902+
const key = readFileSync(testKeyFileName, 'utf8');
1903+
1904+
const defaultHandler = (req: http.IncomingMessage, res: http.ServerResponse) => {
1905+
res.writeHead(200);
1906+
res.end('ok');
1907+
};
1908+
1909+
const server = https.createServer({ key, cert }, requestHandler ?? defaultHandler);
1910+
1911+
const port = await new Promise<number>((resolve) => {
1912+
server.listen(0, () => {
1913+
resolve((server.address() as AddressInfo).port);
1914+
});
1915+
});
1916+
1917+
return {
1918+
server,
1919+
host,
1920+
port,
1921+
ca: cert, // ca is the same as cert here
1922+
};
1923+
}

testdata/certs/test-cert.pem

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDCzCCAfOgAwIBAgIUPTyeIJ44dN2PZYW0a3WGYfcB6iwwDQYJKoZIhvcNAQEL
3+
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTI1MDkwNjEwNDAzN1oYDzQ3NjMw
4+
ODAzMTA0MDM3WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB
5+
AQUAA4IBDwAwggEKAoIBAQCdDiYdfXRhzDLum5pqa6BICCPfQ+vqTfF3aYrqAwV1
6+
C63hYs/yU+IK83ohiPScmShmAP2ofHsP/8R9HK7LEWkvO5ZlGxebE9ARkXa51Gs9
7+
g8IBjH+10EL5BcTHnb+T187rTlSaSpM59LVXhlsI/zzDB6VnvApPyLFpYJ0YoYau
8+
4gA4rMrkZGkziCx85ONdWxYyjh4RemwNxOIzmEHg5R7v7g5yPxmNcmK4BQ0XLFAf
9+
4KgMAlhIpGz03vOz8mP/JTKO8PoB9rmKmsEANB3MQW9C/n4yosVjqN9lyaJXLII3
10+
6QPRi7bxqH5sq2rRfNNA9KbiszySWda7jupB8JgiBcnDAgMBAAGjUzBRMB0GA1Ud
11+
DgQWBBTuleDZd59erSUzMOu46Yz1q9iDoDAfBgNVHSMEGDAWgBTuleDZd59erSUz
12+
MOu46Yz1q9iDoDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBB
13+
dfUNSeJj5oZi9QCFkjqIW0Zr3x1ODjaPVtlvp0lfcRF2qUBbaA8qvvDTbhWrS4Xl
14+
NLzgK+aFhgJOcTj13BYNy1yag7ZnkwunInzsEGYJgC/JgZ93De/gWs88icOUHTo0
15+
Eg/eco6usqykz/1ZDbUwNf5rOItdXt+cp6kpWkrapz4RISddgN0kIdwEOjCKh0+b
16+
EvJ5lH/UUwVrfZ2KI4kz1A1gQzgA1flqwLm7CNxZtRywfZR4F2mpX9dafBFqzm4w
17+
Y9jCrrhS7Y9p3Q0muHLjOOXOAYO+w/Z0av3JqvbQC1bxz3ybjSPjL8bhP3ptJarW
18+
yd4YH2zt3+0omzYwHfRs
19+
-----END CERTIFICATE-----

testdata/certs/test-key.pem

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCdDiYdfXRhzDLu
3+
m5pqa6BICCPfQ+vqTfF3aYrqAwV1C63hYs/yU+IK83ohiPScmShmAP2ofHsP/8R9
4+
HK7LEWkvO5ZlGxebE9ARkXa51Gs9g8IBjH+10EL5BcTHnb+T187rTlSaSpM59LVX
5+
hlsI/zzDB6VnvApPyLFpYJ0YoYau4gA4rMrkZGkziCx85ONdWxYyjh4RemwNxOIz
6+
mEHg5R7v7g5yPxmNcmK4BQ0XLFAf4KgMAlhIpGz03vOz8mP/JTKO8PoB9rmKmsEA
7+
NB3MQW9C/n4yosVjqN9lyaJXLII36QPRi7bxqH5sq2rRfNNA9KbiszySWda7jupB
8+
8JgiBcnDAgMBAAECggEAD0Uk55EfE7Mq8JAof1hfiSFhe3+7HFjftWCJpR8OFMdB
9+
7LwSw9jsDWyG32PVhLRPfTtzbkJMJM4VaKS1SgEzXOhKQyJTNTzD6jFefcrtclmx
10+
Lz1d3WuWV2f8LfxkeBdvgulmyGmfzu7AAvaJO2K1obDIoEFkL0WwGjLOk2qBEde4
11+
V3hXEoBiHkoEE5mEgfUarCL8tLmyiIc0gpE231vKrbuSjyi1V/nV2elYujeVmJ/F
12+
23c5/SodcQnI/tUrN+rIvhBoP6V0ddrieTBtzf/jKrAvvYge3o+X/Z0idIQowyQs
13+
boUD2XHieImMEXwfuGyKj2dtCd8rbhOI5Mfroqa04QKBgQDZWOO1iv2vrz8VevCn
14+
se4n3mBaxfScdbVLNKnwe/7FW+4UKuxB5F5lMMAWPwgN85+NSH7YbcvUkFcLf/Ge
15+
zBPXtDrvkTeQxyzAfvmjrD+1dMgP6wM5PDJ1e7Cz2yo4Hsql1VJ1H+nd1JFfJysL
16+
YwkcDcrIx6aEAdw8qxUZLDdOVwKBgQC4/Fz9IS1UKpuYoU60YPSKABB7JqAJCUlm
17+
trS6eI8qwJW9vpg+9w1T/y+lOYPiYq16u+rF59vdh2883mwJnYiR8QCsv5VfRPuR
18+
dLzZAMMqWtqSXnLbHMHXdyZEZOxh6Qfix0tSRd0A6y876kWE1OkDCi6ARkXVAWnC
19+
oPLxHeNkdQKBgG2v0GskE9b/yARdIOpgf2IbdeEZmdMEDFRB5al5yh9rv4DqEIVI
20+
bOMAcVBIyxXPZyvz9B/heUZy+ZrSHOwY7cKkMEIKtVIZUlprOi0Blr1KjFSMM/pE
21+
iOqFW63I40ujLn32ZEC7tFjBGAQ/ThfXCRfhVf9x0nU4Qx9S77jeeaLNAoGBALVL
22+
N0MpkcgsHeQfKwhjASaCW6SmPS+99z8ADu21m/I1XkvgkEsdSuWociSG0rc7KHPh
23+
2Xxt+LAKvL0160IdLyyAur2S4azF6Zsrgq1WLu/CrPXINN6DN4KYlltvYa+vd3gN
24+
A8e1CpyM4fTha5J8K4U8JEi5FlVklicWICKovSPFAoGAYSgPvteAo2RAN6su9d8O
25+
s7oLXnFLqaF+Fo9vdc9uEKzzdROf7GCpz/6uOb9NCiRFpu8bNyDKK4UpNqMbO97E
26+
Km+QQuBOms16ic/lOUT6sWVe3V6FIs18xBxNNE7LrfPfa8Vory7YoVsXji6SWikT
27+
oLTjd9Tt7SOW/v7q9GHW798=
28+
-----END PRIVATE KEY-----

0 commit comments

Comments
 (0)