(from discussion with @mappum and @diasdavid)
In the current state, the web API is protected by:
127.0.0.1.For further explanations below, consider a CSRF attack where the user crafts any html page such as:
<html><body>
<img src="http://127.0.0.1:5001/api/v0/pin/rm?arg=<precious-file>" />
</body></html>
getting the user to click on the link attempt to load the "image", issuing a HTTP request to the API. Such maliciously crafted links are NOT stopped by the API binding only to 127.0.0.1, as the request would be coming from the user's browser on the same machine, and would work. They ARE stopped by the Referrer check.
Some more convoluted CSRF attacks may exist, so we need to move to "The Right Solution" below, with api tokens (caps). But until then, we must not open security holes.
In the leadup to #1529, users encountered problems developing webapps for use with the ipfs api. Users/developers requested removing the Referrer check. The Referrer check is not perfect, but it is more secure than without it. CORS is not enough to prevent the mentioned CSRF attacks.
We decided that
Access-Control-Allow-Origin header (set by the user). That way, developers can easily get the access they need
# grant API access to http://localhost:1234
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["http://localhost:1234"]'
and still remain protected.
"*"Until we follow _the right solution_ below, the API's security is not as good as it can be. Setting Access-Control-Allow-Origin: * opens a big security hole:
The correct solution would allow:
One (relatively) easy way to do this is with a permissions + signed capabilities.
First, suppose we have a simple language for expressing permissions. Bear with me, the language specifying the permisions could be very different. In particular, i'm sure there are already very good languages out there we can use. But this gets the point across.
[
"pin add [-r] *", // can add direct or recursive pins to any files
"cat /ipfs/QmQv4YQNmRPuTTHs4AgBhKEFDdN7eQYeTbSmr8JVWVfury", // cat files under given path
"swarm peers", // can see the listing of connected peers
]
The idea is to be able to scope specifically what commands the capability grants access to (e.g. can _add_ pins, but not remove them), and even give parameter constraints (e.g. can cat any file under a given root).
Take the permissions, and create a "Signed Merkledag Object" (see elsewhere for this).
# got some permissions
> cat permissions.json
{
"@type": "<capability-identifier>",
"permissions": [ "pin add [-r] *", "cat", "id" ],
}
# add it as an ipfs object/dagnode
> ipfs object add <permissions.json
QmehLm4DyLAi3SUudzC9qZRq8v2x67Afa7v7vt6w4Ps3ZL
# sign the object/dagnode (creates another object/dagnode)
> ipfs key sign QmehLm4DyLAi3SUudzC9qZRq8v2x67Afa7v7vt6w4Ps3ZL
QmQTkWSPct3bypTrqaNkY7BMMWHcETaLZGWdur1AbYgZYz
# show the signature object/dagnode
> ipfs object get QmQTkWSPct3bypTrqaNkY7BMMWHcETaLZGWdur1AbYgZYz
{
"@context": "/ipfs/<signing-context>"
"type": "/ipfs/<signing-context>#Signature"
"key": "<multihash-of-the-signing-key-(the-peer-id)>"
"object": "QmehLm4DyLAi3SUudzC9qZRq8v2x67Afa7v7vt6w4Ps3ZL",
"signature": "<signature-byes>"
}
# the hash is the capability
> my-program-that-uses-api --api-token=QmQTkWSPct3bypTrqaNkY7BMMWHcETaLZGWdur1AbYgZYz cat <foo-hash>
<foo-contents>
This is a simple expression of _what to do_. it could be done anywhere with access to the node: command line, programmatically, and even in a special webui webapp that has the capability of creating capabilities. For example, can have a page with checkboxes that select the permissions + with a big "Sign" button, that dumps out the capability to a field. (will prototype this).
if you want to help us implement the above o/ ping me, as it will be really awesome and useful for other applications/programs beyond ipfs. (to be continued)
it's so cool what falls out of these schemas :+1:
One thing though, how about revoking capabilites?
@krl yeah, i thought the same thing. we can just make these records, like ipns/providers/..., and reuse the record validity stuff from https://github.com/ipfs/specs/tree/master/records ;)
Doesn't this solution have same problem as storing private data in IPFS? Couldn't the hash accidentally be leaked?
It could be solved by additional token in the to be signed part, which would be hashed before storing. So hash of token is stored and then it could be used to confirm that it is true owner of the token.
I would like to implement it but it looks like it is blocked by IPLD.
Instead of having a permission allowing applications to pin/unpin globally, it would be nice to make pins per-application and have quotas. That is, the permission would be:
{
quota: "5MB" // or "infinite"
}
And each pin would list the applications that have requested that the object be pinned. When this list is empty, the object would be eligible for garbage collection.
Rational:
Some Notes:
hi .. I wonder if the capacity token idea and its enhancement has been implemented or started to be implemented? thanks!
There is no implementation in progress, the first thing we need is a spec on how it would work. See issues that references this issue above.
Auth tokens shouldn't be a part of IPFS. It assumes that the IPFS HTTP API is being exposed to the whole internet, rather than just being linked to another container, for which authentication isn't needed and would be pointless overhead.
Users should just put an authentication proxy in front of their HTTP API and use whatever auth they like... For just playing around with IPFS (when you don't care if files get added/removed from your cache), it might be fine to leave unauthenticated. If you're exposing it outside your local network, you should use something like coreos/jwtproxy. If you wanted to use IPFS to build a Dropbox-like storage API for arbitrary applications, then you could build a permission-oriented proxy that sits in front of the IPFS instance and restricts access to resources on a per-URL basis. However, none of this needs to be included in the IPFS core.
As a follow up question .. any suggestion on authentication for limiting the membership of the IPFS nodes?
@song007 - Do you mean current methods for restricting access to the HTTP API? You could put it behind an nginx reverse-proxy and add whatever sort of auth you want right now... Even block off specific URLs for fine-grain control over permissions.
<img src="http://127.0.0.1:5001/api/v0/pin/rm?arg=<precious-file>" />
I know this is a fairly old issue. Is this still how the API is? POST, DELETE, PUT, etc, should be used for modifying requests. Not GET. Would be another way to improve this as well.
@teran-mckinney this won't work because of CORS.
Please elaborate why it won't work because of CORS? CORS will prevent most requests without authorization. There are some holes for compatibility, GET and POST with basic MIME types. The API should be updated to avoid these types for mutation requests and everything should work fine without the referrer check. Having the API use GET for mutations is dangerous and wrong.
As of 0.5.0, we've switched to POST and will be removing the referer check.
Most helpful comment
Please elaborate why it won't work because of CORS? CORS will prevent most requests without authorization. There are some holes for compatibility, GET and POST with basic MIME types. The API should be updated to avoid these types for mutation requests and everything should work fine without the referrer check. Having the API use
GETfor mutations is dangerous and wrong.