dunglas / frankenphp

🧟 The modern PHP app server
https://frankenphp.dev
MIT License
6.64k stars 218 forks source link

Running as a Non-Root User not working on SF 7 #607

Open kilwir76 opened 6 months ago

kilwir76 commented 6 months ago

What happened?

HERE THIS BUG WITH TRY Running as a Non-Root User

I try to change a user root on my docker frankenphp but isn't working. Let's see :

Building frankenphp Step 1/5 : FROM dunglas/frankenphp ---> f57655f0d144 Step 2/5 : ARG USER=www-data ---> Running in 375301e09d4f ---> Removed intermediate container 375301e09d4f ---> 55ddb50b9599 Step 3/5 : USER ${USER} ---> Running in eb7d0b3f58e6 ---> Removed intermediate container eb7d0b3f58e6 ---> ecb86f83c6f0 Step 4/5 : RUN adduser -D ${USER} setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp ---> Running in 5df2cbde730b Option d is ambiguous (debug, disabled-login, disabled-password) adduser [--uid id] [--firstuid id] [--lastuid id] [--gid id] [--firstgid id] [--lastgid id] [--ingroup group] [--add-extra-groups] [--shell shell] [--comment comment] [--home dir] [--no-create-home] [--allow-all-names] [--allow-bad-names] [--disabled-password] [--disabled-login] [--conf file] [--quiet] [--verbose] [--debug] user Add a normal user

adduser --system [--uid id] [--group] [--ingroup group] [--gid id] [--shell shell] [--comment comment] [--home dir] [--no-create-home] [--conf file] [--quiet] [--verbose] [--debug] user Add a system user

adduser --group [--gid ID] [--firstgid id] [--lastgid id] [--conf file] [--quiet] [--verbose] [--debug] group addgroup [--gid ID] [--firstgid id] [--lastgid id] [--conf file] [--quiet] [--verbose] [--debug] group Add a user group

addgroup --system [--gid id] [--conf file] [--quiet] [--verbose] [--debug] group Add a system group

adduser USER GROUP Add an existing user to an existing group ERROR: Service 'frankenphp' failed to build : The command '/bin/sh -c adduser -D ${USER} setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp' returned a non-zero code: 1

This is my dockerfile :

FROM dunglas/frankenphp

ARG USER=www-data USER ${USER}

RUN adduser -D ${USER} \

Caddy requires an additional capability to bind to port 80 and 443

setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp
# Caddy requires write access to /data/caddy and /config/caddy
RUN chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy

And my docker-compose.yaml : version: '3.9'

services:

> frankenphp

frankenphp:
    container_name: bff-iauto-frankenphp
    build:
        context: .
        dockerfile: dockerfile
    ports:
        - 80:80
        - 443:443
        - 443:443/udp
    volumes:
        - $PWD:/app
        - caddy_data:/data
        - caddy_config:/config
###< frankenphp ###

volumes: caddy_data: caddy_config:

Build Type

Official static build

Worker Mode

No

Operating System

GNU/Linux

CPU Architecture

x86_64

Relevant log output

No response

dunglas commented 6 months ago

Can you try to remove the -D, it doesn't look necessary on Alpine. If it works, could you update the docs, please?

kilwir76 commented 6 months ago

Ok but now i have this :

docker logs bff-iauto-frankenphp kev@kev {"level":"info","ts":1709142955.9294713,"msg":"using provided configuration","config_file":"/etc/caddy/Caddyfile","config_adapter":"caddyfile"} {"level":"warn","ts":1709142955.93021,"msg":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":16} {"level":"info","ts":1709142955.93076,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//localhost:2019","//[::1]:2019","//127.0.0.1:2019"]} {"level":"info","ts":1709142955.9309363,"logger":"http.auto_https","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv0","https_port":443} {"level":"info","ts":1709142955.9309967,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"} {"level":"info","ts":1709142955.931001,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc000525600"} {"level":"info","ts":1709142955.931198,"logger":"tls.cache.maintenance","msg":"stopped background certificate maintenance","cache":"0xc000525600"} Error: loading initial config: loading new config: loading http app module: provision http: loading pki app module: provision pki: provisioning CA 'local': loading root cert: open /data/caddy/pki/authorities/local/root.crt: permission denied


Its ok only with docker exec -it bash . We have www-data user , but after if i request i have this

withinboredom commented 6 months ago

You have to give whatever user you are using permission to read/write/access /data and /config

kilwir76 commented 6 months ago

