diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..766f09f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM kong:3.1.1-ubuntu + +USER root +ADD scripts /build +RUN /build/build.sh + +USER kong + +ENTRYPOINT ["/docker-entrypoint.sh"] + +EXPOSE 8000 8443 8001 8444 $EE_PORTS + +STOPSIGNAL SIGQUIT + +HEALTHCHECK --interval=10s --timeout=10s --retries=10 CMD kong health + +CMD ["kong", "docker-start"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..c52cf5f --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# kong31-wallarm + +Docker image with Kong 3.1.1-ubuntu and Wallarm 4.6 - https://hub.docker.com/r/ebberst/kong31-wallarm + +Example of execution: +``` +docker run -it --rm --name kong \ + -e 'KONG_DATABASE=off' \ + -e 'KONG_PROXY_ACCESS_LOG=/dev/stdout' \ + -e 'KONG_ADMIN_ACCESS_LOG=/dev/stdout' \ + -e 'KONG_PROXY_ERROR_LOG=/dev/stderr' \ + -e 'KONG_ADMIN_ERROR_LOG=/dev/stderr' \ + -e 'KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl' \ + -e 'KONG_DECLARATIVE_CONFIG_STRING={"_format_version":"1.1", "services":[{"host":"mockbin.com","port":443,"protocol":"https", "routes":[{"paths":["/"]}]}]}' \ + -e 'WALLARM_API_HOST=api.wallarm.com' \ + -e 'WALLARM_API_TOKEN=' \ + -e 'WALLARM_LABELS=group=' \ + -e 'TARANTOOL_MEMORY_GB=1' \ + -e 'WALLARM_MODE=block' \ + -p 8000:8000 \ + -p 8443:8443 \ + -p 8001:8001 \ + -p 8444:8444 \ + ebberst/kong31-wallarm:latest +``` diff --git a/build_push.sh b/build_push.sh new file mode 100755 index 0000000..2dc5f54 --- /dev/null +++ b/build_push.sh @@ -0,0 +1,5 @@ +set -ex + +docker build --pull \ + --tag ebberst/kong31-wallarm:latest . +docker push ebberst/kong31-wallarm:latest diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..5c5569d --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -ex + +/build/busybox wget https://meganode.wallarm.com/4.6/wallarm-4.6.0.x86_64-glibc.tar.gz -O - | tar -xzv -C / +chown -R kong:kong /opt/wallarm + +cp -v /build/docker-entrypoint.sh /docker-entrypoint.sh +cp -v /build/nginx.lua /usr/local/share/lua/5.1/kong/templates/nginx.lua +cp -v /build/nginx_kong.lua /usr/local/share/lua/5.1/kong/templates/nginx_kong.lua +chown -R kong:kong /usr/local/share/lua/5.1/kong/templates +sed -i -e '/HOST=0\.0\.0\.0/d' /opt/wallarm/env.list + +rm -rf /build diff --git a/scripts/busybox b/scripts/busybox new file mode 100755 index 0000000..14626ba Binary files /dev/null and b/scripts/busybox differ diff --git a/scripts/docker-entrypoint.sh b/scripts/docker-entrypoint.sh new file mode 100755 index 0000000..9c5cfbb --- /dev/null +++ b/scripts/docker-entrypoint.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -Eeo pipefail + +# usage: file_env VAR [DEFAULT] +# ie: file_env 'XYZ_DB_PASSWORD' 'example' +# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of +# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) +file_env() { + local var="$1" + local fileVar="${var}_FILE" + local def="${2:-}" + # Do not continue if _FILE env is not set + if ! [ "${!fileVar:-}" ]; then + return + elif [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + local val="$def" + if [ "${!var:-}" ]; then + val="${!var}" + elif [ "${!fileVar:-}" ]; then + val="$(< "${!fileVar}")" + fi + export "$var"="$val" + unset "$fileVar" +} + +export KONG_NGINX_DAEMON=${KONG_NGINX_DAEMON:=off} + +if [[ "$1" == "kong" ]]; then + + all_kong_options="/usr/local/share/lua/5.1/kong/templates/kong_defaults.lua" + set +Eeo pipefail + while IFS='' read -r LINE || [ -n "${LINE}" ]; do + opt=$(echo "$LINE" | grep "=" | sed "s/=.*$//" | sed "s/ //" | tr '[:lower:]' '[:upper:]') + file_env "KONG_$opt" + done < $all_kong_options + set -Eeo pipefail + + file_env KONG_PASSWORD + PREFIX=${KONG_PREFIX:=/usr/local/kong} + + if [[ "$2" == "docker-start" ]]; then + if [ -n "$WALLARM_MODE" ]; then + sed -i -e "s|wallarm_mode monitoring|wallarm_mode $WALLARM_MODE|g" /usr/local/share/lua/5.1/kong/templates/nginx_kong.lua + fi + kong prepare -p "$PREFIX" "$@" + + ln -sf /dev/stdout $PREFIX/logs/access.log + ln -sf /dev/stdout $PREFIX/logs/admin_access.log + ln -sf /dev/stderr $PREFIX/logs/error.log + + if [ -n "$WALLARM_API_HOST" ]; then + args="$args -H $WALLARM_API_HOST" + fi + if [ -n "$WALLARM_LABELS" ]; then + args="$args --labels $WALLARM_LABELS" + fi + if [ -n "$TARANTOOL_MEMORY_GB" ]; then + sed -i -e "s|SLAB_ALLOC_ARENA=0.2|SLAB_ALLOC_ARENA=$TARANTOOL_MEMORY_GB|g" /opt/wallarm/env.list + fi + /opt/wallarm/register-node $args + /opt/wallarm/supervisord.sh & + + exec /usr/local/openresty/nginx/sbin/nginx \ + -p "$PREFIX" \ + -c nginx.conf + fi +fi + +exec "$@" diff --git a/scripts/nginx.lua b/scripts/nginx.lua new file mode 100644 index 0000000..ca6f5ae --- /dev/null +++ b/scripts/nginx.lua @@ -0,0 +1,76 @@ +return [[ +pid pids/nginx.pid; +error_log ${{PROXY_ERROR_LOG}} ${{LOG_LEVEL}}; + +# injected nginx_main_* directives +> for _, el in ipairs(nginx_main_directives) do +$(el.name) $(el.value); +> end + +> if database == "off" then +lmdb_environment_path ${{LMDB_ENVIRONMENT_PATH}}; +lmdb_map_size ${{LMDB_MAP_SIZE}}; +> end + +load_module /opt/wallarm/modules/kong/ngx_http_wallarm_module.so; + +events { + # injected nginx_events_* directives +> for _, el in ipairs(nginx_events_directives) do + $(el.name) $(el.value); +> end +} + +> if role == "control_plane" or #proxy_listeners > 0 or #admin_listeners > 0 or #status_listeners > 0 then +http { + server { + listen 127.0.0.8:80; + + server_name localhost; + + allow 127.0.0.0/8; + deny all; + + wallarm_mode off; + disable_acl "on"; + access_log off; + + location ~/wallarm-status$ { + wallarm_status on; + } + } + disable_acl "on"; + include 'nginx-kong.conf'; +} +> end + +> if #stream_listeners > 0 or cluster_ssl_tunnel then +stream { +> if #stream_listeners > 0 then + include 'nginx-kong-stream.conf'; +> end + +> if cluster_ssl_tunnel then + server { + listen unix:${{PREFIX}}/cluster_proxy_ssl_terminator.sock; + + proxy_pass ${{cluster_ssl_tunnel}}; + proxy_ssl on; + # as we are essentially talking in HTTPS, passing SNI should default turned on + proxy_ssl_server_name on; +> if proxy_server_ssl_verify then + proxy_ssl_verify on; +> if lua_ssl_trusted_certificate_combined then + proxy_ssl_trusted_certificate '${{LUA_SSL_TRUSTED_CERTIFICATE_COMBINED}}'; +> end + proxy_ssl_verify_depth 5; # 5 should be sufficient +> else + proxy_ssl_verify off; +> end + proxy_socket_keepalive on; + } +> end -- cluster_ssl_tunnel + +} +> end +]] diff --git a/scripts/nginx_kong.lua b/scripts/nginx_kong.lua new file mode 100644 index 0000000..47e499e --- /dev/null +++ b/scripts/nginx_kong.lua @@ -0,0 +1,460 @@ +return [[ +charset UTF-8; +server_tokens off; + +error_log ${{PROXY_ERROR_LOG}} ${{LOG_LEVEL}}; + +lua_package_path '${{LUA_PACKAGE_PATH}};;'; +lua_package_cpath '${{LUA_PACKAGE_CPATH}};;'; +lua_socket_pool_size ${{LUA_SOCKET_POOL_SIZE}}; +lua_socket_log_errors off; +lua_max_running_timers 4096; +lua_max_pending_timers 16384; +lua_ssl_verify_depth ${{LUA_SSL_VERIFY_DEPTH}}; +> if lua_ssl_trusted_certificate_combined then +lua_ssl_trusted_certificate '${{LUA_SSL_TRUSTED_CERTIFICATE_COMBINED}}'; +> end + +lua_shared_dict kong 5m; +lua_shared_dict kong_locks 8m; +lua_shared_dict kong_healthchecks 5m; +lua_shared_dict kong_process_events 5m; +lua_shared_dict kong_cluster_events 5m; +lua_shared_dict kong_rate_limiting_counters 12m; +lua_shared_dict kong_core_db_cache ${{MEM_CACHE_SIZE}}; +lua_shared_dict kong_core_db_cache_miss 12m; +lua_shared_dict kong_db_cache ${{MEM_CACHE_SIZE}}; +lua_shared_dict kong_db_cache_miss 12m; +> if role == "data_plane" then +lua_shared_dict wrpc_channel_dict 5m; +> end +> if database == "cassandra" then +lua_shared_dict kong_cassandra 5m; +> end + +underscores_in_headers on; +> if ssl_ciphers then +ssl_ciphers ${{SSL_CIPHERS}}; +> end + +# injected nginx_http_* directives +> for _, el in ipairs(nginx_http_directives) do +$(el.name) $(el.value); +> end + +init_by_lua_block { + Kong = require 'kong' + Kong.init() +} + +init_worker_by_lua_block { + Kong.init_worker() +} + +exit_worker_by_lua_block { + Kong.exit_worker() +} + +> if (role == "traditional" or role == "data_plane") and #proxy_listeners > 0 then +# Load variable indexes +lua_kong_load_var_index default; + +upstream kong_upstream { + server 0.0.0.1; + + # injected nginx_upstream_* directives +> for _, el in ipairs(nginx_upstream_directives) do + $(el.name) $(el.value); +> end + + balancer_by_lua_block { + Kong.balancer() + } +} + +server { + server_name kong; +> for _, entry in ipairs(proxy_listeners) do + listen $(entry.listener); +> end + wallarm_mode monitoring; + wallarm_application -1; + disable_acl "off"; + + error_page 400 404 405 408 411 412 413 414 417 494 /kong_error_handler; + error_page 500 502 503 504 /kong_error_handler; + + access_log ${{PROXY_ACCESS_LOG}}; + error_log ${{PROXY_ERROR_LOG}} ${{LOG_LEVEL}}; + +> if proxy_ssl_enabled then +> for i = 1, #ssl_cert do + ssl_certificate $(ssl_cert[i]); + ssl_certificate_key $(ssl_cert_key[i]); +> end + ssl_session_cache shared:SSL:10m; + ssl_certificate_by_lua_block { + Kong.ssl_certificate() + } +> end + + # injected nginx_proxy_* directives +> for _, el in ipairs(nginx_proxy_directives) do + $(el.name) $(el.value); +> end +> for _, ip in ipairs(trusted_ips) do + set_real_ip_from $(ip); +> end + + rewrite_by_lua_block { + Kong.rewrite() + } + + access_by_lua_block { + Kong.access() + } + + header_filter_by_lua_block { + Kong.header_filter() + } + + body_filter_by_lua_block { + Kong.body_filter() + } + + log_by_lua_block { + Kong.log() + } + + location / { + default_type ''; + + set $ctx_ref ''; + set $upstream_te ''; + set $upstream_host ''; + set $upstream_upgrade ''; + set $upstream_connection ''; + set $upstream_scheme ''; + set $upstream_uri ''; + set $upstream_x_forwarded_for ''; + set $upstream_x_forwarded_proto ''; + set $upstream_x_forwarded_host ''; + set $upstream_x_forwarded_port ''; + set $upstream_x_forwarded_path ''; + set $upstream_x_forwarded_prefix ''; + set $kong_proxy_mode 'http'; + + proxy_http_version 1.1; + proxy_buffering on; + proxy_request_buffering on; + + proxy_set_header TE $upstream_te; + proxy_set_header Host $upstream_host; + proxy_set_header Upgrade $upstream_upgrade; + proxy_set_header Connection $upstream_connection; + proxy_set_header X-Forwarded-For $upstream_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $upstream_x_forwarded_proto; + proxy_set_header X-Forwarded-Host $upstream_x_forwarded_host; + proxy_set_header X-Forwarded-Port $upstream_x_forwarded_port; + proxy_set_header X-Forwarded-Path $upstream_x_forwarded_path; + proxy_set_header X-Forwarded-Prefix $upstream_x_forwarded_prefix; + proxy_set_header X-Real-IP $remote_addr; + proxy_pass_header Server; + proxy_pass_header Date; + proxy_ssl_name $upstream_host; + proxy_ssl_server_name on; +> if client_ssl then + proxy_ssl_certificate ${{CLIENT_SSL_CERT}}; + proxy_ssl_certificate_key ${{CLIENT_SSL_CERT_KEY}}; +> end + proxy_pass $upstream_scheme://kong_upstream$upstream_uri; + } + + location @unbuffered { + internal; + default_type ''; + set $kong_proxy_mode 'unbuffered'; + + proxy_http_version 1.1; + proxy_buffering off; + proxy_request_buffering off; + + proxy_set_header TE $upstream_te; + proxy_set_header Host $upstream_host; + proxy_set_header Upgrade $upstream_upgrade; + proxy_set_header Connection $upstream_connection; + proxy_set_header X-Forwarded-For $upstream_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $upstream_x_forwarded_proto; + proxy_set_header X-Forwarded-Host $upstream_x_forwarded_host; + proxy_set_header X-Forwarded-Port $upstream_x_forwarded_port; + proxy_set_header X-Forwarded-Path $upstream_x_forwarded_path; + proxy_set_header X-Forwarded-Prefix $upstream_x_forwarded_prefix; + proxy_set_header X-Real-IP $remote_addr; + proxy_pass_header Server; + proxy_pass_header Date; + proxy_ssl_name $upstream_host; + proxy_ssl_server_name on; +> if client_ssl then + proxy_ssl_certificate ${{CLIENT_SSL_CERT}}; + proxy_ssl_certificate_key ${{CLIENT_SSL_CERT_KEY}}; +> end + proxy_pass $upstream_scheme://kong_upstream$upstream_uri; + } + + location @unbuffered_request { + internal; + default_type ''; + set $kong_proxy_mode 'unbuffered'; + + proxy_http_version 1.1; + proxy_buffering on; + proxy_request_buffering off; + + proxy_set_header TE $upstream_te; + proxy_set_header Host $upstream_host; + proxy_set_header Upgrade $upstream_upgrade; + proxy_set_header Connection $upstream_connection; + proxy_set_header X-Forwarded-For $upstream_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $upstream_x_forwarded_proto; + proxy_set_header X-Forwarded-Host $upstream_x_forwarded_host; + proxy_set_header X-Forwarded-Port $upstream_x_forwarded_port; + proxy_set_header X-Forwarded-Path $upstream_x_forwarded_path; + proxy_set_header X-Forwarded-Prefix $upstream_x_forwarded_prefix; + proxy_set_header X-Real-IP $remote_addr; + proxy_pass_header Server; + proxy_pass_header Date; + proxy_ssl_name $upstream_host; + proxy_ssl_server_name on; +> if client_ssl then + proxy_ssl_certificate ${{CLIENT_SSL_CERT}}; + proxy_ssl_certificate_key ${{CLIENT_SSL_CERT_KEY}}; +> end + proxy_pass $upstream_scheme://kong_upstream$upstream_uri; + } + + location @unbuffered_response { + internal; + default_type ''; + set $kong_proxy_mode 'unbuffered'; + + proxy_http_version 1.1; + proxy_buffering off; + proxy_request_buffering on; + + proxy_set_header TE $upstream_te; + proxy_set_header Host $upstream_host; + proxy_set_header Upgrade $upstream_upgrade; + proxy_set_header Connection $upstream_connection; + proxy_set_header X-Forwarded-For $upstream_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $upstream_x_forwarded_proto; + proxy_set_header X-Forwarded-Host $upstream_x_forwarded_host; + proxy_set_header X-Forwarded-Port $upstream_x_forwarded_port; + proxy_set_header X-Forwarded-Path $upstream_x_forwarded_path; + proxy_set_header X-Forwarded-Prefix $upstream_x_forwarded_prefix; + proxy_set_header X-Real-IP $remote_addr; + proxy_pass_header Server; + proxy_pass_header Date; + proxy_ssl_name $upstream_host; + proxy_ssl_server_name on; +> if client_ssl then + proxy_ssl_certificate ${{CLIENT_SSL_CERT}}; + proxy_ssl_certificate_key ${{CLIENT_SSL_CERT_KEY}}; +> end + proxy_pass $upstream_scheme://kong_upstream$upstream_uri; + } + + location @grpc { + internal; + default_type ''; + set $kong_proxy_mode 'grpc'; + + grpc_set_header TE $upstream_te; + grpc_set_header X-Forwarded-For $upstream_x_forwarded_for; + grpc_set_header X-Forwarded-Proto $upstream_x_forwarded_proto; + grpc_set_header X-Forwarded-Host $upstream_x_forwarded_host; + grpc_set_header X-Forwarded-Port $upstream_x_forwarded_port; + grpc_set_header X-Forwarded-Path $upstream_x_forwarded_path; + grpc_set_header X-Forwarded-Prefix $upstream_x_forwarded_prefix; + grpc_set_header X-Real-IP $remote_addr; + grpc_pass_header Server; + grpc_pass_header Date; + grpc_ssl_name $upstream_host; + grpc_ssl_server_name on; +> if client_ssl then + grpc_ssl_certificate ${{CLIENT_SSL_CERT}}; + grpc_ssl_certificate_key ${{CLIENT_SSL_CERT_KEY}}; +> end + grpc_pass $upstream_scheme://kong_upstream; + } + + location = /kong_buffered_http { + internal; + default_type ''; + set $kong_proxy_mode 'http'; + + rewrite_by_lua_block {;} + access_by_lua_block {;} + header_filter_by_lua_block {;} + body_filter_by_lua_block {;} + log_by_lua_block {;} + + proxy_http_version 1.1; + proxy_set_header TE $upstream_te; + proxy_set_header Host $upstream_host; + proxy_set_header Upgrade $upstream_upgrade; + proxy_set_header Connection $upstream_connection; + proxy_set_header X-Forwarded-For $upstream_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $upstream_x_forwarded_proto; + proxy_set_header X-Forwarded-Host $upstream_x_forwarded_host; + proxy_set_header X-Forwarded-Port $upstream_x_forwarded_port; + proxy_set_header X-Forwarded-Path $upstream_x_forwarded_path; + proxy_set_header X-Forwarded-Prefix $upstream_x_forwarded_prefix; + proxy_set_header X-Real-IP $remote_addr; + proxy_pass_header Server; + proxy_pass_header Date; + proxy_ssl_name $upstream_host; + proxy_ssl_server_name on; +> if client_ssl then + proxy_ssl_certificate ${{CLIENT_SSL_CERT}}; + proxy_ssl_certificate_key ${{CLIENT_SSL_CERT_KEY}}; +> end + proxy_pass $upstream_scheme://kong_upstream$upstream_uri; + } + + location = /kong_error_handler { + internal; + default_type ''; + + uninitialized_variable_warn off; + + rewrite_by_lua_block {;} + access_by_lua_block {;} + + content_by_lua_block { + Kong.handle_error() + } + } +} +> end -- (role == "traditional" or role == "data_plane") and #proxy_listeners > 0 + +> if (role == "control_plane" or role == "traditional") and #admin_listeners > 0 then +server { + server_name kong_admin; +> for _, entry in ipairs(admin_listeners) do + listen $(entry.listener); +> end + + access_log ${{ADMIN_ACCESS_LOG}}; + error_log ${{ADMIN_ERROR_LOG}} ${{LOG_LEVEL}}; + +> if admin_ssl_enabled then +> for i = 1, #admin_ssl_cert do + ssl_certificate $(admin_ssl_cert[i]); + ssl_certificate_key $(admin_ssl_cert_key[i]); +> end + ssl_session_cache shared:AdminSSL:10m; +> end + + # injected nginx_admin_* directives +> for _, el in ipairs(nginx_admin_directives) do + $(el.name) $(el.value); +> end + + location / { + default_type application/json; + content_by_lua_block { + Kong.admin_content() + } + header_filter_by_lua_block { + Kong.admin_header_filter() + } + } + + location /robots.txt { + return 200 'User-agent: *\nDisallow: /'; + } +} +> end -- (role == "control_plane" or role == "traditional") and #admin_listeners > 0 + +> if #status_listeners > 0 then +server { + server_name kong_status; +> for _, entry in ipairs(status_listeners) do + listen $(entry.listener); +> end + + access_log ${{STATUS_ACCESS_LOG}}; + error_log ${{STATUS_ERROR_LOG}} ${{LOG_LEVEL}}; + +> if status_ssl_enabled then +> for i = 1, #status_ssl_cert do + ssl_certificate $(status_ssl_cert[i]); + ssl_certificate_key $(status_ssl_cert_key[i]); +> end + ssl_session_cache shared:StatusSSL:1m; +> end + + # injected nginx_status_* directives +> for _, el in ipairs(nginx_status_directives) do + $(el.name) $(el.value); +> end + + location / { + default_type application/json; + content_by_lua_block { + Kong.status_content() + } + header_filter_by_lua_block { + Kong.status_header_filter() + } + } + + location /robots.txt { + return 200 'User-agent: *\nDisallow: /'; + } +} +> end + +> if role == "control_plane" then +server { + server_name kong_cluster_listener; +> for _, entry in ipairs(cluster_listeners) do + listen $(entry.listener) ssl; +> end + + access_log ${{ADMIN_ACCESS_LOG}}; + error_log ${{ADMIN_ERROR_LOG}} ${{LOG_LEVEL}}; + +> if cluster_mtls == "shared" then + ssl_verify_client optional_no_ca; +> else + ssl_verify_client on; + ssl_client_certificate ${{CLUSTER_CA_CERT}}; + ssl_verify_depth 4; +> end + ssl_certificate ${{CLUSTER_CERT}}; + ssl_certificate_key ${{CLUSTER_CERT_KEY}}; + ssl_session_cache shared:ClusterSSL:10m; + + location = /v1/wrpc { + content_by_lua_block { + Kong.serve_wrpc_listener() + } + } + +} +> end -- role == "control_plane" + +> if not legacy_worker_events then +server { + server_name kong_worker_events; + listen unix:${{PREFIX}}/worker_events.sock; + access_log off; + location / { + content_by_lua_block { + require("resty.events.compat").run() + } + } +} +> end -- not legacy_worker_events +]]