Compare commits

...

67 Commits

Author SHA1 Message Date
957a1b9255 Changed tests to be more in line with our terms 2025-10-27 10:03:58 +01:00
afc07d4561 Fix tests: Update field names to match actual model structure (abschnitttyp field, inhalt field) 2025-10-24 17:58:54 +00:00
af06598172 Fix tests: Update Abschnitttyp to AbschnittTyp 2025-10-24 17:54:59 +00:00
4213ca60ac Add comprehensive unit tests for dokumente app 2025-10-24 17:48:08 +00:00
bf2f15fa5c removed initial test lines 2025-10-24 19:42:07 +02:00
c1eb2d7871 Deploy 0.933 2025-10-24 01:07:37 +02:00
b29e894b22 Merge branch 'feature/diagram-post-caching' into testing 2025-10-24 01:04:54 +02:00
0f096d18aa Add MEDIA_ROOT, MEDIA_URL and DIAGRAM_CACHE_DIR to docker settings 2025-10-23 23:00:14 +00:00
9b484787a4 Add media file serving for cached diagrams
- Configure MEDIA_URL/MEDIA_ROOT serving in DEBUG mode
- Separate static and media file configurations for clarity
2025-10-23 22:59:54 +00:00
8dd3b4e9af Add MEDIA_ROOT, MEDIA_URL and DIAGRAM_CACHE_DIR settings 2025-10-23 22:59:40 +00:00
0d0199ca62 Wrong branching point - corrected 2025-10-24 00:54:10 +02:00
5f58d660c0 Merge branch 'feature/diagram-post-caching' into testing 2025-10-24 00:47:18 +02:00
e84f25ca1d New DB 2025-10-24 00:37:40 +02:00
dfb8eeef97 Deploy 931 (with diagram branch in container) - readiness probes off temporarily 2025-10-24 00:30:22 +02:00
0225fb3396 Deploy 931 (with diagram branch in container) - 2nd attempt 2025-10-24 00:27:07 +02:00
7377ddaea3 Deploy 931 (with diagram branch in container) 2025-10-24 00:20:34 +02:00
67c393ecf1 Add documentation for diagram POST caching feature 2025-10-23 22:06:30 +00:00
dbb3ecd5bf Add diagram cache directory to gitignore 2025-10-23 22:06:07 +00:00
966cd46228 Add management command to clear diagram cache
Usage:
  python manage.py clear_diagram_cache
  python manage.py clear_diagram_cache --type plantuml