here's what I've got so far

FROM dunglas/frankenphp

# add additional extensions here:
RUN install-php-extensions \
    pdo_mysql \
    pdo_pgsql \
    gd \
    intl \
    zip \
    bcmath \
    xdebug
    # opcache # disable for local development

# Install git
RUN apt-get update && apt-get install -y git

# Install symfony cli
RUN curl -1sLf 'https://dl.cloudsmith.io/public/symfony/stable/setup.deb.sh' | bash
RUN apt-get update && apt-get install -y symfony-cli

# Install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

ARG USER=www-data

RUN \
    # Use "adduser -D ${USER}" for alpine based distros
    useradd ${USER}; \
    # Add additional capability to bind to port 80 and 443
    setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
    # Give write access to /data/caddy and /config/caddy
    chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy;

USER ${USER}

COPY . /app
sneycampos commented 6 months ago

here's what I've got so far

COPY . /app

How are you solving the UID problem? if your host user has UID 1000 and you run the docker container, using volume, the files inside the container belongs to a user/group 1000, in my case. But the www-data user has UID 33

To solve this problem i'm creating a new user and group with the host UID 1000 (application:application)

image

@dunglas thanks for all your effort. In FrankenPHP can we use any environment variable to set the UID of the www-data user or may i create a new user/group with my host UID?

kilwir76 commented 6 months ago

can you share your code ? @sneycampos plz

sneycampos commented 6 months ago

Isn't much different of yours:

FROM dunglas/frankenphp

ARG USER=application
ARG UID=1000

RUN \
    # Add user and group
    groupadd -g ${UID} ${USER} && \
    useradd -u ${UID} -g ${USER} -m ${USER};

RUN \
    # Use "adduser -D ${USER}" for alpine based distros
#   useradd -D ${USER}; \
    # Add additional capability to bind to port 80 and 443
    setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
    # Give write access to /data/caddy and /config/caddy
    chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy;

USER ${USER}
withinboredom commented 6 months ago

How are you solving the UID problem? if your host user has UID 1000 and you run the docker container, using volume, the files inside the container belongs to a user/group 1000, in my case. But the www-data user has UID 33

