Allowing only PUT requests and not GET to couchdb database

I have a couchdb database (http://couchdb.apache.org/) bound to localhost:5984. I am using the following caddy directives for a proxy:

proxy /database localhost:5984
proxy /_session localhost:5984

The /_session is used for cookie-based login.

Is there a way to only allow PUT requests to localhost:5984/database ?

I would like to ensure that users can only write documents to the database using a PUT, but these users should not be able to GET documents.

Apparently this can be done with Apache (http://stackoverflow.com/questions/5857955/couchdb-write-only-database), but is there a way to use caddy to do the same thing?

Not really – I typically defer this kind of behavior to the backend application.

2 Likes

OK, thanks Matt. What I will do is maybe create an endpoint with the Flask framework, and accept only PUT requests on this endpoint. I’ll post back here when I’ve done this.

Maybe in the future caddy can limit certain requests.

Thanks for your response, and also for caddy!

1 Like

I tried creating an endpoint with Flask, but the proxy did not work well since all of the data had to be passed forward to the server. Unfortunately for this application, Flask does some pre-processing. However, it is possible to build a proxy using the Python Twisted library. This worked well.

In the Caddyfile, I set up a proxy to pass all requests to the database /db to localhost:5001. This is simply a port that I chose. All requests to /_session are sent directly to the Couchdb server listening on port 5984. In the code below, /db is the name of the database. This will have to be changed to the name of the actual database being used.

proxy /db localhost:5001 
proxy /_session localhost:5984

Here is the code used for the proxy. This code is adapted from A basic “man-in-the-middle” proxy with Twisted – Chris Laplante's blog.

I’ve adapted the code to only allow GET requests that are required for the PouchDb client-side library. Note that this code is running on the server and connects directly to localhost.

#!/usr/bin/env python

LISTEN_PORT = 5001
SERVER_PORT = 5984
SERVER_ADDR = "localhost"

from twisted.internet import protocol, reactor

"""
SOURCE: http://blog.laplante.io/2013/08/a-basic-man-in-the-middle-proxy-with-twisted/
Modified to ensure that some GET requests are not passed.  These requests are simply dropped
without any indication.
"""

# Adapted from http://stackoverflow.com/a/15645169/221061
class ServerProtocol(protocol.Protocol):
    def __init__(self):
        self.buffer = None
        self.client = None

    def connectionMade(self):
        factory = protocol.ClientFactory()
        factory.protocol = ClientProtocol
        factory.server = self
        reactor.connectTCP(SERVER_ADDR, SERVER_PORT, factory)

    # Client => Proxy
    def dataReceived(self, data):
        s = str(data, 'utf-8')
        if s.startswith('GET'):  # ensure that GET requests do not pass
            if not s.startswith('GET /db/_local/'):
                return
        # by here, the GET request is permitted
        if self.client:
            self.client.write(data)
        else:
            self.buffer = data

    # Proxy => Client
    def write(self, data):
        self.transport.write(data)


class ClientProtocol(protocol.Protocol):
    def connectionMade(self):
        self.factory.server.client = self
        self.write(self.factory.server.buffer)
        self.factory.server.buffer = ''

    # Server => Proxy
    def dataReceived(self, data):
        self.factory.server.write(data)

    # Proxy => Server
    def write(self, data):
        if data:
            self.transport.write(data)


def main():
    factory = protocol.ServerFactory()
    factory.protocol = ServerProtocol

    reactor.listenTCP(LISTEN_PORT, factory)
    reactor.run()


if __name__ == '__main__':
    main()
1 Like

It seems you could use rewrite to do this, some like (untested!)

rewrite /db {
    if {method} is "PUT"
    to /error404
}
status 404 /error404

i.e. if method is PUT on path /db, rewrite to a location that returns a 404.

1 Like

@thechriswalker: Yes, this does work! Along with the rewrite that you suggest, I had to also use:

proxy /database localhost:5984

One caveat, though, is associated with the use of the Pouchdb library. To prevent the library from reporting a failing database replication from the browser, I had to allow only some GET requests using my Python code. The Client => Proxy code was changed to the following:

# Client => Proxy
def dataReceived(self, data):
    s = str(data, 'utf-8')
    if s.startswith('GET'):  # ensure that some GET requests do not pass
        if not s.startswith('GET /smartstorm/_local/'):  # pass these requests only
            return
    if s.startswith('DELETE'):  # no delete requests should pass
        return
    if self.client:
        self.client.write(data)
    else:
        self.buffer = data

I am assuming that something similar could be done with the rewrite directive as well.

@thechriswalker: Thanks for your suggestion!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.