Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow scoped client certificates #15

Merged
merged 33 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2f4aac0
feat: allow scoped client certificates
nachoaldamav Jan 17, 2024
8a91a38
style: remove new line and use template literal
nachoaldamav Jan 17, 2024
f05a105
fix: use `node-fetch` latest cjs version
nachoaldamav Jan 17, 2024
eff7e9a
test: fix self-signed certificate regex
nachoaldamav Jan 17, 2024
3254db9
style: remove unused `@ts-expect-error`
nachoaldamav Jan 17, 2024
739962b
fix: use Powershell in windows CI (#16)
nachoaldamav Jan 18, 2024
b8b6bdb
refactor: replace `uriToHost ` with `nerf-dart`
nachoaldamav Jan 18, 2024
2cfe141
test: add tests
nachoaldamav Jan 18, 2024
aea8ad2
Merge branch 'main' into allow-scoped-client-certs
zkochan Jan 18, 2024
d57f791
style: update
zkochan Jan 18, 2024
48185aa
style: update
zkochan Jan 18, 2024
06bf859
style: update
zkochan Jan 18, 2024
af60120
style: update
zkochan Jan 18, 2024
6f691da
style: update
zkochan Jan 18, 2024
ab232be
fix: conditional if https
nachoaldamav Jan 18, 2024
d6d8b70
refactor: destructure client certificates
nachoaldamav Jan 18, 2024
d753364
fix: type errors
nachoaldamav Jan 18, 2024
c08464b
test: add more tests
nachoaldamav Jan 18, 2024
14d9aa9
feat: add url component for URL specific functions
nachoaldamav Jan 25, 2024
f1bff63
fix: trailing slash
nachoaldamav Jan 25, 2024
d7600ef
fix: get each uri part correctly
nachoaldamav Jan 31, 2024
03bea7d
refactor: move `getXFromUri` to `url` component
nachoaldamav Jan 31, 2024
25fe3ce
fix: disable v8 cache
nachoaldamav Feb 1, 2024
de85ccb
revert: disable v8 cache
nachoaldamav Feb 1, 2024
9905ec3
refactor: make `getMaxParts` an internal function
nachoaldamav Feb 1, 2024
5b5f415
fix: install bvm manually and use system node version
nachoaldamav Feb 1, 2024
ebba2d3
revert: install bvm globally
nachoaldamav Feb 1, 2024
277243e
ci: use bit v1.6.44
zkochan Feb 1, 2024
2b08af5
fix: install Bit v1.6.44
nachoaldamav Feb 1, 2024
20131e5
chore: remove `settings.json`
nachoaldamav Feb 1, 2024
67d97e3
refactor: change component name to `pnpm.network/config`
nachoaldamav Feb 6, 2024
b2ae53f
fix: replate `\r\n` with `\n` to match UNIX style
nachoaldamav Feb 6, 2024
78f612d
refactor: remove unused functions
nachoaldamav Feb 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 45 additions & 2 deletions network/agent/agent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ jest.mock('agentkeepalive', () => {
})
jest.mock('https-proxy-agent', () => mockHttpAgent('https-proxy'))

function mockHttpAgent (type: string) {
return function Agent (opts: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
function mockHttpAgent(type: string) {
return function Agent(opts: any) {
// eslint-disable-line @typescript-eslint/no-explicit-any
return {
...opts,
__type: type,
Expand Down Expand Up @@ -89,3 +90,45 @@ test("don't use a proxy when the URL is in noProxy", () => {
})
})

test('should return the correct client certificates', () => {
const agent = getAgent('https://foo.com/bar', {
clientCertificates: {
'//foo.com/': {
ca: 'ca',
cert: 'cert',
key: 'key',
},
},
})

expect(agent).toEqual({
ca: 'ca',
cert: 'cert',
key: 'key',
localAddress: undefined,
maxSockets: 50,
rejectUnauthorized: undefined,
timeout: 0,
__type: 'https',
})
})

test('should not return client certificates for a different host', () => {
const agent = getAgent('https://foo.com/bar', {
clientCertificates: {
'//bar.com/': {
ca: 'ca',
cert: 'cert',
key: 'key',
},
},
})

expect(agent).toEqual({
localAddress: undefined,
maxSockets: 50,
rejectUnauthorized: undefined,
timeout: 0,
__type: 'https',
})
})
59 changes: 40 additions & 19 deletions network/agent/agent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { URL } from 'url'
import HttpAgent from 'agentkeepalive'
import LRU from 'lru-cache'
import nerfDart from 'nerf-dart'
import { getProxyAgent, ProxyAgentOptions } from '@pnpm/network.proxy-agent'

const HttpsAgent = HttpAgent.HttpsAgent
Expand All @@ -23,16 +24,27 @@ export function getAgent (uri: string, opts: AgentOptions) {

function getNonProxyAgent (uri: string, opts: AgentOptions) {
const parsedUri = new URL(uri)
const host = nerfDart(uri)
const isHttps = parsedUri.protocol === 'https:'

const clientCertificates = opts.clientCertificates?.[host] ?? null
nachoaldamav marked this conversation as resolved.
Show resolved Hide resolved

/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
const key = [
`https:${isHttps.toString()}`,
`local-address:${opts.localAddress ?? '>no-local-address<'}`,
`strict-ssl:${isHttps ? Boolean(opts.strictSsl).toString() : '>no-strict-ssl<'}`,
`ca:${(isHttps && opts.ca?.toString()) || '>no-ca<'}`,
`cert:${(isHttps && opts.cert?.toString()) || '>no-cert<'}`,
`key:${(isHttps && opts.key) || '>no-key<'}`,
`strict-ssl:${
isHttps ? Boolean(opts.strictSsl).toString() : '>no-strict-ssl<'
}`,
`ca:${
(isHttps && clientCertificates?.ca) || opts.ca?.toString() || '>no-ca<'
nachoaldamav marked this conversation as resolved.
Show resolved Hide resolved
}`,
`cert:${
(isHttps && clientCertificates?.cert) ||
opts.cert?.toString() ||
'>no-cert<'
nachoaldamav marked this conversation as resolved.
Show resolved Hide resolved
}`,
`key:${(isHttps && clientCertificates?.key) || opts.key || '>no-key<'}`,
nachoaldamav marked this conversation as resolved.
Show resolved Hide resolved
].join(':')
/* eslint-enable @typescript-eslint/prefer-nullish-coalescing */

Expand All @@ -45,7 +57,10 @@ function getNonProxyAgent (uri: string, opts: AgentOptions) {
// opts.timeout is a non-zero value, set it to timeout + 1, to ensure that
// the node-fetch-npm timeout will always fire first, giving us more
// consistent errors.
const agentTimeout = typeof opts.timeout !== 'number' || opts.timeout === 0 ? 0 : opts.timeout + 1
const agentTimeout =
typeof opts.timeout !== 'number' || opts.timeout === 0
? 0
: opts.timeout + 1

// NOTE: localAddress is passed to the agent here even though it is an
// undocumented option of the agent's constructor.
Expand All @@ -55,29 +70,35 @@ function getNonProxyAgent (uri: string, opts: AgentOptions) {
// https://github.com/nodejs/node/blob/350a95b89faab526de852d417bbb8a3ac823c325/lib/_http_agent.js#L254
const agent = isHttps
? new HttpsAgent({
ca: opts.ca,
cert: opts.cert,
key: opts.key,
localAddress: opts.localAddress,
maxSockets: opts.maxSockets ?? DEFAULT_MAX_SOCKETS,
rejectUnauthorized: opts.strictSsl,
timeout: agentTimeout,
} as any) // eslint-disable-line @typescript-eslint/no-explicit-any
ca: clientCertificates?.ca || opts.ca,
cert: clientCertificates?.cert || opts.cert,
key: clientCertificates?.key || opts.key,
localAddress: opts.localAddress,
maxSockets: opts.maxSockets ?? DEFAULT_MAX_SOCKETS,
rejectUnauthorized: opts.strictSsl,
timeout: agentTimeout,
} as any) // eslint-disable-line @typescript-eslint/no-explicit-any
: new HttpAgent({
localAddress: opts.localAddress,
maxSockets: opts.maxSockets ?? DEFAULT_MAX_SOCKETS,
timeout: agentTimeout,
} as any) // eslint-disable-line @typescript-eslint/no-explicit-any
localAddress: opts.localAddress,
maxSockets: opts.maxSockets ?? DEFAULT_MAX_SOCKETS,
timeout: agentTimeout,
} as any) // eslint-disable-line @typescript-eslint/no-explicit-any
AGENT_CACHE.set(key, agent)
return agent
}

function checkNoProxy (uri: string, opts: { noProxy?: boolean | string }) {
const host = new URL(uri).hostname.split('.').filter(x => x).reverse()
const host = new URL(uri).hostname
.split('.')
.filter(x => x)
.reverse()
if (typeof opts.noProxy === 'string') {
const noproxyArr = opts.noProxy.split(/\s*,\s*/g)
return noproxyArr.some(no => {
const noParts = no.split('.').filter(x => x).reverse()
const noParts = no
.split('.')
.filter(x => x)
.reverse()
if (noParts.length === 0) {
return false
}
Expand Down
34 changes: 23 additions & 11 deletions network/proxy-agent/proxy-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ export interface ProxyAgentOptions {
noProxy?: boolean | string
strictSsl?: boolean
timeout?: number
clientCertificates?: {
[registryUrl: string]: {
cert: string
key: string
ca?: string
}
}
}

export function getProxyAgent (uri: string, opts: ProxyAgentOptions) {
Expand All @@ -32,7 +39,9 @@ export function getProxyAgent (uri: string, opts: ProxyAgentOptions) {
`https:${isHttps.toString()}`,
`proxy:${pxuri.protocol}//${pxuri.username}:${pxuri.password}@${pxuri.host}:${pxuri.port}`,
`local-address:${opts.localAddress ?? '>no-local-address<'}`,
`strict-ssl:${isHttps ? Boolean(opts.strictSsl).toString() : '>no-strict-ssl<'}`,
`strict-ssl:${
isHttps ? Boolean(opts.strictSsl).toString() : '>no-strict-ssl<'
}`,
`ca:${(isHttps && opts.ca?.toString()) || '>no-ca<'}`,
`cert:${(isHttps && opts.cert?.toString()) || '>no-cert<'}`,
`key:${(isHttps && opts.key) || '>no-key<'}`,
Expand All @@ -58,14 +67,14 @@ function getProxyUri (

let proxy: string | undefined
switch (protocol) {
case 'http:': {
proxy = opts.httpProxy
break
}
case 'https:': {
proxy = opts.httpsProxy
break
}
case 'http:': {
proxy = opts.httpProxy
break
}
case 'https:': {
proxy = opts.httpsProxy
break
}
}

if (!proxy) {
Expand Down Expand Up @@ -114,7 +123,10 @@ function getProxy (
port: proxyUrl.port,
protocol: proxyUrl.protocol,
rejectUnauthorized: opts.strictSsl,
timeout: typeof opts.timeout !== 'number' || opts.timeout === 0 ? 0 : opts.timeout + 1,
timeout:
typeof opts.timeout !== 'number' || opts.timeout === 0
? 0
: opts.timeout + 1,
}

if (proxyUrl.protocol === 'http:' || proxyUrl.protocol === 'https:') {
Expand All @@ -130,7 +142,7 @@ function getProxy (
return undefined
}

function getAuth (user: { username?: string, password?: string }) {
function getAuth (user: { username?: string; password?: string }) {
if (!user.username) {
return undefined
}
Expand Down
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions workspace.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"http-proxy-agent": "5.0.0",
"https-proxy-agent": "5.0.1",
"lru-cache": "7.10.1",
"nerf-dart": "^1.0.0",
"node-fetch": "^2.6.7",
"proxy": "1.0.2",
"safe-execa": "0.1.1",
Expand Down
Loading