Compare commits

...

5 commits

Author SHA1 Message Date
irl
130f6f88d2 ci: adds build and push for forgejo container registry
All checks were successful
Build and publish / build-and-push-arch (push) Successful in 34s
2025-05-26 13:45:51 +01:00
irl
a6ee2d702c temp: remove GB as safe country for testing 2025-05-25 19:19:52 +01:00
irl
2eee2b7cfc feat: api handler 2025-05-25 19:19:52 +01:00
irl
27bfe07065 feat: switch to using key to lookup config 2025-05-17 16:51:22 +01:00
irl
5fe0739635 fix: check for config before rewriting 2025-05-17 16:51:22 +01:00
8 changed files with 166 additions and 62 deletions

View file

@ -0,0 +1,35 @@
name: Build and publish
on:
push:
branches:
- main
- dev
jobs:
build-and-push-arch:
runs-on: docker
container:
image: ghcr.io/catthehacker/ubuntu:act-latest
options: -v /dind/docker.sock:/var/run/docker.sock
steps:
- name: Checkout the repo
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to the registry
uses: docker/login-action@v3
with:
registry: guardianproject.dev
username: irl
password: ${{ secrets.PACKAGE_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: src/
file: src/Dockerfile
push: true
tags: guardianproject.dev/${{ github.repository }}:latest

View file

@ -1,4 +1,6 @@
env JASIMA_MATOMO_HOST;
env JASIMA_PROXY_HOST;
env JASIMA_DEFAULT_ZONE;
env JASIMA_API_KEY;
worker_processes auto;

View file

@ -1,17 +1,24 @@
local api = require "api"
local config = require "config"
local geo = require "geo"
local psl = require "psl"
local utils = require "utils"
local jasima_host = config.get_jasima_host()
ngx.ctx.jasima_host = jasima_host
if not jasima_host then
ngx.log(ngx.DEBUG, "no host specified via header or cookie")
local headers = ngx.req.get_headers()
if headers["Jasima-Api-Key"] then
api.handle_api_request(headers["Jasima-Api-Key"])
end
local jasima_key = config.get_jasima_key()
ngx.ctx.jasima_key = jasima_key
if not jasima_key then
ngx.log(ngx.DEBUG, "no key specified via header or cookie")
return ngx.exit(400)
end
local err
ngx.ctx.jasima_config, err = config.load_config(jasima_host)
ngx.ctx.jasima_config, err = config.load_config(jasima_key)
if err then
ngx.status = 500
ngx.log(ngx.ERR, "could not load config: " .. err)
@ -24,13 +31,19 @@ if not ngx.ctx.jasima_config then
ngx.exit(403)
end
if not ngx.ctx.jasima_config.host_canonical then
ngx.log(ngx.DEBUG, "no origin host specified in config")
return ngx.exit(400)
end
ngx.ctx.jasima_host = ngx.ctx.jasima_config.host_canonical
local country = geo.viewer_country()
if not ngx.ctx.jasima_config.geo_redirect_disable then
ngx.log(ngx.DEBUG, "geo_redirect_disable not configured for site " .. jasima_host)
ngx.log(ngx.DEBUG, "geo_redirect_disable not configured for site " .. ngx.ctx.jasima_host)
if not geo.needs_mirror(country) then
ngx.log(ngx.DEBUG, "mirror is not needed for viewer in " .. country .. " for " .. jasima_host .. ", redirecting")
ngx.log(ngx.DEBUG, "mirror is not needed for viewer in " .. country .. " for " .. ngx.ctx.jasima_host .. ", redirecting")
local request_uri = ngx.var.request_uri
local new_url = "https://" .. jasima_host .. request_uri
local new_url = "https://" .. ngx.ctx.jasima_host .. request_uri
return ngx.redirect(new_url, ngx.HTTP_MOVED_TEMPORARILY)
end
end
@ -44,8 +57,6 @@ if err then
return ngx.exit(500)
end
local headers = ngx.req.get_headers()
-- Remove the headers that should not be proxied to the origin
for k, v in pairs(headers) do
if k:lower():match("^jasima%-") then
@ -60,9 +71,9 @@ if ngx.ctx.jasima_config.headers then
end
end
local host_connect = ngx.ctx.jasima_config.host_connect or jasima_host
local host_header = ngx.ctx.jasima_config.host_header or jasima_host
local host_ssl = ngx.ctx.jasima_config.host_ssl or jasima_host
local host_connect = ngx.ctx.jasima_config.host_connect or ngx.ctx.jasima_host
local host_header = ngx.ctx.jasima_config.host_header or ngx.ctx.jasima_host
local host_ssl = ngx.ctx.jasima_config.host_ssl or ngx.ctx.jasima_host
-- Handle first party Tealium installations
if ngx.ctx.jasima_config.first_party_tealium then
@ -105,7 +116,7 @@ if #ngx.ctx.upstream_ips == 0 then
end
-- Set the nginx host variables
ngx.var.jasima_host = jasima_host
ngx.var.jasima_host = ngx.ctx.jasima_host
ngx.var.jasima_host_connect = host_connect
ngx.var.jasima_host_header = host_header
ngx.var.jasima_host_ssl = host_ssl

47
src/lua/api.lua Normal file
View file

@ -0,0 +1,47 @@
local cjson = require "cjson.safe"
local redis = require "resty.redis"
local _M = {}
function _M.handle_api_request(api_key)
if api_key ~= os.getenv("JASIMA_API_KEY") then
ngx.exit(401)
end
if ngx.var.request_uri == "/update" then
ngx.req.read_body()
local body_data = ngx.req.get_body_data()
if not body_data then
ngx.status = 400
ngx.say("No request body")
ngx.exit(400)
end
local config = cjson.decode(body_data)
local red = redis:new()
red:set_timeout(1000)
red:set_keepalive(10000, 100)
local ok, err = red:connect("redis", 6379)
if not ok then return nil, "Redis connect failed: " .. err end
for jasima_key, rewrite_config in pairs(config.origins) do
local key = "jasima:config:" .. jasima_key
red:set(key, cjson.encode(rewrite_config))
end
for pool, poolmap in pairs(config.pools) do
local key = "jasima:poolmap:" .. pool
red:set(key, cjson.encode(poolmap))
end
if not config.pools.public then
-- This is a default and things break if we don't have it
red:set("jasima:poolmap:public", "{}")
end
ngx.exit(200)
end
ngx.exit(404)
end
return _M

View file

@ -52,22 +52,24 @@ local function rewrite_body(body)
return body
end
if ngx.ctx.rewriting then
local chunk = ngx.arg[1]
local eof = ngx.arg[2]
if ngx.ctx.jasima_config then
if ngx.ctx.rewriting then
local chunk = ngx.arg[1]
local eof = ngx.arg[2]
ngx.ctx.buffered = (ngx.ctx.buffered or "") .. (chunk or "")
ngx.ctx.buffered = (ngx.ctx.buffered or "") .. (chunk or "")
if #ngx.ctx.buffered > 5 * 1024 * 1024 and not eof then
-- Don't just consume memory forever
ngx.arg[1] = rewrite_body(ngx.ctx.buffered) -- We still do our best
ngx.ctx.rewriting = false
return
end
if #ngx.ctx.buffered > 5 * 1024 * 1024 and not eof then
-- Don't just consume memory forever
ngx.arg[1] = rewrite_body(ngx.ctx.buffered) -- We still do our best
ngx.ctx.rewriting = false
return
end
if eof then
ngx.arg[1] = rewrite_body(ngx.ctx.buffered)
else
ngx.arg[1] = nil
if eof then
ngx.arg[1] = rewrite_body(ngx.ctx.buffered)
else
ngx.arg[1] = nil
end
end
end

View file

@ -4,10 +4,18 @@ local redis = require "resty.redis"
local _M = {}
function _M.get_jasima_host()
function _M.get_jasima_key()
local headers = ngx.req.get_headers()
if headers["Jasima-Host"] then
return headers["Jasima-Host"]
if headers["Jasima-Key"] then
return headers["Jasima-Key"]
end
if headers["Host"] then
local host = headers["Host"]
local default_zone = os.getenv("JASIMA_DEFAULT_ZONE")
if string.sub(host, -string.len(default_zone)) == default_zone then
return string.sub(host, 1, string.len(host) - string.len(default_zone) - 1)
end
end
local cookie, err = ck:new()
@ -16,11 +24,9 @@ function _M.get_jasima_host()
return nil
end
local jasima_cookie, err = cookie:get("jasima_host")
local jasima_cookie, err = cookie:get("jasima_key")
if jasima_cookie then
return jasima_cookie
elseif err then
ngx.log(ngx.ERR, "failed to get jasima_host cookie: ", err)
end
return nil
@ -71,9 +77,9 @@ function _M.load_pool_mapping(pool_name)
return cjson.decode(res)
end
function _M.load_config(jasima_host)
function _M.load_config(jasima_key)
local cache = ngx.shared.jasima_cache
local cache_key = "config:" .. jasima_host
local cache_key = "config:" .. jasima_key
local cached = cache:get(cache_key)
if cached then return cjson.decode(cached) end
@ -82,7 +88,7 @@ function _M.load_config(jasima_host)
local ok, err = red:connect("redis", 6379)
if not ok then return nil, "Redis connect failed: " .. err end
local key = "jasima:config:" .. jasima_host
local key = "jasima:config:" .. jasima_key
local res, err = red:get(key)
if not res or res == ngx.null then return nil, "No config in Redis" end

View file

@ -18,7 +18,6 @@ function _M.needs_mirror(country)
local safe_countries = {
US = true, -- United States
GB = true, -- United Kingdom
IE = true, -- Ireland
FR = true, -- France
DE = true, -- Germany

View file

@ -1,29 +1,31 @@
local utils = require "utils"
if ngx.ctx.jasima_config.rewrite_disable then
return
end
if ngx.ctx.jasima_config then
if ngx.ctx.jasima_config.rewrite_disable then
return
end
if ngx.header["Content-Type"] then
local content_type = ngx.header["Content-Type"]:lower()
if content_type:find("text/html") or
content_type:find("text/css") or
content_type:find("text/xml") or
content_type:find("application/javascript") or
content_type:find("application/json") or
content_type:find("application/rss%+xml") or
content_type:find("application/atom%+xml") or
content_type:find("application/vnd%.mpegurl") or
content_type:find("application/x%-mpegurl") then
ngx.log(ngx.DEBUG, "Enabling rewrite due to content type " .. content_type)
ngx.ctx.rewriting = true
ngx.header["Content-Length"] = nil -- We're rewriting the body so this has the wrong value if set
end
end
if ngx.header["Location"] then
if ngx.ctx.jasima_pool_map then
local location = ngx.header["Location"]
ngx.header["Location"] = location:gsub("//([%a%d%.-]+%.[%a%d-]+)/+", utils.get_mirror)
if ngx.header["Content-Type"] then
local content_type = ngx.header["Content-Type"]:lower()
if content_type:find("text/html") or
content_type:find("text/css") or
content_type:find("text/xml") or
content_type:find("application/javascript") or
content_type:find("application/json") or
content_type:find("application/rss%+xml") or
content_type:find("application/atom%+xml") or
content_type:find("application/vnd%.mpegurl") or
content_type:find("application/x%-mpegurl") then
ngx.log(ngx.DEBUG, "Enabling rewrite due to content type " .. content_type)
ngx.ctx.rewriting = true
ngx.header["Content-Length"] = nil -- We're rewriting the body so this has the wrong value if set
end
end
if ngx.header["Location"] then
if ngx.ctx.jasima_pool_map then
local location = ngx.header["Location"]
ngx.header["Location"] = location:gsub("//([%a%d%.-]+%.[%a%d-]+)/+", utils.get_mirror)
end
end
end