Harden app and registry workloads

This commit is contained in:
juvdiaz 2026-05-25 14:36:44 -06:00
parent f7e3065cda
commit 16069b7950
8 changed files with 127 additions and 19 deletions

View File

@ -24,9 +24,21 @@ spec:
operator: In
values:
- debian
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
containers:
- name: registry
image: registry:2
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
ports:
- containerPort: 5000
name: http
@ -51,10 +63,14 @@ spec:
volumeMounts:
- name: registry-vol
mountPath: /var/lib/registry
- name: tmp
mountPath: /tmp
volumes:
- name: registry-vol
persistentVolumeClaim:
claimName: registry-pvc
- name: tmp
emptyDir: {}
---
apiVersion: v1
kind: Service

View File

@ -3,4 +3,6 @@ FROM nginx:1.27-alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY public/ /usr/share/nginx/html/
EXPOSE 80
USER nginx
EXPOSE 8080

View File

@ -1,5 +1,5 @@
server {
listen 80;
listen 8080;
server_name _;
root /usr/share/nginx/html;
index index.html;

View File

@ -17,12 +17,24 @@ spec:
spec:
nodeSelector:
kubernetes.io/hostname: raspberry
securityContext:
runAsNonRoot: true
runAsUser: 101
runAsGroup: 101
fsGroup: 101
fsGroupChangePolicy: OnRootMismatch
containers:
- name: demos-static
image: 192.168.100.68:30500/demos-static:latest
imagePullPolicy: Always
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
ports:
- containerPort: 80
- containerPort: 8080
name: http
readinessProbe:
httpGet:
@ -42,6 +54,20 @@ spec:
memory: 32Mi
limits:
memory: 128Mi
volumeMounts:
- name: nginx-cache
mountPath: /var/cache/nginx
- name: nginx-run
mountPath: /var/run
- name: tmp
mountPath: /tmp
volumes:
- name: nginx-cache
emptyDir: {}
- name: nginx-run
emptyDir: {}
- name: tmp
emptyDir: {}
---
apiVersion: v1
kind: Service
@ -53,7 +79,7 @@ spec:
externalTrafficPolicy: Local
ports:
- port: 80
targetPort: 80
targetPort: http
nodePort: 30081
selector:
app: demos-static

View File

@ -17,7 +17,15 @@ RUN ln -sf /usr/bin/php82 /usr/bin/php
# Alpine keeps Apache site configs here instead of a2enmod
RUN sed -i 's/#LoadModule rewrite_module/LoadModule rewrite_module/' /etc/apache2/httpd.conf && \
sed -i 's/#LoadModule headers_module/LoadModule headers_module/' /etc/apache2/httpd.conf && \
sed -i 's/DirectoryIndex index.html/DirectoryIndex index.php index.html/' /etc/apache2/httpd.conf
sed -i 's/DirectoryIndex index.html/DirectoryIndex index.php index.html/' /etc/apache2/httpd.conf && \
sed -i 's/^Listen 80$/Listen 8080/' /etc/apache2/httpd.conf && \
sed -i 's#^ErrorLog .*#ErrorLog /proc/self/fd/2#' /etc/apache2/httpd.conf && \
sed -i 's#^CustomLog .*#CustomLog /proc/self/fd/1 combined#' /etc/apache2/httpd.conf && \
if grep -q '^PidFile ' /etc/apache2/httpd.conf; then \
sed -i 's#^PidFile .*#PidFile /tmp/httpd.pid#' /etc/apache2/httpd.conf; \
else \
printf '\nPidFile /tmp/httpd.pid\n' >> /etc/apache2/httpd.conf; \
fi
# Copy files directly into Alpine's default web root
COPY . /var/www/localhost/htdocs/
@ -31,9 +39,15 @@ RUN mkdir -p /var/www/localhost/htdocs/db && \
# Match local user permissions for the runtime user (Alpine uses 'apache' instead of 'www-data')
RUN usermod -u 1000 apache && \
groupmod -g 1000 apache
groupmod -g 1000 apache && \
mkdir -p /run/apache2 /var/log/apache2 /tmp/website-lang && \
chown -R apache:apache /run/apache2 /var/log/apache2 /tmp/website-lang /var/www/localhost/htdocs/db
EXPOSE 80
ENV WEBSITE_LANG_WRITE_DIR=/tmp/website-lang
USER apache
EXPOSE 8080
# Start Apache in the foreground
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]

