Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Goal
<!-- What does this PR accomplish? 1 sentence. -->

## Changes
-

## Testing
<!-- How did you verify it? -->

## Checklist
- [ ] Title is a clear sentence (≤ 70 chars)
- [ ] Commits are signed (git log --show-signature)
- [ ] submissions/labN.md updated
84 changes: 84 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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"
58 changes: 58 additions & 0 deletions Vagrantfile
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions app/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
47 changes: 47 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -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:
147 changes: 147 additions & 0 deletions submissions/lab1.md
Original file line number Diff line number Diff line change
@@ -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 <sokoslav1707@gmail.com>
Date: Tue Jun 9 19:21:06 2026 +0300

docs(lab1): start submission

Signed-off-by: Yaroslav Sokolov <sokoslav1707@gmail.com>
```

### 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.
Binary file added submissions/lab2.md
Binary file not shown.
Loading