Yes, volumes are always owned by whatever user created the files. This is how linux filesystems work, in general (it's literally a bind mount). So, if you mount a volume to a directory owned by your local user, then the files in the container will be owned by your local user because it was mounted there.

USER root # all files will be owned by root

COPY . .

USER www-data # all files will be owned by www-data

COPY . .

but mounting different files there and replacing them with files from your computer means they are whatever they are on your computer. There's no getting around this without fancy stuff.

If you are using pure docker, you can just change the process inside the container to match your computer (assuming it isn't privileged):

docker run --user $UID:$GID

if you are using docker compose:

service:
  image: my-image
  user: $UID:$GID

assuming $GID and $UID environment variables are set (they are set automatically in linux ... no idea if they are set on macs).

Starting docker (compose) this way ensures the process owns the files and is running as the same user as the host. It's no required, but it saves quite a bit of shenanigans.

sneycampos commented 6 months ago

if you are using docker compose:

service:
  image: my-image
  user: $UID:$GID

assuming $GID and $UID environment variables are set (they are set automatically in linux ... no idea if they are set on macs).

Starting docker (compose) this way ensures the process owns the files and is running as the same user as the host. It's no required, but it saves quite a bit of shenanigans.

I think it works only if the UID/GID in the host matches with the UID/GID in the container, for example: in OSX my UID/GID is 501:20 but inside the container doesn't exists any user with this UID, so it fails. I'll try in Ubuntu as soon as i can and give any feedback using an user with a UID different from 1000 (1001 for example in Hetzner's ARM instances)

So my question was if is possible to, using env vars, set the UID/GID of the www-data user or if is possible to have a non root user, like "application"

jmortlock commented 6 months ago
Error: loading initial config: loading new config: loading http app module: provision http: loading pki app module: provision pki: provisioning CA 'local': loading root cert: open /data/caddy/pki/authorities/local/root.crt: permission denied

I had a similar issue to this and although the permissions all looked good on that folder when manually logging into the starting it up as a as a non root would always fail.

Turns out the volume on the host had incorrect permissions, so i dropped the container volume and rebuilt it and all is working now.

You can check on your system

docker volume ls | grep caddy

Then inspect the volume

docker volume inspect <volume>

That should give you the host location and you can update permissions directly on that (or nuke it and start again with the correct docker user setup)

kilwir76 commented 6 months ago

even if I change with it:


FROM dunglas/frankenphp

ARG USER=www-data
ARG UID=1000

# add additional extensions here:
RUN install-php-extensions \
    @composer \
    pdo_mysql \
    pdo_pgsql \
    gd \
    intl \
    zip \
    bcmath \
    xdebug
    # opcache # disable for local development

# WORKDIR /app

# Install git
RUN apt-get update && apt-get install -y git

RUN apt-get install -y libnss3-tools

# Install symfony cli
RUN curl -1sLf 'https://dl.cloudsmith.io/public/symfony/stable/setup.deb.sh' | bash
RUN apt-get update && apt-get install -y symfony-cli

# Install composer
# RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
ENV COMPOSER_ALLOW_SUPERUSER=1

# Copy the current directory contents into the container at /app    
COPY . /app

RUN composer install

# RUN \
#     # Add user and group
#     groupadd -g ${UID} ${USER} && \
#     useradd -u ${UID} -g ${USER} -m ${USER};

RUN \
    # Use "adduser -D ${USER}" for alpine based distros
#   useradd -D ${USER}; \
    # Add additional capability to bind to port 80 and 443
    setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
    # Give write access to /data/caddy and /config/caddy
    chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy;

USER ${USER}

RUN echo "END 🐘🐳🐧🚀"
`

I have this as an error that tells me I'm not root, which is logical but annoying.
sneycampos commented 6 months ago

Could you type in your terminal echo $UID to confirm your UID is 1000? Then enter in your container and type echo $UID too. The www-data user isn't with UID 1000 probably

kilwir76 commented 6 months ago

image

interose commented 4 months ago

Hi, I try to get it running in a open shift environment. There is this concept of arbitrary user id's. Therefore I adjusted the folders recursively with the following commands:

RUN chgrp -R 0 /app/var/cache && chmod -R g=u /app/var/cache && chmod -R a+rw /app/var/cache
RUN chgrp -R 0 /app/var/log && chmod -R g=u /app/var/log && chmod -R a+rw /app/var/log
RUN chgrp -R 0 /data && chmod -R g=u /data && chmod -R a+rw /data
RUN chgrp -R 0 /config && chmod -R g=u /config && chmod -R a+rw /config

Additionally I added the following command:

RUN setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp;

I opened a debug shell and I can create, write, read files within the above mentioned folders. The command getcap returns the following result:

1000700000@frontend-76866bd7df-d9x68-debug-lhff9:/usr/local/bin$ getcap frankenphp
frankenphp cap_net_bind_service=eip

Unfortunately I always get the result "Operation not permitted" whenever I try to execute the frankenphp binary.

Has anyone some hint which resources the binary is also needing in order to work as a non-root user?

EDIT: Perhaps I found the answer .... "In addition, the processes running in the container must not listen on privileged ports (ports below 1024), since they are not running as a privileged user." https://docs.openshift.com/container-platform/4.2/openshift_images/create-images.html#use-uid_create-images

withinboredom commented 4 months ago

Unfortunately I always get the result "Operation not permitted" whenever I try to execute the frankenphp binary.

Openshift and K8s simply do not care what capabilities the process has inside the container. Instead, you have to use the provided tools (from k8s/open shift) to grant the privilege to the associated kernel namespace (pod security policies in k8s).

sneycampos commented 4 months ago

Has anyone some hint which resources the binary is also needing in order to work as a non-root user?

Maybe a workaround could be change the http/https ports to a non provileged ports, like 8080 and 8443 and using a reverse proxy in front of, or (if possible to use docker port mappings, like 80:8080 and 443:8443) so you don't need to give root permissins, capacities, etc or anything else to frankenphp

interose commented 4 months ago

Has anyone some hint which resources the binary is also needing in order to work as a non-root user?

Maybe a workaround could be change the http/https ports to a non provileged ports, like 8080 and 8443 and using a reverse proxy in front of, or (if possible to use docker port mappings, like 80:8080 and 443:8443) so you don't need to give root permissins, capacities, etc or anything else to frankenphp

Hi, yes I already thought about that and regarding the open shift doc it is not possible to bind something to privileged ports. So I definetely have to use other ports than 80, 443. On the other hand it is even not possible to get the command frankenphp -v working. And at this point I assume no port binding is executed.

So therefore I'm afraid I have to use a classic nginx/php-fpm approach here.

dunglas commented 4 months ago

@interose how could we reproduce the issue? Is deploying a non-root image in a Kubernetes cluster is enough to trigger the problem? If that's the case, I can take a look.

interose commented 4 months ago

@interose how could we reproduce the issue? Is deploying a non-root image in a Kubernetes cluster is enough to trigger the problem? If that's the case, I can take a look.

@dunglas I have created a bare minimum Dockerfile

FROM dunglas/frankenphp

RUN chgrp -R 0 /data && chmod -R g=u /data && chmod -R a+rw /data
RUN chgrp -R 0 /config && chmod -R g=u /config && chmod -R a+rw /config
RUN chgrp -R 0 /etc/caddy && chmod -R g=u /etc/caddy && chmod -R a+rw /etc/caddy

ENTRYPOINT ["tail", "-f", "/dev/null"]

created a image via docker build -t frankenphp_test . and pushed it into the open shift registry. If I create the container locally everything is working fine. It's this special environment (Openshift / Kubernetes) where it is not working, to be more precise, a simple call to frankenphp -v gives me the following result: (tried also a php -v)

1000700000@frontend-654fdccc96-rm28j:/usr/local/bin$ ./php -v
PHP 8.3.6 (cli) (built: Apr 11 2024 18:05:35) (ZTS)
Copyright (c) The PHP Group
Zend Engine v4.3.6, Copyright (c) Zend Technologies
1000700000@frontend-654fdccc96-rm28j:/usr/local/bin$ ./frankenphp -v
bash: ./frankenphp: Operation not permitted
1000700000@frontend-654fdccc96-rm28j:/usr/local/bin$

So I guess without access to this environment it would be hard to reproduce the issue and to be honest - the more I dig into this (Openshift / Kubernetes) the more I get the feeling this is an edge case here. I also used gdb, strace, ... in order to maybe get some information why it gives me "Operation not permitted" but with no luck. So I definitely could investigate a little bit further if there are some things which I could test here.

withinboredom commented 4 months ago

@dunglas I am able to also reproduce in regular k8s even though it isn't binding to a lower port (afaik).

dunglas commented 4 months ago

Could you do a ls -l ./frankenphp, it looks like the binary itself doesn't have the good permissions.

withinboredom commented 4 months ago

It looks like the issue is here: https://github.com/dunglas/frankenphp/blob/main/Dockerfile#L93

This requires that the associated kernel namespace also have the requisite permissions. Removing this line means you can use high ports in k8s (and probably openshift) without also needing NET_BIND.

interose commented 4 months ago

Could you do a ls -l ./frankenphp, it looks like the binary itself doesn't have the good permissions.

It was 755 (like the other executables within /usr/local/bin). To rule that out I changed it to 777 -> same result.

interose commented 4 months ago

OMG - I noticed I had the wrong dir within one statement - sbin instead of bin. With this

FROM dunglas/frankenphp

RUN chgrp -R 0 /usr/local/bin && chmod -R g=u /usr/local/bin

ENTRYPOINT ["tail", "-f", "/dev/null"]

at least the command frankenphp -v and frankenphp -h works as expected. Sorry, sorry for that.

Now I'm going to test the whole story.

jhonatanhulse commented 3 weeks ago

Ok but now i have this :

docker logs bff-iauto-frankenphp kev@kev {"level":"info","ts":1709142955.9294713,"msg":"using provided configuration","config_file":"/etc/caddy/Caddyfile","config_adapter":"caddyfile"} {"level":"warn","ts":1709142955.93021,"msg":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":16} {"level":"info","ts":1709142955.93076,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//localhost:2019","//[::1]:2019","//127.0.0.1:2019"]} {"level":"info","ts":1709142955.9309363,"logger":"http.auto_https","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv0","https_port":443} {"level":"info","ts":1709142955.9309967,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"} {"level":"info","ts":1709142955.931001,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc000525600"} {"level":"info","ts":1709142955.931198,"logger":"tls.cache.maintenance","msg":"stopped background certificate maintenance","cache":"0xc000525600"} Error: loading initial config: loading new config: loading http app module: provision http: loading pki app module: provision pki: provisioning CA 'local': loading root cert: open /data/caddy/pki/authorities/local/root.crt: permission denied

Its ok only with docker exec -it bash . We have www-data user , but after if i request i have this

@kilwir76 I'm not sure if this helps you but I was facing a similar issue and deleting the volumes caddy_data and caddy_config fixed my problem. I deleted them with docker volume rm and restarted the services.