Kong error with custom javascript plugin

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:

  • Dockerfile
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:

  • Dockerfile
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 !!


© 2019 Kong Inc.    Terms  •  Privacy  •  FAQ