The problem
You would like to restrict access to a Venti archival storage server so that only callers from a specific list of IP addresses are allowed.
(Why would you want to do that? "Authenticating" callers by IP address is very dubious, but (as of mid-2012) Venti implementations don't include authentication, access control, or encryption--which isn't completely crazy, since callers need to know SHA-1 hashes of data in order to retrieve anything. Restricting by IP address doesn't provide any real confidentiality, but it does make it pretty tough for random network vandals to call you up and ask you to store/serve somebody else's copyrighted content, since they would need to spoof a quite-a-few-packets TCP connection.)
A semi-constraint
I was looking to tread lightly, e.g., not:
- Add extensions to the Venti protocol
- Define a new config-file format and modify Venti
- If possible, define a config-file format and write yet another data-shuffling wrapper program
A near miss
It turns out that a utility called
trampoline
already exists to shuffle data back and forth between
two sockets.
What's more, it even has an option to see if callers
are permitted according to the
Plan 9
network database.
However, the admission-control code in trampoline
restricts access according to MAC address,
which doesn't meet my needs.
The solution
I thought about slightly expanding
trampoline
--it really wouldn't be a lot
of code.
But I would have felt bad about submitting it to the
distribution because restricting by IP address seems
even sketchier than restricting by MAC address.
Plus the deal-breaker was that after looking around
for a while I didn't see any pre-existing syntax
for specifying a list of IP address and/or host names,
so I would have had to define my own format and sell
people on the concept, the code, and the UI.
Then laziness struck: maybe I could wrap
something temporary and throw-away around
trampoline
, such as a shell script.
One of the pleasant things about Plan 9 is that,
since "everything is a file", things
that on Unix inherently require page after
page of "sockets" code can often be done with
a small shell script on Plan 9.
The solution has three parts:
- Install your Venti and set it up to take connections only from the local machine (listen on tcp!127.1!17034)
- Add one line to your server's startup script:
aux/listen1 tcp!*!17034 /cfg/venti/restrict &
- Install the script below as (e.g.) /cfg/venti/restrict
#!/bin/rc
# Dave Eckhardt, 2012-05-29
rfork ne
fn log { msg=`{date}^' '^$0^' '^$1; echo $msg >> /sys/log/listen }
fn die { log $1 ; echo $1; exit $1 }
cd $net || die $net
ifs='!' remote=`{read remote}
~ $#remote 2 || die 'bad word count '^$#remote
ipaddr=$remote(1)
ifs='.
' bytes=`{echo $ipaddr}
~ $#bytes 4 || die 'bad byte count '^$#bytes
~ $bytes(1) [0-9] [0-9][0-9] [0-9][0-9][0-9] || die 'invalid ipaddr '^$ipaddr
~ $bytes(2) [0-9] [0-9][0-9] [0-9][0-9][0-9] || die 'invalid ipaddr '^$ipaddr
~ $bytes(3) [0-9] [0-9][0-9] [0-9][0-9][0-9] || die 'invalid ipaddr '^$ipaddr
~ $bytes(4) [0-9] [0-9][0-9] [0-9][0-9][0-9] || die 'invalid ipaddr '^$ipaddr
switch ($ipaddr) {
case 128.2.xxx.yy 128.2.zzz.ww
log 'taking call from '^$ipaddr
exec aux/trampoline tcp!127.0.0.1!17034
die 'could not start aux/trampoline'
}
log 'rejected '^$ipaddr
Enjoy!
Future Work
The "right way" to do this probably involves tlssrv, but this is harder than it sounds:
- While
tlsclient
has code to verify that the server's certificate is trusted,tlssrv
has no corresponding way to specify a list of trusted client certificates - As it turns out, this is not simple to fix,
because the
tlsServer()
library call
doesn't provide the server with a client certificate
(it could treat the
cert
field as in/out). - In turn, that's because the server side of the underlying connection-negotiation code doesn't ask the client for a certificate.
...and that's the point where I said "Gee, a shell script to do the wrong thing seems likely to take a lot less time than two-plus weeks of TLS hacking would".