Skip to content

Commit 90f1d28

Browse files
committed
fix: file permissions & security fixes (urllib deps), healthcheck
1 parent 8daebce commit 90f1d28

4 files changed

Lines changed: 70 additions & 12 deletions

File tree

Dockerfile

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,26 @@ WORKDIR /app
1010
RUN adduser --disabled-password --gecos "" --shell /sbin/nologin appuser
1111

1212
# Création du dossier /config avec les bonnes permissions pour appuser
13-
RUN mkdir -p /config && chown -R appuser /app /config
13+
RUN mkdir -p /config && chown -R appuser:appuser /app /config
14+
15+
# Installer su-exec et les certificats CA
16+
RUN apk add --no-cache su-exec ca-certificates && update-ca-certificates
1417

1518
COPY requirements.txt .
1619
RUN pip install --no-cache-dir --require-hashes -r requirements.txt
20+
# Corriger une vulnérabilité connue d'urllib3 via mise à niveau contrôlée
21+
RUN pip install --no-cache-dir --upgrade 'urllib3>=2.6.3,<3'
1722

1823
COPY . .
1924

2025
COPY entrypoint.sh /entrypoint.sh
21-
RUN chmod +x /entrypoint.sh && chmod 0755 /entrypoint.sh
22-
23-
RUN apk add --no-cache su-exec
26+
RUN chmod +x /entrypoint.sh && chown appuser:appuser /entrypoint.sh
2427

25-
USER appuser
28+
# Passer en root temporairement pour l'entrypoint (qui doit gérer les permissions du volume)
29+
# L'entrypoint s'assurera que appuser peut écrire dans /config
30+
USER root
2631

2732
ENTRYPOINT ["/entrypoint.sh"]
28-
CMD ["python", "src/main.py"]
33+
CMD ["su-exec", "appuser", "python", "src/main.py"]
34+
HEALTHCHECK --interval=5m --timeout=20s --start-period=30s CMD pgrep -f "python src/main.py" >/dev/null || exit 1
2935
LABEL org.opencontainers.image.source="https://github.com/leonpwd/NotifyNotes"

entrypoint.sh

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,37 @@
11
#!/bin/sh
22
# filepath: entrypoint.sh
33

4-
# On tourne déjà en tant qu'appuser grâce à USER appuser dans le Dockerfile.
5-
# Pas besoin de su-exec : il échoue sur setgroups en environnement non privilégié.
6-
exec "$@"
4+
set -eu
5+
set -o pipefail
6+
7+
# Réduire les permissions par défaut des fichiers créés
8+
umask 077
9+
10+
# Créer le répertoire /config s'il n'existe pas
11+
mkdir -p /config
12+
13+
# Restreindre les permissions du répertoire /config et assigner le propriétaire
14+
chmod 700 /config 2>/dev/null || true
15+
chown -R appuser:appuser /config 2>/dev/null || true
16+
17+
# S'assurer que les fichiers JSON existent avec les bonnes permissions
18+
for file in /config/new_notes.json /config/old_notes.json; do
19+
if [ ! -f "$file" ]; then
20+
# Crée un fichier JSON vide avec permissions restrictives
21+
echo "[]" > "$file" 2>/dev/null || true
22+
chmod 600 "$file" 2>/dev/null || true
23+
chown appuser:appuser "$file" 2>/dev/null || true
24+
else
25+
chmod 600 "$file" 2>/dev/null || true
26+
chown appuser:appuser "$file" 2>/dev/null || true
27+
fi
28+
done
29+
30+
# Si su-exec est disponible, exécuter la commande sous appuser; sinon, tenter avec su -s
31+
if command -v su-exec >/dev/null 2>&1; then
32+
exec "$@"
33+
else
34+
echo "Avertissement: su-exec introuvable, tentative de bascule via su" >&2
35+
# shellcheck disable=SC2039
36+
exec sh -c "su -s /bin/sh appuser -c '$*'"
37+
fi

src/main.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import requests
2+
from requests.adapters import HTTPAdapter
3+
from urllib3.util.retry import Retry
24
import time
35
import os
46
import compare_json as comparator
@@ -38,11 +40,25 @@ def get_notes_content():
3840
'Sec-Fetch-User': '?1',
3941
'Cache-Control': 'max-age=0'
4042
}
41-
response = requests.get(URL, headers=headers, timeout=30, verify=True)
43+
# Session avec retry et backoff pour la robustesse réseau
44+
session = requests.Session()
45+
retries = Retry(
46+
total=3,
47+
backoff_factor=1.0,
48+
status_forcelist=[429, 500, 502, 503, 504],
49+
allowed_methods=["GET"],
50+
raise_on_redirect=False,
51+
raise_on_status=False,
52+
)
53+
session.mount("https://", HTTPAdapter(max_retries=retries))
54+
session.mount("http://", HTTPAdapter(max_retries=retries))
55+
56+
# Désactiver les redirections pour réduire les risques liés aux redirects
57+
response = session.get(URL, headers=headers, timeout=(5, 30), verify=True, allow_redirects=False)
4258

4359
if response.status_code != 200:
44-
error_text = response.text[:500] # Limiter la longueur du message d'erreur
45-
raise Exception(f"Erreur lors de la récupération des notes: {response.status_code} - {error_text}")
60+
# N'imprime pas le corps pour éviter d'exposer des données
61+
raise Exception(f"Erreur lors de la récupération des notes: {response.status_code}")
4662
response.raise_for_status()
4763
return response.text
4864

src/parse.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from bs4 import BeautifulSoup
2+
import os
23
import re
34
import json
45
import unicodedata
@@ -138,6 +139,10 @@ def convert_notes_to_json(url_response, json_file):
138139

139140
with open(json_file, "w", encoding="utf-8") as f:
140141
json.dump(organized, f, ensure_ascii=False, indent=2)
142+
try:
143+
os.chmod(json_file, 0o600)
144+
except Exception:
145+
pass
141146
except Exception as e:
142147
print(f"Erreur lors du parsing HTML: {e}")
143148
raise

0 commit comments

Comments
 (0)