Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ $ swaggerhub configure

Environment variables take precedence over the configuration file created by this command.

# Using a proxy

The CLI honors the standard proxy environment variables (lowercase variants also work):

```
export HTTPS_PROXY=http://proxy.example.com:8080 # used for https:// URLs, including the default https://api.swaggerhub.com
export HTTP_PROXY=http://proxy.example.com:8080 # used for http:// URLs, e.g. an on-premise instance served over HTTP
export NO_PROXY=localhost,.internal.example.com # hosts that should bypass the proxy
```

Proxy credentials can be included in the URL: `http://user:password@proxy.example.com:8080`. Always include the scheme (`http://` or `https://`) in the proxy URL.

HTTPS requests are tunnelled through the proxy using `CONNECT`. If your proxy intercepts TLS, see `NODE_EXTRA_CA_CERTS` below. SOCKS proxies are not supported.

# Additional configuration for SwaggerHub On-Premise

If your SwaggerHub On-Premise instance uses a **self-signed or privately signed SSL certificate**, there are additional steps required to make the SwaggerHub CLI trust this certificate.
Expand Down
66 changes: 66 additions & 0 deletions package-lock.json

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

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
"archiver": "^7.0.1",
"deep-extend": "^0.6.0",
"fs-extra": "^11.1.1",
"http-proxy-agent": "^9.1.0",
"https-proxy-agent": "^9.1.0",
"inquirer": "^8.2.5",
"js-yaml": "^5.2.2",
"json-templates": "^5.0.0",
"node-fetch": "^2.6.12",
"proxy-from-env": "^2.1.0",
"semver": "^7.6.0",
"unzipper": "^0.12.3"
},
Expand Down
3 changes: 2 additions & 1 deletion src/support/http/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const fetch = require('node-fetch')
const qs = require('querystring')
const headerTemplates = require('./header-templates')
const defaultHeaders = require('./default-headers')
const { agentFor } = require('./proxy-agent')

const { pipe, pick, omit } = require('../../utils/general')

Expand Down Expand Up @@ -34,7 +35,7 @@ const parseHeaders = options => {
}
}

const request = ({ url, ...options }) => fetch(url, options)
const request = ({ url, ...options }) => fetch(url, { agent: agentFor, ...options })

module.exports = options => (
pipe(parseQuery, parseUrl, parseHeaders, request)(options)
Expand Down
25 changes: 25 additions & 0 deletions src/support/http/proxy-agent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const { getProxyForUrl } = require('proxy-from-env')
const { HttpsProxyAgent } = require('https-proxy-agent')
const { HttpProxyAgent } = require('http-proxy-agent')

const normalizeProxy = proxy => {
try {
return new URL(proxy).href
} catch {
throw new Error('Invalid proxy URL in HTTP_PROXY/HTTPS_PROXY environment variable')
}
}

const agentFor = parsedUrl => {
const proxy = getProxyForUrl(parsedUrl.href)
if (!proxy) {
return undefined
}

const proxyUrl = normalizeProxy(proxy)
return parsedUrl.protocol === 'https:' ?
new HttpsProxyAgent(proxyUrl) :
new HttpProxyAgent(proxyUrl)
}

module.exports = { agentFor }
67 changes: 67 additions & 0 deletions test/support/proxy-agent.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const { expect } = require('@oclif/test')
const { HttpsProxyAgent } = require('https-proxy-agent')
const { HttpProxyAgent } = require('http-proxy-agent')
const { agentFor } = require('../../src/support/http/proxy-agent')

const proxyKeys = ['HTTP_PROXY', 'http_proxy', 'HTTPS_PROXY', 'https_proxy', 'NO_PROXY', 'no_proxy']

describe('proxy-agent', () => {
let saved

beforeEach(() => {
saved = {}
proxyKeys.forEach(key => {
saved[key] = process.env[key]
delete process.env[key]
})
})

afterEach(() => {
proxyKeys.forEach(key => {
if (saved[key] === undefined) {
delete process.env[key]
} else {
process.env[key] = saved[key]
}
})
})

it('should return undefined when no proxy is configured', () => {
expect(agentFor(new URL('https://api.swaggerhub.com/apis'))).to.equal(undefined)
})

it('should use HTTPS_PROXY for https URLs', () => {
process.env.HTTPS_PROXY = 'http://proxy.test:8080'
const agent = agentFor(new URL('https://api.swaggerhub.com/apis'))
expect(agent).to.be.instanceOf(HttpsProxyAgent)
expect(agent.proxy.href).to.equal('http://proxy.test:8080/')
})

it('should use HTTP_PROXY for http URLs', () => {
process.env.HTTP_PROXY = 'http://proxy.test:8080'
expect(agentFor(new URL('http://swaggerhub.test/v1/apis'))).to.be.instanceOf(HttpProxyAgent)
})

it('should not use HTTP_PROXY for https URLs', () => {
process.env.HTTP_PROXY = 'http://proxy.test:8080'
expect(agentFor(new URL('https://api.swaggerhub.com/apis'))).to.equal(undefined)
})

it('should bypass the proxy for hosts matching NO_PROXY', () => {
process.env.HTTPS_PROXY = 'http://proxy.test:8080'
process.env.NO_PROXY = 'swaggerhub.internal'
expect(agentFor(new URL('https://swaggerhub.internal/v1/apis'))).to.equal(undefined)
})

it('should preserve proxy credentials', () => {
process.env.HTTPS_PROXY = 'http://user:pass@proxy.test:8080'
const { proxy } = agentFor(new URL('https://api.swaggerhub.com/apis'))
expect(proxy.username).to.equal('user')
expect(proxy.password).to.equal('pass')
})

it('should throw a clear error for an invalid proxy URL', () => {
process.env.HTTPS_PROXY = 'http://'
expect(() => agentFor(new URL('https://api.swaggerhub.com/apis'))).to.throw(/Invalid proxy URL/)
})
})