Hey James - Phil committed this for me, so I'll try to answer (and I take responsibility for all the dirty hacks in it). I've been sitting on this plugin for probably over a year now, telling myself I'll get time to clean it up and make it better before I submit it, but just never got around to it, and learned last week there was interest in it, so I decided to "let it go" as is...
> Can you move this to the docs? It would be docs/reference/plugins/url_sig.rst I'll try to get some better docs in there. Not sure if just moving that README is up to the standards... > If I'm reading the code right, the error_url key is > error_url = STATUS URL > > Is that right? For error_url in the config file, there's 2 options: 1) you want to send a 403 when the check fails. Just put "error_url=403" here 2) you want to redirect to another url when the check fails. Just put "error_url=302 <the other url>" here > This should be TSConfigDirGet(). You're probably right. I'll change that. > What's wrong with TSHttpTxnClientAddrGet()? Nothing. It's my ignorance. I'll change that too. > You can't sign URLs with query parameters? Nope. Like I said, it's not perfect, but certainly usable given the known restrictions. Cheers, JvD On Apr 18, 2014, at 2:22 PM, James Peach <jpe...@apache.org> wrote: > On Apr 18, 2014, at 12:56 PM, sor...@apache.org wrote: > >> Repository: trafficserver >> Updated Branches: >> refs/heads/master bcfd36abf -> 88f18501d >> >> >> TS-2732: Add url_sig plugin >> >> >> Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo >> Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/88f18501 >> Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/88f18501 >> Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/88f18501 >> >> Branch: refs/heads/master >> Commit: 88f18501d110bdff6a7347f307d94eb3629dad79 >> Parents: bcfd36a >> Author: Phil Sorber <sor...@apache.org> >> Authored: Fri Apr 18 13:49:12 2014 -0600 >> Committer: Phil Sorber <sor...@apache.org> >> Committed: Fri Apr 18 13:55:42 2014 -0600 >> >> ---------------------------------------------------------------------- >> CHANGES | 2 + >> NOTICE | 4 +- >> configure.ac | 1 + >> plugins/experimental/Makefile.am | 1 + >> plugins/experimental/url_sig/Makefile.am | 21 ++ >> plugins/experimental/url_sig/Makefile.tsxs | 26 ++ >> plugins/experimental/url_sig/README | 183 ++++++++++ >> plugins/experimental/url_sig/genkeys.pl | 29 ++ >> plugins/experimental/url_sig/sign.pl | 101 ++++++ >> plugins/experimental/url_sig/url_sig.c | 456 ++++++++++++++++++++++++ >> plugins/experimental/url_sig/url_sig.h | 52 +++ >> 11 files changed, 874 insertions(+), 2 deletions(-) >> ---------------------------------------------------------------------- >> >> >> http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/CHANGES >> ---------------------------------------------------------------------- >> diff --git a/CHANGES b/CHANGES >> index 49c2227..d30d399 100644 >> --- a/CHANGES >> +++ b/CHANGES >> @@ -1,6 +1,8 @@ >> -*- coding: utf-8 -*- >> Changes with Apache Traffic Server 5.0.0 >> >> + *) [TS-2732] Add url_sign (Signed URL's) plugin. >> + >> *) [TS-2650] Redirect handling enhancements in ATS >> >> *) [TS-1125] POST's with Expect: 100-continue are slowed by delayed 100 >> response >> >> http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/NOTICE >> ---------------------------------------------------------------------- >> diff --git a/NOTICE b/NOTICE >> index 00196cb..dacd4a7 100644 >> --- a/NOTICE >> +++ b/NOTICE >> @@ -54,8 +54,8 @@ Copyright (c) 2013 LinkedIn >> >> ~~~ >> >> -remap_stats plugin developed by Comcast. >> -Copyright (C) 2013 Comcast >> +remap_stats and url_sig plugins developed by Comcast. >> +Copyright (C) 2014 Comcast >> >> ~~~ >> >> >> http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/configure.ac >> ---------------------------------------------------------------------- >> diff --git a/configure.ac b/configure.ac >> index bd3ddcf..7467a2d 100644 >> --- a/configure.ac >> +++ b/configure.ac >> @@ -1946,6 +1946,7 @@ AC_CONFIG_FILES([ >> plugins/experimental/s3_auth/Makefile >> plugins/experimental/spdy/Makefile >> plugins/experimental/ts_lua/Makefile >> + plugins/experimental/url_sig/Makefile >> plugins/experimental/xdebug/Makefile >> proxy/Makefile >> proxy/api/ts/Makefile >> >> http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/Makefile.am >> ---------------------------------------------------------------------- >> diff --git a/plugins/experimental/Makefile.am >> b/plugins/experimental/Makefile.am >> index 4b811c1..b8bddce 100644 >> --- a/plugins/experimental/Makefile.am >> +++ b/plugins/experimental/Makefile.am >> @@ -34,6 +34,7 @@ SUBDIRS = \ >> s3_auth \ >> spdy \ >> ts_lua \ >> + url_sig \ >> xdebug >> >> endif >> >> http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/url_sig/Makefile.am >> ---------------------------------------------------------------------- >> diff --git a/plugins/experimental/url_sig/Makefile.am >> b/plugins/experimental/url_sig/Makefile.am >> new file mode 100644 >> index 0000000..a9011ff >> --- /dev/null >> +++ b/plugins/experimental/url_sig/Makefile.am >> @@ -0,0 +1,21 @@ >> +# Licensed to the Apache Software Foundation (ASF) under one >> +# or more contributor license agreements. See the NOTICE file >> +# distributed with this work for additional information >> +# regarding copyright ownership. The ASF licenses this file >> +# to you under the Apache License, Version 2.0 (the >> +# "License"); you may not use this file except in compliance >> +# with the License. You may obtain a copy of the License at >> +# >> +# http://www.apache.org/licenses/LICENSE-2.0 >> +# >> +# Unless required by applicable law or agreed to in writing, software >> +# distributed under the License is distributed on an "AS IS" BASIS, >> +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. >> +# See the License for the specific language governing permissions and >> +# limitations under the License. >> + >> +include $(top_srcdir)/build/plugins.mk >> + >> +pkglib_LTLIBRARIES = url_sig.la >> +url_sig_la_SOURCES = url_sig.c >> +url_sig_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS) >> >> http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/url_sig/Makefile.tsxs >> ---------------------------------------------------------------------- >> diff --git a/plugins/experimental/url_sig/Makefile.tsxs >> b/plugins/experimental/url_sig/Makefile.tsxs >> new file mode 100644 >> index 0000000..e3ee96e >> --- /dev/null >> +++ b/plugins/experimental/url_sig/Makefile.tsxs >> @@ -0,0 +1,26 @@ >> +# Licensed to the Apache Software Foundation (ASF) under one >> +# or more contributor license agreements. See the NOTICE file >> +# distributed with this work for additional information >> +# regarding copyright ownership. The ASF licenses this file >> +# to you under the Apache License, Version 2.0 (the >> +# "License"); you may not use this file except in compliance >> +# with the License. You may obtain a copy of the License at >> +# >> +# http://www.apache.org/licenses/LICENSE-2.0 >> +# >> +# Unless required by applicable law or agreed to in writing, software >> +# distributed under the License is distributed on an "AS IS" BASIS, >> +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. >> +# See the License for the specific language governing permissions and >> +# limitations under the License. >> + >> +TSXS?=tsxs >> + >> +all: url_sig.c >> + $(TSXS) -v -o url_sig.so $? >> + >> +install: >> + $(TSXS) -i -o url_sig.so >> + >> +clean: >> + rm -f *.lo *.so >> >> http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/url_sig/README >> ---------------------------------------------------------------------- >> diff --git a/plugins/experimental/url_sig/README >> b/plugins/experimental/url_sig/README >> new file mode 100644 >> index 0000000..d2a4ce0 >> --- /dev/null >> +++ b/plugins/experimental/url_sig/README > > Can you move this to the docs? It would be docs/reference/plugins/url_sig.rst > >> @@ -0,0 +1,183 @@ >> +Signed URL plugin >> + >> +This ATS plugin checks a signature query string on the URL, and rejects >> (HTTP >> +403), or redirects (HTTP 302) when this check fails. The signature is based >> on >> +a secret (key) that a signing portal and the Traffic Server cache share. The >> +algorithm for the signature can be MD5 or SHA1. When the check passes, the >> +query string is stripped, and the request is handled as if there was no >> query >> +string in the first place. >> + >> +This plugin comes with 2 example perl scripts. The sign.pl script shows how >> to >> +sign a URL using the different options, this script is just an example, in >> any >> +real usage scenario, the content owner will incorporate this functionality >> in >> +their portal. The genkeys.pl script is a quick hack to generate a config >> file >> +with random keys. Keep your keys to yourself (secret) if you are using this >> +plugin, and only share them with the content owner. >> + >> +Signed URLs do not replace DRM. >> + >> +Quick install: >> + You can build with ./configure --enable-experimental-plugins or >> + with tsxs: >> + >> + Make sure devel packages for traffic-server are installed. >> + Make sure that 'tsxs' is in your path. >> + Make sure versions of this plugin and ATS are compatible. >> + >> + make -f Makefile.tsxs >> + make -f Makefile.tsxs install >> + >> + add @plugin=url_sig.so @pparam=<config file> to the remap rule you want >> + to be signed >> + >> + >> +Edge cache debugging >> + To enable the TSDebug verbose logging, change records.config to have: >> + >> + CONFIG proxy.config.diags.debug.enabled INT 1 >> + CONFIG proxy.config.diags.debug.tags STRING url_sig >> + >> + and do a traffic_line -x; Debug output will go to traffic.out. >> + Failed transactions (signature check fails that is) will be logged >> + in to error.log. >> + >> +Signing a URL >> + At the signing portal take the full URL, without any query string, and >> + add on a query string with the following parameters: >> + >> + Client IP address >> + The client IP address that this signature is valid for. >> + C=<client IP address> >> + Expiration >> + The Expiration time (seconds since epoch) of this signature. >> + E=<expiration time in secs since unix epoch> >> + Algorithm >> + The Algorithm used to create the signature. Only 1 (HMAC_SHA1) >> + and 2 (HMAC_MD5) are supported at this time >> + A=<algorithm number> >> + Key index >> + Index of the key used. This is the index of the key in the >> + configuration file on the cache. The set of keys is a shared >> + secret between the signing portal and the edge caches. There >> + is one set of keys per reverse proxy domain (fqdn). >> + K=<key index used> >> + Parts >> + Parts to use for the signature, always excluding the scheme >> + (http://). parts0 = fqdn, parts1..x is the directory parts >> + of the path, if there are more parts to the path than letters >> + in the parts param, the last one is repeated for those. >> + Examples: >> + 1: use fqdn and all of URl path >> + 0110: use part1 and part 2 of path only >> + 01: use everything except the fqdn >> + P=<parts string (0's and 1's> >> + Signature >> + The signature over the parts + the query string up to and >> + including "S=". >> + S=<signature> >> + >> + >> +Example >> + Build, install >> + >> + make -f Makefile.tsxs >> + make -f Makefile.tsxs install >> + >> + Create the config file, using the genkeys script, or otherwise. >> + >> + $ ./genkeys.pl > >> /usr/local/trafficserver-master/etc/trafficserver/sign_test.config >> + $ cat >> /usr/local/trafficserver-master/etc/trafficserver/sign_test.config >> + key0 = YwG7iAxDo6Gaa38KJOceV4nsxiAJZ3DS >> + key1 = nLE3SZKRgaNM9hLz_HnIvrCw_GtTUJT1 >> + key2 = YicZbmr6KlxfxPTJ3p9vYhARdPQ9WJYZ >> + key3 = DTV4Tcn046eM9BzJMeYrYpm3kbqOtBs7 >> + key4 = C1r6R6MINoQd5YSH25fU66tuRhhz3fs_ >> + key5 = l4dxe6YEpYbJtyiOmX5mafhwKImC5kej >> + key6 = ekKNHXu9_oOC5eqIGJVxV0vI9FYvKVya >> + key7 = BrjibTmpTTuhMHqphkQAuCWA0Zg97WQB >> + key8 = rEtWLb1jcYoq9VG8Z8TKgX4GxBuro20J >> + key9 = mrP_6ibDBG4iYpfDB6W8yn3ZyGmdwc6M >> + key10 = tbzoTTGZXPLcvpswCQCYz1DAIZcAOGyX >> + key11 = lWsn6gUeSEW79Fk2kwKVfzhVG87EXLna >> + key12 = Riox0SmGtBWsrieLUHVWtpj18STM4MP1 >> + key13 = kBsn332B7yG3HdcV7Tw51pkvHod7_84l >> + key14 = hYI4GUoIlZRf0AyuXkT3QLvBMEoFxkma >> + key15 = EIgJKwIR0LU9CUeTUdVtjMgGmxeCIbdg >> + error_url = 403 > > If I'm reading the code right, the error_url key is > error_url = STATUS URL > > Is that right? > >> + $ >> + >> + Add the remap rule like >> + >> + map http://test-remap.domain.com http://google.com >> @plugin=url_sig.so @pparam=sign_test.config >> + >> + Restart traffic server or traffic_line -x - verify there are no errors >> in diags.log >> + >> + Try the path unsigned: >> + >> + $ curl -vs -H'Host: test-remap.domain.com' >> http://localhost:8080/ >> + * Adding handle: conn: 0x200f8a0 >> + * Adding handle: send: 0 >> + * Adding handle: recv: 0 >> + * Curl_addHandleToPipeline: length: 1 >> + * - Conn 0 (0x200f8a0) send_pipe: 1, recv_pipe: 0 >> + * About to connect() to localhost port 8080 (#0) >> + * Trying 127.0.0.1... >> + * Connected to localhost (127.0.0.1) port 8080 (#0) >> + > GET / HTTP/1.1 >> + > User-Agent: curl/7.32.0 >> + > Accept: */* >> + > Host: test-remap.domain.com >> + > >> + < HTTP/1.1 403 Forbidden >> + < Date: Tue, 15 Apr 2014 22:57:32 GMT >> + < Connection: close >> + * Server ATS/5.0.0 is not blacklisted >> + < Server: ATS/5.0.0 >> + < Cache-Control: no-store >> + < Content-Type: text/plain >> + < Content-Language: en >> + < Content-Length: 21 >> + < >> + * Closing connection 0 >> + Authorization Denied$ >> + $ >> + >> + Sign the URL and try it again. Run the script with appropriate params, >> and it will output the curl line to run: >> + >> + $ ./sign.pl --url http://test-remap.domain.com/ --useparts 1 >> --algorithm 1 --duration 60 --keyindex 3 --key >> DTV4Tcn046eM9BzJMeYrYpm3kbqOtBs7 >> + curl -s -o /dev/null -v --max-redirs 0 >> 'http://test-remap.domain.com/?E=1397603088&A=1&K=3&P=1&S=28d822f68ac7265db61a8441e0877a98fe1007cc' >> + >> + Since test-remap.domain.com doesn't actually exist, we have to change >> that line slightly: >> + >> + $ curl -s -o /dev/null -v --max-redirs 0 -H 'Host: >> test-remap.domain.com' >> 'http://localhost:8080/?E=1397603088&A=1&K=3&P=1&S=28d822f68ac7265db61a8441e0877a98fe1007cc' >> + * Adding handle: conn: 0xef0a90 >> + * Adding handle: send: 0 >> + * Adding handle: recv: 0 >> + * Curl_addHandleToPipeline: length: 1 >> + * - Conn 0 (0xef0a90) send_pipe: 1, recv_pipe: 0 >> + * About to connect() to localhost port 8080 (#0) >> + * Trying 127.0.0.1... >> + * Connected to localhost (127.0.0.1) port 8080 (#0) >> + > GET >> /?E=1397603088&A=1&K=3&P=1&S=28d822f68ac7265db61a8441e0877a98fe1007cc >> HTTP/1.1 >> + > User-Agent: curl/7.32.0 >> + > Accept: */* >> + > Host: test-remap.domain.com >> + > >> + < HTTP/1.1 200 OK >> + < Location: http://www.google.com/ >> + < Content-Type: text/html; charset=UTF-8 >> + < Date: Tue, 15 Apr 2014 23:04:36 GMT >> + < Expires: Thu, 15 May 2014 23:04:36 GMT >> + < Cache-Control: public, max-age=2592000 >> + * Server ATS/5.0.0 is not blacklisted >> + < Server: ATS/5.0.0 >> + < Content-Length: 219 >> + < X-XSS-Protection: 1; mode=block >> + < X-Frame-Options: SAMEORIGIN >> + < Alternate-Protocol: 80:quic >> + < Age: 0 >> + < Connection: keep-alive >> + < >> + { [data not shown] >> + * Connection #0 to host localhost left intact >> + $ >> >> http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/url_sig/genkeys.pl >> ---------------------------------------------------------------------- >> diff --git a/plugins/experimental/url_sig/genkeys.pl >> b/plugins/experimental/url_sig/genkeys.pl >> new file mode 100755 >> index 0000000..ae5bc07 >> --- /dev/null >> +++ b/plugins/experimental/url_sig/genkeys.pl >> @@ -0,0 +1,29 @@ >> +#!/usr/bin/perl >> + >> +# Licensed to the Apache Software Foundation (ASF) under one >> +# or more contributor license agreements. See the NOTICE file >> +# distributed with this work for additional information >> +# regarding copyright ownership. The ASF licenses this file >> +# to you under the Apache License, Version 2.0 (the >> +# "License"); you may not use this file except in compliance >> +# with the License. You may obtain a copy of the License at >> +# >> +# http://www.apache.org/licenses/LICENSE-2.0 >> +# >> +# Unless required by applicable law or agreed to in writing, software >> +# distributed under the License is distributed on an "AS IS" BASIS, >> +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. >> +# See the License for the specific language governing permissions and >> +# limitations under the License. >> + >> +my $len = 32; >> +my @chars = ( 'a' .. 'z', 'A' .. 'Z', '0' .. '9', '_' ); >> +foreach my $i ( 0 .. 15 ) { >> + my $string = ""; >> + foreach ( 1 .. $len ) { >> + $string .= $chars[ rand @chars ]; >> + } >> + print "key" . $i . " = " . $string . "\n"; >> +} >> +#print "error_url=302 http://www.domain.com/this/is/the/path/error.html\n"; >> +print "error_url = 403\n"; >> >> http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/url_sig/sign.pl >> ---------------------------------------------------------------------- >> diff --git a/plugins/experimental/url_sig/sign.pl >> b/plugins/experimental/url_sig/sign.pl >> new file mode 100755 >> index 0000000..d3fbdeb >> --- /dev/null >> +++ b/plugins/experimental/url_sig/sign.pl >> @@ -0,0 +1,101 @@ >> +#!/usr/bin/perl >> + >> +# Licensed to the Apache Software Foundation (ASF) under one >> +# or more contributor license agreements. See the NOTICE file >> +# distributed with this work for additional information >> +# regarding copyright ownership. The ASF licenses this file >> +# to you under the Apache License, Version 2.0 (the >> +# "License"); you may not use this file except in compliance >> +# with the License. You may obtain a copy of the License at >> +# >> +# http://www.apache.org/licenses/LICENSE-2.0 >> +# >> +# Unless required by applicable law or agreed to in writing, software >> +# distributed under the License is distributed on an "AS IS" BASIS, >> +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. >> +# See the License for the specific language governing permissions and >> +# limitations under the License. >> + >> +use Digest::SHA qw(hmac_sha1 hmac_sha1_hex); >> +use Digest::HMAC_MD5 qw(hmac_md5 hmac_md5_hex); >> +use Getopt::Long; >> +use strict; >> +use warnings; >> +my $key = undef; >> +my $string = undef; >> +my $useparts = undef; >> +my $result = undef; >> +my $duration = undef; >> +my $keyindex = undef; >> +my $verbose = 0; >> +my $url = undef; >> +my $client = undef; >> +my $algorithm = 1; >> + >> +$result = GetOptions( >> + "url=s" => \$url, >> + "useparts=s" => \$useparts, >> + "duration=i" => \$duration, >> + "key=s" => \$key, >> + "client=s" => \$client, >> + "algorithm=i" => \$algorithm, >> + "keyindex=i" => \$keyindex, >> + "verbose" => \$verbose >> +); >> + >> +if ( !defined($key) || !defined($url) || !defined($duration) || >> !defined($keyindex) ) { >> + &help(); >> + exit(1); >> +} >> + >> +$url =~ s/^http:\/\///; >> +my $i = 0; >> +my $part_active = 0; >> +my $j = 0; >> +my @inactive_parts = (); >> +foreach my $part ( split( /\//, $url ) ) { >> + if ( length($useparts) > $i ) { >> + $part_active = substr( $useparts, $i++, 1 ); >> + } >> + if ($part_active) { >> + $string .= $part . "/"; >> + } >> + else { >> + $inactive_parts[$j] = $part; >> + } >> + $j++; >> +} >> +chop($string); >> +if ( defined($client) ) { >> + $string .= "?C=" . $client . "&E=" . ( time() + $duration ) . "&A=" . >> $algorithm . "&K=" . $keyindex . "&P=" . $useparts . "&S="; >> +} >> +else { >> + $string .= "?E=" . ( time() + $duration ) . "&A=" . $algorithm . "&K=" >> . $keyindex . "&P=" . $useparts . "&S="; >> +} >> + >> +$verbose && print "signed string = " . $string . "\n"; >> + >> +my $digest; >> +if ( $algorithm == 1 ) { >> + $digest = hmac_sha1_hex( $string, $key ); >> +} >> +else { >> + $digest = hmac_md5_hex( $string, $key ); >> +} >> +my $qstring = ( split( /\?/, $string ) )[1]; >> + >> +print "curl -s -o /dev/null -v --max-redirs 0 'http://" . $url . "?" . >> $qstring . $digest . "'\n"; >> + >> +sub help { >> + print "sign.pl - Example signing utility in perl for signed URLs\n"; >> + print "Usage: \n"; >> + print " ./sign.pl --url <value> \\ \n"; >> + print " --useparts <value> \\ \n"; >> + print " --algorithm <value> \\ \n"; >> + print " --duration <value> \\ \n"; >> + print " --keyindex <value> \\ \n"; >> + print " [--client <value>] \\ \n"; >> + print " --key <value> \\ \n"; >> + print " [--verbose] \n"; >> + print "\n"; >> +} >> >> http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/url_sig/url_sig.c >> ---------------------------------------------------------------------- >> diff --git a/plugins/experimental/url_sig/url_sig.c >> b/plugins/experimental/url_sig/url_sig.c >> new file mode 100644 >> index 0000000..0cde9d3 >> --- /dev/null >> +++ b/plugins/experimental/url_sig/url_sig.c >> @@ -0,0 +1,456 @@ >> +/** @file >> + Licensed to the Apache Software Foundation (ASF) under one >> + or more contributor license agreements. See the NOTICE file >> + distributed with this work for additional information >> + regarding copyright ownership. The ASF licenses this file >> + to you under the Apache License, Version 2.0 (the >> + "License"); you may not use this file except in compliance >> + with the License. You may obtain a copy of the License at >> + >> + http://www.apache.org/licenses/LICENSE-2.0 >> + >> + Unless required by applicable law or agreed to in writing, software >> + distributed under the License is distributed on an "AS IS" BASIS, >> + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. >> + See the License for the specific language governing permissions and >> + limitations under the License. >> + */ >> + >> +#include "url_sig.h" >> + >> +#include <stdio.h> >> +#include <stdlib.h> >> +#include <string.h> >> +#include <sys/types.h> >> +#include <time.h> >> +#include <openssl/hmac.h> >> +#include <openssl/evp.h> >> +#include <sys/socket.h> >> +#include <netinet/in.h> >> +#include <arpa/inet.h> >> +#include <limits.h> >> +#include <ctype.h> >> + >> +#include <ts/ts.h> >> +#include <ts/remap.h> >> + >> +static const char* PLUGIN_NAME = "url_sig"; >> + >> +struct config { >> + char *map_from; >> + char *map_to; >> + TSHttpStatus err_status; >> + char *err_url; >> + char keys[MAX_KEY_NUM][MAX_KEY_LEN]; >> +}; >> + >> +TSReturnCode TSRemapInit(TSRemapInterface* api_info, char *errbuf, int >> errbuf_size) { >> + if (!api_info) { >> + strncpy(errbuf, "[tsremap_init] - Invalid TSRemapInterface >> argument", (size_t) (errbuf_size - 1)); >> + return TS_ERROR; >> + } >> + >> + if (api_info->tsremap_version < TSREMAP_VERSION) { >> + snprintf(errbuf, errbuf_size - 1, "[TSRemapInit] - Incorrect >> API version %ld.%ld", api_info->tsremap_version >> 16, >> + (api_info->tsremap_version & 0xffff)); >> + return TS_ERROR; >> + } >> + >> + TSDebug(PLUGIN_NAME, "plugin is succesfully initialized"); >> + return TS_SUCCESS; >> +} >> + >> +// To force a config file reload touch remap.config and do a "traffic_line >> -x" >> +TSReturnCode TSRemapNewInstance(int argc, char* argv[], void** ih, char* >> errbuf, int errbuf_size) { >> + char config_file[PATH_MAX]; >> + struct config *cfg; >> + >> + cfg = TSmalloc(sizeof(struct config)); >> + *ih = (void *) cfg; >> + >> + int i = 0; >> + for (i = 0; i < MAX_KEY_NUM; i++) { >> + cfg->keys[i][0] = '\0'; >> + } >> + >> + if (argc != 3) { >> + snprintf(errbuf, errbuf_size - 1, "[TSRemapNewKeyInstance] - >> Argument count wrong (%d)... Need exactly two pparam= (config file name).", >> + argc); >> + return TS_ERROR; >> + } >> + TSDebug(PLUGIN_NAME, "Initializing remap function of %s -> %s with >> config from %s", argv[0], argv[1], argv[2]); >> + cfg->map_from = TSstrndup( argv[0], strlen(argv[0])); >> + cfg->map_to = TSstrndup( argv[0], strlen(argv[1])); >> + >> + const char* install_dir = TSInstallDirGet(); >> + snprintf(config_file, sizeof(config_file), "%s/%s/%s", install_dir, >> "etc/trafficserver", argv[2]); > > This should be TSConfigDirGet(). > >> + TSDebug(PLUGIN_NAME, "config file name: %s", config_file); >> + FILE *file = fopen(config_file, "r"); >> + if (file == NULL ) { >> + snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] - Error >> opening file %s.", config_file); >> + return TS_ERROR; >> + } >> + >> + char line[260]; >> + int line_no = 0; >> + int keynum; >> + while (fgets(line, sizeof(line), file) != NULL ) { >> + TSDebug(PLUGIN_NAME, "LINE: %s (%d)", line, (int) strlen(line)); >> + line_no++; >> + if (line[0] == '#' || strlen(line) <= 1) >> + continue; >> + char *pos = strchr(line, '='); >> + if (pos == NULL ) { >> + TSError("Error parsing line %d of file %s (%s).", >> line_no, config_file, line); >> + } >> + *pos = '\0'; >> + char *value = pos + 1; >> + while (isspace(*value)) // remove whitespace >> + value++; >> + pos = strchr(value, '\n'); // remove the new line, >> terminate the string >> + if (pos != NULL ) { >> + *pos = '\0'; >> + } else { >> + snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] >> - Maximum line (%d) exceeded on line %d.", MAX_KEY_LEN, line_no); >> + return TS_ERROR; >> + } >> + if (strncmp(line, "key", 3) == 0) { >> + if (value != NULL ) { >> + if (strncmp((char *) (line + 3), "0", 1) == 0) { >> + keynum = 0; >> + } else { >> + TSDebug(PLUGIN_NAME, ">>> %s <<<", line +3); >> + keynum = atoi((char *) (line + 3)); >> + if (keynum == 0) { >> + keynum = -1; // Not a Number >> + } >> + } >> + TSDebug(PLUGIN_NAME, "key number %d == %s", >> keynum, value); >> + if (keynum > MAX_KEY_NUM || keynum == -1) { >> + snprintf(errbuf, errbuf_size - 1, >> "[TSRemapNewInstance] - Key number (%d) > MAX_KEY_NUM (%d) or NaN.", keynum, >> MAX_KEY_NUM); >> + return TS_ERROR; >> + } >> + strcpy(&cfg->keys[keynum][0], value); >> + } >> + } else if (strncmp(line, "error_url", 9) == 0) { >> + if (atoi(value)) { >> + cfg->err_status = atoi(value); >> + } >> + value += 3; >> + while (isspace(*value)) >> + value++; >> +// if (strncmp(value, "http://", strlen("http://")) != 0) { >> +// snprintf(errbuf, errbuf_size - 1, >> +// "[TSRemapNewInstance] - Invalid >> config, err_status == 302, but err_url does not start with \"http://\""); >> +// return TS_ERROR; >> +// } >> + if (cfg->err_status == TS_HTTP_STATUS_MOVED_TEMPORARILY) >> + cfg->err_url = TSstrndup(value, strlen(value)); >> + else cfg->err_url = NULL; >> + } else { >> + TSError("Error parsing line %d of file %s (%s).", >> line_no, config_file, line); >> + } >> + } >> + >> + switch (cfg->err_status) { >> + case TS_HTTP_STATUS_MOVED_TEMPORARILY: >> + if (cfg->err_url == NULL ) { >> + snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] >> - Invalid config, err_status == 302, but err_url == NULL"); >> + return TS_ERROR; >> + } >> + printf("[url_sig] mapping {%s -> %s} with status %d and err url >> %s\n", argv[0], argv[1], cfg->err_status, cfg->err_url); >> + break; >> + case TS_HTTP_STATUS_FORBIDDEN: >> + if (cfg->err_url != NULL ) { >> + snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] >> - Invalid config, err_status == 403, but err_url != NULL"); >> + return TS_ERROR; >> + } >> + printf("[url_sig] mapping {%s -> %s} with status %d\n", >> argv[0], argv[1], cfg->err_status); >> + break; >> + default: >> + snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] - >> Return code %d not supported.", cfg->err_status); >> + return TS_ERROR; >> + >> + } >> + >> + i = 0; >> + for (i = 0; i < MAX_KEY_NUM; i++) { >> + if (cfg->keys[i] != NULL && strlen(cfg->keys[i]) > 0) >> + printf("[url_sig] shared secret key[%d] = %s\n", i, >> cfg->keys[i]); >> + } >> + fclose(file); >> + printf("%s version %s initialized.\n", PLUGIN_NAME, VERSION); >> + return TS_SUCCESS; >> +} >> + >> +void TSRemapDeleteInstance(void* ih) { >> + struct config *cfg; >> + cfg = (struct config *) ih; >> + >> + TSError("Cleaning up..."); >> + TSfree(cfg->map_from); >> + TSfree(cfg->map_to); >> + TSfree(cfg->err_url); >> + TSfree(cfg); >> +} >> + >> +void err_log(char *url, char *msg) { >> + if (msg && url) { >> + TSDebug(PLUGIN_NAME, "[URL=%s]: %s", url, msg); >> + TSError("[URL=%s]: %s", url, msg); // This goes to error.log >> + } else { >> + TSError("Invalid err_log request"); >> + } >> +} >> + >> +TSRemapStatus TSRemapDoRemap(void* ih, TSHttpTxn txnp, TSRemapRequestInfo >> *rri) { >> + struct config *cfg; >> + cfg = (struct config *) ih; >> + >> + int url_len = 0; >> + time_t expiration = 0; >> + int algorithm = -1; >> + int keyindex = -1; >> + int cmp_res; >> + int rval; >> + int i = 0; >> + int j = 0; >> + unsigned int sig_len = 0; >> + >> + /* all strings are locally allocated except url... about 25k per >> instance*/ >> + char *url; >> + char signed_part[8192] = { '\0' }; // this initializes the whole array >> and is needed >> + char urltokstr[8192] = { '\0' }; >> + char client_ip[CIP_STRLEN] = { '\0' }; >> + char ipstr[CIP_STRLEN] = { '\0' }; >> + unsigned char sig[MAX_SIG_SIZE + 1]; >> + char sig_string[2 * MAX_SIG_SIZE + 1]; >> + >> + /* these are just pointers into other allocations */ >> + char *signature = NULL; >> + char *parts = NULL; >> + char *part = NULL; >> + char *p = NULL, *pp = NULL; >> + char *query = NULL; >> + >> + int retval, sockfd; >> + socklen_t peer_len; >> + struct sockaddr_in peer; >> + >> + url = TSUrlStringGet(rri->requestBufp, rri->requestUrl, &url_len); >> + >> + if (url_len >= MAX_REQ_LEN - 1) { >> + err_log(url, "URL string too long."); >> + goto deny; >> + } >> + >> + TSDebug(PLUGIN_NAME, "%s", url); >> + >> + query = strstr(url, "?"); >> + if (query == NULL ) { >> + err_log(url, "Has no query string."); >> + goto deny; >> + } >> + >> + if (strncmp(url, "http://", strlen("http://")) != 0) { >> + err_log(url, "Invalid URL scheme - only http supported."); >> + goto deny; >> + } >> + >> + /* first, parse the query string */ >> + query++; /* get rid of the ? */ >> + TSDebug(PLUGIN_NAME, "Query string is:%s", query); >> + >> + // Client IP - this one is optional >> + p = strstr(query, CIP_QSTRING"="); >> + if (p != NULL ) { >> + >> + p += strlen(CIP_QSTRING + 1); >> + pp = strstr(p, "&"); >> + if ((pp - p) > CIP_STRLEN - 1 || (pp - p) < 4) { >> + err_log(url, "IP address string too long or short."); >> + goto deny; >> + } >> + strncpy(client_ip, p + strlen(CIP_QSTRING) + 1, (pp - p - >> (strlen(CIP_QSTRING) + 1))); >> + client_ip[pp - p - (strlen(CIP_QSTRING) + 1)] = '\0'; >> + TSDebug(PLUGIN_NAME, "CIP: -%s-", client_ip); >> + retval = TSHttpTxnClientFdGet(txnp, &sockfd); >> + if (retval != TS_SUCCESS) { >> + err_log(url, "Error getting sockfd."); >> + goto deny; >> + } >> + peer_len = sizeof(peer); >> + if (getpeername(sockfd, (struct sockaddr*) &peer, &peer_len) != >> 0) { >> + perror("Can't get peer address:"); >> + } > > What's wrong with TSHttpTxnClientAddrGet()? > >> + struct sockaddr_in *s = (struct sockaddr_in *) &peer; >> + inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr); >> + TSDebug(PLUGIN_NAME, "Peer address: -%s-", ipstr); >> + if (strcmp(ipstr, client_ip) != 0) { >> + err_log(url, "Client IP doesn't match signature."); >> + goto deny; >> + } >> + } >> + // Expiration >> + p = strstr(query, EXP_QSTRING"="); >> + if (p != NULL ) { >> + p += strlen(EXP_QSTRING) + 1; >> + expiration = atoi(p); >> + if (expiration == 0 || expiration < time(NULL )) { >> + err_log(url, "Invalid expiration, or expired."); >> + goto deny; >> + } >> + TSDebug(PLUGIN_NAME, "Exp: %d", (int) expiration); >> + } else { >> + err_log(url, "Expiration query string not found."); >> + goto deny; >> + } >> + // Algorithm >> + p = strstr(query, ALG_QSTRING"="); >> + if (p != NULL ) { >> + p += strlen(ALG_QSTRING) + 1; >> + algorithm = atoi(p); >> + // The check for a valid algorithm is later. >> + TSDebug(PLUGIN_NAME, "Algorithm: %d", algorithm); >> + } else { >> + err_log(url, "Algorithm query string not found."); >> + goto deny; >> + } >> + // Key index >> + p = strstr(query, KIN_QSTRING"="); >> + if (p != NULL ) { >> + p += strlen(KIN_QSTRING) + 1; >> + keyindex = atoi(p); >> + if (keyindex == -1) { >> + err_log(url, "Invalid key index."); >> + goto deny; >> + } >> + TSDebug(PLUGIN_NAME, "Key Index: %d", keyindex); >> + } else { >> + err_log(url, "KeyIndex query string not found."); >> + goto deny; >> + } >> + // Parts >> + p = strstr(query, PAR_QSTRING"="); >> + if (p != NULL ) { >> + p += strlen(PAR_QSTRING) + 1; >> + parts = p; // NOTE parts is not NULL terminated it is >> terminated by "&" of next param >> + p = strstr(parts, "&"); >> + TSDebug(PLUGIN_NAME, "Parts: %.*s", (int) (p - parts), parts); >> + } else { >> + err_log(url, "PartsSigned query string not found."); >> + goto deny; >> + } >> + // And finally, the sig (has to be last) >> + p = strstr(query, SIG_QSTRING"="); >> + if (p != NULL ) { >> + p += strlen(SIG_QSTRING) + 1; >> + signature = p; // NOTE sig is not NULL terminated, it has to be >> 20 chars >> + if ((algorithm == USIG_HMAC_SHA1 && strlen(signature) < >> SHA1_SIG_SIZE) || (algorithm == USIG_HMAC_MD5 && strlen(signature) < >> MD5_SIG_SIZE)) { >> + err_log(url, "Signature query string too short (< >> 20)."); >> + goto deny; >> + } >> + } else { >> + err_log(url, "Signature query string not found."); >> + goto deny; >> + } >> + >> + /* have the query string, and parameters passed initial checks */ >> + TSDebug(PLUGIN_NAME, "Found all needed parameters: C=%s E=%d A=%d K=%d >> P=%s S=%s", client_ip, (int) expiration, algorithm, keyindex, parts, >> + signature); >> + >> + /* find the string that was signed - cycle through the parts letters, >> adding the part of the fqdn/path if it is 1 */ >> + p = strstr(url, "?"); >> + memcpy(urltokstr, &url[strlen("http://")], p - url - strlen("http://")); >> + part = strtok_r(urltokstr, "/", &p); >> + while (part != NULL ) { >> + if (parts[j] == '1') { >> + strcpy(signed_part + strlen(signed_part), part); >> + strcpy(signed_part + strlen(signed_part), "/"); >> + } >> + if (parts[j + 1] == '0' || parts[j + 1] == '1') // This >> remembers the last part, meaning, if there are no more valid letters in parts >> + j++; // will >> keep repeating the value of the last one >> + part = strtok_r(NULL, "/", &p); >> + } >> + >> + signed_part[strlen(signed_part) - 1] = '?'; // chop off the last /, >> replace with '?' >> + p = strstr(query, SIG_QSTRING"="); >> + strncat(signed_part, query, (p - query) + strlen(SIG_QSTRING) + 1); >> + >> + TSDebug(PLUGIN_NAME, "Signed string=\"%s\"", signed_part); >> + >> + /* calculate the expected the signature with the right algorithm */ >> + switch (algorithm) { >> + case USIG_HMAC_SHA1: >> + HMAC(EVP_sha1(), (const unsigned char *) cfg->keys[keyindex], >> strlen(cfg->keys[keyindex]), (const unsigned char *) signed_part, >> + strlen(signed_part), sig, &sig_len); >> + if (sig_len != SHA1_SIG_SIZE) { >> + TSDebug(PLUGIN_NAME, "sig_len: %d", sig_len); >> + err_log(url, "Calculated sig len != SHA1_SIG_SIZE !"); >> + goto deny; >> + } >> + >> + break; >> + case USIG_HMAC_MD5: >> + HMAC(EVP_md5(), (const unsigned char *) cfg->keys[keyindex], >> strlen(cfg->keys[keyindex]), (const unsigned char *) signed_part, >> + strlen(signed_part), sig, &sig_len); >> + if (sig_len != MD5_SIG_SIZE) { >> + TSDebug(PLUGIN_NAME, "sig_len: %d", sig_len); >> + err_log(url, "Calculated sig len != MD5_SIG_SIZE !"); >> + goto deny; >> + } >> + break; >> + default: >> + err_log(url, "Algorithm not supported."); >> + goto deny; >> + } >> + >> + for (i = 0; i < sig_len; i++) { >> + sprintf(&(sig_string[i * 2]), "%02x", sig[i]); >> + } >> + >> + TSDebug(PLUGIN_NAME, "Expected signature: %s", sig_string); >> + >> + /* and compare to signature that was sent */ >> + cmp_res = strncmp(sig_string, signature, sig_len * 2); >> + if (cmp_res != 0) { >> + err_log(url, "Signature check failed."); >> + goto deny; >> + } else { >> + TSDebug(PLUGIN_NAME, "Signature check passed."); >> + goto allow; >> + } >> + >> + /* ********* Deny ********* */ >> + deny: if (url) >> + TSfree(url); >> + switch (cfg->err_status) { >> + case TS_HTTP_STATUS_MOVED_TEMPORARILY: >> + TSDebug(PLUGIN_NAME, "Redirecting to %s", cfg->err_url); >> + char *start, *end; >> + start = cfg->err_url; >> + end = start + strlen(cfg->err_url); >> + if (TSUrlParse(rri->requestBufp, rri->requestUrl, (const char >> **) &start, end) != TS_PARSE_DONE) { >> + err_log("url", "Error inn TSUrlParse!"); >> + } >> + rri->redirect = 1; >> + break; >> + case TS_HTTP_STATUS_FORBIDDEN: >> + default: >> + /* set status and body to be 403 */ >> + TSHttpTxnSetHttpRetStatus(txnp, TS_HTTP_STATUS_FORBIDDEN); >> + TSHttpTxnErrorBodySet(txnp, TSstrdup("Authorization Denied"), >> strlen("Authorization Denied") - 1, TSstrdup("text/plain")); >> + break; >> + } >> + return TSREMAP_DID_REMAP; >> + >> + /* ********* Allow ********* */ >> + allow: if (url) >> + TSfree(url); >> + /* drop the query string so we can cache-hit */ >> + rval = TSUrlHttpQuerySet(rri->requestBufp, rri->requestUrl, NULL, 0); > > You can't sign URLs with query parameters? > >> + if (rval != TS_SUCCESS) { >> + TSError("Error stripping query string: %d.", rval); >> + } >> + return TSREMAP_NO_REMAP; >> +} >> >> http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/url_sig/url_sig.h >> ---------------------------------------------------------------------- >> diff --git a/plugins/experimental/url_sig/url_sig.h >> b/plugins/experimental/url_sig/url_sig.h >> new file mode 100644 >> index 0000000..c495b4b >> --- /dev/null >> +++ b/plugins/experimental/url_sig/url_sig.h >> @@ -0,0 +1,52 @@ >> +/** @file >> + Licensed to the Apache Software Foundation (ASF) under one >> + or more contributor license agreements. See the NOTICE file >> + distributed with this work for additional information >> + regarding copyright ownership. The ASF licenses this file >> + to you under the Apache License, Version 2.0 (the >> + "License"); you may not use this file except in compliance >> + with the License. You may obtain a copy of the License at >> + >> + http://www.apache.org/licenses/LICENSE-2.0 >> + >> + Unless required by applicable law or agreed to in writing, software >> + distributed under the License is distributed on an "AS IS" BASIS, >> + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. >> + See the License for the specific language governing permissions and >> + limitations under the License. >> + */ >> + >> +#ifndef URL_SIG_H_ >> +#define URL_SIG_H_ >> + >> +#define VERSION "1.0" >> +/* in the query string that we add to sign the url: */ >> +#define CIP_QSTRING "C" /* C=24.0.33.12 designates the client IP address */ >> +#define EXP_QSTRING "E" /* E=1356128799 means expires at (seconds since >> Unix epoch) */ >> +#define ALG_QSTRING "A" /* A=1 means hashing algorithm 1 */ >> +#define KIN_QSTRING "K" /* K=3 means use key number 3 */ >> +#define PAR_QSTRING "P" /* P=1110 means use parts 0, 1 and 2 (and no more) >> for the hashing of the url after removing the 'http://' */ >> + /* and making the parts by doing a split("/") */ >> +#define SIG_QSTRING "S" /* S=9e2828d570a4bee3c964f698b0985ee58b9f6b64 >> means 9e2828d570a4bee3c964f698b0985ee58b9f6b64 is the sig >> + This one has to be the last one of the string */ >> + >> +#define CIP_STRLEN 20 >> +#define EXP_STRLEN 16 >> +#define PAR_STRLEN 16 >> +#define MAX_PARTS 32 >> + >> +#define MAX_HTTP_REQUEST_SIZE 8192 // >> + >> +#define MAX_SIG_SIZE 20 >> +#define SHA1_SIG_SIZE 20 >> +#define MD5_SIG_SIZE 16 >> + >> +#define MAX_REQ_LEN 8192 >> +#define MAX_KEY_LEN 256 >> +#define MAX_KEY_NUM 16 >> + >> +#define USIG_HMAC_SHA1 1 >> +#define USIG_HMAC_MD5 2 >> + >> + >> +#endif /* URL_SIG_H_ */ >> >