Compare commits

...

22 Commits

Author SHA1 Message Date
995eb10df8 f 2026-06-19 14:57:50 +02:00
b495c231e0 change https to gitea 2026-06-19 14:37:41 +02:00
0fb95e78f7 changed login form
All checks were successful
Tests / Declarative: Post Actions No test results found
csharp-secdevops-pipeline-pod/pipeline/head This commit looks good
2026-06-11 11:05:42 +02:00
77d7bb758d added apk commands
All checks were successful
Tests / Declarative: Post Actions No test results found
csharp-secdevops-pipeline-pod/pipeline/head This commit looks good
2026-06-11 09:33:52 +02:00
707e39e70a added pvc saving of Key
Some checks failed
Tests / Declarative: Post Actions No test results found
csharp-secdevops-pipeline-pod/pipeline/head There was a failure building this commit
2026-06-11 08:05:59 +02:00
f096aa9b0a added Login Authentication
All checks were successful
Tests / Declarative: Post Actions No test results found
csharp-secdevops-pipeline-pod/pipeline/head This commit looks good
2026-06-10 13:14:15 +02:00
72a891887e added functionality to add Jenkins Build Number as Label to Image
All checks were successful
Tests / Declarative: Post Actions No test results found
csharp-secdevops-pipeline-pod/pipeline/head This commit looks good
2026-05-13 10:39:04 +02:00
3e752c909b changed back to "@/contrib/html.tpl"
All checks were successful
Tests / Declarative: Post Actions No test results found
csharp-secdevops-pipeline-pod/pipeline/head This commit looks good
2026-05-13 10:14:55 +02:00
ebd021eeb7 updated documentation in Dockerfile und Jenkinsfile and updated to --template "html"
All checks were successful
Tests / Declarative: Post Actions No test results found
csharp-secdevops-pipeline-pod/pipeline/head This commit looks good
2026-05-13 10:10:04 +02:00
06caba6243 changed Dockerfile and Program.cs
All checks were successful
Tests / Declarative: Post Actions No test results found
csharp-secdevops-pipeline-pod/pipeline/head This commit looks good
2026-05-12 12:46:05 +02:00
da97867c33 changed Dockerfile to allow chown
All checks were successful
Tests / Declarative: Post Actions No test results found
csharp-secdevops-pipeline-pod/pipeline/head This commit looks good
2026-05-12 12:32:56 +02:00
981201dc02 added Sqlite Integration to Program.cs
All checks were successful
Tests / Declarative: Post Actions No test results found
csharp-secdevops-pipeline-pod/pipeline/head This commit looks good
2026-05-12 12:20:18 +02:00
251776b9fd Complete Rebuild of Project from HelloWorld to Blazer WebApp
All checks were successful
Tests / Declarative: Post Actions No test results found
csharp-secdevops-pipeline-pod/pipeline/head This commit looks good
2026-05-12 09:16:44 +02:00
e784b7996c reordered the pipeline steps -> build after trivy scan 2026-05-11 21:47:57 +02:00
89410a8b07 changed build process and include dotnet restore before trivy scan 2026-05-11 21:07:18 +02:00
47ea86224a fixed reportFiles Parameter Line
All checks were successful
Tests / Declarative: Post Actions passed: 1
csharp-secdevops-pipeline-pod/pipeline/head This commit looks good
2026-05-07 12:04:17 +02:00
dacd8fd7c4 fixed missing comma
Some checks failed
csharp-secdevops-pipeline-pod/pipeline/head There was a failure building this commit
2026-05-07 12:03:20 +02:00
08b1417605 changed Trivy Security Jenkins
Some checks failed
csharp-secdevops-pipeline-pod/pipeline/head There was a failure building this commit
2026-05-07 12:02:29 +02:00
b9b06263d8 Umstellen auf alpine Versionen
All checks were successful
Tests / Declarative: Post Actions passed: 1
csharp-secdevops-pipeline-pod/pipeline/head This commit looks good
2026-05-07 11:54:27 +02:00
83ff4e89e9 added report creation for trivy image scan
Some checks failed
Tests / Declarative: Post Actions passed: 1
csharp-secdevops-pipeline-pod/pipeline/head There was a failure building this commit
2026-05-07 11:43:48 +02:00
4106136782 remove --push-retry 5 for tests 2026-05-07 11:41:40 +02:00
46f26e947b added trivy image scan and branch master check 2026-05-07 11:23:06 +02:00
184 changed files with 878 additions and 8410 deletions

