diff --git a/.forgejo/workflows/build-and-publish.yaml b/.forgejo/workflows/build-and-publish.yaml index 3a45988..399ccff 100644 --- a/.forgejo/workflows/build-and-publish.yaml +++ b/.forgejo/workflows/build-and-publish.yaml @@ -4,6 +4,7 @@ on: push: branches: - main + - dev jobs: build-and-push-arch: @@ -12,6 +13,15 @@ jobs: image: ghcr.io/catthehacker/ubuntu:act-latest options: -v /dind/docker.sock:/var/run/docker.sock steps: + - name: Set TAG variable + id: set_tag + run: | + if [ "${{ github.ref }}" = "refs/heads/main" ]; then + echo "TAG=latest" >> $GITHUB_ENV + else + echo "TAG=${{ github.ref_name }}" >> $GITHUB_ENV + fi + - name: Checkout the repo uses: actions/checkout@v4 @@ -31,4 +41,4 @@ jobs: context: src/ file: src/Dockerfile push: true - tags: guardianproject.dev/${{ github.repository }}:latest + tags: guardianproject.dev/${{ github.repository }}:${{ env.TAG }} diff --git a/src/default.conf b/src/default.conf index ae1607a..8bbfd57 100644 --- a/src/default.conf +++ b/src/default.conf @@ -8,7 +8,7 @@ lua_shared_dict auto_ssl_settings 64k; lua_ssl_trusted_certificate "/etc/ssl/certs/ca-certificates.crt"; -resolver 127.0.0.11 valid=60 ipv6=off; +resolver local=on valid=60 ipv6=off; init_by_lua_block { auto_ssl = (require "resty.auto-ssl").new() diff --git a/src/default.main b/src/default.main index c203206..6ceb234 100644 --- a/src/default.main +++ b/src/default.main @@ -1,4 +1,6 @@ env JASIMA_MATOMO_HOST; env JASIMA_PROXY_HOST; +env JASIMA_DEFAULT_ZONE; +env JASIMA_API_KEY; worker_processes auto; \ No newline at end of file diff --git a/src/lua/access.lua b/src/lua/access.lua index b304e67..40f073e 100644 --- a/src/lua/access.lua +++ b/src/lua/access.lua @@ -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 diff --git a/src/lua/api.lua b/src/lua/api.lua new file mode 100644 index 0000000..d42eeb0 --- /dev/null +++ b/src/lua/api.lua @@ -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 diff --git a/src/lua/body_filter.lua b/src/lua/body_filter.lua index 3acd201..5cf2033 100644 --- a/src/lua/body_filter.lua +++ b/src/lua/body_filter.lua @@ -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 diff --git a/src/lua/config.lua b/src/lua/config.lua index 9421484..fded9fd 100644 --- a/src/lua/config.lua +++ b/src/lua/config.lua @@ -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 diff --git a/src/lua/geo.lua b/src/lua/geo.lua index 0fed6c9..2988e6f 100644 --- a/src/lua/geo.lua +++ b/src/lua/geo.lua @@ -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 diff --git a/src/lua/header_filter.lua b/src/lua/header_filter.lua index d7c3d17..fba237b 100644 --- a/src/lua/header_filter.lua +++ b/src/lua/header_filter.lua @@ -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