diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3f97155 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +/tmp +/tools +/.trivy +/docs \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..791232c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/tmp +/tools +/.trivy \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b7dc222 --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +IMAGE_NAME := reg.cadoles.com/cadoles/sp +DOCKERFILE ?= + +DAY_SUFFIX_TAG ?= $(shell date +%Y%m%d) + +build: + +_build: + docker \ + build \ + -t "$(IMAGE_NAME):$(IMAGE_TAG)" \ + -f $(DOCKERFILE) \ + . + +scan: + +_scan: tools/trivy/bin/trivy + mkdir -p .trivy/$(IMAGE_NAME)/$(IMAGE_TAG) + tools/trivy/bin/trivy --cache-dir .trivy/.cache image -o ".trivy/$(IMAGE_NAME)/$(IMAGE_TAG)/report.txt" $(TRIVY_ARGS) $(IMAGE_NAME):$(IMAGE_TAG) + cat ".trivy/$(IMAGE_NAME)/$(IMAGE_TAG)/report.txt" + +tools/trivy/bin/trivy: + mkdir -p tools/trivy/bin + curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b ./tools/trivy/bin v0.27.1 + + +release: + +_release: + docker tag $(IMAGE_NAME):$(IMAGE_TAG) $(IMAGE_NAME):$(IMAGE_TAG)-$(DAY_SUFFIX_TAG) + docker push $(IMAGE_NAME):$(IMAGE_TAG)-$(DAY_SUFFIX_TAG) + docker push $(IMAGE_NAME):$(IMAGE_TAG) + +_test: tools/bin/bash_unit + tools/bin/bash_unit ./tests/test_$(IMAGE_TAG).sh + +tools/bin/bash_unit: + mkdir -p tools/bin + cd tools/bin && bash <(curl -s https://raw.githubusercontent.com/pgrange/bash_unit/master/install.sh) + +include recipes/*.mk diff --git a/README.md b/README.md index b73b5f4..89871f6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,19 @@ -# sp-containers +======= +# Shibboleth and OIDC ServiceProvider containers based on "apache". -Shibboleth and OIDC ServiceProvider containers based on "apache". \ No newline at end of file +Recettes de construction d'image de conteneurs Symfony prêts à l'emploi pour le développement ou la production. + +Inspiré de [`sherifabdlnaby/kubephp`](https://github.com/sherifabdlnaby/kubephp) + +## Liste des images + +|Image|Description| +|-----|-----------| +|`reg.cadoles.com/cadoles/sp:alpine-oidc-base`|Alpine 3.18, Apache 2.4, mod-auth-oidc| +|`reg.cadoles.com/cadoles/sp:debian-shib-base`|Debian 11.7, Aapache 2.4, shibboleth-sp 3.2.2, php 7.4, php-fpm | + +[Accéder au dépôt](https://reg.cadoles.com/harbor/projects/5/repositories/sp/artifacts-tab) +## Documentation + +[Voir la documentation](./docs/README.md) +>>>>>>> c2e23db (feat(init): first commit) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..943058c --- /dev/null +++ b/docs/README.md @@ -0,0 +1,13 @@ +# Documentation + +## Tutoriels + +- [Créer une image pour votre projet Symfony](./docker-usage.md) +- [Utilisation avec Kubernetes](./kubernetes-usage.md) +- [Créer une application avec docker-compose](./starting-with-docker-compose.md) + +## Comment faire pour ... ? + +- [Installer des paquets supplémentaires](./additional-packages.md) +- [Contextualiser la configuration](./configuration-contextualization.md) +- [Utiliser une version spécifique de NodeJS](./customize_nodejs_version.md) \ No newline at end of file diff --git a/docs/additional-packages.md b/docs/additional-packages.md new file mode 100644 index 0000000..5e83e77 --- /dev/null +++ b/docs/additional-packages.md @@ -0,0 +1,12 @@ +# Installer des paquets supplémentaires + +Vous pouvez spécifier des paquets supplémentaires (par exemple des dépendances PHP) en ajoutant l'argument de construction `ADDITIONAL_PACKAGES`. + +**Exemple** + +``` +docker build \ + -t my-symfony-app:latest \ + --build-arg "ADDITIONAL_PACKAGES=vim=9.0.0999-r0 curl=7.87.0-r0" \ + . +``` \ No newline at end of file diff --git a/docs/configuration-contextualization.md b/docs/configuration-contextualization.md new file mode 100644 index 0000000..5cd4f80 --- /dev/null +++ b/docs/configuration-contextualization.md @@ -0,0 +1,19 @@ +# Contextualisation la configuration + +Les images intègrent l'utilitaire [`gomplate`](https://docs.gomplate.ca/) par défaut. + +Ce dernier est utilisé au démarrage du conteneur afin de compléter les fichiers prévus à cet effet avec les données de contexte. Cette opération est réalisée par le script [`files/common/scripts/run.sh`](../files/common/scripts/run.sh). + +Le principe général est le suivant: + +1. Au démarrage du conteneur: + 1. Trouver tous les fichiers de l'arborescence correspondant dont le nom termine par `/..gotmpl` + 2. Générer le fichier `/.` via `gomplate` + +> Par exemple, le fichier `/etc/nginx/nginx.conf.gotmpl` sera automatiquement transformé en `/etc/nginx/nginx.conf` au démarrage du conteneur. + +Cette opération s'appliquent également aux fichiers ajoutés par les images étendant les images de base, ainsi que dans les volumes. + +Une grande partie des fichiers de configuration par défaut des images sont prévus pour être compatibles avec ce mécanisme. Vous pouvez donc personnaliser une partie de leurs attributs via des variables d'environnement. + +> [Voir par exemple le fichier `files/common/nginx/nginx.conf.gotmpl`](../files/common/nginx/nginx.conf.gotmpl). \ No newline at end of file diff --git a/docs/customize-nodejs-version.md b/docs/customize-nodejs-version.md new file mode 100644 index 0000000..bb2f16a --- /dev/null +++ b/docs/customize-nodejs-version.md @@ -0,0 +1,9 @@ +# Utiliser une version spécifique de NodeJS + +Les images intègrent l'outil [nvm](https://github.com/nvm-sh/nvm) par défaut. Cet outil permet d'installer une version spécifique de NodeJS. + +Pour personnaliser la version de NodeJS installée dans l'image de votre conteneur, créez un fichier `.nvmrc` à la racine de votre projet Symfony contenant la version de NodeJS souhaitée (par exemple: `lts/hydrogen` ou `v18.13.0`). + +La liste des versions disponibles est visible via la commande `nvm ls-remote`. + +(Plus d'informations sur le site du projet NVM](https://github.com/nvm-sh/nvm#nvmrc) \ No newline at end of file diff --git a/docs/docker-usage.md b/docs/docker-usage.md new file mode 100644 index 0000000..d283e25 --- /dev/null +++ b/docs/docker-usage.md @@ -0,0 +1,102 @@ +# Créer une image Docker pour votre projet Symfony + +> ⚠ La procédure suivante s'applique dans le cadre d'un déploiement sur Docker. +> +> Pour créer une image à destination d'un environnement Kubernetes, préférer +> la procédure ["Usage dans Kubernetes"](./kubernetes-usage.md) + +## Création d'une image + +1. Dans le répertoire de votre projet, créer le fichier `Dockerfile` suivant: + + ```Dockerfile + ARG ADDITIONAL_PACKAGES="" + + FROM reg.cadoles.com/cadoles/symfony:alpine-php-8.1-standalone + ``` + + > La variable d'environnement `ADDITIONAL_PACKAGES` permet de définir une liste de paquets supplémentaires à installer avant d'exécuter les "triggers" de base embaqrués par l'image. Voir ["Installer des paquets supplémentaires"](./additional-packages.md) pour plus d'information. + +2. Créer le fichier `.dockerignore` avec les données suivantes: + + ```text + /vendor + /var + ``` + +3. Construire l'image de votre application + + ```sh + docker build \ + -t my-symfony-app:latest \ + . + ``` + +4. Exécuter l'image de votre application + + ```sh + docker run \ + -it \ + --rm \ + -p 8080:8080 \ + my-symfony-app:latest + ``` + +5. Ouvrir l'URL http://localhost:8080. Votre application devrait s'afficher. + +## Utilisation de votre image en développement + +1. **Avec docker**: + + ```sh + docker run \ + -it --rm \ + -p 8080:8080 \ + -v "${PWD}/src:/app/src:delegated" \ + -v "${PWD}/templates:/app/templates:delegated" \ + -v "${PWD}/translations:/app/translations:delegated" \ + -v "${PWD}/tests:/app/tests:delegated" \ + -v "${PWD}/config:/app/config:delegated" \ + -v "${PWD}/.env:/app/.env:delegated" \ + test-symfony-app + ``` + +2. **Avec docker-compose**: + 1. Créer un fichier `Dockerfile.standalone` à la racine de votre projet: + + ```dockerfile + ARG ADDITIONAL_PACKAGES="" + + FROM reg.cadoles.com/cadoles/symfony:alpine-php-8.1-standalone + ``` + + 2. Créer un fichier `docker-compose.yml`: + + ```yaml + version: '3' + services: + + app: + build: + context: . + dockerfile: Dockerfile.standalone + ports: + - ${APP_HTTP_PORT:-8080}:8080 + volumes: + - ./src:/app/src:delegated + - ./templates:/app/templates:delegated + - ./translations:/app/translations:delegated + - ./tests:/app/tests:delegated + - ./config:/app/config:delegated + - ./.env:/app/.env:delegated + environment: + PHP_FPM_MEMORY_LIMIT: 128m + APP_ENV: dev + ## truncated for brevity + ``` + + 3. Démarrer l'environnement + + ```sh + docker-compose up + ``` diff --git a/docs/kubernetes-usage.md b/docs/kubernetes-usage.md new file mode 100644 index 0000000..df18509 --- /dev/null +++ b/docs/kubernetes-usage.md @@ -0,0 +1,3 @@ +# Utilisation avec Kubernetes + +> `TODO` \ No newline at end of file diff --git a/docs/starting-with-docker-compose.md b/docs/starting-with-docker-compose.md new file mode 100644 index 0000000..6eb6aea --- /dev/null +++ b/docs/starting-with-docker-compose.md @@ -0,0 +1,115 @@ +# Créer un projet en utilisant docker-compose + +> ⚠ La procédure suivante s'applique dans le cadre du développement d'une application +> en utilisant l'image ["standalone"](./docker-usage.md). + +## Création du projet symfony + +1. **Création du projet** + +```shell +symfony new app --webapp [other options...] +# voir https://symfony.com/download pour l'installation de symfony CLI +``` + +2. **Création du ficher Dockerfile** +A la racine du projet, créer le fichier `Dockerfile` et placer ce contenu: +```dockerfile +ARG ADDITIONAL_PACKAGES="php81-pdo=8.1.14-r0 php81-pdo_pgsql=8.1.14-r0" + +FROM reg.cadoles.com/cadoles/symfony:alpine-php-8.1-standalone +``` +**Notes**: adapter la liste des paquets additionnels à votre besoin. + +3. **Création du fichier docker-compose** +A la racine du projet, créer le fichier (ou remplacer celui existant)`docker-compose.yml`, supprimer le fichier `docker-compose.override.yml` et placer ce contenu: +```yaml +version: '3' +services: + + app: + build: + context: . + dockerfile: Dockerfile + volumes: + - ./:/app:delegated + environment: + PHP_FPM_MEMORY_LIMIT: 128m + APP_ENV: dev + DATABASE_URL: postgresql://${POSTGRES_PASSWORD:-app}:${POSTGRES_PASSWORD:-app}@db:5432/${POSTGRES_DB:-app}?serverVersion=15&chartset=utf8 + ports: + - ${APP_HTTP_PORT:-8080}:8080 + + db: + image: postgres:${POSTGRES_VERSION:-15}-alpine + restart: unless-stopped + environment: + POSTGRES_DB: ${POSTGRES_DB:-app} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-app} + POSTGRES_USER: ${POSTGRES_USER:-app} + volumes: + - app-db-data:/var/lib/postgresql/data:rw + +volumes: + app-db-data: +``` +4. Création du fichier `Makefile` +A la racine du projet, créer le fichier `Makefile` et placer ce contenu: +```make +dc := docker-compose +de := $(dc) exec +dr := $(dc) run --rm +app := $(dr) --no-deps app + +.DEFAULT_GOAL := help +.PHONY: help +help: ## Affiche cette aide + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.PHONY: dev +dev: vendor/autoload.php ## Lance le serveur de développement + $(dc) up + +# ----------------------------------- +# Utilitaires +# ----------------------------------- +.PHONY: php +app: ## Se connecte au conteneur PHP + $(app) /bin/sh + +# ----------------------------------- +# Builds +# ----------------------------------- +.PHONY: build +build: ## build application image + docker build \ + -t app-standalone:latest \ + . + +# ----------------------------------- +# Dependencies +# ----------------------------------- +vendor/autoload.php: composer.lock + $(app) composer install + touch vendor/autoload.php + +``` + +## Démarrer l'environnement + +```shell +cd app/ +make +# doit afficher la liste des commandes disponible +make dev +# doit démarrer l'environnement docker défini dans votre docker-compose.yml +# l'application doit être disponible à l'adresse http://localhost:8080 +``` + +## Installer des dépendances `composer` + +```shell +make app +# dans le conteneur +composer require .... +``` \ No newline at end of file diff --git a/files/alpine/sp-oidc/base/Dockerfile b/files/alpine/sp-oidc/base/Dockerfile new file mode 100644 index 0000000..75466f2 --- /dev/null +++ b/files/alpine/sp-oidc/base/Dockerfile @@ -0,0 +1,14 @@ +FROM reg.cadoles.com/proxy_cache/library/alpine:edge +#FROM reg.cadoles.com/proxy_cache/library/httpd:alpine3.18 + +# Adding testing repo +RUN echo "https://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories + +RUN apk update && apk add apache-mod-auth-openidc + +COPY files/alpine/sp-oidc/base/conf.d/mod-auth-openidc.conf /etc/apache2/conf.d/mod-auth-openidc.conf +COPY files/alpine/sp-oidc/base/conf.d/default-vhost.conf /etc/apache2/conf.d/default-vhost.conf +COPY files/alpine/sp-oidc/base/scripts/httpd-foreground /usr/local/bin/ + + +CMD ["httpd-foreground"] diff --git a/files/alpine/sp-oidc/base/conf.d/Dockerfile b/files/alpine/sp-oidc/base/conf.d/Dockerfile new file mode 100644 index 0000000..7222535 --- /dev/null +++ b/files/alpine/sp-oidc/base/conf.d/Dockerfile @@ -0,0 +1,14 @@ +FROM reg.cadoles.com/proxy_cache/library/alpine:edge +#FROM reg.cadoles.com/proxy_cache/library/httpd:alpine3.18 + +# Adding testing repo +RUN echo "https://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories + +RUN apk update && apk add apache-mod-auth-openidc + +COPY conf.d/mod-auth-openidc.conf /etc/apache2/conf.d/mod-auth-openidc.conf +COPY conf.d/default-vhost.conf /etc/apache2/conf.d/default-vhost.conf +COPY scripts/httpd-foreground /usr/local/bin/ + +CMD ["httpd-foreground"] + diff --git a/files/alpine/sp-oidc/base/conf.d/default-vhost.conf b/files/alpine/sp-oidc/base/conf.d/default-vhost.conf new file mode 100644 index 0000000..0595315 --- /dev/null +++ b/files/alpine/sp-oidc/base/conf.d/default-vhost.conf @@ -0,0 +1,29 @@ + + ServerName ${SP_SERVER_NAME}:80 + + DocumentRoot /var/www/html + + CustomLog /proc/self/fd/1 common + + ErrorDocument 400 /error/ + ErrorDocument 401 /error/ + ErrorDocument 403 /error/ + ErrorDocument 404 /error/ + ErrorDocument 500 /error/ + ErrorDocument 502 /error/ + ErrorDocument 503 /error/ + ErrorDocument 504 /error/ + + + SSLOptions +StdEnvVars + + + SSLOptions +StdEnvVars + + + + AllowOverride All + Options +Indexes + Require all granted + + \ No newline at end of file diff --git a/files/alpine/sp-oidc/base/conf.d/mod-auth-openidc.conf b/files/alpine/sp-oidc/base/conf.d/mod-auth-openidc.conf new file mode 100644 index 0000000..3b3d881 --- /dev/null +++ b/files/alpine/sp-oidc/base/conf.d/mod-auth-openidc.conf @@ -0,0 +1,14 @@ +LoadModule auth_openidc_module modules/mod_auth_openidc.so + +OIDCProviderMetadataURL ${SP_OIDC_PROVIDER_METADATA_URL} #http://portal.mse.local:8000/auth/.well-known/openid-configuration +OIDCClientID ${SP_OIDC_CLIENT_NAME} #mse +OIDCClientSecret ${SP_OIDC_CLIENT_SERCRET} #$mse&123456$ +OIDCProviderTokenEndpointAuth client_secret_basic +OIDCCookieSameSite On +OIDCSessionType client-cookie +OIDCXForwardedHeaders X-Forwarded-Host +# OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content +OIDCRedirectURI ${SP_OIDC_REDIRECT_URI} #http://portal.mse.local:8000/protected/redirect_uri +OIDCCryptoPassphrase ${SP_OIDC_CRYPTO_PASSPHRASE} #$mse&123456$ +OIDCOAuthAcceptTokenAs header +OIDCUnAutzAction 302 ${SP_OIDC_ERROR_URI} #http://portal.mse.local:8000/erreur?msg=mod_auth_fail \ No newline at end of file diff --git a/files/alpine/sp-oidc/base/conf.d/scripts/httpd-foreground b/files/alpine/sp-oidc/base/conf.d/scripts/httpd-foreground new file mode 100644 index 0000000..e109f0e --- /dev/null +++ b/files/alpine/sp-oidc/base/conf.d/scripts/httpd-foreground @@ -0,0 +1,7 @@ +#!/bin/sh +set -e + +# Apache gets grumpy about PID files pre-existing +rm -f /run/apache2/httpd.pid + +exec httpd -DFOREGROUND "$@" \ No newline at end of file diff --git a/files/alpine/sp-oidc/base/conf.d/test_alpine-sp-oidc.sh b/files/alpine/sp-oidc/base/conf.d/test_alpine-sp-oidc.sh new file mode 100644 index 0000000..ef09f33 --- /dev/null +++ b/files/alpine/sp-oidc/base/conf.d/test_alpine-sp-oidc.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )"; + +source "$SCRIPT_DIR/lib.sh" + +# Test reg.cadoles.com/cadoles/symfony:alpine-php-7.4-standalone with Symfony 4.4 +test_alpine_sp_oidc() { + # FIXME + + local container_name=$(run_symfony_app_container "4.4" "7.4" "alpine-php-7.4-standalone") + local image_name=$(docker inspect -f '{{.Config.Image}}' ${container_name}) + + trap_add "docker kill ${container_name}" EXIT + trap_add "docker rmi -f ${image_name}" EXIT + + local app_url="http://$(docker port ${container_name} 8080/tcp)" + + # Check that application is responding as expected + local page_content=$(curl -s "${app_url}" | pandoc -f html -t plain) + + assert_matches 'Welcome to Symfony 4\.4\.*' "${page_content}" "Could not find Symfony default welcome message !" +} diff --git a/files/alpine/sp-oidc/base/scripts/httpd-foreground b/files/alpine/sp-oidc/base/scripts/httpd-foreground new file mode 100644 index 0000000..e109f0e --- /dev/null +++ b/files/alpine/sp-oidc/base/scripts/httpd-foreground @@ -0,0 +1,7 @@ +#!/bin/sh +set -e + +# Apache gets grumpy about PID files pre-existing +rm -f /run/apache2/httpd.pid + +exec httpd -DFOREGROUND "$@" \ No newline at end of file diff --git a/files/common/healthcheck/check-multiple.sh b/files/common/healthcheck/check-multiple.sh new file mode 100644 index 0000000..1596a0e --- /dev/null +++ b/files/common/healthcheck/check-multiple.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -eu + +for CHECK in $@; do + /bin/sh "/usr/local/share/cadoles-symfony/healthcheck/check-${CHECK}.sh" +done \ No newline at end of file diff --git a/files/common/healthcheck/check-nginx.sh b/files/common/healthcheck/check-nginx.sh new file mode 100644 index 0000000..26bf64d --- /dev/null +++ b/files/common/healthcheck/check-nginx.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -eu + +wget --quiet --tries=1 --spider 127.0.0.1:8090/healthcheck || exit 1; \ No newline at end of file diff --git a/files/common/healthcheck/check-php-fpm.sh b/files/common/healthcheck/check-php-fpm.sh new file mode 100644 index 0000000..763d692 --- /dev/null +++ b/files/common/healthcheck/check-php-fpm.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -eu + +# TODO \ No newline at end of file diff --git a/files/common/nginx/conf.d/app.conf.gotmpl b/files/common/nginx/conf.d/app.conf.gotmpl new file mode 100644 index 0000000..d84d5b4 --- /dev/null +++ b/files/common/nginx/conf.d/app.conf.gotmpl @@ -0,0 +1,89 @@ +upstream backend { + server {{ env.Getenv "NGINX_APP_UPSTREAM_BACKEND_SERVER" "unix:/tmp/php-fpm.sock" }}; + keepalive {{ env.Getenv "NGINX_APP_UPSTREAM_BACKEND_KEEPALIVE" "40" }}; + # Must be less than php-fpm.conf:pm.max_requests + keepalive_requests {{ env.Getenv "NGINX_APP_UPSTREAM_BACKEND_KEEPALIVE_REQUESTS" "250" }}; + keepalive_timeout {{ env.Getenv "NGINX_APP_UPSTREAM_BACKEND_KEEPALIVE_TIMEOUT" "10" }}; +} + +server { + listen {{ env.Getenv "NGINX_APP_SERVER_LISTEN" "8080" }} default_server; + + server_name {{ env.Getenv "NGINX_APP_SERVER_NAME" "_" }}; + set $base /app; + root $base{{ env.Getenv "NGINX_APP_ROOT" "/public"}}; + + # deny all dot files except .well-known + location ~ /\.(?!well-known) { + deny all; + } + + # index.php + index index.php; + + + # index.php fallback + location / { + # try to serve file directly, fallback to index.php + try_files $uri {{ env.Getenv "NGINX_APP_PHP_INDEX" "/index.php"}}$is_args$args; + } + + # Disable falling back to PHP script for the asset directories; + location ~ ^/({{ env.Getenv "NGINX_APP_ASSETS_DIRECTORIES" "public|bundles|web"}})/ { + try_files $uri =404; + } + + # handle non-files + location ~ {{ env.Getenv "NGINX_APP_PHP_NON_FILE_PATTERN" "^/index\\.php(/|$)" }} { + # default fastcgi_params + include fastcgi_params; + + # fastcgi settings + fastcgi_pass backend; + fastcgi_index index.php; + fastcgi_buffers 8 16k; + fastcgi_buffer_size 32k; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + + # fastcgi params + fastcgi_param DOCUMENT_ROOT $realpath_root; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + fastcgi_param PHP_ADMIN_VALUE "open_basedir=none"; + + # Prevents URIs that include the front controller. This will 404: + # http://domain.tld/index.php/some-path + # Remove the internal directive to allow URIs like this + internal; + } + + # return 404 for all other php files not matching the front controller + # this prevents access to other php files you don't want to be accessible. + location ~ \.php$ { + return 404; + } + + # favicon.ico + location = /favicon.ico { + log_not_found off; + access_log off; + } + + # robots.txt + location = /robots.txt { + log_not_found off; + access_log off; + } + + # assets, media + location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv)$ { + expires 7d; + access_log off; + } + + # svg, fonts + location ~* \.(?:svgz?|ttf|ttc|otf|eot|woff2?)$ { + add_header Access-Control-Allow-Origin "*"; + expires 7d; + access_log off; + } +} \ No newline at end of file diff --git a/files/common/nginx/conf.d/compression.conf.gotmpl b/files/common/nginx/conf.d/compression.conf.gotmpl new file mode 100644 index 0000000..20fc259 --- /dev/null +++ b/files/common/nginx/conf.d/compression.conf.gotmpl @@ -0,0 +1,10 @@ +# Compression +gzip on; +gzip_disable "msie6"; +gzip_vary on; +gzip_proxied any; +gzip_comp_level 6; +gzip_buffers 32 16k; +gzip_http_version 1.1; +gzip_min_length 250; +gzip_types image/jpeg image/bmp image/svg+xml text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript image/x-icon; \ No newline at end of file diff --git a/files/common/nginx/conf.d/healthcheck.conf.gotmpl b/files/common/nginx/conf.d/healthcheck.conf.gotmpl new file mode 100644 index 0000000..9084488 --- /dev/null +++ b/files/common/nginx/conf.d/healthcheck.conf.gotmpl @@ -0,0 +1,9 @@ +server { + listen 8090; + location /healthcheck { + stub_status; + access_log off; + allow 127.0.0.1; + deny all; + } +} \ No newline at end of file diff --git a/files/common/nginx/conf.d/log.conf.gotmpl b/files/common/nginx/conf.d/log.conf.gotmpl new file mode 100644 index 0000000..681cf48 --- /dev/null +++ b/files/common/nginx/conf.d/log.conf.gotmpl @@ -0,0 +1,3 @@ +# logging +access_log /dev/stdout; +error_log stderr {{ env.Getenv "NGINX_ERROR_LOG_LEVEL" "warn" }}; \ No newline at end of file diff --git a/files/common/nginx/conf.d/mime.conf.gotmpl b/files/common/nginx/conf.d/mime.conf.gotmpl new file mode 100644 index 0000000..5047d1a --- /dev/null +++ b/files/common/nginx/conf.d/mime.conf.gotmpl @@ -0,0 +1,3 @@ +# MIME +include mime.types; +default_type application/octet-stream; \ No newline at end of file diff --git a/files/common/nginx/conf.d/non-root.conf.gotmpl b/files/common/nginx/conf.d/non-root.conf.gotmpl new file mode 100644 index 0000000..6a065e6 --- /dev/null +++ b/files/common/nginx/conf.d/non-root.conf.gotmpl @@ -0,0 +1,6 @@ +# Non Root Temp Paths +client_body_temp_path /tmp/client_temp; +proxy_temp_path /tmp/proxy_temp_path; +fastcgi_temp_path /tmp/fastcgi_temp; +uwsgi_temp_path /tmp/uwsgi_temp; +scgi_temp_path /tmp/scgi_temp; \ No newline at end of file diff --git a/files/common/nginx/conf.d/x-forward.conf.gotmpl b/files/common/nginx/conf.d/x-forward.conf.gotmpl new file mode 100644 index 0000000..1a9bae2 --- /dev/null +++ b/files/common/nginx/conf.d/x-forward.conf.gotmpl @@ -0,0 +1,4 @@ +# Replace loadbalancer IP(real-ip) with actual client IP. +set_real_ip_from 0.0.0.0/0; +real_ip_header X-Forwarded-For; +real_ip_recursive on; \ No newline at end of file diff --git a/files/common/nginx/nginx.conf.gotmpl b/files/common/nginx/nginx.conf.gotmpl new file mode 100644 index 0000000..2b0a124 --- /dev/null +++ b/files/common/nginx/nginx.conf.gotmpl @@ -0,0 +1,50 @@ +# user www-data; +pid /tmp/nginx.pid; +worker_processes auto; +daemon off; + +events { + worker_connections {{ env.Getenv "NGINX_EVENTS_WORKER_CONNECTIONS" "1024" }}; +} + +http { + charset {{ env.Getenv "NGINX_CHARSET" "utf-8" }}; + + # copies data between one FD and other from within the kernel + # faster than read() + write() + sendfile {{ env.Getenv "NGINX_SENDFILE" "on" }}; + + # send headers in one piece, it is better than sending them one by one + tcp_nopush {{ env.Getenv "NGINX_TCP_NOPUSH" "on" }}; + + # don't buffer data sent, good for small data bursts in real time + tcp_nodelay {{ env.Getenv "NGINX_TCP_NODELAY" "on" }}; + + # allow the server to close connection on non responding client, this will free up memory + reset_timedout_connection {{ env.Getenv "NGINX_RESET_TIMEDOUT_CONNECTION" "on" }}; + + # hide server info for security + server_tokens {{ env.Getenv "NGINX_SERVER_TOKENS" "off" }}; + + log_not_found {{ env.Getenv "NGINX_LOG_NOT_FOUND" "off" }}; + types_hash_max_size 2048; + + # if the request body size is more than the buffer size, then the entire (or partial) + # request body is written into a temporary file + client_body_buffer_size 128k; + + # maximum body size + client_max_body_size {{ env.Getenv "NGINX_CLIENT_MAX_BODY_SIZE" "16M" }}; + + # maximum number and size of buffers for large headers to read from client request + large_client_header_buffers 4 256k; + + # cache information about FDs, frequently accessed files + open_file_cache max=200000 inactive=20s; + open_file_cache_valid 60s; + open_file_cache_min_uses 5; + open_file_cache_errors off; + + # load configs + include /etc/nginx/conf.d/*.conf; +} \ No newline at end of file diff --git a/files/common/nvm/nvm-wrapper.sh b/files/common/nvm/nvm-wrapper.sh new file mode 100644 index 0000000..0a29d18 --- /dev/null +++ b/files/common/nvm/nvm-wrapper.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +export NVM_NODEJS_ORG_MIRROR=https://unofficial-builds.nodejs.org/download/release + +NVM_DIR="$HOME/.nvm" +source "$NVM_DIR/nvm.sh" + +nvm_get_arch() { nvm_echo "x64-musl"; } + +nvm $@ \ No newline at end of file diff --git a/files/common/scripts/install-dependencies.sh b/files/common/scripts/install-dependencies.sh new file mode 100644 index 0000000..0bdfbf9 --- /dev/null +++ b/files/common/scripts/install-dependencies.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -eo pipefail + +main() { + if [ "${INSTALL_DEPENDENCIES}" == "0" ]; then + echo "Dependencies installation disabled. Doing nothing." + exit + fi + + install_additional_packages +} + +# Return 3 for unknown distribution +install_additional_packages() { + if [ -z "${ADDITIONAL_PACKAGES}" ]; then + return + fi + + echo "Installing additional packages '${ADDITIONAL_PACKAGES}'..." + + if [ -f "/etc/debian_version" ]; then + export DEBIAN_FRONTEND=noninteractive + apt-get update -y + apt-get install -y ${ADDITIONAL_PACKAGES} + elif [ -f "/etc/alpine-release" ]; then + apk update + apk add --no-cache ${ADDITIONAL_PACKAGES} + else + return 3 + fi +} + +main \ No newline at end of file diff --git a/files/common/scripts/run.sh b/files/common/scripts/run.sh new file mode 100644 index 0000000..36065a7 --- /dev/null +++ b/files/common/scripts/run.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -eo pipefail + +main() { + echo "Generating filesystem templates..." + generate_templates + exec $@ +} + +generate_templates() { + # Find *.gotmpl files and generate associated configuration files + local template_files=$(find / -type f -name '*.gotmpl') + + for tmpl in $template_files; do + local dest_file=${tmpl%".gotmpl"} + echo "Generating file '$dest_file'..." + gomplate -f "$tmpl" > "$dest_file" + chmod $(stat -c '%a' "$tmpl") "$dest_file" + chown $(stat -c '%u:%g' "$tmpl") "$dest_file" + done +} + +main $@ \ No newline at end of file diff --git a/files/debian/sp-shib/base/Dockerfile b/files/debian/sp-shib/base/Dockerfile new file mode 100644 index 0000000..5df0f69 --- /dev/null +++ b/files/debian/sp-shib/base/Dockerfile @@ -0,0 +1,27 @@ +FROM reg.cadoles.com/proxy_cache/library/debian:stable-slim + + +RUN export DEBIAN_FRONTEND=noninteractive && \ + apt-get update -y && \ + apt-get install -y libapache2-mod-shib php-fpm + +RUN a2enmod rewrite expires headers remoteip ssl \ + proxy proxy_fcgi proxy_http proxy_balancer \ + lbmethod_bybusyness lbmethod_byrequests lbmethod_bytraffic lbmethod_heartbeat + +EXPOSE 80 + +COPY files/common/healthcheck /usr/local/share/cadoles/healthcheck +COPY files/common/scripts /usr/local/share/cadoles/scripts + +SHELL ["/bin/bash", "-c"] +CMD ["/bin/bash"] + +# ========= Child image build triggers ========== + +ONBUILD COPY . /app +ONBUILD ARG ADDITIONAL_PACKAGES +ONBUILD ARG INSTALL_DEPENDENCIES +ONBUILD ARG INSTALL_COMPOSER_DEPENDENCIES +ONBUILD ARG INSTALL_NPM_DEPENDENCIES +ONBUILD RUN . /usr/local/share/cadoles/scripts/install-dependencies.sh diff --git a/recipes/alpine-sp-oidc.mk b/recipes/alpine-sp-oidc.mk new file mode 100644 index 0000000..5125a58 --- /dev/null +++ b/recipes/alpine-sp-oidc.mk @@ -0,0 +1,28 @@ +build: build-alpine-sp-oidc-base + +build-alpine-sp-oidc-base: + $(MAKE) \ + IMAGE_TAG=alpine-sp-oidc-base \ + DOCKERFILE=files/alpine/sp-oidc/base/Dockerfile \ + _build + +scan: scan-alpine-sp-oidc-base + +scan-alpine-sp-oidc-base: + $(MAKE) \ + IMAGE_TAG=alpine-sp-oidc-base \ + _scan + +release: release-alpine-sp-oidc-base + +release-alpine-sp-oidc-base: + $(MAKE) \ + IMAGE_TAG=alpine-sp-oidc-base \ + _release + +test: test-alpine-sp-oidc-base + +test-alpine-sp-oidc-base: + $(MAKE) \ + IMAGE_TAG=alpine-sp-oidc-base \ + _test \ No newline at end of file diff --git a/recipes/debian-sp-shib.mk b/recipes/debian-sp-shib.mk new file mode 100644 index 0000000..902bda3 --- /dev/null +++ b/recipes/debian-sp-shib.mk @@ -0,0 +1,28 @@ +build: build-debian-sp-shib-base + +build-debian-sp-shib-base: + $(MAKE) \ + IMAGE_TAG=debian-sp-shib-base \ + DOCKERFILE=files/debian/sp-shib/base/Dockerfile \ + _build + +scan: scan-debian-sp-shib-base + +scan-debian-sp-shib-base: + $(MAKE) \ + IMAGE_TAG=debian-sp-shib-base \ + _scan + +release: release-debian-sp-shib-base + +release-debian-sp-shib-base: + $(MAKE) \ + IMAGE_TAG=debian-sp-shib-base \ + _release + +test: test-debian-sp-shib-base + +test-debian-sp-shib-base: + $(MAKE) \ + IMAGE_TAG=debian-sp-shib-base \ + _test \ No newline at end of file diff --git a/tests/lib.sh b/tests/lib.sh new file mode 100644 index 0000000..f4453cd --- /dev/null +++ b/tests/lib.sh @@ -0,0 +1,114 @@ +#!/bin/bash + +log() { printf '%s\n' "$*"; } +error() { log "ERROR: $*" >&2; } +fatal() { error "$@"; exit 1; } + +run_symfony_app_container() { + set -e + trap "set +e" RETURN + + local symfony_version=${1} + local php_version=${2} + local image_tag=${3} + + cd "${SCRIPT_DIR}/.." + + # Create temporary directory + local project_dir=$(mktemp -d) + local run_id=$(date +%s) + + # Defer cleanup on exit + trap_add "rm -rf ${project_dir}" EXIT + + # Create dummy Symfony project + symfony new --dir="${project_dir}" --version="${symfony_version}" --php="${php_version}" --webapp 1>&2 + + cat > "${project_dir}/Dockerfile" < "${project_dir}/.dockerignore" <&2 + + local http_port=$(get_unused_port) + local container_name="symfony-test-${image_tag}-${run_id}" + + # Start project image + docker run \ + -d \ + --rm \ + -p "${http_port}:8080" \ + --name "${container_name}" \ + "${image_name}" \ + 1>&2 + + # Wait container is healthy with a timeout + local duration=0 + local last_tick=$(date +%s) + local container_status=$(docker inspect -f '{{.State.Health.Status}}' ${container_name}) + + log "Waiting for container to become healthy..." 1>&2 + until [ "${container_status}" == "healthy" ]; do + sleep 1 + + local now=$(date +%s) + duration=$(( ${duration} + ( ${now} - ${last_tick} ) )) + + + if [ ${duration} -gt 60 ]; then + fail "Container did not become healthy before timeout !" + fi + + last_tick=${now} + container_status=$(docker inspect -f '{{.State.Health.Status}}' ${container_name}) + done + + echo "${container_name}" +} + +get_unused_port() { + set -e + trap "set +e" RETURN + + local lport=32768; + local uport=60999; + while true; do + local mport=$[${lport} + (${RANDOM} % $uport)]; + (echo "" >/dev/tcp/127.0.0.1/${mport}) >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo ${mport}; + return 0; + fi + done +} + +# From https://stackoverflow.com/a/7287873 +trap_add() { + local trap_add_cmd=$1; + shift || fatal "${FUNCNAME} usage error" + for trap_add_name in "$@"; do + trap -- "$( + # helper fn to get existing trap command from output + # of trap -p + extract_trap_cmd() { printf '%s\n' "$3"; } + # print existing trap command with newline + eval "extract_trap_cmd $(trap -p "${trap_add_name}")" + # print the new trap command + printf '%s\n' "${trap_add_cmd}" + )" "${trap_add_name}" \ + || fatal "unable to add to trap ${trap_add_name}" + done +} + +declare -f -t trap_add \ No newline at end of file diff --git a/tests/test_alpine-sp-oidc.sh b/tests/test_alpine-sp-oidc.sh new file mode 100644 index 0000000..ef09f33 --- /dev/null +++ b/tests/test_alpine-sp-oidc.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )"; + +source "$SCRIPT_DIR/lib.sh" + +# Test reg.cadoles.com/cadoles/symfony:alpine-php-7.4-standalone with Symfony 4.4 +test_alpine_sp_oidc() { + # FIXME + + local container_name=$(run_symfony_app_container "4.4" "7.4" "alpine-php-7.4-standalone") + local image_name=$(docker inspect -f '{{.Config.Image}}' ${container_name}) + + trap_add "docker kill ${container_name}" EXIT + trap_add "docker rmi -f ${image_name}" EXIT + + local app_url="http://$(docker port ${container_name} 8080/tcp)" + + # Check that application is responding as expected + local page_content=$(curl -s "${app_url}" | pandoc -f html -t plain) + + assert_matches 'Welcome to Symfony 4\.4\.*' "${page_content}" "Could not find Symfony default welcome message !" +}