12
.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
# .NET Build-Artefakte
bin/
obj/
# Kompilierte Bibliotheken
*.so
*.dll
*.exe
*.pdb
# Datenbank-Dateien (lokale SQLite)
*.db

20
Components/App.razor Normal file
View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="SecDevOpsLab.styles.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet />
</head>
<body>
<Routes />
<script src="_framework/blazor.web.js"></script>
</body>
</html>

View File

@@ -0,0 +1,23 @@
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

View File

@@ -0,0 +1,96 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

View File

@@ -0,0 +1,30 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">SecDevOpsLab</a>
</div>
</div>
<input type="checkbox" title="Navigation menu" class="navbar-toggler" />
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="weather">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
</NavLink>
</div>
</nav>
</div>

View File

@@ -0,0 +1,105 @@
.navbar-toggler {
appearance: none;
cursor: pointer;
width: 3.5rem;
height: 2.5rem;
color: white;
position: absolute;
top: 0.5rem;
right: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
}
.navbar-toggler:checked {
background-color: rgba(255, 255, 255, 0.5);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep .nav-link {
color: #d7d7d7;
background: none;
border: none;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
width: 100%;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep .nav-link:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.nav-scrollable {
display: none;
}
.navbar-toggler:checked ~ .nav-scrollable {
display: block;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.nav-scrollable {
/* Never collapse the sidebar for wide screens */
display: block;
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}

View File

@@ -0,0 +1,32 @@
@page "/books"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@using SecDevOpsLab.Models
@using SecDevOpsLab.Data
@inject AppDbContext Db
@rendermode InteractiveServer
<h3>Bücherverwaltung</h3>
<input @bind="newBook.Title" placeholder="Titel" />
<input @bind="newBook.Author" placeholder="Autor" />
<button @onclick="Save">Speichern</button>
<hr />
<ul>
@foreach (var b in bookList) {
<li>@b.Title von @b.Author</li>
}
</ul>
@code {
private Book newBook = new();
private List<Book> bookList = new();
protected override async Task OnInitializedAsync() => bookList = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync(Db.Books);
private async Task Save() {
Db.Books.Add(newBook);
await Db.SaveChangesAsync();
newBook = new();
bookList = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync(Db.Books);
}
}

View File

@@ -0,0 +1,19 @@
@page "/counter"
@rendermode InteractiveServer
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}

View File

@@ -0,0 +1,36 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@code{
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}

View File

@@ -0,0 +1,7 @@
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.

View File

@@ -0,0 +1,40 @@
@page "/login"
@using System.Security.Claims
@using Microsoft.AspNetCore.Authentication
@using Microsoft.AspNetCore.Authentication.Cookies
@inject NavigationManager Navigation
<h3>SecDevOps Lab Login</h3>
@if (!string.IsNullOrEmpty(errorMessage))
{
<div class="alert alert-danger">@errorMessage</div>
}
@* Wichtig: Ein traditionelles HTML-Formular nutzen, um Cookies setzen zu können *@
<form action="/api/auth/login" method="post">
<AntiforgeryToken />
<div class="mb-3">
<label class="form-label">Benutzername</label>
<input type="text" name="username" class="form-control" required />
</div>
<div class="mb-3">
<label class="form-label">Passwort</label>
<input type="password" name="password" class="form-control" required />
</div>
<button type="submit" class="btn btn-primary">Einloggen</button>
</form>
@code {
private string? errorMessage;
protected override void OnInitialized()
{
// Falls ein Fehler beim Login auftrat, fangen wir ihn über die URL ab
var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
if (Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query).TryGetValue("error", out var error))
{
errorMessage = "Ungültige Zugangsdaten.";
}
}
}

View File

@@ -0,0 +1,64 @@
@page "/weather"
@attribute [StreamRendering]
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate streaming rendering
await Task.Delay(500);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
}
private class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}

6
Components/Routes.razor Normal file
View File

@@ -0,0 +1,6 @@
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
</Router>

10
Components/_Imports.razor Normal file
View File

@@ -0,0 +1,10 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using SecDevOpsLab
@using SecDevOpsLab.Components

8
Data/AppDbContext.cs Normal file
View File

@@ -0,0 +1,8 @@
namespace SecDevOpsLab.Data;
using Microsoft.EntityFrameworkCore;
using SecDevOpsLab.Models;
public class AppDbContext : DbContext {
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Book> Books => Set<Book>();
}

View File

