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 <[email protected]> Authored: Fri Apr 18 13:49:12 2014 -0600 Committer: Phil Sorber <[email protected]> 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 @@ -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 + $ + + 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]); + 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:"); + } + 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); + 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_ */
