Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ curl -H "Content-Type: application/json" \
http://ceryx-api-host/api/routes/publicly.accessible.domain
```

### HTTPS redirects
### Enforce HTTPS

You can enforce redirection from HTTP to HTTPS for any host you would like.

Expand All @@ -117,6 +117,17 @@ curl -H "Content-Type: application/json" \

The above functionality works in `PUT` update requests as well.

### Redirect to target, instead of proxying

Instead of proxying the request to the targetm you can prompt the client to redirect the request there itself.

```
curl -H "Content-Type: application/json" \
-X POST \
-d '{"source":"sourcelair.com","target":"https://www.sourcelair.com", "settings": {"mode": "redirect"}}' \
http://ceryx-api-host/api/routes
```

## Ceryx web UI

The [Ceryx Web community project](https://github.com/parisk/ceryx-web) provides a sweet web UI
Expand Down
6 changes: 4 additions & 2 deletions api/ceryx/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ def encode_settings(settings):
Encode and sanitize settings in order to be written to Redis.
"""
encoded_settings = {
'enforce_https': str(int(settings.get('enforce_https', False)))
'enforce_https': str(int(settings.get('enforce_https', False))),
'mode': settings.get('mode', 'proxy'),
}

return encoded_settings
Expand All @@ -32,7 +33,8 @@ def decode_settings(settings):
_str(k): _str(v) for k, v in settings.items()
}
decoded = {
'enforce_https': bool(int(_settings.get('enforce_https', '0')))
'enforce_https': bool(int(_settings.get('enforce_https', '0'))),
'mode': _settings.get('mode', 'proxy'),
}

return decoded
Expand Down
33 changes: 17 additions & 16 deletions api/ceryx/types.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
from apistar import types, validators


SETTINGS_VALIDATOR = validators.Object(
properties={
'enforce_https': validators.Boolean(default=False),
'mode': validators.String(
default='proxy',
enum=['proxy', 'redirect'],
),
},
default={
'enforce_https': False,
'mode': 'proxy',
},
)


class RouteWithoutSource(types.Type):
target = validators.String()
settings = validators.Object(
properties={
'enforce_https': validators.Boolean(default=False),
},
default={
'enforce_https': False,
},
)
settings = SETTINGS_VALIDATOR


class Route(types.Type):
source = validators.String()
target = validators.String()
settings = validators.Object(
properties={
'enforce_https': validators.Boolean(default=False),
},
default={
'enforce_https': False,
},
)
settings = SETTINGS_VALIDATOR
63 changes: 63 additions & 0 deletions api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def test_create_route(self):
'target': 'localhost:11235',
'settings': {
'enforce_https': False,
'mode': 'proxy',
}
}

Expand All @@ -59,20 +60,23 @@ def test_enforce_https(self):
'target': 'localhost:11235',
'settings': {
'enforce_https': True,
'mode': 'proxy',
},
}
route_enforce_https_false = {
'source': 'test-enforce-https-false.dev',
'target': 'localhost:11235',
'settings': {
'enforce_https': False,
'mode': 'proxy',
},
}
expected_response_without_enforce_https = {
'source': 'test-no-enforce-https.dev',
'target': 'localhost:11235',
'settings': {
'enforce_https': False,
'mode': 'proxy',
},
}

Expand Down Expand Up @@ -109,6 +113,65 @@ def test_enforce_https(self):
response.json(), route_enforce_https_false,
)

def test_mode(self):
"""
Assert that creating a route with or without the `mode` setting returns
the expected results.
"""
route_without_mode = {
'source': 'www.my-website.dev',
'target': 'localhost:11235',
}
route_mode_proxy = {
'source': 'www.my-website.dev',
'target': 'localhost:11235',
'settings': {
'enforce_https': False,
'mode': 'proxy',
},
}
route_mode_redirect = {
'source': 'my-website.dev',
'target': 'www.my-website.dev',
'settings': {
'enforce_https': False,
'mode': 'redirect',
},
}

response = self.client.post('/api/routes', json=route_without_mode)
self.assertEqual(response.status_code, 201)
self.assertDictEqual(
response.json(), route_mode_proxy,
)

response = self.client.get('/api/routes/www.my-website.dev')
self.assertDictEqual(
response.json(), route_mode_proxy,
)

response = self.client.post('/api/routes', json=route_mode_proxy)
self.assertEqual(response.status_code, 201)
self.assertDictEqual(
response.json(), route_mode_proxy,
)

response = self.client.get('/api/routes/www.my-website.dev')
self.assertDictEqual(
response.json(), route_mode_proxy,
)

response = self.client.post('/api/routes', json=route_mode_redirect)
self.assertEqual(response.status_code, 201)
self.assertDictEqual(
response.json(), route_mode_redirect,
)

response = self.client.get('/api/routes/my-website.dev')
self.assertDictEqual(
response.json(), route_mode_redirect,
)

def test_delete_route(self):
"""
Assert that deleting a route, will actually delete it.
Expand Down
19 changes: 14 additions & 5 deletions ceryx/nginx/lualib/router.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ local host = ngx.var.host
local is_not_https = (ngx.var.scheme ~= "https")
local cache = ngx.shared.ceryx

function route(source, target)
function route(source, target, mode)
if mode == "redirect" then
ngx.log(ngx.INFO, "Redirecting request for " .. source .. " to " .. target .. ".")
return ngx.redirect(target, ngx.HTTP_MOVED_PERMANENTLY)
end

ngx.var.container_url = target
ngx.log(ngx.INFO, "Routing request for " .. source .. " to " .. target .. ".")
ngx.log(ngx.INFO, "Proxying request for " .. source .. " to " .. target .. ".")
end

local prefix = os.getenv("CERYX_REDIS_PREFIX")
Expand Down Expand Up @@ -41,8 +46,9 @@ if redis_password then
end
ngx.log(ngx.DEBUG, "Authenticated with Redis.")

local settings_key = prefix .. ":settings:" .. host

if is_not_https then
local settings_key = prefix .. ":settings:" .. host
local enforce_https, flags = cache:get(host .. ":enforce_https")

if enforce_https == nil then
Expand All @@ -56,11 +62,14 @@ if is_not_https then
end
end

-- Get routing mode
local mode, mode_flags = red:hget(settings_key, "mode")

-- Check if key exists in local cache
res, flags = cache:get(host)
if res then
ngx.log(ngx.DEBUG, "Cache hit for " .. host .. ".")
route(host, res)
route(host, res, mode)
else
ngx.log(ngx.DEBUG, "Cache miss for " .. host .. ".")

Expand All @@ -85,6 +94,6 @@ else
end

-- Save found key to local cache for 5 seconds
route(host, res)
route(host, res, mode)
cache:set(host, res, 5)
ngx.log(ngx.DEBUG, "Saving route from " .. host .. " to " .. res .. " in local cache for 5 seconds.")
16 changes: 14 additions & 2 deletions ceryx/tests/routes.bats
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,26 @@
[ $ceryx_status_code -eq $upstream_status_code ]
}

@test "301 response and appropriate 'Location' header when enforce_https=true" {
@test "301 response when enforce_https=true" {
curl -s -o /dev/null \
-X POST \
-H 'Content-Type: application/json' \
-d '{"source": "enforced-https-route", "target": "somewhere", "settings":{"enforce_https":true}}' \
-d '{"source": "enforced-https-route", "target": "somewhere", "settings":{"enforce_https": true}}' \
http://api:5555/api/routes/

status_code=$(curl -s -o /dev/null -I -w "%{http_code}" -H "Host: enforced-https-route" http://ceryx/)

[ $status_code -eq 301 ]
}

@test "301 response when mode=redirect" {
curl -s -o /dev/null \
-X POST \
-H 'Content-Type: application/json' \
-d '{"source": "redirected-route", "target": "redirection-target", "settings":{"mode": "redirect"}}' \
http://api:5555/api/routes/

status_code=$(curl -s -o /dev/null -I -w "%{http_code}" -H "Host: redirected-route" http://ceryx/)

[ $status_code -eq 301 ]
}