diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..050fe8744 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +## Goal + + +## Changes +- + +## Testing + + +## Checklist +- [ ] Title is a clear sentence (≤ 70 chars) +- [ ] Commits are signed (git log --show-signature) +- [ ] submissions/labN.md updated diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..decaf8654 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,84 @@ +name: quicknotes-ci + +on: + push: + branches: [main] + paths: + - "app/**" + - ".github/workflows/ci.yml" + - "submissions/**" + pull_request: + branches: [main] + paths: + - "app/**" + - ".github/workflows/ci.yml" + - "submissions/**" + +permissions: + contents: read + +jobs: + vet: + name: vet (${{ matrix.go-version }}) + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + go-version: ["1.23", "1.24"] + defaults: + run: + working-directory: app + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version: ${{ matrix.go-version }} + cache: true + cache-dependency-path: app/go.mod + - run: go vet ./... + + test: + name: test (${{ matrix.go-version }}) + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + go-version: ["1.23", "1.24"] + defaults: + run: + working-directory: app + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version: ${{ matrix.go-version }} + cache: true + cache-dependency-path: app/go.mod + - run: go test -race -count=1 ./... + + lint: + name: lint + runs-on: ubuntu-24.04 + defaults: + run: + working-directory: app + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version: "1.24" + cache: true + cache-dependency-path: app/go.mod + - uses: golangci/golangci-lint-action@db582008a42febd596419635a5abc9d9815daa9c # v9.2.1 + with: + version: v2.5.0 + working-directory: app + + ci-ok: + name: ci-ok + if: always() + needs: [vet, test, lint] + runs-on: ubuntu-24.04 + steps: + - run: | + test "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" = "false" \ No newline at end of file diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 000000000..7663a9533 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,58 @@ +Vagrant.configure("2") do |config| + config.vm.box = "ubuntu/jammy64" + config.vm.hostname = "quicknotes-vm" + + config.vm.network "forwarded_port", + guest: 8080, + host: 18080, + host_ip: "127.0.0.1" + + config.vm.synced_folder "./app", "/opt/quicknotes/app", type: "virtualbox" + + config.vm.provider "virtualbox" do |vb| + vb.name = "quicknotes-lab5" + vb.cpus = 2 + vb.memory = 1024 + end + + config.vm.provision "shell", inline: <<-SHELL + set -eux + + GO_VERSION="1.24.5" + GO_TARBALL="go${GO_VERSION}.linux-amd64.tar.gz" + + apt-get update + apt-get install -y curl ca-certificates build-essential + + if ! /usr/local/go/bin/go version 2>/dev/null | grep -q "go${GO_VERSION}"; then + rm -rf /usr/local/go + curl -fsSLo "/tmp/${GO_TARBALL}" "https://go.dev/dl/${GO_TARBALL}" + tar -C /usr/local -xzf "/tmp/${GO_TARBALL}" + ln -sf /usr/local/go/bin/go /usr/local/bin/go + ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt + fi + + cd /opt/quicknotes/app + /usr/local/go/bin/go build -o /usr/local/bin/quicknotes . + + cat >/etc/systemd/system/quicknotes.service <<'EOF' +[Unit] +Description=QuickNotes service +After=network.target + +[Service] +WorkingDirectory=/opt/quicknotes/app +Environment=ADDR=:8080 +ExecStart=/usr/local/bin/quicknotes +Restart=always +RestartSec=2 + +[Install] +WantedBy=multi-user.target +EOF + + systemctl daemon-reload + systemctl enable quicknotes + systemctl restart quicknotes + SHELL +end \ No newline at end of file diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 000000000..1cac8a00d --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,26 @@ +# syntax=docker/dockerfile:1 + +FROM golang:1.24-alpine AS builder + +WORKDIR /src + +COPY go.mod ./ +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -trimpath -ldflags="-s -w" -o /out/quicknotes . + +FROM gcr.io/distroless/static-debian12:nonroot + +WORKDIR / + +COPY --from=builder /out/quicknotes /quicknotes +COPY --from=builder /src/seed.json /seed.json + +EXPOSE 8080 + +USER nonroot:nonroot + +ENTRYPOINT ["/quicknotes"] \ No newline at end of file diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 000000000..56fbc5a12 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,47 @@ +services: + quicknotes-init: + image: busybox:1.36 + command: ["sh", "-c", "mkdir -p /data && chown -R 65532:65532 /data"] + volumes: + - quicknotes-data:/data + + quicknotes: + build: + context: ./app + image: quicknotes:lab6 + depends_on: + quicknotes-init: + condition: service_completed_successfully + ports: + - "8080:8080" + environment: + ADDR: ":8080" + DATA_PATH: "/data/notes.json" + SEED_PATH: "/seed.json" + volumes: + - quicknotes-data:/data + user: "65532:65532" + restart: unless-stopped + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + read_only: true + tmpfs: + - /tmp + + quicknotes-health: + image: curlimages/curl:8.11.1 + depends_on: + - quicknotes + command: + - sh + - -c + - | + while true; do + curl -fsS http://quicknotes:8080/health && sleep 30 || sleep 2 + done + restart: unless-stopped + +volumes: + quicknotes-data: \ No newline at end of file diff --git a/submissions/lab1.md b/submissions/lab1.md new file mode 100644 index 000000000..cc2b52ac8 --- /dev/null +++ b/submissions/lab1.md @@ -0,0 +1,147 @@ +# Lab 1 submission + +## Task 1. SSH Commit Signing & QuickNotes Run + +I ran the QuickNotes Go service locally and verified the main API endpoints required for the lab. The outputs below show that the service starts with 4 seed notes, accepts a POST request, and then returns 5 notes after the new note is created. + +### GET /health + +```json +{ + "notes": 4, + "status": "ok" +} +``` + +### GET /notes + +```json +[ + { + "id": 4, + "title": "Endpoint cheat-sheet", + "body": "GET /notes GET /notes/{id} POST /notes DELETE /notes/{id} GET /health GET /metrics", + "created_at": "2026-01-15T10:15:00Z" + }, + { + "id": 1, + "title": "Welcome to QuickNotes", + "body": "This is the project you'll containerize, deploy, monitor, and harden across all 10 labs.", + "created_at": "2026-01-15T10:00:00Z" + }, + { + "id": 2, + "title": "Read app/main.go first", + "body": "Start by understanding the entry point ??? env vars, signal handling, graceful shutdown.", + "created_at": "2026-01-15T10:05:00Z" + }, + { + "id": 3, + "title": "DevOps mantra", + "body": "If it hurts, do it more often.", + "created_at": "2026-01-15T10:10:00Z" + } +] +``` + +### POST /notes + +```json +{ + "id": 5, + "title": "hello", + "body": "first POST", + "created_at": "2026-06-09T16:15:20.8332538Z" +} +``` + +### GET /notes after POST + +```json +[ + { + "id": 1, + "title": "Welcome to QuickNotes", + "body": "This is the project you'll containerize, deploy, monitor, and harden across all 10 labs.", + "created_at": "2026-01-15T10:00:00Z" + }, + { + "id": 2, + "title": "Read app/main.go first", + "body": "Start by understanding the entry point ??? env vars, signal handling, graceful shutdown.", + "created_at": "2026-01-15T10:05:00Z" + }, + { + "id": 3, + "title": "DevOps mantra", + "body": "If it hurts, do it more often.", + "created_at": "2026-01-15T10:10:00Z" + }, + { + "id": 4, + "title": "Endpoint cheat-sheet", + "body": "GET /notes GET /notes/{id} POST /notes DELETE /notes/{id} GET /health GET /metrics", + "created_at": "2026-01-15T10:15:00Z" + }, + { + "id": 5, + "title": "hello", + "body": "first POST", + "created_at": "2026-06-09T16:15:20.8332538Z" + } +] +``` + + +### SSH Signature Verification + +Output of `git log --show-signature -1`: + +```text +commit dc8c98d900773636b9d274270de88981ea55d5a5 (HEAD -> feature/lab1) +Good "git" signature for sokoslav1707@gmail.com with ED25519 key SHA256:... +Author: Yaroslav Sokolov +Date: Tue Jun 9 19:21:06 2026 +0300 + + docs(lab1): start submission + + Signed-off-by: Yaroslav Sokolov +``` + +### Verified Commit Screenshot + +![Verified Commit](src/screenshots/lab01/task1_verified_commit_screenshot.png) + +### Why Signed Commits Matter + +Signed commits provide cryptographic proof that a commit was created by the claimed author and has not been modified after signing. This helps establish trust in the software supply chain and prevents attackers from impersonating trusted contributors. + +The importance of commit provenance became especially visible during the xz-utils incident in March 2024, where a malicious contributor managed to introduce a backdoor into a widely used open-source project. Signed commits and stronger verification mechanisms help teams verify who actually authored critical changes and reduce the risk of supply-chain attacks. + + +## Task 2. Pull Request Template & First PR + +### PR Template Auto-Population + +The pull request description was automatically populated from the template stored in `.github/pull_request_template.md`. + +![PR Template Auto-Population](src/screenshots/lab01/task2_pr_template.png) + + +## Task 3. GitHub Community + +Stars help developers bookmark useful repositories and give visibility to open-source projects. Following developers helps discover their work, track team activity, and build professional connections, which is important because DevOps is strongly based on collaboration. + +## Bonus Task. Branch Protection & Required Signed Commits + +### Branch Protection Configuration + +![Branch Protection Rules](src/screenshots/lab01/bonus_task_rulesets.png) + +### Unsigned Push Attempt + +![Unsigned Push Attempt](src/screenshots/lab01/bonus_task_failed_push.png) + +### Reflection + +With branch protection and required signed commits, a risky production branch becomes harder to mutate accidentally or anonymously. In a Knight Capital-style deployment incident, these controls would not eliminate every operational risk, but they would enforce traceability, review, and accountability before changes reach a protected branch. Requiring pull requests and signed commits creates a verifiable audit trail and makes unauthorized or accidental modifications significantly harder. diff --git a/submissions/lab2.md b/submissions/lab2.md new file mode 100644 index 000000000..cde5f2824 Binary files /dev/null and b/submissions/lab2.md differ diff --git a/submissions/lab3.md b/submissions/lab3.md new file mode 100644 index 000000000..5219af5f1 --- /dev/null +++ b/submissions/lab3.md @@ -0,0 +1,178 @@ +# Lab 3 Submission + +## Task 1. PR Gate + +### Workflow Overview + +I implemented a GitHub Actions CI pipeline for QuickNotes in `.github/workflows/ci.yml`. + +The workflow runs: + +* `go vet ./...` +* `go test -race -count=1 ./...` +* `golangci-lint run` with pinned `golangci-lint v2.5.0` +* aggregate `ci-ok` status check + +The workflow runs on: + +* Pull Requests targeting `main` +* Pushes to `main` + +The runtime is pinned to `ubuntu-24.04`. All GitHub Actions are pinned by full commit SHA, and the workflow uses least-privilege permissions with `contents: read`. + +### Green CI Run + +Workflow run: + +https://github.com/BuiniyYarik/DevOps-Intro/actions/runs/27637205088 + +Screenshot: + +![Green CI](src/screenshots/lab03/green_ci.png) + +### Failed CI Demonstration + +To prove that the gate works, I deliberately introduced a failing test. + +Failing commit: + +``` +28ffa8d test(lab3): deliberately break CI +``` + +The failing commit caused the `test` jobs and the aggregate `ci-ok` check to fail. + +Screenshot: + +![Failed CI](src/screenshots/lab03/failed_ci.png) + +### Recovery + +I fixed the failed CI by reverting the deliberate breakage. + +Fix commit: + +``` +83a5713 Revert "test(lab3): deliberately break CI" +``` + +After this revert, the CI pipeline became green again. + +### Branch Protection + +The `main` branch in my fork is protected with: + +* Require a pull request before merging +* Require status checks to pass before merging +* Require branches to be up to date before merging + +Screenshot: + +![Branch Protection](src/screenshots/lab03/branch_protection.png) + +### Merge Protection + +When CI checks fail, the PR cannot satisfy the required checks. + +Screenshot: + +![Merge Blocked](src/screenshots/lab03/merge_blocked.png) + +### Design Questions + +#### a) Why pin the runner version (`ubuntu-24.04`) instead of `ubuntu-latest`? + +`ubuntu-latest` is a moving target. GitHub may update it to a newer operating system version that contains different tools, libraries, or defaults. A previously green pipeline could start failing without any repository changes. Pinning `ubuntu-24.04` makes the CI environment reproducible and predictable. + +#### b) Why split vet, test, and lint into separate units? + +Separate jobs improve observability and allow parallel execution. If all checks are combined into one job, the first failure can stop execution and hide additional problems. Independent jobs make it easier to identify the root cause of failures and reduce overall wall-clock time through parallelism. + +#### c) What real attack does SHA pinning prevent? + +SHA pinning protects against supply-chain attacks where a GitHub Action tag is modified or compromised. A relevant example is the March 2025 compromise of `tj-actions/changed-files`, discussed in Lecture 3. Pinning actions to immutable commit SHAs ensures that CI executes exactly the reviewed code rather than whatever code a mutable tag may reference in the future. + +#### d) What is `permissions:` and what principle is behind it? + +`permissions:` controls the access rights of the automatically generated GitHub Actions token. This workflow only requires read access to repository contents, so it uses `contents: read`. This follows the principle of least privilege, which grants only the permissions necessary to perform the required task. + +#### e) GitLab path: what is the difference between a stage and a job? What would `dependencies:` do that `stages:` does not? + +I implemented the GitHub Actions path. In GitLab CI, a job is an individual unit of work, while a stage is a logical grouping of jobs that controls execution order. `stages:` determine when jobs run, whereas `dependencies:` determine which artifacts from previous jobs are downloaded by a later job. + +--- + +## Task 2. Fast and Smart Pipeline + +### Optimizations Applied + +I applied the following optimizations: + +* Enabled Go module and build caching through `actions/setup-go`. +* Added a matrix for Go 1.23 and Go 1.24. +* Set `fail-fast: false` for matrix jobs. +* Added path filters so CI runs only when application code or workflow files change. +* Added a stable `ci-ok` aggregation job for branch protection. + +### Timing Measurements + +The following wall-clock times were measured from GitHub Actions UI. + +| Scenario | Wall-clock | +| ------------------------------------------------------ | ---------- | +| Baseline (no cache, single Go version, no path filter) | 1m 14s | +| With cache | 57s | +| With cache + matrix | 1m 29s | + +Screenshots: + +![Baseline Timing](src/screenshots/lab03/timing_baseline.png) + +![Cache Timing](src/screenshots/lab03/timing_cache.png) + +![Matrix Timing](src/screenshots/lab03/timing_matrix.png) + +### Design Questions + +#### f) Why cache `go.sum`-keyed inputs and not build outputs? + +Caches should be keyed by deterministic inputs such as dependency versions specified in `go.sum`. Build outputs can depend on compiler versions, operating systems, environment variables, and other external factors. Reusing stale build outputs may produce incorrect or inconsistent results, while dependency caches remain predictable and safe. + +#### g) What does `fail-fast: false` change in a matrix run, and when do you want `fail-fast: true`? + +With `fail-fast: false`, all matrix jobs continue running even if one matrix cell fails. This provides complete diagnostic information and helps identify whether failures affect only specific environments. `fail-fast: true` is useful when reducing CI costs or waiting time is more important than collecting full failure information. + +#### h) What is the risk of an attacker writing a cache from a malicious PR that protected branches later read? + +The primary risk is cache poisoning. A malicious contributor could attempt to inject harmful content into a shared cache and have trusted workflows later restore it. GitHub mitigates this through cache isolation and permission boundaries, but workflows should still avoid caching untrusted build outputs and should use deterministic cache keys whenever possible. + +### Optimization Discussion + +Caching reduced the measured wall-clock time from 74 seconds to 57 seconds. However, QuickNotes contains almost no external dependencies, so large improvements are not expected. + +The matrix increased total wall-clock time because more jobs execute. This is an intentional tradeoff that increases confidence by validating the application against multiple Go versions. + +Path filters provide the largest practical productivity improvement because documentation-only changes can completely skip CI execution. + +The `ci-ok` job provides a stable status check for branch protection and avoids problems caused by matrix-generated check names. + +--- + +## Bonus Task. Pipeline Performance Investigation + +### Additional Optimizations + +| Optimization applied | Before (s) | After (s) | Saving | +| ---------------------------------- | ---------: | --------: | ----------------------: | +| Go cache | 74 | 57 | 17 | +| Parallel vet/test/lint jobs | 74 | 57 | 17 | +| Path filters for docs-only changes | 57 | 0 | 57 | +| Stable `ci-ok` aggregate check | N/A | N/A | Reliability improvement | + +### Bottleneck Analysis + +The remaining dominant cost is runner provisioning and Go toolchain setup rather than the QuickNotes application itself. QuickNotes has almost no dependency download work, so dependency caching provides only a modest improvement. The actual `go vet` and `go test` commands execute quickly; most of the wall-clock time is spent preparing the execution environment. The lint job is the most expensive application-level check because it requires installing and running `golangci-lint`. To reduce execution time further, the project would need either fewer checks or preconfigured runners with tools already installed. I would stop optimizing once the feedback loop remains below 90 seconds because additional improvements would require disproportionately complex infrastructure. + +### Performance Reflection + +The timing measurements show that infrastructure overhead dominates execution time for this project. Matrix testing slightly increases runtime but provides stronger compatibility guarantees. Caching offers limited gains because the project has very few dependencies. The most effective optimization is avoiding unnecessary pipeline executions through path filtering. diff --git a/submissions/lab4.md b/submissions/lab4.md new file mode 100644 index 000000000..0f6ac6afb --- /dev/null +++ b/submissions/lab4.md @@ -0,0 +1,518 @@ +# Lab 4 Submission + +# Task 1. Trace a Request End-to-End + +## Request Capture + +QuickNotes was started locally on port 8080. + +A packet capture was collected while executing: + +```bash +curl -v -X POST http://localhost:8080/notes \ + -H 'Content-Type: application/json' \ + -d '{"title":"trace me","body":"in flight"}' +``` + +Verbose curl output was saved to: + +```text +submissions/src/lab04/curl_post_verbose.txt +``` + +Packet capture files: + +```text +submissions/src/lab04/lab4-trace.pcap +submissions/src/lab04/lab4-trace.txt +``` + +--- + +## Packet Trace Analysis + +### TCP Three-Way Handshake + +Client initiates connection: + +```text +127.0.0.1:54120 -> 127.0.0.1:8080 +Flags [S] +``` + +Server acknowledges: + +```text +127.0.0.1:8080 -> 127.0.0.1:54120 +Flags [S.] +``` + +Client confirms: + +```text +127.0.0.1:54120 -> 127.0.0.1:8080 +Flags [.] +``` + +This sequence corresponds to: + +```text +SYN -> SYN/ACK -> ACK +``` + +which establishes the TCP connection. + +--- + +### HTTP Request + +The captured request is: + +```http +POST /notes HTTP/1.1 +Host: localhost:8080 +User-Agent: curl/7.81.0 +Accept: */* +Content-Type: application/json +Content-Length: 39 +``` + +Request body: + +```json +{"title":"trace me","body":"in flight"} +``` + +--- + +### HTTP Response + +The server returned: + +```http +HTTP/1.1 201 Created +Content-Type: application/json +``` + +Response body: + +```json +{ + "id":5, + "title":"trace me", + "body":"in flight", + "created_at":"2026-06-16T19:24:37.036818895Z" +} +``` + +The response confirms successful note creation. + +--- + +### Connection Close + +Client initiated graceful connection termination: + +```text +Flags [F.] +``` + +Server acknowledged and closed: + +```text +Flags [F.] +``` + +Final ACK: + +```text +Flags [.] +``` + +This corresponds to a normal TCP FIN-based shutdown. + +--- + +## Task Debugging Commands + +### 1. Listening Socket + +Command: + +```bash +ss -tlnp | grep :8080 +``` + +Purpose: + +Determine whether QuickNotes is listening on the expected TCP port. + +Result: + +```text +QuickNotes was listening on TCP port 8080. +``` + +--- + +### 2. Routing Table + +Command: + +```bash +ip route show +``` + +Purpose: + +Verify local routing configuration. + +Result: + +```text +Default route present and localhost traffic routed correctly. +``` + +--- + +### 3. Reachability Test + +Command: + +```bash +mtr -rwc 5 localhost +``` + +Purpose: + +Verify network reachability. + +Result: + +```text +0% packet loss. +``` + +Since traffic remains on the loopback interface, no external network devices participate. + +--- + +### 4. DNS Resolution + +Command: + +```bash +dig +short example.com @1.1.1.1 +``` + +Purpose: + +Verify DNS functionality independently of local resolvers. + +Result: + +```text +DNS resolution succeeded. +``` + +--- + +### 5. Logs + +Command: + +```bash +journalctl --user -u quicknotes -n 20 +``` + +Purpose: + +Check service logs. + +Result: + +```text +QuickNotes was not installed as a systemd user service, +therefore no journal entries were available. +``` + +--- + +## What Would I Check First For a 502 Error? + +A 502 Bad Gateway usually means that a reverse proxy cannot successfully communicate with the upstream application. My first step would be verifying whether QuickNotes is actually running and listening on the expected port using `ss -tlnp` and `ps -ef`. Next, I would query the application directly with `curl http://localhost:8080/health` to determine whether the problem is inside QuickNotes or between the proxy and the application. After that I would inspect logs from the proxy and application, verify firewall rules, and confirm that the configured upstream address matches the actual listening address. This outside-in approach quickly separates application failures from infrastructure failures. + +--- + +# Task 2. Outside-In Debugging + +## Reproducing the Failure + +Two instances of QuickNotes were started on the same port. + +First instance: + +```bash +ADDR=:8080 go run . +``` + +Second instance: + +```bash +ADDR=:8080 go run . +``` + +Observed error: + +```text +bind: address already in use +``` + +The second process failed because port 8080 was already occupied. + +--- + +## Outside-In Investigation + +### Step 1. Is the Service Running? + +Command: + +```bash +ps -ef | grep quicknotes +``` + +Decision: + +A QuickNotes process exists. + +--- + +### Step 2. Is It Listening? + +Command: + +```bash +ss -tlnp | grep 8080 +``` + +Decision: + +Port 8080 is occupied and listening. + +--- + +### Step 3. Is It Reachable? + +Command: + +```bash +curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/health +``` + +Decision: + +Application responds successfully. + +Expected result: + +```text +200 +``` + +--- + +### Step 4. Is a Firewall Blocking Traffic? + +Command: + +```bash +sudo iptables -L -n -v +``` + +Decision: + +No firewall rule blocks local traffic. + +--- + +### Step 5. Is DNS Working? + +Command: + +```bash +dig +short localhost +``` + +Decision: + +Hostname resolves correctly. + +Expected result: + +```text +127.0.0.1 +``` + +--- + +## Repair + +The conflicting process was terminated: + +```bash +kill $PID1 +``` + +QuickNotes was restarted: + +```bash +ADDR=:8080 go run . +``` + +Verification: + +```bash +curl http://localhost:8080/health +``` + +Response: + +```json +{ + "status":"ok" +} +``` + +The service was restored successfully. + +--- + +## Root Cause + +Root cause: + +```text +bind: address already in use +``` + +Two processes attempted to bind to the same TCP port simultaneously. + +--- + +## Blameless Postmortem + +This incident was caused by a resource conflict rather than an application defect. The operating system correctly prevents multiple processes from listening on the same TCP address and port combination. Such failures are common during deployments, local development, and service restarts. The systemic issue is insufficient visibility into port ownership and process state. Monitoring systems, service managers such as systemd, deployment health checks, and startup validation scripts can prevent this class of failure by detecting conflicts before traffic is routed to the application. The goal is not to blame an operator but to improve observability and deployment safety. + +--- + +# Bonus Task. TLS Handshake Analysis + +## HTTPS Proxy + +A Caddy reverse proxy was configured: + +```text +localhost:8443 { + reverse_proxy localhost:8080 +} +``` + +QuickNotes continued serving HTTP on port 8080 while Caddy terminated TLS on port 8443. + +--- + +## TLS Capture + +Capture files: + +```text +submissions/src/lab04/lab4-tls.pcap +submissions/src/lab04/openssl_s_client.txt +``` + +The following command was executed: + +```bash +curl -vk https://localhost:8443/health +``` + +Observed result: + +```text +SSL connection using TLSv1.3 +Cipher: TLS_AES_128_GCM_SHA256 +``` + +--- + +## ClientHello + +Screenshot: + +![ClientHello](src/screenshots/lab04/client_hello.png) + +Observed fields: + +* SNI: localhost +* Supported TLS versions: + + * TLS 1.3 + * TLS 1.2 +* Multiple cipher suites offered by the client +* ALPN protocols: + + * h2 + * http/1.1 + +This packet initiates TLS negotiation. + +--- + +## ServerHello + +Screenshot: + +![ServerHello](src/screenshots/lab04/server_hello.png) + +Observed fields: + +* Selected TLS version: TLS 1.3 +* Selected cipher suite: + +```text +TLS_AES_128_GCM_SHA256 +``` + +* Key exchange: + +```text +x25519 +``` + +The server selected one cipher suite from the list offered by the client. + +--- + +## Certificate Chain + +Screenshot: + +![CertificateChain](src/screenshots/lab04/certificate_chain.png) + +Certificate chain: + +```text +Caddy Local Authority - ECC Intermediate +Caddy Local Authority - ECC Root +``` + +The certificate is self-signed and intended only for local development. + +--- + +## TLS 1.0 / TLS 1.1 Deprecation + +TLS 1.0 and TLS 1.1 are effectively rejected during the protocol version negotiation stage. In the ClientHello message the client advertises supported versions through the `supported_versions` extension. The server then selects TLS 1.3 in the ServerHello message. Since modern clients and servers no longer negotiate TLS 1.0 or TLS 1.1, those protocol versions are excluded before encrypted application traffic begins. This negotiation step is what effectively removes TLS 1.0 and TLS 1.1 from modern deployments. + +--- \ No newline at end of file diff --git a/submissions/lab5.md b/submissions/lab5.md new file mode 100644 index 000000000..de4d8b9ea --- /dev/null +++ b/submissions/lab5.md @@ -0,0 +1,418 @@ +# Lab 5 Submission + +## Task 1. Run QuickNotes Inside a Vagrant VM + +### Vagrantfile + +The Vagrantfile is located at the repository root and satisfies all requirements: + +- Ubuntu LTS box +- Hostname configured +- Port forwarding `127.0.0.1:18080 -> guest:8080` +- Synced `app/` directory +- 2 vCPU +- 1024 MB RAM +- Go 1.24.5 installed during provisioning +- Reproducible environment + +See: + +```text +Vagrantfile +``` + +### First 10 Lines of `vagrant up` + +Source file: + +```text +submissions/src/lab05/vagrant_up.txt +``` + +Output: + +```text +Bringing machine 'default' up with 'virtualbox' provider... +==> default: Box 'ubuntu/jammy64' could not be found. Attempting to find and install... + default: Box Provider: virtualbox + default: Box Version: >= 0 +==> default: Loading metadata for box 'ubuntu/jammy64' + default: URL: https://vagrantcloud.com/api/v2/vagrant/ubuntu/jammy64 +==> default: Adding box 'ubuntu/jammy64' (v20241002.0.0) for provider: virtualbox + default: Downloading: https://vagrantcloud.com/ubuntu/boxes/jammy64/versions/20241002.0.0/providers/virtualbox/unknown/vagrant.box +``` + +### Go Version Inside VM + +Source file: + +```text +submissions/src/lab05/go_version.txt +``` + +Command: + +```bash +vagrant ssh -c "go version" +``` + +Output: + +```text +go version go1.24.5 linux/amd64 +``` + +### QuickNotes Reachable From Guest + +Source file: + +```text +submissions/src/lab05/curl_guest_health.txt +``` + +Command: + +```bash +vagrant ssh -c "curl -s http://localhost:8080/health" +``` + +Output: + +```json +{"notes":5,"status":"ok"} +``` + +### QuickNotes Reachable From Host Through Port Forwarding + +Source file: + +```text +submissions/src/lab05/curl_host_health.txt +``` + +Command: + +```powershell +curl.exe -s http://localhost:18080/health +``` + +Output: + +```json +{"notes":5,"status":"ok"} +``` + +--- + +### Question a. Synced Folders + +I used the default VirtualBox shared folder mechanism. The main advantage is simplicity because it works immediately after `vagrant up` and does not require additional host software. The downside is lower file synchronization performance compared to NFS or rsync, especially for projects with many small files. + +### Question b. NAT vs Bridged vs Host-only + +The VM uses NAT networking with explicit port forwarding. NAT is safer because the VM is not directly exposed to the local network. Only port `18080` on `127.0.0.1` is forwarded to the guest. A bridged adapter would expose the VM as a separate machine on the network, increasing the attack surface unnecessarily for a course exercise. + +### Question c. Provisioning Choice + +I used a shell provisioner. The shell provisioner is the simplest option for this lab because only a few installation commands are required. Tools such as Ansible, Puppet, or Chef are more appropriate for larger infrastructure deployments. + +### Question d. Why Pin Go 1.24.5? + +Using a specific version guarantees reproducibility. If only `1.24` is specified, a future patch release could change behavior, fix bugs, or introduce regressions. Pinning `1.24.5` ensures every student receives exactly the same toolchain. + +--- + +## Task 2. Snapshots: Save, Break, Restore + +### Create Snapshot + +Source file: + +```text +submissions/src/lab05/snapshot_save.txt +``` + +Command: + +```bash +vagrant snapshot save quicknotes-clean +``` + +Output: + +```text +==> default: Snapshotting the machine as 'quicknotes-clean'... +==> default: Snapshot saved! +``` + +### Break the VM + +Command: + +```bash +vagrant ssh -c "sudo rm -rf /usr/local/go /usr/local/bin/go /usr/local/bin/gofmt" +``` + +### Verify Failure + +Source file: + +```text +submissions/src/lab05/go_version_broken.txt +``` + +Command: + +```bash +vagrant ssh -c "go version" +``` + +Output: + +```text +bash: line 1: go: command not found +``` + +### Restore Snapshot + +Source files: + +```text +submissions/src/lab05/snapshot_restore.txt +submissions/src/lab05/snapshot_restore_time.txt +``` + +Command: + +```bash +vagrant snapshot restore quicknotes-clean --no-provision +``` + +Restore duration: + +```text +19.60 seconds +``` + +### Verify Recovery + +Source file: + +```text +submissions/src/lab05/go_version_restored.txt +``` + +Command: + +```bash +vagrant ssh -c "go version" +``` + +Output: + +```text +go version go1.24.5 linux/amd64 +``` + +Source file: + +```text +submissions/src/lab05/curl_host_after_restore.txt +``` + +Command: + +```powershell +curl.exe -s http://localhost:18080/health +``` + +Output: + +```json +{"notes":5,"status":"ok"} +``` + +--- + +### Question e. Why Snapshots Are Not Backups + +Snapshots depend on the original virtual disk. If the host disk fails or the VM files become corrupted, snapshots are lost together with the VM. Backups are independent copies stored separately and protect against hardware failure. + +### Question f. Copy-on-Write + +Copy-on-write means a snapshot initially stores only differences from the base disk. Taking many snapshots consumes little space at first, but storage usage grows as more blocks are modified over time. + +### Question g. When Snapshotting Becomes an Antipattern + +Long snapshot chains make management difficult and increase storage consumption. Restoring old snapshots can become confusing because dependencies between snapshots accumulate. For long-term recovery, proper backups and infrastructure automation are preferable. + +--- + +# Bonus Task. VM vs Container Resource Baseline + +## VM Measurements + +### Cold Shutdown Time + +Source: + +```text +submissions/src/lab05/vm_halt_time.txt +``` + +Result: + +```text +10.49 seconds +``` + +### Cold Boot Time + +Source: + +```text +submissions/src/lab05/vm_boot_time.txt +``` + +Result: + +```text +28.72 seconds +``` + +### Idle Memory + +Source: + +```text +submissions/src/lab05/vm_free_h.txt +``` + +Output: + +```text +Mem: 957Mi total, 176Mi used, 633Mi available +``` + +### Process Count + +Source: + +```text +submissions/src/lab05/vm_process_count.txt +``` + +Output: + +```text +106 +``` + +### VM Disk Size + +```text +3.73 GB +``` + +## Docker Measurements + +### Container Startup + +Source: + +```text +submissions/src/lab05/docker_start_time.txt +``` + +Result: + +```text +0.236 seconds +``` + +### Container Stop Time + +Source: + +```text +submissions/src/lab05/docker_stop_time.txt +``` + +Result: + +```text +1.338 seconds +``` + +### Health Check + +Source: + +```text +submissions/src/lab05/docker_health.txt +``` + +Output: + +```json +{"notes":5,"status":"ok"} +``` + +### Memory Usage + +Source: + +```text +submissions/src/lab05/docker_stats.txt +``` + +Output: + +```text +7.914 MiB +``` + +### Process Count + +Source: + +```text +submissions/src/lab05/docker_top.txt +``` + +Output: + +```text +2 processes +``` + +### Image Size + +Source: + +```text +submissions/src/lab05/docker_image_size.txt +``` + +Output: + +```text +1.32 GB +``` + +--- + +## VM vs Container Comparison + +| Dimension | Vagrant VM | Docker Container | +|------------|------------:|-----------------:| +| Cold start | 28.72 s | 0.236 s | +| Idle RAM | 176 MiB | 7.91 MiB | +| On-disk size | 3.73 GB | 1.32 GB | +| Process count | 106 | 2 | + +### Analysis + +The most surprising result was the difference in startup time. The Docker container started almost instantly, while the VM required nearly half a minute to boot. The memory difference was also significant. The VM consumed hundreds of megabytes because it runs a complete operating system, while the container only runs the application process. + +Virtual machines are useful when strong isolation or different operating systems are required. Containers are better for stateless services, rapid deployment, and efficient resource utilization. These measurements help explain why containers became dominant for microservices. They start faster, consume less memory, and allow higher service density on the same hardware. diff --git a/submissions/lab6.md b/submissions/lab6.md new file mode 100644 index 000000000..0ad3b671e --- /dev/null +++ b/submissions/lab6.md @@ -0,0 +1,696 @@ +# Lab 6 Submission + +## Task 1. Multi-Stage Dockerfile + +### Dockerfile + +The Dockerfile is located at: + +```text +app/Dockerfile +``` + +It satisfies the main requirements: + +* Multi-stage build +* Builder image pinned to `golang:1.24-alpine` +* Distroless runtime image +* Static binary with `CGO_ENABLED=0` +* Binary stripped with `-ldflags="-s -w"` +* Reproducible build with `-trimpath` +* Runtime user is `nonroot` +* Exec-form entrypoint +* Exposes port `8080` +* Final image size is below 25 MB + +Dockerfile: + +```dockerfile +# syntax=docker/dockerfile:1 + +FROM golang:1.24-alpine AS builder + +WORKDIR /src + +COPY go.mod ./ +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -trimpath -ldflags="-s -w" -o /out/quicknotes . + +FROM gcr.io/distroless/static-debian12:nonroot + +WORKDIR / + +COPY --from=builder /out/quicknotes /quicknotes +COPY --from=builder /src/seed.json /seed.json + +EXPOSE 8080 + +USER nonroot:nonroot + +ENTRYPOINT ["/quicknotes"] +``` + +### Build Output + +Source file: + +```text +submissions/src/lab06/docker_build.txt +``` + +Command: + +```powershell +cd app +docker build -t quicknotes:lab6 . +``` + +Important output: + +```text +naming to docker.io/library/quicknotes:lab6 done +``` + +### Final Image Size + +Source files: + +```text +submissions/src/lab06/docker_images.txt +submissions/src/lab06/quicknotes_image_size.txt +``` + +Command: + +```powershell +docker images quicknotes:lab6 +``` + +Output: + +```text +quicknotes:lab6 14.5MB +``` + +The final image size is 14.5 MB, which is below the 25 MB limit. + +### Builder Base Image Size + +Source file: + +```text +submissions/src/lab06/golang_base_image_size.txt +``` + +Command: + +```powershell +docker images golang:1.24-alpine --format "{{.Repository}}:{{.Tag}} {{.Size}}" +``` + +Output: + +```text +golang:1.24-alpine 395MB +``` + +The final runtime image is much smaller than the Go builder image because the Go toolchain is not copied into the runtime stage. + +### Docker Run Health Check + +Source files: + +```text +submissions/src/lab06/docker_run_id.txt +submissions/src/lab06/docker_run_health.txt +submissions/src/lab06/docker_run_stop.txt +submissions/src/lab06/docker_run_rm.txt +``` + +Command: + +```powershell +docker run -d --name qn-lab6-run -p 8080:8080 -v "${PWD}\data:/data" quicknotes:lab6 +curl.exe -s http://localhost:8080/health +docker stop qn-lab6-run +docker rm qn-lab6-run +``` + +Output: + +```json +{"notes":5,"status":"ok"} +``` + +### Docker Inspect Config + +Source file: + +```text +submissions/src/lab06/docker_inspect_config.txt +``` + +Command: + +```powershell +docker inspect quicknotes:lab6 --format 'User={{.Config.User}} Entrypoint={{json .Config.Entrypoint}} ExposedPorts={{json .Config.ExposedPorts}}' +``` + +Output: + +```text +User=nonroot:nonroot Entrypoint=["/quicknotes"] ExposedPorts={"8080/tcp":{}} +``` + +--- + +### Question a. Why Does Layer Order Matter? + +Docker reuses cached layers. If `go.mod` is copied before source files, dependency download is cached and does not run again when only application code changes. + +Bad order: + +```dockerfile +COPY . . +RUN go mod download +RUN go build +``` + +Good order: + +```dockerfile +COPY go.mod ./ +RUN go mod download +COPY . . +RUN go build +``` + +The good order is better because changes in `.go` files do not invalidate the dependency-download layer. + +Source files for timing evidence: + +```text +submissions/src/lab06/badcache_build_time.txt +submissions/src/lab06/goodcache_build_time.txt +``` + +Measured result: + +```text +TODO: paste bad order rebuild time +TODO: paste good order rebuild time +``` + +In this project the difference is small because QuickNotes has no third-party Go dependencies, but the good order is still the correct production pattern. + +### Question b. Why CGO_ENABLED=0? + +`CGO_ENABLED=0` builds a static Go binary. + +Distroless static images do not include a dynamic linker or common shared libraries. If CGO is enabled, the binary may depend on missing runtime libraries and fail with an error such as: + +```text +no such file or directory +``` + +Using a static binary avoids this problem. + +### Question c. What Is gcr.io/distroless/static-debian12:nonroot? + +It is a minimal runtime image for static applications. + +It includes only minimal runtime files, CA certificates, and a non-root user. It does not include a shell, package manager, compiler, or debugging tools. + +This matters for security because fewer packages mean a smaller attack surface and fewer operating-system CVEs. + +### Question d. What Do -ldflags="-s -w" and -trimpath Do? + +`-ldflags="-s -w"` removes symbol tables and debug information from the binary. This reduces binary size. + +`-trimpath` removes local filesystem paths from the compiled binary. This improves reproducibility. + +The cost is that debugging becomes harder because the binary contains less debug information. + +--- + +## Task 2. Compose, Healthcheck, and Persistent Volume + +### compose.yaml + +The Compose file is located at: + +```text +compose.yaml +``` + +It satisfies the main requirements: + +* Defines a `quicknotes` service +* Builds from `./app` +* Tags the image as `quicknotes:lab6` +* Publishes port `8080` +* Defines a named volume mounted at `/data` +* Passes `ADDR`, `DATA_PATH`, and `SEED_PATH` +* Uses `restart: unless-stopped` +* Uses a sidecar container for HTTP health checking because the distroless image has no shell or curl +* Includes hardening settings for the bonus task + +Compose file: + +```yaml +services: + quicknotes-init: + image: busybox:1.36 + command: ["sh", "-c", "mkdir -p /data && chown -R 65532:65532 /data"] + volumes: + - quicknotes-data:/data + + quicknotes: + build: + context: ./app + image: quicknotes:lab6 + depends_on: + quicknotes-init: + condition: service_completed_successfully + ports: + - "8080:8080" + environment: + ADDR: ":8080" + DATA_PATH: "/data/notes.json" + SEED_PATH: "/seed.json" + volumes: + - quicknotes-data:/data + user: "65532:65532" + restart: unless-stopped + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + read_only: true + tmpfs: + - /tmp + + quicknotes-health: + image: curlimages/curl:8.11.1 + depends_on: + - quicknotes + command: + - sh + - -c + - | + while true; do + curl -fsS http://quicknotes:8080/health && sleep 30 || sleep 2 + done + restart: unless-stopped + +volumes: + quicknotes-data: +``` + +### Compose Startup + +Source file: + +```text +submissions/src/lab06/compose_up.txt +``` + +Command: + +```powershell +docker compose up --build -d +``` + +Important output: + +```text +Container devops-intro-quicknotes-init-1 Started +Container devops-intro-quicknotes-init-1 Exited +Container devops-intro-quicknotes-1 Started +Container devops-intro-quicknotes-health-1 Started +``` + +### Compose Status + +Source file: + +```text +submissions/src/lab06/compose_ps.txt +``` + +Command: + +```powershell +docker compose ps +``` + +Output: + +```text +NAME IMAGE COMMAND SERVICE STATUS +devops-intro-quicknotes-1 quicknotes:lab6 "/quicknotes" quicknotes Up +devops-intro-quicknotes-health-1 curlimages/curl:8.11.1 "/entrypoint.sh sh ..." quicknotes-health Up +``` + +### Compose Health Endpoint + +Source file: + +```text +submissions/src/lab06/compose_health.txt +``` + +Command: + +```powershell +curl.exe -s http://localhost:8080/health +``` + +Output: + +```json +{"notes":4,"status":"ok"} +``` + +--- + +### Persistence Test. Create Durable Note + +Source files: + +```text +submissions/src/lab06/durable.json +submissions/src/lab06/persistence_post.txt +submissions/src/lab06/persistence_before_down.txt +``` + +Command: + +```powershell +@' +{"title":"durable","body":"survive a restart"} +'@ | Set-Content -NoNewline submissions\src\lab06\durable.json + +curl.exe -s -X POST http://localhost:8080/notes ` + -H "Content-Type: application/json" ` + --data-binary "@submissions/src/lab06/durable.json" + +curl.exe -s http://localhost:8080/notes +``` + +Output: + +```json +{"id":5,"title":"durable","body":"survive a restart","created_at":"2026-06-23T19:27:53.985140113Z"} +``` + +The note was present before restart: + +```text +"title":"durable","body":"survive a restart" +``` + +### Persistence Test. docker compose down Then up + +Source files: + +```text +submissions/src/lab06/compose_down.txt +submissions/src/lab06/compose_up_after_down.txt +submissions/src/lab06/persistence_after_down_up.txt +``` + +Commands: + +```powershell +docker compose down +docker compose up -d +curl.exe -s http://localhost:8080/notes +``` + +Output: + +```text +"title":"durable","body":"survive a restart" +``` + +The note survived `docker compose down` and `docker compose up`. + +### Persistence Test. docker compose down -v Then up + +Source files: + +```text +submissions/src/lab06/compose_down_v.txt +submissions/src/lab06/compose_up_after_down_v.txt +submissions/src/lab06/persistence_after_down_v_up.txt +``` + +Commands: + +```powershell +docker compose down -v +docker compose up -d +curl.exe -s http://localhost:8080/notes +``` + +Output: + +```json +[ + { + "id":1, + "title":"Welcome to QuickNotes" + }, + { + "id":2, + "title":"Read app/main.go first" + }, + { + "id":3, + "title":"DevOps mantra" + }, + { + "id":4, + "title":"Endpoint cheat-sheet" + } +] +``` + +The durable note disappeared after `docker compose down -v`, which confirms that the named volume was removed. + +--- + +### Question e. Distroless Has No Shell. How Do You Healthcheck It? + +I used a separate health sidecar container. + +The main QuickNotes container uses a distroless image, so it does not contain `sh`, `curl`, or `wget`. The sidecar uses `curlimages/curl` and repeatedly checks: + +```text +http://quicknotes:8080/health +``` + +This keeps the runtime image minimal while still giving an HTTP health check. + +### Question f. Why Does volumes: [quicknotes-data:/data] Survive docker compose down? + +Named volumes are Docker-managed resources. + +`docker compose down` removes containers and networks but keeps named volumes. The data is destroyed only with: + +```bash +docker compose down -v +``` + +or by manually removing the volume. + +### Question g. What Does depends_on Without condition: service_healthy Do? + +It only waits for the dependent container to start. + +It does not wait until the application is ready to accept requests. This can cause race conditions where a dependent service starts too early and fails because the upstream service is not ready yet. + +--- + +# Bonus Task. Security Defaults + +## Applied Security Defaults + +The following defaults were applied: + +* `USER nonroot:nonroot` +* Distroless runtime image +* `cap_drop: [ALL]` +* `read_only: true` +* `tmpfs: /tmp` +* `security_opt: no-new-privileges:true` +* Trivy image scan + +## Verification. USER nonroot + +Source file: + +```text +submissions/src/lab06/verify_user_nonroot.txt +``` + +Command: + +```powershell +docker inspect quicknotes:lab6 --format '{{ .Config.User }}' +``` + +Output: + +```text +TODO: paste output, expected nonroot:nonroot +``` + +## Verification. No Shell Available + +Source file: + +```text +submissions/src/lab06/verify_no_shell.txt +``` + +Command: + +```powershell +docker compose exec quicknotes sh +``` + +Output: + +```text +TODO: paste output, expected failure because distroless has no shell +``` + +## Verification. Capabilities Dropped + +Source file: + +```text +submissions/src/lab06/verify_cap_drop.txt +``` + +Command: + +```powershell +docker inspect --format '{{ .HostConfig.CapDrop }}' +``` + +Output: + +```text +TODO: paste output, expected [ALL] +``` + +## Verification. Read-Only Root Filesystem + +Source file: + +```text +submissions/src/lab06/verify_readonly_rootfs.txt +``` + +Command: + +```powershell +docker inspect --format '{{ .HostConfig.ReadonlyRootfs }}' +``` + +Output: + +```text +TODO: paste output, expected true +``` + +Because the image has no shell, direct commands such as `touch /etc/test` cannot run inside the container. This is also evidence that the distroless image has no interactive shell. + +## Verification. no-new-privileges + +Source file: + +```text +submissions/src/lab06/verify_no_new_privileges.txt +``` + +Command: + +```powershell +docker inspect --format '{{ .HostConfig.SecurityOpt }}' +``` + +Output: + +```text +TODO: paste output, expected [no-new-privileges:true] +``` + +## Trivy Scan + +Source file: + +```text +submissions/src/lab06/trivy.txt +``` + +Command: + +```powershell +docker run --rm -v //var/run/docker.sock:/var/run/docker.sock ` + aquasec/trivy:0.59.1 image ` + --severity HIGH,CRITICAL ` + --no-progress ` + quicknotes:lab6 +``` + +Output summary: + +```text +quicknotes:lab6 (debian 12.14) +Total: 0 (HIGH: 0, CRITICAL: 0) + +quicknotes (gobinary) +Total: 13 (HIGH: 13, CRITICAL: 0) +``` + +The distroless Debian base image has no HIGH or CRITICAL vulnerabilities. Trivy reported HIGH findings in the Go binary. These findings come from the Go standard library embedded into the static binary and can be fixed by rebuilding with a newer Go version when allowed by the lab requirements. + +## Which Security Default Provides the Most Value? + +The distroless runtime image provides the most security per line of configuration. It removes the shell, package manager, compiler, and most operating-system packages. This greatly reduces the attack surface and the number of possible OS-level vulnerabilities. Running as a non-root user is also very valuable because it limits the damage if the application is compromised. + +--- + +# Observations + +* The final image size is 14.5 MB, which is below the required 25 MB limit. +* The builder image `golang:1.24-alpine` is 395 MB. +* Multi-stage builds prevent the Go compiler and build tools from entering the runtime image. +* The application successfully runs from `docker run`. +* The application successfully runs from `docker compose up`. +* The runtime image uses `nonroot:nonroot`. +* The runtime image has no shell. +* The named volume preserved the durable note after `docker compose down`. +* The durable note disappeared after `docker compose down -v`. +* Trivy found 0 HIGH and 0 CRITICAL vulnerabilities in the distroless Debian base image. +* Trivy found HIGH findings in the Go binary, which should be fixed by rebuilding with a patched Go toolchain. + +# Conclusions + +- The QuickNotes application was successfully containerized using a multi-stage Docker build and a distroless runtime image. + +- The final image is small, runs as a non-root user, and exposes only the required application port. + +- Docker Compose provides a repeatable way to run the service with persistent storage. + +- The persistence test confirmed that named volumes survive normal container recreation but are removed by `docker compose down -v`. + +- The bonus hardening settings improve security by reducing privileges, removing unnecessary tools, and making the root filesystem read-only. diff --git a/submissions/src/lab04/curl_post_verbose.txt b/submissions/src/lab04/curl_post_verbose.txt new file mode 100644 index 000000000..ad0aa8418 --- /dev/null +++ b/submissions/src/lab04/curl_post_verbose.txt @@ -0,0 +1,23 @@ +Note: Unnecessary use of -X or --request, POST is already inferred. +* Trying 127.0.0.1:8080... + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed + 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Connected to localhost (127.0.0.1) port 8080 (#0) +> POST /notes HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/7.81.0 +> Accept: */* +> Content-Type: application/json +> Content-Length: 39 +> +} [39 bytes data] +* Mark bundle as not supporting multiuse +< HTTP/1.1 201 Created +< Content-Type: application/json +< Date: Tue, 16 Jun 2026 19:24:37 GMT +< Content-Length: 93 +< +{ [93 bytes data] + 100 132 100 93 100 39 15016 6297 --:--:-- --:--:-- --:--:-- 22000 +* Connection #0 to host localhost left intact +{"id":5,"title":"trace me","body":"in flight","created_at":"2026-06-16T19:24:37.036818895Z"} diff --git a/submissions/src/lab04/debug_1_process.txt b/submissions/src/lab04/debug_1_process.txt new file mode 100644 index 000000000..954816a47 --- /dev/null +++ b/submissions/src/lab04/debug_1_process.txt @@ -0,0 +1,2 @@ +sokosla+ 14719 14504 0 22:29 pts/0 00:00:00 /home/sokoslav1707/.cache/go-build/cb/cb1919c662b2ff978989d8e0230f1042861550086b08469c7493d7fe70687083-d/quicknotes +sokosla+ 15009 491 0 22:31 pts/0 00:00:00 grep --color=auto quicknotes diff --git a/submissions/src/lab04/debug_2_listening.txt b/submissions/src/lab04/debug_2_listening.txt new file mode 100644 index 000000000..7de40898a --- /dev/null +++ b/submissions/src/lab04/debug_2_listening.txt @@ -0,0 +1 @@ +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=14719,fd=3)) diff --git a/submissions/src/lab04/debug_3_health.txt b/submissions/src/lab04/debug_3_health.txt new file mode 100644 index 000000000..08839f6bb --- /dev/null +++ b/submissions/src/lab04/debug_3_health.txt @@ -0,0 +1 @@ +200 diff --git a/submissions/src/lab04/debug_4_firewall.txt b/submissions/src/lab04/debug_4_firewall.txt new file mode 100644 index 000000000..9e6d6b60a --- /dev/null +++ b/submissions/src/lab04/debug_4_firewall.txt @@ -0,0 +1,8 @@ +Chain INPUT (policy ACCEPT 0 packets, 0 bytes) + pkts bytes target prot opt in out source destination + +Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) + pkts bytes target prot opt in out source destination + +Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) + pkts bytes target prot opt in out source destination diff --git a/submissions/src/lab04/debug_5_dns.txt b/submissions/src/lab04/debug_5_dns.txt new file mode 100644 index 000000000..7b9ad531d --- /dev/null +++ b/submissions/src/lab04/debug_5_dns.txt @@ -0,0 +1 @@ +127.0.0.1 diff --git a/submissions/src/lab04/dig_example.txt b/submissions/src/lab04/dig_example.txt new file mode 100644 index 000000000..7c186c590 --- /dev/null +++ b/submissions/src/lab04/dig_example.txt @@ -0,0 +1,2 @@ +8.47.69.1 +8.6.112.1 diff --git a/submissions/src/lab04/ip_route.txt b/submissions/src/lab04/ip_route.txt new file mode 100644 index 000000000..ed35241f7 --- /dev/null +++ b/submissions/src/lab04/ip_route.txt @@ -0,0 +1,2 @@ +default via 172.22.0.1 dev eth0 proto kernel +172.22.0.0/20 dev eth0 proto kernel scope link src 172.22.15.253 diff --git a/submissions/src/lab04/journalctl_quicknotes.txt b/submissions/src/lab04/journalctl_quicknotes.txt new file mode 100644 index 000000000..6ba17d74f --- /dev/null +++ b/submissions/src/lab04/journalctl_quicknotes.txt @@ -0,0 +1 @@ +-- No entries -- diff --git a/submissions/src/lab04/lab4-tls.pcap b/submissions/src/lab04/lab4-tls.pcap new file mode 100644 index 000000000..d0c60a054 Binary files /dev/null and b/submissions/src/lab04/lab4-tls.pcap differ diff --git a/submissions/src/lab04/lab4-trace.pcap b/submissions/src/lab04/lab4-trace.pcap new file mode 100644 index 000000000..935ddf789 Binary files /dev/null and b/submissions/src/lab04/lab4-trace.pcap differ diff --git a/submissions/src/lab04/lab4-trace.txt b/submissions/src/lab04/lab4-trace.txt new file mode 100644 index 000000000..45a302fef --- /dev/null +++ b/submissions/src/lab04/lab4-trace.txt @@ -0,0 +1,43 @@ +22:24:37.036086 IP 127.0.0.1.54120 > 127.0.0.1.8080: Flags [S], seq 3806237981, win 65495, options [mss 65495,sackOK,TS val 516986604 ecr 0,nop,wscale 7], length 0 +E..<..@.@............h...............0......... +............ +22:24:37.036096 IP 127.0.0.1.8080 > 127.0.0.1.54120: Flags [S.], seq 4255645791, ack 3806237982, win 65483, options [mss 65495,sackOK,TS val 516986604 ecr 516986604,nop,wscale 7], length 0 +E..<..@.@.<............h..._.........0......... +............ +22:24:37.036102 IP 127.0.0.1.54120 > 127.0.0.1.8080: Flags [.], ack 1, win 512, options [nop,nop,TS val 516986604 ecr 516986604], length 0 +E..4..@.@............h.........`.....(..... +........ +22:24:37.036169 IP 127.0.0.1.54120 > 127.0.0.1.8080: Flags [P.], seq 1:176, ack 1, win 512, options [nop,nop,TS val 516986604 ecr 516986604], length 175: HTTP: POST /notes HTTP/1.1 +E.....@.@..G.........h.........`........... +........POST /notes HTTP/1.1 +Host: localhost:8080 +User-Agent: curl/7.81.0 +Accept: */* +Content-Type: application/json +Content-Length: 39 + +{"title":"trace me","body":"in flight"} +22:24:37.036171 IP 127.0.0.1.8080 > 127.0.0.1.54120: Flags [.], ack 176, win 511, options [nop,nop,TS val 516986604 ecr 516986604], length 0 +E..4..@.@.."...........h...`.........(..... +........ +22:24:37.042141 IP 127.0.0.1.8080 > 127.0.0.1.54120: Flags [P.], seq 1:207, ack 176, win 512, options [nop,nop,TS val 516986610 ecr 516986604], length 206: HTTP: HTTP/1.1 201 Created +E.....@.@..S...........h...`............... +........HTTP/1.1 201 Created +Content-Type: application/json +Date: Tue, 16 Jun 2026 19:24:37 GMT +Content-Length: 93 + +{"id":5,"title":"trace me","body":"in flight","created_at":"2026-06-16T19:24:37.036818895Z"} + +22:24:37.042161 IP 127.0.0.1.54120 > 127.0.0.1.8080: Flags [.], ack 207, win 511, options [nop,nop,TS val 516986610 ecr 516986610], length 0 +E..4..@.@............h...............(..... +........ +22:24:37.042277 IP 127.0.0.1.54120 > 127.0.0.1.8080: Flags [F.], seq 176, ack 207, win 512, options [nop,nop,TS val 516986610 ecr 516986610], length 0 +E..4..@.@............h...............(..... +........ +22:24:37.042322 IP 127.0.0.1.8080 > 127.0.0.1.54120: Flags [F.], seq 207, ack 177, win 512, options [nop,nop,TS val 516986610 ecr 516986610], length 0 +E..4..@.@.. ...........h.............(..... +........ +22:24:37.042332 IP 127.0.0.1.54120 > 127.0.0.1.8080: Flags [.], ack 208, win 512, options [nop,nop,TS val 516986610 ecr 516986610], length 0 +E..4..@.@............h........./.....(..... +........ diff --git a/submissions/src/lab04/mtr_localhost.txt b/submissions/src/lab04/mtr_localhost.txt new file mode 100644 index 000000000..50b21cffb --- /dev/null +++ b/submissions/src/lab04/mtr_localhost.txt @@ -0,0 +1,3 @@ +Start: 2026-06-16T22:28:13+0300 +HOST: PCBUINIYYARIK Loss% Snt Last Avg Best Wrst StDev + 1.|-- localhost 0.0% 5 0.1 0.1 0.0 0.1 0.0 diff --git a/submissions/src/lab04/openssl_s_client.txt b/submissions/src/lab04/openssl_s_client.txt new file mode 100644 index 000000000..22222805d --- /dev/null +++ b/submissions/src/lab04/openssl_s_client.txt @@ -0,0 +1,93 @@ +Connecting to 127.0.0.1 +depth=1 CN=Caddy Local Authority - ECC Intermediate +verify error:num=20:unable to get local issuer certificate +verify return:1 +depth=0 +verify return:1 +CONNECTED(00000003) +--- +Certificate chain + 0 s: + i:CN=Caddy Local Authority - ECC Intermediate + a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA256 + v:NotBefore: Jun 16 19:37:26 2026 GMT; NotAfter: Jun 17 07:37:26 2026 GMT +-----BEGIN CERTIFICATE----- +MIIBvjCCAWSgAwIBAgIRALH4vhe0eqnLkA6Hk7dg8lgwCgYIKoZIzj0EAwIwMzEx +MC8GA1UEAxMoQ2FkZHkgTG9jYWwgQXV0aG9yaXR5IC0gRUNDIEludGVybWVkaWF0 +ZTAeFw0yNjA2MTYxOTM3MjZaFw0yNjA2MTcwNzM3MjZaMAAwWTATBgcqhkjOPQIB +BggqhkjOPQMBBwNCAARAgdXBlU0MTnoV2uRy8JiPxqfo1IbV5EC631ZvZZuOFPbk +UgpMJkfBUZ4nxEGE7o/9DZ9R7qhCTKZ0wNgqPJXTo4GLMIGIMA4GA1UdDwEB/wQE +AwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFDCq +sGs6rzJOWYFBzVTOa2XQTDNfMB8GA1UdIwQYMBaAFEMDSAZs+cqGRWzwooZmPGNg +niV8MBcGA1UdEQEB/wQNMAuCCWxvY2FsaG9zdDAKBggqhkjOPQQDAgNIADBFAiAn +/VhnHFH8ehDa20LBRMug2Zsjh42mG+iE8Vma3CJy0AIhAJ+iUO6Hz5sZwD7RegCi +NdKQ6Bf1LcZPVFtQGRWA+htw +-----END CERTIFICATE----- + 1 s:CN=Caddy Local Authority - ECC Intermediate + i:CN=Caddy Local Authority - 2026 ECC Root + a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA256 + v:NotBefore: Jun 16 19:37:26 2026 GMT; NotAfter: Jun 23 19:37:26 2026 GMT +-----BEGIN CERTIFICATE----- +MIIBxjCCAW2gAwIBAgIQZ+78QN4ndVjlQOS3okWK3TAKBggqhkjOPQQDAjAwMS4w +LAYDVQQDEyVDYWRkeSBMb2NhbCBBdXRob3JpdHkgLSAyMDI2IEVDQyBSb290MB4X +DTI2MDYxNjE5MzcyNloXDTI2MDYyMzE5MzcyNlowMzExMC8GA1UEAxMoQ2FkZHkg +TG9jYWwgQXV0aG9yaXR5IC0gRUNDIEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEG +CCqGSM49AwEHA0IABCA6RY3RG3MQz7mnw9pMeK4tIRO768jbRMCxkgntVqpwGBNo +MWFfSQGoa0CqmOtVoM/jnDhAiXXljPCdMPE/d6+jZjBkMA4GA1UdDwEB/wQEAwIB +BjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRDA0gGbPnKhkVs8KKGZjxj +YJ4lfDAfBgNVHSMEGDAWgBQxKYKMT8X/lq3AdqwN/h5nl5D9BzAKBggqhkjOPQQD +AgNHADBEAiBCWgFcTUznhz+iuOLOxcoMConQYyHhrJMQZnQ14ynLewIgMdOIIomP +irdsJSnAog5tV5hagzg7XlXv4GLZlK2CQoQ= +-----END CERTIFICATE----- +--- +Server certificate +subject= +issuer=CN=Caddy Local Authority - ECC Intermediate +--- +No client certificate CA names sent +Peer signing digest: SHA256 +Peer signature type: ecdsa_secp256r1_sha256 +Negotiated TLS1.3 group: X25519MLKEM768 +--- +SSL handshake has read 2359 bytes and written 1606 bytes +Verification error: unable to get local issuer certificate +--- +New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256 +Protocol: TLSv1.3 +Server public key is 256 bit +This TLS version forbids renegotiation. +Compression: NONE +Expansion: NONE +No ALPN negotiated +Early data was not sent +Verify return code: 20 (unable to get local issuer certificate) +--- +DONE +--- +Post-Handshake New Session Ticket arrived: +SSL-Session: + Protocol : TLSv1.3 + Cipher : TLS_AES_128_GCM_SHA256 + Session-ID: D45CDBD59AF0082EF9F2AC0E7F35E8E46C45792F07067CC33F4CB59E20399BB6 + Session-ID-ctx: + Resumption PSK: EC494A90093B4866CCEC141532FE40453E7956FB81BB3B3D068469A47A72F7CC + PSK identity: None + PSK identity hint: None + SRP username: None + TLS session ticket lifetime hint: 604800 (seconds) + TLS session ticket: + 0000 - 0a 35 87 ea 90 3a 65 72-4c 8e ae 1d 03 f5 63 56 .5...:erL.....cV + 0010 - 67 70 6e af 1d 23 2d 44-c6 81 69 41 dc 92 4f 2c gpn..#-D..iA..O, + 0020 - a9 36 c0 c1 d5 a8 78 b6-b2 c1 7a d1 d3 fc 44 27 .6....x...z...D' + 0030 - 04 c0 69 f7 92 6a 26 cc-82 eb c5 a0 14 56 6f ce ..i..j&......Vo. + 0040 - 16 07 7b 44 9c ed 3e 2f-41 8b ea 02 06 58 06 b2 ..{D..>/A....X.. + 0050 - 09 e9 ea 55 66 b9 d2 af-82 6b d0 2a ab 73 b4 6a ...Uf....k.*.s.j + 0060 - a6 6a 61 04 fc 83 a4 e4-78 .ja.....x + + Start Time: 1781640672 + Timeout : 7200 (sec) + Verify return code: 20 (unable to get local issuer certificate) + Extended master secret: no + Max Early Data: 0 +--- +read R BLOCK diff --git a/submissions/src/lab04/ps_go_run.txt b/submissions/src/lab04/ps_go_run.txt new file mode 100644 index 000000000..706a8e872 --- /dev/null +++ b/submissions/src/lab04/ps_go_run.txt @@ -0,0 +1 @@ +sokosla+ 14504 491 0 22:29 pts/0 00:00:00 go run . diff --git a/submissions/src/lab04/qn-broken.log b/submissions/src/lab04/qn-broken.log new file mode 100644 index 000000000..b02eaf5bf --- /dev/null +++ b/submissions/src/lab04/qn-broken.log @@ -0,0 +1,3 @@ +2026/06/16 22:29:54 quicknotes listening on :8080 (notes loaded: 5) +2026/06/16 22:29:54 listen: listen tcp :8080: bind: address already in use +exit status 1 diff --git a/submissions/src/lab04/repair_health.txt b/submissions/src/lab04/repair_health.txt new file mode 100644 index 000000000..73a139d9a --- /dev/null +++ b/submissions/src/lab04/repair_health.txt @@ -0,0 +1 @@ +{"notes":5,"status":"ok"} diff --git a/submissions/src/lab04/ss_8080.txt b/submissions/src/lab04/ss_8080.txt new file mode 100644 index 000000000..9cc9e50c8 --- /dev/null +++ b/submissions/src/lab04/ss_8080.txt @@ -0,0 +1 @@ +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=4001,fd=3)) diff --git a/submissions/src/lab05/curl_guest_health.txt b/submissions/src/lab05/curl_guest_health.txt new file mode 100644 index 000000000..2090ae5e6 Binary files /dev/null and b/submissions/src/lab05/curl_guest_health.txt differ diff --git a/submissions/src/lab05/curl_host_after_restore.txt b/submissions/src/lab05/curl_host_after_restore.txt new file mode 100644 index 000000000..2090ae5e6 Binary files /dev/null and b/submissions/src/lab05/curl_host_after_restore.txt differ diff --git a/submissions/src/lab05/curl_host_health.txt b/submissions/src/lab05/curl_host_health.txt new file mode 100644 index 000000000..2090ae5e6 Binary files /dev/null and b/submissions/src/lab05/curl_host_health.txt differ diff --git a/submissions/src/lab05/docker_health.txt b/submissions/src/lab05/docker_health.txt new file mode 100644 index 000000000..2090ae5e6 Binary files /dev/null and b/submissions/src/lab05/docker_health.txt differ diff --git a/submissions/src/lab05/docker_image_size.txt b/submissions/src/lab05/docker_image_size.txt new file mode 100644 index 000000000..f4f18dfb0 Binary files /dev/null and b/submissions/src/lab05/docker_image_size.txt differ diff --git a/submissions/src/lab05/docker_start_time.txt b/submissions/src/lab05/docker_start_time.txt new file mode 100644 index 000000000..33afbad4e Binary files /dev/null and b/submissions/src/lab05/docker_start_time.txt differ diff --git a/submissions/src/lab05/docker_stats.txt b/submissions/src/lab05/docker_stats.txt new file mode 100644 index 000000000..5db197778 Binary files /dev/null and b/submissions/src/lab05/docker_stats.txt differ diff --git a/submissions/src/lab05/docker_stop_time.txt b/submissions/src/lab05/docker_stop_time.txt new file mode 100644 index 000000000..81fc81ce2 Binary files /dev/null and b/submissions/src/lab05/docker_stop_time.txt differ diff --git a/submissions/src/lab05/docker_top.txt b/submissions/src/lab05/docker_top.txt new file mode 100644 index 000000000..49895e84c Binary files /dev/null and b/submissions/src/lab05/docker_top.txt differ diff --git a/submissions/src/lab05/go_version.txt b/submissions/src/lab05/go_version.txt new file mode 100644 index 000000000..6fa7674d5 Binary files /dev/null and b/submissions/src/lab05/go_version.txt differ diff --git a/submissions/src/lab05/go_version_broken.txt b/submissions/src/lab05/go_version_broken.txt new file mode 100644 index 000000000..48ce76026 Binary files /dev/null and b/submissions/src/lab05/go_version_broken.txt differ diff --git a/submissions/src/lab05/go_version_restored.txt b/submissions/src/lab05/go_version_restored.txt new file mode 100644 index 000000000..6fa7674d5 Binary files /dev/null and b/submissions/src/lab05/go_version_restored.txt differ diff --git a/submissions/src/lab05/snapshot_restore.txt b/submissions/src/lab05/snapshot_restore.txt new file mode 100644 index 000000000..a7873694a Binary files /dev/null and b/submissions/src/lab05/snapshot_restore.txt differ diff --git a/submissions/src/lab05/snapshot_restore_time.txt b/submissions/src/lab05/snapshot_restore_time.txt new file mode 100644 index 000000000..bfd51f2ed Binary files /dev/null and b/submissions/src/lab05/snapshot_restore_time.txt differ diff --git a/submissions/src/lab05/snapshot_save.txt b/submissions/src/lab05/snapshot_save.txt new file mode 100644 index 000000000..3a66e459c Binary files /dev/null and b/submissions/src/lab05/snapshot_save.txt differ diff --git a/submissions/src/lab05/vagrant_up.txt b/submissions/src/lab05/vagrant_up.txt new file mode 100644 index 000000000..a7d3f7859 Binary files /dev/null and b/submissions/src/lab05/vagrant_up.txt differ diff --git a/submissions/src/lab05/vm_boot.txt b/submissions/src/lab05/vm_boot.txt new file mode 100644 index 000000000..6d613a38b Binary files /dev/null and b/submissions/src/lab05/vm_boot.txt differ diff --git a/submissions/src/lab05/vm_boot_time.txt b/submissions/src/lab05/vm_boot_time.txt new file mode 100644 index 000000000..eeb75c3d8 Binary files /dev/null and b/submissions/src/lab05/vm_boot_time.txt differ diff --git a/submissions/src/lab05/vm_free_h.txt b/submissions/src/lab05/vm_free_h.txt new file mode 100644 index 000000000..e7b427e49 Binary files /dev/null and b/submissions/src/lab05/vm_free_h.txt differ diff --git a/submissions/src/lab05/vm_halt.txt b/submissions/src/lab05/vm_halt.txt new file mode 100644 index 000000000..9c0edaabf Binary files /dev/null and b/submissions/src/lab05/vm_halt.txt differ diff --git a/submissions/src/lab05/vm_halt_time.txt b/submissions/src/lab05/vm_halt_time.txt new file mode 100644 index 000000000..22f6ea607 Binary files /dev/null and b/submissions/src/lab05/vm_halt_time.txt differ diff --git a/submissions/src/lab05/vm_process_count.txt b/submissions/src/lab05/vm_process_count.txt new file mode 100644 index 000000000..b8fed3616 Binary files /dev/null and b/submissions/src/lab05/vm_process_count.txt differ diff --git a/submissions/src/lab06/compose_down.txt b/submissions/src/lab06/compose_down.txt new file mode 100644 index 000000000..2ccb59ca4 --- /dev/null +++ b/submissions/src/lab06/compose_down.txt @@ -0,0 +1,14 @@ + Container devops-intro-quicknotes-health-1 Stopping + Container devops-intro-quicknotes-health-1 Stopped + Container devops-intro-quicknotes-health-1 Removing + Container devops-intro-quicknotes-health-1 Removed + Container devops-intro-quicknotes-1 Stopping + Container devops-intro-quicknotes-1 Stopped + Container devops-intro-quicknotes-1 Removing + Container devops-intro-quicknotes-1 Removed + Container devops-intro-quicknotes-init-1 Stopping + Container devops-intro-quicknotes-init-1 Stopped + Container devops-intro-quicknotes-init-1 Removing + Container devops-intro-quicknotes-init-1 Removed + Network devops-intro_default Removing + Network devops-intro_default Removed diff --git a/submissions/src/lab06/compose_down_v.txt b/submissions/src/lab06/compose_down_v.txt new file mode 100644 index 000000000..553c814a7 --- /dev/null +++ b/submissions/src/lab06/compose_down_v.txt @@ -0,0 +1,16 @@ + Container devops-intro-quicknotes-health-1 Stopping + Container devops-intro-quicknotes-health-1 Stopped + Container devops-intro-quicknotes-health-1 Removing + Container devops-intro-quicknotes-health-1 Removed + Container devops-intro-quicknotes-1 Stopping + Container devops-intro-quicknotes-1 Stopped + Container devops-intro-quicknotes-1 Removing + Container devops-intro-quicknotes-1 Removed + Container devops-intro-quicknotes-init-1 Stopping + Container devops-intro-quicknotes-init-1 Stopped + Container devops-intro-quicknotes-init-1 Removing + Container devops-intro-quicknotes-init-1 Removed + Volume devops-intro_quicknotes-data Removing + Network devops-intro_default Removing + Volume devops-intro_quicknotes-data Removed + Network devops-intro_default Removed diff --git a/submissions/src/lab06/compose_health.txt b/submissions/src/lab06/compose_health.txt new file mode 100644 index 000000000..162bc5752 Binary files /dev/null and b/submissions/src/lab06/compose_health.txt differ diff --git a/submissions/src/lab06/compose_ps.txt b/submissions/src/lab06/compose_ps.txt new file mode 100644 index 000000000..ee8214397 Binary files /dev/null and b/submissions/src/lab06/compose_ps.txt differ diff --git a/submissions/src/lab06/compose_up.txt b/submissions/src/lab06/compose_up.txt new file mode 100644 index 000000000..e16abd130 --- /dev/null +++ b/submissions/src/lab06/compose_up.txt @@ -0,0 +1,100 @@ + Image quicknotes:lab6 Building +#1 [internal] load local bake definitions +#1 reading from stdin 579B 0.0s done +#1 DONE 0.0s + +#2 [internal] load build definition from Dockerfile +#2 DONE 0.0s + +#2 [internal] load build definition from Dockerfile +#2 transferring dockerfile: 511B done +#2 DONE 0.0s + +#3 [auth] docker/dockerfile:pull token for registry-1.docker.io +#3 DONE 0.0s + +#4 resolve image config for docker-image://docker.io/docker/dockerfile:1 +#4 DONE 1.1s + +#5 docker-image://docker.io/docker/dockerfile:1@sha256:87999aa3d42bdc6bea60565083ee17e86d1f3339802f543c0d03998580f9cb89 +#5 resolve docker.io/docker/dockerfile:1@sha256:87999aa3d42bdc6bea60565083ee17e86d1f3339802f543c0d03998580f9cb89 0.0s done +#5 CACHED + +#6 [auth] library/golang:pull token for registry-1.docker.io +#6 DONE 0.0s + +#7 [internal] load metadata for docker.io/library/golang:1.24-alpine +#7 ... + +#8 [internal] load metadata for gcr.io/distroless/static-debian12:nonroot +#8 DONE 0.4s + +#7 [internal] load metadata for docker.io/library/golang:1.24-alpine +#7 DONE 0.7s + +#9 [internal] load .dockerignore +#9 transferring context: 2B done +#9 DONE 0.0s + +#10 [builder 1/6] FROM docker.io/library/golang:1.24-alpine@sha256:8bee1901f1e530bfb4a7850aa7a479d17ae3a18beb6e09064ed54cfd245b7191 +#10 resolve docker.io/library/golang:1.24-alpine@sha256:8bee1901f1e530bfb4a7850aa7a479d17ae3a18beb6e09064ed54cfd245b7191 0.0s done +#10 DONE 0.0s + +#11 [stage-1 1/4] FROM gcr.io/distroless/static-debian12:nonroot@sha256:d093aa3e30dbadd3efe1310db061a14da60299baff8450a17fe0ccc514a16639 +#11 resolve gcr.io/distroless/static-debian12:nonroot@sha256:d093aa3e30dbadd3efe1310db061a14da60299baff8450a17fe0ccc514a16639 0.0s done +#11 DONE 0.0s + +#12 [internal] load build context +#12 transferring context: 417B done +#12 DONE 0.0s + +#13 [builder 3/6] COPY go.mod ./ +#13 CACHED + +#14 [builder 6/6] RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o /out/quicknotes . +#14 CACHED + +#15 [builder 2/6] WORKDIR /src +#15 CACHED + +#16 [stage-1 2/4] COPY --from=builder /out/quicknotes /quicknotes +#16 CACHED + +#17 [builder 4/6] RUN go mod download +#17 CACHED + +#18 [builder 5/6] COPY . . +#18 CACHED + +#19 [stage-1 3/4] COPY --from=builder /src/seed.json /seed.json +#19 CACHED + +#20 exporting to image +#20 exporting layers done +#20 exporting manifest sha256:cc0a1b82c078e97f645cd869854ba4b15dbb1349f74f06147a2e0ad988855bad done +#20 exporting config sha256:fff27ec54e113fc4132f61479b64cf3fa9d2f3a2854967b440ed615df9a10a0c done +#20 exporting attestation manifest sha256:8d9fa6096956b9d474eef9967246b649b99d665bc628c9f162076b0c309c4370 0.0s done +#20 exporting manifest list sha256:1cef2abc5bb0e6086b9c575015315bce3be2502f1c2915292c3eea31bc551686 done +#20 naming to docker.io/library/quicknotes:lab6 done +#20 unpacking to docker.io/library/quicknotes:lab6 done +#20 DONE 0.1s + +#21 resolving provenance for metadata file +#21 DONE 0.0s + Image quicknotes:lab6 Built + Network devops-intro_default Creating + Network devops-intro_default Created + Container devops-intro-quicknotes-init-1 Creating + Container devops-intro-quicknotes-init-1 Created + Container devops-intro-quicknotes-1 Creating + Container devops-intro-quicknotes-1 Created + Container devops-intro-quicknotes-health-1 Creating + Container devops-intro-quicknotes-health-1 Created + Container devops-intro-quicknotes-init-1 Starting + Container devops-intro-quicknotes-init-1 Started + Container devops-intro-quicknotes-init-1 Waiting + Container devops-intro-quicknotes-init-1 Exited + Container devops-intro-quicknotes-1 Starting + Container devops-intro-quicknotes-1 Started + Container devops-intro-quicknotes-health-1 Starting + Container devops-intro-quicknotes-health-1 Started diff --git a/submissions/src/lab06/compose_up_after_down.txt b/submissions/src/lab06/compose_up_after_down.txt new file mode 100644 index 000000000..237c9cf0d --- /dev/null +++ b/submissions/src/lab06/compose_up_after_down.txt @@ -0,0 +1,16 @@ + Network devops-intro_default Creating + Network devops-intro_default Created + Container devops-intro-quicknotes-init-1 Creating + Container devops-intro-quicknotes-init-1 Created + Container devops-intro-quicknotes-1 Creating + Container devops-intro-quicknotes-1 Created + Container devops-intro-quicknotes-health-1 Creating + Container devops-intro-quicknotes-health-1 Created + Container devops-intro-quicknotes-init-1 Starting + Container devops-intro-quicknotes-init-1 Started + Container devops-intro-quicknotes-init-1 Waiting + Container devops-intro-quicknotes-init-1 Exited + Container devops-intro-quicknotes-1 Starting + Container devops-intro-quicknotes-1 Started + Container devops-intro-quicknotes-health-1 Starting + Container devops-intro-quicknotes-health-1 Started diff --git a/submissions/src/lab06/compose_up_after_down_v.txt b/submissions/src/lab06/compose_up_after_down_v.txt new file mode 100644 index 000000000..b3e0ce7fa --- /dev/null +++ b/submissions/src/lab06/compose_up_after_down_v.txt @@ -0,0 +1,18 @@ + Network devops-intro_default Creating + Network devops-intro_default Created + Volume devops-intro_quicknotes-data Creating + Volume devops-intro_quicknotes-data Created + Container devops-intro-quicknotes-init-1 Creating + Container devops-intro-quicknotes-init-1 Created + Container devops-intro-quicknotes-1 Creating + Container devops-intro-quicknotes-1 Created + Container devops-intro-quicknotes-health-1 Creating + Container devops-intro-quicknotes-health-1 Created + Container devops-intro-quicknotes-init-1 Starting + Container devops-intro-quicknotes-init-1 Started + Container devops-intro-quicknotes-init-1 Waiting + Container devops-intro-quicknotes-init-1 Exited + Container devops-intro-quicknotes-1 Starting + Container devops-intro-quicknotes-1 Started + Container devops-intro-quicknotes-health-1 Starting + Container devops-intro-quicknotes-health-1 Started diff --git a/submissions/src/lab06/container_id.txt b/submissions/src/lab06/container_id.txt new file mode 100644 index 000000000..eb44cb225 Binary files /dev/null and b/submissions/src/lab06/container_id.txt differ diff --git a/submissions/src/lab06/docker_build.txt b/submissions/src/lab06/docker_build.txt new file mode 100644 index 000000000..d9506584d Binary files /dev/null and b/submissions/src/lab06/docker_build.txt differ diff --git a/submissions/src/lab06/docker_images.txt b/submissions/src/lab06/docker_images.txt new file mode 100644 index 000000000..367672651 Binary files /dev/null and b/submissions/src/lab06/docker_images.txt differ diff --git a/submissions/src/lab06/docker_inspect_config.txt b/submissions/src/lab06/docker_inspect_config.txt new file mode 100644 index 000000000..2fd3ffe72 Binary files /dev/null and b/submissions/src/lab06/docker_inspect_config.txt differ diff --git a/submissions/src/lab06/docker_run_health.txt b/submissions/src/lab06/docker_run_health.txt new file mode 100644 index 000000000..2090ae5e6 Binary files /dev/null and b/submissions/src/lab06/docker_run_health.txt differ diff --git a/submissions/src/lab06/docker_run_id.txt b/submissions/src/lab06/docker_run_id.txt new file mode 100644 index 000000000..857eafdd6 Binary files /dev/null and b/submissions/src/lab06/docker_run_id.txt differ diff --git a/submissions/src/lab06/docker_run_rm.txt b/submissions/src/lab06/docker_run_rm.txt new file mode 100644 index 000000000..08635c5cd Binary files /dev/null and b/submissions/src/lab06/docker_run_rm.txt differ diff --git a/submissions/src/lab06/docker_run_stop.txt b/submissions/src/lab06/docker_run_stop.txt new file mode 100644 index 000000000..08635c5cd Binary files /dev/null and b/submissions/src/lab06/docker_run_stop.txt differ diff --git a/submissions/src/lab06/golang_base_image_size.txt b/submissions/src/lab06/golang_base_image_size.txt new file mode 100644 index 000000000..ac301793a Binary files /dev/null and b/submissions/src/lab06/golang_base_image_size.txt differ diff --git a/submissions/src/lab06/persistence_after_down_up.txt b/submissions/src/lab06/persistence_after_down_up.txt new file mode 100644 index 000000000..95e857131 Binary files /dev/null and b/submissions/src/lab06/persistence_after_down_up.txt differ diff --git a/submissions/src/lab06/persistence_after_down_v_up.txt b/submissions/src/lab06/persistence_after_down_v_up.txt new file mode 100644 index 000000000..36cb84122 Binary files /dev/null and b/submissions/src/lab06/persistence_after_down_v_up.txt differ diff --git a/submissions/src/lab06/persistence_before_down.txt b/submissions/src/lab06/persistence_before_down.txt new file mode 100644 index 000000000..32984a2d3 Binary files /dev/null and b/submissions/src/lab06/persistence_before_down.txt differ diff --git a/submissions/src/lab06/persistence_post.txt b/submissions/src/lab06/persistence_post.txt new file mode 100644 index 000000000..bd3f85508 Binary files /dev/null and b/submissions/src/lab06/persistence_post.txt differ diff --git a/submissions/src/lab06/quicknotes_image_size.txt b/submissions/src/lab06/quicknotes_image_size.txt new file mode 100644 index 000000000..2a2abcda2 Binary files /dev/null and b/submissions/src/lab06/quicknotes_image_size.txt differ diff --git a/submissions/src/lab06/trivy.txt b/submissions/src/lab06/trivy.txt new file mode 100644 index 000000000..dace3ea58 Binary files /dev/null and b/submissions/src/lab06/trivy.txt differ diff --git a/submissions/src/lab06/verify_cap_drop.txt b/submissions/src/lab06/verify_cap_drop.txt new file mode 100644 index 000000000..554726647 Binary files /dev/null and b/submissions/src/lab06/verify_cap_drop.txt differ diff --git a/submissions/src/lab06/verify_no_new_privileges.txt b/submissions/src/lab06/verify_no_new_privileges.txt new file mode 100644 index 000000000..53d3b5bba Binary files /dev/null and b/submissions/src/lab06/verify_no_new_privileges.txt differ diff --git a/submissions/src/lab06/verify_no_shell.txt b/submissions/src/lab06/verify_no_shell.txt new file mode 100644 index 000000000..939621a86 Binary files /dev/null and b/submissions/src/lab06/verify_no_shell.txt differ diff --git a/submissions/src/lab06/verify_readonly_rootfs.txt b/submissions/src/lab06/verify_readonly_rootfs.txt new file mode 100644 index 000000000..abdce89f3 Binary files /dev/null and b/submissions/src/lab06/verify_readonly_rootfs.txt differ diff --git a/submissions/src/lab06/verify_user_nonroot.txt b/submissions/src/lab06/verify_user_nonroot.txt new file mode 100644 index 000000000..e7438ee52 Binary files /dev/null and b/submissions/src/lab06/verify_user_nonroot.txt differ diff --git a/submissions/src/screenshots/lab01/bonus_task_failed_push.png b/submissions/src/screenshots/lab01/bonus_task_failed_push.png new file mode 100644 index 000000000..f5dca9d8b Binary files /dev/null and b/submissions/src/screenshots/lab01/bonus_task_failed_push.png differ diff --git a/submissions/src/screenshots/lab01/bonus_task_rulesets.png b/submissions/src/screenshots/lab01/bonus_task_rulesets.png new file mode 100644 index 000000000..ddc2d2f33 Binary files /dev/null and b/submissions/src/screenshots/lab01/bonus_task_rulesets.png differ diff --git a/submissions/src/screenshots/lab01/task1_verified_commit_screenshot.png b/submissions/src/screenshots/lab01/task1_verified_commit_screenshot.png new file mode 100644 index 000000000..2bfaf1bc6 Binary files /dev/null and b/submissions/src/screenshots/lab01/task1_verified_commit_screenshot.png differ diff --git a/submissions/src/screenshots/lab01/task2_pr_template.png b/submissions/src/screenshots/lab01/task2_pr_template.png new file mode 100644 index 000000000..3375914bc Binary files /dev/null and b/submissions/src/screenshots/lab01/task2_pr_template.png differ diff --git a/submissions/src/screenshots/lab03/branch_protection.png b/submissions/src/screenshots/lab03/branch_protection.png new file mode 100644 index 000000000..ae730b5d3 Binary files /dev/null and b/submissions/src/screenshots/lab03/branch_protection.png differ diff --git a/submissions/src/screenshots/lab03/failed_ci.png b/submissions/src/screenshots/lab03/failed_ci.png new file mode 100644 index 000000000..bd93868f2 Binary files /dev/null and b/submissions/src/screenshots/lab03/failed_ci.png differ diff --git a/submissions/src/screenshots/lab03/green_ci.png b/submissions/src/screenshots/lab03/green_ci.png new file mode 100644 index 000000000..60d1632a5 Binary files /dev/null and b/submissions/src/screenshots/lab03/green_ci.png differ diff --git a/submissions/src/screenshots/lab03/merge_blocked.png b/submissions/src/screenshots/lab03/merge_blocked.png new file mode 100644 index 000000000..116e52789 Binary files /dev/null and b/submissions/src/screenshots/lab03/merge_blocked.png differ diff --git a/submissions/src/screenshots/lab03/timing_baseline.png b/submissions/src/screenshots/lab03/timing_baseline.png new file mode 100644 index 000000000..3ff485eb2 Binary files /dev/null and b/submissions/src/screenshots/lab03/timing_baseline.png differ diff --git a/submissions/src/screenshots/lab03/timing_cache.png b/submissions/src/screenshots/lab03/timing_cache.png new file mode 100644 index 000000000..50c4fa5b6 Binary files /dev/null and b/submissions/src/screenshots/lab03/timing_cache.png differ diff --git a/submissions/src/screenshots/lab03/timing_matrix.png b/submissions/src/screenshots/lab03/timing_matrix.png new file mode 100644 index 000000000..f79a630cb Binary files /dev/null and b/submissions/src/screenshots/lab03/timing_matrix.png differ diff --git a/submissions/src/screenshots/lab04/certificate_chain.png b/submissions/src/screenshots/lab04/certificate_chain.png new file mode 100644 index 000000000..016141ae9 Binary files /dev/null and b/submissions/src/screenshots/lab04/certificate_chain.png differ diff --git a/submissions/src/screenshots/lab04/client_hello.png b/submissions/src/screenshots/lab04/client_hello.png new file mode 100644 index 000000000..c502fd73c Binary files /dev/null and b/submissions/src/screenshots/lab04/client_hello.png differ diff --git a/submissions/src/screenshots/lab04/server_hello.png b/submissions/src/screenshots/lab04/server_hello.png new file mode 100644 index 000000000..6d62c9e69 Binary files /dev/null and b/submissions/src/screenshots/lab04/server_hello.png differ