@@ -1,27 +1,51 @@
# --- Stage 1: Build (Die Bau-Umgebung) ---
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
# Basis Image das für die Build Umgebung verwendet wird
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build-env
# Wechseln ins Arbeitsverzeichnis
WORKDIR /app
# 1. Nur Projektdatei kopieren und Abhängigkeiten laden (Nutzt Docker-Caching)
COPY *.sln ./
COPY MyHelloWorld/*.csproj ./MyHelloWorld/
COPY MyHelloWorld.Tests/*.csproj ./MyHelloWorld.Tests/
# Kopieren der Projektdatei in Arbeitsverzeichnis
COPY *.csproj ./
# Laden der Abhängigkeiten
RUN dotnet restore
# 2. Den restlichen Quellcode kopieren und die App kompilieren
# Kopieren des restlichen Quellcodes
COPY . ./
RUN dotnet publish MyHelloWorld/*.csproj -c Release -o out
# Kompilieren eds Quellcodes (Projektdatei muss nicht zwingend angegeben werden)
RUN dotnet publish "SecDevOpsLab.csproj" -c Release -o out
# --- Stage 2: Runtime (Das fertige, schlanke Image) ---
FROM mcr.microsoft.com/dotnet/aspnet:8.0
# Noch schlangere Basis Image für die Runtime Umgebung
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
# Wechseln ins Arbeitsverzeichnis
WORKDIR /app
# 3. Den vorinstallierten User 'app' von Microsoft nutzen
# Öffnen des Ports für die Web-App (Standard bei .NET 8 Web-Apps ist 8080)
EXPOSE 8080
# Konfigurieren des integrierten Kestrel-Webserver einer ASP.NET Core App
ENV ASPNETCORE_URLS=http://+:8080
# Wechseln auf root (nur kurz)
USER root
# OS patchen
RUN apk update && apk upgrade --no-cache
# Ändern des File Owner (wichtig dass die Sqlite DB geschrieben werden kann)
RUN mkdir -p /app/data && chown -R app:app /app/data && chmod -R 775 /app/data
# Wechsel auf non-root User (app ist ein vorinstallierter User von Microsoft)
USER app
# 4. Nur die fertigen Binärdateien aus der Bau-Umgebung rüberschieben
# Kopieren der fertigen Binärdateien aus der Build Umgebung
COPY --from=build-env /app/out .
# 5. Startbefehl festlegen
# WICHTIG: Falls deine DLL anders heißt (z.B. MyHelloWorld.dll), passe den Namen hier an!
# Definieren eines Arguments, das dann beim kaniko Aufruf mitübergeben wird
ARG JENKINS_BUILD=unknown
# Schreiben des Inhalts des übergebenen Werts des Arguments als Label in das Image
# kubectl get pods --show-labels
# kubectl get pod <pod-name> -n bookmanager-apps -o jsonpath='{.status.containerStatuses[*].imageID}'
# kubectl get pods -L jenkins.build.number
LABEL org.opencontainers.image.version=${JENKINS_BUILD} \
managed-by="Jenkins"
# Festlegen des Start Befehls
ENTRYPOINT ["dotnet", "SecDevOpsLab.dll"]

133
Jenkinsfile vendored
View File

@@ -1,7 +1,8 @@
pipeline {
agent {
kubernetes {
// Definiert den Pod mit dem .NET 8 SDK Image
// Definieren des Pod mit 3 Containern als Build Agent, Trivy und Kaniko
yaml '''
apiVersion: v1
kind: Pod
@@ -26,94 +27,139 @@ pipeline {
}
options {
// Log-Rotation: Bewahre nur die letzten 10 Builds auf
// und lösche Berichte/Artefakte von Builds, die älter als 7 Tage sind.
// Aktivieren von Log Rotation:
// *) Letzten 10 Builds werden aufbewahrt
// ** Builds, die älter als 7 Tage sind, werden gelöscht
buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '10', daysToKeepStr: '7'))
}
stages {
// Auschecken der Sourcen zum App Projekt
stage('Checkout Source') {
steps {
// Ersetze 'dein-user' und 'dein-repo' durch die Namen aus Gitea
git url: 'http://130.61.26.230:30080/dev-master/secdevops-csharp-app.git',
git url: 'https://gitea.dagobert84.duckdns.org/dev-master/secdevops-csharp-app.git',
branch: 'master'
}
}
stage('Build with .NET 8') {
steps {
// Führt den Build-Befehl im spezialisierten Container aus
container('dotnet8') {
sh 'dotnet --version' // Zur Bestätigung der Version
sh 'dotnet build'
}
}
}
stage('Security: Trivy Scan') {
steps {
// Ausführen des 'dotnet restore' für den nachfolgenden Trivy Scan, der diese wiederhergestellte Dateien/NuGet Pakete und Abhängigkeiten scanned
// Generiert automatisch das obj/ mit der project.assets.json
// Alle Container eines Pods teilen sich das Jenkins Arbeitsverzeichnis
container('dotnet8') {
sh 'dotnet restore'
}
// Ausführen des Trivy Scans
//
// Wichtig: trivy ersetzt -> dotnet list package --vulnerable --include-transitive
container('trivy') {
// Wir erstellen ein Verzeichnis für den Report
// Erzeugen des Directory zum Speichern des Reports
sh 'mkdir -p reports'
// Der Befehl erzeugt die HTML-Datei
// --format template: Nutzt ein Layout
// --template "@/contrib/html.tpl": Das Standard-Trivy-Layout
// Scannt das Dateisystem auf Schwachstellen (NuGet) und Secrets
// --exit-code 1 lässt die Pipeline bei kritischen Fehlern abbrechen
sh 'trivy fs --scanners vuln,misconfig,secret,license --exit-code 1 --severity HIGH,CRITICAL --format template --template "@/contrib/html.tpl" -o reports/trivy-report.html .'
// Ausführen des Scans hinsichtlich Vulnerabilities, Miskonfigurationen, Secrets und Licences im Jenkins Arbeitsverzeichnis
// Abbruch bei bei kritischen Fehlern (--exit-code 1 --severity HIGH,CRITICAL)
sh 'trivy fs --scanners vuln,misconfig,secret,license --exit-code 1 --severity HIGH,CRITICAL --format template --template "@/contrib/html.tpl" -o reports/trivy-fs-report.html .'
}
}
}
stage('Unit Tests') {
steps {
container('dotnet8') {
// Kompilieren der Anwendung (DLLs werden erzeugt)
// Schritt muss gar nicht durchgeführt werden, da kaniko das Image erzeugt
stage('Build with .NET 8') {
steps {
// Führt den Build-Befehl im spezialisierten Container aus
container('dotnet8') {
sh 'dotnet --version' // Zur Bestätigung der Version
sh 'dotnet build --configuration Release' // optimierter Build Prozess ohne Debug und ungenutzt Pfade
}
}
}
//stage('Unit Tests') {
// steps {
// container('dotnet8') {
// Erstellt eine XML-Datei im Format 'junit', die Jenkins lesen kann
// sh 'dotnet test --configuration Release'
sh 'dotnet test --configuration Release --logger "junit;LogFileName=results.xml"'
}
}
}
// sh 'dotnet test --configuration Release --logger "junit;LogFileName=results.xml"'
// }
//}
//}
stage('Set Build Name') {
steps {
script {
// Setzt den Namen des aktuellen Laufs auf die Version + Build-Nummer
// Setzen des Namen des aktuellen Laufs auf die Version + Build-Nummer
currentBuild.displayName = "v1.0.0-build-${env.BUILD_NUMBER}"
}
}
}
stage('Docker Build & Push') {
when {
branch 'master'
}
steps {
container('kaniko') {
// Nutze die ID, die du in Jenkins für den Token vergeben hast
// Stellt die Informationen aus dem Token in Form von Umgebungsvariablen der Jenkins Pipeline zur Verfügung
// Nachfolgend werden diese Credentials im JSON Format in config.json geschrieben
// Vorgehen ist zwar nicht extrem sicher, aber die Lebenszeit im Container ist kurz, dass diese base64 kodierten Daten zurückverwandelt werden könnten
// Erzeugen des Directory zum Speichern des Reports, falls das bei einem vorigen Schritt nicht durchgeführt wurde
sh 'mkdir -p reports'
withCredentials([usernamePassword(credentialsId: 'gitea-registry-token',
usernameVariable: 'GITEA_USER',
passwordVariable: 'GITEA_TOKEN')]) {
sh '''
# WORKAROUND: Dem Container beibringen, wer git.example.com ist
echo "130.61.26.230 git.example.com" >> /etc/hosts
# WORKAROUND: Dem Container beibringen, wer git.example.com ist, ansonsten funktioniert das Übertragen des Images an Git nicht!!!
#echo "130.61.26.230 git.example.com" >> /etc/hosts
# Erstellt die Docker-Konfiguration für Kaniko
# Das $(echo ...) Kommando kombiniert User und Token für den Login
echo "{\\"auths\\":{\\"130.61.26.230:30080\\":{\\"auth\\":\\"\$(echo -n \${GITEA_USER}:\${GITEA_TOKEN} | base64)\\"}}}" > /kaniko/.docker/config.json
echo "{\\"auths\\":{\\"https://gitea.dagobert84.duckdns.org\\":{\\"auth\\":\\"\$(echo -n \${GITEA_USER}:\${GITEA_TOKEN} | base64)\\"}}}" > /kaniko/.docker/config.json
# Der Bau- und Push-Befehl
# Wir taggen das Image mit 'latest' UND der Build-Nummer zur Sicherheit
/kaniko/executor --context `pwd` \
--dockerfile `pwd`/Dockerfile \
--insecure \
--push-retry 5 \
--skip-tls-verify \
--destination 130.61.26.230:30080/dev-master/secdevops-csharp-app:latest \
--destination 130.61.26.230:30080/dev-master/secdevops-csharp-app:${BUILD_NUMBER}
--build-arg JENKINS_BUILD=${BUILD_NUMBER} \
--destination https://gitea.dagobert84.duckdns.org/dev-master/secdevops-csharp-app:latest \
--destination https://gitea.dagobert84.duckdns.org/dev-master/secdevops-csharp-app:${BUILD_NUMBER}
'''
}
}
}
}
stage('Security: Trivy Image Scan') {
when {
branch 'master'
}
steps {
// Trivy Scan wird auf das Image im Git Repository angewendet. Das Image wird heruntergeladen.
container('trivy') {
// 1. Scan ausführen und als HTML-Report speichern (Achte auf den neuen Dateinamen)
sh '''
trivy image --insecure \
--severity HIGH,CRITICAL \
--format template \
--template "@/contrib/html.tpl" \
--exit-code 1 \
-o reports/trivy-image-report.html \
https://gitea.dagobert84.duckdns.org/dev-master/secdevops-csharp-app:latest
'''
// Den Scan ein zweites Mal kurz ohne Report ausführen, damit die Pipeline bei Lücken blockiert
// sh 'trivy image --insecure --exit-code 1 --severity HIGH,CRITICAL 130.61.26.230:30080/dev-master/secdevops-csharp-app:latest'
}
}
}
}
post {
@@ -123,21 +169,20 @@ pipeline {
}
}
always {
// Sammelt die Testergebnisse ein (die wir im Test-Schritt erzeugen)
// Das **/ bedeutet: Suche in allen Unterordnern nach .xml Dateien
junit testResults: '**/TestResults/*.xml', allowEmptyResults: true
// Suchen und Einelen von etwaigen Testreports von JUnit. Keine Vorhanden? -auch ok
junit testResults: '**/*.xml', allowEmptyResults: true
// Dieser Schritt macht den Report im Jenkins-Menü links sichtbar
// Verwenden des HTML Publisher Modules zum Schreiben der gefundenen Testreports in das Build Menu und speichert den HTML Bericht dann historisch ab (keepAll)
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'reports',
reportFiles: 'trivy-report.html',
reportFiles: 'trivy-fs-report.html,trivy-image-report.html',
reportName: 'Trivy Security Report'
])
// Meldet den Status zurück, wenn das Gitea-Plugin korrekt konfiguriert ist
// Schreiben des Build Status in das Build Log
echo "Pipeline beendet: ${currentBuild.result}"
}
}

