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

Filtered, throttled HTTP(S)-only networking for Docker jobs #1571

Merged
merged 3 commits into from
Jan 9, 2023

Conversation

simonwo
Copy link
Contributor

@simonwo simonwo commented Dec 30, 2022

This PR adds a new “HTTP” network type that jobs can now use to specify they need HTTP networking (as opposed to fully general networking) and the domain names they require access to.

The Docker executor supports this new networking type. It enforces the HTTP-only nature of the connection by separately running an HTTP proxy (one per job shard). All traffic can only leave the executor container via the proxy, and hence only HTTP(S) between declared domains are
accessible.

The HTTP connection is also throttled to ~1 Mbit. This is so that if multiple compute nodes run a job concurrently, there is not an accidental DDoS of remote endpoints.

Now that we have networking, it is possible for networked jobs to run bacalhau themselves and trigger new jobs. Whilst this is a powerful feature, it can also lead to a “fork bomb”-style exhaustion attack as jobs could clone themselves with ever increasing concurrency. This is
particularly dangerous as it would require taking down a majority of the nodes on the network to prevent the jobs from respawning.

So we also now append a header to all outbound HTTP requests made by a job using HTTP networking that signals that the HTTP request is coming from a running job. We configure the submission endpoint to reject any job creation request that contains this header. In this way, we now prevent jobs from creating other jobs.

When we have paid jobs, this is less of an issue as the payment channel will eventually exhaust. We can later selectively add the header only for unpaid jobs, but for now it covers all jobs.

Note that the current default behaviour of compute nodes rejecting all networked jobs by default remains in place. Compute providers still need to opt-in to networking for this feature to be accessible.

@simonwo
Copy link
Contributor Author

simonwo commented Dec 30, 2022

Not ready for prime time yet because it needs an amd64 build of the gateway image and some tweaks to the tests. But they all do pass with the correct behaviour locally, so something on CI must be different.

Copy link
Collaborator

@aronchick aronchick left a comment

Choose a reason for hiding this comment

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

Some very small comments - my basic only significant thought is the flow. Unless i misunderstand, we're asking the users to pick the domain? What's the minimum happy-case flow? And then how do we customize from there?

@@ -139,14 +136,21 @@ ${BINARY_PATH}: ${CMD_FILES} ${PKG_FILES}
################################################################################
# Target: build-docker-images
################################################################################
IPFS_FUSE_IMAGE ?= "binocarlos/bacalhau-ipfs-sidecar-image"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we still need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question… @binocarlos do you know if we still use the IPFS FUSE sidecar?

.PHONY: build-ipfs-sidecar-image
build-ipfs-sidecar-image:
docker build -t $(IPFS_FUSE_IMAGE):$(IPFS_FUSE_TAG) docker/ipfs-sidecar-image

HTTP_GATEWAY_IMAGE ?= "ghcr.io/bacalhau-project/http-gateway"
Copy link
Collaborator

Choose a reason for hiding this comment

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

I have a docker repo for our project - should we move it over here? or vice versa (move the images over there?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Happy with either! Personally I lean towards GitHub just because it’s less things to administer…

@@ -212,6 +214,10 @@ func newDockerRunCmd() *cobra.Command { //nolint:funlen
NetworkFlag(&ODR.Networking), "network",
`Networking capability required by the job`,
)
dockerRunCmd.PersistentFlags().StringArrayVar(
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not super clear on this model - do we really want/need job specifiers to detail this? Shouldn't it be the node operators who light it up - but the job either works or it doesn't?

@simonwo
Copy link
Contributor Author

simonwo commented Jan 2, 2023

Unless i misunderstand, we're asking the users to pick the domain? What's the minimum happy-case flow? And then how do we customize from there?

This is great feedback, thanks! I’ll make it more clear in the documentation.

The model is: the job specifier submits a job with the domain(s) it will need to communicate with, the compute provider uses this to make some decision about the risk of the job and bids accordingly, and then at run time the traffic is limited to only the domain(s) specified.

As a command, something like:

bacalhau docker run —network=http —domain=crates.io —domain=github.com -v Qmy1234myd4t4:/code rust/compile 

The “risk” for the compute provider is that the job does something that violates its terms, the terms of its hosting provider or ISP, or even the law in it’s jurisdiction (e.g. accessing and spreading illegal content, performing cyber attacks). So the same sort of risk as operating a Tor exit node.

The risk for the job specifier is that we are operating in an environment they are paying for, so there is an incentive to hijack that environment (e.g. via a compromised package download that runs a crypto miner on install, and uses up all of the paid-for job time). Having the traffic enforced to only domains specified makes those sorts of attacks much trickier and less valuable.

The compute provider might well enforce its limits by other means, but having the domains specified up front allows it to skip bidding on jobs it knows will fail in its executor. So this is hopefully a better UX for job specifiers who can have their job picked up only by someone who will run it successfully.

Do you think needing to specify the domains makes the UX too burdensome for the job specifier? If so, we could look at ways to reduce that; some ideas we had include a) allowing jobs to run in an unfiltered mode and produce a list of the domains accessed that can be supplied to apply the limits next time, and/or b) ship with a default allowlist that the Bacalhau team/community maintains of commonly used, generally safe domains that don’t need to be specified up front and are available by default to all jobs.