View File

@ -3,10 +3,17 @@
// Include this at the top of every page.
// Provides: $lang, $text, $en, $availableLangs
$availableLangs = array_map(
$staticLangDir = __DIR__ . '/lang';
$runtimeLangDir = getenv('WEBSITE_LANG_WRITE_DIR') ?: null;
$langFiles = glob($staticLangDir . '/*.php') ?: [];
if ($runtimeLangDir && is_dir($runtimeLangDir)) {
$langFiles = array_merge($langFiles, glob($runtimeLangDir . '/*.php') ?: []);
}
$availableLangs = array_values(array_unique(array_map(
fn($f) => basename($f, '.php'),
glob(__DIR__ . '/lang/*.php')
);
$langFiles
)));
function getLang($supported) {
if (isset($_GET['lang']) && in_array($_GET['lang'], $supported)) {
@ -18,12 +25,15 @@ function getLang($supported) {
$lang = getLang($availableLangs);
$file = __DIR__ . "/lang/$lang.php";
$file = $runtimeLangDir ? "$runtimeLangDir/$lang.php" : '';
if (!$file || !file_exists($file)) {
$file = "$staticLangDir/$lang.php";
}
if (!file_exists($file)) {
$lang = 'nah';
$file = __DIR__ . "/lang/nah.php";
$file = "$staticLangDir/nah.php";
}
// Always load English as translation source
$en = include __DIR__ . '/lang/en.php';
$en = include "$staticLangDir/en.php";
$text = array_replace($en, include $file);

View File

@ -48,11 +48,18 @@ foreach ($base as $key => $value) {
$lines[] = "];";
$content = implode("\n", $lines) . "\n";
$path = __DIR__ . "/lang/$lang.php";
if (file_put_contents($path, $content) === false) {
$langDir = getenv('WEBSITE_LANG_WRITE_DIR') ?: (__DIR__ . '/lang');
if (!is_dir($langDir) && !mkdir($langDir, 0755, true)) {
http_response_code(500);
echo json_encode(['error' => 'Could not write file — check permissions on lang/']);
echo json_encode(['error' => 'Could not create language directory']);
exit;
}
echo json_encode(['success' => true, 'lang' => $lang, 'path' => "lang/$lang.php"]);
$path = "$langDir/$lang.php";
if (file_put_contents($path, $content) === false) {
http_response_code(500);
echo json_encode(['error' => 'Could not write language file']);
exit;
}
echo json_encode(['success' => true, 'lang' => $lang]);

View File

@ -22,6 +22,12 @@ spec:
spec:
nodeSelector:
kubernetes.io/hostname: raspberry
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution: # requiredDuringSchedulingIgnoredDuringExecution:
@ -38,8 +44,17 @@ spec:
- name: php-app
image: 192.168.100.68:30500/php-website:latest
imagePullPolicy: Always
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
env:
- name: WEBSITE_LANG_WRITE_DIR
value: /tmp/website-lang
ports:
- containerPort: 80
- containerPort: 8080
name: http
readinessProbe:
httpGet:
@ -59,6 +74,24 @@ spec:
memory: 64Mi
limits:
memory: 256Mi
volumeMounts:
- name: apache-run
mountPath: /run/apache2
- name: apache-logs
mountPath: /var/log/apache2
- name: website-db
mountPath: /var/www/localhost/htdocs/db
- name: tmp
mountPath: /tmp
volumes:
- name: apache-run
emptyDir: {}
- name: apache-logs
emptyDir: {}
- name: website-db
emptyDir: {}
- name: tmp
emptyDir: {}
---
apiVersion: v1
kind: Service
@ -70,7 +103,7 @@ spec:
externalTrafficPolicy: Local
ports:
- port: 80
targetPort: 80
targetPort: http
nodePort: 30080
selector:
app: php-website