8
Models/Books.cs Normal file
View File

@@ -0,0 +1,8 @@
namespace SecDevOpsLab.Models;
using System.ComponentModel.DataAnnotations;
public class Book {
public int Id { get; set; }
[Required] public string Title { get; set; } = string.Empty;
[Required] public string Author { get; set; } = string.Empty;
}

View File

@@ -1,30 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<WarningsAsErrors></WarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="JunitXml.TestLogger" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyHelloWorld\SecDevOpsLab.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,21 +0,0 @@
using Xunit;
using MyHelloWorld; // Dein Namespace der Haupt-App
namespace MyHelloWorld.Tests
{
public class HelloTests
{
[Fact]
public void GetGreeting_ShouldReturnCorrectText()
{
// Arrange
var generator = new HelloGenerator();
// Act
var result = generator.GetGreeting();
// Assert
Assert.Equal("Hello World", result);
}
}
}

View File

@@ -1,12 +0,0 @@
{
"runtimeOptions": {
"tfm": "net8.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "8.0.0"
},
"configProperties": {
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

View File

@@ -1,23 +0,0 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"SecDevOpsLab/1.0.0": {
"runtime": {
"SecDevOpsLab.dll": {}
}
}
}
},
"libraries": {
"SecDevOpsLab/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}

View File

@@ -1,12 +0,0 @@
{
"runtimeOptions": {
"tfm": "net8.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "8.0.0"
},
"configProperties": {
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

Some files were not shown because too many files have changed in this diff Show More