We could also just move on to higher level features that use this capability like e.g. bacalhau run rust that use a pre-designed template with domains listed.

@simonwo simonwo force-pushed the http-filtering branch 4 times, most recently from b8cb234 to 9eaca5b Compare January 9, 2023 05:09
@simonwo simonwo marked this pull request as ready for review January 9, 2023 05:37
@aronchick
Copy link
Collaborator

Honestly, my only feedback is what did the default look like. I really like the entire ability to target very specific domains for set of nodes that you host so you're on the right track. It's only what happens when sets nothing or sets networking on with no other specifications.

Like, what if we have a default set of blessed domains that we make available? Not sure!

@simonwo
Copy link
Contributor Author

simonwo commented Jan 9, 2023

It's only what happens when sets nothing or sets networking on with no other specifications.

Ah right! Yes, at the moment jobs by default still use no networking and compute nodes by default still reject all jobs with networking. Specifying no domains is the same as no networking overall.

Like, what if we have a default set of blessed domains that we make available? Not sure!

Yeah, blessed domains is one way, blessed job types is another, allowing certain domains for jobs signed with a certain trusted key might be another, or even something like jobs submitted by someone who’s real world identity is attested to by some external service, etc.

Given that all of these are now possible for CPs to implement using the external probe features, maybe we should start by demonstrating/documenting some of those as CP examples? And configuring our own compute nodes to meet our own immediate use cases and see what solutions fall out of that?

@simonwo simonwo enabled auto-merge (rebase) January 9, 2023 10:21
The Docker executor cleans up the container it creates at the end of a
job, and if the executor shuts down, uses a label-based approach to
ensure everything has been caught.

This commit changes the job cleanup behaviour to also use labels. This
makes cleanup more robust and able to handle more than just a single
container per job. This is in preparation for jobs having more than a
single object (e.g. having a network and auxiliary containers set up).
This commit adds a new “HTTP” network type that jobs can now use to
specify they need HTTP networking (as opposed to fully general networking)
and the domain names they require access to.

The Docker executor supports this new networking type. It enforces the
HTTP-only nature of the connection by separately running an HTTP proxy
(one per job shard). All traffic can only leave the executor container
via the proxy, and hence only HTTP(S) between declared domains are
accessible.

The HTTP connection is also throttled to ~1 Mbit. This is so that if
multiple compute nodes run a job concurrently, there is not an
accidental DDoS of remote endpoints.

Note that the current default behaviour of compute nodes rejecting all
networked jobs by default remains in place. Compute providers still
need to opt-in to networking for this feature to be accessible.
Now that we have networking, it is possible for networked jobs to run
Bacalhau themselves and trigger new jobs. Whilst this is a powerful
feature, it can also lead to a “fork bomb”-style exhaustion attack as
jobs could clone themselves with ever increasing concurrency. This is
particularly dangerous as it would require taking down a majority of
the nodes on the network to prevent the jobs from respawning.

Now, we append a header to all outbound HTTP requests made by a job
using HTTP networking that signals that the HTTP request is coming
from a running job. We configure the submission endpoint to reject
any job creation request that contains this header. In this way, we
now prevent jobs from creating other jobs.

When we have paid jobs, this is less of an issue as the payment
channel will eventually exhaust. We can later selectively add the
header only for unpaid jobs, but for now it covers all jobs.
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.

2 participants