Initial open source commit
This commit is contained in:
commit
eaa5cfef6f
|
@ -0,0 +1,6 @@
|
|||
tmp
|
||||
.rvmrc
|
||||
Gemfile.lock
|
||||
logs
|
||||
build
|
||||
t/servroot
|
|
@ -0,0 +1,18 @@
|
|||
[submodule "contrib/nginx"]
|
||||
path = contrib/nginx
|
||||
url = https://github.com/nginx/nginx.git
|
||||
[submodule "contrib/ngx_devel_kit"]
|
||||
path = contrib/ngx_devel_kit
|
||||
url = https://github.com/simpl/ngx_devel_kit.git
|
||||
[submodule "contrib/lua-nginx-module"]
|
||||
path = contrib/lua-nginx-module
|
||||
url = https://github.com/chaoslawful/lua-nginx-module.git
|
||||
[submodule "contrib/memc-nginx-module"]
|
||||
path = contrib/memc-nginx-module
|
||||
url = https://github.com/agentzh/memc-nginx-module.git
|
||||
[submodule "contrib/echo-nginx-module"]
|
||||
path = contrib/echo-nginx-module
|
||||
url = https://github.com/agentzh/echo-nginx-module.git
|
||||
[submodule "contrib/headers-more-nginx-module"]
|
||||
path = contrib/headers-more-nginx-module
|
||||
url = https://github.com/agentzh/headers-more-nginx-module.git
|
|
@ -0,0 +1,8 @@
|
|||
source 'https://rubygems.org/'
|
||||
|
||||
gem 'sinatra'
|
||||
gem 'httparty'
|
||||
gem 'god'
|
||||
gem 'haml'
|
||||
# For reloading every request to the sinatra test apps
|
||||
gem 'shotgun'
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2014 Lookout, Inc
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,149 @@
|
|||
# Debian package name & version
|
||||
MAJOR_VER=0
|
||||
MINOR_VER=1
|
||||
PATCH_VER=0
|
||||
PKG_NAME=borderpatrol
|
||||
BUILD_VER=0${BUILD_NUMBER}-dev
|
||||
|
||||
# binaries
|
||||
CC:=clang
|
||||
LUAROCKS=luarocks
|
||||
|
||||
# nginx and lua modules
|
||||
MODULE_PATH=${PWD}
|
||||
MODULE_PKG_DIR=${MODULE_PATH}/pkg
|
||||
CONTRIB_PATH=${MODULE_PATH}/contrib
|
||||
NGINX_PATH=${CONTRIB_PATH}/nginx
|
||||
NDK_PATH=${CONTRIB_PATH}/ngx_devel_kit
|
||||
MEMC_NGINX_PATH=${CONTRIB_PATH}/memc-nginx-module
|
||||
LUA_MODULE_PATH=${CONTRIB_PATH}/lua-nginx-module
|
||||
ECHO_MODULE_PATH=${CONTRIB_PATH}/echo-nginx-module
|
||||
STICKY_MODULE_PATH=${CONTRIB_PATH}/nginx-sticky-module
|
||||
HEADERS_MORE_MODULE_PATH=${CONTRIB_PATH}/headers-more-nginx-module
|
||||
|
||||
NGINX_MODULES=--add-module=${NDK_PATH} \
|
||||
--add-module=${MEMC_NGINX_PATH} \
|
||||
--add-module=${LUA_MODULE_PATH} \
|
||||
--add-module=${STICKY_MODULE_PATH} \
|
||||
--add-module=${HEADERS_MORE_MODULE_PATH} \
|
||||
--add-module=${ECHO_MODULE_PATH} # only needed for
|
||||
|
||||
# build locations
|
||||
BUILD_PATH=${MODULE_PATH}/build
|
||||
DESTDIR=${PWD}/${PKG_NAME}
|
||||
|
||||
# packaging locations
|
||||
CONF_DIR=/etc/${PKG_NAME}
|
||||
SBIN_DIR=/usr/sbin
|
||||
LOG_DIR=/var/log/${PKG_NAME}
|
||||
SHARE_DIR=/usr/share/${PKG_NAME}
|
||||
|
||||
# test locations
|
||||
TEST_DIR = ${MODULE_PATH}/t
|
||||
TEST_RUN_DIR = ${TEST_DIR}/servroot
|
||||
|
||||
UNAME:=$(shell uname -s)
|
||||
|
||||
ifeq ($(UNAME), Darwin)
|
||||
CFLAGS+="-I /usr/local/include -Wno-error"
|
||||
LD_FLAGS+="-L /usr/local/lib -L /usr/lib -liconv"
|
||||
endif
|
||||
|
||||
all: build
|
||||
|
||||
$(BUILD_PATH)/.install_rocks:
|
||||
@$(LUAROCKS) install luajson --to=$(BUILD_PATH)/usr
|
||||
@$(LUAROCKS) install luacrypto --to=$(BUILD_PATH)/usr
|
||||
@touch $(BUILD_PATH)/.install_rocks
|
||||
|
||||
build: submodules compile mkdirs $(BUILD_PATH)/.install_rocks
|
||||
@cp ${NGINX_PATH}/objs/nginx ${BUILD_PATH}${SBIN_DIR}/${PKG_NAME}
|
||||
@cp -rp ${PWD}/src/*.lua ${BUILD_PATH}${SHARE_DIR}
|
||||
@cp ${PWD}/src/robots.txt ${BUILD_PATH}${SHARE_DIR}
|
||||
@cp ${PWD}/src/config/nginx.conf.sample ${BUILD_PATH}${CONF_DIR}/sites-available/${PKG_NAME}.conf.sample
|
||||
@cp ${PWD}/src/ssl/server.crt ${BUILD_PATH}${CONF_DIR}/ssl/server.crt
|
||||
@cp ${PWD}/src/ssl/server.key ${BUILD_PATH}${CONF_DIR}/ssl/server.key
|
||||
|
||||
submodules:
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
mkdirs:
|
||||
@mkdir -p ${BUILD_PATH}${CONF_DIR}/conf.d
|
||||
@mkdir -p ${BUILD_PATH}${CONF_DIR}/sites-available
|
||||
@mkdir -p ${BUILD_PATH}${CONF_DIR}/sites-enabled
|
||||
@mkdir -p ${BUILD_PATH}${CONF_DIR}/ssl
|
||||
@mkdir -p ${BUILD_PATH}${SHARE_DIR}
|
||||
@mkdir -p ${BUILD_PATH}${CONF_DIR}
|
||||
@mkdir -p ${BUILD_PATH}${SBIN_DIR}
|
||||
|
||||
compile:
|
||||
@if [ ! -f ${NGINX_PATH}/Makefile ]; then (cd ${NGINX_PATH} && \
|
||||
./configure --prefix=/usr \
|
||||
--sbin-path=${SBIN_DIR}/borderpatrol \
|
||||
--conf-path=${CONF_DIR}/borderpatrol.conf \
|
||||
--pid-path=/var/run/borderpatrol.pid \
|
||||
--error-log-path=${LOG_DIR}/error.log \
|
||||
--http-log-path=${LOG_DIR}/access.log \
|
||||
${NGINX_MODULES} \
|
||||
--with-ld-opt=${LD_FLAGS} \
|
||||
--with-cc-opt=${CFLAGS} \
|
||||
--with-http_ssl_module); \
|
||||
fi;
|
||||
@(cd ${NGINX_PATH} && make -j2)
|
||||
|
||||
.PHONY : test
|
||||
|
||||
test: build
|
||||
@TEST_NGINX_BINARY=${PKG_NAME} PATH=${BUILD_PATH}${SBIN_DIR}:${PATH} prove -r ${TEST_DIR}/*.t
|
||||
|
||||
mocktest: build
|
||||
god -Dbc t/borderpatrol.god
|
||||
|
||||
make clean:
|
||||
rm -rf ${BUILD_PATH}
|
||||
rm -rf ${DESTDIR}
|
||||
rm -rf ngx_borderpatrol*
|
||||
rm -rf *.deb
|
||||
|
||||
distclean: clean
|
||||
(cd ${NGINX_PATH} && if [ -f Makefile ]; then make clean; fi;)
|
||||
|
||||
pkg: test
|
||||
|
||||
# copy the build target dir to the package dir
|
||||
rm -rf ${DESTDIR}
|
||||
mv ${BUILD_PATH} ${DESTDIR}
|
||||
|
||||
# Install configs under /etc/borderpatrol
|
||||
cp ${MODULE_PKG_DIR}/borderpatrol.conf ${DESTDIR}${CONF_DIR}/borderpatrol.conf
|
||||
chmod 0600 ${DESTDIR}${CONF_DIR}/borderpatrol.conf
|
||||
chmod 0600 ${DESTDIR}${CONF_DIR}/sites-available/*
|
||||
|
||||
# Install package hooks
|
||||
cp ${MODULE_PKG_DIR}/after-install.sh ${DESTDIR}
|
||||
|
||||
# Setup upstart config
|
||||
mkdir -p ${DESTDIR}/etc/init.d
|
||||
cp ${MODULE_PKG_DIR}/borderpatrol.init ${DESTDIR}/etc/init.d/borderpatrol
|
||||
chmod 755 ${DESTDIR}/etc/init.d/borderpatrol
|
||||
|
||||
# Create extra directories
|
||||
mkdir -p ${DESTDIR}/var/log/borderpatrol
|
||||
mkdir -p ${DESTDIR}/var/borderpatrol
|
||||
mkdir -p ${DESTDIR}/var/cache/borderpatrol
|
||||
|
||||
##########################################################################
|
||||
# create the borderpatrol package
|
||||
|
||||
# install fpm if needed
|
||||
test -n "$(shell gem query --local fpm|grep fpm)" || gem install fpm
|
||||
|
||||
cd ${DESTDIR} && fpm -s dir -t deb -n borderpatrol -v ${MAJOR_VER}.${MINOR_VER}.${PATCH_VER}-${BUILD_VER} -C ${DESTDIR} \
|
||||
-p borderpatrol-VERSION_ARCH.deb \
|
||||
--after-install after-install.sh \
|
||||
-d libssl1.0.0 \
|
||||
-d luarocks \
|
||||
usr/ etc/ var/
|
||||
|
||||
mv ${DESTDIR}/*.deb ${PWD}/
|
|
@ -0,0 +1,121 @@
|
|||
# BorderPatrol for Nginx
|
||||
|
||||
BorderPatrol is an nginx module to perform authentication and session management at the border of your network.
|
||||
|
||||
BorderPatrol makes the assumption that you have some set of services that require authentication and a service that
|
||||
hands out tokens to clients to access that service. You may not want those tokens to be sent across the internet, even
|
||||
over SSL, for a variety of reasons. To this end, BorderPatrol maintains a lookup table of session-id to auth token
|
||||
in memcached.
|
||||
|
||||
## Overview Diagram
|
||||
|
||||
+-------------+
|
||||
| BROWSER |
|
||||
+--+----------+
|
||||
| ^
|
||||
REQ | | RESP
|
||||
| |
|
||||
v | SVC
|
||||
+------------+----+ CALL +-------------------------------+
|
||||
| +------->| SERVICE A REQUIRING |
|
||||
| |<-------| AUTHENTICATION |
|
||||
| | +-------------------------------+
|
||||
| NGINX |
|
||||
| | +-------------------------------+
|
||||
| +------->| SERVICE B REQUIRING |
|
||||
| |<-------| AUTHENTICATION |
|
||||
+-----------------+ +-------------------------------+
|
||||
| ^ | ^
|
||||
CACHE | | | | AUTH
|
||||
LOOKUP | | | | LOOKUP
|
||||
v | v |
|
||||
+-----------+-+ +---+----------+
|
||||
| SESSION | | AUTH |
|
||||
| STORE | | SERVICE |
|
||||
+-------------+ +--------------+
|
||||
|
||||
## Use cases
|
||||
|
||||
**Assumption:** All content to be access via BorderPatrol requires authentication
|
||||
|
||||
There are three primary use cases for BorderPatrol:
|
||||
|
||||
* A client has an auth token in the session store and the request is forwarded to the downstream service -or-
|
||||
* A client does not have an auth_token for the specified service but has a master token, a call to the auth service will be made to get a service token for the downstream service -or-
|
||||
* A client does not have an auth_token, and the client is redirected to a login page which posts back to nginx, performs an auth service lookup (and returns a master token and a service token from the auth service) and, on success, creates an entry in the session store for subsequent requests.
|
||||
|
||||
### Use Case 1: Authorized Access
|
||||
|
||||
* Client requests a protected resource via BorderPatrol
|
||||
* BorderPatrol looks up the session_id from the HTTP request in the SessionStore
|
||||
* If service token present, BorderPatrol sets the Auth-Token header to the service token and allows the request to continue to the protected resource
|
||||
|
||||
### Use Case 2: Unauthorized Access
|
||||
|
||||
* Client requests a protected resource via BorderPatrol
|
||||
* BorderPatrol looks up the session_id from the HTTP request in the SessionStore
|
||||
* Record exists in cache and there is a master token but no service token for specified downstream service
|
||||
* A call is made to the Auth Service using the master token to get a service token
|
||||
* BorderPatrol updates the session_id/{master_token, service_token_1, service_token_2...} pair in the SessionStore with appropriate expiry
|
||||
* BorderPatrol redirects with the appropriate service Auth-Token header to the protected resource
|
||||
|
||||
### Use Case 3: Unauthorized Access
|
||||
|
||||
* Client requests a protected resource via BorderPatrol
|
||||
* BorderPatrol looks up the session_id from the HTTP request in the SessionStore
|
||||
* If there is a cache miss, BorderPatrol serves up a login page
|
||||
* On submittal, this posts to the AuthService (via BorderPatrol)
|
||||
* On successful authentication (which returns a master token and a service token for the downstream service), the AuthService sets the Auth-Token header
|
||||
* BorderPatrol sets the session_id/{master_token, service_token} pair in the SessionStore with appropriate expiry
|
||||
* BorderPatrol redirects with the appropriate service Auth-Token header to the protected resource
|
||||
|
||||
### Caching detail
|
||||
|
||||
The tokens cached in the session store are a string representation of a JSON structure as follows.
|
||||
|
||||
{
|
||||
"master_token" : "MMM",
|
||||
"service_tokens" : { "service_a": "AAA", "service_b": "BBB" }
|
||||
}
|
||||
|
||||
The token that has the key of 'master_token' is the Master Token, and can be used to make a call to the Auth Service to get other service tokens.
|
||||
Service Tokens have a key name that corresponds to the name of the downstream service.
|
||||
|
||||
|
||||
### Installation
|
||||
|
||||
#### Darwin
|
||||
|
||||
* get homebrew (http://mxcl.github.io/homebrew/)
|
||||
* brew install luarocks
|
||||
* brew install pcre
|
||||
* brew install lua
|
||||
* brew install luajit
|
||||
* make
|
||||
|
||||
#### Linux
|
||||
* apt-get install luarocks
|
||||
* make
|
||||
|
||||
### Running unit tests
|
||||
|
||||
You'll need the Test::Nginx CPAN module.
|
||||
|
||||
* cpan install Test::Nginx
|
||||
* make test
|
||||
|
||||
### Running full mock services locally
|
||||
|
||||
* bundle install
|
||||
* make mocktest
|
||||
* In a browser, hit https://localhost:4443/b/
|
||||
|
||||
### Additional Notes
|
||||
|
||||
make mocktest uses God to run 4 processes, on the following ports
|
||||
4443 Mock BorderPatrol
|
||||
9081 Mock Authorization service
|
||||
9082 Mock downstream service A
|
||||
9083 Mock downstream service B
|
||||
|
||||
Once you stop Mocktest, manually kill the processes above
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright (C) 2010 Jerome Loyet (jerome at loyet dot net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
|
@ -0,0 +1,129 @@
|
|||
Nginx Sticky Module
|
||||
--
|
||||
|
||||
Description:
|
||||
A nginx module to add a sticky cookie to be always forwarded the the same
|
||||
upstream server.
|
||||
|
||||
When dealing with several backend servers, it's sometimes useful that one
|
||||
client (browser) is always served by the same backend server
|
||||
(for session persistance for example).
|
||||
|
||||
Using a persistance by IP (with the ip_hash upstream module) is maybe not
|
||||
a good idea because there could be situations where a lot of different
|
||||
browsers are coming with the same IP address (behind proxies)and the load
|
||||
balancing system won't be fair.
|
||||
|
||||
Using a cookie to track the upstream server makes each browser unique.
|
||||
|
||||
When the sticky module can't apply, it switchs back to the classic Round Robin
|
||||
Upstream or returns a "Bad Gateway" (depending on the no_fallback flag).
|
||||
|
||||
Sticky module can't apply when cookies are not supported by the browser
|
||||
|
||||
* Sticky module is based on a "best effort" algorithm. Its aim is not to handle
|
||||
* security somehow. It's been made to ensure that normal users are always
|
||||
* redirected to the same backend server: that's all!
|
||||
|
||||
Installation
|
||||
|
||||
You'll need to re-compile Nginx from source to include this module.
|
||||
Modify your compile of Nginx by adding the following directive
|
||||
(modified to suit your path of course):
|
||||
|
||||
./configure ... --add-module=/absolute/path/to/nginx-sticky-module
|
||||
make
|
||||
make install
|
||||
|
||||
Usage
|
||||
upstream {
|
||||
sticky;
|
||||
server 127.0.0.1:9000;
|
||||
server 127.0.0.1:9001;
|
||||
server 127.0.0.1:9002;
|
||||
}
|
||||
|
||||
sticky [name=route] [domain=.foo.bar] [path=/] [expires=1h] [hash=index|md5|sha1] [no_fallback];
|
||||
- name: the name of the cookies used to track the persistant upstream srv
|
||||
default: route
|
||||
|
||||
- domain: the domain in which the cookie will be valid
|
||||
default: nothing. Let the browser handle this.
|
||||
|
||||
- path: the path in which the cookie will be valid
|
||||
default: nothing. Let the browser handle this.
|
||||
|
||||
- expires: the validity duration of the cookie
|
||||
default: nothing. It's a session cookie.
|
||||
restriction: must be a duration greater than one second
|
||||
|
||||
- hash: the hash mechanism to encode upstream server. It cant' be used
|
||||
with hmac.
|
||||
md5|sha1: well known hash
|
||||
index: it's not hashed, an in-memory index is used instead
|
||||
it's quicker and the overhead is shorter
|
||||
Warning: the matching against upstream servers list
|
||||
is inconsistent. So, at reload, if upstreams servers
|
||||
has changed, index values are not guaranted to
|
||||
correspond to the same server as before!
|
||||
USE IT WITH CAUTION and only if you need to!
|
||||
default: md5
|
||||
|
||||
- hmac: the HMAC hash mechanism to encode upstream server
|
||||
It's like the hash mechanism but it uses hmac_key
|
||||
to secure the hashing. It can't be used with hash.
|
||||
md5|sha1: well known hash
|
||||
default: none. see hash.
|
||||
|
||||
-hmac_key: the key to use with hmac. It's mandatory when hmac is set
|
||||
default: nothing.
|
||||
|
||||
-no_fallback: when this flag is set, nginx will return a 502 (Bad Gateway or
|
||||
Proxy Error) if a request comes with a cookie and the
|
||||
corresponding backend is unavailable.
|
||||
|
||||
Detail Mechanism
|
||||
see docs/sticky.{vsd,pdf}
|
||||
|
||||
Warnings:
|
||||
- sticky module does not work with the "backup" option of the "server" configuration item.
|
||||
- sticky module does not work with the nginx_http_upstream_check_module.
|
||||
- sticky module may require to configure nginx with SSL support.
|
||||
|
||||
Contributing
|
||||
http://code.google.com/p/nginx-sticky-module/
|
||||
|
||||
TODO
|
||||
Stress
|
||||
Code review
|
||||
|
||||
Author
|
||||
Jerome Loyet <jerome at loyet dot net>
|
||||
|
||||
Copyright & License
|
||||
This module is licenced under the BSD license.
|
||||
|
||||
Copyright (C) 2010 Jerome Loyet (jerome at loyet dot net)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
|
@ -0,0 +1,6 @@
|
|||
ngx_addon_name=ngx_http_sticky_module
|
||||
HTTP_MODULES="$HTTP_MODULES ngx_http_sticky_module"
|
||||
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_sticky_module.c $ngx_addon_dir/ngx_http_sticky_misc.c"
|
||||
NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/ngx_http_sticky_misc.h"
|
||||
USE_MD5=YES
|
||||
USE_SHA1=YES
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,315 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) 2010 Jerome Loyet (jerome at loyet dot net)
|
||||
*/
|
||||
|
||||
#include <nginx.h>
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_http.h>
|
||||
#include <ngx_md5.h>
|
||||
#include <ngx_sha1.h>
|
||||
|
||||
#include "ngx_http_sticky_misc.h"
|
||||
|
||||
#ifndef ngx_str_set
|
||||
#define ngx_str_set(str, text) (str)->len = sizeof(text) - 1; (str)->data = (u_char *) text
|
||||
#endif
|
||||
|
||||
ngx_int_t ngx_http_sticky_misc_set_cookie(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value, ngx_str_t *domain, ngx_str_t *path, time_t expires)
|
||||
{
|
||||
u_char *cookie, *p;
|
||||
size_t len;
|
||||
ngx_table_elt_t *set_cookie, *elt;
|
||||
ngx_str_t remove;
|
||||
ngx_list_part_t *part;
|
||||
ngx_uint_t i;
|
||||
|
||||
if (value == NULL) {
|
||||
ngx_str_set(&remove, "_remove_");
|
||||
value = &remove;
|
||||
}
|
||||
|
||||
/* name = value */
|
||||
len = name->len + 1 + value->len;
|
||||
|
||||
/*; Domain= */
|
||||
if (domain->len > 0) {
|
||||
len += sizeof("; Domain=") - 1 + domain->len;
|
||||
}
|
||||
|
||||
/*; Max-Age= */
|
||||
if (expires != NGX_CONF_UNSET) {
|
||||
len += sizeof("; Max-Age=") - 1 + NGX_TIME_T_LEN;
|
||||
}
|
||||
|
||||
/* ; Path= */
|
||||
if (path->len > 0) {
|
||||
len += sizeof("; Path=") - 1 + path->len;
|
||||
}
|
||||
|
||||
cookie = ngx_pnalloc(r->pool, len);
|
||||
if (cookie == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
p = ngx_copy(cookie, name->data, name->len);
|
||||
*p++ = '=';
|
||||
p = ngx_copy(p, value->data, value->len);
|
||||
|
||||
if (domain->len > 0) {
|
||||
p = ngx_copy(p, "; Domain=", sizeof("; Domain=") - 1);
|
||||
p = ngx_copy(p, domain->data, domain->len);
|
||||
}
|
||||
|
||||
if (expires != NGX_CONF_UNSET) {
|
||||
p = ngx_copy(p, "; Max-Age=", sizeof("; Max-Age=") - 1);
|
||||
p = ngx_snprintf(p, NGX_TIME_T_LEN, "%T", expires);
|
||||
}
|
||||
|
||||
if (path->len > 0) {
|
||||
p = ngx_copy(p, "; Path=", sizeof("; Path=") - 1);
|
||||
p = ngx_copy(p, path->data, path->len);
|
||||
}
|
||||
|
||||
part = &r->headers_out.headers.part;
|
||||
elt = part->elts;
|
||||
set_cookie = NULL;
|
||||
|
||||
for (i=0 ;; i++) {
|
||||
if (part->nelts > 1 || i >= part->nelts) {
|
||||
if (part->next == NULL) {
|
||||
break;
|
||||
}
|
||||
part = part->next;
|
||||
elt = part->elts;
|
||||
i = 0;
|
||||
}
|
||||
/* ... */
|
||||
if (ngx_strncmp(elt->value.data, name->data, name->len) == 0) {
|
||||
set_cookie = elt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* found a Set-Cookie header with the same name: replace it */
|
||||
if (set_cookie != NULL) {
|
||||
set_cookie->value.len = p - cookie;
|
||||
set_cookie->value.data = cookie;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
set_cookie = ngx_list_push(&r->headers_out.headers);
|
||||
if (set_cookie == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
set_cookie->hash = 1;
|
||||
ngx_str_set(&set_cookie->key, "Set-Cookie");
|
||||
set_cookie->value.len = p - cookie;
|
||||
set_cookie->value.data = cookie;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_int_t ngx_http_sticky_misc_md5(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *digest)
|
||||
{
|
||||
ngx_md5_t md5;
|
||||
u_char hash[MD5_DIGEST_LENGTH];
|
||||
|
||||
digest->data = ngx_pcalloc(pool, MD5_DIGEST_LENGTH * 2);
|
||||
if (digest->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
digest->len = MD5_DIGEST_LENGTH * 2;
|
||||
ngx_md5_init(&md5);
|
||||
ngx_md5_update(&md5, in, len);
|
||||
ngx_md5_final(hash, &md5);
|
||||
|
||||
ngx_hex_dump(digest->data, hash, MD5_DIGEST_LENGTH);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_int_t ngx_http_sticky_misc_sha1(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *digest)
|
||||
{
|
||||
ngx_sha1_t sha1;
|
||||
u_char hash[SHA_DIGEST_LENGTH];
|
||||
|
||||
digest->data = ngx_pcalloc(pool, SHA_DIGEST_LENGTH * 2);
|
||||
if (digest->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
digest->len = SHA_DIGEST_LENGTH * 2;
|
||||
ngx_sha1_init(&sha1);
|
||||
ngx_sha1_update(&sha1, in, len);
|
||||
ngx_sha1_final(hash, &sha1);
|
||||
|
||||
ngx_hex_dump(digest->data, hash, SHA_DIGEST_LENGTH);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_int_t ngx_http_sticky_misc_hmac_md5(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *key, ngx_str_t *digest)
|
||||
{
|
||||
u_char hash[MD5_DIGEST_LENGTH];
|
||||
u_char k[MD5_CBLOCK];
|
||||
ngx_md5_t md5;
|
||||
u_int i;
|
||||
|
||||
digest->data = ngx_pcalloc(pool, MD5_DIGEST_LENGTH * 2);
|
||||
if (digest->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
digest->len = MD5_DIGEST_LENGTH * 2;
|
||||
|
||||
ngx_memzero(k, sizeof(k));
|
||||
|
||||
if (key->len > MD5_CBLOCK) {
|
||||
ngx_md5_init(&md5);
|
||||
ngx_md5_update(&md5, key->data, key->len);
|
||||
ngx_md5_final(k, &md5);
|
||||
} else {
|
||||
ngx_memcpy(k, key->data, key->len);
|
||||
}
|
||||
|
||||
/* XOR ipad */
|
||||
for (i=0; i < MD5_CBLOCK; i++) {
|
||||
k[i] ^= 0x36;
|
||||
}
|
||||
|
||||
ngx_md5_init(&md5);
|
||||
ngx_md5_update(&md5, k, MD5_CBLOCK);
|
||||
ngx_md5_update(&md5, in, len);
|
||||
ngx_md5_final(hash, &md5);
|
||||
|
||||
/* Convert k to opad -- 0x6A = 0x36 ^ 0x5C */
|
||||
for (i=0; i < MD5_CBLOCK; i++) {
|
||||
k[i] ^= 0x6a;
|
||||
}
|
||||
|
||||
ngx_md5_init(&md5);
|
||||
ngx_md5_update(&md5, k, MD5_CBLOCK);
|
||||
ngx_md5_update(&md5, hash, MD5_DIGEST_LENGTH);
|
||||
ngx_md5_final(hash, &md5);
|
||||
|
||||
ngx_hex_dump(digest->data, hash, MD5_DIGEST_LENGTH);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_int_t ngx_http_sticky_misc_hmac_sha1(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *key, ngx_str_t *digest)
|
||||
{
|
||||
u_char hash[SHA_DIGEST_LENGTH];
|
||||
u_char k[SHA_CBLOCK];
|
||||
ngx_sha1_t sha1;
|
||||
u_int i;
|
||||
|
||||
digest->data = ngx_pcalloc(pool, SHA_DIGEST_LENGTH * 2);
|
||||
if (digest->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
digest->len = SHA_DIGEST_LENGTH * 2;
|
||||
|
||||
ngx_memzero(k, sizeof(k));
|
||||
|
||||
if (key->len > SHA_CBLOCK) {
|
||||
ngx_sha1_init(&sha1);
|
||||
ngx_sha1_update(&sha1, key->data, key->len);
|
||||
ngx_sha1_final(k, &sha1);
|
||||
} else {
|
||||
ngx_memcpy(k, key->data, key->len);
|
||||
}
|
||||
|
||||
/* XOR ipad */
|
||||
for (i=0; i < SHA_CBLOCK; i++) {
|
||||
k[i] ^= 0x36;
|
||||
}
|
||||
|
||||
ngx_sha1_init(&sha1);
|
||||
ngx_sha1_update(&sha1, k, SHA_CBLOCK);
|
||||
ngx_sha1_update(&sha1, in, len);
|
||||
ngx_sha1_final(hash, &sha1);
|
||||
|
||||
/* Convert k to opad -- 0x6A = 0x36 ^ 0x5C */
|
||||
for (i=0; i < SHA_CBLOCK; i++) {
|
||||
k[i] ^= 0x6a;
|
||||
}
|
||||
|
||||
ngx_sha1_init(&sha1);
|
||||
ngx_sha1_update(&sha1, k, SHA_CBLOCK);
|
||||
ngx_sha1_update(&sha1, hash, SHA_DIGEST_LENGTH);
|
||||
ngx_sha1_final(hash, &sha1);
|
||||
|
||||
ngx_hex_dump(digest->data, hash, SHA_DIGEST_LENGTH);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_int_t ngx_http_sticky_misc_text_raw(ngx_pool_t *pool, struct sockaddr *in, ngx_str_t *digest)
|
||||
{
|
||||
size_t len;
|
||||
if (!in) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
switch (in->sa_family) {
|
||||
case AF_INET:
|
||||
len = NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1;
|
||||
break;
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
case AF_INET6:
|
||||
len = NGX_INET6_ADDRSTRLEN + sizeof(":65535") - 1;
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_UNIX_DOMAIN)
|
||||
case AF_UNIX:
|
||||
len = sizeof("unix:") - 1 + NGX_UNIX_ADDRSTRLEN;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
digest->data = ngx_pnalloc(pool, len);
|
||||
if (digest->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
digest->len = ngx_sock_ntop(in, digest->data, len, 1);
|
||||
return NGX_OK;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_int_t ngx_http_sticky_misc_text_md5(ngx_pool_t *pool, struct sockaddr *in, ngx_str_t *digest)
|
||||
{
|
||||
ngx_str_t str;
|
||||
if (ngx_http_sticky_misc_text_raw(pool, in, &str) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_http_sticky_misc_md5(pool, (void *)str.data, str.len, digest) != NGX_OK) {
|
||||
ngx_pfree(pool, &str);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return ngx_pfree(pool, &str);
|
||||
}
|
||||
|
||||
ngx_int_t ngx_http_sticky_misc_text_sha1(ngx_pool_t *pool, struct sockaddr *in, ngx_str_t *digest)
|
||||
{
|
||||
ngx_str_t str;
|
||||
if (ngx_http_sticky_misc_text_raw(pool, in, &str) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_http_sticky_misc_sha1(pool, (void *)str.data, str.len, digest) != NGX_OK) {
|
||||
ngx_pfree(pool, &str);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return ngx_pfree(pool, &str);
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) 2010 Jerome Loyet (jerome at loyet dot net)
|
||||
*/
|
||||
|
||||
#ifndef _NGX_HTTP_STICKY_MISC_H_INCLUDED_
|
||||
#define _NGX_HTTP_STICKY_MISC_H_INCLUDED_
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_http.h>
|
||||
#include <ngx_string.h>
|
||||
|
||||
typedef ngx_int_t (*ngx_http_sticky_misc_hash_pt)(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *digest);
|
||||
typedef ngx_int_t (*ngx_http_sticky_misc_hmac_pt)(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *key, ngx_str_t *digest);
|
||||
typedef ngx_int_t (*ngx_http_sticky_misc_text_pt)(ngx_pool_t *pool, struct sockaddr *in, ngx_str_t *digest);
|
||||
|
||||
ngx_int_t ngx_http_sticky_misc_set_cookie (ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value, ngx_str_t *domain, ngx_str_t *path, time_t expires);
|
||||
ngx_int_t ngx_http_sticky_misc_md5(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *digest);
|
||||
ngx_int_t ngx_http_sticky_misc_sha1(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *digest);
|
||||
ngx_int_t ngx_http_sticky_misc_hmac_md5(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *key, ngx_str_t *digest);
|
||||
ngx_int_t ngx_http_sticky_misc_hmac_sha1(ngx_pool_t *pool, void *in, size_t len, ngx_str_t *key, ngx_str_t *digest);
|
||||
|
||||
ngx_int_t ngx_http_sticky_misc_text_raw(ngx_pool_t *pool, struct sockaddr *in, ngx_str_t *digest);
|
||||
ngx_int_t ngx_http_sticky_misc_text_md5(ngx_pool_t *pool, struct sockaddr *in, ngx_str_t *digest);
|
||||
ngx_int_t ngx_http_sticky_misc_text_sha1(ngx_pool_t *pool, struct sockaddr *in, ngx_str_t *digest);
|
||||
|
||||
#endif /* _NGX_HTTP_STICKY_MISC_H_INCLUDED_ */
|
|
@ -0,0 +1,691 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Jerome Loyet <jerome at loyet dot net>
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_http.h>
|
||||
|
||||
#include "ngx_http_sticky_misc.h"
|
||||
|
||||
/* define a peer */
|
||||
typedef struct {
|
||||
ngx_http_upstream_rr_peer_t *rr_peer;
|
||||
ngx_str_t digest;
|
||||
} ngx_http_sticky_peer_t;
|
||||
|
||||
/* the configuration structure */
|
||||
typedef struct {
|
||||
ngx_http_upstream_srv_conf_t uscf;
|
||||
ngx_str_t cookie_name;
|
||||
ngx_str_t cookie_domain;
|
||||
ngx_str_t cookie_path;
|
||||
time_t cookie_expires;
|
||||
ngx_str_t hmac_key;
|
||||
ngx_http_sticky_misc_hash_pt hash;
|
||||
ngx_http_sticky_misc_hmac_pt hmac;
|
||||
ngx_http_sticky_misc_text_pt text;
|
||||
ngx_uint_t no_fallback;
|
||||
ngx_http_sticky_peer_t *peers;
|
||||
} ngx_http_sticky_srv_conf_t;
|
||||
|
||||
|
||||
/* the custom sticky struct used on each request */
|
||||
typedef struct {
|
||||
/* the round robin data must be first */
|
||||
ngx_http_upstream_rr_peer_data_t rrp;
|
||||
ngx_event_get_peer_pt get_rr_peer;
|
||||
int selected_peer;
|
||||
int no_fallback;
|
||||
ngx_http_sticky_srv_conf_t *sticky_conf;
|
||||
ngx_http_request_t *request;
|
||||
} ngx_http_sticky_peer_data_t;
|
||||
|
||||
|
||||
static ngx_int_t ngx_http_init_sticky_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us);
|
||||
static ngx_int_t ngx_http_get_sticky_peer(ngx_peer_connection_t *pc, void *data);
|
||||
static char *ngx_http_sticky_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
|
||||
static void *ngx_http_sticky_create_conf(ngx_conf_t *cf);
|
||||
|
||||
|
||||
static ngx_command_t ngx_http_sticky_commands[] = {
|
||||
|
||||
{ ngx_string("sticky"),
|
||||
NGX_HTTP_UPS_CONF|NGX_CONF_ANY,
|
||||
ngx_http_sticky_set,
|
||||
0,
|
||||
0,
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_http_module_t ngx_http_sticky_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
NULL, /* postconfiguration */
|
||||
|
||||
NULL, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
|
||||
ngx_http_sticky_create_conf, /* create server configuration */
|
||||
NULL, /* merge server configuration */
|
||||
|
||||
NULL, /* create location configuration */
|
||||
NULL /* merge location configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_http_sticky_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_http_sticky_module_ctx, /* module context */
|
||||
ngx_http_sticky_commands, /* module directives */
|
||||
NGX_HTTP_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* function called by the upstream module to init itself
|
||||
* it's called once per instance
|
||||
*/
|
||||
ngx_int_t ngx_http_init_upstream_sticky(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
|
||||
{
|
||||
ngx_http_upstream_rr_peers_t *rr_peers;
|
||||
ngx_http_sticky_srv_conf_t *conf;
|
||||
ngx_uint_t i;
|
||||
|
||||
/* call the rr module on wich the sticky module is based on */
|
||||
if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/* calculate each peer digest once and save */
|
||||
rr_peers = us->peer.data;
|
||||
|
||||
/* do nothing there's only one peer */
|
||||
if (rr_peers->number <= 1 || rr_peers->single) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/* tell the upstream module to call ngx_http_init_sticky_peer when it inits peer */
|
||||
us->peer.init = ngx_http_init_sticky_peer;
|
||||
|
||||
conf = ngx_http_conf_upstream_srv_conf(us, ngx_http_sticky_module);
|
||||
|
||||
/* if 'index', no need to alloc and generate digest */
|
||||
if (!conf->hash && !conf->hmac && !conf->text) {
|
||||
conf->peers = NULL;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/* create our own upstream indexes */
|
||||
conf->peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_sticky_peer_t) * rr_peers->number);
|
||||
if (conf->peers == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/* parse each peer and generate digest if necessary */
|
||||
for (i = 0; i < rr_peers->number; i++) {
|
||||
conf->peers[i].rr_peer = &rr_peers->peer[i];
|
||||
|
||||
if (conf->hmac) {
|
||||
/* generate hmac */
|
||||
conf->hmac(cf->pool, rr_peers->peer[i].sockaddr, rr_peers->peer[i].socklen, &conf->hmac_key, &conf->peers[i].digest);
|
||||
|
||||
} else if (conf->text) {
|
||||
/* generate text */
|
||||
conf->text(cf->pool, rr_peers->peer[i].sockaddr, &conf->peers[i].digest);
|
||||
|
||||
} else {
|
||||
/* generate hash */
|
||||
conf->hash(cf->pool, rr_peers->peer[i].sockaddr, rr_peers->peer[i].socklen, &conf->peers[i].digest);
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* FIXME: is it possible to log to debug level when at configuration stage ? */
|
||||
ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "[sticky/ngx_http_init_upstream_sticky] generated digest \"%V\" for upstream at index %d", &conf->peers[i].digest, i);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* function called by the upstream module when it inits each peer
|
||||
* it's called once per request
|
||||
*/
|
||||
static ngx_int_t ngx_http_init_sticky_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us)
|
||||
{
|
||||
ngx_http_sticky_peer_data_t *iphp;
|
||||
ngx_str_t route;
|
||||
ngx_uint_t i;
|
||||
ngx_int_t n;
|
||||
|
||||
/* alloc custom sticky struct */
|
||||
iphp = ngx_palloc(r->pool, sizeof(ngx_http_sticky_peer_data_t));
|
||||
if (iphp == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/* attach it to the request upstream data */
|
||||
r->upstream->peer.data = &iphp->rrp;
|
||||
|
||||
/* call the rr module on which the sticky is based on */
|
||||
if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/* set the callback to select the next peer to use */
|
||||
r->upstream->peer.get = ngx_http_get_sticky_peer;
|
||||
|
||||
/* init the custom sticky struct */
|
||||
iphp->get_rr_peer = ngx_http_upstream_get_round_robin_peer;
|
||||
iphp->selected_peer = -1;
|
||||
iphp->no_fallback = 0;
|
||||
iphp->sticky_conf = ngx_http_conf_upstream_srv_conf(us, ngx_http_sticky_module);
|
||||
iphp->request = r;
|
||||
|
||||
/* check weather a cookie is present or not and save it */
|
||||
if (ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &iphp->sticky_conf->cookie_name, &route) != NGX_DECLINED) {
|
||||
/* a route cookie has been found. Let's give it a try */
|
||||
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] got cookie route=%V, let's try to find a matching peer", &route);
|
||||
|
||||
/* hash, hmac or text, just compare digest */
|
||||
if (iphp->sticky_conf->hash || iphp->sticky_conf->hmac || iphp->sticky_conf->text) {
|
||||
|
||||
/* check internal struct has been set */
|
||||
if (!iphp->sticky_conf->peers) {
|
||||
/* log a warning, as it will continue without the sticky */
|
||||
ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "[sticky/init_sticky_peer] internal peers struct has not been set");
|
||||
return NGX_OK; /* return OK, in order to continue */
|
||||
}
|
||||
|
||||
/* search the digest found in the cookie in the peer digest list */
|
||||
for (i = 0; i < iphp->rrp.peers->number; i++) {
|
||||
|
||||
/* ensure the both len are equal and > 0 */
|
||||
if (iphp->sticky_conf->peers[i].digest.len != route.len || route.len <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ngx_strncmp(iphp->sticky_conf->peers[i].digest.data, route.data, route.len)) {
|
||||
/* we found a match */
|
||||
iphp->selected_peer = i;
|
||||
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] the route \"%V\" matches peer at index %ui", &route, i);
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
/* switch back to index, just convert to integer and ensure it corresponds to a valid peer */
|
||||
n = ngx_atoi(route.data, route.len);
|
||||
if (n == NGX_ERROR) {
|
||||
ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "[sticky/init_sticky_peer] unable to convert the route \"%V\" to an integer value", &route);
|
||||
} else if (n >= 0 && n < (ngx_int_t)iphp->rrp.peers->number) {
|
||||
/* found one */
|
||||
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] the route \"%V\" matches peer at index %i", &route, n);
|
||||
iphp->selected_peer = n;
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* nothing was found, just continue with rr */
|
||||
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] the route \"%V\" does not match any peer. Just ignoring it ...", &route);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/* nothing found */
|
||||
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[sticky/init_sticky_peer] route cookie not found", &route);
|
||||
return NGX_OK; /* return OK, in order to continue */
|
||||
}
|
||||
|
||||
/*
|
||||
* function called by the upstream module to choose the next peer to use
|
||||
* called at least one time per request
|
||||
*/
|
||||
static ngx_int_t ngx_http_get_sticky_peer(ngx_peer_connection_t *pc, void *data)
|
||||
{
|
||||
ngx_http_sticky_peer_data_t *iphp = data;
|
||||
ngx_http_sticky_srv_conf_t *conf = iphp->sticky_conf;
|
||||
ngx_int_t selected_peer = -1;
|
||||
time_t now = ngx_time();
|
||||
uintptr_t m;
|
||||
ngx_uint_t n, i;
|
||||
ngx_http_upstream_rr_peer_t *peer = NULL;
|
||||
|
||||
ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] get sticky peer, try: %ui, n_peers: %ui, no_fallback: %ui/%ui", pc->tries, iphp->rrp.peers->number, conf->no_fallback, iphp->no_fallback);
|
||||
|
||||
/* TODO: cached */
|
||||
|
||||
/* has the sticky module already choosen a peer to connect to and is it a valid peer */
|
||||
/* is there more than one peer (otherwise, no choices to make) */
|
||||
if (iphp->selected_peer >= 0 && iphp->selected_peer < (ngx_int_t)iphp->rrp.peers->number && !iphp->rrp.peers->single) {
|
||||
ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] let's try the selected peer (%i)", iphp->selected_peer);
|
||||
|
||||
n = iphp->selected_peer / (8 * sizeof(uintptr_t));
|
||||
m = (uintptr_t) 1 << iphp->selected_peer % (8 * sizeof(uintptr_t));
|
||||
|
||||
/* has the peer not already been tried ? */
|
||||
if (!(iphp->rrp.tried[n] & m)) {
|
||||
peer = &iphp->rrp.peers->peer[iphp->selected_peer];
|
||||
|
||||
/* if the no_fallback flag is set */
|
||||
if (conf->no_fallback) {
|
||||
|
||||
iphp->no_fallback = 1;
|
||||
|
||||
/* if peer is down */
|
||||
if (peer->down) {
|
||||
ngx_log_error(NGX_LOG_NOTICE, pc->log, 0, "[sticky/get_sticky_peer] the selected peer is down and no_fallback is flagged");
|
||||
return NGX_BUSY;
|
||||
}
|
||||
|
||||
/* if it's been ignored for long enought (fail_timeout), reset timeout */
|
||||
/* do this check before testing peer->fails ! :) */
|
||||
if (now - peer->accessed > peer->fail_timeout) {
|
||||
peer->fails = 0;
|
||||
}
|
||||
|
||||
/* if peer is failed */
|
||||
if (peer->max_fails > 0 && peer->fails >= peer->max_fails) {
|
||||
ngx_log_error(NGX_LOG_NOTICE, pc->log, 0, "[sticky/get_sticky_peer] the selected peer is maked as failed and no_fallback is flagged");
|
||||
return NGX_BUSY;
|
||||
}
|
||||
}
|
||||
|
||||
/* ensure the peer is not marked as down */
|
||||
if (!peer->down) {
|
||||
|
||||
/* if it's not failedi, use it */
|
||||
if (peer->max_fails == 0 || peer->fails < peer->max_fails) {
|
||||
selected_peer = (ngx_int_t)n;
|
||||
|
||||
/* if it's been ignored for long enought (fail_timeout), reset timeout and use it */
|
||||
} else if (now - peer->accessed > peer->fail_timeout) {
|
||||
peer->fails = 0;
|
||||
selected_peer = (ngx_int_t)n;
|
||||
|
||||
/* it's failed or timeout did not expire yet */
|
||||
} else {
|
||||
/* mark the peer as tried */
|
||||
iphp->rrp.tried[n] |= m;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* we have a valid peer, tell the upstream module to use it */
|
||||
if (peer && selected_peer >= 0) {
|
||||
ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] peer found at index %i", selected_peer);
|
||||
|
||||
iphp->rrp.current = iphp->selected_peer;
|
||||
pc->cached = 0;
|
||||
pc->connection = NULL;
|
||||
pc->sockaddr = peer->sockaddr;
|
||||
pc->socklen = peer->socklen;
|
||||
pc->name = &peer->name;
|
||||
|
||||
iphp->rrp.tried[n] |= m;
|
||||
|
||||
} else {
|
||||
ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] no sticky peer selected, switch back to classic rr");
|
||||
|
||||
if (iphp->no_fallback) {
|
||||
ngx_log_error(NGX_LOG_NOTICE, pc->log, 0, "[sticky/get_sticky_peer] No fallback in action !");
|
||||
return NGX_BUSY;
|
||||
}
|
||||
|
||||
ngx_int_t ret = iphp->get_rr_peer(pc, &iphp->rrp);
|
||||
if (ret != NGX_OK) {
|
||||
ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] ngx_http_upstream_get_round_robin_peer returned %i", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* search for the choosen peer in order to set the cookie */
|
||||
for (i = 0; i < iphp->rrp.peers->number; i++) {
|
||||
|
||||
if (iphp->rrp.peers->peer[i].sockaddr == pc->sockaddr && iphp->rrp.peers->peer[i].socklen == pc->socklen) {
|
||||
if (conf->hash || conf->hmac || conf->text) {
|
||||
ngx_http_sticky_misc_set_cookie(iphp->request, &conf->cookie_name, &conf->peers[i].digest, &conf->cookie_domain, &conf->cookie_path, conf->cookie_expires);
|
||||
ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] set cookie \"%V\" value=\"%V\" index=%ui", &conf->cookie_name, &conf->peers[i].digest, i);
|
||||
} else {
|
||||
ngx_str_t route;
|
||||
ngx_uint_t tmp = i;
|
||||
route.len = 0;
|
||||
do {
|
||||
route.len++;
|
||||
} while (tmp /= 10);
|
||||
route.data = ngx_pcalloc(iphp->request->pool, sizeof(u_char) * (route.len + 1));
|
||||
if (route.data == NULL) {
|
||||
break;
|
||||
}
|
||||
ngx_snprintf(route.data, route.len, "%d", i);
|
||||
route.len = ngx_strlen(route.data);
|
||||
ngx_http_sticky_misc_set_cookie(iphp->request, &conf->cookie_name, &route, &conf->cookie_domain, &conf->cookie_path, conf->cookie_expires);
|
||||
ngx_log_debug(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[sticky/get_sticky_peer] set cookie \"%V\" value=\"%V\" index=%ui", &conf->cookie_name, &tmp, i);
|
||||
}
|
||||
break; /* found and hopefully the cookie have been set */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* reset the selection in order to bypass the sticky module when the upstream module will try another peers if necessary */
|
||||
iphp->selected_peer = -1;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Function called when the sticky command is parsed on the conf file
|
||||
*/
|
||||
static char *ngx_http_sticky_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
ngx_http_upstream_srv_conf_t *upstream_conf;
|
||||
ngx_http_sticky_srv_conf_t *sticky_conf;
|
||||
ngx_uint_t i;
|
||||
ngx_str_t tmp;
|
||||
ngx_str_t name = ngx_string("route");
|
||||
ngx_str_t domain = ngx_string("");
|
||||
ngx_str_t path = ngx_string("");
|
||||
ngx_str_t hmac_key = ngx_string("");
|
||||
time_t expires = NGX_CONF_UNSET;
|
||||
ngx_http_sticky_misc_hash_pt hash = NGX_CONF_UNSET_PTR;
|
||||
ngx_http_sticky_misc_hmac_pt hmac = NULL;
|
||||
ngx_http_sticky_misc_text_pt text = NULL;
|
||||
ngx_uint_t no_fallback = 0;
|
||||
|
||||
/* parse all elements */
|
||||
for (i = 1; i < cf->args->nelts; i++) {
|
||||
ngx_str_t *value = cf->args->elts;
|
||||
|
||||
/* is "name=" is starting the argument ? */
|
||||
if ((u_char *)ngx_strstr(value[i].data, "name=") == value[i].data) {
|
||||
|
||||
/* do we have at least on char after "name=" ? */
|
||||
if (value[i].len <= sizeof("name=") - 1) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"name=\"");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* save what's after "name=" */
|
||||
name.len = value[i].len - ngx_strlen("name=");
|
||||
name.data = (u_char *)(value[i].data + sizeof("name=") - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* is "domain=" is starting the argument ? */
|
||||
if ((u_char *)ngx_strstr(value[i].data, "domain=") == value[i].data) {
|
||||
|
||||
/* do we have at least on char after "domain=" ? */
|
||||
if (value[i].len <= ngx_strlen("domain=")) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"domain=\"");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* save what's after "domain=" */
|
||||
domain.len = value[i].len - ngx_strlen("domain=");
|
||||
domain.data = (u_char *)(value[i].data + sizeof("domain=") - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* is "path=" is starting the argument ? */
|
||||
if ((u_char *)ngx_strstr(value[i].data, "path=") == value[i].data) {
|
||||
|
||||
/* do we have at least on char after "path=" ? */
|
||||
if (value[i].len <= ngx_strlen("path=")) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"path=\"");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* save what's after "domain=" */
|
||||
path.len = value[i].len - ngx_strlen("path=");
|
||||
path.data = (u_char *)(value[i].data + sizeof("path=") - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* is "expires=" is starting the argument ? */
|
||||
if ((u_char *)ngx_strstr(value[i].data, "expires=") == value[i].data) {
|
||||
|
||||
/* do we have at least on char after "expires=" ? */
|
||||
if (value[i].len <= sizeof("expires=") - 1) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"expires=\"");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* extract value */
|
||||
tmp.len = value[i].len - ngx_strlen("expires=");
|
||||
tmp.data = (u_char *)(value[i].data + sizeof("expires=") - 1);
|
||||
|
||||
/* convert to time, save and validate */
|
||||
expires = ngx_parse_time(&tmp, 1);
|
||||
if (expires == NGX_ERROR || expires < 1) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value for \"expires=\"");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* is "text=" is starting the argument ? */
|
||||
if ((u_char *)ngx_strstr(value[i].data, "text=") == value[i].data) {
|
||||
|
||||
/* only hash or hmac can be used, not both */
|
||||
if (hmac || hash != NGX_CONF_UNSET_PTR) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "please choose between \"hash=\", \"hmac=\" and \"text\"");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* do we have at least on char after "name=" ? */
|
||||
if (value[i].len <= sizeof("text=") - 1) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"text=\"");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* extract value to temp */
|
||||
tmp.len = value[i].len - ngx_strlen("text=");
|
||||
tmp.data = (u_char *)(value[i].data + sizeof("text=") - 1);
|
||||
|
||||
/* is name=raw */
|
||||
if (ngx_strncmp(tmp.data, "raw", sizeof("raw") - 1) == 0 ) {
|
||||
text = ngx_http_sticky_misc_text_raw;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* is name=md5 */
|
||||
if (ngx_strncmp(tmp.data, "md5", sizeof("md5") - 1) == 0 ) {
|
||||
text = ngx_http_sticky_misc_text_md5;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* is name=sha1 */
|
||||
if (ngx_strncmp(tmp.data, "sha1", sizeof("sha1") - 1) == 0 ) {
|
||||
text = ngx_http_sticky_misc_text_sha1;
|
||||
continue;
|
||||
}
|
||||
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "wrong value for \"text=\": raw, md5 or sha1");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* is "hash=" is starting the argument ? */
|
||||
if ((u_char *)ngx_strstr(value[i].data, "hash=") == value[i].data) {
|
||||
|
||||
/* only hash or hmac can be used, not both */
|
||||
if (hmac || text) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "please choose between \"hash=\", \"hmac=\" and \"text=\"");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* do we have at least on char after "hash=" ? */
|
||||
if (value[i].len <= sizeof("hash=") - 1) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"hash=\"");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* extract value to temp */
|
||||
tmp.len = value[i].len - ngx_strlen("hash=");
|
||||
tmp.data = (u_char *)(value[i].data + sizeof("hash=") - 1);
|
||||
|
||||
/* is hash=index */
|
||||
if (ngx_strncmp(tmp.data, "index", sizeof("index") - 1) == 0 ) {
|
||||
hash = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* is hash=md5 */
|
||||
if (ngx_strncmp(tmp.data, "md5", sizeof("md5") - 1) == 0 ) {
|
||||
hash = ngx_http_sticky_misc_md5;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* is hash=sha1 */
|
||||
if (ngx_strncmp(tmp.data, "sha1", sizeof("sha1") - 1) == 0 ) {
|
||||
hash = ngx_http_sticky_misc_sha1;
|
||||
continue;
|
||||
}
|
||||
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "wrong value for \"hash=\": index, md5 or sha1");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* is "hmac=" is starting the argument ? */
|
||||
if ((u_char *)ngx_strstr(value[i].data, "hmac=") == value[i].data) {
|
||||
|
||||
/* only hash or hmac can be used, not both */
|
||||
if (hash != NGX_CONF_UNSET_PTR || text) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "please choose between \"hash=\", \"hmac=\" and \"text\"");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* do we have at least on char after "hmac=" ? */
|
||||
if (value[i].len <= sizeof("hmac=") - 1) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"hmac=\"");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* extract value */
|
||||
tmp.len = value[i].len - ngx_strlen("hmac=");
|
||||
tmp.data = (u_char *)(value[i].data + sizeof("hmac=") - 1);
|
||||
|
||||
/* is hmac=md5 ? */
|
||||
if (ngx_strncmp(tmp.data, "md5", sizeof("md5") - 1) == 0 ) {
|
||||
hmac = ngx_http_sticky_misc_hmac_md5;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* is hmac=sha1 ? */
|
||||
if (ngx_strncmp(tmp.data, "sha1", sizeof("sha1") - 1) == 0 ) {
|
||||
hmac = ngx_http_sticky_misc_hmac_sha1;
|
||||
continue;
|
||||
}
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "wrong value for \"hmac=\": md5 or sha1");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* is "hmac_key=" is starting the argument ? */
|
||||
if ((u_char *)ngx_strstr(value[i].data, "hmac_key=") == value[i].data) {
|
||||
|
||||
/* do we have at least on char after "hmac_key=" ? */
|
||||
if (value[i].len <= ngx_strlen("hmac_key=")) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "a value must be provided to \"hmac_key=\"");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* save what's after "hmac_key=" */
|
||||
hmac_key.len = value[i].len - ngx_strlen("hmac_key=");
|
||||
hmac_key.data = (u_char *)(value[i].data + sizeof("hmac_key=") - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* is "no_fallback" flag present ? */
|
||||
if (ngx_strncmp(value[i].data, "no_fallback", sizeof("no_fallback") - 1) == 0 ) {
|
||||
no_fallback = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid arguement (%V)", &value[i]);
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* if has and hmac and name have not been set, default to md5 */
|
||||
if (hash == NGX_CONF_UNSET_PTR && hmac == NULL && text == NULL) {
|
||||
hash = ngx_http_sticky_misc_md5;
|
||||
}
|
||||
|
||||
/* don't allow meaning less parameters */
|
||||
if (hmac_key.len > 0 && hash != NGX_CONF_UNSET_PTR) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"hmac_key=\" is meaningless when \"hmac\" is used. Please remove it.");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* ensure we have an hmac key if hmac's been set */
|
||||
if (hmac_key.len == 0 && hmac != NULL) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "please specify \"hmac_key=\" when using \"hmac\"");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* ensure hash is NULL to avoid conflicts later */
|
||||
if (hash == NGX_CONF_UNSET_PTR) {
|
||||
hash = NULL;
|
||||
}
|
||||
|
||||
/* save the sticky parameters */
|
||||
sticky_conf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_sticky_module);
|
||||
sticky_conf->cookie_name = name;
|
||||
sticky_conf->cookie_domain = domain;
|
||||
sticky_conf->cookie_path = path;
|
||||
sticky_conf->cookie_expires = expires;
|
||||
sticky_conf->hash = hash;
|
||||
sticky_conf->hmac = hmac;
|
||||
sticky_conf->text = text;
|
||||
sticky_conf->hmac_key = hmac_key;
|
||||
sticky_conf->no_fallback = no_fallback;
|
||||
sticky_conf->peers = NULL; /* ensure it's null before running */
|
||||
|
||||
upstream_conf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
|
||||
|
||||
/*
|
||||
* ensure another upstream module has not been already loaded
|
||||
* peer.init_upstream is set to null and the upstream module use RR if not set
|
||||
* But this check only works when the other module is declared before sticky
|
||||
*/
|
||||
if (upstream_conf->peer.init_upstream) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "You can't use sticky with another upstream module");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/* configure the upstream to get back to this module */
|
||||
upstream_conf->peer.init_upstream = ngx_http_init_upstream_sticky;
|
||||
|
||||
upstream_conf->flags = NGX_HTTP_UPSTREAM_CREATE
|
||||
| NGX_HTTP_UPSTREAM_MAX_FAILS
|
||||
| NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
|
||||
| NGX_HTTP_UPSTREAM_DOWN
|
||||
| NGX_HTTP_UPSTREAM_WEIGHT;
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* alloc stick configuration
|
||||
*/
|
||||
static void *ngx_http_sticky_create_conf(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_http_sticky_srv_conf_t *conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_sticky_srv_conf_t));
|
||||
if (conf == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
return conf;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [ "$(ls -A /etc/borderpatrol/sites-enabled)" ]; then
|
||||
echo "/etc/borderpatrol/sites-enabled is not empty; skipping symlinking of default conf file"
|
||||
else
|
||||
ln -s /etc/borderpatrol/sites-available/default.conf /etc/borderpatrol/sites-enabled/default.conf || true
|
||||
fi
|
|
@ -0,0 +1,9 @@
|
|||
pid /var/run/borderpatrol.pid;
|
||||
daemon on;
|
||||
|
||||
events {
|
||||
worker_connections 40;
|
||||
}
|
||||
|
||||
include /etc/borderpatrol/conf.d/*.conf;
|
||||
include /etc/borderpatrol/sites-enabled/*.conf;
|
|
@ -0,0 +1,148 @@
|
|||
#!/bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: borderpatrol
|
||||
# Required-Start: $network $remote_fs $local_fs
|
||||
# Required-Stop: $network $remote_fs $local_fs
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Stop/start borderpatrol
|
||||
### END INIT INFO
|
||||
|
||||
# Author: Sergey Budnevitch <sb@nginx.com>
|
||||
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC=borderpatrol
|
||||
NAME=borderpatrol
|
||||
CONFFILE=/etc/borderpatrol/borderpatrol.conf
|
||||
DAEMON=/usr/sbin/borderpatrol
|
||||
PIDFILE=/var/run/$NAME.pid
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
|
||||
[ -x $DAEMON ] || exit 0
|
||||
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
|
||||
DAEMON_ARGS="-c $CONFFILE $DAEMON_ARGS"
|
||||
|
||||
. /lib/init/vars.sh
|
||||
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
do_start()
|
||||
{
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
|
||||
$DAEMON_ARGS
|
||||
RETVAL="$?"
|
||||
return "$RETVAL"
|
||||
}
|
||||
|
||||
do_stop()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
|
||||
RETVAL="$?"
|
||||
rm -f $PIDFILE
|
||||
return "$RETVAL"
|
||||
}
|
||||
|
||||
do_reload() {
|
||||
#
|
||||
start-stop-daemon --stop --signal HUP --quiet --pidfile $PIDFILE --name $NAME
|
||||
RETVAL="$?"
|
||||
return "$RETVAL"
|
||||
}
|
||||
|
||||
do_configtest() {
|
||||
if [ "$#" -ne 0 ]; then
|
||||
case "$1" in
|
||||
-q)
|
||||
FLAG=$1
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
fi
|
||||
$DAEMON -t $FLAG -c $CONFFILE
|
||||
RETVAL="$?"
|
||||
return $RETVAL
|
||||
}
|
||||
|
||||
do_upgrade() {
|
||||
OLDBINPIDFILE=$PIDFILE.oldbin
|
||||
|
||||
do_configtest -q || return 6
|
||||
start-stop-daemon --stop --signal USR2 --quiet --pidfile $PIDFILE --name $NAME
|
||||
RETVAL="$?"
|
||||
sleep 1
|
||||
if [ -f $OLDBINPIDFILE -a -f $PIDFILE ]; then
|
||||
start-stop-daemon --stop --signal QUIT --quiet --pidfile $OLDBINPIDFILE --name $NAME
|
||||
RETVAL="$?"
|
||||
else
|
||||
echo $"Upgrade failed!"
|
||||
RETVAL=1
|
||||
return $RETVAL
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
|
||||
;;
|
||||
configtest)
|
||||
do_configtest
|
||||
;;
|
||||
upgrade)
|
||||
do_upgrade
|
||||
;;
|
||||
reload|force-reload)
|
||||
log_daemon_msg "Reloading $DESC" "$NAME"
|
||||
do_reload
|
||||
log_end_msg $?
|
||||
;;
|
||||
restart|force-reload)
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_configtest -q || exit $RETVAL
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart|reload|force-reload|upgrade|configtest}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
|
||||
exit $RETVAL
|
|
@ -0,0 +1,16 @@
|
|||
if (ngx.var.auth_token == "") then
|
||||
-- make a sub request to pull session data
|
||||
local res = ngx.location.capture("/auth")
|
||||
|
||||
ngx.log(ngx.DEBUG, "==== GET /auth " .. res.status .. " " .. res.body)
|
||||
|
||||
if res.status == ngx.HTTP_OK then
|
||||
-- set the auth token in the request variables so it can be pulled out
|
||||
-- and passed as a header then return and allow the request chain to
|
||||
-- continue. if there's no auth token do the same thing, allowing the
|
||||
-- upstream service to allow or deny access.
|
||||
ngx.var.auth_token = res.header['Auth-Token']
|
||||
end
|
||||
else
|
||||
ngx.log(ngx.DEBUG, "==== skipping GET /auth since auth_token is present: [" .. ngx.var.auth_token .. "]")
|
||||
end
|
|
@ -0,0 +1,91 @@
|
|||
local json = require("json")
|
||||
local sessionid = require("sessionid")
|
||||
|
||||
-------------------------------------------
|
||||
-- Make the call to the Account Service
|
||||
-------------------------------------------
|
||||
|
||||
local session_id = ngx.var.cookie_border_session
|
||||
|
||||
-- require session because the only valid scenario for arriving here is via redirect, which should have already set
|
||||
-- session
|
||||
if not session_id then
|
||||
ngx.log(ngx.INFO, "==== access denied: no session_id")
|
||||
ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
||||
end
|
||||
ngx.log(ngx.DEBUG, "==== session_id: " .. session_id)
|
||||
|
||||
if not sessionid.is_valid(session_id) then
|
||||
ngx.log(ngx.INFO, "==== access denied: session id invalid " .. session_id)
|
||||
ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
||||
end
|
||||
|
||||
-- Retrieve original target url and derive service
|
||||
local res = ngx.location.capture('/session?id=BP_URL_SID_' .. session_id)
|
||||
|
||||
ngx.log(ngx.DEBUG, "==== GET /session?id=BP_URL_SID_" .. session_id .. " " .. res.status)
|
||||
|
||||
-- Get original downstream url they were going to before being redirected
|
||||
original_url = res.body
|
||||
|
||||
-- get service name from first part of original uri
|
||||
local service_uri = string.match(original_url, "^/([^/]+)")
|
||||
local service = nil
|
||||
if service_uri then
|
||||
ngx.log(ngx.DEBUG, "==== service uri is: " .. service_uri)
|
||||
service = service_mappings[service_uri]
|
||||
end
|
||||
|
||||
-- check service
|
||||
if not service then
|
||||
if not service_uri then
|
||||
ngx.log(ngx.DEBUG, "==== no valid service uri provided")
|
||||
else
|
||||
ngx.log(ngx.DEBUG, "==== service not set for service uri: " .. service_uri)
|
||||
end
|
||||
ngx.redirect('/account/password')
|
||||
end
|
||||
|
||||
ngx.req.read_body()
|
||||
local args = ngx.req.get_post_args()
|
||||
|
||||
-- the account service expects 'e=user@example.com&p=password&t=3&s=servicename'
|
||||
args['service'] = service
|
||||
res = ngx.location.capture('/account', { method = ngx.HTTP_POST, body = ngx.encode_args(args) })
|
||||
|
||||
ngx.log(ngx.DEBUG, "==== POST /account " .. res.status .. " " .. res.body)
|
||||
|
||||
-- assume any 2xx is success
|
||||
-- On failure, redirect to login
|
||||
if res.status >= ngx.HTTP_SPECIAL_RESPONSE then
|
||||
ngx.log(ngx.DEBUG, "==== Authorization against Account Service failed: " .. res.body)
|
||||
ngx.redirect('/account')
|
||||
end
|
||||
|
||||
-- parse the response body
|
||||
local all_tokens_json = res.body
|
||||
local all_tokens = json.decode(all_tokens_json)
|
||||
|
||||
-- looking for auth tokens
|
||||
if not all_tokens then
|
||||
ngx.log(ngx.DEBUG, "==== no tokens found, redirecting to /account")
|
||||
ngx.redirect('/account')
|
||||
end
|
||||
|
||||
-- looking for service token
|
||||
if not all_tokens["service_tokens"][service] then
|
||||
ngx.log(ngx.DEBUG, "==== parse failure, or service token not found, redirecting to /account")
|
||||
ngx.redirect('/account')
|
||||
end
|
||||
|
||||
-- Extract token for specific service
|
||||
local auth_token = all_tokens["service_tokens"][service]
|
||||
|
||||
-- store all tokens in memcache via internal subrequest
|
||||
local res = ngx.location.capture('/session?id=BPSID_' .. session_id ..
|
||||
'&arg_exptime=' .. sessionid.EXPTIME, { body = all_tokens_json, method = ngx.HTTP_PUT })
|
||||
|
||||
ngx.log(ngx.DEBUG, "==== PUT /session?id=BPSID_" .. session_id ..
|
||||
'&arg_exptime=' .. sessionid.EXPTIME .. " " .. res.status)
|
||||
|
||||
ngx.redirect(original_url)
|
|
@ -0,0 +1,151 @@
|
|||
pid /tmp/nginx.pid;
|
||||
daemon off;
|
||||
|
||||
http {
|
||||
lua_package_path "../../build/usr/share/borderpatrol/?.lua;../../build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "../../build/usr/lib/lua/5.1/?.so;;";
|
||||
limit_req_zone $binary_remote_addr zone=auth_zone:100m rate=100r/m;
|
||||
|
||||
error_log logs/error.log debug;
|
||||
access_log logs/access.log;
|
||||
|
||||
# used to store and retrieve keys from memcached
|
||||
upstream session_store {
|
||||
server localhost:11211;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
# this is an app server protected by border patrol. If it returns a 401
|
||||
# when an attempt is made to access a protected resource, borderpatrol redirects
|
||||
# to the account service login
|
||||
upstream b {
|
||||
server localhost:9082;
|
||||
}
|
||||
|
||||
# this is an app server protected by border patrol. If it returns a 401
|
||||
# when an attempt is made to access a protected resource, borderpatrol redirects
|
||||
# to the account service login
|
||||
upstream c {
|
||||
server localhost:9083;
|
||||
}
|
||||
|
||||
# this is the account service. displays the login screen and also calls the auth service
|
||||
# to get a master token and a service token
|
||||
upstream account {
|
||||
server localhost:9084;
|
||||
}
|
||||
|
||||
# Nginx Lua has no SSL support for cosockets. This is unfortunate.
|
||||
# This proxies all requests to use the native NGINX request, though
|
||||
# it's a little hacky and sort of dirty.
|
||||
upstream token_server {
|
||||
server localhost:9081;
|
||||
}
|
||||
|
||||
# Service mappings, map service urls to service names
|
||||
init_by_lua 'service_mappings = {b="smb", c="flexd"}';
|
||||
|
||||
server {
|
||||
listen 4443 default_server ssl;
|
||||
root html;
|
||||
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH;
|
||||
ssl_session_cache shared:SSL:16m;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_certificate ../ssl/server.crt;
|
||||
ssl_certificate_key ../ssl/server.key;
|
||||
|
||||
# GET /session?id=foo -> memcache get
|
||||
# POST /session?id=foo -> memcache add, value is request body
|
||||
# PUT /session?id=foo -> memcache set, value is request body
|
||||
location = /session {
|
||||
internal;
|
||||
set $memc_key $arg_id;
|
||||
set $memc_exptime $arg_exptime;
|
||||
memc_pass session_store;
|
||||
}
|
||||
|
||||
# DELETE /session_delete?id=foo -> memcache delete
|
||||
location = /session_delete {
|
||||
internal;
|
||||
set $memc_key $arg_id;
|
||||
memc_pass session_store;
|
||||
}
|
||||
|
||||
location = /auth {
|
||||
internal;
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/validate.lua';
|
||||
}
|
||||
|
||||
location = /serviceauth {
|
||||
internal;
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/service_token.lua';
|
||||
}
|
||||
|
||||
location = /authtoken {
|
||||
internal;
|
||||
rewrite ^/(.*) /api/auth/public/v1/account_token.json break;
|
||||
proxy_pass http://token_server;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location = /mastertoken {
|
||||
internal;
|
||||
rewrite ^/(.*) /api/auth/service/v1/account_token.json break;
|
||||
proxy_pass http://token_server;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location = / {
|
||||
limit_req zone=auth_zone burst=25;
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/authorize.lua';
|
||||
}
|
||||
|
||||
location = /logout {
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/logout.lua';
|
||||
}
|
||||
|
||||
location ~ /(b|c)* {
|
||||
set $original_uri $uri;
|
||||
rewrite ^/(.*) / break;
|
||||
set $auth_token $http_auth_token;
|
||||
access_by_lua_file '../../build/usr/share/borderpatrol/access.lua';
|
||||
proxy_set_header auth-token $auth_token;
|
||||
proxy_pass http://$1;
|
||||
proxy_intercept_errors on;
|
||||
error_page 401 = @redirect;
|
||||
}
|
||||
|
||||
location @redirect {
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/redirect.lua';
|
||||
}
|
||||
|
||||
location = /health {
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/health_check.lua';
|
||||
}
|
||||
|
||||
location /robots.txt {
|
||||
alias ../../build/usr/share/borderpatrol/robots.txt;
|
||||
}
|
||||
|
||||
location / {
|
||||
set $auth_token $http_auth_token;
|
||||
access_by_lua_file '../../build/usr/share/borderpatrol/access.lua';
|
||||
proxy_set_header auth-token $auth_token;
|
||||
|
||||
# http://hostname/upstream_name/uri -> http://upstream_name/uri
|
||||
rewrite ^/([^/]+)/?(.*)$ /$2 break;
|
||||
proxy_pass http://$1;
|
||||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
events {
|
||||
worker_connections 40;
|
||||
}
|
||||
|
||||
# vim: ft=conf
|
|
@ -0,0 +1,55 @@
|
|||
--
|
||||
-- This script serves up an HTML page that displays current health of the
|
||||
-- BorderPatrol. The only check, currently, is that memcache is reachable.
|
||||
--
|
||||
local errors = {}
|
||||
local health_check = {}
|
||||
|
||||
-- print out the actual HTML
|
||||
function health_check.output(errors)
|
||||
ngx.header.content_type = 'text/html';
|
||||
ngx.print([[
|
||||
<html>
|
||||
<head>
|
||||
<title>Border Patrol Health</title>
|
||||
</head>
|
||||
<body>
|
||||
]])
|
||||
|
||||
if #errors > 0 then
|
||||
ngx.print("<h3>Errors</h3><ul>")
|
||||
for i, v in ipairs(errors) do
|
||||
ngx.print("<li>" .. v .. "</li>")
|
||||
end
|
||||
ngx.print("</ul>")
|
||||
else
|
||||
ngx.print("Everything is ok.")
|
||||
end
|
||||
|
||||
ngx.print([[
|
||||
</body>
|
||||
</html>
|
||||
]])
|
||||
end
|
||||
|
||||
local res = ngx.location.capture('/session?id=health_check', { method = ngx.HTTP_POST, body = os.time() })
|
||||
if not res.status == ngx.HTTP_OK then
|
||||
errors[#errors+1] = "memcache add: " .. res.status .. ": " .. res.body
|
||||
end
|
||||
|
||||
res = ngx.location.capture('/session?id=health_check')
|
||||
if not res.status == ngx.HTTP_OK then
|
||||
errors[#errors+1] = "memcache get: " .. res.status .. ": " .. res.body
|
||||
end
|
||||
|
||||
res = ngx.location.capture('/session?id=health_check', { method = ngx.HTTP_PUT, body = os.time() })
|
||||
if not res.status == ngx.HTTP_OK then
|
||||
errors[#errors+1] = "memcache set: " .. res.status .. ": " .. res.body
|
||||
end
|
||||
|
||||
res = ngx.location.capture('/session?id=health_check', { method = ngx.HTTP_DELETE })
|
||||
if not res.status == ngx.HTTP_OK then
|
||||
errors[#errors+1] = "memcache delete: " .. res.status .. ": " .. res.body
|
||||
end
|
||||
|
||||
health_check.output(errors)
|
|
@ -0,0 +1,37 @@
|
|||
-------------------------------------------
|
||||
-- delete auth token by session id
|
||||
-------------------------------------------
|
||||
|
||||
local args = ngx.req.get_uri_args()
|
||||
local destination = args['destination']
|
||||
|
||||
-- default to reasonable paths.
|
||||
-- allow only relative paths
|
||||
if not destination or string.sub(destination,1,1) ~= '/' then destination = '/' end
|
||||
|
||||
local session_id = ngx.var.cookie_border_session
|
||||
|
||||
if session_id then
|
||||
ngx.log(ngx.DEBUG, "==== session_id: " .. session_id)
|
||||
|
||||
-- expires a session
|
||||
local res = ngx.location.capture('/session_delete?id=BPSID_' .. session_id,
|
||||
{ method = ngx.HTTP_DELETE })
|
||||
|
||||
ngx.log(ngx.DEBUG, "DELETE /session_delete?id=BPSID_" .. session_id .. " " .. res.status)
|
||||
|
||||
-- expires the temporary url session
|
||||
local res = ngx.location.capture('/session_delete?id=BP_URL_SID_' .. session_id,
|
||||
{ method = ngx.HTTP_DELETE })
|
||||
|
||||
ngx.log(ngx.DEBUG, "DELETE /session_delete?id=BP_URL_SID_" .. session_id .. " " .. res.status)
|
||||
|
||||
-- unset session cookie (expires now() - 1yr)
|
||||
ngx.header['Set-Cookie'] = 'border_session=' ..
|
||||
'; path=/; expires=' .. ngx.cookie_time(ngx.time() - 3600 * 24 * 360)
|
||||
|
||||
else
|
||||
ngx.log(ngx.INFO, "==== session_id not set")
|
||||
end
|
||||
|
||||
ngx.redirect(destination)
|
|
@ -0,0 +1,25 @@
|
|||
local sessionid = require("sessionid")
|
||||
|
||||
local original_url = ngx.var.original_uri
|
||||
|
||||
-- Create session key and store the url as value (value will be updated later with master and service tokens
|
||||
local session_id = sessionid.generate();
|
||||
|
||||
-- store original url in memcache via internal subrequest
|
||||
local res = ngx.location.capture('/session?id=BP_URL_SID_' .. session_id ..
|
||||
'&arg_exptime=' .. sessionid.EXPTIME_TMP, { body = original_url, method = ngx.HTTP_POST })
|
||||
|
||||
ngx.log(ngx.DEBUG, "==== POST /session?id=BP_URL_SID_" .. session_id ..
|
||||
'&arg_exptime=' .. sessionid.EXPTIME .. " " .. res.status)
|
||||
|
||||
if res.status == ngx.HTTP_CREATED then
|
||||
-- set the cookie with the session_id
|
||||
ngx.header['Set-Cookie'] = 'border_session=' .. session_id .. '; path=/; HttpOnly; Secure;'
|
||||
-- Redirect to account service login
|
||||
ngx.redirect('/account')
|
||||
else
|
||||
ngx.log(ngx.ERR, "==== an error occurred trying to save session: " .. res.status)
|
||||
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
User-agent: *
|
||||
Disallow: /
|
|
@ -0,0 +1,49 @@
|
|||
local json = require("json")
|
||||
local sessionid = require("sessionid")
|
||||
|
||||
-------------------------------------------
|
||||
-- get service token using master token
|
||||
-------------------------------------------
|
||||
|
||||
local session_id = ngx.var.cookie_border_session
|
||||
|
||||
if not session_id then
|
||||
ngx.log(ngx.INFO, "==== access denied: no session_id")
|
||||
ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
||||
end
|
||||
|
||||
ngx.log(ngx.DEBUG, "==== session_id: " .. session_id)
|
||||
|
||||
if not sessionid.is_valid(session_id) then
|
||||
ngx.log(ngx.INFO, "==== access denied: session id invalid " .. session_id)
|
||||
ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
||||
end
|
||||
|
||||
ngx.req.read_body()
|
||||
local args = ngx.req.get_post_args()
|
||||
local master_token = args['mastertoken']
|
||||
local service = args['service']
|
||||
|
||||
if master_token and service then
|
||||
ngx.log(ngx.DEBUG, "==== master token: " .. master_token )
|
||||
local params = {}
|
||||
params['services'] = service
|
||||
ngx.req.set_header("Auth-Token", master_token)
|
||||
|
||||
res = ngx.location.capture('/mastertoken', { method = ngx.HTTP_POST, body = ngx.encode_args(params)})
|
||||
|
||||
ngx.log(ngx.DEBUG, "==== POST /mastertoken " .. res.status .. " " .. res.body)
|
||||
ngx.req.clear_header("Auth-Token")
|
||||
|
||||
-- assume any 2xx is success
|
||||
if res.status >= ngx.HTTP_SPECIAL_RESPONSE then
|
||||
ngx.log(ngx.DEBUG, "==== error getting service token using master token: ")
|
||||
ngx.exit(ngx.HTTP_BAD_REQUEST)
|
||||
end
|
||||
else
|
||||
ngx.log(ngx.DEBUG, "==== master token missing: ")
|
||||
ngx.exit(ngx.HTTP_BAD_REQUEST)
|
||||
end
|
||||
ngx.say(res.body)
|
||||
ngx.exit(ngx.HTTP_OK)
|
||||
|
|
@ -0,0 +1,275 @@
|
|||
-- session_id module to issue and validate signed session ids
|
||||
local crypto = require("crypto")
|
||||
|
||||
local module = {}
|
||||
|
||||
-- record expiration time in memcached
|
||||
local EXPTIME = 60 * 60
|
||||
local EXPTIME_TMP = 60 * 60 * 24 * 7
|
||||
|
||||
-- defines how often secrets will be rotated
|
||||
local SECRETS_EXP_INTERVAL = 60 * 60 * 24
|
||||
|
||||
-- internal session cookie lifetime to prevent unbound sessions
|
||||
local SESSION_COOKIE_LIFETIME = SECRETS_EXP_INTERVAL * 2
|
||||
|
||||
-- lease lifetime (short-living)
|
||||
local SECRETS_LEASE_INTERVAL = 5
|
||||
|
||||
-- signature length - used for basic validation
|
||||
local HMAC_SHA1_SIGN_LENGTH = 20
|
||||
|
||||
-- number of random bytes generated for session ids
|
||||
local DATA_LENGTH = 16
|
||||
|
||||
-- secret length - we keep 2 secrets in memory
|
||||
local KEY_LENGTH = 64
|
||||
|
||||
-- stores the time when to check whether we need to rotate secrets
|
||||
local ts_next_refresh = -1
|
||||
|
||||
-- encode string and make it url-safe
|
||||
local function encode(str, urlsafe)
|
||||
|
||||
local enc_str = ngx.encode_base64(str)
|
||||
if urlsafe then
|
||||
enc_str = string.gsub(enc_str, "+", "-") -- plus -> dash
|
||||
enc_str = string.gsub(enc_str, "/", "_") -- slash to underscore
|
||||
enc_str = string.gsub(enc_str, "=", "*") -- equal to star
|
||||
end
|
||||
|
||||
return enc_str
|
||||
end
|
||||
|
||||
-- decode string
|
||||
-- returns empty string if data is not base64 encoded
|
||||
local function decode(str, urlsafe)
|
||||
|
||||
local dec_str = str
|
||||
if urlsafe then
|
||||
dec_str = string.gsub(dec_str, "-", "+") -- plus -> dash
|
||||
dec_str = string.gsub(dec_str, "_", "/") -- slash to underscore
|
||||
dec_str = string.gsub(dec_str, "*", "=") -- equal to star
|
||||
end
|
||||
|
||||
return ngx.decode_base64(dec_str)
|
||||
end
|
||||
|
||||
-- serializes secret into string
|
||||
local function serialize_secret(secret)
|
||||
local str
|
||||
if secret and type(secret) == "table" then
|
||||
str = secret.data .. ":" .. secret.ts
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
-- deserializes secret string into a table
|
||||
local function deserialize_secret(str)
|
||||
local secret
|
||||
if str and type(str) == "string" then
|
||||
local startPos, endPos, data, ts = string.find(str, "(.+):(.+)")
|
||||
secret = {}
|
||||
secret.data = data
|
||||
secret.ts = tonumber(ts)
|
||||
end
|
||||
return secret
|
||||
end
|
||||
|
||||
--
|
||||
-- function to save secrets in SHM and memcached
|
||||
-- secret1 mandatory
|
||||
-- secret2 optional
|
||||
--
|
||||
local function save_secrets(secret1, secret2)
|
||||
|
||||
local secret1_str = serialize_secret(secret1)
|
||||
local secret2_str = serialize_secret(secret2)
|
||||
|
||||
-- persist in memcached
|
||||
local res = ngx.location.capture('/session?id=BPS1',
|
||||
{ body = secret1_str, method = ngx.HTTP_PUT })
|
||||
if res.status ~= ngx.HTTP_CREATED then
|
||||
ngx.log(ngx.WARN, "==== failed to persist secret1 " .. secret1_str .. " " .. res.status)
|
||||
else
|
||||
ngx.log(ngx.DEBUG, "==== secret1 persisted successfully " .. secret1_str .. " " .. res.status)
|
||||
end
|
||||
|
||||
-- secret2 is optional
|
||||
if secret2_str then
|
||||
-- persist in memcached
|
||||
res = ngx.location.capture('/session?id=BPS2',
|
||||
{ body = secret2_str, method = ngx.HTTP_PUT })
|
||||
if res.status ~= ngx.HTTP_CREATED then
|
||||
ngx.log(ngx.WARN, "==== failed to persist secret2 " .. secret2_str .. " " .. res.status)
|
||||
else
|
||||
ngx.log(ngx.DEBUG, "==== secret2 persisted successfully " .. secret2_str .. " " .. res.status)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- function to retrieve secrets, secret1 is current, secret2 is rotated
|
||||
--
|
||||
local function get_secrets()
|
||||
|
||||
local secret1, secret2
|
||||
|
||||
local res1, res2 = ngx.location.capture_multi{
|
||||
{ '/session?id=BPS1', { method = ngx.HTTP_GET } },
|
||||
{ '/session?id=BPS2', { method = ngx.HTTP_GET } },
|
||||
}
|
||||
|
||||
if res1.status >= ngx.HTTP_INTERNAL_SERVER_ERROR then
|
||||
ngx.log(ngx.ERR, "==== could not fetch secrets - memcached down?")
|
||||
ngx.exit(res1.status)
|
||||
end
|
||||
|
||||
if res1.status == ngx.HTTP_OK then
|
||||
secret1 = deserialize_secret(res1.body)
|
||||
ts_next_refresh = secret1.ts + SECRETS_EXP_INTERVAL -- update to the time when the cookie expired
|
||||
elseif res1.status == ngx.HTTP_NOT_FOUND then
|
||||
ngx.log(ngx.ERR, "==== secret1 not found - did memcached just restart?")
|
||||
-- trigger key refresh in case memcached got restarted
|
||||
ts_next_refresh = 0
|
||||
else
|
||||
ngx.log(ngx.ERR, "==== failed to retrieve secret1 " .. res1.status)
|
||||
end
|
||||
|
||||
if res2.status == ngx.HTTP_OK then
|
||||
secret2 = deserialize_secret(res2.body)
|
||||
else
|
||||
ngx.log(ngx.WARN, "==== failed to retrieve secret2 - that's prob okay in case secrets have not been rotated yet. " .. res2.status)
|
||||
end
|
||||
|
||||
return secret1, secret2
|
||||
end
|
||||
|
||||
--
|
||||
-- function to refresh secrets for hmac signing
|
||||
--
|
||||
local function refresh_keys_if_required()
|
||||
|
||||
-- initial check
|
||||
if (ts_next_refresh == -1) then
|
||||
local secret1, secret2 = get_secrets()
|
||||
if secret1 then
|
||||
ts_next_refresh = secret1.ts + SECRETS_EXP_INTERVAL
|
||||
else
|
||||
ts_next_refresh = 0
|
||||
end
|
||||
end
|
||||
|
||||
if (ngx.time() > ts_next_refresh) then
|
||||
ngx.log(ngx.DEBUG, "==== it's time to refresh keys...")
|
||||
|
||||
local res = ngx.location.capture("/session?id=BP_LEASE", { method = ngx.HTTP_GET })
|
||||
if res.status == ngx.HTTP_NOT_FOUND then
|
||||
|
||||
local res = ngx.location.capture("/session?id=BP_LEASE&exptime=" .. SECRETS_LEASE_INTERVAL,
|
||||
{ body = "1", method = ngx.HTTP_POST })
|
||||
if res.status == ngx.HTTP_CREATED then
|
||||
|
||||
local secret1, secret2 = get_secrets()
|
||||
|
||||
-- generate new secret
|
||||
local new_secret = {}
|
||||
new_secret.data = ngx.encode_base64(crypto.rand.bytes(KEY_LENGTH))
|
||||
new_secret.ts = ngx.time();
|
||||
|
||||
-- persist new secrets
|
||||
save_secrets(new_secret,secret1)
|
||||
|
||||
res = ngx.location.capture("/session_delete?id=BP_LEASE", { method = ngx.HTTP_GET })
|
||||
ngx.log(ngx.DEBUG, "==== lease deleted " .. res.status)
|
||||
|
||||
ts_next_refresh = new_secret.ts + SECRETS_EXP_INTERVAL
|
||||
|
||||
elseif res.status == ngx.HTTP_OK then
|
||||
ngx.log(ngx.DEBUG, "=== lease to update keys already taken - concurrent update?")
|
||||
else
|
||||
ngx.log(ngx.WARN, "==== failed to acquire lease " .. res.status)
|
||||
end
|
||||
else
|
||||
ngx.log(ngx.WARN, "==== refresh keys not needed yet")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- generate session_id
|
||||
--
|
||||
local function generate()
|
||||
|
||||
refresh_keys_if_required()
|
||||
|
||||
-- create 128 bit of random bytes
|
||||
local data = crypto.rand.bytes(DATA_LENGTH)
|
||||
local ts = ngx.time()
|
||||
local secret1, secret2 = get_secrets()
|
||||
|
||||
-- we could also use 'crypto' for 'digesting' but benchmark shows it's slightly
|
||||
-- slower: crypto.hmac.digest("sha1", data, secret1, true)
|
||||
local signature = ngx.hmac_sha1(secret1.data, data .. ts)
|
||||
local session_id = encode(data, true) .. ":" .. ts .. ":" .. encode(signature, true)
|
||||
|
||||
return session_id
|
||||
end
|
||||
|
||||
--
|
||||
-- function to validate session_id
|
||||
--
|
||||
local function is_valid(session_id)
|
||||
|
||||
refresh_keys_if_required()
|
||||
|
||||
-- parse session id
|
||||
local startPos, endPos, data, ts, signature = string.find(session_id, "(.+):(.+):(.+)")
|
||||
ts = tonumber(ts)
|
||||
|
||||
-- check for the obvious
|
||||
if not data or not signature or not ts then
|
||||
return false
|
||||
end
|
||||
|
||||
-- check whether the cookie expired already
|
||||
if ngx.time() > ts + SESSION_COOKIE_LIFETIME then
|
||||
return false
|
||||
end
|
||||
|
||||
-- decode
|
||||
data = decode(data, true)
|
||||
signature = decode(signature, true)
|
||||
|
||||
-- check whether data or signature became nil (happens in case it couldn't be decoded properly)
|
||||
if not data or #data ~= DATA_LENGTH or not signature or #signature ~= HMAC_SHA1_SIGN_LENGTH then
|
||||
return false
|
||||
end
|
||||
|
||||
-- re-compute signature
|
||||
local secret1, secret2 = get_secrets()
|
||||
if not secret1 then
|
||||
-- did memcache just restarted?
|
||||
return false
|
||||
else
|
||||
local computed_signature = ngx.hmac_sha1(secret1.data, data .. ts)
|
||||
|
||||
local match = computed_signature == signature
|
||||
if not match and secret2 then
|
||||
-- fallback - check with secret2
|
||||
-- TBD: issue fresh session cookie to prevent session loss after secrets
|
||||
-- get rotated, a bit overkill for now
|
||||
computed_signature = ngx.hmac_sha1(secret2.data, data .. ts)
|
||||
match = computed_signature == signature
|
||||
end
|
||||
|
||||
return match
|
||||
end
|
||||
end
|
||||
|
||||
module.generate = generate
|
||||
module.is_valid = is_valid
|
||||
module.EXPTIME = EXPTIME
|
||||
module.EXPTIME_TMP = EXPTIME_TMP
|
||||
|
||||
return module
|
|
@ -0,0 +1,11 @@
|
|||
This folder contains self-signed SSL key and certs which will be used for local testing with your browser:
|
||||
|
||||
Generate new ones via (won't be needed though):
|
||||
|
||||
openssl genrsa -des3 -out server.key 1024
|
||||
openssl req -new -key server.key -out server.csr
|
||||
cp server.key server.key.org
|
||||
openssl rsa -in server.key.org -out server.key
|
||||
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
|
||||
|
||||
Additional details can be found <a href="https://www.digitalocean.com/community/articles/how-to-create-a-ssl-certificate-on-nginx-for-ubuntu-12-04">here</a>.
|
|
@ -0,0 +1,15 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICTzCCAbgCCQDst2eQl9PzIzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQGEwJV
|
||||
UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xFjAUBgNVBAoT
|
||||
DUxvb2tvdXQsIEluYy4xIDAeBgNVBAMTF3NlbGYtc2lnbmVkLmxvb2tvdXQuY29t
|
||||
MB4XDTEzMDkxNzE3MTIzNFoXDTE0MDkxNzE3MTIzNFowbDELMAkGA1UEBhMCVVMx
|
||||
CzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRYwFAYDVQQKEw1M
|
||||
b29rb3V0LCBJbmMuMSAwHgYDVQQDExdzZWxmLXNpZ25lZC5sb29rb3V0LmNvbTCB
|
||||
nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAnqcN6ZWG1q3E2HDU5Z1OM9HIAV64
|
||||
1XBN7JW/+CKR+GOpuRGqlwSQ4QYmB+TZJnB+p0f6whKnb5Cch33m0oSP//NCy0Oz
|
||||
/TgP33T5bPu+a7/MUIYjpYLwsiD4fWb1AALbJ+ZxqIinouiJUy79tpWU8SjrTxRO
|
||||
ubLY3FWHKmpSyQ0CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBDKshY0e8H/nu2UaF4
|
||||
TV41QX6g6kPEbEGDGlmyuP5n5gDAD0trdBFZpPCRCnoMGObD9InQGQ/ExVpyvvle
|
||||
0AcRl0Gan6C/zv9brCTf4Ks0Y/s8u9YwmEA+qB5W020Y01GQ+WfNtlM67X2I2j7G
|
||||
N0AE28/IALl4c1BUwdHs2dVN7A==
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,15 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQCepw3plYbWrcTYcNTlnU4z0cgBXrjVcE3slb/4IpH4Y6m5EaqX
|
||||
BJDhBiYH5NkmcH6nR/rCEqdvkJyHfebShI//80LLQ7P9OA/fdPls+75rv8xQhiOl
|
||||
gvCyIPh9ZvUAAtsn5nGoiKei6IlTLv22lZTxKOtPFE65stjcVYcqalLJDQIDAQAB
|
||||
AoGAJNIMjoufca9+oeT95BRwE+K6EmdTamXYD/JpTUNosUcgGs2Y09fBcBgnN2nL
|
||||
Y/pzyosQDX6a0W+0hFWZ/n25lYXgN9gWK5y9xxE/WGuZCgFEcGrPzvXM/r1zSh1s
|
||||
N10dmpQJbIPEfgOVDZuVokk2+FUJZUxDLbGghPhcWElJUEECQQDPXGKiV5ZVl2U9
|
||||
wm3VfhT2iKvpsod5573PSeUs12PtExxwIpZJcD54RRrgJWUtubTWBlSgF02egkko
|
||||
PihrDofpAkEAw93UYKDoxW42tLvVMMvxOPsshHh+P4A+XjzKXxrNceXxHF0h+xFB
|
||||
w+6ywoVGnpp6Om08mw3zhaQfTBj2DaWlhQJAKL1x84tZ0f8ouPWWNrfKzpUTkZqt
|
||||
21mYhT1zdVfsHgv/LljdRhhzbZXGLfuq4Uz3JoWf4sQxT88xKGLt9fqo4QJANzcS
|
||||
xsa1t+pw+5Qz7lSfxOtxykpZdLdHXbOPbS4WGnSy+sb6bFeaDYz90b5WgSGVMWFY
|
||||
A3H0Y4k31XD39DLtLQJBALODq7HHEsKAfZTvYOEVmok7EfMV11Cvt9nrAnHtuFK1
|
||||
Nw+eYNPCpckN8qVUzsiUHfWdtAYUuvMzcpEF3M2oVMw=
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,83 @@
|
|||
local json = require("json")
|
||||
local sessionid = require("sessionid")
|
||||
|
||||
-------------------------------------------
|
||||
-- Lookup auth token by session id
|
||||
-------------------------------------------
|
||||
|
||||
local session_id = ngx.var.cookie_border_session
|
||||
|
||||
if not session_id then
|
||||
ngx.log(ngx.INFO, "==== access denied: no session_id")
|
||||
ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
||||
end
|
||||
|
||||
ngx.log(ngx.DEBUG, "==== session_id: " .. session_id)
|
||||
|
||||
if not sessionid.is_valid(session_id) then
|
||||
ngx.log(ngx.INFO, "==== access denied: session id invalid " .. session_id)
|
||||
ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
||||
end
|
||||
|
||||
local auth_token = nil
|
||||
local all_tokens_json = nil
|
||||
|
||||
local res = ngx.location.capture('/session?id=BPSID_' .. session_id)
|
||||
ngx.log(ngx.DEBUG, "GET /session?id=BPSID_" .. session_id .. " " ..
|
||||
res.status)
|
||||
|
||||
local all_tokens = nil
|
||||
|
||||
if res.status == ngx.HTTP_OK then
|
||||
all_tokens_json = res.body
|
||||
all_tokens = json.decode(all_tokens_json, {nothrow = true})
|
||||
|
||||
if all_tokens then
|
||||
-- get service name from uri
|
||||
local service_uri = string.match(ngx.var.request_uri,"^/([^/]+)")
|
||||
local service = nil
|
||||
if service_uri then
|
||||
service = service_mappings[service_uri]
|
||||
end
|
||||
|
||||
auth_token = all_tokens['service_tokens'][service]
|
||||
|
||||
if not auth_token then
|
||||
ngx.log(ngx.INFO, "==== token not found in session for service : " .. service_uri)
|
||||
master_token = all_tokens['auth_service']
|
||||
if master_token then
|
||||
local params = {}
|
||||
params['mastertoken'] = master_token
|
||||
params['service'] = service
|
||||
res = ngx.location.capture('/serviceauth', { method = ngx.HTTP_POST, body = ngx.encode_args(params) })
|
||||
|
||||
-- TODO Check response status (in this case, don't do anything, treat it as missing token)
|
||||
local specific_token_json = res.body
|
||||
auth_token = json.decode(specific_token_json, {nothrow = true})['service_tokens'][service]
|
||||
if auth_token then
|
||||
all_tokens['service_tokens'][service] = auth_token
|
||||
all_tokens_json = json.encode(all_tokens)
|
||||
end
|
||||
ngx.log(ngx.INFO, "==== retrieved service token for service: " .. service .. " " .. auth_token)
|
||||
end
|
||||
end
|
||||
-- reset the auth_token TTL to maintain a rolling session window
|
||||
res = ngx.location.capture('/session?id=BPSID_' .. session_id ..
|
||||
"&exptime=" .. sessionid.EXPTIME, { body = all_tokens_json, method = ngx.HTTP_PUT })
|
||||
if res.status ~= ngx.HTTP_CREATED then
|
||||
ngx.log(ngx.WARN, "==== failed to refresh session w/ id " .. session_id ..
|
||||
" " .. res.status)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not auth_token then
|
||||
ngx.log(ngx.INFO, "==== access denied: no auth token for session_id " ..
|
||||
session_id)
|
||||
ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
||||
end
|
||||
|
||||
-- If we made it this far, we're good. Inject the Auth-Token header
|
||||
ngx.header['Auth-Token'] = auth_token
|
||||
ngx.log(ngx.INFO, "==== request auth header set")
|
||||
ngx.exit(ngx.HTTP_OK)
|
|
@ -0,0 +1,91 @@
|
|||
use lib 'lib';
|
||||
use Test::Nginx::Socket;
|
||||
|
||||
$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;
|
||||
|
||||
plan tests => $Test::Nginx::Socket::RepeatEach * 2 * blocks();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
|
||||
=== TEST 1: test w/ auth-token present in client request
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
|
||||
upstream b {
|
||||
server 127.0.0.1:$TEST_NGINX_SERVER_PORT; # self
|
||||
}
|
||||
|
||||
--- config
|
||||
location /testpath {
|
||||
echo_status 200;
|
||||
echo_duplicate 1 $echo_client_request_headers;
|
||||
echo 'everything is ok';
|
||||
echo_flush;
|
||||
}
|
||||
location /auth {
|
||||
echo_status 200;
|
||||
echo_flush;
|
||||
}
|
||||
location /b/testpath {
|
||||
set $auth_token $http_auth_token;
|
||||
access_by_lua_file '../../build/usr/share/borderpatrol/access.lua';
|
||||
proxy_set_header auth-token $auth_token;
|
||||
|
||||
# http://hostname/upstream_name/uri -> http://upstream_name/uri
|
||||
rewrite ^/([^/]+)/?(.*)$ /$2 break;
|
||||
proxy_pass http://$1;
|
||||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
--- request
|
||||
GET /b/testpath
|
||||
--- more_headers
|
||||
auth-token: tokentokentokentoken
|
||||
--- error_code: 200
|
||||
--- response_body_like
|
||||
auth-token: tokentokentoken.+everything is ok$
|
||||
|
||||
=== TEST 2: test w/o auth-token not present in client request but with valid session
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
init_by_lua 'service_mappings = {b="smb",s="flexd"}';
|
||||
upstream b {
|
||||
server 127.0.0.1:$TEST_NGINX_SERVER_PORT; # self
|
||||
}
|
||||
--- config
|
||||
location /testpath {
|
||||
echo_status 200;
|
||||
echo_duplicate 1 $echo_client_request_headers;
|
||||
echo 'everything is ok';
|
||||
echo_flush;
|
||||
}
|
||||
location /auth {
|
||||
internal;
|
||||
echo_status 200;
|
||||
more_set_headers 'Auth-Token: tokentokentokentoken';
|
||||
echo_flush;
|
||||
}
|
||||
location /b/testpath {
|
||||
set $auth_token $http_auth_token;
|
||||
access_by_lua_file '../../build/usr/share/borderpatrol/access.lua';
|
||||
proxy_set_header auth-token $auth_token;
|
||||
|
||||
# http://hostname/upstream_name/uri -> http://upstream_name/uri
|
||||
rewrite ^/([^/]+)/?(.*)$ /$2 break;
|
||||
proxy_pass http://$1;
|
||||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
--- request
|
||||
GET /b/testpath
|
||||
--- more_headers
|
||||
Cookie: border_session=this-is-a-session-id # not checked here!
|
||||
--- error_code: 200
|
||||
--- response_body_like
|
||||
auth-token: tokentokentoken.+everything is ok$
|
|
@ -0,0 +1,271 @@
|
|||
use lib 'lib';
|
||||
use Test::Nginx::Socket;
|
||||
|
||||
$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;
|
||||
|
||||
repeat_each(1);
|
||||
|
||||
plan tests => repeat_each() * (2 * blocks()) - 1;
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
|
||||
=== TEST 0: initialize memcached
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
init_by_lua 'service_mappings = {b="smb", s="flexd"}';
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location = /setup {
|
||||
# clear
|
||||
echo_subrequest GET '/memc_setup?cmd=flush_all';
|
||||
echo_subrequest POST '/memc_setup?key=BP_LEASE' -b '1';
|
||||
|
||||
echo_subrequest POST '/memc_setup?key=BPS1' -b 'mysecret:1595116800';
|
||||
}
|
||||
--- request
|
||||
GET /setup
|
||||
--- more_headers
|
||||
Content-type: application/x-www-form-urlencoded
|
||||
--- error_code: 200
|
||||
--- response_body_like
|
||||
OK\r
|
||||
STORED\r
|
||||
STORED\r
|
||||
|
||||
=== TEST 1: test successful login
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
init_by_lua 'service_mappings = {b="smb", s="flexd"}';
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location = /setup {
|
||||
# clear
|
||||
echo_subrequest GET '/memc_setup?cmd=flush_all';
|
||||
echo_subrequest POST '/memc_setup?key=BP_LEASE' -b '1';
|
||||
echo_subrequest POST '/memc_setup?key=BPS1' -b 'mysecret:1595116800';
|
||||
echo_subrequest POST '/memc_setup?key=BP_URL_SID_MDEyMzQ1Njc4OTAxMjM0NQ**:1595116800:9Wc0CzZKO7Mq5Y2NbTaHrIp/gMg*' -b '/b';
|
||||
echo_status 200;
|
||||
}
|
||||
location = /session {
|
||||
internal;
|
||||
set $memc_key $arg_id;
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location /authorize { # under test
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/authorize.lua';
|
||||
}
|
||||
location /account {
|
||||
internal;
|
||||
echo_status 200;
|
||||
echo '{"auth_service": "tokentokentokentoken", "service_tokens": {"smb": "tokentokentokentoken"}}';
|
||||
echo_flush;
|
||||
}
|
||||
--- request eval
|
||||
["GET /setup", "POST /authorize
|
||||
username=foo&password=bar"]
|
||||
--- more_headers
|
||||
Content-type: application/x-www-form-urlencoded
|
||||
Cookie: border_session=MDEyMzQ1Njc4OTAxMjM0NQ**:1595116800:9Wc0CzZKO7Mq5Y2NbTaHrIp/gMg*
|
||||
---- response_headers_like
|
||||
Set-Cookie: border_session=.+:.+; path=/; HttpOnly; Secure;$
|
||||
Location: http://localhost(?::\d+)?/b$
|
||||
--- error_code eval
|
||||
[200,302]
|
||||
|
||||
=== TEST 2: test unsuccessful login
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
init_by_lua 'service_mappings = {b="smb", s="flexd"}';
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location = /setup {
|
||||
# clear
|
||||
echo_subrequest GET '/memc_setup?cmd=flush_all';
|
||||
echo_subrequest POST '/memc_setup?key=BP_LEASE' -b '1';
|
||||
echo_subrequest POST '/memc_setup?key=BPS1' -b 'mysecret:1595116800';
|
||||
echo_subrequest POST '/memc_setup?key=BP_URL_SID_MDEyMzQ1Njc4OTAxMjM0NQ**:1595116800:9Wc0CzZKO7Mq5Y2NbTaHrIp/gMg*' -b '/b';
|
||||
echo_status 200;
|
||||
}
|
||||
location = /session {
|
||||
internal;
|
||||
set $memc_key $arg_id;
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location /authorize { # under test
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/authorize.lua';
|
||||
}
|
||||
location /account {
|
||||
internal;
|
||||
echo_status 403;
|
||||
echo_flush;
|
||||
}
|
||||
--- request eval
|
||||
["GET /setup", "POST /authorize
|
||||
username=foo&password=bar"]
|
||||
--- more_headers
|
||||
Content-type: application/x-www-form-urlencoded
|
||||
Cookie: border_session=MDEyMzQ1Njc4OTAxMjM0NQ**:1595116800:9Wc0CzZKO7Mq5Y2NbTaHrIp/gMg*
|
||||
---- response_headers_like
|
||||
Set-Cookie: border_session=.+:.+; path=/; HttpOnly; Secure;$
|
||||
Location: http://localhost(?::\d+)?/b$
|
||||
--- error_code eval
|
||||
[200,302]
|
||||
|
||||
=== TEST 3: test failed login with memcached down
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
init_by_lua 'service_mappings = {b="smb", s="flexd"}';
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location /session { # memcached
|
||||
internal;
|
||||
echo_status 502; # simulate memcached down
|
||||
echo_flush;
|
||||
}
|
||||
location /authorize { # under test
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/authorize.lua';
|
||||
}
|
||||
location /account {
|
||||
internal;
|
||||
echo_status 200;
|
||||
echo '{"auth_service": "tokentokentokentoken", "service_tokens": {"smb": "tokentokentokentoken"}}';
|
||||
echo_flush;
|
||||
}
|
||||
--- request eval
|
||||
"POST /authorize
|
||||
username=foo&password=bar"
|
||||
--- more_headers
|
||||
Content-type: application/x-www-form-urlencoded
|
||||
Cookie: border_session=MDEyMzQ1Njc4OTAxMjM0NQ**:1595116800:9Wc0CzZKO7Mq5Y2NbTaHrIp/gMg*
|
||||
---- response_headers_like
|
||||
Set-Cookie: border_session=.+:.+; path=/; HttpOnly; Secure;$
|
||||
Location: http://localhost(?::\d+)?/b$
|
||||
--- error_code: 502
|
||||
|
||||
=== TEST 4: test failed login with Account Service down
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
init_by_lua 'service_mappings = {b="smb", s="flexd"}';
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location = /setup {
|
||||
# clear
|
||||
echo_subrequest GET '/memc_setup?cmd=flush_all';
|
||||
echo_subrequest POST '/memc_setup?key=BP_LEASE' -b '1';
|
||||
echo_subrequest POST '/memc_setup?key=BPS1' -b 'mysecret:1595116800';
|
||||
echo_subrequest POST '/memc_setup?key=BP_URL_SID_MDEyMzQ1Njc4OTAxMjM0NQ**:1595116800:9Wc0CzZKO7Mq5Y2NbTaHrIp/gMg*' -b '/b';
|
||||
echo_status 200;
|
||||
}
|
||||
location = /session {
|
||||
internal;
|
||||
set $memc_key $arg_id;
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location /authorize { # under test
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/authorize.lua';
|
||||
}
|
||||
location /account {
|
||||
internal;
|
||||
echo_status 500;
|
||||
echo_flush;
|
||||
}
|
||||
--- request eval
|
||||
["GET /setup", "POST /authorize
|
||||
username=foo&password=bar"]
|
||||
--- more_headers
|
||||
Content-type: application/x-www-form-urlencoded
|
||||
Cookie: border_session=MDEyMzQ1Njc4OTAxMjM0NQ**:1595116800:9Wc0CzZKO7Mq5Y2NbTaHrIp/gMg*
|
||||
---- response_headers_like
|
||||
Set-Cookie: border_session=.+:.+; path=/; HttpOnly; Secure;$
|
||||
Location: http://localhost(?::\d+)?/b$
|
||||
--- error_code eval
|
||||
[200,302]
|
||||
|
||||
=== TEST 5: test invalid service url
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
init_by_lua 'service_mappings = {b="smb", s="flexd"}';
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location = /setup {
|
||||
# clear
|
||||
echo_subrequest GET '/memc_setup?cmd=flush_all';
|
||||
echo_subrequest POST '/memc_setup?key=BP_LEASE' -b '1';
|
||||
echo_subrequest POST '/memc_setup?key=BPS1' -b 'mysecret:1595116800';
|
||||
echo_subrequest POST '/memc_setup?key=BP_URL_SID_MDEyMzQ1Njc4OTAxMjM0NQ**:1595116800:9Wc0CzZKO7Mq5Y2NbTaHrIp/gMg*' -b '/x';
|
||||
echo_status 200;
|
||||
}
|
||||
location = /session {
|
||||
internal;
|
||||
set $memc_key $arg_id;
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location /authorize { # under test
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/authorize.lua';
|
||||
}
|
||||
location /account {
|
||||
internal;
|
||||
echo_status 403;
|
||||
echo_flush;
|
||||
}
|
||||
--- request eval
|
||||
["GET /setup", "POST /authorize
|
||||
username=foo&password=bar"]
|
||||
--- more_headers
|
||||
Content-type: application/x-www-form-urlencoded
|
||||
Cookie: border_session=MDEyMzQ1Njc4OTAxMjM0NQ**:1595116800:9Wc0CzZKO7Mq5Y2NbTaHrIp/gMg*
|
||||
---- response_headers_like
|
||||
Set-Cookie: border_session=.+:.+; path=/; HttpOnly; Secure;$
|
||||
Location: http://localhost(?::\d+)?/b$
|
||||
--- error_code eval
|
||||
[200,302]
|
|
@ -0,0 +1,69 @@
|
|||
# vim: ft=ruby
|
||||
|
||||
ROOT_PATH = Dir.pwd
|
||||
TEST_PATH = File.join(ROOT_PATH, 't')
|
||||
SERVICE_PATH = File.join(TEST_PATH, 'services')
|
||||
SERVER_PATH = File.join(TEST_PATH, 'servroot')
|
||||
LOG_PATH = File.join(SERVER_PATH, 'logs')
|
||||
|
||||
GROUP_NAME = 'borderpatrol'
|
||||
|
||||
# Watch the api service
|
||||
God.watch do |w|
|
||||
w.name = 'api_service'
|
||||
w.group = GROUP_NAME
|
||||
w.dir = SERVICE_PATH
|
||||
w.start = 'bundle exec shotgun api_service.rb -p 9082'
|
||||
w.log = File.join(LOG_PATH, 'api_service.out')
|
||||
w.keepalive
|
||||
end
|
||||
|
||||
# Watch the 2nd api service
|
||||
God.watch do |w|
|
||||
w.name = 'api_service2'
|
||||
w.group = GROUP_NAME
|
||||
w.dir = SERVICE_PATH
|
||||
w.start = 'bundle exec shotgun api_service2.rb -p 9083'
|
||||
w.log = File.join(LOG_PATH, 'api_service2.out')
|
||||
w.keepalive
|
||||
end
|
||||
|
||||
# Watch the account service
|
||||
God.watch do |w|
|
||||
w.name = 'account_service'
|
||||
w.group = GROUP_NAME
|
||||
w.dir = SERVICE_PATH
|
||||
w.start = 'bundle exec shotgun account_service.rb -p 9084'
|
||||
w.log = File.join(LOG_PATH, 'account_service.out')
|
||||
w.keepalive
|
||||
end
|
||||
|
||||
# Watch the token server
|
||||
God.watch do |w|
|
||||
w.name = 'token_service'
|
||||
w.group = GROUP_NAME
|
||||
w.dir = SERVICE_PATH
|
||||
w.start = 'bundle exec shotgun auth_service.rb -p 9081'
|
||||
w.log = File.join(LOG_PATH, 'token_service.out')
|
||||
w.keepalive
|
||||
end
|
||||
|
||||
God.watch do |w|
|
||||
w.name = 'memcache'
|
||||
w.group = GROUP_NAME
|
||||
w.dir = SERVER_PATH
|
||||
w.start = 'memcached -vvv'
|
||||
w.log = File.join(LOG_PATH, 'memcached.out')
|
||||
w.keepalive
|
||||
end
|
||||
|
||||
God.watch do |w|
|
||||
w.name = 'nginx'
|
||||
w.group = GROUP_NAME
|
||||
w.dir = SERVER_PATH
|
||||
w.start = "#{ROOT_PATH}/build/usr/sbin/borderpatrol -g 'error_log #{LOG_PATH}/error.log;' -p #{SERVER_PATH} -c #{ROOT_PATH}/build/etc/borderpatrol/sites-available/borderpatrol.conf.sample"
|
||||
w.log = File.join(LOG_PATH, 'nginx.out')
|
||||
w.keepalive
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
use lib 'lib';
|
||||
use Test::Nginx::Socket;
|
||||
|
||||
$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;
|
||||
|
||||
repeat_each(1);
|
||||
|
||||
plan tests => repeat_each() * (1 * blocks());
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
|
||||
=== TEST 1: heath controller
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
--- config
|
||||
location = /session {
|
||||
set $memc_key $arg_id;
|
||||
set $memc_exptime 10;
|
||||
|
||||
memc_cmds_allowed get set add delete;
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
|
||||
location /health {
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/health_check.lua';
|
||||
}
|
||||
--- request
|
||||
GET /health
|
||||
--- error_code: 200
|
|
@ -0,0 +1,77 @@
|
|||
use lib 'lib';
|
||||
use Test::Nginx::Socket;
|
||||
|
||||
$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;
|
||||
|
||||
repeat_each(1);
|
||||
|
||||
plan tests => repeat_each() * (2 * blocks());
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
|
||||
=== TEST 1: test logout w/o destination and w/o session_id
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
--- config
|
||||
location /logout { # under test
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/logout.lua';
|
||||
}
|
||||
--- request
|
||||
GET /logout
|
||||
--- error_code: 302
|
||||
--- response_headers_like: Location: http://localhost(?::\d+)?/$
|
||||
|
||||
=== TEST 2: test logout w/ relative destination and w/o session_id
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
--- config
|
||||
location /logout { # under test
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/logout.lua';
|
||||
}
|
||||
--- request
|
||||
GET /logout?destination=/somepath
|
||||
--- error_code: 302
|
||||
--- response_headers_like: Location: http://localhost(?::\d+)?/somepath$
|
||||
|
||||
=== TEST 3: test logout w/ absolute destination and w/o session_id
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
--- config
|
||||
location /logout { # under test
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/logout.lua';
|
||||
}
|
||||
--- request
|
||||
GET /logout?destination=http://www.evil.org
|
||||
--- error_code: 302
|
||||
--- response_headers_like: Location: http://localhost(?::\d+)?/$
|
||||
|
||||
=== TEST 4: test logout w/ session_id
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
--- config
|
||||
location /logout { # under test
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/logout.lua';
|
||||
}
|
||||
location /session_delete { # memcached
|
||||
internal;
|
||||
echo_status 400; # simulates successful write into memcached
|
||||
echo_flush;
|
||||
}
|
||||
--- request
|
||||
GET /logout
|
||||
--- more_headers
|
||||
Cookie: border_session=wfxLNdl2BrLN9NVuQ9_wiA**:4lcaas0Onjxsn2D6kDVPTw**
|
||||
--- error_code: 302
|
||||
--- response_headers_like
|
||||
Set-Cookie: border_session=; path=/; expires=.+GMT$
|
||||
--- response_headers_like: Location: http://localhost(?::\d+)?/$
|
|
@ -0,0 +1,105 @@
|
|||
use lib 'lib';
|
||||
use Test::Nginx::Socket;
|
||||
|
||||
$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;
|
||||
|
||||
repeat_each(1);
|
||||
|
||||
plan tests => repeat_each() * (2 * blocks()) - 1;
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
|
||||
=== TEST 0: initialize memcached
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
init_by_lua 'service_mappings = {b="smb", s="flexd"}';
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location = /setup {
|
||||
# clear
|
||||
echo_subrequest GET '/memc_setup?cmd=flush_all';
|
||||
echo_subrequest POST '/memc_setup?key=BP_LEASE' -b '1';
|
||||
|
||||
echo_subrequest POST '/memc_setup?key=BPS1' -b 'mysecret:1595116800';
|
||||
}
|
||||
--- request
|
||||
GET /setup
|
||||
--- more_headers
|
||||
Content-type: application/x-www-form-urlencoded
|
||||
--- error_code: 200
|
||||
--- response_body_like
|
||||
OK\r
|
||||
STORED\r
|
||||
STORED\r
|
||||
|
||||
=== TEST 1: test successful redirect
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
init_by_lua 'service_mappings = {b="smb", s="flexd"}';
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location = /session {
|
||||
internal;
|
||||
set $memc_key $arg_id;
|
||||
set $memc_value $arg_val;
|
||||
set $memc_exptime $arg_exptime;
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location /redirect {
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/redirect.lua';
|
||||
}
|
||||
|
||||
--- request eval
|
||||
"POST /redirect"
|
||||
--- more_headers
|
||||
Content-type: application/x-www-form-urlencoded
|
||||
--- error_code: 302
|
||||
--- response_headers_like
|
||||
Location: http://localhost(?::\d+)?/account$
|
||||
|
||||
=== TEST 2: test memcached down
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
init_by_lua 'service_mappings = {b="smb", s="flexd"}';
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location /session { # memcached
|
||||
internal;
|
||||
echo_status 502; # simulate memcached down
|
||||
echo_flush;
|
||||
}
|
||||
location /redirect {
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/redirect.lua';
|
||||
}
|
||||
--- request eval
|
||||
"POST /redirect"
|
||||
--- more_headers
|
||||
Content-type: application/x-www-form-urlencoded
|
||||
--- error_code: 502
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
use lib 'lib';
|
||||
use Test::Nginx::Socket;
|
||||
|
||||
$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;
|
||||
|
||||
repeat_each(1);
|
||||
|
||||
plan tests => repeat_each() * (2 * blocks()) -3;
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
|
||||
=== TEST 0: test getting service token with valid master token and valid service name
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location = /session {
|
||||
internal;
|
||||
set $memc_key $arg_id;
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
|
||||
location = /mastertoken {
|
||||
echo_status 200;
|
||||
echo '{smb: "tokentokentokentoken"}';
|
||||
echo_flush;
|
||||
}
|
||||
|
||||
location /serviceauth { # under test
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/service_token.lua';
|
||||
}
|
||||
|
||||
--- request eval
|
||||
"POST /serviceauth
|
||||
mastertoken=DEADBEEF&service=smb"
|
||||
--- more_headers
|
||||
Cookie: border_session=MDEyMzQ1Njc4OTAxMjM0NQ**:1595116800:9Wc0CzZKO7Mq5Y2NbTaHrIp/gMg*
|
||||
Content-type: application/x-www-form-urlencoded
|
||||
|
||||
--- response_body_like
|
||||
{smb: "tokentokentokentoken"}
|
||||
|
||||
--- error_code: 200
|
||||
|
||||
=== TEST 1: test attempt to get service token with valid master token and invalid service name
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location = /session {
|
||||
internal;
|
||||
set $memc_key $arg_id;
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
|
||||
location = /mastertoken {
|
||||
echo_status 400;
|
||||
echo_flush;
|
||||
}
|
||||
|
||||
location /serviceauth { # under test
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/service_token.lua';
|
||||
}
|
||||
|
||||
--- request eval
|
||||
"POST /serviceauth
|
||||
mastertoken=DEADBEEF&service=junkservice"
|
||||
--- more_headers
|
||||
Cookie: border_session=MDEyMzQ1Njc4OTAxMjM0NQ**:1595116800:9Wc0CzZKO7Mq5Y2NbTaHrIp/gMg*
|
||||
Content-type: application/x-www-form-urlencoded
|
||||
|
||||
--- error_code: 400
|
||||
|
||||
=== TEST 2: test attempt to get service token with nil master token
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location = /session {
|
||||
internal;
|
||||
set $memc_key $arg_id;
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
|
||||
location = /mastertoken {
|
||||
echo_status 400;
|
||||
echo_flush;
|
||||
}
|
||||
|
||||
location /serviceauth { # under test
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/service_token.lua';
|
||||
}
|
||||
|
||||
--- request eval
|
||||
"POST /serviceauth
|
||||
service=junkservice"
|
||||
--- more_headers
|
||||
Cookie: border_session=MDEyMzQ1Njc4OTAxMjM0NQ**:1595116800:9Wc0CzZKO7Mq5Y2NbTaHrIp/gMg*
|
||||
Content-type: application/x-www-form-urlencoded
|
||||
|
||||
--- error_code: 400
|
||||
|
||||
=== TEST 3: test attempt to get service token with valid master token but missing service name
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location = /session {
|
||||
internal;
|
||||
set $memc_key $arg_id;
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
|
||||
location = /mastertoken {
|
||||
echo_status 400;
|
||||
echo_flush;
|
||||
}
|
||||
|
||||
location /serviceauth { # under test
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/service_token.lua';
|
||||
}
|
||||
|
||||
--- request eval
|
||||
"POST /serviceauth
|
||||
service=junkservice"
|
||||
--- more_headers
|
||||
Cookie: border_session=MDEyMzQ1Njc4OTAxMjM0NQ**:1595116800:9Wc0CzZKO7Mq5Y2NbTaHrIp/gMg*
|
||||
Content-type: application/x-www-form-urlencoded
|
||||
|
||||
--- error_code: 400
|
||||
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
require 'sinatra'
|
||||
require 'json'
|
||||
require "net/http"
|
||||
require "uri"
|
||||
|
||||
KEYMASTER_URI = 'http://localhost:9081/api/auth/service/v1/account_master_token.json'
|
||||
|
||||
|
||||
post '/' do
|
||||
$stderr.write "apiserver #{request.url}\n"
|
||||
service = request['service']
|
||||
username = request['username']
|
||||
password = request['password']
|
||||
|
||||
uri = URI.parse(KEYMASTER_URI)
|
||||
params = {'s'=> service, 'e' => username, 'p' => password}
|
||||
response = Net::HTTP.post_form(uri, params)
|
||||
$stderr.write "keymaster status was: #{response.code} response body was #{response.body}\n"
|
||||
if response.code == '200'
|
||||
$stderr.write "account service returning: #{response.body}\n"
|
||||
content_type :json
|
||||
response.body
|
||||
else
|
||||
$stderr.write "Unable to Authorize user!\n"
|
||||
halt 401, 'Unable to Authorize user!'
|
||||
end
|
||||
end
|
||||
|
||||
get '/' do
|
||||
$stderr.write "apiserver #{request.url}\n"
|
||||
haml :login, :content_type => 'text/html'
|
||||
end
|
||||
|
||||
get '/password' do
|
||||
$stderr.write "apiserver #{request.url}\n"
|
||||
haml :password, :content_type => 'text/html'
|
||||
end
|
||||
|
||||
__END__
|
||||
|
||||
@@ layout
|
||||
%html
|
||||
%head
|
||||
%title
|
||||
Account Service
|
||||
%body{:style => 'text-align: center'}
|
||||
= yield
|
||||
|
||||
@@ index
|
||||
%h1 Welcome to the Account Service!
|
||||
%a{:href => '/logout?destination=/b/'}
|
||||
logout
|
||||
|
||||
@@ loggedout
|
||||
%h1 Oops, You are not logged in.
|
||||
%a{:href => '/b/login'}
|
||||
login
|
||||
|
||||
@@ login
|
||||
%h1
|
||||
ACCOUNT SERVICE LOGIN
|
||||
|
||||
%form{:action => "/", :method => 'post'}
|
||||
%label
|
||||
Username
|
||||
%input{:name => "username", :type => "text", :value => "user@example.com"}
|
||||
%br/
|
||||
%label
|
||||
Password
|
||||
%input{:name => "password", :type => "password", :value => "password"}
|
||||
%br/
|
||||
%input{:name => "service", :type => "hidden", :value => "smb"}
|
||||
%input{:type => "submit", :name => "login", :value => "login"}
|
||||
|
||||
@@ password
|
||||
%h1
|
||||
THIS IS THE ACCOUNT MANAGEMENT PAGE
|
||||
|
||||
%form{:action => "/", :method => 'post'}
|
||||
%label
|
||||
Username
|
||||
%input{:name => "username", :type => "text", :value => "user@example.com"}
|
||||
%br/
|
||||
%label
|
||||
Password
|
||||
%input{:name => "password", :type => "password", :value => "password"}
|
||||
%br/
|
||||
%input{:name => "service", :type => "hidden", :value => "smb"}
|
||||
%input{:type => "submit", :name => "login", :value => "login"}
|
|
@ -0,0 +1,48 @@
|
|||
require 'sinatra'
|
||||
|
||||
["/", "/first/second"].each do |path|
|
||||
get path do
|
||||
token = request.env['HTTP_AUTH_TOKEN']
|
||||
$stderr.write "apiserver #{request.url} token = #{token}\n"
|
||||
|
||||
if token != 'LIVEKALESMB'
|
||||
halt 401, 'Ooops, request not authenticated. Did you login?'
|
||||
#haml :loggedout, :content_type => 'text/html'
|
||||
else
|
||||
haml :index, :content_type => 'text/html'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
get '/login' do
|
||||
$stderr.write "apiserver #{request.url}\n"
|
||||
haml :login, :content_type => 'text/html'
|
||||
end
|
||||
|
||||
get '/unrestricted' do
|
||||
'This is an unsecured resource.'
|
||||
end
|
||||
|
||||
get '/unrestricted/1' do
|
||||
'This is an unsecured resource.'
|
||||
end
|
||||
|
||||
__END__
|
||||
|
||||
@@ layout
|
||||
%html
|
||||
%head
|
||||
%title
|
||||
Device Login
|
||||
%body{:style => 'text-align: center'}
|
||||
= yield
|
||||
|
||||
@@ index
|
||||
%h1 Welcome to the First Server!
|
||||
%a{:href => '/logout?destination=/b/'}
|
||||
logout
|
||||
|
||||
@@ loggedout
|
||||
%h1 Oops, You are not logged in.
|
||||
%a{:href => '/b'}
|
||||
login
|
|
@ -0,0 +1,48 @@
|
|||
require 'sinatra'
|
||||
|
||||
["/", "/first/second"].each do |path|
|
||||
get path do
|
||||
token = request.env['HTTP_AUTH_TOKEN']
|
||||
$stderr.write "apiserver2 #{request.url} token = #{token}\n"
|
||||
|
||||
if token != 'LIVEKALEFLEXD'
|
||||
halt 401, 'Ooops, request not authenticated. Did you login?'
|
||||
#haml :loggedout, :content_type => 'text/html'
|
||||
else
|
||||
haml :index, :content_type => 'text/html'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
get '/login' do
|
||||
$stderr.write "apiserver2 #{request.url}\n"
|
||||
haml :login, :content_type => 'text/html'
|
||||
end
|
||||
|
||||
get '/unrestricted' do
|
||||
'This is an unsecured resource.'
|
||||
end
|
||||
|
||||
get '/unrestricted/1' do
|
||||
'This is an unsecured resource.'
|
||||
end
|
||||
|
||||
__END__
|
||||
|
||||
@@ layout
|
||||
%html
|
||||
%head
|
||||
%title
|
||||
Device Login
|
||||
%body{:style => 'text-align: center'}
|
||||
= yield
|
||||
|
||||
@@ index
|
||||
%h1 Welcome to the Second Server!
|
||||
%a{:href => '/logout?destination=/c/'}
|
||||
logout
|
||||
|
||||
@@ loggedout
|
||||
%h1 Oops, You are not logged in.
|
||||
%a{:href => '/c'}
|
||||
login
|
|
@ -0,0 +1,49 @@
|
|||
require 'sinatra'
|
||||
require 'json'
|
||||
|
||||
REQUIRED_PARAMS = [:e, :p, :s]
|
||||
|
||||
# There are 2 possible scenarios
|
||||
# Get a service token by itself
|
||||
# wget -S --post-data "e=user@example.com&p=password&s=smb" http://localhost:9081/api/auth/service/v1/account_master_token.json
|
||||
|
||||
# Get a list of service tokens (using a username and password)
|
||||
# wget -S --post-data "e=user@example.com&p=password&s=smb,flexd" http://localhost:9081/api/auth/service/v1/account_master_token.json
|
||||
post '/api/auth/service/v1/account_master_token.json' do
|
||||
$stderr.write "apiserver #{request.url} params = #{params}\n"
|
||||
REQUIRED_PARAMS.each do |r|
|
||||
(status 500 and return) unless params.include?(r.to_s)
|
||||
end
|
||||
|
||||
if params[:e] == 'user@example.com' && params[:p] == 'password'
|
||||
resp = build_tokens(params[:s])
|
||||
resp['auth_service'] = 'DEADBEEF'
|
||||
content_type :json
|
||||
resp.to_json
|
||||
else
|
||||
status 401
|
||||
end
|
||||
end
|
||||
|
||||
post '/api/auth/service/v1/account_token.json' do
|
||||
$stderr.write "authserver #{request.url}\n"
|
||||
$stderr.write "authserver master token #{request.env['HTTP_AUTH_TOKEN']}\n"
|
||||
(status 500 and return) unless params.include?('services')
|
||||
master_token = request.env['HTTP_AUTH_TOKEN']
|
||||
(status 401 and return) unless master_token == 'DEADBEEF'
|
||||
(status 401 and return) unless params[:services] !~ /auth_service/
|
||||
$stderr.write "authserver service = #{params.inspect}\n"
|
||||
resp = build_tokens(params[:services])
|
||||
$stderr.write "authserver RETURNING #{resp.inspect}\n"
|
||||
content_type :json
|
||||
resp.to_json
|
||||
end
|
||||
|
||||
def build_tokens(services)
|
||||
resp = {'service_tokens' => {}}
|
||||
$stderr.write resp
|
||||
services.split(',').map do |service_name|
|
||||
resp['service_tokens'][service_name] = "LIVEKALE#{service_name.upcase}"
|
||||
end
|
||||
resp
|
||||
end
|
|
@ -0,0 +1,128 @@
|
|||
use lib 'lib';
|
||||
use Test::Nginx::Socket;
|
||||
|
||||
$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;
|
||||
|
||||
plan tests => $Test::Nginx::Socket::RepeatEach * 2 * blocks();
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
|
||||
=== TEST 1: basic expiry
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
--- config
|
||||
location /exptime {
|
||||
echo 'flush_all';
|
||||
echo_location '/memc?cmd=flush_all';
|
||||
|
||||
echo 'set foo BAR';
|
||||
echo_subrequest PUT '/memc?key=foo&exptime=1' -b BAR;
|
||||
|
||||
echo 'get foo - 0 sec';
|
||||
echo_location '/memc?key=foo';
|
||||
echo;
|
||||
|
||||
echo_blocking_sleep 1.1;
|
||||
|
||||
echo 'get foo - 1.1 sec';
|
||||
echo_location '/memc?key=foo';
|
||||
}
|
||||
location /memc {
|
||||
echo_before_body "status: $echo_response_status";
|
||||
echo_before_body "exptime: $memc_exptime";
|
||||
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
set $memc_exptime $arg_exptime;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
--- request
|
||||
GET /exptime
|
||||
--- response_body_like
|
||||
^flush_all
|
||||
status: 200
|
||||
exptime:
|
||||
OK\r
|
||||
set foo BAR
|
||||
status: 201
|
||||
exptime: 1
|
||||
STORED\r
|
||||
get foo - 0 sec
|
||||
status: 200
|
||||
exptime:
|
||||
BAR
|
||||
get foo - 1\.1 sec
|
||||
status: 404
|
||||
exptime:
|
||||
<html>.*?404 Not Found.*$
|
||||
|
||||
=== TEST 2: set and reset exptime
|
||||
--- config
|
||||
location /exptime {
|
||||
echo 'flush_all';
|
||||
echo_location '/memc?cmd=flush_all';
|
||||
|
||||
echo 'set foo BAR';
|
||||
echo_subrequest PUT '/memc?key=foo&exptime=1' -b BAR;
|
||||
|
||||
echo 'get foo - 0 sec';
|
||||
echo_location '/memc?key=foo';
|
||||
echo;
|
||||
|
||||
echo 'set foo BAZ';
|
||||
echo_subrequest PUT '/memc?key=foo&exptime=2' -b BAR;
|
||||
|
||||
echo_blocking_sleep 1;
|
||||
|
||||
echo 'get foo - 1 sec';
|
||||
echo_location '/memc?key=foo';
|
||||
echo;
|
||||
|
||||
echo_blocking_sleep 1.1;
|
||||
|
||||
echo 'get foo - 2 sec';
|
||||
echo_location '/memc?key=foo';
|
||||
}
|
||||
location /memc {
|
||||
echo_before_body "status: $echo_response_status";
|
||||
echo_before_body "exptime: $memc_exptime";
|
||||
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
set $memc_exptime $arg_exptime;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
--- request
|
||||
GET /exptime
|
||||
--- response_body_like
|
||||
^flush_all
|
||||
status: 200
|
||||
exptime:
|
||||
OK\r
|
||||
set foo BAR
|
||||
status: 201
|
||||
exptime: 1
|
||||
STORED\r
|
||||
get foo - 0 sec
|
||||
status: 200
|
||||
exptime:
|
||||
BAR
|
||||
set foo BAZ
|
||||
status: 201
|
||||
exptime: 2
|
||||
STORED\r
|
||||
get foo - 1 sec
|
||||
status: 200
|
||||
exptime:
|
||||
BAR
|
||||
get foo - 2 sec
|
||||
status: 404
|
||||
exptime:
|
||||
<html>.*?404 Not Found.*$
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
use lib 'lib';
|
||||
use Test::Nginx::Socket;
|
||||
|
||||
$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;
|
||||
|
||||
repeat_each(1);
|
||||
|
||||
plan tests => repeat_each() * (2 * blocks()) - 1;
|
||||
|
||||
run_tests();
|
||||
|
||||
__DATA__
|
||||
|
||||
|
||||
=== TEST 1: test valid sessionid and valid auth token
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
init_by_lua 'service_mappings = {auth="smb", s="flexd"}';
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location = /setup {
|
||||
# clear
|
||||
echo_subrequest GET '/memc_setup?cmd=flush_all';
|
||||
echo_subrequest POST '/memc_setup?key=BP_LEASE' -b '1';
|
||||
echo_subrequest POST '/memc_setup?key=BPS1' -b 'mysecret:1595116800';
|
||||
echo_subrequest POST '/memc_setup?key=BPSID_MDEyMzQ1Njc4OTAxMjM0NQ**:1595116800:9Wc0CzZKO7Mq5Y2NbTaHrIp/gMg*' -b '{"auth_service": "aaa","service_tokens": {"smb": "bbb"}}';
|
||||
}
|
||||
location = /session {
|
||||
internal;
|
||||
set $memc_key $arg_id;
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
|
||||
location = /validate {
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/validate.lua';
|
||||
}
|
||||
|
||||
location /auth { # under test
|
||||
echo_location /setup;
|
||||
echo_location /validate;
|
||||
echo $echo_response_status;
|
||||
}
|
||||
--- request
|
||||
GET /auth
|
||||
--- more_headers
|
||||
Cookie: border_session=MDEyMzQ1Njc4OTAxMjM0NQ**:1595116800:9Wc0CzZKO7Mq5Y2NbTaHrIp/gMg*
|
||||
--- response_body_like
|
||||
OK\r
|
||||
STORED\r
|
||||
STORED\r
|
||||
STORED\r
|
||||
200
|
||||
|
||||
=== TEST 2: test valid sessionid and but no token stored
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location = /setup {
|
||||
# clear
|
||||
echo_subrequest GET '/memc_setup?cmd=flush_all';
|
||||
echo_subrequest POST '/memc_setup?key=BP_LEASE' -b '1';
|
||||
echo_subrequest POST '/memc_setup?key=BPS1' -b 'mysecret:1234567890';
|
||||
}
|
||||
location = /session {
|
||||
internal;
|
||||
set $memc_key $arg_id;
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
|
||||
location = /validate {
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/validate.lua';
|
||||
}
|
||||
|
||||
location /auth { # under test
|
||||
echo_location /setup;
|
||||
echo_location /validate;
|
||||
echo $echo_response_status;
|
||||
}
|
||||
--- request
|
||||
GET /auth
|
||||
--- more_headers
|
||||
Cookie: border_session=MDEyMzQ1Njc4OTAxMjM0NQ**:Mv+cEjtny9UIrLFYBKFKWQoBvPk*
|
||||
--- response_body_like
|
||||
OK\r
|
||||
STORED\r
|
||||
STORED\r
|
||||
.*401 Authorization Required.*
|
||||
|
||||
=== TEST 3: test missing sessionid
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
--- config
|
||||
location /auth { # under test
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/validate.lua';
|
||||
}
|
||||
--- request
|
||||
GET /auth
|
||||
--- error_code: 401
|
||||
|
||||
=== TEST 4: test valid sessionid and valid auth token
|
||||
--- main_config
|
||||
--- http_config
|
||||
lua_package_path "./build/usr/share/borderpatrol/?.lua;./build/usr/share/lua/5.1/?.lua;;";
|
||||
lua_package_cpath "./build/usr/lib/lua/5.1/?.so;;";
|
||||
--- config
|
||||
location /memc_setup {
|
||||
internal;
|
||||
set $memc_cmd $arg_cmd;
|
||||
set $memc_key $arg_key;
|
||||
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
location = /setup {
|
||||
# clear
|
||||
echo_subrequest GET '/memc_setup?cmd=flush_all';
|
||||
echo_subrequest POST '/memc_setup?key=BP_LEASE' -b '1';
|
||||
echo_subrequest POST '/memc_setup?key=BPS1' -b 'mysecret:1234567890';
|
||||
}
|
||||
location = /session {
|
||||
internal;
|
||||
set $memc_key $arg_id;
|
||||
memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
|
||||
}
|
||||
|
||||
location = /validate {
|
||||
content_by_lua_file '../../build/usr/share/borderpatrol/validate.lua';
|
||||
}
|
||||
|
||||
location /auth { # under test
|
||||
echo_location /setup;
|
||||
echo_location /validate;
|
||||
echo $echo_response_status;
|
||||
}
|
||||
--- request
|
||||
GET /auth
|
||||
--- more_headers
|
||||
Cookie: border_session==MTExMTExMTExMTExMTExMQ**:Mv+cEjtny9UIrLFYBKFKWQoBvPk*
|
||||
--- response_body_like
|
||||
OK\r
|
||||
STORED\r
|
||||
STORED\r
|
||||
.*401 Authorization Required.*
|
Loading…
Reference in New Issue