Announcement

A hardened runtime image for Kipchak

Mamluk · · 7 min read
A hardened runtime image for Kipchak

Kipchak Enterprise includes a runtime image: the container image in which a Kipchak API runs in production. It is built for organisations whose security requirements a general-purpose PHP image does not meet, such as those in regulated industries or with strict container policies.

The image contains two programs: FrankenPHP, built with PHP as a single static binary, and a static PHP command-line interface for console scripts. It has no shell, no package manager, no system libraries and no root user. It is built for PHP 8.4 and PHP 8.5, on 64-bit Intel and ARM servers.

What a general-purpose image contains

The free Kipchak images are based on Debian. They contain a shell, a package manager, compilers, and several hundred system packages, because they are designed to be extended: an API can install any extension or tool it needs. Each of those packages is software that must be patched when a vulnerability is found in it, and each program is one that an attacker who gains code execution in the container can use.

A production API uses very little of it. The runtime image removes everything that a running API does not use.

How the image is built

FrankenPHP and PHP are compiled into one static binary. PHP, its extensions, the Caddy web server and every library they depend on, including OpenSSL, curl and ICU, are compiled from source into a single executable. The binary loads no libraries at run time, so the image needs no C library and no other shared libraries.

Nothing is downloaded pre-built. Every library is compiled from its published source code in Mamluk’s build pipeline. No compiled code from a third party enters the binary.

The image is otherwise empty. It is built from scratch, the empty image, and contains the binary, a static PHP command-line interface for console scripts, the system’s certificate authorities, the PHP and web server configuration, and one unprivileged user. The image is approximately 310 MB, almost all of it the two static binaries. The free Kipchak FrankenPHP image is approximately 2 GB.

The web server has only what an API needs. Caddy is built without the Mercure and Vulcain modules that FrankenPHP includes by default. The administration interface is disabled, because the configuration of a running container does not change. The server serves plain HTTP and HTTP/2 behind a load balancer or ingress that terminates TLS, and it does not manage certificates.

How the image runs

Not as root. The server runs as an unprivileged user and listens on port 8080. It requires no Linux capabilities, and it runs under the most restrictive Kubernetes pod security settings: a non-root user, all capabilities dropped, privilege escalation disallowed, and the default seccomp profile.

On a read-only filesystem. The only directory the server writes to is /tmp, which is mounted separately. The rest of the filesystem, including the application’s code, can be mounted read-only.

With code that cannot change. The application’s code is owned by the root user and is read-only to the server, so a compromised process cannot modify it. PHP’s opcode cache does not check the files for changes, because they cannot change.

Without the means to run other programs. There is no shell, and no program in the image other than FrankenPHP and PHP. The PHP functions that start external programs, exec, shell_exec, system, passthru, popen, proc_open and pcntl_exec, are disabled. An attacker who gains code execution through a vulnerability in an application has no tools in the container to use.

What it includes

The image includes the PHP extensions that Kipchak and its drivers use, among them OPcache, APCu, the Memcached and Redis extensions for the Memcached and Valkey drivers, PDO for MySQL, PostgreSQL and SQLite, intl, GD, sodium and the YAML extension.

It also includes rdkafka, which the Kafka driver requires. The free Kipchak images do not include it, because it lengthens their build considerably and increases their size. In the runtime image it is part of the static binary, and adds nothing to the build of an API.

A second image accompanies each runtime image, for building an API. It contains the same binary, a PHP command-line interface with the same extensions, Composer and git. An API’s Dockerfile installs its dependencies in the build image and copies the result into the runtime image, so that Composer verifies the API’s requirements against exactly the extensions it will run with.

How it is kept current

A static binary does not receive security updates from an operating system. Updates are made by rebuilding the image.

  • PHP is rebuilt daily at the latest release of each supported version, so a PHP security release is in the image within a day of its publication.
  • Libraries and extensions are rebuilt weekly from their latest releases, so a security release of OpenSSL, curl or any other library is in the image within a week.
  • Each image records its contents: every extension and its version, and every source compiled into it, for use as a software bill of materials.

Container vulnerability scanners identify the Go modules in the binary, such as Caddy, but not the C libraries compiled into it. The weekly rebuild and the record in each image address this.

What does not work in it

The restrictions that make the image secure also mean that some things a general-purpose image allows are not possible.

  • Programs cannot be added. The image has no package manager and no shell, so apt-get and similar commands cannot be used to extend it. The Git driver, which runs the git program, does not work. Any library that starts an external program does not work, for the same reason. The Alpine variant, described below, exists for these cases.
  • Extensions cannot be added. Extensions are compiled into the binary. An API that needs an extension the image does not include requires a different build of the image.
  • There is no debugger. Xdebug is not included. Development continues to use the free development images.
  • There is no JIT compiler. PHP’s JIT is not available in this build of PHP. OPcache is.
  • The server listens on port 8080. An unprivileged user cannot listen on ports below 1024. The port is mapped by the container platform, as is usual in Kubernetes.
  • A running container cannot be entered with a shell. Diagnosis uses HTTP health checks, logs, and, in Kubernetes, an ephemeral debug container attached to the pod.

Command-line PHP scripts, such as database migrations and queue consumers, run with the static PHP command-line interface in the image, which needs no shell. It has the same extensions and settings as the server.

An Alpine variant, for APIs that need programs

Some APIs need a program in their image, such as git for the Git driver. For these, each runtime image has an Alpine variant. It contains the same binary, with the same settings and the same unprivileged user, on Alpine Linux, which adds a shell and a package manager. A program is added in the API’s Dockerfile:

FROM 1x.ax/mamluk/enterprise/php/8.5:franken-alpine

USER root
RUN apk add --no-cache git
USER 65532

In the Alpine variant, the PHP functions that start programs are enabled. The binary is static, so it uses nothing from Alpine, and the variant receives the same daily and weekly rebuilds as the runtime image.

The variant gives up part of what makes the runtime image secure: every package added is software that must be patched, and the shell is available to an attacker. It is intended for APIs that need a program, and the runtime image remains the recommended choice for all others. Extensions still cannot be added in the Alpine variant, because they are compiled into the binary.

Documentation

The runtime image documentation lists the images and extensions, and describes how to build an API on the runtime image, how to add programs with the Alpine variant, and the Kubernetes security settings the image supports.

Licensing

The runtime image is part of Kipchak Enterprise, and is covered by the Kipchak Enterprise terms. The open-source software it contains remains under its own licences.

Share
← Back to the Journal

More from the Journal