diff --git a/cert-chain.txt b/cert-chain.txt new file mode 100644 index 000000000..e583013d2 --- /dev/null +++ b/cert-chain.txt @@ -0,0 +1,85 @@ +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 18:45:09 2026 GMT; NotAfter: Jun 17 06:45:09 2026 GMT +-----BEGIN CERTIFICATE----- +MIIBvzCCAWSgAwIBAgIRAKAfcMliWfFQqP2uILcv8RkwCgYIKoZIzj0EAwIwMzEx +MC8GA1UEAxMoQ2FkZHkgTG9jYWwgQXV0aG9yaXR5IC0gRUNDIEludGVybWVkaWF0 +ZTAeFw0yNjA2MTYxODQ1MDlaFw0yNjA2MTcwNjQ1MDlaMAAwWTATBgcqhkjOPQIB +BggqhkjOPQMBBwNCAARzv2IEdTRf6dMXmpOqTczHNuOSUrv9zKv+GBaieABfLmVQ +KuTNbt1JML8ASUSoyhAg1FugSCbiLQtjqO8l9iXSo4GLMIGIMA4GA1UdDwEB/wQE +AwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFKhE +lw+VZTEovQY+OQVIGYkKML37MB8GA1UdIwQYMBaAFElhYr54rChV5P57JoFNX5r7 +mqWYMBcGA1UdEQEB/wQNMAuCCWxvY2FsaG9zdDAKBggqhkjOPQQDAgNJADBGAiEA +nw3FfY8U4r9TveCxKehQP1n3Rf3W6Jcl2MRiodY4TR8CIQCtS0Fi0XWr9RTxGluL +bg6b2fle+qwRU5JkLbam6HMgpg== +-----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 18:45:09 2026 GMT; NotAfter: Jun 23 18:45:09 2026 GMT +-----BEGIN CERTIFICATE----- +MIIBxzCCAW6gAwIBAgIRAM8Iism4v6rs441Ml1RakpUwCgYIKoZIzj0EAwIwMDEu +MCwGA1UEAxMlQ2FkZHkgTG9jYWwgQXV0aG9yaXR5IC0gMjAyNiBFQ0MgUm9vdDAe +Fw0yNjA2MTYxODQ1MDlaFw0yNjA2MjMxODQ1MDlaMDMxMTAvBgNVBAMTKENhZGR5 +IExvY2FsIEF1dGhvcml0eSAtIEVDQyBJbnRlcm1lZGlhdGUwWTATBgcqhkjOPQIB +BggqhkjOPQMBBwNCAAQXkRGfeie3v6A2plmPe2b13kHed/LcQxgOvbE3jKeDUshn +copYAOkS7FEt166G80Ie4fuDsjhjPhROr7/oEqsVo2YwZDAOBgNVHQ8BAf8EBAMC +AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUSWFivnisKFXk/nsmgU1f +mvuapZgwHwYDVR0jBBgwFoAUCdXqj89bFeBdywbjiiosiH4VvX8wCgYIKoZIzj0E +AwIDRwAwRAIgQw/AbgM0cZs3epjz/CSYF1oFmISpw8gBktIprsaMdHwCIDkOq6fu +mRdCNF20XYh2bfa5sJiOiVvKoRe0zlAt8uoP +-----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 1271 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) +--- +--- +Post-Handshake New Session Ticket arrived: +SSL-Session: + Protocol : TLSv1.3 + Cipher : TLS_AES_128_GCM_SHA256 + Session-ID: 2CB0DA995EB4E89B81B007E7AFFFC21661CD6B8CFF82665C616B5DB5432E328D + Session-ID-ctx: + Resumption PSK: FDFC38567D0E7BF6B6A7D4547316668C851E32F4139B76884394E74354AA7F8C + PSK identity: None + PSK identity hint: None + SRP username: None + TLS session ticket lifetime hint: 604800 (seconds) + TLS session ticket: + 0000 - ce 22 de 78 0d 8f b0 8d-41 53 47 7d 24 c1 5e 8a .".x....ASG}$.^. + 0010 - c8 7f 8a c7 c9 81 98 73-d5 c4 6e ac a0 da b6 f2 .......s..n..... + 0020 - d7 ec ca 6c 5a b8 2a e4-a4 fe d3 d3 a3 b8 58 ae ...lZ.*.......X. + 0030 - e4 0a ca b0 de 9d df d1-09 fe 29 85 c0 72 e6 50 ..........)..r.P + 0040 - f7 5f 38 53 15 7e 1a fc-4c bd 4c e2 9a d1 ed e1 ._8S.~..L.L..... + 0050 - 73 f5 f7 6b 0e aa 40 02-cb 0a cd 6d ad 9f fb 76 s..k..@....m...v + 0060 - ad 84 d3 52 69 00 ff 74-18 ...Ri..t. + + Start Time: 1781636491 + 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..a0ac9b887 Binary files /dev/null and b/lab4-tls.pcap differ diff --git a/lab4-trace.txt b/lab4-trace.txt new file mode 100644 index 000000000..610fb7b73 --- /dev/null +++ b/lab4-trace.txt @@ -0,0 +1,43 @@ +20:14:38.597650 IP 127.0.0.1.51824 > 127.0.0.1.8080: Flags [S], seq 3335370418, win 65495, options [mss 65495,sackOK,TS val 1766035711 ecr 0,nop,wscale 7], length 0 +E.. 127.0.0.1.51824: Flags [S.], seq 535201973, ack 3335370419, win 65483, options [mss 65495,sackOK,TS val 1766035711 ecr 1766035711,nop,wscale 7], length 0 +E..<..@.@.<............p.............0......... +iC..iC...... +20:14:38.597752 IP 127.0.0.1.51824 > 127.0.0.1.8080: Flags [.], ack 1, win 512, options [nop,nop,TS val 1766035711 ecr 1766035711], length 0 +E..4P9@.@............p...............(..... +iC..iC.. +20:14:38.597956 IP 127.0.0.1.51824 > 127.0.0.1.8080: Flags [P.], seq 1:175, ack 1, win 512, options [nop,nop,TS val 1766035711 ecr 1766035711], length 174: HTTP: POST /notes HTTP/1.1 +E...P:@.@............p..................... +iC..iC..POST /notes HTTP/1.1 +Host: 127.0.0.1:8080 +User-Agent: curl/8.5.0 +Accept: */* +Content-Type: application/json +Content-Length: 39 + +{"title":"trace me","body":"in flight"} +20:14:38.597960 IP 127.0.0.1.8080 > 127.0.0.1.51824: Flags [.], ack 175, win 511, options [nop,nop,TS val 1766035711 ecr 1766035711], length 0 +E..4..@.@..............p.......a.....(..... +iC..iC.. +20:14:38.616063 IP 127.0.0.1.8080 > 127.0.0.1.51824: Flags [P.], seq 1:207, ack 175, win 512, options [nop,nop,TS val 1766035729 ecr 1766035711], length 206: HTTP: HTTP/1.1 201 Created +E.....@.@..<...........p.......a........... +iC..iC..HTTP/1.1 201 Created +Content-Type: application/json +Date: Tue, 16 Jun 2026 17:14:38 GMT +Content-Length: 93 + +{"id":7,"title":"trace me","body":"in flight","created_at":"2026-06-16T17:14:38.598761313Z"} + +20:14:38.616127 IP 127.0.0.1.51824 > 127.0.0.1.8080: Flags [.], ack 207, win 511, options [nop,nop,TS val 1766035729 ecr 1766035729], length 0 +E..4P;@.@............p.....a.........(..... +iC..iC.. +20:14:38.616587 IP 127.0.0.1.51824 > 127.0.0.1.8080: Flags [F.], seq 175, ack 207, win 512, options [nop,nop,TS val 1766035730 ecr 1766035729], length 0 +E..4P<@.@............p.....a.........(..... +iC..iC.. +20:14:38.616709 IP 127.0.0.1.8080 > 127.0.0.1.51824: Flags [F.], seq 207, ack 176, win 512, options [nop,nop,TS val 1766035730 ecr 1766035730], length 0 +E..4..@.@.. ...........p.......b.....(..... +iC..iC.. +20:14:38.616742 IP 127.0.0.1.51824 > 127.0.0.1.8080: Flags [.], ack 208, win 512, options [nop,nop,TS val 1766035730 ecr 1766035730], length 0 +E..4P=@.@............p.....b.........(..... +iC..iC.. diff --git a/screenshots/lab4/certchain.png b/screenshots/lab4/certchain.png new file mode 100644 index 000000000..93acf24ea Binary files /dev/null and b/screenshots/lab4/certchain.png differ diff --git a/screenshots/lab4/clienthello.png b/screenshots/lab4/clienthello.png new file mode 100644 index 000000000..7badeebb2 Binary files /dev/null and b/screenshots/lab4/clienthello.png differ diff --git a/screenshots/lab4/serverhello.png b/screenshots/lab4/serverhello.png new file mode 100644 index 000000000..adb0159f6 Binary files /dev/null and b/screenshots/lab4/serverhello.png differ diff --git a/submissions/lab4.md b/submissions/lab4.md new file mode 100644 index 000000000..4771e6dbb --- /dev/null +++ b/submissions/lab4.md @@ -0,0 +1,379 @@ +# Task 1 — Trace a request end-to-end + +## Packet capture + +The decoded packet capture is saved in `lab4-trace.txt`. + +## Annotated trace + +### TCP three-way handshake + +```text +20:14:38.597650 IP 127.0.0.1.51824 > 127.0.0.1.8080: Flags [S], seq 3335370418, length 0 +20:14:38.597701 IP 127.0.0.1.8080 > 127.0.0.1.51824: Flags [S.], seq 535201973, ack 3335370419, length 0 +20:14:38.597752 IP 127.0.0.1.51824 > 127.0.0.1.8080: Flags [.], ack 1, length 0 +``` + +### HTTP request + +```text +20:14:38.597956 IP 127.0.0.1.51824 > 127.0.0.1.8080: Flags [P.], length 174 +POST /notes HTTP/1.1 +Host: 127.0.0.1:8080 +User-Agent: curl/8.5.0 +Accept: */* +Content-Type: application/json +Content-Length: 39 + +{"title":"trace me","body":"in flight"} +``` + +### HTTP response + +```text +20:14:38.61063 IP 127.0.0.1.8080 > 127.0.0.1.51824: Flags [P.], length 206 +HTTP/1.1 201 Created +Content-Type: application/json +Date: Tue, 16 Jun 2026 17:14:38 GMT +Content-Length: 93 + +{"id":7,"title":"trace me","body":"in flight","created_at":"2026-06-16T17:14:38.598761313Z"} +``` + +### Connection close + +```text +20:14:38.616587 IP 127.0.0.1.51824 > 127.0.0.1.8080: Flags [F.], seq 175, ack 207, length 0 +20:14:38.616709 IP 127.0.0.1.8080 > 127.0.0.1.51824: Flags [F.], seq 207, ack 176, length 0 +20:14:38.61742 IP 127.0.0.1.51824 > 127.0.0.1.8080: Flags [.], ack 208, length 0 +``` + +## Debugging commands + +### 1. Listening socket + +Command: + +```bash +ss -tlnp | grep :8080 +``` + +Output: + +```bash +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=8708,fd=3)) +``` + +### 2. Routes + +Command: + +```bash +ip route show +``` + +Output: + +```bash +default via 172.20.48.1 dev eth0 proto kernel +172.20.48.0/20 dev eth0 proto kernel scope link src 172.20.56.44 +``` + +### 3. Reachability + +Command: + +```bash +mtr -rwc 5 localhost +``` + +Output: + +```bash +Start: 2026-06-16T20:39:09+0300 +HOST: WIN-UTA15SENBQB Loss% Snt Last Avg Best Wrst StDev + 1.|-- localhost 0.0% 5 0.1 0.1 0.0 0.4 0.2 +``` + +### 4. DNS + +Command: + +```bash +dig +short example.com @1.1.1.1 +``` + +Output: + +```bash +104.20.23.154 +172.66.147.243 +``` + +### 5. Logs + +Command: + +```bash +journalctl --user -u quicknotes -n 20 || true +``` + +Output: + +```bash +-- No entries -- +``` + +## What would you check first if QuickNotes returned 502? + +```text +If QuickNotes returned 502, I would first check whether the gateway or reverse proxy can reach the backend on port 8080. I would verify that the QuickNotes process is running, that ss -tlnp shows a listener on :8080, and that curl http://127.0.0.1:8080/health returns a successful response locally. If the backend is healthy, I would then check proxy logs, firewall rules, DNS, and routing to find where the request fails. +``` + +# Task 2 — Outside-in debugging on a broken deploy + +## Broken deploy reproduction + +Command: + +```bash +cd app/ + +ADDR=:8080 go run . & +PID1=$! +sleep 1 + +ADDR=:8080 go run . 2>&1 | tee /tmp/qn-broken.log & +PID2=$! +sleep 2 + +ps -ef | grep "go run" | grep -v grep +cat /tmp/qn-broken.log +``` + +Output: + +```text +2026/06/16 21:17:58 quicknotes listening on :8080 (notes loaded: 7) +2026/06/16 21:17:58 listen: listen tcp :8080: bind: address already in use +exit status 1 +``` + +Root cause: + +```text +listen tcp :8080: bind: address already in use +``` + +The second QuickNotes instance failed because port `8080` was already occupied by the first running instance. + +## Outside-in chain + +### 1. Is the process running? + +Command: + +```bash +ps -ef | grep quicknotes +``` + +Output: + +```text +nichita 9489 9416 0 21:17 pts/0 00:00:00 /home/nichita/.cache/go-build/.../quicknotes +nichita 9738 5770 0 21:20 pts/0 00:00:00 grep --color=auto quicknotes +``` + +Decision: + +A QuickNotes process is running. This means the service is not completely down; at least one instance is alive. + +### 2. Is it listening? + +Command: + +```bash +ss -tlnp | grep 8080 +``` + +Output: + +```text +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=9489,fd=3)) +``` + +Decision: + +QuickNotes is listening on port `8080`. The port conflict happened because this existing process already owned the port. + +### 3. Is it reachable from the host? + +Command: + +```bash +curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/health +``` + +Output: + +```text +200 +``` + +Decision: + +The running QuickNotes instance is reachable from the host and returns a successful HTTP status code. + +### 4. Is the firewall blocking it? + +Command: + +```bash +sudo iptables -L -n -v 2>/dev/null || sudo nft list ruleset 2>/dev/null || true +``` + +Output: + +```text +No blocking rule was reported by the command. +``` + +Decision: + +The failure is not caused by firewall blocking. The service is reachable locally, and the root cause is the port conflict. + +### 5. Is DNS working? + +Command: + +```bash +dig +short localhost +``` + +Output: + +```text +127.0.0.1 +``` + +Decision: + +`localhost` resolves correctly to `127.0.0.1`, so DNS is not the cause of the failure. + +### Repair and re-verify + +Command: + +```bash +kill 9489 +sleep 1 + +ADDR=:8080 go run . & +PID3=$! +sleep 1 + +curl -s http://127.0.0.1:8080/health +``` + +Output: + +```text +{"notes":7,"status":"ok"} +``` + +Decision: + +After killing the old QuickNotes process and starting a new single instance, the `/health` endpoint returned `status: ok`, so the service was repaired. + +### Mini-postmortem + +The failure was caused by two QuickNotes instances trying to bind to the same port. This is a systemic deployment issue, not an individual mistake: the deploy process allowed a new instance to start without first checking whether the old one was still using `:8080`. Better tooling could prevent this with systemd service management, health checks, pre-deploy port checks, and a restart strategy that safely stops or replaces the old process. A robust deployment should make this class of error visible before users are affected. + +## Bonus Task — Decode the TLS handshake + +### HTTPS layer + +I added a local HTTPS layer using Caddy as a TLS-terminating reverse proxy. Caddy listened on `localhost:8443` and forwarded requests to QuickNotes on `localhost:8080`. + +Caddyfile: + +```text +localhost:8443 { + reverse_proxy localhost:8080 +} +``` + +### HTTPS verification + +Command: + +```bash +curl -vk https://localhost:8443/health +``` + +Important output: + +```text +* TLSv1.3 (OUT), TLS handshake, Client hello (1) +* TLSv1.3 (IN), TLS handshake, Server hello (2) +* TLSv1.3 (IN), TLS handshake, Certificate (11) +* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey +* ALPN: server accepted h2 +> GET /health HTTP/2 +< HTTP/2 200 +< server: Caddy +``` + +Decision: + +The request reached QuickNotes through the HTTPS reverse proxy. The TLS connection used TLS 1.3 with `TLS_AES_128_GCM_SHA256`, and Caddy returned `HTTP/2 200`. + +### TLS packet capture + +Command: + +```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 TLS capture is saved as `lab4-tls.pcap`. + +### ClientHello + +Screenshot: + +![ClientHello](../screenshots/lab4/clienthello.png) + +The ClientHello is the first TLS negotiation message sent by the client. In this capture, the client connects to `localhost` and offers modern TLS versions through the `supported_versions` extension: TLS 1.3 and TLS 1.2. It also includes supported cipher suites, ALPN, SNI (`localhost`), and key exchange information such as `x25519`. + +### ServerHello + +Screenshot: + +![ServerHello](../screenshots/lab4/serverhello.png) + +The ServerHello is the server response that selects the final TLS parameters for the connection. In this capture, the server selected TLS 1.3 through the `supported_versions` extension, chose the cipher suite `TLS_AES_128_GCM_SHA256`, and used `x25519` for key exchange. After this step, the rest of the TLS connection becomes encrypted application data. + +### Certificate chain + +Command: + +```bash +openssl s_client -connect localhost:8443 -servername localhost -showcerts