Hi!
We need to implement a custom plugin in javascript. We followed the instructions here:
The second page tutorial is not working (docker-compose command fails with the current repo code).
Finally, we came up with this code:
FROM kong:2.5.0-alpine
USER root
RUN apk add --no-cache --update nodejs npm
RUN apk add --no-cache --virtual .gyp python3 make g++ \
&& npm -g config set user $USER && npm install kong-pdk -g \
&& apk del .gyp
docker-compose
kong:
build:
context: .
dockerfile: ./Dockerfile
user: "${KONG_USER:-kong}"
depends_on:
- db
environment:
KONG_ADMIN_ACCESS_LOG: /dev/stdout
KONG_ADMIN_ERROR_LOG: /dev/stderr
KONG_PROXY_LISTEN: "${KONG_PROXY_LISTEN:-0.0.0.0:8000}"
KONG_ADMIN_LISTEN: "${KONG_ADMIN_LISTEN:-0.0.0.0:8001}"
KONG_CASSANDRA_CONTACT_POINTS: db
KONG_DATABASE: postgres
KONG_PG_DATABASE: ${KONG_PG_DATABASE:-kong}
KONG_PG_HOST: db
KONG_PG_USER: ${KONG_PG_USER:-kong}
KONG_PROXY_ACCESS_LOG: /dev/stdout
KONG_PROXY_ERROR_LOG: /dev/stderr
KONG_PG_PASSWORD_FILE: /run/secrets/kong_postgres_password
KONG_PREFIX: ${KONG_PREFIX:-/var/run/kong}
# Enable the JS plugin server
KONG_PLUGINSERVER_NAMES: js
KONG_PLUGINSERVER_JS_SOCKET: /usr/local/kong/js_pluginserver.sock
KONG_PLUGINSERVER_JS_START_CMD: /usr/bin/kong-js-pluginserver -v --plugins-directory /usr/local/kong/js-plugins
KONG_PLUGINSERVER_JS_QUERY_CMD: /usr/bin/kong-js-pluginserver --plugins-directory /usr/local/kong/js-plugins --dump-all-plugins
# # Allow plugins to be used. The plugin name is your JS file name e.g. hello.js
KONG_PLUGINS: bundled, validator
secrets:
- kong_postgres_password
ports:
# The following two environment variables default to an insecure value (0.0.0.0)
# according to the CIS Security test.
- "${KONG_INBOUND_PROXY_LISTEN:-0.0.0.0}:8000:8000/tcp"
- "${KONG_INBOUND_SSL_PROXY_LISTEN:-0.0.0.0}:8443:8443/tcp"
# Making them mandatory but undefined, like so would be backwards-breaking:
# - "${KONG_INBOUND_PROXY_LISTEN?Missing inbound proxy host}:8000:8000/tcp"
# - "${KONG_INBOUND_SSL_PROXY_LISTEN?Missing inbound proxy ssl host}:8443:8443/tcp"
# Alternative is deactivating check 5.13 in the security bench, if we consider Kong's own config to be enough security here
- "127.0.0.1:8001:8001/tcp"
- "127.0.0.1:8444:8444/tcp"
healthcheck:
test: ["CMD", "kong", "health"]
interval: 10s
timeout: 10s
retries: 10
restart: on-failure:5
read_only: true
volumes:
- kong_prefix_vol:${KONG_PREFIX:-/var/run/kong}
- kong_tmp_vol:/tmp
- ./plugins:/usr/local/kong/js-plugins
deploy:
restart_policy:
delay: 50s
condition: on-failure
max_attempts: 5
window: 10s
resources:
limits:
cpus: '2'
memory: '2g'
security_opt:
- no-new-privileges
We have a folder called plugins where is located the validator.js file:
'use strict';
// This is an example plugin that adds a header to the response
class ValidatorPlugin {
constructor(config) {
this.config = config
}
async access(kong) {
let host = await kong.request.getHeader("host")
if (host === undefined) {
return await kong.log.err("unable to get header for request")
}
let message = this.config.message || "hello"
// the following can be "parallel"ed
await Promise.all([
kong.response.setHeader("x-hello-from-javascript", "Javascript validator says " + message + " to " + host),
])
}
}
module.exports = {
Plugin: ValidatorPlugin,
Schema: [
{ message: { type: "string" } },
],
Version: '0.1.0',
Priority: 0,
}
But when the container starts we get this error:
kong_1 | 2021/07/26 08:54:37 [notice] 32#0: *17 [kong] process.lua:270 external pluginserver 'js' terminated: exit 1, context: ngx.timer
kong_1 | 2021/07/26 08:54:38 [notice] 32#0: *17 [kong] process.lua:254 Starting js, context: ngx.timer
What are we missing?
Finally, we found the problem. It was related to this line in the docker-compose file:
read_only: true
That caused the container file system to be read-only, and the plugin server command:
/usr/bin/kong-js-pluginserver -v --plugins-directory /usr/local/kong/js-plugins
Failed with this error:
Error: listen EROFS: read-only file system /usr/local/kong/js_pluginserver.sock
at Server.setupListenHandle [as _listen2] (net.js:1301:21)
at listenInCluster (net.js:1366:12)
at Server.listen (net.js:1463:5)
at Listener.serve (/usr/lib/node_modules/kong-pdk/listener.js:94:16)
at Object.<anonymous> (/usr/lib/node_modules/kong-pdk/bin/kong-js-pluginserver:36:5)
at Module._compile (internal/modules/cjs/loader.js:1063:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
at Module.load (internal/modules/cjs/loader.js:928:32)
at Function.Module._load (internal/modules/cjs/loader.js:769:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
Once the line was removed Kong started properly, and the plugin was ready to be used.
Hello David,
I’m currently trying to create a Javascript plugin with Kong, but with no very much succcess.
It appears that your ‘docker-compose.yml’ is missing some informations ?
Could you please, update the file content ?
Likewise, do you know where the file ’ ```
js_pluginserver.sock
Thank you very much !
Gheorghe
Hi, Gheorghe!
The docker-compose file is correct I think, anyway, this is the current content of the file (removing some env variables)
# original https://github.com/Kong/docker-kong/blob/7afdded8e9dc30a1262f11292f4921154db0ba08/compose/docker-compose.yml
version: '3.8'
volumes:
kong_data: {}
kong_prefix_vol:
driver_opts:
type: tmpfs
device: tmpfs
kong_tmp_vol:
driver_opts:
type: tmpfs
device: tmpfs
services:
kong:
build:
context: .
dockerfile: ./Dockerfile
user: kong
depends_on:
- db
environment:
KONG_PROXY_LISTEN: 0.0.0.0:8000
KONG_ADMIN_LISTEN: 0.0.0.0:8001
KONG_PG_DATABASE: kong
KONG_PG_HOST: db
KONG_PG_USER: kong
KONG_PG_PASSWORD: kong
KONG_LOG_LEVEL: debug
KONG_ADMIN_USERNAME: adminuser
KONG_ADMIN_PASSWORD: password
KONG_ADMIN_CONSUMER_ID: dc2febff-c981-42ad-a268-ed1d110c9d6c
CACHE_NUMBER_OF_ITEMS: 1000
RATE_LIMIT_REQ_PER_MIN: 10000
ports:
- "0.0.0.0:8000:8000/tcp"
- "0.0.0.0:8443:8443/tcp"
- "127.0.0.1:8001:8001/tcp"
- "127.0.0.1:8444:8444/tcp"
healthcheck:
test: ["CMD", "kong", "health"]
interval: 60s
timeout: 10s
retries: 10
restart: on-failure:5
volumes:
- kong_prefix_vol:/usr/local/kong/run
- kong_tmp_vol:/tmp
- ./plugins:/usr/local/kong/plugins
- ./scripts/docker/kong:/scripts
- ./kong.yaml:/etc/kong/kong.yaml
- ./kong.conf:/etc/kong/kong.conf
deploy:
restart_policy:
delay: 50s
condition: on-failure
max_attempts: 5
window: 10s
resources:
limits:
cpus: '2'
memory: '2g'
security_opt:
- no-new-privileges
db:
image: postgres:13-alpine
environment:
POSTGRES_DB: kong
POSTGRES_USER: kong
POSTGRES_PASSWORD: kong
healthcheck:
test: ["CMD", "pg_isready", "-U", "kong"]
interval: 30s
timeout: 30s
retries: 3
restart: on-failure
ports:
- 5432:5432
deploy:
restart_policy:
condition: on-failure
stdin_open: true
tty: true
volumes:
- kong_data:/var/lib/postgresql/data
konga:
image: pantsel/konga
ports:
- 1337:1337
links:
- kong:kong
container_name: konga
environment:
- NODE_ENV=development
Anyway, in order to make it work, we also need more files:
FROM kong/kong:2.5.1
USER root
RUN apk add --no-cache --update nodejs npm gettext
RUN apk add --no-cache --virtual .gyp python3 make g++ \
&& npm -g config set user $USER && npm install kong-pdk -g
COPY --chown=kong ./kong.conf /etc/kong
COPY --chown=kong ./kong.yaml /etc/kong
USER kong
RUN mkdir -p /usr/local/kong/plugins
RUN mkdir -p /usr/local/kong/scripts
RUN mkdir -p /usr/local/kong/run
COPY --chown=kong ./plugins /usr/local/kong/plugins
RUN cd /usr/local/kong/plugins && npm install --only=production
COPY --chown=kong ./scripts/docker/kong /usr/local/kong/scripts
RUN chmod +x /usr/local/kong/scripts/docker-entrypoint.sh
EXPOSE 8000 8443 8001 8444
ENTRYPOINT ["/usr/local/kong/scripts/docker-entrypoint.sh"]
CMD ["kong", "docker-start"]
- Entry point script (docker-entrypoint.sh)
#!/usr/bin/env bash
set -Eeo pipefail
cd /usr/local/kong/plugins && npm install
cd /usr/local/bin
kong migrations bootstrap
kong migrations up
kong migrations finish
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
PREFIX=/usr/local/kong/run
file_env KONG_PG_PASSWORD
file_env KONG_PG_USER
file_env KONG_PG_DATABASE
if [[ "$2" == "docker-start" ]]; then
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
SALTED="${KONG_ADMIN_PASSWORD}${KONG_ADMIN_CONSUMER_ID}"
export KONG_ADMIN_HASHED_PASSWORD=$(printf "%s" "$SALTED" | openssl dgst -sha1 | awk '{print $NF}')
envsubst < /etc/kong/kong.yaml > /home/kong/kong-final.yaml
kong config db_import /home/kong/kong-final.yaml
exec /usr/local/openresty/nginx/sbin/nginx \
-p "$PREFIX" \
-c nginx.conf
fi
fi
exec "$@"
- kong.conf file with the global variables (that can be also set up in the docker-compose file). The file contents are:
prefix = /usr/local/kong/run
proxy_access_log = /dev/stdout
proxy_error_log = /dev/stderr
admin_access_log = /dev/stdout
admin_error_log = /dev/stderr
plugins = bundled,validator,api-key-auth,configure-rate-limits
pluginserver_names = js
pluginserver_js_socket = /usr/local/kong/js_pluginserver.sock
pluginserver_js_start_cmd = /usr/local/kong/plugins/node_modules/kong-pdk/bin/kong-js-pluginserver -v --plugins-directory /usr/local/kong/plugins
pluginserver_js_query_cmd = /usr/local/kong/plugins/node_modules/kong-pdk/bin/kong-js-pluginserver --plugins-directory /usr/local/kong/plugins --dump-all-plugins
lua_ssl_trusted_certificate = /etc/ssl/cert.pem
- kong.yaml with the initial import data (global plugins, services, routes, consumers…) this file is imported in the entry point script (kong config db_import…). If you don’t need it, you can remove that from the script and docker-compose file.
Hope this helps.
Thank you very much for your reply David !!