diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..df6d3ab42 --- /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/images/lab4/client_hello.png b/images/lab4/client_hello.png new file mode 100644 index 000000000..94837f341 Binary files /dev/null and b/images/lab4/client_hello.png differ diff --git a/images/lab4/server_hello.png b/images/lab4/server_hello.png new file mode 100644 index 000000000..d874d6a80 Binary files /dev/null and b/images/lab4/server_hello.png differ diff --git a/lab4-tls-cert-chain.txt b/lab4-tls-cert-chain.txt new file mode 100644 index 000000000..8b974df42 --- /dev/null +++ b/lab4-tls-cert-chain.txt @@ -0,0 +1,91 @@ +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: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA256 + v:NotBefore: Jun 16 07:45:40 2026 GMT; NotAfter: Jun 16 19:45:40 2026 GMT +-----BEGIN CERTIFICATE----- +MIIBvTCCAWSgAwIBAgIRAJrlOvv1xXKHJs6IYvG2GDIwCgYIKoZIzj0EAwIwMzEx +MC8GA1UEAxMoQ2FkZHkgTG9jYWwgQXV0aG9yaXR5IC0gRUNDIEludGVybWVkaWF0 +ZTAeFw0yNjA2MTYwNzQ1NDBaFw0yNjA2MTYxOTQ1NDBaMAAwWTATBgcqhkjOPQIB +BggqhkjOPQMBBwNCAAR30QYXszqgpQoJisP6BJKazM3X0r9hFqfZ8PFYhTMkM6t9 +ORUSqQJSgVLaJIuzY0rBy5FIcD6/RiyPoyKokQvKo4GLMIGIMA4GA1UdDwEB/wQE +AwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFGqW +9Ip7pHJHYDS8o8+gdl7Kn2HfMB8GA1UdIwQYMBaAFLjiPlDYrvwjQBfpko8JzWfw +7AatMBcGA1UdEQEB/wQNMAuCCWxvY2FsaG9zdDAKBggqhkjOPQQDAgNHADBEAiAL +41liEFpLWDPX3K5wr67EL5NLiuiljN8jPC2c6IlRnAIgGJMmUDojXL/pyZ9V8y8/ +EaCzv5vH9Nq74AYPDJ9cX2c= +-----END CERTIFICATE----- + 1 s:CN = Caddy Local Authority - ECC Intermediate + i:CN = Caddy Local Authority - 2026 ECC Root + a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA256 + v:NotBefore: Jun 16 07:45:40 2026 GMT; NotAfter: Jun 23 07:45:40 2026 GMT +-----BEGIN CERTIFICATE----- +MIIBxzCCAW2gAwIBAgIQTpEU+/u8ZAawQawZScpyhDAKBggqhkjOPQQDAjAwMS4w +LAYDVQQDEyVDYWRkeSBMb2NhbCBBdXRob3JpdHkgLSAyMDI2IEVDQyBSb290MB4X +DTI2MDYxNjA3NDU0MFoXDTI2MDYyMzA3NDU0MFowMzExMC8GA1UEAxMoQ2FkZHkg +TG9jYWwgQXV0aG9yaXR5IC0gRUNDIEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEG +CCqGSM49AwEHA0IABMZ6lJkcHW3lYICoaAeQkST2SSpRGrAiAlwc4vNV4cyU03ID +UBAJ9TdBHLVear6Id6DAOYrbrJsuxgznhfs8wC2jZjBkMA4GA1UdDwEB/wQEAwIB +BjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBS44j5Q2K78I0AX6ZKPCc1n +8OwGrTAfBgNVHSMEGDAWgBQovihpmeas2KyZh1iplb9Sld9v7DAKBggqhkjOPQQD +AgNIADBFAiEAtKbDrU6XBFRBC3IqwWqup/ur4RbjJVQiE9ZUufTenhkCIHSVJkxx +nT2de8yXAU0LU9FMoA5Z2nkEFoaW5KZ3ajgP +-----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 +Server Temp Key: X25519, 253 bits +--- +SSL handshake has read 1268 bytes and written 375 bytes +Verification error: unable to get local issuer certificate +--- +New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256 +Server public key is 256 bit +Secure Renegotiation IS NOT supported +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: 60E6B3958ABE456185453D6FFBC7DE704E4368426A5A7D5205E139A2AA6EC877 + Session-ID-ctx: + Resumption PSK: C0AA652F3FAD09EFC7E7D37E42115A077A4302514ECAB4B90DB585829BA3A5DF + PSK identity: None + PSK identity hint: None + SRP username: None + TLS session ticket lifetime hint: 604800 (seconds) + TLS session ticket: + 0000 - a4 90 b0 cf 50 43 b5 d5-b9 58 76 37 09 5c a0 26 ....PC...Xv7.\.& + 0010 - 96 0a 07 dd 7d 25 40 58-bd c1 f5 bd 15 e5 3a f5 ....}%@X......:. + 0020 - 76 9a 90 11 7c 83 6a 9f-c5 4b 35 35 19 1c 23 48 v...|.j..K55..#H + 0030 - b9 d5 24 87 78 4c f2 15-a2 fd 22 51 28 ed ec 4b ..$.xL...."Q(..K + 0040 - 15 7b 57 d1 1f 4b 48 f5-fc d7 19 e3 2a 13 7d a0 .{W..KH.....*.}. + 0050 - cb 95 d5 a8 9f ef 54 2b-76 03 83 75 a1 da 81 f9 ......T+v..u.... + 0060 - c9 f9 90 ac c7 44 3c d5-c7 .....D<.. + + Start Time: 1781596498 + 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/lab4-tls.pcap b/lab4-tls.pcap new file mode 100644 index 000000000..375871c0d Binary files /dev/null and b/lab4-tls.pcap differ diff --git a/lab4-trace.pcap b/lab4-trace.pcap new file mode 100644 index 000000000..d5ae400a9 Binary files /dev/null and b/lab4-trace.pcap differ diff --git a/lab4-trace.txt b/lab4-trace.txt new file mode 100644 index 000000000..f527d24c2 --- /dev/null +++ b/lab4-trace.txt @@ -0,0 +1,124 @@ +10:07:22.001973 IP6 ::1.41078 > ::1.8080: Flags [S], seq 1569259689, win 65476, options [mss 65476,sackOK,TS val 384143260 ecr 0,nop,wscale 14], length 0 +`.m..(.@.................................v..]............0......... +............ +10:07:22.002000 IP6 ::1.8080 > ::1.41078: Flags [S.], seq 3570494351, ack 1569259690, win 65464, options [mss 65476,sackOK,TS val 384143260 ecr 384143260,nop,wscale 14], length 0 +`.Nz.(.@...................................v..k.]........0......... +............ +10:07:22.002013 IP6 ::1.41078 > ::1.8080: Flags [.], ack 1, win 4, options [nop,nop,TS val 384143260 ecr 384143260], length 0 +`.m.. .@.................................v..].....k......(..... +........ +10:07:22.002070 IP6 ::1.41078 > ::1.8080: Flags [P.], seq 1:175, ack 1, win 4, options [nop,nop,TS val 384143260 ecr 384143260], length 174: HTTP: POST /notes HTTP/1.1 +`.m....@.................................v..].....k............ +........POST /notes HTTP/1.1 +Host: localhost:8080 +User-Agent: curl/8.5.0 +Accept: */* +Content-Type: application/json +Content-Length: 39 + +{"title":"trace me","body":"in flight"} +10:07:22.002074 IP6 ::1.8080 > ::1.41078: Flags [.], ack 175, win 4, options [nop,nop,TS val 384143260 ecr 384143260], length 0 +`.Nz. .@...................................v..k.]..X.....(..... +........ +10:07:22.003042 IP6 ::1.8080 > ::1.41078: Flags [P.], seq 1:207, ack 175, win 4, options [nop,nop,TS val 384143261 ecr 384143260], length 206: HTTP: HTTP/1.1 201 Created +`.Nz...@...................................v..k.]..X........... +........HTTP/1.1 201 Created +Content-Type: application/json +Date: Tue, 16 Jun 2026 07:07:22 GMT +Content-Length: 93 + +{"id":7,"title":"trace me","body":"in flight","created_at":"2026-06-16T07:07:22.002480556Z"} + +10:07:22.003080 IP6 ::1.41078 > ::1.8080: Flags [.], ack 207, win 4, options [nop,nop,TS val 384143261 ecr 384143261], length 0 +`.m.. .@.................................v..]..X..l^.....(..... +........ +10:07:22.003282 IP6 ::1.41078 > ::1.8080: Flags [F.], seq 175, ack 207, win 4, options [nop,nop,TS val 384143261 ecr 384143261], length 0 +`.m.. .@.................................v..]..X..l^.....(..... +........ +10:07:22.003353 IP6 ::1.8080 > ::1.41078: Flags [F.], seq 207, ack 176, win 4, options [nop,nop,TS val 384143262 ecr 384143261], length 0 +`.Nz. .@...................................v..l^]..Y.....(..... +........ +10:07:22.003378 IP6 ::1.41078 > ::1.8080: Flags [.], ack 208, win 4, options [nop,nop,TS val 384143262 ecr 384143262], length 0 +`.m.. .@.................................v..]..Y..l_.....(..... +........ +10:12:01.557276 IP6 ::1.35994 > ::1.8080: Flags [S], seq 1629583095, win 65476, options [mss 65476,sackOK,TS val 384422815 ecr 0,nop,wscale 14], length 0 +`....(.@....................................a!v..........0......... +............ +10:12:01.557299 IP6 ::1.8080 > ::1.35994: Flags [S.], seq 1230713277, ack 1629583096, win 65464, options [mss 65476,sackOK,TS val 384422816 ecr 384422815,nop,wscale 14], length 0 +`..\.(.@....................................I[1.a!v......0......... +............ +10:12:01.557311 IP6 ::1.35994 > ::1.8080: Flags [.], ack 1, win 4, options [nop,nop,TS val 384422816 ecr 384422816], length 0 +`.... .@....................................a!v.I[1......(..... +........ +10:12:01.557359 IP6 ::1.35994 > ::1.8080: Flags [P.], seq 1:84, ack 1, win 4, options [nop,nop,TS val 384422816 ecr 384422816], length 83: HTTP: GET /health HTTP/1.1 +`....s.@....................................a!v.I[1......{..... +........GET /health HTTP/1.1 +Host: localhost:8080 +User-Agent: curl/8.5.0 +Accept: */* + + +10:12:01.557363 IP6 ::1.8080 > ::1.35994: Flags [.], ack 84, win 4, options [nop,nop,TS val 384422816 ecr 384422816], length 0 +`..\. .@....................................I[1.a!wK.....(..... +........ +10:12:01.557560 IP6 ::1.8080 > ::1.35994: Flags [P.], seq 1:135, ack 84, win 4, options [nop,nop,TS val 384422816 ecr 384422816], length 134: HTTP: HTTP/1.1 200 OK +`..\...@....................................I[1.a!wK........... +........HTTP/1.1 200 OK +Content-Type: application/json +Date: Tue, 16 Jun 2026 07:12:01 GMT +Content-Length: 26 + +{"notes":7,"status":"ok"} + +10:12:01.557577 IP6 ::1.35994 > ::1.8080: Flags [.], ack 135, win 4, options [nop,nop,TS val 384422816 ecr 384422816], length 0 +`.... .@....................................a!wKI[2D.....(..... +........ +10:12:01.557639 IP6 ::1.35994 > ::1.8080: Flags [F.], seq 84, ack 135, win 4, options [nop,nop,TS val 384422816 ecr 384422816], length 0 +`.... .@....................................a!wKI[2D.....(..... +........ +10:12:01.557671 IP6 ::1.8080 > ::1.35994: Flags [F.], seq 135, ack 85, win 4, options [nop,nop,TS val 384422816 ecr 384422816], length 0 +`..\. .@....................................I[2Da!wL.....(..... +........ +10:12:01.557688 IP6 ::1.35994 > ::1.8080: Flags [.], ack 136, win 4, options [nop,nop,TS val 384422816 ecr 384422816], length 0 +`.... .@....................................a!wLI[2E.....(..... +........ +10:13:08.030007 IP6 ::1.43750 > ::1.8080: Flags [S], seq 3364631319, win 65476, options [mss 65476,sackOK,TS val 384489288 ecr 0,nop,wscale 14], length 0 +`..O.(.@......................................3..........0......... +...H........ +10:13:08.030026 IP6 ::1.8080 > ::1.43750: Flags [S.], seq 3430200936, ack 3364631320, win 65464, options [mss 65476,sackOK,TS val 384489288 ecr 384489288,nop,wscale 14], length 0 +`.@3.(.@.....................................t.h..3......0......... +...H...H.... +10:13:08.030039 IP6 ::1.43750 > ::1.8080: Flags [.], ack 1, win 4, options [nop,nop,TS val 384489288 ecr 384489288], length 0 +`..O. .@......................................3..t.i.....(..... +...H...H +10:13:08.030094 IP6 ::1.43750 > ::1.8080: Flags [P.], seq 1:175, ack 1, win 4, options [nop,nop,TS val 384489288 ecr 384489288], length 174: HTTP: POST /notes HTTP/1.1 +`..O...@......................................3..t.i........... +...H...HPOST /notes HTTP/1.1 +Host: localhost:8080 +User-Agent: curl/8.5.0 +Accept: */* +Content-Type: application/json +Content-Length: 39 + +{"title":"trace me","body":"in flight"} +10:13:08.030099 IP6 ::1.8080 > ::1.43750: Flags [.], ack 175, win 4, options [nop,nop,TS val 384489288 ecr 384489288], length 0 +`.@3. .@.....................................t.i..3......(..... +...H...H +10:13:08.031214 IP6 ::1.8080 > ::1.43750: Flags [P.], seq 1:207, ack 175, win 4, options [nop,nop,TS val 384489289 ecr 384489288], length 206: HTTP: HTTP/1.1 201 Created +`.@3...@.....................................t.i..3............ +...I...HHTTP/1.1 201 Created +Content-Type: application/json +Date: Tue, 16 Jun 2026 07:13:08 GMT +Content-Length: 93 + +{"id":8,"title":"trace me","body":"in flight","created_at":"2026-06-16T07:13:08.030657439Z"} + +10:13:08.031236 IP6 ::1.43750 > ::1.8080: Flags [.], ack 207, win 4, options [nop,nop,TS val 384489289 ecr 384489289], length 0 +`..O. .@......................................3..t.7.....(..... +...I...I +10:13:08.031402 IP6 ::1.43750 > ::1.8080: Flags [F.], seq 175, ack 207, win 4, options [nop,nop,TS val 384489290 ecr 384489289], length 0 +`..O. .@......................................3..t.7.....(..... +...J...I +10:13:08.031497 IP6 ::1.8080 > ::1.43750: Flags [F.], seq 207, ack 176, win 4, options [nop,nop,TS val 384489290 ecr 384489290], length 0 +`.@3. .@.....................................t.7..3......(..... +...J...J diff --git a/submissions/lab4.md b/submissions/lab4.md new file mode 100644 index 000000000..e2b01bbb2 --- /dev/null +++ b/submissions/lab4.md @@ -0,0 +1,523 @@ +# Lab 4 + +## Task 1 + +### Decode the capture + +The packet capture in `lab4-trace.txt` shows a complete localhost request to QuickNotes over IPv6 loopback (`::1`) on port `8080`. The relevant request starts at `10:07:22.001973`. + +#### TCP three-way handshake + +```text +10:07:22.001973 IP6 ::1.41078 > ::1.8080: Flags [S], seq 1569259689, ... length 0 +10:07:22.002000 IP6 ::1.8080 > ::1.41078: Flags [S.], seq 3570494351, ack 1569259690, ... length 0 +10:07:22.002013 IP6 ::1.41078 > ::1.8080: Flags [.], ack 1, ... length 0 +``` + +Annotation: + +- The first packet is the client SYN from ephemeral port `41078` to QuickNotes on port `8080`. +- The second packet is the server SYN/ACK from port `8080` back to the client. +- The third packet is the client ACK, completing the TCP three-way handshake. + +#### HTTP request + +```text +10:07:22.002070 IP6 ::1.41078 > ::1.8080: Flags [P.], seq 1:175, ack 1, ... length 174: HTTP: POST /notes HTTP/1.1 +POST /notes HTTP/1.1 +Host: localhost:8080 +User-Agent: curl/8.5.0 +Accept: */* +Content-Type: application/json +Content-Length: 39 + +{"title":"trace me","body":"in flight"} +``` + +Annotation: + +- The HTTP request line is `POST /notes HTTP/1.1`. +- The request is sent to `localhost:8080`. +- The body is JSON with title `trace me` and body `in flight`. + +#### HTTP response + +```text +10:07:22.003042 IP6 ::1.8080 > ::1.41078: Flags [P.], seq 1:207, ack 175, ... length 206: HTTP: HTTP/1.1 201 Created +HTTP/1.1 201 Created +Content-Type: application/json +Date: Tue, 16 Jun 2026 07:07:22 GMT +Content-Length: 93 + +{"id":7,"title":"trace me","body":"in flight","created_at":"2026-06-16T07:07:22.002480556Z"} +``` + +Annotation: + +- QuickNotes returned `HTTP/1.1 201 Created`, which means the note was created successfully. +- The response body includes the created note with `id`, `title`, `body`, and `created_at`. + +#### Connection close + +```text +10:07:22.003282 IP6 ::1.41078 > ::1.8080: Flags [F.], seq 175, ack 207, ... length 0 +10:07:22.003353 IP6 ::1.8080 > ::1.41078: Flags [F.], seq 207, ack 176, ... length 0 +10:07:22.003378 IP6 ::1.41078 > ::1.8080: Flags [.], ack 208, ... length 0 +``` + +Annotation: + +- The client starts the connection shutdown with `FIN`. +- The server replies with its own `FIN`. +- The final client ACK completes the TCP connection close. + +### Five debugging commands + +#### 1. What's listening? + +Command: + +```bash +ss -tlnp | grep :8080 +``` + +Output: + +```text +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=2898953,fd=3)) +``` + +Decision: + +QuickNotes is running and listening on TCP port `8080`. The process name is `quicknotes`, with PID `2898953`. + +#### 2. Routes from the host + +Command: + +```bash +ip route show +``` + +Output: + +```text +default via 10.90.120.1 dev eno1 proto dhcp src 10.90.120.190 metric 100 +default via 10.91.48.1 dev wlp179s0 proto dhcp src 10.91.48.93 metric 600 +10.90.120.0/22 dev eno1 proto kernel scope link src 10.90.120.190 metric 100 +10.91.48.0/20 dev wlp179s0 proto kernel scope link src 10.91.48.93 metric 600 +172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown +172.18.0.0/16 dev br-24184173a529 proto kernel scope link src 172.18.0.1 linkdown +172.19.0.0/16 dev br-92a7596f9709 proto kernel scope link src 172.19.0.1 linkdown +172.20.0.0/16 dev br-1fb3ccd5b653 proto kernel scope link src 172.20.0.1 linkdown +192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1 linkdown +``` + +Decision: + +The host has active default routes through `eno1` and `wlp179s0`. Docker and virtualization bridge networks are present but currently marked `linkdown`, so they are not relevant to the local QuickNotes request on `localhost`. + +#### 3. Local reachability + +Command: + +```bash +mtr -rwc 5 localhost +``` + +Output: + +```text +Start: 2026-06-16T10:17:52+0300 +HOST: pc Loss% Snt Last Avg Best Wrst StDev + 1.|-- localhost 0.0% 5 0.1 0.1 0.1 0.1 0.0 +``` + +Decision: + +`localhost` is reachable with `0.0%` packet loss and about `0.1 ms` latency. The loopback path is healthy. + +#### 4. DNS check + +Command: + +```bash +dig +short example.com @1.1.1.1 +``` + +Output: + +```text +8.6.112.0 +8.47.69.0 +``` + +Decision: + +DNS resolution through Cloudflare DNS (`1.1.1.1`) works because the query returned IP addresses for `example.com`. + +#### 5. User service logs + +Command: + +```bash +journalctl --user -u quicknotes -n 20 || true +``` + +Output: + +```text +-- No entries -- +``` + +Decision: + +There are no `systemd --user` journal entries for `quicknotes`. This is expected because QuickNotes was run manually for this task, not as a user-level systemd service. + +### 502 debugging reflection + +If QuickNotes returned `502 Bad Gateway`, I would first check the component in front of QuickNotes, such as a reverse proxy, load balancer, or gateway, because a 502 usually means the gateway could not get a valid response from the upstream service. I would verify that QuickNotes is actually running, confirm it is listening on the expected address and port with `ss -tlnp`, and test the upstream directly with `curl http://localhost:8080/health`. If the app is not reachable, I would inspect process logs for crashes, bind failures, or configuration errors. If the app is reachable locally, I would then check the proxy upstream address, routing, firewall rules, and DNS configuration. + +## Task 2 + +### Broken deploy reproduction + +Commands: + +```bash +cd app/ +ADDR=:8080 go run . & +PID1=$! +sleep 1 +ADDR=:8080 go run . 2>&1 | tee /tmp/qn-broken.log & +PID2=$! +sleep 2 +cat /tmp/qn-broken.log +ps -ef | grep quicknotes | grep -v grep +ps -ef | grep "go run" | grep -v grep +``` + +Output: + +```text +[3] 2913573 +2026/06/16 10:26:01 quicknotes listening on :8080 (notes loaded: 8) + +[4] 2914079 +2026/06/16 10:26:26 quicknotes listening on :8080 (notes loaded: 8) +2026/06/16 10:26:26 listen: listen tcp :8080: bind: address already in use +exit status 1 + +[4]+ Done ADDR=:8080 go run . 2>&1 | tee /tmp/qn-broken.log + +2026/06/16 10:26:26 quicknotes listening on :8080 (notes loaded: 8) +2026/06/16 10:26:26 listen: listen tcp :8080: bind: address already in use +exit status 1 + +mostafa 2913671 2913573 0 10:26 pts/3 00:00:00 /home/mostafa/.cache/go-build/32/32655a38a659cde499c37fabd7e81ec7a161a2e7297b91b6bb17f272b23aa937-d/quicknotes +mostafa 2913573 2899005 2 10:26 pts/3 00:00:02 /snap/go/11200/bin/go run . +``` + +Decision: + +The broken deploy was reproduced successfully. The first QuickNotes instance started on port `8080`. The second instance attempted to bind to the same port and failed with `listen tcp :8080: bind: address already in use`. + +### Outside-in debugging chain + +#### 1. Is the process running? + +Commands: + +```bash +ps -ef | grep quicknotes | grep -v grep +ps -ef | grep "go run" | grep -v grep +``` + +Output: + +```text +mostafa 2913671 2913573 0 10:26 pts/3 00:00:00 /home/mostafa/.cache/go-build/32/32655a38a659cde499c37fabd7e81ec7a161a2e7297b91b6bb17f272b23aa937-d/quicknotes +mostafa 2913573 2899005 2 10:26 pts/3 00:00:02 /snap/go/11200/bin/go run . +``` + +Decision: + +One QuickNotes process is still running. The failed second startup exited, but the first `go run .` wrapper and its compiled `quicknotes` child process remain active. + +#### 2. Is it listening? + +Command: + +```bash +ss -tlnp | grep 8080 +``` + +Output: + +```text +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=2913671,fd=3)) +``` + +Decision: + +Port `8080` is already owned by the running `quicknotes` process. This confirms that the second process failed because the address was already in use. + +#### 3. Is it reachable from the host? + +Commands: + +```bash +curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/health +curl -s http://localhost:8080/health +``` + +Output: + +```text +200 +{"notes":8,"status":"ok"} +``` + +Decision: + +The active QuickNotes instance is reachable from the host and returns a healthy response. The issue is not local reachability to the already-running service. + +#### 4. Is a firewall blocking it? + +Command: + +```bash +sudo iptables -L -n -v 2>/dev/null || sudo nft list ruleset 2>/dev/null || true +``` + +Output excerpt: + +```text +Chain INPUT (policy ACCEPT 39M packets, 61G bytes) + pkts bytes target prot opt in out source destination + 39M 61G LIBVIRT_INP 0 -- * * 0.0.0.0/0 0.0.0.0/0 + +Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) + pkts bytes target prot opt in out source destination + 14M 14G DOCKER-USER 0 -- * * 0.0.0.0/0 0.0.0.0/0 + 14M 14G DOCKER-FORWARD 0 -- * * 0.0.0.0/0 0.0.0.0/0 + 0 0 LIBVIRT_FWX 0 -- * * 0.0.0.0/0 0.0.0.0/0 + 0 0 LIBVIRT_FWI 0 -- * * 0.0.0.0/0 0.0.0.0/0 + 0 0 LIBVIRT_FWO 0 -- * * 0.0.0.0/0 0.0.0.0/0 + +Chain OUTPUT (policy ACCEPT 28M packets, 23G bytes) + pkts bytes target prot opt in out source destination + 28M 23G LIBVIRT_OUT 0 -- * * 0.0.0.0/0 0.0.0.0/0 + +Chain DOCKER-USER (1 references) + pkts bytes target prot opt in out source destination + 14M 14G RETURN 0 -- * * 0.0.0.0/0 0.0.0.0/0 +``` + +Decision: + +The firewall output shows default `ACCEPT` policies for `INPUT` and `OUTPUT`, and the localhost health check already succeeded. A firewall block is not the cause of this failure. + +#### 5. Does DNS resolve localhost? + +Command: + +```bash +dig +short localhost +``` + +Output: + +```text +127.0.0.1 +``` + +Decision: + +`localhost` resolves to `127.0.0.1`, so local name resolution is working. DNS is not the cause of the failed startup. + +### Repair and re-verify + +Commands: + +```bash +ss -tlnp | grep 8080 +kill 2913671 +sleep 1 +ss -tlnp | grep 8080 || echo "nothing listening on 8080" +ADDR=:8080 go run . & +PID1=$! +sleep 1 +ss -tlnp | grep 8080 +curl -s http://localhost:8080/health +``` + +Output: + +```text +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=2913671,fd=3)) +2026/06/16 10:33:18 shutting down +nothing listening on 8080 + +[3] 2918953 +2026/06/16 10:33:31 quicknotes listening on :8080 (notes loaded: 8) + +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=2919053,fd=3)) +{"notes":8,"status":"ok"} +``` + +Decision: + +The conflicting listener was stopped, port `8080` was confirmed free, QuickNotes restarted cleanly, and `/health` returned a valid response. + +### Root cause + +The second QuickNotes instance failed because another QuickNotes process was already listening on TCP port `8080`. The exact root-cause error was: + +```text +listen: listen tcp :8080: bind: address already in use +``` + +### Mini-postmortem + +The broken deploy was caused by two QuickNotes instances trying to bind to the same TCP port, `:8080`. This is a systemic deployment risk because process startup can fail when port ownership is not coordinated, old processes are left running, or service managers do not enforce a single active instance. The application code was not the root problem; the runtime environment already had the required port occupied. + +This kind of failure can be prevented with service supervision and deployment checks. A `systemd` unit can manage one authoritative process, stop old instances cleanly, restart failed services, and expose logs through `journalctl`. Pre-flight checks such as `ss -tlnp | grep 8080` can detect port conflicts before rollout. Health checks can confirm that the intended instance is serving traffic after restart. + +## Bonus Task + +### HTTPS layer + +QuickNotes only serves HTTP directly on port `8080`, so I added Caddy as a TLS-terminating reverse proxy on port `8443`. + +Commands: + +```bash +echo 'localhost:8443 { + reverse_proxy localhost:8080 +}' | sudo tee /etc/caddy/Caddyfile + +sudo systemctl restart caddy +sudo systemctl status caddy --no-pager +``` + +Output excerpt: + +```text +caddy.service - Caddy +Loaded: loaded (/usr/lib/systemd/system/caddy.service; enabled; preset: enabled) +Active: active (running) since Tue 2026-06-16 10:50:33 MSK +Main PID: 2930518 (caddy) +``` + +Decision: + +Caddy started successfully and listened as the HTTPS reverse proxy for `localhost:8443`. + +### HTTPS health check + +Command: + +```bash +curl -vk https://localhost:8443/health +``` + +Output excerpt: + +```text +* TLSv1.3 (OUT), TLS handshake, Client hello (1): +* TLSv1.3 (IN), TLS handshake, Server hello (2): +* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey +* ALPN: server accepted h2 +< HTTP/2 200 +< server: Caddy +{"notes":8,"status":"ok"} +``` + +Decision: + +The HTTPS request completed successfully. Caddy terminated TLS, negotiated `TLSv1.3` with cipher suite `TLS_AES_128_GCM_SHA256`, and proxied the request to QuickNotes. + +### TLS packet capture + +Commands: + +```bash +sudo tcpdump -i lo -nn -s 0 -w lab4-tls.pcap 'tcp port 8443' & +TCPDUMP_PID=$! + +curl -vk https://localhost:8443/health + +sudo kill $TCPDUMP_PID +wait $TCPDUMP_PID 2>/dev/null +``` + +The resulting capture file is `lab4-tls.pcap`. + +### ClientHello + +Screenshot: + +![ClientHello packet in Wireshark](../images/lab4/client_hello.png) + +Annotation: + +- The ClientHello is visible in Wireshark as `Client Hello (SNI=localhost)`. +- The client connects to Caddy on `localhost:8443`. +- The Server Name Indication extension identifies the requested hostname as `localhost`. +- The ClientHello advertises supported TLS versions and cipher suites, allowing the server to choose a mutually supported TLS version and cipher. + +### ServerHello + +Screenshot: + +![ServerHello packet in Wireshark](../images/lab4/server_hello.png) + +Annotation: + +- The ServerHello is visible in Wireshark after the matching ClientHello. +- The negotiated protocol is `TLSv1.3`. +- The selected cipher suite is `TLS_AES_128_GCM_SHA256`. +- The selected temporary key exchange group is `X25519`, matching the `curl -vk` and `openssl s_client` output. + +### Certificate chain + +Command: + +```bash +openssl s_client -connect localhost:8443 -servername localhost -showcerts &1 | tee lab4-tls-cert-chain.txt +``` + +Output excerpt: + +```text +Certificate chain + 0 s: + i:CN = Caddy Local Authority - ECC Intermediate + a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA256 + v:NotBefore: Jun 16 07:45:40 2026 GMT; NotAfter: Jun 16 19:45:40 2026 GMT + 1 s:CN = Caddy Local Authority - ECC Intermediate + i:CN = Caddy Local Authority - 2026 ECC Root + a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA256 + v:NotBefore: Jun 16 07:45:40 2026 GMT; NotAfter: Jun 23 07:45:40 2026 GMT +--- +Server certificate +subject= +issuer=CN = Caddy Local Authority - ECC Intermediate +--- +Server Temp Key: X25519, 253 bits +--- +SSL handshake has read 1268 bytes and written 375 bytes +Verification error: unable to get local issuer certificate +--- +New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256 +Verify return code: 20 (unable to get local issuer certificate) +``` + +Decision: + +The certificate chain is Caddy's local development certificate chain. The verification warning is expected because the local Caddy root certificate is not trusted by the host certificate store. The TLS handshake still completed successfully. + +### TLS 1.0 and TLS 1.1 deprecation + +TLS 1.0 and TLS 1.1 are rejected during the protocol-version negotiation part of the handshake. The client advertises supported versions in the ClientHello, and the server selects an acceptable version in the ServerHello. In this capture, the server selected `TLSv1.3`; a client that only supported TLS 1.0 or TLS 1.1 would fail during negotiation before any HTTP request reached QuickNotes.