6 Commits
Author SHA1 Message Date
DragonSlayer_14 437b25d72a Merge pull request 'Merge testing in main: Release 1.0.1' (#4) from testing into main
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Skipped
Security Scans / Trivy & OSV-Scanner (push) Successful in 55s
Main Release & Publish / Build, Publish Crate to Gitea Registry & Create Release (push) Successful in 1m7s
TruffleHog Secret Scan / TruffleHog (push) Successful in 29s
Reviewed-on: #4
2026-09-12 22:58:07 +00:00
DragonSlayer_14 ebf00fd1b0 Merge pull request 'Merge dev in testing: Feat: Fügt CI/CD-Workflows, Sicherheitsprüfungen und Abhängigkeitsmanagement hinzu.' (#3) from dev into testing
Security Scans / Trivy & OSV-Scanner (push) Successful in 38s
Unit-Tests / Unit-Tests (pull_request) Skipped
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Successful in 40s
TruffleHog Secret Scan / TruffleHog (push) Successful in 16s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 16s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 22s
Reviewed-on: #3
2026-09-12 22:54:42 +00:00
DragonSlayer_14 a66baf1dc6 Feat: Fügt CI/CD-Workflows, Sicherheitsprüfungen und Abhängigkeitsmanagement hinzu.
TruffleHog Secret Scan / TruffleHog (push) Successful in 18s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 30s
Security Scans / Trivy & OSV-Scanner (push) Successful in 34s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 17s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 26s
Unit-Tests / Unit-Tests (pull_request) Successful in 29s
2026-09-13 00:48:03 +02:00
DragonSlayer_14 adc3f4d5d2 Merge pull request 'Merge testing in main: Release 1.0.0' (#2) from testing into main
Main Release & Publish / Build, Publish Crate to Gitea Registry & Create Release (push) Successful in 18s
Reviewed-on: #2
2026-08-26 17:09:51 +00:00
DragonSlayer_14 f906ec3e6f Merge pull request 'Merge dev in testing: Feat: Erstellt die Crate' (#1) from dev into testing
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Successful in 18s
Reviewed-on: #1
2026-08-26 17:08:35 +00:00
DragonSlayer_14 07177265df Feat: Erstellt die Crate 2026-08-26 19:08:10 +02:00
20 changed files with 999 additions and 102 deletions
+9
View File
@@ -0,0 +1,9 @@
[registry]
default = "gitea"
[registries.gitea]
index = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/" # Sparse index
# index = "https://gitea.creative-dragonslayer.de/Rust-Crates/_cargo-index.git" # Git
[net]
git-fetch-with-cli = true
+54
View File
@@ -0,0 +1,54 @@
name: Code Quality (Auto-Format & Clippy-Fix)
on:
push:
branches:
- dev
workflow_dispatch:
jobs:
fix:
name: Formatierung & Clippy automatisch beheben
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v7
with:
ref: ${{ gitea.ref_name || github.ref_name }}
token: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
- name: Install Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
components: clippy, rustfmt
cache: false
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Formatierung automatisch beheben
run: cargo fmt
- name: Clippy-Fixes automatisch anwenden
run: cargo clippy --fix --allow-dirty --allow-staged --all-targets
- name: Änderungen committen & pushen
run: |
if [ -n "$(git status --porcelain)" ]; then
git config user.name "Gitea-Bot"
git config user.email "no-reply@creativedragonslayer.de"
git add -A
git commit -m "Style: Automatische Formatierung & Clippy-Fixes"
git push origin HEAD:${{ gitea.ref_name || github.ref_name }}
else
echo "Keine Formatierungs- oder Clippy-Änderungen."
fi
+12 -1
View File
@@ -14,11 +14,22 @@ jobs:
uses: actions/checkout@v7
- name: Install Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
cache: false
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Run Tests
run: |
cargo test
+26
View File
@@ -0,0 +1,26 @@
name: Renovate
on:
schedule:
- cron: "0 * * * *"
workflow_dispatch:
jobs:
renovate:
name: Dependency-Updates prüfen & Pull Requests erstellen
runs-on: ubuntu-latest
container: ghcr.io/renovatebot/renovate:44.82.0
steps:
- name: Renovate ausführen
run: renovate
env:
RENOVATE_PLATFORM: gitea
RENOVATE_ENDPOINT: ${{ gitea.server_url || github.server_url }}/api/v1/
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
RENOVATE_REPOSITORIES: ${{ gitea.repository || github.repository }}
RENOVATE_AUTODISCOVER: "false"
RENOVATE_GIT_AUTHOR: "Renovate Bot <renovate-bot@creativedragonslayer.de>"
RENOVATE_HOST_RULES: >-
[{"hostType":"cargo","matchHost":"${{ gitea.server_url || github.server_url }}","token":"${{ secrets.RENOVATE_TOKEN }}"}]
GITHUB_COM_TOKEN: ${{ secrets.GH_RENOVATE_TOKEN }}
LOG_LEVEL: info
+95
View File
@@ -0,0 +1,95 @@
name: Security Scans
on:
push:
branches:
- main
- testing
- dev
pull_request:
schedule:
- cron: "0 5 * * 1"
workflow_dispatch:
jobs:
security-scan:
name: Trivy & OSV-Scanner
runs-on: ubuntu-latest
env:
TRIVY_VERSION: "0.74.0"
OSV_SCANNER_VERSION: "2.5.1"
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Lokales bin-Verzeichnis zum PATH hinzufügen
run: |
mkdir -p "$HOME/.local/bin"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Cache Trivy-Binary
id: cache-trivy
uses: actions/cache@v6
with:
path: ~/.local/bin/trivy
key: trivy-bin-${{ runner.os }}-${{ env.TRIVY_VERSION }}
- name: Install Trivy
if: steps.cache-trivy.outputs.cache-hit != 'true'
run: |
curl -fsSL -o trivy.tar.gz \
"https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz"
tar -xzf trivy.tar.gz trivy
chmod +x trivy
mv trivy "$HOME/.local/bin/trivy"
rm -f trivy.tar.gz
- name: Ermittle Cache-Datum für Trivy-DB
run: echo "CACHE_DATE=$(date -u +%Y-%m-%d)" >> "$GITHUB_ENV"
- name: Cache Trivy-Schwachstellen-Datenbank
uses: actions/cache@v6
with:
path: ~/.cache/trivy
key: trivy-db-${{ runner.os }}-${{ env.CACHE_DATE }}
restore-keys: |
trivy-db-${{ runner.os }}-
- name: Run Trivy Scanner
run: |
trivy fs \
--scanners vuln,secret,misconfig \
--severity CRITICAL,HIGH \
--format json \
--output trivy-results.json \
--exit-code 0 \
.
- name: Cache OSV-Scanner-Binary
id: cache-osv-scanner
uses: actions/cache@v6
with:
path: ~/.local/bin/osv-scanner
key: osv-scanner-bin-${{ runner.os }}-${{ env.OSV_SCANNER_VERSION }}
- name: Install OSV-Scanner
if: steps.cache-osv-scanner.outputs.cache-hit != 'true'
run: |
curl -fsSL -o "$HOME/.local/bin/osv-scanner" \
"https://github.com/google/osv-scanner/releases/download/v${OSV_SCANNER_VERSION}/osv-scanner_linux_amd64"
chmod +x "$HOME/.local/bin/osv-scanner"
- name: Run OSV-Scanner
run: |
set +e
osv-scanner scan source --recursive --format json --output-file osv-results.json .
echo "OSV_EXIT=$?" >> "$GITHUB_ENV"
- name: Ergebnisse & Gitea-Issue erstellen/aktualisieren
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
TOKEN: ${{ secrets.SECURITY_TOKEN }}
RUN_URL: ${{ gitea.server_url || github.server_url }}/${{ gitea.repository || github.repository }}/actions/runs/${{ gitea.run_id || github.run_id }}
run: |
python3 scripts/report-security-issue.py trivy-results.json osv-results.json
+12 -1
View File
@@ -14,11 +14,22 @@ jobs:
uses: actions/checkout@v7
- name: Install Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
cache: false
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Run Tests
run: |
cargo test
+59
View File
@@ -0,0 +1,59 @@
name: TruffleHog Secret Scan
on:
push:
pull_request:
schedule:
- cron: "0 6 * * 1"
workflow_dispatch:
jobs:
trufflehog-scan:
name: TruffleHog
runs-on: ubuntu-latest
env:
TRUFFLEHOG_VERSION: "3.97.4"
steps:
- name: Checkout Repository
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Lokales bin-Verzeichnis zum PATH hinzufügen
run: |
mkdir -p "$HOME/.local/bin"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Cache TruffleHog-Binary
id: cache-trufflehog
uses: actions/cache@v6
with:
path: ~/.local/bin/trufflehog
key: trufflehog-bin-${{ runner.os }}-${{ env.TRUFFLEHOG_VERSION }}
- name: Install TruffleHog
if: steps.cache-trufflehog.outputs.cache-hit != 'true'
run: |
curl -fsSL -o trufflehog.tar.gz \
"https://github.com/trufflesecurity/trufflehog/releases/download/v${TRUFFLEHOG_VERSION}/trufflehog_${TRUFFLEHOG_VERSION}_linux_amd64.tar.gz"
tar -xzf trufflehog.tar.gz trufflehog
chmod +x trufflehog
mv trufflehog "$HOME/.local/bin/trufflehog"
rm trufflehog.tar.gz
- name: Run TruffleHog Scanner
run: |
set +e
trufflehog git file://. --results=verified,unknown --fail --json > trufflehog-results.json
echo "TRUFFLEHOG_EXIT=$?" >> "$GITHUB_ENV"
- name: Ergebnisse & Gitea-Issue erstellen/aktualisieren
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
TOKEN: ${{ secrets.SECURITY_TOKEN }}
RUN_URL: ${{ gitea.server_url || github.server_url }}/${{ gitea.repository || github.repository }}/actions/runs/${{ gitea.run_id || github.run_id }}
ISSUE_TITLE: "Security-Scan: TruffleHog Secrets"
ISSUE_LABEL: "security-scan-trufflehog"
run: |
python3 scripts/report-security-issue.py "" "" trufflehog-results.json
+38
View File
@@ -0,0 +1,38 @@
name: Unit-Tests
on:
pull_request:
types:
- opened
- synchronize
- reopened
branches:
- testing
jobs:
test:
name: Unit-Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Install Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
cache: false
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Run Tests
run: cargo test
+19 -18
View File
@@ -1,3 +1,21 @@
# ---> Rust
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# ---> JetBrains
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
@@ -91,21 +109,4 @@ fabric.properties
# Built Visual Studio Code Extensions
*.vsix
# ---> Rust
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.junie/plans
+1
View File
@@ -3,6 +3,7 @@
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
+15 -11
View File
@@ -1,12 +1,14 @@
# AGENTS.md
Dieses Dokument definiert Richtlinien, Konventionen und Arbeitsanweisungen für KI-Agenten und LLM-Tools, die an diesem Repository oder daraus erstellten Rust-Crates arbeiten.
Dieses Dokument definiert Richtlinien, Konventionen und Arbeitsanweisungen für KI-Agenten und LLM-Tools, die an diesem Repository (`sudo-ctdra`) arbeiten.
---
## 1. Projektübersicht & Kontext
- **Typ:** Rust Library Crate Template
- **Projektname:** `sudo-ctdra`
- **Typ:** Rust Library
- **Zweck:** Bereitstellung von Hilfsfunktionen zur Überprüfung (`is_run_as_root`) und Anforderung von Root-Rechten via `sudo` (`run_as_root`).
- **Rust Edition:** `2024`
- **Einstiegspunkt:** `src/lib.rs`
- **CI/CD Plattform:** Gitea Actions (`.gitea/workflows/`)
@@ -20,18 +22,19 @@ Dieses Dokument definiert Richtlinien, Konventionen und Arbeitsanweisungen für
### 2.1 Sprache & Idiomatik
- Verwende modernes, idiomatisches Rust (Edition 2024).
- Bevorzuge explizite Typen und klare Signaturen in öffentlichen Schnittstellen (`pub`).
- Halte die API ergonomisch und benutzerfreundlich.
- Halte die API ergonomisch, leichtgewichtig und benutzerfreundlich.
### 2.2 Fehlerbehandlung
- Nutze `Result<T, E>` und `Option<T>` für alle potenziell fehlschlagenden Operationen.
- Definiere aussagekräftige, domänenspezifische Fehlertypen (z. B. via `thiserror` oder standardmäßig `std::error::Error`).
- Nutze `Result<T, E>`, `Option<T>` oder explizite Fehlerrückgaben (`std::io::Error`) für alle potenziell fehlschlagenden Operationen.
- **Verboten im produktiven Bibliothekscode (`src/`):**
- Unbegründete `unwrap()`, `expect()` oder `panic!()` Aufrufe.
- Abrupter Programmabbruch via `std::process::exit` innerhalb von Bibliotheksfunktionen (Fehler müssen stattdessen an den Aufrufer zurückgegeben werden).
- Direktes oder ungefragtes Logging via Hilfsfunktionen oder Makros im Bibliothekscode; Fehlermeldungen / Fehlerstrukturen sind als Rückgabewerte zu liefern.
- Ignorieren von Fehlern via `let _ = ...`, es sei denn, es ist explizit begründet und dokumentiert.
### 2.3 Dokumentation
- Dokumentiere alle öffentlichen Module, Structs, Enums, Traits und Funktionen mit Rustdoc-Kommentaren (`///` bzw. Modul-Ebene `//!`).
- Füge für öffentliche Schnittstellen nach Möglichkeit Code-Beispiele ein, die über `cargo test --doc` automatisch validiert werden.
- Füge für öffentliche Schnittstellen Code-Beispiele ein, die über `cargo test --doc` automatisch validiert werden.
### 2.4 Code-Qualität & Formatierung
- Halte den Code stets formatiert gemäß `rustfmt` (`cargo fmt`).
@@ -92,10 +95,11 @@ Die CI/CD-Pipelines werden über Gitea Actions gesteuert:
---
## 5. Arbeitsanweisungen für Agenten bei Projekt-Initialisierung
## 5. Arbeitsanweisungen für Agenten
Wenn dieser Template-Stand verwendet wird, um eine neue Crate zu erstellen:
1. Aktualisiere die `Cargo.toml`-Metadaten (`name`, `version`, `authors`, `repository`, `description`).
2. Passe die `README.md` an die konkrete Funktionalität der neuen Crate an.
3. Behalte die Gitea-Workflows bei oder passe sie an das Ziel-Repository an.
Bei Änderungen an diesem Repository:
1. Pflege die Metadaten in `Cargo.toml` (`version`, `description`, etc.).
2. Halte `README.md` und `AGENTS.md` aktuell bezüglich Funktionsumfang und Konventionen.
3. Behalte die Gitea-Workflows bei bzw. passe sie bei Änderungen an.
4. Stelle sicher, dass keine Secrets, temporären Build-Dateien (`target/`) oder IDE-spezifischen Caches (außer `.idea` Konfigurationen) committed werden.
5. Führe stets alle Prüfungen gemäß Abschnitt 3.2 vor Abschluss der Arbeiten durch.
Generated
+11 -2
View File
@@ -3,5 +3,14 @@
version = 4
[[package]]
name = "rust-creat-template"
version = "1.0.0"
name = "libc"
version = "1.0.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d0f24f33af482526a4e3f9b47f0abb2c6377a1713c8aa4a8106994689a4cfa5"
[[package]]
name = "sudo-ctdra"
version = "1.0.1"
dependencies = [
"libc",
]
+6 -14
View File
@@ -1,23 +1,15 @@
[package]
name = "rust-creat-template" # TODO Setzen
version = "1.0.0" # TODO Setzen
name = "sudo-ctdra"
version = "1.0.1"
edition = "2024"
authors = ['DragonSlayer_14'] # TODO Setzen
authors = ['DragonSlayer_14']
readme = "README.md"
license = "GPL-3.0-or-later"
repository = "https://gitea.creative-dragonslayer.de/Templates/rust-crate-template" # TODO Setzen
description = "Ein Template-Projekt, das fürs erstellen von Rust-Crates verwendet werden kann." # TODO Setzen
repository = "https://gitea.creative-dragonslayer.de/Rust-Crates/sudo"
description = "Eine Rust-Bibliothek zur Überprüfung und Anforderung von Root-Rechten über sudo unter Linux."
[dependencies]
libc = "1.0.0-alpha.4"
[profile.release]
debug = "none"
[registry]
default = "gitea"
[registries.gitea]
index = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
[net]
git-fetch-with-cli = true
+2 -2
View File
@@ -208,7 +208,7 @@ If you develop a new program, and you want it to be of the greatest possible use
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
rust-crate-template
sudo
Copyright (C) 2026 Rust-Crates
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
@@ -221,7 +221,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
rust-crate-template Copyright (C) 2026 Rust-Crates
sudo Copyright (C) 2026 Rust-Crates
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
+51 -53
View File
@@ -1,17 +1,54 @@
# Rust Crate Template
# sudo-ctdra
Ein vorkonfiguriertes Template-Projekt für die schnelle und standardisierte Entwicklung von Rust-Crates (Libraries) mit automatisierter CI/CD-Pipeline für Gitea.
Eine kompakte, leichtgewichtige Rust-Bibliothek zur Überprüfung und Anforderung von Root-/Administrator-Rechten über `sudo` unter Linux und Unix-Systemen.
---
## 🚀 Übersicht & Features
- **Rust Edition 2024**: Moderner Rust-Standard mit optimierten Profil-Einstellungen (`profile.release.debug = "none"`).
- **Automatisierte CI/CD-Workflows (Gitea Actions)**:
- **Testing-Pipeline (`testing`-Branch)**: Führt Tests und Compiler-Checks aus und erstellt automatisch ein Gitea Pre-Release (`v<VERSION>-preview`) inklusive `.crate`-Paket als Release-Asset.
- **Main-Release-Pipeline (`main`-Branch)**: Führt Tests und Compiler-Checks aus, paketiert die Crate, veröffentlicht sie in der Gitea Cargo Package Registry und erstellt ein offizielles Gitea Release (`v<VERSION>`) mit Asset.
- **Integrierte Gitea Package Registry**: Vorkonfigurierte sparse index Registry-Anbindung.
- **GPL-3.0-or-later Lizenz**: Vorkonfiguriert mit Lizenzdatei und Metadaten.
- **Root-Prüfung (`is_run_as_root`)**: Schnelle und zuverlässige Überprüfung der effektiven Benutzer-ID (`libc::geteuid() == 0`).
- **Prozess-Eskalation (`run_as_root`)**: Startet das aktuelle Programm über `sudo` neu, übergibt alle Befehlszeilenargumente (`std::env::args`) und ersetzt den aktuellen Prozess ([`CommandExt::exec`](https://doc.rust-lang.org/std/os/unix/process/trait.CommandExt.html#tymethod.exec)).
- **Saubere Fehlerbehandlung**: Gibt bei Fehlschlägen einen [`std::io::Error`] zurück, anstatt das Programm unkontrolliert zu beenden oder ungefragt zu loggen.
- **Rust Edition 2024**: Moderner Rust-Standard mit optimierten Profileinstellungen.
- **Automatisierte CI/CD-Workflows**: Vorkonfigurierte Gitea Actions für Tests, Compiler-Checks, Paketierung und Releases.
---
## 📦 Einbindung
Füge die Crate zu deiner `Cargo.toml` hinzu:
```toml
[dependencies]
sudo-ctdra = { version = "1.0.0", registry = "gitea" }
```
Falls die Gitea Package Registry genutzt wird, trage diese in deiner `.cargo/config.toml` ein:
```toml
[registries.gitea]
index = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
```
---
## 💡 Anwendungsbeispiel
```rust
use sudo_ctdra::{is_run_as_root, run_as_root};
fn main() {
if is_run_as_root() {
println!("Programm läuft mit Root-Rechten.");
// Privilegierte Aktionen durchführen...
} else {
println!("Normale Benutzerrechte erkannt. Starte neu als Root über sudo...");
let err = run_as_root();
eprintln!("Fehler beim Ausführen von 'sudo': {err}");
std::process::exit(1);
}
}
```
---
@@ -25,7 +62,9 @@ Ein vorkonfiguriertes Template-Projekt für die schnelle und standardisierte Ent
│ └── testing.yaml # CI/CD: Test, Check & Pre-Release für 'testing'
├── .idea/ # Vorkonfigurierte JetBrains IDE Einstellungen
├── src/
│ └── lib.rs # Einstiegspunkt der Crate / Library
│ └── lib.rs # Einstiegspunkt der Crate / Bibliotheksfunktionen
├── tests/
│ └── integration_tests.rs # Integrations- und Subprozess-Tests
├── Cargo.lock
├── Cargo.toml # Crate-Manifest & Metadaten
├── LICENSE # GNU General Public License v3.0
@@ -35,39 +74,16 @@ Ein vorkonfiguriertes Template-Projekt für die schnelle und standardisierte Ent
---
## 🛠️ Verwendung als Template
### 1. Template initialisieren & Metadaten anpassen
Passe nach dem Klonen bzw. Erstellen des neuen Repositories die Platzhalter in `Cargo.toml` an:
```toml
[package]
name = "mein-crate-name"
version = "0.1.0"
edition = "2024"
authors = ['DeinName <deine.email@example.com>']
readme = "README.md"
license = "GPL-3.0-or-later"
repository = "https://gitea.example.com/Organisation/mein-crate-name"
description = "Beschreibung der Crate."
```
### 2. Gitea Repository Secrets einrichten
Für die Veröffentlichung und Release-Erstellung in Gitea Actions muss mindestens ein Access-Token als Repository-Secret hinterlegt werden (z. B. unter `Einstellungen -> Secrets -> Actions`):
- `PACKAGE_TOKEN` (oder alternativ `RELEASE_TOKEN`, `GITEA_TOKEN`): Ein Personal Access Token mit Berechtigungen für Packages (`write:package`) und Repositories/Releases (`write:repository`).
---
## 💻 Lokale Entwicklung & Befehle
Die gängigen Cargo-Befehle zur Entwicklung:
Die gängigen Cargo-Befehle zur Entwicklung und Validierung:
- **Kompilierung prüfen:**
```bash
cargo check --all-targets
```
- **Tests ausführen:**
- **Tests ausführen (Unit-, Integrations- und Doc-Tests):**
```bash
cargo test
```
@@ -97,25 +113,7 @@ Die gängigen Cargo-Befehle zur Entwicklung:
| `testing` | Push auf `testing` | • `cargo test`<br>• `cargo check --all-targets`<br>• `cargo package`<br>• Erstellt/Aktualisiert Pre-Release `v<VERSION>-preview` mit `.crate`-Asset |
| `main` | Push auf `main` | • `cargo test`<br>• `cargo check --all-targets`<br>• `cargo package`<br>• Veröffentlicht Crate in Gitea Package Registry<br>• Erstellt/Aktualisiert Release `v<VERSION>` mit `.crate`-Asset |
> **Hinweis zur Versionierung:** Die Versionsnummer wird automatisch aus `Cargo.toml` (`version = "..."`) ausgelesen. Passe vor einem Merge auf `main` oder `testing` die Version in `Cargo.toml` entsprechend SemVer an.
---
## 📦 Verwenden der Crate in anderen Projekten
Um die in der Gitea Package Registry veröffentlichte Crate in einem anderen Cargo-Projekt zu verwenden:
1. Trage die Registry in deiner lokalen `~/.cargo/config.toml` oder projektweiten `.cargo/config.toml` ein:
```toml
[registries.gitea]
index = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
```
2. Binde die Crate in deiner `Cargo.toml` ein:
```toml
[dependencies]
mein-crate-name = { version = "1.0.0", registry = "gitea" }
```
> **Hinweis zur Versionierung:** Die Versionsnummer wird automatisch aus `Cargo.toml` (`version = "..."`) ausgelesen. Passe vor einem Release / Merge die Version in `Cargo.toml` entsprechend **SemVer** an.
---
+50
View File
@@ -0,0 +1,50 @@
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
#-------------------------------------------------------------------------------#
#################################################################################
# WARNING: Do not store sensitive information in this file, #
# as its contents will be included in the Qodana report. #
#################################################################################
version: "1.0"
#Specify inspection profile for code analysis
profile:
name: qodana.starter
#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>
#Disable inspections
#exclude:
# - name: <SomeDisabledInspectionId>
# paths:
# - <path/where/not/run/inspection>
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
# Quality gate. Will fail the CI/CD pipeline if any condition is not met
# severityThresholds - configures maximum thresholds for different problem severities
# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code
# dependencyLicenses - fails the run on prohibited or unknown dependency licenses
# Code Coverage is available in Ultimate and Ultimate Plus plans
#failureConditions:
# severityThresholds:
# any: 15
# critical: 5
# testCoverageThresholds:
# fresh: 70
# total: 50
# dependencyLicenses:
# failOnProhibited: true
# failOnUnknown: false
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
linter: jetbrains/qodana-<linter>:2026.2
+102
View File
@@ -0,0 +1,102 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"timezone": "Europe/Berlin",
"schedule": ["before 6am on monday"],
"allowCustomCrateRegistries": true,
"baseBranchPatterns": [
"dev"
],
"packageRules": [
{
"matchFileNames": [".gitea/workflows/**"],
"groupName": "Gitea Actions",
"separateMajorMinor": false,
"separateMinorPatch": false
},
{
"matchManagers": ["cargo"],
"groupName": "Cargo Dependencies",
"separateMajorMinor": false,
"separateMinorPatch": false
},
{
"matchManagers": ["dockerfile", "docker-compose"],
"groupName": "Docker-Images",
"separateMajorMinor": false,
"separateMinorPatch": false
}
],
"customManagers": [
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"TRIVY_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "aquasecurity/trivy",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"OSV_SCANNER_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "google/osv-scanner",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"TRUFFLEHOG_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "trufflesecurity/trufflehog",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"CARGO_BINSTALL_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "cargo-bins/cargo-binstall",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"CARGO_DEB_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "cargo-deb",
"datasourceTemplate": "crate"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"CARGO_GENERATE_RPM_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "cargo-generate-rpm",
"datasourceTemplate": "crate"
}
]
}
+312
View File
@@ -0,0 +1,312 @@
#!/usr/bin/env python3
"""Erstellt oder kommentiert ein Gitea-Issue mit den Ergebnissen der Security-Scans.
Sucht ein offenes Issue mit dem Label ISSUE_LABEL (Standard: "security-scan").
Existiert eines, wird der aktuelle Scan-Stand als neuer Kommentar angehängt
(die Historie bleibt erhalten). Existiert keines (z.B. weil das letzte
geschlossen wurde), wird ein neues Issue erstellt. Gibt es keine Funde mehr,
wird ein offenes Issue nur kommentiert, nicht geschlossen.
Titel und Label lassen sich per Umgebungsvariable ISSUE_TITLE / ISSUE_LABEL
überschreiben, damit z.B. TruffleHog-Funde in ein eigenes Issue laufen statt
in das gemeinsame Trivy/OSV-Issue.
"""
import json
import os
import sys
import urllib.error
import urllib.request
LABEL_NAME = os.environ.get("ISSUE_LABEL", "security-scan")
LABEL_COLOR = "#b60205"
ISSUE_TITLE = os.environ.get("ISSUE_TITLE", "Security-Scan: Offene Schwachstellen")
SEVERITY_ORDER = {
"VERIFIED": -1,
"CRITICAL": 0,
"HIGH": 1,
"MEDIUM": 2,
"LOW": 3,
"UNKNOWN": 4,
"UNVERIFIED": 6,
}
def api(method, path, token, gitea_url, data=None):
url = f"{gitea_url}/api/v1{path}"
body = json.dumps(data).encode() if data is not None else None
req = urllib.request.Request(url, data=body, method=method)
req.add_header("Authorization", f"token {token}")
req.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req, timeout=10) as resp:
raw = resp.read()
return json.loads(raw) if raw else None
except urllib.error.HTTPError as e:
print(f"Gitea API Fehler ({method} {path}): {e.code} {e.read().decode()}", file=sys.stderr)
raise
def make_finding(source, id, severity, package, installed="-", fixed="-", target="-"):
return {
"source": source,
"id": id,
"severity": severity,
"package": package,
"installed": installed,
"fixed": fixed,
"target": target,
}
def cvss_score_to_severity(score):
try:
score = float(score)
except (TypeError, ValueError):
return "UNKNOWN"
if score >= 9.0:
return "CRITICAL"
if score >= 7.0:
return "HIGH"
if score >= 4.0:
return "MEDIUM"
if score > 0.0:
return "LOW"
return "UNKNOWN"
def load_trivy(path):
findings = []
if not path or not os.path.isfile(path):
return findings
with open(path) as f:
data = json.load(f)
for result in data.get("Results", []) or []:
target = result.get("Target", "?")
for vuln in result.get("Vulnerabilities", []) or []:
findings.append(make_finding(
"Trivy",
vuln.get("VulnerabilityID", "?"),
vuln.get("Severity", "UNKNOWN"),
vuln.get("PkgName", "?"),
installed=vuln.get("InstalledVersion", "?"),
fixed=vuln.get("FixedVersion") or "-",
target=target,
))
for misc in result.get("Misconfigurations", []) or []:
findings.append(make_finding(
"Trivy (Misconfig)",
misc.get("ID", "?"),
misc.get("Severity", "UNKNOWN"),
misc.get("Title", "?"),
target=target,
))
for secret in result.get("Secrets", []) or []:
findings.append(make_finding(
"Trivy (Secret)",
secret.get("RuleID", "?"),
secret.get("Severity", "UNKNOWN"),
secret.get("Title", "?"),
target=target,
))
return findings
def load_osv(path):
findings = []
if not path or not os.path.isfile(path):
return findings
with open(path) as f:
data = json.load(f)
for result in data.get("results", []) or []:
source = (result.get("source") or {}).get("path", "?")
for pkg in result.get("packages", []) or []:
info = pkg.get("package", {})
pkg_name = f"{info.get('name', '?')} ({info.get('ecosystem', '?')})"
severity_by_id = {}
for group in pkg.get("groups", []) or []:
label = cvss_score_to_severity(group.get("max_severity"))
for vuln_id in group.get("ids", []) or []:
severity_by_id[vuln_id] = label
for vuln in pkg.get("vulnerabilities", []) or []:
vuln_id = vuln.get("id", "?")
findings.append(make_finding(
"OSV-Scanner",
vuln_id,
severity_by_id.get(vuln_id, "UNKNOWN"),
pkg_name,
installed=info.get("version", "?"),
target=source,
))
return findings
def load_trufflehog(path):
findings = []
if not path or not os.path.isfile(path):
return findings
with open(path) as f:
for line in f:
line = line.strip()
if not line:
continue
try:
entry = json.loads(line)
except json.JSONDecodeError:
continue
git_meta = ((entry.get("SourceMetadata") or {}).get("Data") or {}).get("Git") or {}
findings.append(make_finding(
"TruffleHog",
entry.get("DetectorName", "?"),
"VERIFIED" if entry.get("Verified") else "UNVERIFIED",
git_meta.get("file", "?"),
target=git_meta.get("commit", "-"),
))
return findings
def sort_findings(findings):
return sorted(findings, key=lambda f: (SEVERITY_ORDER.get(f["severity"], 9), f["id"]))
def print_summary(findings):
if not findings:
print("Keine Funde.")
return
widths = {
key: max(len(key), *(len(str(f[key])) for f in findings))
for key in ("source", "id", "severity", "package", "installed", "fixed", "target")
}
header = ("source", "id", "severity", "package", "installed", "fixed", "target")
row_fmt = " ".join(f"{{:{widths[k]}}}" for k in header)
print(row_fmt.format(*header))
print(row_fmt.format(*("-" * widths[k] for k in header)))
for f in findings:
print(row_fmt.format(*(str(f[k]) for k in header)))
TRUFFLEHOG_GUIDANCE = """### Vorgehen bei gefundenen Secrets
1. **Sofort rotieren/widerrufen**: Das betroffene Secret (Token, Passwort, Schlüssel) beim jeweiligen Dienst ungültig machen und durch ein neues ersetzen. Ein einmal committetes Secret gilt als kompromittiert, auch wenn es später aus der Historie entfernt wird.
2. **Ursache beheben**: Neues Secret nur noch über Umgebungsvariablen/Secrets-Store einbinden, nicht erneut hart codieren.
3. **Historie bereinigen (optional, manuell, erst nach Schritt 1)**: Mit `git filter-repo` oder BFG Repo-Cleaner den Commit-Inhalt entfernen, danach `git push --force` in Absprache mit allen Mitwirkenden bestehende Clones/Forks werden dadurch ungültig.
4. **Issue schließen**, sobald rotiert wurde. TruffleHog findet das alte Secret ggf. weiterhin in der Historie nach der Rotation ist das unkritisch.
> Der Wert des Secrets selbst wird hier bewusst nicht ausgegeben, auch nicht gekürzt nur Detector, Datei und Commit. Fund lässt sich über "Ziel" (Commit-Hash) und "Paket" (Dateipfad) lokalisieren."""
def escape_md_cell(value):
return str(value).replace("|", "\\|").replace("\r", " ").replace("\n", " ")
def build_report(findings, run_url):
lines = [
"Automatisch erstellt vom Security-Scan-Workflow.",
f"Lauf: {run_url}" if run_url else "",
"",
"| Quelle | ID | Schweregrad | Paket | Installiert | Fix | Ziel |",
"|---|---|---|---|---|---|---|",
]
for f in findings:
cells = (f["source"], f["id"], f["severity"], f["package"], f["installed"], f["fixed"], f["target"])
lines.append("| " + " | ".join(escape_md_cell(c) for c in cells) + " |")
if any(f["source"] == "TruffleHog" for f in findings):
lines.append("")
lines.append(TRUFFLEHOG_GUIDANCE)
return "\n".join(lines)
def ensure_label(token, gitea_url, repo):
page = 1
while True:
labels = api("GET", f"/repos/{repo}/labels?limit=50&page={page}", token, gitea_url) or []
for label in labels:
if label.get("name") == LABEL_NAME:
return label["id"]
if len(labels) < 50:
break
page += 1
created = api("POST", f"/repos/{repo}/labels", token, gitea_url, {
"name": LABEL_NAME,
"color": LABEL_COLOR,
"description": "Automatisch verwaltet vom Security-Scan-Workflow",
})
return created["id"]
def find_open_issue(token, gitea_url, repo):
issues = api(
"GET",
f"/repos/{repo}/issues?state=open&type=issues&labels={LABEL_NAME}",
token,
gitea_url,
) or []
for issue in issues:
if issue.get("title") == ISSUE_TITLE:
return issue
return None
def main():
trivy_path = sys.argv[1] if len(sys.argv) > 1 else None
osv_path = sys.argv[2] if len(sys.argv) > 2 else None
trufflehog_path = sys.argv[3] if len(sys.argv) > 3 else None
gitea_url = os.environ.get("GITEA_URL", "").strip().rstrip("/")
repo = os.environ.get("REPO", "").strip()
token = os.environ.get("TOKEN", "").strip()
run_url = os.environ.get("RUN_URL", "")
osv_exit = int(os.environ.get("OSV_EXIT", "0"))
trufflehog_exit = int(os.environ.get("TRUFFLEHOG_EXIT", "0"))
findings = sort_findings(load_trivy(trivy_path) + load_osv(osv_path) + load_trufflehog(trufflehog_path))
print_summary(findings)
if not token or not gitea_url or not repo:
missing = [name for name, val in [("TOKEN", token), ("GITEA_URL", gitea_url), ("REPO", repo)] if not val]
print(f"{', '.join(missing)} nicht gesetzt oder leer überspringe Gitea-Issue-Synchronisation.")
else:
open_issue = find_open_issue(token, gitea_url, repo)
if findings:
report = build_report(findings, run_url)
if open_issue:
print(f"Kommentiere bestehendes Issue #{open_issue['number']} mit {len(findings)} Fund(en).")
api("POST", f"/repos/{repo}/issues/{open_issue['number']}/comments", token, gitea_url, {"body": report})
else:
label_id = ensure_label(token, gitea_url, repo)
print(f"Erstelle neues Issue mit {len(findings)} Fund(en).")
api("POST", f"/repos/{repo}/issues", token, gitea_url, {
"title": ISSUE_TITLE,
"body": report,
"labels": [label_id],
})
elif open_issue:
print(f"Keine aktuellen Funde mehr. Kommentiere Issue #{open_issue['number']}.")
api("POST", f"/repos/{repo}/issues/{open_issue['number']}/comments", token, gitea_url, {
"body": f"Aktueller Scan hat keine offenen Schwachstellen mehr gefunden.\n\n{run_url}".strip(),
})
else:
print("Keine Funde und kein offenes Issue vorhanden.")
osv_ok_exits = {0, 1}
trufflehog_ok_exits = {0, 183}
if osv_exit not in osv_ok_exits:
print(f"WARNUNG: osv-scanner beendete sich mit unerwartetem Exit-Code {osv_exit} - Scan evtl. unvollständig.", file=sys.stderr)
if trufflehog_exit not in trufflehog_ok_exits:
print(f"WARNUNG: trufflehog beendete sich mit unerwartetem Exit-Code {trufflehog_exit} - Scan evtl. unvollständig.", file=sys.stderr)
has_trivy_findings = any(f["source"].startswith("Trivy") for f in findings)
if (
has_trivy_findings
or osv_exit == 1
or trufflehog_exit == 183
or osv_exit not in osv_ok_exits
or trufflehog_exit not in trufflehog_ok_exits
):
sys.exit(1)
if __name__ == "__main__":
main()
+86
View File
@@ -0,0 +1,86 @@
//! Eine leichtgewichtige Rust-Bibliothek zur Überprüfung und Anforderung von Root-Rechten über `sudo`.
//!
//! # Beispiele
//!
//! ```rust
//! use sudo_ctdra::is_run_as_root;
//!
//! if is_run_as_root() {
//! println!("Das Programm läuft mit Root-Rechten.");
//! } else {
//! println!("Das Programm läuft mit normalen Benutzerrechten.");
//! }
//! ```
use std::env;
use std::io;
use std::os::unix::process::CommandExt;
use std::process::Command;
/// Prüft, ob das Programm mit Root-/Administrator-Rechten ausgeführt wird.
///
/// Ermittelt anhand der effektiven Benutzer-ID (`geteuid() == 0`), ob der aktuelle
/// Prozess über Root-Rechte verfügt.
///
/// # Returns
///
/// * `true` - Das Programm läuft mit erhöhten Rechten (EUID == 0)
/// * `false` - Das Programm läuft mit normalen Benutzerrechten
///
/// # Beispiele
///
/// ```rust
/// use sudo_ctdra::is_run_as_root;
///
/// let is_root = is_run_as_root();
/// println!("Läuft als Root: {}", is_root);
/// ```
#[must_use]
pub fn is_run_as_root() -> bool {
unsafe { libc::geteuid() == 0 }
}
/// Startet das Programm mit Root-Rechten über `sudo` neu.
///
/// Diese Funktion versucht das Programm mit erhöhten Rechten via `sudo` neu zu starten
/// und übergibt dabei alle bisherigen Befehlszeilenargumente (`std::env::args`).
///
/// Da bei erfolgreicher Ausführung der bestehende Prozess durch den `sudo`-Aufruf
/// ersetzt wird ([`CommandExt::exec`]), kehrt diese Funktion im Erfolgsfall nicht zurück.
///
/// # Returns
///
/// Gibt einen [`std::io::Error`] zurück, falls die Ausführung von `sudo` fehlschlägt.
///
/// # Beispiele
///
/// ```no_run
/// use sudo_ctdra::{is_run_as_root, run_as_root};
///
/// if !is_run_as_root() {
/// let err = run_as_root();
/// eprintln!("Fehler beim Neustart mit Root-Rechten: {err}");
/// }
/// ```
pub fn run_as_root() -> io::Error {
let commandline_args: Vec<String> = env::args().collect();
Command::new("sudo").args(&commandline_args).exec()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_run_as_root_matches_libc_geteuid() {
let expected = unsafe { libc::geteuid() == 0 };
assert_eq!(is_run_as_root(), expected);
}
#[test]
fn test_is_run_as_root_consistency() {
let first = is_run_as_root();
let second = is_run_as_root();
assert_eq!(first, second);
}
}
+39
View File
@@ -0,0 +1,39 @@
use std::process::Command;
use sudo_ctdra::{is_run_as_root, run_as_root};
#[test]
fn test_public_api_is_run_as_root() {
let is_root = is_run_as_root();
let expected = unsafe { libc::geteuid() == 0 };
assert_eq!(is_root, expected);
}
#[test]
fn test_run_as_root_returns_io_error_when_command_fails() {
if std::env::var("TEST_RUN_AS_ROOT_SUBPROCESS").is_ok() {
let err = run_as_root();
if err.kind() == std::io::ErrorKind::NotFound {
std::process::exit(42);
} else {
std::process::exit(1);
}
}
let current_exe =
std::env::current_exe().expect("Pfad zur Test-Executable konnte nicht ermittelt werden");
let output = Command::new(current_exe)
.arg("test_run_as_root_returns_io_error_when_command_fails")
.arg("--exact")
.arg("--nocapture")
.env("TEST_RUN_AS_ROOT_SUBPROCESS", "1")
.env("PATH", "")
.output()
.expect("Subprozess konnte nicht ausgeführt werden");
assert_eq!(
output.status.code(),
Some(42),
"Subprozess sollte mit Exit-Code 42 beendet worden sein (ErrorKind::NotFound). Stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
}