Go-ipfs: API Tokens

Created on 29 Jul 2015  路  15Comments  路  Source: ipfs/go-ipfs

(from discussion with @mappum and @diasdavid)

Current State and Stopgaps

In the current state, the web API is protected by:

  • API binding to 127.0.0.1.
  • CORS checks.
  • a Referrer check.

CSRF attacks

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.

Need to relax Referrer check

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

  • we should move to build the api tokens solution
  • in the meantime, #1529 will not remove the Referrer check, but will just relax it to follow the CORS 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.

The importance of NOT setting "*"

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:

  • Exposes user to CSRF attacks as mentioned above.
  • _IF_ the API is exposed (socket bound to 0.0.0.0 -- NOT by default) any host who could issue a malicious HTTP request.

The Right Solution -- API Tokens

The correct solution would allow:

  • granular permissions per-token (and thus per-application)
  • allow users (developers and end users) to _select_ permissions
  • use capabilities, not user accounts.

One (relatively) easy way to do this is with a permissions + signed capabilities.

Permissions

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).

Permissions as a Signed Capability

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).

Attention

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)

neecommunity-input topisecurity

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 GET for mutations is dangerous and wrong.

All 15 comments

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:

  1. Users don't really care about what applications pin (assuming they can control what applications can access), only how much disk space each application uses.
  2. This keeps applications from interfering with each other.

Some Notes:

  • IMO, some security critical parts of the API (upgrading, mounting, changing the config, etc) should only be exposed over a unix domain socket when possible. This splits the API but makes some attacks harder.
  • It may be convenient to allow capability delegation.
  • I'm not so sure about the proposed permissions signing design. For one, it doesn't provide a way to revoke permissions. It also assumes that IPFS multihashes are private; IIRC, they are broadcast in the wantlist.

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Kubuxu picture Kubuxu  路  3Comments

jonchoi picture jonchoi  路  3Comments

ArcticLampyrid picture ArcticLampyrid  路  3Comments

Jorropo picture Jorropo  路  3Comments

0x6431346e picture 0x6431346e  路  3Comments