2025-10-23 22:05:36 +00:00
1ee9b3c46f Add commands package 2025-10-23 22:05:26 +00:00
8f57f5fc5b Add management module 2025-10-23 22:05:21 +00:00
cd7195b3aa Update diagram rendering to use POST with caching
- Replace URL-encoded GET approach with POST requests
- Use local filesystem cache for generated diagrams
- Add error handling with fallback message
- Serve diagrams from MEDIA_URL instead of proxy
2025-10-23 22:05:13 +00:00
020dff0871 Add diagram caching module with POST support
- Implement content-based hashing for cache keys
- POST diagram content to Kroki server instead of URL encoding
- Store generated SVGs in local filesystem cache
- Add cache clearing functionality
2025-10-23 22:04:19 +00:00
1dbdbc7f3c Initialize diagramm_proxy module 2025-10-23 22:03:39 +00:00
4d1232b764 Tests - not runnable yet. 2025-10-23 16:35:31 +02:00
fe2e02934a README added, first try at signing commits. 2025-10-23 09:27:25 +02:00
add1a88ce4 README added, first try at signing commits. 2025-10-23 09:26:44 +02:00
3c23918e1f No-clobber back on for database in ArgoCD 2025-10-23 09:01:45 +02:00
fa0a2a9df9 new data structure due to renaming - clobbering database temporarily in init-container 2025-10-23 08:25:12 +00:00
Adrian A. Baumann
9feaf6686f Deploy 930 2025-10-23 09:42:41 +02:00
7087be672a Added "Geltungsbereich" back into search function and corrected it; Changed "standards" page to "dokumente" internally 2025-10-23 09:35:23 +02:00
Adrian A. Baumann
969141601d Merge branch 'rename_standards' into development 2025-10-22 15:14:48 +02:00
Adrian A. Baumann
b391ab0ef6 >Renamed app "standards" to "dokumente" - finally working as expected. 2025-10-22 15:08:42 +02:00
4de2ad38c5 Readme added 2025-10-21 23:46:17 +02:00
d46d937e93 Geltungsbereich removed from search scope for now. Maybe check Haystack or other dedicated search engines 2025-10-21 16:16:42 +02:00
4d713b3763 Suche konsolidiert, unterscheidet nicht mehr nach Abschnittstyp. Möglicherweise optimierungswürdig 2025-10-21 15:55:49 +02:00
a08e2186f3 Documentation folder added with examples of import structured files (plain-text) 2025-10-21 14:29:47 +02:00
762f13fa6a Deploy 929 2025-10-20 07:38:18 +02:00
8b6d1653f0 Empty line in manage.py removed 2025-10-20 07:31:39 +02:00
de0a475a57 Static collection in Docker build 2025-10-16 16:15:57 +02:00
2aaab3b3d4 Added comments to standard_detail.html 2025-10-14 10:26:34 +02:00
db06ae0630 kroki containers pulled to local repo - typo corrected 2025-10-06 16:29:56 +02:00
6afc9f8f4e kroki containers pulled to local repo 2025-10-06 16:28:57 +02:00
5e0616dc6c debugging 2025-10-06 16:10:12 +02:00
a55736f736 Removed docker.io for kroki images 2025-10-06 16:08:18 +02:00
d97a66690a Removed docker.io for kroki images 2025-10-06 15:57:18 +02:00
784fbea088 Back to don't clobber database when copying. 2025-10-06 15:49:00 +02:00
6e8a978ae5 clobber database when copying. 2025-10-06 15:47:51 +02:00
2065d69a80 turns out busybox copy has different command line options. 2025-10-06 15:01:29 +02:00
dbd75f9e30 turns out busybox copy has different command line options. 2025-10-06 14:56:12 +02:00
077b376953 sleep added to init container; debugging... 2025-10-06 14:54:42 +02:00
7c1b89a13b turns out busybox copy has different command line options. 2025-10-06 14:51:30 +02:00
b0bfb4a38a sleep added to init container; debugging... 2025-10-06 14:48:54 +02:00
244e9e155f Data-Loader added as initcontainer 2025-10-06 14:34:54 +02:00
bba32d08e3 v026 2025-10-06 14:13:32 +02:00
4b257bae44 Added version number and trigger deployment 2025-10-06 13:41:51 +02:00
89f427462d Added Metas for all models 2025-10-06 13:31:15 +02:00
94f381c02f renamed and adjusted import script 2025-10-03 15:25:49 +02:00
a24c1059c8 minor changes for deployment 2025-10-03 10:25:04 +02:00
d7ddb0a88c Ingress changed to be publicly accessible 2025-10-03 08:10:43 +02:00
dd75bd20c4 New preload data 2025-10-03 00:40:37 +02:00
6c1b4938cf Merge branch 'development' 2025-10-02 14:26:57 +02:00
23f6c9bb31 Changed class name "Standards" to "Dokumente" 2025-10-02 14:18:11 +02:00
53c828c77f Deploy 0.923 2025-10-01 22:44:24 +02:00
412a5f3824 Merge pull request 'development' (#1) from development into main
Reviewed-on: #1
2025-10-01 20:36:58 +00:00
931131b8e6 Removed django debug toolbar 2025-10-01 22:34:49 +02:00
506b40db6c Removed debug toolbar 2025-10-01 22:33:53 +02:00
55 changed files with 3916 additions and 359 deletions

6
.gitignore vendored
View File

@@ -1,4 +1,3 @@
r0126.txt
__pycache__/
**/*.pyc
lib/
@@ -7,5 +6,10 @@ bin/
pyvenv.cfg
include/
keys/
.venv/
.idea/
*.kate-swp
# Diagram cache directory
media/diagram_cache/

View File

@@ -29,5 +29,6 @@ RUN rm -rf /app/Dockerfile* \
/app/data-loader \
/app/keys \
/app/requirements.txt
RUN python3 manage.py collectstatic
CMD ["gunicorn","--bind","0.0.0.0:8000","--workers","3","VorgabenUI.wsgi:application"]

View File

@@ -0,0 +1,105 @@
# Diagram POST Caching Implementation
This feature replaces the URL-encoded GET approach for diagram generation with POST requests and local filesystem caching.
## Changes Overview
### New Files
- `diagramm_proxy/__init__.py` - Module initialization
- `diagramm_proxy/diagram_cache.py` - Caching logic and POST request handling
- `abschnitte/management/commands/clear_diagram_cache.py` - Management command for cache clearing
### Modified Files
- `abschnitte/utils.py` - Updated `render_textabschnitte()` to use caching
- `.gitignore` - Added cache directory exclusion
## Configuration Required
Add to your Django settings file (e.g., `VorgabenUI/settings.py`):
```python
# Diagram cache settings
DIAGRAM_CACHE_DIR = 'diagram_cache' # relative to MEDIA_ROOT
# Ensure MEDIA_ROOT and MEDIA_URL are configured
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
```
### URL Configuration
Ensure media files are served in development. In your main `urls.py`:
```python
from django.conf import settings
from django.conf.urls.static import static
# ... existing urlpatterns ...
# Serve media files in development
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
```
## How It Works
1. When a diagram is rendered, the system computes a SHA256 hash of the diagram content
2. It checks if a cached SVG exists for that hash
3. If cached: serves the existing file
4. If not cached: POSTs content to Kroki server, saves the response, and serves it
5. Diagrams are served from `MEDIA_URL/diagram_cache/{type}/{hash}.svg`
## Benefits
- **No URL length limitations** - Content is POSTed instead of URL-encoded
- **Improved performance** - Cached diagrams are served directly from filesystem
- **Reduced server load** - Kroki server is only called once per unique diagram
- **Persistent cache** - Survives application restarts
- **Better error handling** - Graceful fallback on generation failures
## Usage
### Viewing Diagrams
No changes required - diagrams will be automatically cached on first render.
### Clearing Cache
Clear all cached diagrams:
```bash
python manage.py clear_diagram_cache
```
Clear diagrams of a specific type:
```bash
python manage.py clear_diagram_cache --type plantuml
python manage.py clear_diagram_cache --type mermaid
```
## Testing
1. Create or view a page with diagrams
2. Verify diagrams render correctly
3. Check that `media/diagram_cache/` directory is created with cached SVGs
4. Refresh the page - second load should be faster (cache hit)
5. Check logs for cache hit/miss messages
6. Test cache clearing command
## Migration Notes
- Existing diagrams will be regenerated on first view after deployment
- The old URL-based approach is completely replaced
- No database migrations needed
- Ensure `requests` library is installed (already in requirements.txt)
## Troubleshooting
### Diagrams not rendering
- Check that MEDIA_ROOT and MEDIA_URL are configured correctly
- Verify Kroki server is accessible at `http://svckroki:8000`
- Check application logs for error messages
- Ensure media directory is writable
### Cache not working
- Verify Django storage configuration
- Check file permissions on media/diagram_cache directory
- Review logs for cache-related errors

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,337 @@
>>>Einleitung
>>>text
Dieser Standard unterliegt den Definitionen vom Zentraldokument für Standard IT-Sicherheit. Ziel & Zweck, Änderungswesen etc. sind da definiert und werden hier nicht ein zweites Mal aufgeführt.
>>>geltungsbereich
>>>text
Dieser Standard dient als Umsetzungsanforderung für Richtlinien IT-Sicherheit zu Serversystemen im BIT. Betroffen sind von diesem Standard grundsätzlich alle Serversysteme . Einzelne, explizite Ausschlüsse werden in den Anforderungen spezifiziert
>>>Vorgabe Organisation
>>>Nummer 1
>>>Titel
R0009.2.1 CMDB-Erfassung
>>>Kurztext
>>>Text
Die Konfigurationsmanagement-Datenbank stellt die integrierte Informationsgrundlage für das Service-Management BIT sicher und ist das IT-Repository fürs IT-Sicherheitsmanagement.
>>>Langtext
>>>Text
Jedes Asset (Server, Gateway, Router, Switch usw.), dass sich im BIT-Netz befindet, muss in einer möglichst standardisierten Form dokumentiert sein. Ist die Betriebsverantwortung bei einem anderen Leistungserbringer, ist dies entsprechend zu dokumentieren und einzufordern.
>>>Vorgabe Technik
>>>Nummer 1
>>>Titel
Dienste und Protokolle
>>>Kurztext
>>>Text
Nicht benötigte Dienste und Protokolle deaktivieren
>>>Langtext
>>>Text
Nach einer Standardinstallation von Systemen und Softwareprodukten sind typischerweise lokale oder aus dem Netz erreichbare Dienste und Protokolle aktiv, die für den Betreib und die Funktionalität des Systems nicht notwendig sind. Hierzu gehören auch Dienste und Protokolle, die aufgrund ihrer bekannten Sicherheits-Schwachstellen, die gegebenen falls für die Verletzung von Schutzzielen wie Vertraulichkeit, Integrität und Verfügbarkeit genutzt werden können. Solche Dienste (wie z.B. SSL anstatt TLS) und Protokolle müssen auf einem System vollständig deaktiviert sein (System-Härtung). Dabei ist es wichtig zu beachten, dass die Deaktivierung auch nach einem Neustart des Systems bestehen bleibt.
Als Referenz für System-Härtung sollen die spezifischen Hersteller Sicherheits-Konfigurationen und weitere Referenzen wie CIS-Workbench angewendet werden.
>>>Vorgabe Technik
>>>Nummer 2
>>>Titel
Einschränkungen Tools auf Serversystemen
>>>Kurztext
>>>Text
Es dürfen keine Entwicklungstools, wie Compiler und Debugger, sowie Source Code Repositories auf einem produktiven Server vorhanden sein.
>>>Langtext
>>>Text
Es dürfen keine Entwicklungstools, wie Compiler und Debugger, sowie Source Code Repositories auf einem produktiven Server vorhanden sein. Falls diese temporär benötigt werden (z.B. in einer krisenbedingten Restore-Situation) dürfen sie nur punktuell und so lange wie nötig auf den Systemen installiert werden.
>>>Vorgabe Technik
>>>Nummer 3
>>>Titel
Erreichbarkeit von Diensten
>>>Kurztext
>>>Text
Dienste können nur eingeschränkt erreichbar sein und die Konfiguration darf nur autorisiert erfolgen.
>>>Langtext
>>>Text
In der Regel sind aktivierte Dienste in der Grundkonfiguration über alle verfügbaren Schnittstellen eines Systems erreichbar und können von anderen Systemen in den angeschlossenen Netzen erreicht werden. Diese Erreichbarkeit ist funktional weder notwendig noch sinnvoll. Daher dürfen auf einem System die Dienste nur auf Schnittstellen aktiviert werden, auf denen deren Nutzung erforderlich ist. Auf den Schnittstellen, auf denen einen Dienst aktiv ist, muss dessen Erreichbarkeit auf legitime Kommunikationspartner eingeschränkt werden. Diese Einschränkung muss lokal, also ohne zusätzliche netzseitige Massnahmen, wie z. B. eine Firewall, erfolgen
Dies kann zum Beispiel durch gegenseitige Authentisierung der Serversysteme mittels Zertifikats oder Key-Austausch erreicht werden.
>>>Vorgabe Technik
>>>Nummer 4
>>>Titel
Lokale Firewalls anwenden
>>>Kurztext
>>>Text
Vorgeschaltete Firewwalls bieten keinen umfassenden Schutz.
>>>Langtext
>>>Text
Zonen Übergreifende Firewall oder Netz-Segmentierung sind keine Garantie für eine effiziente Einschränkung an nur berechtigte Zugriffe.
Ein lokaler Paketfilter (Firewall) stellt sicher, dass Dienste, insbesondere Management-Dienste, nur an den erforderlichen Schnittstellen erreichbar sind.
Ist auch Teil der Grund-Konfiguration durch System-Härtung (R0009.T.1).
>>>Vorgabe Informationen
>>>Nummer 1
>>>Titel
BIT-Externer Betrieb
>>>Kurztext
>>>Text
Die Verschlüsselung der Datenträger soll wo immer möglich verwendet werden. Es ist ein MUSS wo keine angepasste physische Zugriff Sicherheit gesichert werden kann.
>>>Langtext
>>>Text
Falls sich das System nicht in einem BIT betriebenen RZ oder in einem abgesicherten Raum mit mindestens «Sehr hohem Schutzbedarf» befindet, müssen verwendete Datenträger vollständig verschlüsselt sein.
Der Schutzbedarf oder auch die Schutzklassen (SK) sind in der (Richtlinie Informationssicherheit)[https://community.bit.admin.ch/team/eabit/Private/iktvorgaben/IKTVorgaben%20genehmigt/R0135_V5.2.pdf] BIT definiert.
>>>Vorgabe Informationen
>>>Nummer 2
>>>Titel
Vermeiden von Überlastsituationen
>>>Kurztext
>>>Text
Das System muss sich gegen Überlastsituationen schützen
>>>Langtext
>>>Text
Ein System muss über Schutzmechanismen verfügen, die Überlastsituationen soweit wie möglich verhindern. Insbesondere ist eine partielle oder komplette Beeinträchtigung der Verfügbarkeit des Systems zu vermeiden. Beispiele für mögliche Schutzmassnahmen sind:
>>>Liste-ungeordnet
Begrenzung auf Netzwerkzonen
Begrenzung des pro Anwendung verfügbaren Arbeitsspeichers
Begrenzung der maximalen Sessions einer Web-Anwendung
Festlegen der maximalen Grösse eines Datensatzes
Begrenzung von CPU-Ressourcen pro Prozess
Priorisieren von Prozessen
>>>Text
Begrenzung der Anzahl oder der Grösse von Transaktionen eines Benutzers oder von einer IP-Adresse in einem bestimmten Zeitraum.
>>>Vorgabe Informationen
>>>Nummer 3
>>>Titel
Reaktion auf Überlastsituationen
>>>Kurztext
>>>Text
Falls eine Überlastsituation nicht verhindert werden kann muss sich das System berechenbar verhalten.
>>>Langtext
>>>Text
Ein System muss so konzipiert sein, dass es mit Überlastsituationen in kontrollierter Weise umgeht. Trotzdem kann es zu Situationen kommen, bei denen die Schutzmassnahmen gegen Überlastungen nicht mehr
ausreichend sind.
In einem solchen Fall muss sichergestellt sein, dass das System nicht in einen undefinierten und möglicherweise unsicheren Zustand gerät. Dies kann im Extremfall bedeuten, dass ein kontrolliertes Herunterfahren des Systems eher hinnehmbar ist als ein unkontrolliertes Versagen der Sicherheitsfunktionen und somit ein Verlust des
Systemschutzes.
>>>Vorgabe Informationen
>>>Nummer 4
>>>Titel
Dynamische Inhalte
>>>Kurztext
>>>Text
Zunehmende (dynamische) Inhalte dürfen Systemfunktionen nicht beeinträchtigen.
>>>Langtext
>>>Text
Zunehmende Logdaten oder Uploads dürfen die Funktionalität des Systems nicht beeinträchtigen.
>>>Vorgabe Informationen
>>>Nummer 5
>>>Titel
IP-Schnittstellen
>>>Kurztext
>>>Text
Das System darf keine IP-Pakete verarbeiten, deren Absenderadresse nicht über die Schnittstelle erreicht wird, an der das Paket eingegangen ist.
>>>Langtext
>>>Text
Es muss darauf geachtet werden, dass Systeme nicht über unnötige Default-Routen verfügen, was z. B. der Fall ist, wenn Systeme nur intern verwendet werden.
>>>Text
In einer solchen Konstellation handelt es sich i. d. R. um Pakete mit gefälschten Absenderadressen oder einen Fehler im Routing. Das eintreffende Paket muss als nicht vertrauenswürdig verworfen werden, Ein Umsetzungsbeispiel: Die Verwendung der Funktion «Reverse Path Filter» (RPF), die dafür sorgt, dass genau solche Pakete verworfen werden.
>>>Vorgabe Informationen
>>>Nummer 6
>>>Titel
Disaster Recovery Plan
>>>Kurztext
>>>Text
Ein Wiederanlaufplan / Disaster Revovery Plan muss beschrieben und sichergestellt sein.
>>>Langtext
>>>Text
Wiederaufsetz- und Neustart Prozeduren im Fehlerfall, sowie Notfallpläne müssen dokumentiert sein. Die Vorbereitung und das Testen von Routine-Betriebsabläufen müssen nach festgelegten Standards erfolgen. Definierte Sicherheitsmassnahmen müssen nachweislich wirksam implementiert sein. Verfahren zur Sicherstellung des Geschäftsbetriebs müssen definiert und geregelt sein.
>>>Vorgabe Informationen
>>>Nummer 7
>>>Titel
Partitionierung
>>>Kurztext
>>>Text
Partitionen müssen, wenn möglich, getrennt werden.
>>>Langtext
>>>Text
Daten-, Applikations- und Systempartitionen müssen, wenn möglich, getrennt werden. Eine Trennung der Daten liefert einen zusätzlichen Sicherheitslayer, sollte es zu Systemausfällen oder Malware-Befall kommen.
Abhängig vom Schutzbedarf sollte auch entsprechend sichergestellt werden, dass die einzelnen Partitionen mit einer RAID-Funktionalität geschützt werden.
>>>Vorgabe Systeme
>>>Nummer 1
>>>Titel
Softwareinstallation
>>>Kurztext
>>>Text
Nicht benötigte (autorisierte) Software darf nicht installiert oder muss deinstalliert werden
>>>Langtext
>>>Text
Bei der Installation eines Systems werden oftmals Software-Komponenten installiert oder auch einzelne Teile einer Software aktiviert, die für den Betrieb und die Funktion des Systems nicht notwendig sind. Hierzu zählen auch Teile einer Software, die als Anwendungsbeispiele (z. B. Default-Web-Seiten, Beispieldatenbanken, Testdaten) installiert werden, aber typischerweise nicht verwendet werden. Solche Komponenten dürfen entweder bei der Installation nicht mit installiert werden oder müssen im Anschluss an die Installation gelöscht werden. Des Weiteren ist es nicht erlaubt Software auf einem System zu installieren, die nicht für den Betrieb, die Wartung oder Funktion des Systems notwendig ist
>>>Vorgabe Systeme
>>>Nummer 2
>>>Titel
Softwarefunktionen
>>>Kurztext
>>>Text
Nicht benötigte Funktionen der eingesetzten Software und Hardware müssen deaktiviert werden
>>>Langtext
>>>Text
Bei der Installation von Software und Hardware werden oftmals Funktionen aktiviert, die nicht für den Betrieb und die Funktionalität des Systems notwendig sind. Funktionen der Software sind meistens ein fester Bestandteil, der nicht einzeln gelöscht oder deinstalliert werden kann. Solche Funktionen müssen über die Konfiguration oder Einstellungen dauerhaft deaktiviert werden
Neben Funktionen der Software sind nach der Systeminstallation oftmals Hardware-Funktionen aktiviert, die nicht für den Einsatz des Systems benötigt werden. Solche Funktionen, wie beispielsweise nicht benötigte Schnittstellen, müssen dauerhaft deaktiviert werden, so dass sie auch nach einem Neustart deaktiviert bleiben.
>>>Vorgabe Systeme
>>>Nummer 3
>>>Titel
Netzwerkprotokolle
>>>Kurztext
>>>Text
Die IPv4-/IPv6-Adressen aller Schnittstellen eines Servers müssen fest konfiguriert werden
>>>Langtext
>>>Text
IP-Adressen, auf denen Dienste angeboten werden, dürfen nicht durch äussere Einflüsse verändert werden können, auch nicht bei einem erzwungenen Reboot. Eine automatische Zuweisung von IP-Adressen, z. B. mittels DHCPv4/v6 oder IPv6-Autokonfiguration, ist nur dann zulässig, wenn sie nach initialer Vergabe der Adresse(n) abgeschaltet oder anderweitig abgesichert wird. IPv6 Router Advertisements müssen ignoriert werden.
Es wird empfohlen den Host-Anteil der IPv6-Adressen zufällig zu bilden, da auf Grund des sehr grossen Adressbereiches von IPv6 ein Auffinden von Systemen für einen Angreifer durch Scans sehr aufwändig ist.
Nicht benutzte Protokolle (z.B. IPv6) können komplett abgeschaltet werden.
>>>Vorgabe Systeme
>>>Nummer 4
>>>Titel
Netzwerkfunktionen
>>>Kurztext
>>>Text
Netzfunktionen im Betriebssystemkern, die für den Betrieb als Server nicht benötigt werden, müssen abgeschaltet werden (Kernel Parameter).
>>>Langtext
>>>Text
Ein Server braucht nicht zu routen, daher muss die Routing-Funktion abgeschaltet sein. Ebenso muss das
Antworten auf Broadcast-ICMP-Pakete abgeschaltet sein. Diese und weitere Netzfunktionen sind normalerweise bereits im Auslieferungszustand korrekt gesetzt.
>>>Vorgabe Systeme
>>>Nummer 5
>>>Titel
Autorun-Funktionen
>>>Kurztext
>>>Text
Das automatische Starten von Anwendungen auf Wechseldatenträgern muss abgeschaltet werden.
>>>Langtext
>>>Text
Wechseldatenträger etwa CD-, DVD-, USB-Sticks oder USB-Laufwerke dürfen darauf enthaltene Anwendungen nicht automatisch starten.
>>>Vorgabe Systeme
>>>Nummer 6
>>>Titel
Verarbeitung von transferierten Daten
>>>Kurztext
>>>Text
Die Verarbeitung von ICMPv4-/ICMPv6-Paketen, die für den Betrieb nicht benötigt werden, muss deaktiviert werden.
>>>Langtext
>>>Text
Es gibt verschiedene Typen von ICMPv4 und ICMPv6, die in den meisten Netzen nicht verwendet werden, aber ein potentielles Risiko darstellen. Diese Typen müssen deaktiviert oder gefiltert werden.
>>>text
Folgende ICMP-Typen sind erlaubt und dürfen genutzt werden:
>>>Liste ungeordnet
Echo Request [Type 8 (v4), Type 128 (v6)]
Echo Reply [Type 0 (v4), Type 129 (v6) ]
Destination Unreachable [Type 3 (v4), Type 1 (v6)]
Time Exceeded [Type 11 (v4), Type 3 (v6)]
Parameter Problem [Type 12 (v4), Type 4 (v6)]
Packet Too Big [Type 2 (nur v6)]
Neighbor Solicitation [Type 135 (nur v6)]
Neighbor Advertisement [Type 136 (nur v6)]
>>>text
Es besteht die Möglichkeit, dass weitere Typen notwendig sind. Dies ist im Einzelfall zu prüfen.
In *keinem Fall* dürfen beantwortet oder verarbeitet werden:
>>>Liste ungeordnet
Timestamp Reply [Type 14 (v4)]
Netmask Reply [Type 18 (v4)]
Information Reply [Type 16 (v4)]
Redirect [Type 5 (v4), Type 137 (v6)]
Router Solicitation [Type 133 (v6)]
Router Advertisement [Type 134 (v6)]
>>>Vorgabe Systeme
>>>Nummer 7
>>>Titel
IP-Headers
>>>Kurztext
>>>Text
IP-Pakete mit nicht benötigten Optionen oder Erweiterungs-Headern dürfen nicht bearbeitet werden.
>>>Langtext
>>>Text
IP Optionen und Erweiterungs-Header (z. B. Source Routing) werden nur in seltenen Ausnahmefällen benötigt. Somit sind alle Pakete mit gesetzten IP-Optionen und Erweiterungs-Headern auf Standard-Servern zu filtern.
>>>Vorgabe Systeme
>>>Nummer 8
>>>Titel
Default-Konten
>>>Kurztext
>>>Text
Vordefinierte Konten müssen gelöscht oder deaktiviert werden.
>>>Langtext
>>>Text
Auf vielen Systemen existieren vordefinierte Konten (z. B. Gast, Admin) die teilweise ohne oder mit bekannten Passwörtern vorkonfiguriert sind. Diese Standardbenutzer müssen gelöscht oder deaktiviert werden. Sollten diese Massnahmen nicht umsetzbar sein, so sind solche Konten für einen Fernzugriff zu sperren. In jedem Fall müssen gesperrte und deaktivierte Konten mit einem möglichst komplexen Passwort (12 Zeichen und mehr, Nutzung von Gross-/ Kleinbuchstaben, Zahlen und Sonderzeichen) versehen werden, so dass auch im Falle eine Fehlkonfiguration die unberechtigte Nutzung eines solchen Kontos verhindert wird.
Ausgenommen von der Anforderung, Konten zu löschen oder zu deaktivieren, sind Konten, die ausschliesslich der internen Nutzung auf dem entsprechenden System dienen und die für die Funktionalität einer oder mehrerer Anwendungen des Systems notwendig sind. Auch für ein solches Konto muss sichergestellt werden, dass ein Fernzugriff oder eine lokale Anmeldung nicht möglich ist und dass ein Benutzer des Systems ein solches Konto nicht missbräuchlich nutzen kann.
>>>Vorgabe Anwendungen
>>>Nummer 1
>>>Titel
Wartung und Pflege
>>>Kurztext
>>>Text
Software- und Hardware-Komponenten, für die es keine Wartung oder Pflege durch den Lieferanten, Hersteller oder Entwickler gibt, dürfen nicht verwendet werden
>>>Langtext
>>>Text
Es dürfen auf einem System nur Betriebssystem-, Middleware- und Anwendungs-Software sowie Hardware- Komponenten eingesetzt werden, für die ein Support durch Lieferanten, Hersteller, Entwickler oder anderen Vertragspartner besteht. Komponenten die End-of-Life oder End-of-Support sind dürfen nicht eingesetzt werden. Ausgenommen hiervon sind Komponenten für die ein spezieller Support-Vertrag abgeschlossen wurde, durch den auch über den Lebendzyklus des Produkts hinaus die Behebung von Sicherheitsschwachstellen gewährleistet ist und von SI-SUR so genehmigt wurdeWeiterführende Informationen
>>>Vorgabe Anwendungen
>>>Nummer 2
>>>Titel
Schwachstellen
>>>Kurztext
>>>Text
Bekannt gewordene Schwachstellen in der Software oder Hardware des Systems müssen behoben oder abgesichert werden
>>>Langtext
>>>Text
Vor der Installation einer Software- oder auch Hardware-Komponente muss überprüft werden, ob bereits Schwachstellen in der einzusetzenden Version gefunden und veröffentlicht wurden. Sollte die entsprechende Komponente von einer Schwachstelle betroffen sein, darf sie nicht installiert oder verwendet werden. Eine Ausnahme hiervon sind Komponenten, für die bereits eine Massnahme zum Beheben der Schwachstelle wie z. B. ein Patch, ein Update oder ein Workaround vom Hersteller zur Verfügung gestellt wurde. In diesem Fall muss die zusätzliche Massnahme auf dem System umgesetzt werden. Zudem ist dies ein fortlaufender Prozess während des kompletten Life-Cycles des Systems, um auftretende Schwachstellen zeitnah zu beheben.
Zeitvorgaben für das beheben von entsprechenden Schwachstellen werden vom SI-SUR-CSIRT vorgegeben
>>>Vorgabe Zonen
>>>Nummer 1
>>>Titel
Abgesicherte Räume
>>>Kurztext
>>>Text
Falls sich das System nicht in einem BIT-betriebenen abgesicherten Raum (RZ) befindet, muss der Schutzbedarf «sehr hoher Schutzbedarf (SN3)» gewährleistet werden. Das BIOS muss vor nicht autorisierten Veränderungen geschützt werden.
>>>Langtext
>>>Text
Server, die öffentlich oder in Räumlichkeiten von Kunden installiert sind, müssen besonders vor unautorisiertem Zugriff und Veränderungen geschützt werden: Es müssen die Einstellungen des BIOS gegen Auslesen und
Manipulation geschützt werden. Bei Verwendung eines Passworts muss dieses exklusiv für den einzelnen Server sein und darf keine Rückschlüsse auf ein Unterscheidungsmerkmal des Servers ermöglichen.
Das BIOS muss so konfiguriert sein, dass sich darüber ausschliesslich das vorgesehene Betriebssystem von der dafür vorgesehenen Partition starten lässt

View File

@@ -0,0 +1,629 @@
>>>Einleitung
>>>text
Hier ist die Einleitung
>>>geltungsbereich
>>>text
Container-Sicherheit startet bei fundamentalen Themen wie dem Härten der Systeme, wobei der gesamte Container-Stack hierzu sinnvollerweise in Schichten
zu betrachten ist. Ausgehend von einer Planungsphase müssen alle vorhandenen Schichten des Container-Stacks betrachtet und gehärtet werden.
>>>text
Wir unterscheiden die untenstehenden Schichten:
>>>liste geordnet
Container Host
Container Runtime
Container Registry
Container Images
Container Orchestrator
Persistent Storage
>>>text
In diesem Standard geht es ausschliesslich um die Sicherheitsanforderung zur Einrichtung und Betrieb von Containern (Schicht 2-5). Er konkretisiert den IT-Grundschutz und ergänzt den Standard IT-Sicherheit Serversysteme und die jeweiligen Richtlinien der IT-Sicherheit um Spezifika von Containern. Die Anforderungen der erwähnten Richtlinien sollten von den Container-Hosts (Schicht 1) erfüllt werden, unabhängig davon, ob diese selbst auf physischen Servern ausgeführt werden oder virtualisiert sind. Sicherheitsanforderungen möglicher Server-Funktionen wie Webserver oder Groupware usw. sind Gegenstand eigener Sicherheitsrichtlinien. Der Schwerpunkt des Standards IT-Sicherheit Container liegt auf dem Betrieb von Container-Virtualisierung. Die Installation von Anwendungen innerhalb von Containern wird darin nicht vollständig abgedeckt.
>>>Vorgabe Organisation
>>>Titel
Resilienz
>>>Nummer 1
>>>Kurztext
>>>Text
Es muss davon ausgegangen werden, dass nicht immer alle Kommunikationspartner zur Verfügung stehen. Dies muss bereits im Design eines Microservices berücksichtigt werden.
>>>Langtext
>>>Text
In einem verteilten System können Netzwerkprobleme auftreten, z. B. Verzögerungen, Paketverluste oder temporäre Verbindungsabbrüche. Ein Microservice sollte daher darauf vorbereitet sein, dass andere Services zeitweise nicht erreichbar sind oder langsamer reagieren. Wenn jeder Service darauf angewiesen ist, dass alle anderen ständig verfügbar sind, entsteht eine starke Kopplung, die den Vorteil der Microservices-Architektur untergräbt. Services sollten unabhängig voneinander laufen und auch dann ihre Kernfunktionen erfüllen können, wenn andere Teile des Systems ausfallen.
Indem man von vornherein davon ausgeht, dass nicht alle Services jederzeit verfügbar sind, wird die Gesamtarchitektur stabiler, flexibler und besser auf reale Betriebsbedingungen vorbereitet. Für jeden Microservice mit Abhängigkeiten muss daher festgelegt werden, wie er auf Nichterreichbarkeit der Abhängigkeiten reagiert (z.B. "Läuft weiter", "arbeitet die Queue ab und nimmt keine neuen Aufträge entgegen", "stoppt sofort alle Prozesse" oder ähnlich).
>>>Vorgabe Organisation
>>>Nummer 2
>>>Titel
Service Discovery
>>>Kurztext
>>>Text
Der Netzwerkstandort einer Microservice-Instanz ist verfügbar und aktuell.
Ein Schlüsselelement bei Service Discovery ist die Service Registry. Weiterhin kennt das Service Discovery alle laufenden Microservices und führt nach, auf welcher IP/Port Kombination diese gerade laufen.
>>>Langtext
>>>Text
Die Verfügbarkeit und Aktualität des Netzwerkstandorts eines Microservices ist ein zentrales Element in Microservices-Architekturen. Mithilfe einer Service Registry und einem effizienten Service Discovery-Mechanismus wird sichergestellt, dass alle Microservices zuverlässig miteinander kommunizieren können, auch in dynamischen und skalierbaren Umgebungen. Dies fördert Resilienz, Flexibilität und Skalierbarkeit des gesamten Systems.
Die Microservices stellen sicher, dass die Service Discovery die entsprechenden Informationen aus den Microservices herauslesen kann.
>>>Vorgabe Organisation
>>>Nummer 3
>>>Titel
Deployment in Container
>>>Kurztext
>>>Text
Als Best Practice gilt ein Microservice pro Container.
>>>Langtext
>>>Text
Das Deployment der Microservices erfolgt in Containern, welche dynamisch bereitgestellt und abgebaut werden, je nach Skalierungsanforderung. Daher gilt als Grundsatz, dass pro Microservice ein eigener Container aufgebaut wird und vice versa in jedem Container ein Microservice läuft. So wird das Deployment neuer Microservices und Microservice Updates vereinfacht.
Zusätzlich reduziert diese Isolation Konflikte zwischen Abhängigkeiten verschiedener Microservices und vereinfacht die Verwaltung.
>>>Stichworte
Deployment, Microservice, Isolation
>>>Vorgabe Technik
>>>Nummer 1
>>>Titel
Implementation
>>>Kurztext
>>>Text
Technologien, die bedingen, dass interne Details exponiert werden, müssen vermieden werden.
>>>Langtext
>>>Text
Wie ein Microservice implementiert ist, darf gegen aussen keine Rolle spielen und sollte als Grundsatz nicht offengelegt werden. Die API muss dokumentiert sein, sollte aber nach Möglichkeit keine Rückschlüsse auf die Implementation zulassen.
>>>Vorgabe Technik
>>>Nummer 2
>>>Titel
Microservice-Aufrufe
>>>Kurztext
>>>Text
Microservices kommunizieren über APIs und benutzen, je nach Anwendungs-Scope, den API-Gateway für die Kommunikation.
>>>Langtext
>>>Text
Microservice-zu-Microservice Aufrufe im Kontext derselben Fachanwendung erfolgen direkt von Microservice zu Microservice und gehen nicht über den API Gateway. Microservice Aufrufe, die den Kontext einer Fachanwendung verlassen, gehen über den API Gateway. Dazu bietet jeder Microservice eine Schnittstelle, die nach Bedarf über einen API Microgateway angeboten werden kann. Die Schnittstellen müssen so realisiert sein, dass ein Microservice nicht direkt von den Implementierungsdetails eines anderen Microservice abhängt, wie z. B. dem Datenmodell in der Datenbank (lose Kopplung). Die Kommunikation zwischen Microservices (auch über API Gateway) muss auf einige Protokolle wie REST oder Messaging begrenzt sein.
>>>Vorgabe Technik
>>>Nummer 3
>>>Titel
Identity Provider
>>>Kurztext
>>>Text
Die Authentisierung erfolgt über den Identity Provider.
>>>Langtext
>>>Text
Benutzer-Anfragen werden via IAM Infrastruktur authentifiziert. Benutzer-Identitäten und -Rollen werden zentral in der IAM-Infrastruktur verwaltet.
>>>Vorgabe Technik
>>>Nummer 4
>>>Titel
Authentisierungs-Token
>>>Kurztext
>>>Text
Bei erfolgreicher Authentisierung wird ein Authentisierungs-Token erstellt und signiert, das den Anfrager bei allen folgenden Serviceaufrufen authentisiert.
>>>Langtext
>>>Text
Bei erfolgreicher Authentisierung wird ein Authentisierungs-Token erstellt und signiert, welches den Anfrager bei allen folgenden Serviceaufrufen authentisiert.
>>>Text
Die Authentisierungs-Tokens haben mindestens folgende Merkmale:
>>>Liste-ungeordnet
Sie müssen eine lokale Validierung durch den Empfänger unterstützen
Sie müssen leichtgewichtig sein (einfach zu parsen, …)
Sie müssen klein sein (und können Teil jeder Anfrage sein)
>>>Text
Die Authentisierungs-Tokens sollten über eine möglichst kurze Lebensdauer verfügen, damit eine regelmässige Re-Authentisierung forciert wird.
>>>Vorgabe Technik
>>>Nummer 5
>>>Titel
Service-zu-Service-Kommunikation
>>>Kurztext
>>>Text
Service-zu-Service-Kommunikation wird mittels technischem User und Client-Zertifikaten authentisiert.
>>>Langtext
>>>Text
Service-zu-Service-Kommunikation wird mittels technischem User und Client-Zertifikaten authentisiert.
Bei Service zu Service Kommunikation kann die Authentisierung und Autorisierung delegiert werden. Welche Service zu Service Kommunikation mittels Delegation erfolgt, muss fallweise bestimmt werden.
>>>Vorgabe Technik
>>>Nummer 26
>>>Titel
Autorisierung durch Microservice und Autorisierungsmechanismen
>>>Kurztext
>>>Text
>>>Langtext
>>>Text
Jeder Microservice muss jeden Request autorisieren (anhand AuthToken), da nur der Microservice selbst die Autorisierung auf die Daten vornehmen kann. Diese Aufgabe kann weder an einen API-Gateway noch an ein Service Mesh delegiert werden.
>>>Liste-ungeordnet
Die Autorisierungsmechanismen haben folgende Merkmale:
Sie müssen Rollenbasiert sein
Sie müssen Genehmigungen / Claims unterstützen
Sie müssen die unabhängige Entwicklung der Microservices unterstützen
Es gibt eine zentrale Übersicht und Verwaltung der Rollen und Genehmigungen / Claims
Sie müssen eine Delegation der Autorisierung unterstützen
>>>Vorgabe Technik
>>>Nummer 7
>>>Titel
Persistenz
>>>Kurztext
>>>Text
Microservices sollen ihre Daten selbst führen.
>>>Langtext
>>>Text
In der Regel wird ein Data Store pro Microservice geführt. Mit welcher Art von Datenspeicherung (Relationale Datenbank, Distributed Datenbank, In-Memory Data Store) die Persistenz realisiert wird, ist abhängig von den Anforderungen der Microservices sowie den Technologievorgaben des BIT. Die Abläufe, die diese Data Stores verwalten, sind separate Services.
>>>Vorgabe Technik
>>>Nummer 8
>>>Titel
Message Oriented Middleware / Event Broker Services
>>>Kurztext
>>>Text
Die Kommunikation zwischen Microservices erfolgt über die MOM. Events werden mit dedizierten Event Broker Services abgefangen und propagiert.
>>>Langtext
>>>Text
Die Message Oriented Middleware (MOM) muss hochverfügbar sein und einen hohen Durchsatz bieten. Einen Datenabgleich zwischen den verschiedenen Microservices und ihren Data Stores erfolgt Event basiert über die MOM.
Eine Event-basierende Kollaboration führt dazu, dass Geschäftslogik nicht zentralisiert, sondern verteilt ist. In einer Event-basierenden Kollaboration sind die Microservices in hohem Masse voneinander entkoppelt. Der Event Broker übernimmt somit die Rolle eines Vermittlers.
>>>Vorgabe Technik
>>>Nummer 9
>>>Titel
Correlation IDs
>>>Kurztext
>>>Text
Standardisierte Correlation IDs werden verwendet, damit Microservices-Aufruf-Ketten ausgewertet werden können.
>>>Langtext
>>>Text
In einer Microservice Architektur interagieren in der Regel immer mehrere Microservices, um eine Geschäftsfunktion zu erfüllen. Correlation-IDs werden beim ersten Anruf generiert, in der Kette entlang weitergegeben und jeweils geloggt.
>>>Vorgabe Technik
>>>Nummer 10
>>>Titel
Monitoring und Logging
>>>Kurztext
>>>Text
Alle Services müssen Monitoring-Metriken und Logs in einer einheitlichen Art und Weise (wenn möglich in Standardformaten) abgeben oder zugänglich machen.
>>>Langtext
>>>Text
Alle Services müssen Monitoring-Metriken und Logs in einer einheitlichen Art und Weise (wenn möglich in Standardformaten) abgeben oder zugänglich machen. Das Monitoring greift dabei auf standardisierte Messpunkte in den Elementen der inneren Architektur (Microservices, APIs) zu. Logs müssen standardisiert erfasst werden. Zusammen mit dem Service Mesh (mit Service Discovery / Registry) wird eine dynamische Übersicht der aktuell instanziierten Microservices geliefert. Auch Metadaten, z. B. zur Authentifizierung müssen standardisiert sein.
Das Monitoring muss ein Synthetic (Semantic) Monitoring unterstützen. Das bedeutet, es muss regelmässig eine Submenge der automatisierten Fachanwendungstests in der Produktionsumgebung durchgeführt werden können. Die Resultate daraus werden in das Monitoring und Event Management eingespielt, welches bei Fehlerfall Alerts ausführt.
>>>Text
Folgende weitere Punkte müssen minimal berücksichtigt werden:
>>>Liste-ungeordnet
Antwortzeiten und Fehlerraten von Service-Aufrufen werden aufgezeichnet.
Antworten von Downstream-Aufrufen (weiterführende Service-Aufrufe) werden aufgezeichnet. Im Minimum die Antwortzeiten der weiterführenden Service-Aufrufe.
Das darunterliegende OS und dessen Prozesse müssen überwacht werden (Host), damit eine Kapazitätsplanung möglich wird.
Host-Level, Microservices-Level und System-Level-Metriken können aggregiert werden.
Die Metriken müssen lange genug verfügbar bleiben, damit Trends erkannt werden können.
>>>Text
Logs müssen gespeichert und ausgewertet resp. aggregiert werden können. Dabei sind die entsprechenden Vorgaben betreffend die zentrale Logging-Infrastruktur und die übrigen zu protokollierenden Daten zu berücksichtigen.
>>>Vorgabe Technik
>>>Nummer 11
>>>Titel
Sicherheit des Basis-Images
>>>Kurztext
>>>Text
Alle nicht benötigten Bestandteile der Software, die im Container ausgeführt wird, müssen deinstalliert werden. Die Konfiguration der Software muss gehärtet werden. Images externer Lieferanten müssen einen staging-Prozess durchlaufen.
>>>Langtext
>>>Text
Die für den Container verwendeten Basis-Images müssen bekannt sein und dessen Sicherheits- und Schwachstellenstatus bewertet werden.
Basis-Images müssen aus für das BIT vertrauenswürdigen Quellen stammen und regelmässig aktualisiert und mit den aktuellsten Sicherheits-Patches versehen werden.
Das Image darf nur die für die Lösung/Anforderungen erforderlichen Package-Komponenten/Bibliotheken/Hilfsprogramme enthalten.
_Hinweis:_ Aus Gründen der Unterstützung (Support-Verträge) durch Red Hat empfehlen wir, nur Red Hat UBI-Images als Basis-Images für selbst entwickelte Anwendungen zu verwenden. Es gibt kleinere Versionen von UBI, und es gibt UBI-Images, die Middleware-Pakete und von Red Hat unterstützte Sprachframeworks enthalten. Es gibt keine allgemeine Sicherheitsempfehlung für oder gegen UBI; entscheidend ist die Reduzierung der Angriffsfläche. Ein weiterer Vorteil eines Einsatzes von RedHat Images ist, dass sie VEX Files und die entsprechende Analyse mitliefern, was die Beurteilung von Vulnerabilities stark beschleunigt und unterstützt: https://www.cisa.gov/sites/default/files/2023-01/VEX_Use_Cases_Aprill2022.pdf
Für die Anlieferung von Container Images externer Lieferanten muss ein dedizierter Staging Prozess verwendet werden, damit potentielle Schwachstellen frühzeitig erkannt und behoben werden können. Erst nach erfolgreichem Durchlaufen dieses Prozesses dürfen solche externe Container Images in den BIT internen Entwicklungsprozess und in produktive Umgebungen eingefügt werden.
>>>Vorgabe Technik
>>>Nummer 12
>>>Titel
Orchestrator-Installation nur aus offiziellen und vertrauten Quellen
>>>Kurztext
>>>Text
Die Software des Orchestrators muss von einer offiziellen und vertrauten Quelle stammen.
>>>Langtext
>>>Text
Die Software des Orchestrators muss von einer offiziellen und vertrauten Quelle stammen. Dies muss mit einer geeigneten Checksumme oder einem geeigneten Hash mit dem Softwarelieferanten gegengeprüft werden.
>>>Vorgabe Technik
>>>Nummer 13
>>>Titel
Hochverfügbarkeit (HA)
>>>Kurztext
>>>Text
Der Container Orchestrator soll die Verfügbarkeit der Container ihrem Schutzbedarf entsprechend sicherstellen.
>>>Langtext
>>>Text
Der Container Orchestrator sollte alle Container mit hohen oder sehr hohen Anforderungen an die Verfügbarkeit bei Ausfall von einem oder mehrere Knoten automatisch auf noch verfügbaren Knoten neu starten
Der Orchestrator muss für HA, bzw. automatisches Failover konzipiert, bzw. konfiguriert sein.
>>>Vorgabe Technik
>>>Nummer 14
>>>Titel
Integritätsschutz für Container Images
>>>Kurztext
>>>Text
Die Integrität der Container-Images muss gewährleistet und überprüfbar sein.
>>>Langtext
>>>Text
Alle angelieferten Container Images müssen entweder signiert (nicht self-signed) sein oder der Hashwert des Container Images muss separat mitgeliefert oder auf der Webseite des Herstellers abrufbar sein.
Wenn eine digitale Signatur vorhanden ist, muss diese geprüft werden, um seine Integrität und Authentizität sicherzustellen. Wenn der Container statt nach Tag nach Hash gezogen wird, ist eine Prüfung nicht erforderlich.
>>>Vorgabe Technik
>>>Nummer 15
>>>Titel
Durchführung von Image Rebuilds
>>>Kurztext
>>>Text
Updates von Packages innerhalb eines Containers sind nicht erlaubt.
>>>Langtext
>>>Text
Es ist nicht erlaubt, Updates von Packages durchzuführen, wenn diese Packages Bestandteil des Container Images sind. Der Grund liegt darin, dass falls eine Update Instruktion in einem Dockerfile vorhanden ist, derselbe Update Layer verwendet wird, der sich im Cache befindet. Dies verhindert, dass ein neueres Update nicht Bestandteil von nachfolgenden Builds wird. Dies gilt ebenso für applikatorische Build Images, diese müssen neu gebildet werden und dürfen nicht in einem laufenden Container gepatched werden.
>>>Vorgabe Technik
>>>Nummer 16
>>>Titel
Verwendung von expliziten Versionen bei Basis-Images
>>>Kurztext
>>>Text
Images sollen mit expliziten Versionen geführt werden.
>>>Langtext
>>>Text
Es muss jederzeit verifizierbar sein, welche Versionen der Images im Einsatz sind, dies um die Nachvollziehbarkeit und Reproduzierbarkeit der Images sicher zu stellen. Es muss eine explizite Version angegeben werden. Z. B. ist die Verwendung von Tags wie "latest" nicht zulässig.
Von einer Verwendung des "Stable"-Tags wird ebenfalls abgeraten, da die Absenz einer expliziten Versionsnummern potenzielle Inkompatibilitäten zur Folge hat, und da eine Inventarisierung nach Versionsnummern so erschwert wird.
>>>Vorgabe Technik
>>>Nummer 17
>>>Titel
Container-Konfiguration
>>>Kurztext
>>>Text
Die Konfiguration des Containers mit den Sicherheitsanforderungen der Applikation übereinstimmen.
>>>Langtext
>>>Text
Die Konfiguration des Containers muss auf offene Ports, Volume-Mounts, Umgebungsvariablen, Einstiegspunkt, etc. geprüft werden und ob diese mit der beabsichtigten Funktionalität und den Sicherheitsanforderungen übereinstimmen.
>>>text
Folgende Vorgaben müssen immer umgesetzt sein:
>>>Liste-geordnet
Um potentielle Attacken zu minimieren dürfen nicht verwendete Ports nicht exponiert werden. Es dürfen nur Ports exponiert werden, die auch von der Anwendung gebraucht werden.
Der Port 22 darf nicht verwendet werden. Auch ist die interaktive Verwendung von SSH in der Produktion nur mit einer Ausnahmebewilligung erlaubt.
Unter 1024 dürfen nur die Standard-Ports verwendet werden. Über 1024 sollten Standard-Ports verwendet werden (siehe Service Name and Transport Protocol Port Number Registry (iana.org)).
Die Default Security Context Constraints (SCC) unter RedHat dürfen nicht verändert werden (Creating security context constraints).
>>>Vorgabe Technik
>>>Nummer 18
>>>Titel
Health-Check
>>>Kurztext
>>>Text
Die Anweisung "HEALTHCHECK" oder ähnliche Funktionalität muss jedem Container-Image hinzugefügt sein.
>>>Langtext
>>>Text
Jeder Container muss über eine Funktionalität verfügen, über welche die aktuelle "Gesundheit" des Containers abgefragt werden kann. Diese Funktionalität muss von der Orchestrierungsinfrastruktur gelesen und ausgewertet werden können. Idealerweise ist die Funktionalität über die Infrastruktur so standardisiert wie möglich.
>>>Vorgabe Technik
>>>Nummer 19
>>>Titel
Konfiguration der Netzwerke
>>>Kurztext
>>>Text
Container-Netzwerkverkehr muss segmentiert werden.
>>>Langtext
>>>Text
Container-Netzwerkverkehr muss mittels Netzwerk Policies beschränkt werden (ein- und ausgehend). Der Ost-West und Nord-Süd-Verkehr muss segmentiert werden, um die Angriffsfläche zu minimieren und den Informationsfluss zu kontrollieren.
Der Datenverkehr innerhalb der Namespaces muss mittels Netzwerk Policies gesteuert werden, um die Kommunikation zwischen Pods auf ein notwendiges Minimum zu beschränken.
>>>Vorgabe Anwendungen
>>>Nummer 1
>>>Titel
Persistenz von Zwischenergebnissen
>>>Kurztext
>>>Text
Zwischenergebnisse, auf die die Anwendungen im Container zugreifen, müssen persistent ausserhalb des Containers gespeichert werden.
>>>Langtext
>>>Text
Zwischenergebnisse, auf die die Anwendungen im Container zugreifen, müssen persistent ausserhalb des Containers gespeichert werden.
Nutzdaten der Anwendung werden in der Regel auf einem Persistenten Volume ausserhalb des Containers abgelegt und angehängt und entsprechend gesichert. Zwischenergebnisse oder dateibasierten Protokolldaten, der Verarbeitung fällt eine fehlende Datensicherung oft nur dann auf, wenn ein Container beendet und entfernt ist und die enthaltenen Daten unwiderruflich verloren sind. Sind die Protokolldaten oder Zwischenergebnisse verloren, kann die Verarbeitung nicht lückenlos dokumentiert und somit deren Ergebnisse nicht mehr nachvollzogen werden.
>>>Vorgabe Anwendungen
>>>Nummer 2
>>>Titel
Kontinuierliche Container-Laufzeit-Überwachung
>>>Kurztext
>>>Text
Das Laufzeitverhalten der Container muss auf Anomalien hin überwacht werden.
>>>Langtext
>>>Text
Das Laufzeitverhalten der Container muss auf Anomalien hin überwacht werden. Dies umfasst beispielsweise Punkte wie:
>>>Liste-ungeordnet
Container kann nicht gestartet werden
Container starten nicht wie erwartet
Fehler bei liveness probes
Überschreitung von Limiten (z.B. Ressourcen)
>>>Vorgabe Anwendungen
>>>Nummer 3
>>>Titel
Lizenzen
>>>Kurztext
>>>Text
Die auf den Containern laufende Software muss lizenziert sein.
>>>Langtext
>>>Text
Eine Übersicht der Lizenzbedingungen der eingesetzten Softwarekomponenten muss vorhanden sein und die Bestätigung, dass die Komponenten in der Bundesverwaltung eingesetzt werden dürfen (kommerzielles Einsatzgebiet).
Eine nach Möglichkeit automatisierte Lizenzverwaltung für sämtliche im Container Image enthaltenen Software- Komponenten muss geführt werden.
>>>Vorgabe Informationen
>>>Nummer 1
>>>Titel
Container-Image-Metadaten
>>>Kurztext
>>>Text
Die Metadaten der Container Images müssen in aktueller Version vorliegen und gepflegt werden.
>>>Langtext
>>>Text
Die Metadaten des Container Images müssen geprüft werden (z. B. den Namen, die Version und die Beschreibung), um sicher zu stellen, dass die Metadaten den Zweck und den Inhalt des Containers genau wiedergeben.
Für die Nachverfolgung (Nachvollziehbarkeit) und für die Behebung von Schwachstellen ist es notwendig die Images mit folgenden Informationen (Labels) zu ergänzen: Besitzer, Entwicklerteam, Lizenzinformation, Herleitung, Abhängigkeiten.
>>>Vorgabe Informationen
>>>Nummer 2
>>>Titel
Umgang mit Secrets
>>>Kurztext
>>>Text
Container müssen auf Secrets gescannt werden.
>>>Langtext
>>>Text
Container Images müssen regelmässig auf offengelegte Geheimnisse (Secrets, Passwörter, Schlüssel, etc.) gescannt werden. Secrets, Passwörter und Schlüssel dürfen nicht in Dockerfiles geschrieben werden.
>>>Vorgabe Systeme
>>>Nummer 1
>>>Titel
Identitätsmanagement der Administratoren
>>>Kurztext
>>>Text
Alle administrativen Zugänge zum Container-Diensten muss durch personenbezogene Accounts und starke Authentisierung geschützt sein.
>>>Langtext
>>>Text
Alle administrativen Zugänge zum Container-Diensten muss durch personenbezogene Accounts und starke Authentisierung geschützt sein. Zugänge, die von der Verwaltungssoftware genutzt werden, sollten ebenfalls durch separate Accounts und starke Authentisierung geschützt sein.
Benutzerkonten und die zugehörigen Berechtigungen werden im BIT auf zentralen Systemen für das Identity-Management verwaltet. Damit diese Berechtigungsinformationen provisioniert werden können, muss das System entweder zentrale Schnittstellen (z. B. EIAM zur Autorisierung, Kerberos zur Authentisierung, Sperrlisteninformation bei Zertifikaten) oder dezentrale Mechanismen (z. B. Public-Key-Authentifizierung) unterstützen. Eine zentrale Lösung für das Identity-Management ist vorrangig zu nutzen.
>>>Vorgabe Systeme
>>>Nummer 2
>>>Titel
Accounts der Anwendungsdienste
>>>Kurztext
>>>Text
Die Accounts innerhalb der Container dürfen keine Berechtigungen auf den Container-Host haben.
>>>Langtext
>>>Text
Die Accounts innerhalb der Container dürfen keine Berechtigungen auf den Container-Host haben. Idealerweise sind die Accounts auf den Containern komplett auf die Container isoliert. Wenn es technisch notwendig ist, können Accounts über Container im gleichen Namespace geteilt werden.
>>>Vorgabe Systeme
>>>Nummer 3
>>>Titel
Verwendung einer Trusted Registry
>>>Kurztext
>>>Text
Es muss sichergestellt sein, dass Images nur aus vertrauenswürdigen Quellen stammen.
>>>Langtext
>>>Text
Die Container Images müssen in die Private Registry des BIT eingebunden werden können. Der Lieferant/Entwickler/Hersteller muss bestätigen, dass für alle im Container Image enthaltenen Software-Komponenten dies rechtlich in Ordnung ist. Alle Images werden vor der Aufnahme in die Private Registry auf Schwachstellen gescannt, bei schwerwiegenden Schwachstellen müssen die Container-Images vor der Aufnahme überarbeitet werden.
Es dürfen keine Images von unbekannten Registries verwendet werden.
>>>Vorgabe Systeme
>>>Nummer 4
>>>Titel
Speicherung von Zugangsdaten für die Repositories
>>>Kurztext
>>>Text
Zugangsdaten müssen so gespeichert und verwaltet werden, dass nur berechtigte Personen hierauf zugreifen können.
>>>Langtext
>>>Text
Zugangsdaten müssen so gespeichert und verwaltet werden, dass nur berechtigte Personen hierauf zugreifen können. Insbesondere muss bei der Verwaltung der Images und der in den Images betriebenen Anwendungen darauf geachtet werden, dass die Zugangsdaten nur an zugangsgeschützten Orten gespeichert werden. Die von der Container-Software bereitgestellten Verwaltungsmechanismen für Zugangsdaten sollten eingesetzt werden.
>>>Text
Folgende Zugangsdaten müssen mindestens berücksichtigt werden:
>>>Liste-ungeordnet
Passwörter jeglicher Accounts,
API-Keys für von der Anwendung genutzte Dienste sowie Private Schlüssel bei Public-Key Authentisierung
>>>Vorgabe Systeme
>>>Nummer 5
>>>Titel
Freigabe von Images (Containerfile)
>>>Kurztext
>>>Text
Alle Containerfiles für den produktiven Betrieb müssen einen geeigneten Freigabeprozess durchlaufen.
>>>Langtext
>>>Text
Alle Containerfiles für den produktiven Betrieb müssen einen geeigneten Freigabeprozess durchlaufen.
Containerfiles enthalten die Bauanleitung für ein Image. D.h. enthält die notwendigen Anweisungen zum Erstellen eines Images und stellt so die Reproduzierbarkeit eines Images bei jeder neuen Erstellung sicher. Images sollten stets nur ein absolutes Minimum an Code beinhalten, gerade so viel, wie zwingend erforderlich ist, um den Service oder die Applikation auszuführen, für den das Image gedacht ist.
>>>Vorgabe Systeme
>>>Nummer 6
>>>Titel
Updates von Containern
>>>Kurztext
>>>Text
Wenn sicherheitsrelevante Updates der zugrundeliegenden Images oder der betriebenen Software des Anwendungsdienstes erscheinen, müssen die Images für die Container neu erstellt und daraus neue Container instanziiert werden.
>>>Langtext
>>>Text
Wenn sicherheitsrelevante Updates der zugrundeliegenden Images oder der betriebenen Software des Anwendungsdienstes erscheinen, müssen die Images für die Container neu erstellt und daraus neue Container instanziiert werden.
Von extern bezogene Images sollten nur dann eingesetzt werden, wenn der Anbieter für diese Images auch regelmässig und bei sicherheitsrelevanten Änderungen schnell neue Versionen bereitstellt. Besser ist, wenn eine eigene Trusted Registry (z.B. Docker Trusted Registry DTR) bereitgestellt wird. Diese erweitert die klassische Registry um ein rollenbasiertes Zugangsmodell (RBAC), die Möglichkeit, Images digital zu signieren, und einen eingebauten Image-Scanner. Dabei sollte nebst den technische Massnahmen sichergestellt sein, dass nur Images aus dieser Registry eingesetzt werden.
Hierbei hilft der Image-Scanner, die bekannten Verwundbarkeiten in Container-Images zu finden.
>>>Vorgabe Systeme
>>>Nummer 7
>>>Titel
Einbinden von Volumes
>>>Kurztext
>>>Text
Die Container dürfen nur auf die für den Betrieb notwendigen Volumes und Verzeichnisse zugreifen können.
>>>Langtext
>>>Text
Die Container dürfen nur auf die für den Betrieb notwendigen Volumes und Verzeichnisse zugreifen können. Wenn Schreibrechte nicht benötigt werden, müssen diese eingeschränkt werden. Der private Modus von Volumes muss genutzt werden, sofern es keine Notwendigkeit für den Shared-Modus gibt. Meist benötigen Container gar keinen Schreib-Zugriff auf ein Container-Directory im Shared-Storage-Modus. Gut ist es auch, wenn das verwendete Dateisystem Roll-Backs unterstützt.
>>>Vorgabe Systeme
>>>Nummer 8
>>>Titel
Sicherer Zugang zur Registry
>>>Kurztext
>>>Text
Der Zugriff auf die Registry muss abgesichert werden.
>>>Langtext
>>>Text
Der Zugriff auf die Registry muss zusätzlich zu den weiteren relevanten Vorgaben (z. B. IT-Grundschutz) wie folgt abgesichert werden:
>>>Liste-ungeordnet
Admin Zugriffe müssen via Privileged Access Management (PAM) erfolgen.
RBAC (Role Based Access Management)
>>>Vorgabe Systeme
>>>Nummer 9
>>>Titel
Mengengerüst
>>>Kurztext
>>>Text
Requests und Limits müssen entsprechend den Anforderungen der Anwendungen definiert sein.
>>>Langtext
>>>Text
Requests und Limits müssen entsprechend den Anforderungen der Anwendungen definiert sein.
>>>text
Bedeutung:
>>>Liste-ungeordnet
Angemessene CPU- und Speicheranforderungen und Grenzwerte für Pods auf der Grundlage der erwarteten Last müssen definiert sein.
Es muss ein Kapazitätsmanagement-/Rechte-Sizing-Prozess definiert und angewandt werden. Der Ressourcenverbrauch der Pods muss regelmässig überprüft und an die Anforderungen/Limits angepasst werden.
Ein Leistungstest kann definiert und durchgeführt werden, um zu verstehen, wo die Grenzen der Anwendung liegen und wie hoch die durchschnittliche Arbeitslast ist.
Deployments müssen über mindestens zwei RZ bereitgestellt sein.
>>>text
Mehr Information: [RedHat OpenShift](https://docs.openshift.com/container-platform/4.12/applications/quotas/quotas-setting-per-project.html)
>>>Vorgabe Systeme
>>>Nummer 10
>>>Titel
Externe Libraries
>>>Kurztext
>>>Text
Externe Libraries müssen sicherheitsüberprüft sein
>>>Langtext
>>>Text
Externe Libraries müssen sicherheitsüberprüft sein
>>>Liste-ungeordnet
Ein automatisiertes Dependency Scanning der im Container Image enthaltenen Third Party Libraries muss durchgeführt werden.
Es muss geprüft werden, ob es veraltete oder anfällige Komponenten gibt. Für diese Pakete müssen die neuesten Sicherheitspatches eingepflegt werden, bevor sie in Produktion gehen.
>>>Vorgabe Systeme
>>>Nummer 11
>>>Titel
Schwachstellenanalyse für Container Images
>>>Kurztext
>>>Text
Container-Images müssen auf Schwachstellen gescannt werden.
>>>Langtext
>>>Text
Container Images sollten zum Zeitpunkt der Erstellung, in der Registrierung, bei der Bereitstellung und während der Laufzeit regelmässig gescannt werden, um neue Schwachstellen im Laufe der Zeit (Drift) erkennen zu können.
>>>Vorgabe Systeme
>>>Nummer 12
>>>Titel
Bekannte Schwachstellen
>>>Kurztext
>>>Text
Deployment aktualisierter Container Images mit bekannten Schwachstellen wird eingeschränkt
>>>Langtext
>>>Text
Eine aktualisierte Version eines Container Images mit bekannten Schwachstellen, welches heute bereits in Produktion ist, darf nur eingespielt werden, wenn die aktualisierte Version nur die gleichen Schwachstellen oder (besser) weniger Schwachstellen aufweist. Dies muss durch den ISBO des Data Owners abgenommen sein.
>>>Vorgabe Systeme
>>>Nummer 13
>>>Titel
Registries / Partitionen für die Bereitstellung
>>>Kurztext
>>>Text
Produktive und nicht-produktive Umgebungen müssen getrennt werden.
>>>Langtext
>>>Text
Produktionsumgebungen müssen von Entwicklungs- und/oder nicht-Produktiven Umgebungen getrennt werden, das heisst es müssen separate Registries oder Partitionen verwendet werden.
>>>Vorgabe Systeme
>>>Nummer 14
>>>Titel
Privilegien / Zugriffsrechte
>>>Kurztext
>>>Text
Das "Least Privilege"-Prinzip muss auf Containern umgesetzt werden.
>>>Langtext
>>>Text
Container müssen als Nicht-Root-Benutzer ausgeführt werden (z.B. USER ${UID} for OpenShift "mustRunAsNonRoot"). Container werden standardmäßig als "root" ausgeführt, dies ist ein Risiko, insbesondere wenn der Container mit erhöhten Privilegien ausgeführt wird.
Container müssen immer mit so wenig Privilegien wie möglich ausgestattet sein. Es dürfen nur ein minimales Set an privilegierten Zugriffen vorhanden sein und diese sind nur für die Behandlung von Notfällen vorgesehen. Namespaces müssen als "read-only" laufen.
Für die Definition von Rollen darf nur das notwendige Set an Ressourcen und Manipulationen verwendet werden.
>>>Vorgabe Zonen
>>>Nummer 1
>>>Titel
Separierung der Netze
>>>Kurztext
>>>Text
Die Netze müssen der jeweiligen Zonen-Policy der Bundesverwaltung entsprechen.
>>>Langtext
>>>Text
Die Netze für die Administration des Hosts, die Administration der Container- und die einzelnen Netze der Anwendungsdienste müssen den jeweiligen Zonenpolicies entsprechen. Wenn Unbefugte auf das Datennetz oder auf den Containern-Hosts zugreifen, können sie nicht über ungeschützte administrative Zugänge Befehle ausführen, die der Verfügbarkeit, Vertraulichkeit und Integrität der verarbeitenden Daten schaden.
>>>Vorgabe Zonen
>>>Nummer 2
>>>Titel
Verschlüsselung der Netzkommunikation
>>>Kurztext
>>>Text
Daten, die über virtuelle oder physische Netze zwischen den Containern übertragen werden, müssen verschlüsselt sein.
>>>Langtext
>>>Text
Mechanismen zur Authentisierung und Verschlüsselung der Zugänge sind häufig vorhanden, aber nicht standardmässig aktiviert. Entwickler müssen sich auf das „Absichern” der Applikation konzentrieren, als sich auf physische Netzwerk-Security-Tools zu verlassen. Physische Firewalls und andere Arten von Perimeter-Netzwerken/DMZs funktionieren nämlich in einer Container-basierten Umgebung meist nicht.
>>>Stichworte Netzwerkkommunikation, Verschlüsselung
>>>Checkliste
Die Netzkommunikation ist verschlüsselt.

View File

@@ -1 +1,6 @@
# VDeployment2
# vgui-cicd
There are examples for importing text in the "Documentation"-directory. Actual documentation follows.
Documentation on Confluence so far.
This commit should be signed.

View File

@@ -38,7 +38,7 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'standards',
'dokumente',
'abschnitte',
'stichworte',
'mptt',
@@ -126,6 +126,13 @@ STATICFILES_DIRS= (
os.path.join(BASE_DIR,"static"),
)
# Media files (User-uploaded content)
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Diagram cache settings
DIAGRAM_CACHE_DIR = 'diagram_cache' # relative to MEDIA_ROOT
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field

View File

@@ -43,7 +43,7 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'standards',
'dokumente',
'abschnitte',
'stichworte',
'referenzen',
@@ -52,11 +52,9 @@ INSTALLED_APPS = [
'pages',
'nested_admin',
'revproxy.apps.RevProxyConfig',
'debug_toolbar',
]
MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
@@ -141,6 +139,13 @@ STATICFILES_DIRS= (
os.path.join(BASE_DIR,"static"),
)
# Media files (User-uploaded content)
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Diagram cache settings
DIAGRAM_CACHE_DIR = 'diagram_cache' # relative to MEDIA_ROOT
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field

View File

@@ -18,9 +18,8 @@ from django.contrib import admin
from django.urls import include, path, re_path
from django.conf import settings
from django.conf.urls.static import static
from debug_toolbar.toolbar import debug_toolbar_urls
from diagramm_proxy.views import DiagrammProxyView
import standards.views
import dokumente.views
import pages.views
import referenzen.views
@@ -29,11 +28,17 @@ admin.site.site_header="Autorenumgebung"
urlpatterns = [
path('',pages.views.startseite),
path('search/',pages.views.search),
path('standards/', include("standards.urls")),
path('dokumente/', include("dokumente.urls")),
path('autorenumgebung/', admin.site.urls),
path('stichworte/', include("stichworte.urls")),
path('referenzen/', referenzen.views.tree, name="referenz_tree"),
path('referenzen/<str:refid>/', referenzen.views.detail, name="referenz_detail"),
re_path(r'^diagramm/(?P<path>.*)$', DiagrammProxyView.as_view()),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) +debug_toolbar_urls()
]
# Serve static files
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
# Serve media files (including cached diagrams)
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@@ -0,0 +1 @@
# Management commands

View File

@@ -0,0 +1 @@
# Commands package

View File

@@ -0,0 +1,23 @@
from django.core.management.base import BaseCommand
from diagramm_proxy.diagram_cache import clear_cache
class Command(BaseCommand):
help = 'Clear cached diagrams'
def add_arguments(self, parser):
parser.add_argument(
'--type',
type=str,
help='Diagram type to clear (e.g., plantuml, mermaid)',
)
def handle(self, *args, **options):
diagram_type = options.get('type')
if diagram_type:
self.stdout.write(f'Clearing cache for {diagram_type}...')
clear_cache(diagram_type)
else:
self.stdout.write('Clearing all diagram caches...')
clear_cache()
self.stdout.write(self.style.SUCCESS('Cache cleared successfully'))

View File

@@ -3,6 +3,10 @@ import base64
import zlib
import re
from textwrap import dedent
from django.conf import settings
# Import the caching function
from diagramm_proxy.diagram_cache import get_cached_diagram
DIAGRAMMSERVER="/diagramm"
@@ -25,15 +29,23 @@ def render_textabschnitte(queryset):
elif typ == "tabelle":
html = md_table_to_html(inhalt)
elif typ == "diagramm":
temp=inhalt.splitlines()
diagramtype=temp.pop(0)
diagramoptions='width="100%"'
if temp[0][0:6].lower() == "option":
diagramoptions=temp.pop(0).split(":",1)[1]
rest="\n".join(temp)
html = '<p><img '+diagramoptions+' src="'+DIAGRAMMSERVER+"/"+diagramtype+"/svg/"
html += base64.urlsafe_b64encode(zlib.compress(rest.encode("utf-8"),9)).decode()
html += '"></p>'
temp = inhalt.splitlines()
diagramtype = temp.pop(0)
diagramoptions = 'width="100%"'
if temp and temp[0][0:6].lower() == "option":
diagramoptions = temp.pop(0).split(":", 1)[1]
rest = "\n".join(temp)
# Use caching instead of URL encoding
try:
cache_path = get_cached_diagram(diagramtype, rest)
# Generate URL to serve from media/static
diagram_url = settings.MEDIA_URL + cache_path
html = f'<p><img {diagramoptions} src="{diagram_url}"></p>'
except Exception as e:
# Fallback to error message
html = f'<p class="text-danger">Error generating diagram: {str(e)}</p>'
elif typ == "code":
html = "<pre><code>"
html += markdown(inhalt, extensions=['tables', 'attr_list'])

12
argocd/001_pvc.yaml Normal file
View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: django-data-pvc
namespace: vorgabenui
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi

View File

@@ -16,9 +16,16 @@ spec:
securityContext:
fsGroup: 999
fsGroupChangePolicy: "OnRootMismatch"
initContainers:
- name: loader
image: git.baumann.gr/adebaumann/vui-data-loader:0.8
command: [ "sh","-c","cp -n preload/preload.sqlite3 /data/db.sqlite3; chown -R 999:999 /data; ls -la /data; sleep 10; exit 0" ]
volumeMounts:
- name: data
mountPath: /data
containers:
- name: web
image: git.baumann.gr/adebaumann/vui:0.922
image: git.baumann.gr/adebaumann/vui:0.933
imagePullPolicy: Always
ports:
- containerPort: 8000

View File

@@ -15,7 +15,7 @@ spec:
spec:
containers:
- name: kroki
image: docker.io/yuzutech/kroki:latest
image: git.baumann.gr/adebaumann/kroki:0.026
ports:
- containerPort: 8000
readinessProbe:
@@ -35,15 +35,15 @@ spec:
timeoutSeconds: 2
failureThreshold: 3
- name: mermaid
image: docker.io/yuzutech/kroki-mermaid:latest
image: git.baumann.gr/adebaumann/kroki-mermaid:0.026
ports:
- containerPort: 8002
- name: bpmn
image: docker.io/yuzutech/kroki-bpmn:latest
image: git.baumann.gr/adebaumann/kroki-bpmn:0.026
ports:
- containerPort: 8003
- name: excalidraw
image: docker.io/yuzutech/kroki-excalidraw:latest
image: git.baumann.gr/adebaumann/kroki-excalidraw:0.026
ports:
- containerPort: 8004
---

View File

@@ -7,7 +7,7 @@ metadata:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: vorgabenui.adebaumann.com
- host: vorgabenportal.knowyoursecurity.com
http:
paths:
- path: /

View File

@@ -8,5 +8,4 @@ RUN chown appuser:appuser /preload/preload.sqlite3
RUN mkdir /data
RUN chown appuser:appuser /data
USER root
CMD ["sh"]

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
# Diagram proxy module

View File

@@ -0,0 +1,91 @@
import hashlib
import os
import requests
from pathlib import Path
from django.conf import settings
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
import logging
logger = logging.getLogger(__name__)
# Configure cache directory
CACHE_DIR = getattr(settings, 'DIAGRAM_CACHE_DIR', 'diagram_cache')
KROKI_UPSTREAM = "http://svckroki:8000"
def get_cache_path(diagram_type, content_hash):
"""Generate cache file path for a diagram."""
return os.path.join(CACHE_DIR, diagram_type, f"{content_hash}.svg")
def compute_hash(content):
"""Compute SHA256 hash of diagram content."""
return hashlib.sha256(content.encode('utf-8')).hexdigest()
def get_cached_diagram(diagram_type, diagram_content):
"""
Retrieve diagram from cache or generate it via POST.
Args:
diagram_type: Type of diagram (e.g., 'plantuml', 'mermaid')
diagram_content: Raw diagram content
Returns:
Path to cached diagram file (relative to MEDIA_ROOT)
"""
content_hash = compute_hash(diagram_content)
cache_path = get_cache_path(diagram_type, content_hash)
# Check if diagram exists in cache
if default_storage.exists(cache_path):
logger.debug(f"Cache hit for {diagram_type} diagram: {content_hash[:8]}")
return cache_path
# Generate diagram via POST request
logger.info(f"Cache miss for {diagram_type} diagram: {content_hash[:8]}, generating...")
try:
url = f"{KROKI_UPSTREAM}/{diagram_type}/svg"
response = requests.post(
url,
data=diagram_content.encode('utf-8'),
headers={'Content-Type': 'text/plain'},
timeout=30
)
response.raise_for_status()
# Ensure cache directory exists
cache_dir = os.path.dirname(default_storage.path(cache_path))
os.makedirs(cache_dir, exist_ok=True)
# Save to cache
default_storage.save(cache_path, ContentFile(response.content))
logger.info(f"Diagram cached successfully: {cache_path}")
return cache_path
except requests.RequestException as e:
logger.error(f"Error generating diagram: {e}")
raise
def clear_cache(diagram_type=None):
"""
Clear cached diagrams.
Args:
diagram_type: If specified, only clear diagrams of this type
"""
if diagram_type:
cache_path = os.path.join(CACHE_DIR, diagram_type)
else:
cache_path = CACHE_DIR
if default_storage.exists(cache_path):
full_path = default_storage.path(cache_path)
# Walk through and delete files
for root, dirs, files in os.walk(full_path):
for file in files:
file_path = os.path.join(root, file)
try:
os.remove(file_path)
logger.info(f"Deleted cached diagram: {file_path}")
except OSError as e:
logger.error(f"Error deleting {file_path}: {e}")

View File

@@ -99,8 +99,8 @@ class PersonAdmin(admin.ModelAdmin):
@admin.register(Standard)
class StandardAdmin(NestedModelAdmin):
@admin.register(Dokument)
class DokumentAdmin(NestedModelAdmin):
actions_on_top=True
inlines = [EinleitungInline,GeltungsbereichInline,VorgabeInline]
#filter_horizontal=['autoren','pruefende']
@@ -118,7 +118,7 @@ class StandardAdmin(NestedModelAdmin):
#admin.site.register(Stichwort)
admin.site.register(Checklistenfrage)
#admin.site.register(Dokumententyp)
admin.site.register(Dokumententyp)
#admin.site.register(Person)
admin.site.register(Thema)
#admin.site.register(Referenz, DraggableM§PTTAdmin)

View File

@@ -3,4 +3,4 @@ from django.apps import AppConfig
class standardsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'standards'
name = 'dokumente'

View File

@@ -1,11 +1,11 @@
# Standards/management/commands/import_standard.py
# Document/management/commands/import_standard.py
import re
from pathlib import Path
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from standards.models import (
Standard,
from dokumente.models import (
Dokument,
Dokumententyp,
Thema,
Vorgabe,
@@ -21,15 +21,15 @@ from stichworte.models import Stichwort
class Command(BaseCommand):
help = (
"Import a security standard from a structured text file.\n"
"Import a policy document from a structured text file.\n"
"Supports Einleitung, Geltungsbereich, Vorgaben (Kurztext/Langtext with AbschnittTyp), "
"Stichworte (comma-separated), Checklistenfragen, dry-run, verbose, and purge."
)
def add_arguments(self, parser):
parser.add_argument("file_path", type=str, help="Path to the plaintext file")
parser.add_argument("--nummer", required=True, help="Standard number (e.g., STD-001)")
parser.add_argument("--name", required=True, help='Standard name (e.g., "IT-Sicherheit Container")')
parser.add_argument("--nummer", required=True, help="Document number (e.g., STD-001)")
parser.add_argument("--name", required=True, help='Document name (e.g., "IT-Sicherheit Container")')
parser.add_argument("--dokumententyp", required=True, help='Dokumententyp name (e.g., "IT-Sicherheit")')
parser.add_argument("--gueltigkeit_von", default=None, help="Start date (YYYY-MM-DD)")
parser.add_argument("--gueltigkeit_bis", default=None, help="End date (YYYY-MM-DD)")
@@ -63,8 +63,8 @@ class Command(BaseCommand):
if dry_run:
self.stdout.write(self.style.WARNING("Dry run: no database changes will be made."))
# get or create Standard (we want a real instance even in purge to count existing rows)
standard, created = Standard.objects.get_or_create(
# get or create Document (we want a real instance even in purge to count existing rows)
standard, created = Dokument.objects.get_or_create(
nummer=nummer,
defaults={
"dokumententyp": dokumententyp,
@@ -74,9 +74,9 @@ class Command(BaseCommand):
},
)
if created:
self.stdout.write(self.style.SUCCESS(f"Created Standard {nummer} {name}"))
self.stdout.write(self.style.SUCCESS(f"Created Document {nummer} {name}"))
else:
self.stdout.write(self.style.WARNING(f"Standard {nummer} already exists; content may be updated."))
self.stdout.write(self.style.WARNING(f"Document {nummer} already exists; content may be updated."))
# purge (Einleitung + Geltungsbereich + Vorgaben cascade)
if purge:
@@ -347,6 +347,6 @@ class Command(BaseCommand):
)
self.stdout.write(self.style.SUCCESS(
"Dry run complete" if dry_run else f"Imported standard {nummer} {name} with {len(vorgaben_data)} Vorgaben"
"Dry run complete" if dry_run else f"Imported document {nummer} {name} with {len(vorgaben_data)} Vorgaben"
))

View File

@@ -53,7 +53,7 @@ class Migration(migrations.Migration):
('rght', models.PositiveIntegerField(editable=False)),
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
('level', models.PositiveIntegerField(editable=False)),
('oberreferenz', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='unterreferenzen', to='standards.referenz')),
('oberreferenz', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='unterreferenzen', to='dokumente.referenz')),
],
options={
'verbose_name_plural': 'Referenzen',
@@ -65,7 +65,7 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('inhalt', models.TextField(blank=True, null=True)),
('abschnitttyp', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='abschnitte.abschnitttyp')),
('erklaerung', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='standards.referenz')),
('erklaerung', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dokumente.referenz')),
],
options={
'verbose_name': 'Erklärung',
@@ -80,9 +80,9 @@ class Migration(migrations.Migration):
('gueltigkeit_bis', models.DateField(blank=True, null=True)),
('signatur_cso', models.CharField(blank=True, max_length=255)),
('anhaenge', models.TextField(blank=True)),
('autoren', models.ManyToManyField(related_name='verfasste_dokumente', to='standards.person')),
('dokumententyp', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='standards.dokumententyp')),
('pruefende', models.ManyToManyField(related_name='gepruefte_dokumente', to='standards.person')),
('autoren', models.ManyToManyField(related_name='verfasste_dokumente', to='dokumente.person')),
('dokumententyp', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='dokumente.dokumententyp')),
('pruefende', models.ManyToManyField(related_name='gepruefte_dokumente', to='dokumente.person')),
],
options={
'verbose_name': 'Standard',
@@ -95,7 +95,7 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('inhalt', models.TextField(blank=True, null=True)),
('abschnitttyp', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='abschnitte.abschnitttyp')),
('geltungsbereich', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='standards.standard')),
('geltungsbereich', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dokumente.standard')),
],
options={
'verbose_name': 'Geltungsbereichs-Abschnitt',
@@ -108,8 +108,8 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('datum', models.DateField()),
('aenderung', models.TextField()),
('autoren', models.ManyToManyField(to='standards.person')),
('dokument', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='changelog', to='standards.standard')),
('autoren', models.ManyToManyField(to='dokumente.person')),
('dokument', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='changelog', to='dokumente.standard')),
],
),
migrations.CreateModel(
@@ -120,10 +120,10 @@ class Migration(migrations.Migration):
('titel', models.CharField(max_length=255)),
('gueltigkeit_von', models.DateField()),
('gueltigkeit_bis', models.DateField(blank=True, null=True)),
('dokument', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vorgaben', to='standards.standard')),
('referenzen', models.ManyToManyField(blank=True, to='standards.referenz')),
('dokument', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vorgaben', to='dokumente.standard')),
('referenzen', models.ManyToManyField(blank=True, to='dokumente.referenz')),
('stichworte', models.ManyToManyField(blank=True, to='stichworte.stichwort')),
('thema', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='standards.thema')),
('thema', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='dokumente.thema')),
],
options={
'verbose_name_plural': 'Vorgaben',
@@ -134,7 +134,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('frage', models.CharField(max_length=255)),
('vorgabe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='checklistenfragen', to='standards.vorgabe')),
('vorgabe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='checklistenfragen', to='dokumente.vorgabe')),
],
options={
'verbose_name_plural': 'Fragen für Checkliste',
@@ -145,7 +145,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('inhalt', models.TextField(blank=True, null=True)),
('abschnitt', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='standards.vorgabe')),
('abschnitt', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dokumente.vorgabe')),
('abschnitttyp', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='abschnitte.abschnitttyp')),
],
options={
@@ -158,7 +158,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('inhalt', models.TextField(blank=True, null=True)),
('abschnitt', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='standards.vorgabe')),
('abschnitt', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dokumente.vorgabe')),
('abschnitttyp', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='abschnitte.abschnitttyp')),
],
options={

View File

@@ -8,7 +8,7 @@ class Migration(migrations.Migration):
dependencies = [
('abschnitte', '0001_initial'),
('standards', '0001_initial'),
('dokumente', '0001_initial'),
]
operations = [
@@ -18,7 +18,7 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('inhalt', models.TextField(blank=True, null=True)),
('abschnitttyp', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='abschnitte.abschnitttyp')),
('einleitung', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='standards.standard')),
('einleitung', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dokumente.standard')),
],
options={
'verbose_name': 'Einleitungs-Abschnitt',

View File

@@ -6,7 +6,7 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('standards', '0002_einleitung'),
('dokumente', '0002_einleitung'),
]
operations = [

View File

@@ -7,7 +7,7 @@ class Migration(migrations.Migration):
dependencies = [
('referenzen', '0001_initial'),
('standards', '0003_einleitung_order_geltungsbereich_order_and_more'),
('dokumente', '0003_einleitung_order_geltungsbereich_order_and_more'),
]
operations = [

View File

@@ -7,7 +7,7 @@ class Migration(migrations.Migration):
dependencies = [
('rollen', '0001_initial'),
('standards', '0004_remove_referenzerklaerung_erklaerung_and_more'),
('dokumente', '0004_remove_referenzerklaerung_erklaerung_and_more'),
]
operations = [

View File

@@ -0,0 +1,21 @@
# Generated by Django 5.2.5 on 2025-10-02 12:13
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('dokumente', '0005_vorgabe_relevanz'),
]
operations = [
migrations.RenameModel(
old_name='Standard',
new_name='Dokument',
),
migrations.AlterModelOptions(
name='dokument',
options={'verbose_name': 'Dokument', 'verbose_name_plural': 'Dokumente'},
),
]

View File

@@ -0,0 +1,29 @@
# Generated by Django 5.2.5 on 2025-10-06 11:29
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('dokumente', '0006_rename_standard_dokument_alter_dokument_options'),
]
operations = [
migrations.AlterModelOptions(
name='changelog',
options={'verbose_name': 'Changelog-Eintrag', 'verbose_name_plural': 'Changelog'},
),
migrations.AlterModelOptions(
name='checklistenfrage',
options={'verbose_name': 'Frage für Checkliste', 'verbose_name_plural': 'Fragen für Checkliste'},
),
migrations.AlterModelOptions(
name='dokumententyp',
options={'verbose_name': 'Dokumententyp', 'verbose_name_plural': 'Dokumententypen'},
),
migrations.AlterModelOptions(
name='vorgabelangtext',
options={'verbose_name': 'Langtext-Abschnitt', 'verbose_name_plural': 'Langtext'},
),
]

View File

@@ -13,6 +13,10 @@ class Dokumententyp(models.Model):
def __str__(self):
return self.name
class Meta:
verbose_name="Dokumententyp"
verbose_name_plural="Dokumententypen"
class Person(models.Model):
name = models.CharField(max_length=100, primary_key=True)
@@ -33,7 +37,7 @@ class Thema(models.Model):
verbose_name_plural="Themen"
class Standard(models.Model):
class Dokument(models.Model):
nummer = models.CharField(max_length=50, primary_key=True)
dokumententyp = models.ForeignKey(Dokumententyp, on_delete=models.PROTECT)
name = models.CharField(max_length=255)
@@ -48,12 +52,12 @@ class Standard(models.Model):
return f"{self.nummer} {self.name}"
class Meta:
verbose_name_plural="Standards"
verbose_name="Standard"
verbose_name_plural="Dokumente"
verbose_name="Dokument"
class Vorgabe(models.Model):
nummer = models.IntegerField()
dokument = models.ForeignKey(Standard, on_delete=models.CASCADE, related_name='vorgaben')
dokument = models.ForeignKey(Dokument, on_delete=models.CASCADE, related_name='vorgaben')
thema = models.ForeignKey(Thema, on_delete=models.PROTECT)
titel = models.CharField(max_length=255)
referenzen = models.ManyToManyField(Referenz, blank=True)
@@ -77,17 +81,17 @@ class Vorgabe(models.Model):
return "expired" if not verbose else "Ist seit dem "+self.gueltigkeit_bis.strftime('%d.%m.%Y')+" nicht mehr in Kraft."
def __str__(self):
return f"{self.Vorgabennummer()}: {self.titel}"
class Meta:
verbose_name_plural="Vorgaben"
def __str__(self):
return f"{self.Vorgabennummer()}: {self.titel}"
class VorgabeLangtext(Textabschnitt):
abschnitt=models.ForeignKey(Vorgabe,on_delete=models.CASCADE)
class Meta:
verbose_name_plural="Langtext-Abschnitte"
verbose_name_plural="Langtext"
verbose_name="Langtext-Abschnitt"
class VorgabeKurztext(Textabschnitt):
@@ -97,13 +101,13 @@ class VorgabeKurztext(Textabschnitt):
verbose_name="Kurztext-Abschnitt"
class Geltungsbereich(Textabschnitt):
geltungsbereich=models.ForeignKey(Standard,on_delete=models.CASCADE)
geltungsbereich=models.ForeignKey(Dokument,on_delete=models.CASCADE)
class Meta:
verbose_name_plural="Geltungsbereich"
verbose_name="Geltungsbereichs-Abschnitt"
class Einleitung(Textabschnitt):
einleitung=models.ForeignKey(Standard,on_delete=models.CASCADE)
einleitung=models.ForeignKey(Dokument,on_delete=models.CASCADE)
class Meta:
verbose_name_plural="Einleitung"
verbose_name="Einleitungs-Abschnitt"
@@ -117,12 +121,17 @@ class Checklistenfrage(models.Model):
class Meta:
verbose_name_plural="Fragen für Checkliste"
verbose_name="Frage für Checkliste"
class Changelog(models.Model):
dokument = models.ForeignKey(Standard, on_delete=models.CASCADE, related_name='changelog')
dokument = models.ForeignKey(Dokument, on_delete=models.CASCADE, related_name='changelog')
autoren = models.ManyToManyField(Person)
datum = models.DateField()
aenderung = models.TextField()
def __str__(self):
return f"{self.datum} {self.dokument.nummer}"
class Meta:
verbose_name_plural="Changelog"
verbose_name="Changelog-Eintrag"

View File

@@ -5,27 +5,32 @@
{% if standard.history == True %}
<h2>Version vom {{ standard.check_date }}</h2>
{% endif %}
<!-- Autoren, Prüfende etc. -->
<p><strong>Autoren:</strong> {{ standard.autoren.all|join:", " }}</p>
<p><strong>Prüfende:</strong> {{ standard.pruefende.all|join:", " }}</p>
<p><strong>Gültigkeit:</strong> {{ standard.gueltigkeit_von }} bis {{ standard.gueltigkeit_bis }}</p>
<!-- Start Einleitung -->
{% if standard.einleitung_html %}
<h2>Einleitung</h2>
{% for typ, html in standard.einleitung_html %}
<div>{{ html|safe }}</div>
{% endfor %}
{% endif %}
<!-- End Einleitung -->
<!-- Start Geltungsbereich -->
{% if standard.geltungsbereich_html %}
<h2>Geltungsbereich</h2>
{% for typ, html in standard.geltungsbereich_html %}
<div>{{ html|safe }}</div>
{% endfor %}
{% endif %}
<!-- End Geltungsbereich -->
<h2>Vorgaben</h2>
{% for vorgabe in vorgaben %}
<!-- Start Vorgabe -->
{% if standard.history == True or vorgabe.long_status == "active" %}
<a id="{{ vorgabe.Vorgabennummer }}"></a><div class="card mb-4">
{% if vorgabe.long_status == "active"%}
@@ -46,7 +51,7 @@
</div>
<div class="card-body p-0">
<!-- Start Kurztext -->
{% comment %} KURZTEXT BLOCK {% endcomment %}
{% if vorgabe.kurztext_html.0.1 %}
<div class="p-3 mb-3 bg-light border-3" style="width: 100%;">
@@ -57,13 +62,14 @@
{% endfor %}
</div>
{% endif %}
<!-- Langtext -->
<div class="p-3 mb-3">
{% comment %} LANGTEXT BLOCK {% endcomment %}
{# <h5>Langtext</h5> #}
{% for typ, html in vorgabe.langtext_html %}
{% if html %}<div class="mb-3">{{ html|safe }}</div>{% endif %}
{% endfor %}
<!-- Checklistenfragen -->
{% comment %} CHECKLISTENFRAGEN BLOCK {% endcomment %}
<h5>Checklistenfragen</h5>
{% if vorgabe.checklistenfragen.all %}

View File

@@ -2,10 +2,10 @@
{% block content %}
<h1>Standards Informatiksicherheit</h1>
<ul>
{% for standard in standards %}
{% for dokument in dokumente %}
<li>
<a href="{% url 'standard_detail' nummer=standard.nummer %}">
{{ standard.nummer }} {{ standard.name }}
<a href="{% url 'standard_detail' nummer=dokument.nummer %}">
{{ dokument.nummer }} {{ dokument.name }}
</a>
</li>
{% endfor %}

497
dokumente/tests.py Normal file
View File

@@ -0,0 +1,497 @@
from django.test import TestCase, Client
from django.urls import reverse
from datetime import date, timedelta
from .models import (
Dokumententyp, Person, Thema, Dokument, Vorgabe,
VorgabeLangtext, VorgabeKurztext, Geltungsbereich,
Einleitung, Checklistenfrage, Changelog
)
from abschnitte.models import AbschnittTyp
from referenzen.models import Referenz
from stichworte.models import Stichwort
from rollen.models import Rolle
class DokumententypModelTest(TestCase):
"""Test cases for Dokumententyp model"""
def setUp(self):
self.dokumententyp = Dokumententyp.objects.create(
name="Standard IT-Sicherheit",
verantwortliche_ve="SR-SUR-SEC"
)
def test_dokumententyp_creation(self):
"""Test that Dokumententyp is created correctly"""
self.assertEqual(self.dokumententyp.name, "Standard IT-Sicherheit")
self.assertEqual(self.dokumententyp.verantwortliche_ve, "SR-SUR-SEC")
def test_dokumententyp_str(self):
"""Test string representation of Dokumententyp"""
self.assertEqual(str(self.dokumententyp), "Standard IT-Sicherheit")
def test_dokumententyp_verbose_name(self):
"""Test verbose name"""
self.assertEqual(
Dokumententyp._meta.verbose_name,
"Dokumententyp"
)
self.assertEqual(
Dokumententyp._meta.verbose_name_plural,
"Dokumententypen"
)
class PersonModelTest(TestCase):
"""Test cases for Person model"""
def setUp(self):
self.person = Person.objects.create(
name="Max Mustermann",
funktion="Manager"
)
def test_person_creation(self):
"""Test that Person is created correctly"""
self.assertEqual(self.person.name, "Max Mustermann")
self.assertEqual(self.person.funktion, "Manager")
def test_person_str(self):
"""Test string representation of Person"""
self.assertEqual(str(self.person), "Max Mustermann")
def test_person_verbose_name_plural(self):
"""Test verbose name plural"""
self.assertEqual(
Person._meta.verbose_name_plural,
"Personen"
)
class ThemaModelTest(TestCase):
"""Test cases for Thema model"""
def setUp(self):
self.thema = Thema.objects.create(
name="Security",
erklaerung="Security related topics"
)
def test_thema_creation(self):
"""Test that Thema is created correctly"""
self.assertEqual(self.thema.name, "Security")
self.assertEqual(self.thema.erklaerung, "Security related topics")
def test_thema_str(self):
"""Test string representation of Thema"""
self.assertEqual(str(self.thema), "Security")
def test_thema_blank_erklaerung(self):
"""Test that erklaerung can be blank"""
thema = Thema.objects.create(name="Testing")
self.assertEqual(thema.erklaerung, "")
class DokumentModelTest(TestCase):
"""Test cases for Dokument model"""
def setUp(self):
self.dokumententyp = Dokumententyp.objects.create(
name="Policy",
verantwortliche_ve="Legal"
)
self.autor = Person.objects.create(
name="John Doe",
funktion="Author"
)
self.pruefer = Person.objects.create(
name="Jane Smith",
funktion="Reviewer"
)
self.dokument = Dokument.objects.create(
nummer="DOC-001",
dokumententyp=self.dokumententyp,
name="Security Policy",
gueltigkeit_von=date.today(),
signatur_cso="CSO-123",
anhaenge="Appendix A, B"
)
self.dokument.autoren.add(self.autor)
self.dokument.pruefende.add(self.pruefer)
def test_dokument_creation(self):
"""Test that Dokument is created correctly"""
self.assertEqual(self.dokument.nummer, "DOC-001")
self.assertEqual(self.dokument.name, "Security Policy")
self.assertEqual(self.dokument.dokumententyp, self.dokumententyp)
def test_dokument_str(self):
"""Test string representation of Dokument"""
self.assertEqual(str(self.dokument), "DOC-001 Security Policy")
def test_dokument_many_to_many_relationships(self):
"""Test many-to-many relationships"""
self.assertIn(self.autor, self.dokument.autoren.all())
self.assertIn(self.pruefer, self.dokument.pruefende.all())
def test_dokument_optional_fields(self):
"""Test optional fields can be None or blank"""
dokument = Dokument.objects.create(
nummer="DOC-002",
dokumententyp=self.dokumententyp,
name="Test Document"
)
self.assertIsNone(dokument.gueltigkeit_von)
self.assertIsNone(dokument.gueltigkeit_bis)
self.assertEqual(dokument.signatur_cso, "")
self.assertEqual(dokument.anhaenge, "")
class VorgabeModelTest(TestCase):
"""Test cases for Vorgabe model"""
def setUp(self):
self.dokumententyp = Dokumententyp.objects.create(
name="Standard IT-Sicherheit",
verantwortliche_ve="SR-SUR-SEC"
)
self.dokument = Dokument.objects.create(
nummer="R01234",
dokumententyp=self.dokumententyp,
name="IT Standard"
)
self.thema = Thema.objects.create(name="Security")
self.vorgabe = Vorgabe.objects.create(
nummer=1,
dokument=self.dokument,
thema=self.thema,
titel="Password Requirements",
gueltigkeit_von=date.today() - timedelta(days=30)
)
def test_vorgabe_creation(self):
"""Test that Vorgabe is created correctly"""
self.assertEqual(self.vorgabe.nummer, 1)
self.assertEqual(self.vorgabe.dokument, self.dokument)
self.assertEqual(self.vorgabe.thema, self.thema)
def test_vorgabennummer(self):
"""Test Vorgabennummer generation"""
expected = "R01234.S.1"
self.assertEqual(self.vorgabe.Vorgabennummer(), expected)
def test_vorgabe_str(self):
"""Test string representation of Vorgabe"""
expected = "R01234.S.1: Password Requirements"
self.assertEqual(str(self.vorgabe), expected)
def test_get_status_active(self):
"""Test get_status returns 'active' for current vorgabe"""
status = self.vorgabe.get_status()
self.assertEqual(status, "active")
def test_get_status_future(self):
"""Test get_status returns 'future' for future vorgabe"""
future_vorgabe = Vorgabe.objects.create(
nummer=2,
dokument=self.dokument,
thema=self.thema,
titel="Future Requirement",
gueltigkeit_von=date.today() + timedelta(days=30)
)
status = future_vorgabe.get_status()
self.assertEqual(status, "future")
def test_get_status_expired(self):
"""Test get_status returns 'expired' for expired vorgabe"""
expired_vorgabe = Vorgabe.objects.create(
nummer=3,
dokument=self.dokument,
thema=self.thema,
titel="Old Requirement",
gueltigkeit_von=date.today() - timedelta(days=60),
gueltigkeit_bis=date.today() - timedelta(days=10)
)
status = expired_vorgabe.get_status()
self.assertEqual(status, "expired")
def test_get_status_verbose(self):
"""Test get_status with verbose=True"""
future_vorgabe = Vorgabe.objects.create(
nummer=4,
dokument=self.dokument,
thema=self.thema,
titel="Future Test",
gueltigkeit_von=date.today() + timedelta(days=10)
)
status = future_vorgabe.get_status(verbose=True)
self.assertIn("Ist erst ab dem", status)
self.assertIn("in Kraft", status)
def test_get_status_with_custom_check_date(self):
"""Test get_status with custom check_date"""
vorgabe = Vorgabe.objects.create(
nummer=5,
dokument=self.dokument,
thema=self.thema,
titel="Test Requirement",
gueltigkeit_von=date.today() - timedelta(days=60),
gueltigkeit_bis=date.today() - timedelta(days=10)
)
check_date = date.today() - timedelta(days=30)
status = vorgabe.get_status(check_date=check_date)
self.assertEqual(status, "active")
class VorgabeTextAbschnitteTest(TestCase):
"""Test cases for VorgabeLangtext and VorgabeKurztext"""
def setUp(self):
self.dokumententyp = Dokumententyp.objects.create(
name="Standard IT-Sicherheit",
verantwortliche_ve="SR-SUR-SEC"
)
self.dokument = Dokument.objects.create(
nummer="R01234",
dokumententyp=self.dokumententyp,
name="Test Standard"
)
self.thema = Thema.objects.create(name="Testing")
self.vorgabe = Vorgabe.objects.create(
nummer=1,
dokument=self.dokument,
thema=self.thema,
titel="Test Vorgabe",
gueltigkeit_von=date.today()
)
self.abschnitttyp = AbschnittTyp.objects.create(
abschnitttyp="Paragraph"
)
def test_vorgabe_langtext_creation(self):
"""Test VorgabeLangtext creation"""
langtext = VorgabeLangtext.objects.create(
abschnitt=self.vorgabe,
abschnitttyp=self.abschnitttyp,
inhalt="This is a long text description",
order=1
)
self.assertEqual(langtext.abschnitt, self.vorgabe)
self.assertEqual(langtext.inhalt, "This is a long text description")
def test_vorgabe_kurztext_creation(self):
"""Test VorgabeKurztext creation"""
kurztext = VorgabeKurztext.objects.create(
abschnitt=self.vorgabe,
abschnitttyp=self.abschnitttyp,
inhalt="Short summary",
order=1
)
self.assertEqual(kurztext.abschnitt, self.vorgabe)
self.assertEqual(kurztext.inhalt, "Short summary")
class DokumentTextAbschnitteTest(TestCase):
"""Test cases for Geltungsbereich and Einleitung"""
def setUp(self):
self.dokumententyp = Dokumententyp.objects.create(
name="Policy",
verantwortliche_ve="Legal"
)
self.dokument = Dokument.objects.create(
nummer="POL-001",
dokumententyp=self.dokumententyp,
name="Test Policy"
)
self.abschnitttyp = AbschnittTyp.objects.create(
abschnitttyp="Paragraph"
)
def test_geltungsbereich_creation(self):
"""Test Geltungsbereich creation"""
geltungsbereich = Geltungsbereich.objects.create(
geltungsbereich=self.dokument,
abschnitttyp=self.abschnitttyp,
inhalt="Applies to all employees",
order=1
)
self.assertEqual(geltungsbereich.geltungsbereich, self.dokument)
self.assertEqual(geltungsbereich.inhalt, "Applies to all employees")
def test_einleitung_creation(self):
"""Test Einleitung creation"""
einleitung = Einleitung.objects.create(
einleitung=self.dokument,
abschnitttyp=self.abschnitttyp,
inhalt="This document defines...",
order=1
)
self.assertEqual(einleitung.einleitung, self.dokument)
self.assertEqual(einleitung.inhalt, "This document defines...")
class ChecklistenfrageModelTest(TestCase):
"""Test cases for Checklistenfrage model"""
def setUp(self):
self.dokumententyp = Dokumententyp.objects.create(
name="Standard IT-Sicherheit",
verantwortliche_ve="QA"
)
self.dokument = Dokument.objects.create(
nummer="QA-001",
dokumententyp=self.dokumententyp,
name="QA Standard"
)
self.thema = Thema.objects.create(name="Quality")
self.vorgabe = Vorgabe.objects.create(
nummer=1,
dokument=self.dokument,
thema=self.thema,
titel="Quality Check",
gueltigkeit_von=date.today()
)
self.frage = Checklistenfrage.objects.create(
vorgabe=self.vorgabe,
frage="Have all tests passed?"
)
def test_checklistenfrage_creation(self):
"""Test Checklistenfrage creation"""
self.assertEqual(self.frage.vorgabe, self.vorgabe)
self.assertEqual(self.frage.frage, "Have all tests passed?")
def test_checklistenfrage_str(self):
"""Test string representation"""
self.assertEqual(str(self.frage), "Have all tests passed?")
def test_checklistenfrage_related_name(self):
"""Test related name works correctly"""
self.assertIn(self.frage, self.vorgabe.checklistenfragen.all())
class ChangelogModelTest(TestCase):
"""Test cases for Changelog model"""
def setUp(self):
self.dokumententyp = Dokumententyp.objects.create(
name="Standard IT-Sicherheit",
verantwortliche_ve="SR-SUR-SEC"
)
self.dokument = Dokument.objects.create(
nummer="R01234",
dokumententyp=self.dokumententyp,
name="IT Standard"
)
self.autor = Person.objects.create(
name="John Doe",
funktion="Developer"
)
self.changelog = Changelog.objects.create(
dokument=self.dokument,
datum=date.today(),
aenderung="Initial version"
)
self.changelog.autoren.add(self.autor)
def test_changelog_creation(self):
"""Test Changelog creation"""
self.assertEqual(self.changelog.dokument, self.dokument)
self.assertEqual(self.changelog.aenderung, "Initial version")
self.assertIn(self.autor, self.changelog.autoren.all())
def test_changelog_str(self):
"""Test string representation"""
expected = f"{date.today()} R01234"
self.assertEqual(str(self.changelog), expected)
class ViewsTestCase(TestCase):
"""Test cases for views"""
def setUp(self):
self.client = Client()
self.dokumententyp = Dokumententyp.objects.create(
name="Standard IT-Sicherheit",
verantwortliche_ve="SR-SUR-SEC"
)
self.dokument = Dokument.objects.create(
nummer="R01234",
dokumententyp=self.dokumententyp,
name="Test Standard"
)
self.thema = Thema.objects.create(name="Testing")
self.vorgabe = Vorgabe.objects.create(
nummer=1,
dokument=self.dokument,
thema=self.thema,
titel="Test Requirement",
gueltigkeit_von=date.today()
)
self.abschnitttyp = AbschnittTyp.objects.create(
abschnitttyp="Paragraph"
)
def test_standard_list_view(self):
"""Test standard_list view"""
response = self.client.get(reverse('standard_list'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "R01234")
self.assertIn('dokumente', response.context)
def test_standard_detail_view(self):
"""Test standard_detail view"""
response = self.client.get(
reverse('standard_detail', kwargs={'nummer': 'R01234'})
)
self.assertEqual(response.status_code, 200)
self.assertIn('standard', response.context)
self.assertIn('vorgaben', response.context)
self.assertEqual(response.context['standard'], self.dokument)
def test_standard_detail_view_404(self):
"""Test standard_detail view returns 404 for non-existent document"""
response = self.client.get(
reverse('standard_detail', kwargs={'nummer': 'NONEXISTENT'})
)
self.assertEqual(response.status_code, 404)
def test_standard_checkliste_view(self):
"""Test standard_checkliste view"""
response = self.client.get(
reverse('standard_checkliste', kwargs={'nummer': 'R01234'})
)
self.assertEqual(response.status_code, 200)
self.assertIn('standard', response.context)
self.assertIn('vorgaben', response.context)
def test_standard_history_view(self):
"""Test standard_detail with history (check_date)"""
url = reverse('standard_history', kwargs={'nummer': 'R01234'})
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
class URLPatternsTest(TestCase):
"""Test URL patterns"""
def test_standard_list_url_resolves(self):
"""Test that standard_list URL resolves correctly"""
url = reverse('standard_list')
self.assertEqual(url, '/dokumente/')
def test_standard_detail_url_resolves(self):
"""Test that standard_detail URL resolves correctly"""
url = reverse('standard_detail', kwargs={'nummer': 'TEST-001'})
self.assertEqual(url, '/dokumente/TEST-001/')
def test_standard_checkliste_url_resolves(self):
"""Test that standard_checkliste URL resolves correctly"""
url = reverse('standard_checkliste', kwargs={'nummer': 'TEST-001'})
self.assertEqual(url, '/dokumente/TEST-001/checkliste/')
def test_standard_history_url_resolves(self):
"""Test that standard_history URL resolves correctly"""
url = reverse('standard_history', kwargs={'nummer': 'TEST-001'})
self.assertEqual(url, '/dokumente/TEST-001/history/')

View File

@@ -1,5 +1,5 @@
from django.shortcuts import render, get_object_or_404
from .models import Standard
from .models import Dokument
from abschnitte.utils import render_textabschnitte
from datetime import date
@@ -9,14 +9,14 @@ calendar=parsedatetime.Calendar()
def standard_list(request):
standards = Standard.objects.all()
dokumente = Dokument.objects.all()
return render(request, 'standards/standard_list.html',
{'standards': standards}
{'dokumente': dokumente}
)
def standard_detail(request, nummer,check_date=""):
standard = get_object_or_404(Standard, nummer=nummer)
standard = get_object_or_404(Dokument, nummer=nummer)
if check_date:
check_date = calendar.parseDT(check_date)[0].date()
@@ -48,7 +48,7 @@ def standard_detail(request, nummer,check_date=""):
def standard_checkliste(request, nummer):
standard = get_object_or_404(Standard, nummer=nummer)
standard = get_object_or_404(Dokument, nummer=nummer)
vorgaben = list(standard.vorgaben.all())
return render(request, 'standards/standard_checkliste.html', {
'standard': standard,

View File

@@ -7,8 +7,8 @@ spec:
restartPolicy: Never
containers:
- name: loader
image: adebaumann/vgui-preloader:0.4
command: ["sh","-c","cp -v /preload/preload.sqlite3 /data/db.sqlite3; chown -R 999:999 /data; ls -la /data"]
image: adebaumann/vgui-preloader:0.5
command: ["sh","-c","cp -v --debug --update=none /preload/preload.sqlite3 /data/db.sqlite3; chown -R 999:999 /data; ls -la /data; exit 0"]
volumeMounts:
- name: data
mountPath: /data

View File

@@ -16,6 +16,13 @@ spec:
securityContext:
fsGroup: 999
fsGroupChangePolicy: "OnRootMismatch"
initContainers:
- name: loader
image: adebaumann/vgui-preloader:0.5
command: [ "sh","-c","cp -v --debug --update=none /preload/preload.sqlite3 /data/db.sqlite3; chown -R 999:999 /data; ls -la /data; exit 0" ]
volumeMounts:
- name: data
mountPath: /data
containers:
- name: web
image: docker.io/adebaumann/vui:0.917

View File

@@ -16,7 +16,7 @@
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-item nav-link active" href="/standards">Standards</a>
<a class="nav-item nav-link active" href="/dokumente">Standards</a>
<a class="nav-item nav-link" href="/referenzen">Referenzen</a>
<a class="nav-item nav-link" href="/stichworte">Stichworte</a>
<a class="nav-item nav-link" href="/search">Suche</a>
@@ -28,6 +28,6 @@
<div class="flex-fill">{% block content %}Main Content{% endblock %}</div>
<div class="col-md-2">{% block sidebar_right %}{% endblock %}</div>
</div>
<div>VorgabenUI v0.8</div>
<div>VorgabenUI v0.931</div>
</body>
</html>

View File

@@ -12,9 +12,9 @@
{% endfor %}
{% endif %}
{% if resultat.kurztext %}
<h2>Vorgaben mit "{{ suchbegriff }}" im Kurztext</h2>
{% for standard, vorgaben in resultat.kurztext.items %}
{% if resultat.all %}
<h2>Vorgaben mit "{{ suchbegriff }}"</h2>
{% for standard, vorgaben in resultat.all.items %}
<h4>{{ standard }}</h4>
<ul>
{% for vorgabe in vorgaben %}
@@ -24,18 +24,7 @@
{% endfor %}
{% endif %}
{% if resultat.langtext %}
<h2>Vorgaben mit "{{ suchbegriff }}" im Langtext</h2>
{% for standard, vorgaben in resultat.langtext.items %}
<h4>{{ standard }}</h4>
<ul>
{% for vorgabe in vorgaben %}
<li><a href="{% url 'standard_detail' nummer=vorgabe.dokument.nummer %}#{{vorgabe.Vorgabennummer}}">{{vorgabe}}</a></li>
{% endfor %}
</ul>
{% endfor %}
{% endif %}
{% if not resultat.langtext and not resultat.kurztext and not resultat.geltungsbereich %}
{% if not resultat.all %}
<h2>Keine Resultate für "{{suchbegriff}}"</h2>
{% endif %}
{% endblock %}

View File

@@ -15,27 +15,6 @@
placeholder="Suchbegriff eingeben …"
required>
</div>
<!-- Check-box group -->
<fieldset class="mb-4">
<legend class="h6 mb-2">In folgenden Bereichen suchen:</legend>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="kurztext" id="kurztext" name="suchbereich[]" checked>
<label class="form-check-label" for="kurztext">Kurztext</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="langtext" id="langtext" name="suchbereich[]" checked>
<label class="form-check-label" for="langtext">Langtext</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="geltungsbereich" id="geltungsbereich" name="suchbereich[]">
<label class="form-check-label" for="geltungsbereich">Geltungsbereich</label>
</div>
</fieldset>
<button type="submit" class="btn btn-primary">Suchen</button>
</form>
{% endblock %}

View File

@@ -3,7 +3,7 @@
<h1>Vorgaben Informatiksicherheit BIT</h1>
<h2>Aktuell erfasste Standards</h2>
<ul>
{% for standard in standards %}
{% for standard in dokumente %}
<li><a href="{% url 'standard_detail' nummer=standard.nummer %}">{{ standard }}</a></li>
{% endfor %}
</ul>

View File

@@ -1,34 +1,31 @@
from django.shortcuts import render
from abschnitte.utils import render_textabschnitte
from standards.models import Standard, VorgabeLangtext, VorgabeKurztext, Geltungsbereich
from dokumente.models import Dokument, VorgabeLangtext, VorgabeKurztext, Geltungsbereich
from itertools import groupby
import datetime
import pprint
def startseite(request):
standards=list(Standard.objects.all())
return render(request, 'startseite.html', {"standards":standards,})
standards=list(Dokument.objects.all())
return render(request, 'startseite.html', {"dokumente":standards,})
def search(request):
if request.method == "GET":
return render(request, 'search.html')
elif request.method == "POST":
suchbegriff=request.POST.get("q")
areas=request.POST.getlist("suchbereich[]")
result= {}
geltungsbereich=set()
if "kurztext" in areas:
qs = VorgabeKurztext.objects.filter(inhalt__contains=suchbegriff).exclude(abschnitt__gueltigkeit_bis__lt=datetime.date.today())
result["kurztext"] = {k: [o.abschnitt for o in g] for k, g in groupby(qs, key=lambda o: o.abschnitt.dokument)}
if "langtext" in areas:
qs = VorgabeLangtext.objects.filter(inhalt__contains=suchbegriff).exclude(abschnitt__gueltigkeit_bis__lt=datetime.date.today())
result['langtext']= {k: [o.abschnitt for o in g] for k, g in groupby(qs, key=lambda o: o.abschnitt.dokument)}
if "geltungsbereich" in areas:
result["geltungsbereich"]={}
geltungsbereich=set(list([x.geltungsbereich for x in Geltungsbereich.objects.filter(inhalt__contains=suchbegriff)]))
for s in geltungsbereich:
result["geltungsbereich"][s]=render_textabschnitte(s.geltungsbereich_set.order_by("order"))
result= {"all": {}}
qs = VorgabeKurztext.objects.filter(inhalt__contains=suchbegriff).exclude(abschnitt__gueltigkeit_bis__lt=datetime.date.today())
result["kurztext"] = {k: [o.abschnitt for o in g] for k, g in groupby(qs, key=lambda o: o.abschnitt.dokument)}
qs = VorgabeLangtext.objects.filter(inhalt__contains=suchbegriff).exclude(abschnitt__gueltigkeit_bis__lt=datetime.date.today())
result['langtext']= {k: [o.abschnitt for o in g] for k, g in groupby(qs, key=lambda o: o.abschnitt.dokument)}
for r in result.keys():
for s in result[r].keys():
result[r][s]=set(result[r][s])
result["all"][s] = set(result[r][s])
result["geltungsbereich"]={}
geltungsbereich=set(list([x.geltungsbereich for x in Geltungsbereich.objects.filter(inhalt__contains=suchbegriff)]))
for s in geltungsbereich:
result["geltungsbereich"][s]=render_textabschnitte(s.geltungsbereich_set.order_by("order"))
pprint.pp (result)
return render(request,"results.html",{"suchbegriff":suchbegriff,"resultat":result})

View File

@@ -6,7 +6,6 @@ charset-normalizer==3.4.3
curtsies==0.4.3
cwcwidth==0.1.10
Django==5.2.5
django-debug-toolbar==6.0.0
django-js-asset==3.1.2
django-mptt==0.17.0
django-mptt-admin==2.8.0

View File

@@ -1,177 +0,0 @@
# Standards/management/commands/import_standard.py
import re
from pathlib import Path
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from standards.models import (
Standard,
Vorgabe,
VorgabeKurztext,
VorgabeLangtext,
Geltungsbereich,
Dokumententyp,
Thema,
)
from abschnitte.models import AbschnittTyp
class Command(BaseCommand):
help = "Import a security standard from a structured text file"
def add_arguments(self, parser):
parser.add_argument("file_path", type=str, help="Path to the plaintext file")
parser.add_argument("--nummer", required=True, help="Standard number (e.g., STD-001)")
parser.add_argument("--name", required=True, help="Standard name (e.g., IT-Sicherheit Container)")
parser.add_argument("--dokumententyp", required=True, help="Dokumententyp name")
parser.add_argument("--gueltigkeit_von", default=None, help="Start date (YYYY-MM-DD)")
parser.add_argument("--gueltigkeit_bis", default=None, help="End date (YYYY-MM-DD)")
parser.add_argument("--dry-run", action="store_true", help="Perform a dry run without saving to the database")
parser.add_argument("--verbose", action="store_true", help="Verbose output for dry run")
def handle(self, *args, **options):
dry_run = options["dry_run"]
verbose = options["verbose"]
file_path = Path(options["file_path"])
if not file_path.exists():
raise CommandError(f"File {file_path} does not exist")
nummer = options["nummer"]
name = options["name"]
dokumententyp_name = options["dokumententyp"]
try:
dokumententyp = Dokumententyp.objects.get(name=dokumententyp_name)
except Dokumententyp.DoesNotExist:
raise CommandError(f"Dokumententyp '{dokumententyp_name}' does not exist")
if dry_run:
self.stdout.write(self.style.WARNING("Dry run: no database changes will be made"))
# Create or get the Standard
if dry_run:
standard = {"nummer": nummer, "name": name, "dokumententyp": dokumententyp}
else:
standard, created = Standard.objects.get_or_create(
nummer=nummer,
defaults={
"dokumententyp": dokumententyp,
"name": name,
"gueltigkeit_von": options["gueltigkeit_von"],
"gueltigkeit_bis": options["gueltigkeit_bis"],
},
)
if not created:
self.stdout.write(self.style.WARNING(f"Standard {nummer} already exists, updating content"))
# Read and parse the file
content = file_path.read_text(encoding="utf-8")
blocks = re.split(r"^>>>", content, flags=re.MULTILINE)
blocks = [b.strip() for b in blocks if b.strip()]
geltungsbereich_sections = []
current_vorgabe = None
vorgaben_data = []
current_context = "geltungsbereich"
abschnittstyp_headers = ["text", "liste geordnet", "liste ungeordnet"]
for block in blocks:
lines = block.splitlines()
header = lines[0].strip()
text = "\n".join(lines[1:]).strip()
header_lower = header.lower()
# Determine AbschnittTyp if applicable
abschnitt_typ = None
if header_lower in abschnittstyp_headers:
try:
abschnitt_typ = AbschnittTyp.objects.get(abschnitttyp=header_lower)
except AbschnittTyp.DoesNotExist:
self.stdout.write(self.style.WARNING(f"AbschnittTyp '{header_lower}' not found, defaulting to 'text'"))
abschnitt_typ = AbschnittTyp.objects.get(abschnitttyp="text")
if header_lower == "geltungsbereich":
current_context = "geltungsbereich"
elif header_lower.startswith("vorgabe"):
if current_vorgabe:
vorgaben_data.append(current_vorgabe)
thema_name = header.split(" ", 1)[1].strip()
current_vorgabe = {"thema": thema_name, "titel": "", "nummer": None, "kurztext": [], "langtext": []}
current_context = "vorgabe_none"
elif header_lower.startswith("titel") and current_vorgabe:
current_vorgabe["titel"] = text
elif header_lower.startswith("nummer") and current_vorgabe:
nummer_match = re.search(r"\d+", header)
if nummer_match:
current_vorgabe["nummer"] = int(nummer_match.group())
current_context = "vorgabe_none"
elif header_lower == "kurztext":
current_context = "vorgabe_kurztext"
elif header_lower == "langtext":
current_context = "vorgabe_langtext"
elif header_lower in abschnittstyp_headers:
abschnitt = {"inhalt": text, "typ": abschnitt_typ}
if current_context == "geltungsbereich":
geltungsbereich_sections.append(abschnitt)
if dry_run and verbose:
self.stdout.write(self.style.SUCCESS(f"[DRY RUN] Geltungsbereich Abschnitt (Abschnittstyp: {abschnitt_typ}): {text[:50]}..."))
elif current_context == "vorgabe_kurztext" and current_vorgabe:
current_vorgabe["kurztext"].append(abschnitt)
if dry_run and verbose:
self.stdout.write(self.style.SUCCESS(f"[DRY RUN] Vorgabe {current_vorgabe['nummer']} Kurztext Abschnitt (Abschnittstyp: {abschnitt_typ}): {text[:50]}..."))
elif current_context == "vorgabe_langtext" and current_vorgabe:
current_vorgabe["langtext"].append(abschnitt)
if dry_run and verbose:
self.stdout.write(self.style.SUCCESS(f"[DRY RUN] Vorgabe {current_vorgabe['nummer']} Langtext Abschnitt (Abschnittstyp: {abschnitt_typ}): {text[:50]}..."))
if current_vorgabe:
vorgaben_data.append(current_vorgabe)
# Save Geltungsbereich
for sektion in geltungsbereich_sections:
if dry_run:
self.stdout.write(self.style.SUCCESS(f"[DRY RUN] Would create Geltungsbereich Abschnitt (Abschnittstyp: {sektion['typ']}): {sektion['inhalt'][:50]}..."))
else:
Geltungsbereich.objects.create(
geltungsbereich=standard,
abschnitttyp=sektion["typ"],
inhalt=sektion["inhalt"],
)
# Save Vorgaben
for v in vorgaben_data:
try:
thema = Thema.objects.get(name=v["thema"])
except Thema.DoesNotExist:
self.stdout.write(self.style.WARNING(f"Thema '{v['thema']}' not found, skipping Vorgabe {v['nummer']}"))
continue
if dry_run:
self.stdout.write(self.style.SUCCESS(f"[DRY RUN] Would create Vorgabe {v['nummer']}: '{v['titel']}' (Thema: {v['thema']})"))
for sektion in v["kurztext"]:
self.stdout.write(self.style.SUCCESS(f"[DRY RUN] Kurztext Abschnitt (Abschnittstyp: {sektion['typ']}): {sektion['inhalt'][:50]}..."))
for sektion in v["langtext"]:
self.stdout.write(self.style.SUCCESS(f"[DRY RUN] Langtext Abschnitt (Abschnittstyp: {sektion['typ']}): {sektion['inhalt'][:50]}..."))
else:
vorgabe = Vorgabe.objects.create(
nummer=v["nummer"],
dokument=standard,
thema=thema,
titel=v["titel"],
gueltigkeit_von=timezone.now().date(),
)
for sektion in v["kurztext"]:
VorgabeKurztext.objects.create(abschnitt=vorgabe, abschnitttyp=sektion["typ"], inhalt=sektion["inhalt"])
for sektion in v["langtext"]:
VorgabeLangtext.objects.create(abschnitt=vorgabe, abschnitttyp=sektion["typ"], inhalt=sektion["inhalt"])
self.stdout.write(self.style.SUCCESS(
f"{'Dry run complete' if dry_run else f'Imported standard {standard} with {len(vorgaben_data)} Vorgaben'}"
))

View File

@@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -1,14 +0,0 @@
.nested-inline h3 {
background-color: #f3f3f3;
border-bottom: 1px solid #ccc;
padding: 0.4em;
margin: 0;
font-weight: bold;
cursor: pointer;
}
.nested-inline fieldset.module {
margin-bottom: 1em;
border: 1px solid #ccc;
padding: 0;
}

View File

@@ -1,24 +0,0 @@
document.addEventListener("DOMContentLoaded", function () {
const inlineSections = document.querySelectorAll(".nested-inline fieldset.module");
inlineSections.forEach(section => {
const header = section.querySelector("h2");
const content = Array.from(section.children).filter(child => !child.matches("h2"));
if (header && content.length > 0) {
header.style.cursor = "pointer";
header.style.userSelect = "none";
header.style.background = "#f3f3f3";
header.style.borderBottom = "1px solid #ccc";
header.style.padding = "4px";
// Collapse by default
content.forEach(el => el.style.display = "none");
header.addEventListener("click", () => {
const currentlyVisible = content[0].style.display !== "none";
content.forEach(el => el.style.display = currentlyVisible ? "none" : "block");
});
}
});
});