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

Improve proxy security (#100) #134

Merged
merged 29 commits into from
Apr 27, 2024
Merged

Improve proxy security (#100) #134

merged 29 commits into from
Apr 27, 2024

Conversation

toplenboren
Copy link
Contributor

@toplenboren toplenboren commented Apr 1, 2024

Fixes #100

I added some small improvements to proxy.ts service

I've implemented:

  • Do not allow POST / PUT and pretty much everything besides GET
  • Do not allow requests to reserved ips
  • Do not allow anything besides http or https
  • Remove shady headers
  • Added a handy README note
  • Test that init real target http server and proxy and test end to end user

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)

  • Don’t rush. Check all changes in PR again.
  • Run pnpm test.
  • Think about changing documentation.
    • If you added a script to scripts/, add a comment with a description.
    • If you added a new folder, add its description to the project’s README.md.
    • If you added config, describe how we use this tool in the config’s comment.
    • If you added something to the project’s architecture, describe it in the project’s README.md.
    • Try to focus on “why?”, not “how?”.
  • If you added a new dependency, check our requirements.
  • Think about testing
    • If you added a feature, add unit tests.
    • If you added a new state to the UI, add visual tests.
    • If you fixed the bug, think about preventing bug regression in the future.
  • If you changed web client:
    • Think about moving code to core/. What code will also be useful on other platforms?
    • Run pnpm size and check the difference in the JS bundle size. Is it relevant to the changes? Change the limit in web/.size-limit.json if necessary.
    • Think about keyboard UX. Is it easy to use the new feature with only one hand on a keyboard? Is it easy to understand what keys to press?
    • Think about HTML semantics.
    • Think about accessibility. Check a11y recommendations. Think about how screen reader users will use the tool. Is it easy to use on a screen with bad contrast?
    • Think about translations. Will
    • Think about right-to-left languages. What parts of the screen should be mirrored for Arabic or Hebrew languages?
  • If you changed the colors token in the web client:
    • Think about app loading styles inlined in index.html.
  • If you changed core/:
    • Think about making types more precise. Can you better explain data relations by type?
    • Think about conflict resolution. We don’t need some very smart changing merging; just 2 changes of the same item on different clients should not break the database. What if the user changes an item on one machine and removes it on another?
    • Think about log and storage migration.
  • If you changed English translations:
    • Change translation ID if you change the meaning of the text.

proxy/README.md Outdated Show resolved Hide resolved
proxy/README.md Outdated Show resolved Hide resolved
proxy/package.json Outdated Show resolved Hide resolved
@toplenboren toplenboren changed the title [draft] Improve proxy security (#100) Improve proxy security (#100) Apr 8, 2024
@toplenboren toplenboren requested a review from ai April 8, 2024 22:23
proxy/index.ts Show resolved Hide resolved
proxy/proxy.ts Outdated Show resolved Hide resolved
proxy/proxy.ts Outdated Show resolved Hide resolved
proxy/proxy.ts Show resolved Hide resolved
proxy/proxy.ts Outdated Show resolved Hide resolved
)
})

describe('proxy tests', async () => {
Copy link
Contributor

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.

Copy link
Contributor Author

@toplenboren toplenboren Apr 19, 2024

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?

Copy link
Contributor

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.

proxy/test/proxy.test.ts Outdated Show resolved Hide resolved
proxy/test/proxy.test.ts Outdated Show resolved Hide resolved
proxy/test/utils.ts Outdated Show resolved Hide resolved
proxy/test/utils.ts Outdated Show resolved Hide resolved
proxy/test/utils.ts Outdated Show resolved Hide resolved
proxy/test/utils.ts Outdated Show resolved Hide resolved
proxy/test/utils.ts Outdated Show resolved Hide resolved
proxy/README.md Outdated Show resolved Hide resolved
proxy/README.md Outdated Show resolved Hide resolved
targetResponse.headers.get('content-type') ?? 'text/plain'
})
sent = true
res.write(await targetResponse.text())
Copy link
Contributor

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

proxy/proxy.ts Outdated Show resolved Hide resolved
let targetResponse = await fetch(url, {
headers: {
...(req.headers as HeadersInit),
host: new URL(url).host
Copy link
Contributor

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.

Copy link
Contributor Author

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()
Copy link
Contributor

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

@toplenboren
Copy link
Contributor Author

Hey, @ai I've done some cool stuff! Can you please re-review my PR?

Done:

  • Introduced timeout feature
  • Refactored error handling: All Bad Requests will not be looged and will return 400 to user
  • Fixed minor issues, such as README.md structure or typescript errors

Questions:

  • I have a question about single describe in tests. Please check it
  • I have a question about configuration

Left to do:

  1. Debounce
  2. Hide client IP
  3. Limit size of response
  4. Introduce partial return for optimizations

@toplenboren toplenboren requested a review from ai April 20, 2024 13:29
proxy/proxy.ts Outdated
@@ -0,0 +1,126 @@
// @ts-ignore
Copy link
Contributor

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
Copy link
Contributor

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`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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 = (
Copy link
Contributor

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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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

@@ -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
Copy link
Contributor

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

@ai ai merged commit e30fc0e into hplush:main Apr 27, 2024
2 of 5 checks passed
@ai
Copy link
Contributor

ai commented Apr 27, 2024

I am merging this because I need it for other task

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improve proxy security
2 participants