-
Notifications
You must be signed in to change notification settings - Fork 37
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
Improve proxy security (#100) #134
Conversation
c2fa283
to
3ef4209
Compare
) | ||
}) | ||
|
||
describe('proxy tests', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don’t use single describe
per file in other tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have a single describe here? The reason is that I want to utilise before
and after
so that I can init and destroy http server.
If I ditch single describe, then I'd have to write a lot of:
await initTestHttpServer(
'proxy',
createProxyServer({ hostnameWhitelist: ['localhost'], silentMode: true })
)
await initTestHttpServer('target', targetServer)
let proxyServerUrl = ''
let targetServerUrl = ''
before(() => {
let proxy = getTestHttpServer('proxy')
if (proxy) {
proxyServerUrl = proxy.baseUrl
}
let target = getTestHttpServer('target')
if (target) {
targetServerUrl = target.baseUrl
}
if (!target || !proxy) {
throw new Error(
"Couldn't set up target server or proxy server. Please check out 'proxy.test.js'"
)
}
})
Maybe there is a way to support before
outside the single describe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use before
/after
without description
.
Just use beforeEach
or beforeAll
from node:test
.
targetResponse.headers.get('content-type') ?? 'text/plain' | ||
}) | ||
sent = true | ||
res.write(await targetResponse.text()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to also improve a performance a little?
Why do we need to first create a full copy of the HTTP document in the proxy memory and only then send it to the user? Instead, on receiving first bites we can send them to the client. It improves performance and memory consumption.
https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams
let targetResponse = await fetch(url, { | ||
headers: { | ||
...(req.headers as HeadersInit), | ||
host: new URL(url).host |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we write client’s IP to avoid using proxy to hide IP? There is X-Forwarded-For
header for that.
Later we will add privacy mechanism for paid users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Let me take some time to research usecases and write tests :D
}) | ||
sent = true | ||
res.write(await targetResponse.text()) | ||
res.end() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is another nice abuse protection system. We can limit the size of response https://github.com/gridaco/cors.sh/blob/main/services/proxy.cors.sh/src/limit/payload-limit.ts
4f86594
to
54f263b
Compare
Hey, @ai I've done some cool stuff! Can you please re-review my PR? Done:
Questions:
Left to do:
|
proxy/proxy.ts
Outdated
@@ -0,0 +1,126 @@ | |||
// @ts-ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let’s define simple types in global.d.ts
. It should be a few lines just for API which we use.
proxy/proxy.ts
Outdated
silentMode?: boolean | ||
} = {} | ||
): http.Server => { | ||
// Main toggle for production features |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The arguments are very simple to explain them in comments (in contrast with different hacks)
proxy/README.md
Outdated
## Abuse Protection | ||
|
||
- Proxy allows only GET requests | ||
- Proxy do not allow requests to reserved ip addresses like `127.0.0.1` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Proxy do not allow requests to reserved ip addresses like `127.0.0.1` | |
- Proxy do not allow requests to in-cloud IP addresses like `127.0.0.1` |
proxy/README.md
Outdated
|
||
- Proxy allows only GET requests | ||
- Proxy do not allow requests to reserved ip addresses like `127.0.0.1` | ||
- Proxy allows only http or https |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Proxy allows only http or https | |
- Proxy allows only HTTP or HTTPS protocols |
proxy/README.md
Outdated
|
||
## Test Strategy | ||
|
||
Proxy is tested using e2e testing. To write tests `initTestHttpServer` should be used |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Proxy is tested using e2e testing. To write tests `initTestHttpServer` should be used | |
To test proxy we emulate the real HTTP servers (end-to-end testing). |
Docs are for juniors.
proxy/proxy.ts
Outdated
} | ||
} | ||
|
||
export const createProxyServer = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let’s use function
if it is a function
proxy/proxy.ts
Outdated
|
||
let requestUrl = new URL(url) | ||
if (!hostnameWhitelist.includes(requestUrl.hostname)) { | ||
// Do not allow requests to addresses that are reserved: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Do not allow requests to addresses that are reserved: | |
// Do not allow requests to addresses inside our cloud: |
We should explain “why we did it” not “what we did”
proxy/proxy.ts
Outdated
} | ||
} | ||
|
||
// Remove all cookie headers and host header from request |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Remove all cookie headers and host header from request | |
// Remove all cookie headers so they will not be set on proxy domain | |
// Replace Host header to target |
proxy/test/utils.ts
Outdated
@@ -0,0 +1,68 @@ | |||
// Use this function if you need to init local http server for testing. Express is supported. Server will be closed automatically after tests finish |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don’t need docs here since the test is a good example
I am merging this because I need it for other task |
Fixes #100
I added some small improvements to
proxy.ts
serviceI've implemented:
I asked a few questions in #100. Let's use issue to discuss planned changes
Motivation
We do not want our proxy to be abused and banned, thus all the security stuff. Check #100 for details
Deps
I needed a package to check whether IP is reserved or not. Thought that we do not want to bloat our code with this stuff. I found three packages: isMartianPacket, bogon and isReservedIp
I found is-reserved-ip code to be strange and bogon to have one more dependency 👍
I decided to pick isMartianPacket.Package seems to be maintained, and I found the lack of types (Can implement this myself 🥨) and lack of adoption as main drawback (thou need to say that many people just introduce code for this task in their projects).
Checklist (Gonna be checked once we are out of draft state)
pnpm test
.scripts/
, add a comment with a description.README.md
.README.md
.core/
. What code will also be useful on other platforms?pnpm size
and check the difference in the JS bundle size. Is it relevant to the changes? Change the limit inweb/.size-limit.json
if necessary.index.html
.core/
: