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

Cloudflare Workers support #2971

Merged
merged 8 commits into from
May 15, 2023
Merged

Conversation

petebacondarwin
Copy link
Collaborator

@petebacondarwin petebacondarwin commented May 4, 2023

CI is green. Please take a look at the code. Probably easiest commit by commit

@petebacondarwin petebacondarwin force-pushed the cf-workers branch 3 times, most recently from af3721d to e1b088b Compare May 4, 2023 17:45
@petebacondarwin petebacondarwin marked this pull request as ready for review May 4, 2023 21:51
@charmander charmander changed the title Serverless support... Cloudflare Workers support May 4, 2023
packages/pg/lib/stream.js Outdated Show resolved Hide resolved
packages/pg/lib/stream.js Outdated Show resolved Hide resolved
packages/pg/lib/stream.js Outdated Show resolved Hide resolved
packages/pg/lib/stream.js Outdated Show resolved Hide resolved
packages/pg-connection-string/index.js Outdated Show resolved Hide resolved
packages/pg/lib/connection.js Outdated Show resolved Hide resolved
packages/pg/lib/crypto/crypto-shim.js Outdated Show resolved Hide resolved
@petebacondarwin petebacondarwin marked this pull request as draft May 5, 2023 10:53
@petebacondarwin
Copy link
Collaborator Author

Converting back to draft while I refactor things on the back of the reviews. Thanks @brianc @jasnell and @charmander

@sehrope
Copy link
Contributor

sehrope commented May 5, 2023

This is a lot of code for what looks to be a series of shims. Was mostly neutral on it until I saw it even adds a sha256 implementation to the repo. If you intend to support non-SASL auth we'd need an md5 implementation too. Doesn't feel right to expand the module to include all of that.

Is it possible to instead add it as an external shim to the node environment before pg gets loaded? This module can't be the only one that would need / benefit from something that normalizes the CloudFlare environment to be more node-like.

If it is going to be added, something like this should be externalized and injected at runtime similar to how we handled Promises before they were part of node core. That would involve pulling out the crypto and fs access functions and delegating them to something passed in the connection config object (defaulting to loading node crypto and fs). I don't really like expanding the module for that but at least it keeps the maintenance of the shim external. The contract for the module would be whatever set of functions we actually use.

@elithrar
Copy link

elithrar commented May 5, 2023

That would involve pulling out the crypto and fs access functions and delegating them to something passed in the connection config object (defaulting to loading node crypto and fs).

Can you expand on this? If this becomes an explicit config option, it becomes much higher friction for users & ORMs that depend on pg end up requiring changes as well, which doesn't seem scalable.

Note that this should allow node-postgres to work in other serverless environments, like Deno and Bun, once they also allow TCP socket creation. The crypto & FS shims are relevant there too.

@petebacondarwin
Copy link
Collaborator Author

@sehrope - thanks for your comments. That and the other reviews gave me enough motivation to go and do further refactoring to avoid the Node.js API shims altogether. Now the code is mostly using the more modern WebCrypto APIs.

@petebacondarwin petebacondarwin marked this pull request as ready for review May 5, 2023 15:59
@sehrope
Copy link
Contributor

sehrope commented May 6, 2023

Can you expand on this? If this becomes an explicit config option, it becomes much higher friction for users & ORMs that depend on pg end up requiring changes as well, which doesn't seem scalable.

Of course it's higher friction if you're going to compare it to zero friction. But that's what you get for running on something that purports to be node but doesn't provide the entire userspace.

The Cloudflare worker docs already instruct the end user they need to explicitly enable a flag to turn on nodejs compatibility for things like Buffer:

https://developers.cloudflare.com/workers/runtime-apis/nodejs/buffer/

To use Node.js APIs in your Worker, add the nodejs_compat compatibility flag to your wrangler.toml file.

Ideally any special handling to make the environment node-like should be happening on Cloudflare's end.

If need be, we can expand the configurability of this module to facilitate that. But whatever leverages those options to provide support for a specific provider should be owned by that provider.

Note that this should allow node-postgres to work in other serverless environments, like Deno and Bun, once they also allow TCP socket creation. The crypto & FS shims are relevant there too.

If there's an actual standard or API then sure, but rolling our own sha256 or adding a service providers specific socket implementation (e.g. class CFSocket) is not sustainable. We're not going to support N such implementations and be worried about subtlety breaking one of them.

Again, I'm not against adding support for this use case, I just think it should be done in a way that minimizes code ownership for this project and allows multiple implementations.

The PR for what I'm describing would not have any provider specific code. It'd be a series of commits to pull out internal references to things like crypto operations (for md5 and sha256), reading from the filesystem / pgpass, and creating the socket streams. The defaults would be using the nodejs built-ins for crypto, fs, and net so there would be no net change to the module. Anybody that wants to override it for a non-nodejs platform would shim those fields, either globally or in the config object passed to pg.

A good analogy is the addition of dynamic passwords to this module. IIRC, the original request was someone wanting to authenticate with AWS IAM auth against an RDS database. Rather than adding in the AWS SDK and baking in a provider specific option for signing auth requests, we have a generic interface that allows specifying an async function to provide a password dynamically. That's applicable to N providers and it's up to each of them to support however they work internally.

Swapping the deprecated Node.js API for the modern cross
environment API.
@petebacondarwin petebacondarwin force-pushed the cf-workers branch 2 times, most recently from 8249ba2 to cc35b46 Compare May 10, 2023 19:31
@petebacondarwin
Copy link
Collaborator Author

@sehrope and @charmander - I have moved the CloudflareSocket class to its own package as requested.
Now the only changes in the main packages are to clean up the use of Node.js, such as moving to more modern WebCrypto API, and not requiring in packages untill they are needed.

@petebacondarwin
Copy link
Collaborator Author

Ideally we would have pg-cloudflare as a simple dependency of pg, so that there is nothing more for the user to do other than add the library to their Worker project.

@petebacondarwin
Copy link
Collaborator Author

Oh no 😭 WebCrypto.subtle only became available in node 15.

The only place we are stuck with node's original crypto API
is for generating md5 hashes, which are not supported by WebCrypto.
@petebacondarwin
Copy link
Collaborator Author

All green @brianc - please take a look when you have some time.

@brianc
Copy link
Owner

brianc commented May 12, 2023

This is a huge PR - thank you so much for putting in the time and effort here from everyone involved. 😄 For a bit of background I've been meeting with cloudflare about this both in person here in Austin, TX and on video calls off and on for a month discussing this & so on. Really cool to see how little actual code change was involved. I hear the concern about making things more generic and accepting shims being injected in...there is still a bunch of churn on some of these web crypto and stream modules...hopefully they settle down & get "standardized" soon and we can look at making some of these injectables more dynamic. I'm cool w/ having them more bespoke when there are only node & CF workers being supported & can work towards more dynamic injectable things in the future if/when a 3rd supported platform wants to add their stuff.

@petebacondarwin
Copy link
Collaborator Author

Thanks @brianc - are we good to merge this?

@petebacondarwin petebacondarwin merged commit f206293 into brianc:master May 15, 2023
@petebacondarwin petebacondarwin deleted the cf-workers branch May 15, 2023 06:29
Comment on lines +149 to +164

const debug = false

function dump(data: unknown) {
if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
const hex = Buffer.from(data).toString('hex')
const str = new TextDecoder().decode(data)
return `\n>>> STR: "${str.replace(/\n/g, '\\n')}"\n>>> HEX: ${hex}\n`
} else {
return data
}
}

function log(...args: unknown[]) {
debug && console.log(...args.map(dump))
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Leftovers?

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.

6 participants