diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..3da493f8e --- /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/submissions/lab4-docker-trace.pcap b/submissions/lab4-docker-trace.pcap new file mode 100644 index 000000000..a322a76b4 Binary files /dev/null and b/submissions/lab4-docker-trace.pcap differ diff --git a/submissions/lab4-docker-trace.txt b/submissions/lab4-docker-trace.txt new file mode 100644 index 000000000..a06c8a29f --- /dev/null +++ b/submissions/lab4-docker-trace.txt @@ -0,0 +1,50 @@ +Lab 4 annotated packet trace +Environment: Linux container, loopback interface lo +Capture file: submissions/lab4-docker-trace.pcap +Decoded with: tcpdump -r submissions/lab4-docker-trace.pcap -nn -A + +reading from file submissions/lab4-docker-trace.pcap, link-type EN10MB (Ethernet), snapshot length 262144 +14:16:25.452682 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [S], seq 4278554227, win 65495, options [mss 65495,sackOK,TS val 2462602185 ecr 0,nop,wscale 7], length 0 +E..<9.@.@..................s.........0......... +..S......... +14:16:25.452689 IP 127.0.0.1.8080 > 127.0.0.1.47304: Flags [S.], seq 1200757750, ack 4278554228, win 65483, options [mss 65495,sackOK,TS val 2462602185 ecr 2462602185,nop,wscale 7], length 0 +E..<..@.@.<.............G......t.....0......... +..S...S..... +14:16:25.452695 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [.], ack 1, win 512, options [nop,nop,TS val 2462602185 ecr 2462602185], length 0 +E..49.@.@..................tG........(..... +..S...S. +14:16:25.452898 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [P.], seq 1:176, ack 1, win 512, options [nop,nop,TS val 2462602185 ecr 2462602185], length 175: HTTP: POST /notes HTTP/1.1 +E...9.@.@..^...............tG.............. +..S...S.POST /notes HTTP/1.1 +Host: localhost:8080 +User-Agent: curl/7.88.1 +Accept: */* +Content-Type: application/json +Content-Length: 39 + +{"title":"trace me","body":"in flight"} +14:16:25.452912 IP 127.0.0.1.8080 > 127.0.0.1.47304: Flags [.], ack 176, win 511, options [nop,nop,TS val 2462602185 ecr 2462602185], length 0 +E..4.<@.@.y.............G......#.....(..... +..S...S. +14:16:25.453162 IP 127.0.0.1.8080 > 127.0.0.1.47304: Flags [P.], seq 1:207, ack 176, win 512, options [nop,nop,TS val 2462602185 ecr 2462602185], length 206: HTTP: HTTP/1.1 201 Created +E....=@.@.x.............G......#........... +..S...S.HTTP/1.1 201 Created +Content-Type: application/json +Date: Sun, 14 Jun 2026 14:16:25 GMT +Content-Length: 93 + +{"id":5,"title":"trace me","body":"in flight","created_at":"2026-06-14T14:16:25.453003253Z"} + +14:16:25.453165 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [.], ack 207, win 511, options [nop,nop,TS val 2462602185 ecr 2462602185], length 0 +E..49.@.@..................#G........(..... +..S...S. +14:16:25.456667 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [F.], seq 176, ack 207, win 512, options [nop,nop,TS val 2462602189 ecr 2462602185], length 0 +E..49.@.@..................#G........(..... +..S...S. +14:16:25.456716 IP 127.0.0.1.8080 > 127.0.0.1.47304: Flags [F.], seq 207, ack 177, win 512, options [nop,nop,TS val 2462602189 ecr 2462602189], length 0 +E..4.>@.@.y.............G......$.....(..... +..S...S. +14:16:25.456730 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [.], ack 208, win 512, options [nop,nop,TS val 2462602189 ecr 2462602189], length 0 +E..49.@.@.. +...............$G........(..... +..S...S. diff --git a/submissions/lab4-linux-evidence.txt b/submissions/lab4-linux-evidence.txt new file mode 100644 index 000000000..5b79a7232 --- /dev/null +++ b/submissions/lab4-linux-evidence.txt @@ -0,0 +1,141 @@ +# Lab 4 Linux Docker Evidence + +Date: 2026-06-14T14:16:21Z +Kernel: Linux ae8db4a22f62 6.12.54-linuxkit #1 SMP Tue Nov 4 21:21:47 UTC 2025 aarch64 GNU/Linux +Go: go version go1.24.13 linux/arm64 +Installed tools: iproute2 dnsutils mtr-tiny tcpdump curl procps iptables nftables jq systemd + +## QuickNotes startup +2026/06/14 14:16:24 quicknotes listening on :8080 (notes loaded: 4) + +## Capture command +tcpdump -i lo -nn -s 0 -A tcp port 8080 -w submissions/lab4-docker-trace.pcap + +## curl -v POST /notes +Note: Unnecessary use of -X or --request, POST is already inferred. + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed + 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 127.0.0.1:8080... +* Connected to localhost (127.0.0.1) port 8080 (#0) +> POST /notes HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/7.88.1 +> Accept: */* +> Content-Type: application/json +> Content-Length: 39 +> +} [39 bytes data] +< HTTP/1.1 201 Created +< Content-Type: application/json +< Date: Sun, 14 Jun 2026 14:16:25 GMT +< Content-Length: 93 +< +{ [93 bytes data] + 100 132 100 93 100 39 8200 3438 --:--:-- --:--:-- --:--:-- 12000 +* Connection #0 to host localhost left intact +{"id":5,"title":"trace me","body":"in flight","created_at":"2026-06-14T14:16:25.453003253Z"} + +## tcpdump capture log +tcpdump: listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes +10 packets captured +20 packets received by filter +0 packets dropped by kernel + +## tcpdump decode +reading from file submissions/lab4-docker-trace.pcap, link-type EN10MB (Ethernet), snapshot length 262144 +14:16:25.452682 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [S], seq 4278554227, win 65495, options [mss 65495,sackOK,TS val 2462602185 ecr 0,nop,wscale 7], length 0 +E..<9.@.@..................s.........0......... +..S......... +14:16:25.452689 IP 127.0.0.1.8080 > 127.0.0.1.47304: Flags [S.], seq 1200757750, ack 4278554228, win 65483, options [mss 65495,sackOK,TS val 2462602185 ecr 2462602185,nop,wscale 7], length 0 +E..<..@.@.<.............G......t.....0......... +..S...S..... +14:16:25.452695 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [.], ack 1, win 512, options [nop,nop,TS val 2462602185 ecr 2462602185], length 0 +E..49.@.@..................tG........(..... +..S...S. +14:16:25.452898 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [P.], seq 1:176, ack 1, win 512, options [nop,nop,TS val 2462602185 ecr 2462602185], length 175: HTTP: POST /notes HTTP/1.1 +E...9.@.@..^...............tG.............. +..S...S.POST /notes HTTP/1.1 +Host: localhost:8080 +User-Agent: curl/7.88.1 +Accept: */* +Content-Type: application/json +Content-Length: 39 + +{"title":"trace me","body":"in flight"} +14:16:25.452912 IP 127.0.0.1.8080 > 127.0.0.1.47304: Flags [.], ack 176, win 511, options [nop,nop,TS val 2462602185 ecr 2462602185], length 0 +E..4.<@.@.y.............G......#.....(..... +..S...S. +14:16:25.453162 IP 127.0.0.1.8080 > 127.0.0.1.47304: Flags [P.], seq 1:207, ack 176, win 512, options [nop,nop,TS val 2462602185 ecr 2462602185], length 206: HTTP: HTTP/1.1 201 Created +E....=@.@.x.............G......#........... +..S...S.HTTP/1.1 201 Created +Content-Type: application/json +Date: Sun, 14 Jun 2026 14:16:25 GMT +Content-Length: 93 + +{"id":5,"title":"trace me","body":"in flight","created_at":"2026-06-14T14:16:25.453003253Z"} + +14:16:25.453165 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [.], ack 207, win 511, options [nop,nop,TS val 2462602185 ecr 2462602185], length 0 +E..49.@.@..................#G........(..... +..S...S. +14:16:25.456667 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [F.], seq 176, ack 207, win 512, options [nop,nop,TS val 2462602189 ecr 2462602185], length 0 +E..49.@.@..................#G........(..... +..S...S. +14:16:25.456716 IP 127.0.0.1.8080 > 127.0.0.1.47304: Flags [F.], seq 207, ack 177, win 512, options [nop,nop,TS val 2462602189 ecr 2462602189], length 0 +E..4.>@.@.y.............G......$.....(..... +..S...S. +14:16:25.456730 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [.], ack 208, win 512, options [nop,nop,TS val 2462602189 ecr 2462602189], length 0 +E..49.@.@.. +...............$G........(..... +..S...S. + +## ss -tlnp | grep :8080 +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=2698,fd=3)) + +## ip route show +default via 172.17.0.1 dev eth0 +172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2 + +## mtr -rwc 5 localhost +Start: 2026-06-14T14:16:26+0000 +HOST: ae8db4a22f62 Loss% Snt Last Avg Best Wrst StDev + 1.|-- localhost 0.0% 5 0.0 0.1 0.0 0.2 0.1 + +## dig +short example.com @1.1.1.1 +172.66.147.243 +104.20.23.154 + +## journalctl --user -u quicknotes -n 20 || true +No journal files were found. +-- No entries -- + +## Broken deploy: second ADDR=:8080 go run . +2026/06/14 14:16:36 quicknotes listening on :8080 (notes loaded: 4) +2026/06/14 14:16:36 listen: listen tcp :8080: bind: address already in use +exit status 1 + +## ps -ef | grep quicknotes +root 2698 898 0 14:16 ? 00:00:00 /tmp/go-build1352676339/b001/exe/quicknotes + +## ss -tlnp | grep 8080 +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=2698,fd=3)) + +## curl health HTTP code +200 + +## iptables -L -n -v 2>/dev/null || nft list ruleset 2>/dev/null || true +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 + +## dig +short localhost +127.0.0.1 + +## Repair: kill conflicting first instance and restart +2026/06/14 14:16:38 quicknotes listening on :8080 (notes loaded: 4) +{"notes":4,"status":"ok"} + diff --git a/submissions/lab4-tls-certificate.txt b/submissions/lab4-tls-certificate.txt new file mode 100644 index 000000000..f2643bf2d --- /dev/null +++ b/submissions/lab4-tls-certificate.txt @@ -0,0 +1,41 @@ +Certificate chain from: + + openssl s_client -connect localhost:8443 -servername localhost -showcerts GET /health HTTP/1.1 +> Host: localhost:8443 +> User-Agent: curl/7.88.1 +> Accept: */* +> +{ [5 bytes data] +* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): +{ [265 bytes data] +* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): +{ [265 bytes data] +* old SSL session ID is stale, removing +{ [5 bytes data] +< HTTP/1.1 200 OK +< Server: nginx/1.22.1 +< Date: Sun, 14 Jun 2026 15:06:37 GMT +< Content-Type: application/json +< Content-Length: 26 +< Connection: keep-alive +< +{ [26 bytes data] + 100 26 100 26 0 0 1209 0 --:--:-- --:--:-- --:--:-- 1238 +* Connection #0 to host localhost left intact +{"notes":4,"status":"ok"} + +## tcpdump TLS capture log +tcpdump: listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes +16 packets captured +32 packets received by filter +0 packets dropped by kernel + +## openssl s_client -connect localhost:8443 -showcerts + 0030 - 03 8c 28 97 b8 82 c3 89-2a 48 d5 0d 98 b0 36 56 ..(.....*H....6V + 0040 - 8c f8 8a bd 18 91 bd b2-16 7a 35 56 d4 7e ac 53 .........z5V.~.S + 0050 - a9 28 68 fc 79 5f 69 39-03 a8 e8 dd 71 3e b9 e3 .(h.y_i9....q>.. + 0060 - b7 de 7e c7 0f 40 45 15-28 4b 50 0f bd 39 74 50 ..~..@E.(KP..9tP + 0070 - 80 c7 d8 c0 5d 33 9e 73-0f 0c fb 6c 2c 1e c3 d4 ....]3.s...l,... + 0080 - d3 64 5c d5 08 e0 71 7e-e1 58 43 d7 49 57 4b 1c .d\...q~.XC.IWK. + 0090 - 84 99 b9 48 18 72 58 d2-f3 4f 9d 39 12 35 ea 4b ...H.rX..O.9.5.K + 00a0 - b0 77 41 53 7e cd ec f6-1f b4 9b 4c 47 72 44 19 .wAS~......LGrD. + 00b0 - c4 be f4 65 07 76 68 db-68 ee da 49 1b 1a 33 31 ...e.vh.h..I..31 + 00c0 - 9f 37 68 69 e1 94 25 2c-85 15 b9 23 fe 04 6f f4 .7hi..%,...#..o. + 00d0 - 68 07 86 35 dc 81 6e aa-08 cc d0 e2 cd 0b a1 2d h..5..n........- + 00e0 - 5e d8 8c 88 5c 81 84 e1-d8 f4 3e 74 28 60 78 17 ^...\.....>t(`x. + + Start Time: 1781449598 + Timeout : 7200 (sec) + Verify return code: 18 (self-signed certificate) + Extended master secret: no + Max Early Data: 0 +--- +read R BLOCK diff --git a/submissions/lab4-tls-serverhello.txt b/submissions/lab4-tls-serverhello.txt new file mode 100644 index 000000000..745db2f33 --- /dev/null +++ b/submissions/lab4-tls-serverhello.txt @@ -0,0 +1,147 @@ +Running as user "root" and group "root". This could be dangerous. +Frame 6: 1450 bytes on wire (11600 bits), 1450 bytes captured (11600 bits) + Encapsulation type: Ethernet (1) + Arrival Time: Jun 14, 2026 15:06:37.060423000 UTC + [Time shift for this packet: 0.000000000 seconds] + Epoch Time: 1781449597.060423000 seconds + [Time delta from previous captured frame: 0.001696000 seconds] + [Time delta from previous displayed frame: 0.000000000 seconds] + [Time since reference or first frame: 0.003893000 seconds] + Frame Number: 6 + Frame Length: 1450 bytes (11600 bits) + Capture Length: 1450 bytes (11600 bits) + [Frame is marked: False] + [Frame is ignored: False] + [Protocols in frame: eth:ethertype:ip:tcp:tls] +Ethernet II, Src: 00:00:00_00:00:00 (00:00:00:00:00:00), Dst: 00:00:00_00:00:00 (00:00:00:00:00:00) + Destination: 00:00:00_00:00:00 (00:00:00:00:00:00) + Address: 00:00:00_00:00:00 (00:00:00:00:00:00) + .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default) + .... ...0 .... .... .... .... = IG bit: Individual address (unicast) + Source: 00:00:00_00:00:00 (00:00:00:00:00:00) + Address: 00:00:00_00:00:00 (00:00:00:00:00:00) + .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default) + .... ...0 .... .... .... .... = IG bit: Individual address (unicast) + Type: IPv4 (0x0800) +Internet Protocol Version 4, Src: 127.0.0.1, Dst: 127.0.0.1 + 0100 .... = Version: 4 + .... 0101 = Header Length: 20 bytes (5) + Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT) + 0000 00.. = Differentiated Services Codepoint: Default (0) + .... ..00 = Explicit Congestion Notification: Not ECN-Capable Transport (0) + Total Length: 1436 + Identification: 0x545a (21594) + 010. .... = Flags: 0x2, Don't fragment + 0... .... = Reserved bit: Not set + .1.. .... = Don't fragment: Set + ..0. .... = More fragments: Not set + ...0 0000 0000 0000 = Fragment Offset: 0 + Time to Live: 64 + Protocol: TCP (6) + Header Checksum: 0xe2ff [validation disabled] + [Header checksum status: Unverified] + Source Address: 127.0.0.1 + Destination Address: 127.0.0.1 +Transmission Control Protocol, Src Port: 8443, Dst Port: 48542, Seq: 1, Ack: 518, Len: 1384 + Source Port: 8443 + Destination Port: 48542 + [Stream index: 0] + [Conversation completeness: Incomplete, DATA (15)] + [TCP Segment Len: 1384] + Sequence Number: 1 (relative sequence number) + Sequence Number (raw): 4134712766 + [Next Sequence Number: 1385 (relative sequence number)] + Acknowledgment Number: 518 (relative ack number) + Acknowledgment number (raw): 2445260649 + 1000 .... = Header Length: 32 bytes (8) + Flags: 0x018 (PSH, ACK) + 000. .... .... = Reserved: Not set + ...0 .... .... = Accurate ECN: Not set + .... 0... .... = Congestion Window Reduced: Not set + .... .0.. .... = ECN-Echo: Not set + .... ..0. .... = Urgent: Not set + .... ...1 .... = Acknowledgment: Set + .... .... 1... = Push: Set + .... .... .0.. = Reset: Not set + .... .... ..0. = Syn: Not set + .... .... ...0 = Fin: Not set + [TCP Flags: .......AP...] + Window: 512 + [Calculated window size: 65536] + [Window size scaling factor: 128] + Checksum: 0x0391 [unverified] + [Checksum Status: Unverified] + Urgent Pointer: 0 + Options: (12 bytes), No-Operation (NOP), No-Operation (NOP), Timestamps + TCP Option - No-Operation (NOP) + Kind: No-Operation (1) + TCP Option - No-Operation (NOP) + Kind: No-Operation (1) + TCP Option - Timestamps: TSval 3291739510, TSecr 3291739508 + Kind: Time Stamp Option (8) + Length: 10 + Timestamp value: 3291739510 + Timestamp echo reply: 3291739508 + [Timestamps] + [Time since first frame in this TCP stream: 0.003893000 seconds] + [Time since previous frame in this TCP stream: 0.001696000 seconds] + [SEQ/ACK analysis] + [iRTT: 0.000012000 seconds] + [Bytes in flight: 1384] + [Bytes sent since last PSH flag: 1384] + TCP payload (1384 bytes) +Transport Layer Security + TLSv1.2 Record Layer: Handshake Protocol: Server Hello + Content Type: Handshake (22) + Version: TLS 1.2 (0x0303) + Length: 122 + Handshake Protocol: Server Hello + Handshake Type: Server Hello (2) + Length: 118 + Version: TLS 1.2 (0x0303) + Random: 185e18aa2feb7fb42c8b5cd604ade65c1aae48b96ac0c55740e5f4df5e94f643 + Session ID Length: 32 + Session ID: b571efe8bef9079d29ebecaa49299fb59699e0485b2ef4ab30923af9ff9f169c + Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302) + Compression Method: null (0) + Extensions Length: 46 + Extension: supported_versions (len=2) + Type: supported_versions (43) + Length: 2 + Supported Version: TLS 1.3 (0x0304) + Extension: key_share (len=36) + Type: key_share (51) + Length: 36 + Key Share extension + Key Share Entry: Group: x25519, Key Exchange length: 32 + Group: x25519 (29) + Key Exchange Length: 32 + Key Exchange: bedf3f432d666011c453973d39c4017d7ec44ce59d8000b8a2024c2b5812f97c + [JA3S Fullstring: 771,4866,43-51] + [JA3S: 15af977ce25de452b96affa2addb1036] + TLSv1.3 Record Layer: Change Cipher Spec Protocol: Change Cipher Spec + Content Type: Change Cipher Spec (20) + Version: TLS 1.2 (0x0303) + Length: 1 + Change Cipher Spec Message + TLSv1.3 Record Layer: Application Data Protocol: Application Data + Opaque Type: Application Data (23) + Version: TLS 1.2 (0x0303) + Length: 42 + Encrypted Application Data: ff9f7e3446ce3007c07ef94af8535b4f6bbcec7120e4534a60747a6cd111a546bc2451ce... + TLSv1.3 Record Layer: Application Data Protocol: Application Data + Opaque Type: Application Data (23) + Version: TLS 1.2 (0x0303) + Length: 839 + Encrypted Application Data: 7d02b720f7a6f9caaf7b79fd09336e052e58733f106eabcf7fe3b900e461d8d04d03b6ba... + TLSv1.3 Record Layer: Application Data Protocol: Application Data + Opaque Type: Application Data (23) + Version: TLS 1.2 (0x0303) + Length: 281 + Encrypted Application Data: 0a237e28e00a8d8f255ef10871ed827eef504f6363ea555663053d37e7bb1160beb345ef... + TLSv1.3 Record Layer: Application Data Protocol: Application Data + Opaque Type: Application Data (23) + Version: TLS 1.2 (0x0303) + Length: 69 + Encrypted Application Data: f87fd63a7e6b20ec031e8011237eadce12692e27287739c6ddd9005883f9f7d1c4aa8fa7... + diff --git a/submissions/lab4-tls-summary.txt b/submissions/lab4-tls-summary.txt new file mode 100644 index 000000000..1a2a21667 --- /dev/null +++ b/submissions/lab4-tls-summary.txt @@ -0,0 +1,73 @@ +# TLS handshake summary + +Environment: Linux container +Proxy: nginx on https://localhost:8443 +Upstream: QuickNotes on http://127.0.0.1:8080 +Capture: submissions/lab4-tls.pcap +Decoder: tshark (Wireshark CLI) + +## curl result + +curl negotiated TLS 1.3 and successfully reached QuickNotes through nginx: + + SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 + Server certificate: subject: CN=localhost + issuer: CN=localhost + HTTP/1.1 200 OK + {"notes":4,"status":"ok"} + +## ClientHello + +Full decode: submissions/lab4-tls-clienthello.txt + +Important fields: + + SNI: localhost + Cipher suites offered include: + TLS_AES_256_GCM_SHA384 (0x1302) + TLS_CHACHA20_POLY1305_SHA256 (0x1303) + TLS_AES_128_GCM_SHA256 (0x1301) + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030) + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f) + + supported_versions extension: + TLS 1.3 (0x0304) + TLS 1.2 (0x0303) + TLS 1.1 (0x0302) + TLS 1.0 (0x0301) + +## ServerHello + +Full decode: submissions/lab4-tls-serverhello.txt + +Important fields: + + selected cipher suite: + TLS_AES_256_GCM_SHA384 (0x1302) + + supported_versions extension: + TLS 1.3 (0x0304) + + key share group: + x25519 + +## Certificate chain + +Full openssl output: submissions/lab4-tls-evidence.txt +Certificate excerpt: submissions/lab4-tls-certificate.txt + + subject=CN = localhost + issuer=CN = localhost + self-signed certificate + public key: RSA 2048 + signature algorithm: RSA-SHA256 + +## TLS 1.0 / 1.1 deprecation note + +The decisive negotiation step is ServerHello. The client offered several protocol +versions in the supported_versions extension, including TLS 1.0 and TLS 1.1, but +nginx was configured with only TLSv1.2 and TLSv1.3 enabled. The server selected +TLS 1.3 in ServerHello's supported_versions extension, so TLS 1.0 and TLS 1.1 +were not chosen. If a client only supported TLS 1.0/1.1, the server would reject +the handshake during protocol version negotiation before any HTTP request could +be sent. diff --git a/submissions/lab4-tls.pcap b/submissions/lab4-tls.pcap new file mode 100644 index 000000000..f1a70b6ad Binary files /dev/null and b/submissions/lab4-tls.pcap differ diff --git a/submissions/lab4-trace.pcap b/submissions/lab4-trace.pcap new file mode 100644 index 000000000..083a24f41 Binary files /dev/null and b/submissions/lab4-trace.pcap differ diff --git a/submissions/lab4-trace.txt b/submissions/lab4-trace.txt new file mode 100644 index 000000000..6dc852542 --- /dev/null +++ b/submissions/lab4-trace.txt @@ -0,0 +1,50 @@ +Lab 4 annotated packet trace +Environment: Linux container, loopback interface lo +Capture file: submissions/lab4-trace.pcap +Decoded with: tcpdump -r submissions/lab4-trace.pcap -nn -A + +reading from file submissions/lab4-trace.pcap, link-type EN10MB (Ethernet), snapshot length 262144 +14:16:25.452682 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [S], seq 4278554227, win 65495, options [mss 65495,sackOK,TS val 2462602185 ecr 0,nop,wscale 7], length 0 +E..<9.@.@..................s.........0......... +..S......... +14:16:25.452689 IP 127.0.0.1.8080 > 127.0.0.1.47304: Flags [S.], seq 1200757750, ack 4278554228, win 65483, options [mss 65495,sackOK,TS val 2462602185 ecr 2462602185,nop,wscale 7], length 0 +E..<..@.@.<.............G......t.....0......... +..S...S..... +14:16:25.452695 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [.], ack 1, win 512, options [nop,nop,TS val 2462602185 ecr 2462602185], length 0 +E..49.@.@..................tG........(..... +..S...S. +14:16:25.452898 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [P.], seq 1:176, ack 1, win 512, options [nop,nop,TS val 2462602185 ecr 2462602185], length 175: HTTP: POST /notes HTTP/1.1 +E...9.@.@..^...............tG.............. +..S...S.POST /notes HTTP/1.1 +Host: localhost:8080 +User-Agent: curl/7.88.1 +Accept: */* +Content-Type: application/json +Content-Length: 39 + +{"title":"trace me","body":"in flight"} +14:16:25.452912 IP 127.0.0.1.8080 > 127.0.0.1.47304: Flags [.], ack 176, win 511, options [nop,nop,TS val 2462602185 ecr 2462602185], length 0 +E..4.<@.@.y.............G......#.....(..... +..S...S. +14:16:25.453162 IP 127.0.0.1.8080 > 127.0.0.1.47304: Flags [P.], seq 1:207, ack 176, win 512, options [nop,nop,TS val 2462602185 ecr 2462602185], length 206: HTTP: HTTP/1.1 201 Created +E....=@.@.x.............G......#........... +..S...S.HTTP/1.1 201 Created +Content-Type: application/json +Date: Sun, 14 Jun 2026 14:16:25 GMT +Content-Length: 93 + +{"id":5,"title":"trace me","body":"in flight","created_at":"2026-06-14T14:16:25.453003253Z"} + +14:16:25.453165 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [.], ack 207, win 511, options [nop,nop,TS val 2462602185 ecr 2462602185], length 0 +E..49.@.@..................#G........(..... +..S...S. +14:16:25.456667 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [F.], seq 176, ack 207, win 512, options [nop,nop,TS val 2462602189 ecr 2462602185], length 0 +E..49.@.@..................#G........(..... +..S...S. +14:16:25.456716 IP 127.0.0.1.8080 > 127.0.0.1.47304: Flags [F.], seq 207, ack 177, win 512, options [nop,nop,TS val 2462602189 ecr 2462602189], length 0 +E..4.>@.@.y.............G......$.....(..... +..S...S. +14:16:25.456730 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [.], ack 208, win 512, options [nop,nop,TS val 2462602189 ecr 2462602189], length 0 +E..49.@.@.. +...............$G........(..... +..S...S. diff --git a/submissions/lab4.md b/submissions/lab4.md new file mode 100644 index 000000000..23e7399c9 --- /dev/null +++ b/submissions/lab4.md @@ -0,0 +1,394 @@ +# Lab 4 - OS & Networking: Trace, Debug, and Read the Substrate + +## Environment + +I completed the lab in a disposable Linux container so the Linux networking tools from the task were available. + +```text +Image: golang:1.24-bookworm +Kernel: Linux ae8db4a22f62 6.12.54-linuxkit #1 SMP Tue Nov 4 21:21:47 UTC 2025 aarch64 GNU/Linux +Go: go version go1.24.13 linux/arm64 +Installed tools: iproute2 dnsutils mtr-tiny tcpdump curl procps iptables nftables jq systemd +``` + +QuickNotes startup: + +```bash +ADDR=:8080 DATA_PATH=/tmp/quicknotes-lab4-docker.json SEED_PATH=seed.json go run . +``` + +```text +2026/06/14 14:16:24 quicknotes listening on :8080 (notes loaded: 4) +``` + +Full raw Linux output is also saved in `submissions/lab4-linux-evidence.txt`. + +## Task 1 - Trace a Request End-to-End + +### 1.1 Start QuickNotes and capture + +I captured loopback traffic inside the Linux container: + +```bash +tcpdump -i lo -nn -s 0 -A 'tcp port 8080' -w submissions/lab4-trace.pcap +``` + +```text +tcpdump: listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes +10 packets captured +20 packets received by filter +0 packets dropped by kernel +``` + +Then I sent one request: + +```bash +curl -v -X POST http://localhost:8080/notes \ + -H 'Content-Type: application/json' \ + -d '{"title":"trace me","body":"in flight"}' +``` + +```text +* Trying 127.0.0.1:8080... +* Connected to localhost (127.0.0.1) port 8080 (#0) +> POST /notes HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/7.88.1 +> Accept: */* +> Content-Type: application/json +> Content-Length: 39 +> +} [39 bytes data] +< HTTP/1.1 201 Created +< Content-Type: application/json +< Date: Sun, 14 Jun 2026 14:16:25 GMT +< Content-Length: 93 +< +{"id":5,"title":"trace me","body":"in flight","created_at":"2026-06-14T14:16:25.453003253Z"} +``` + +### 1.2 Decode the capture + +The decoded trace is saved in `submissions/lab4-trace.txt`, and the Linux pcap is saved as `submissions/lab4-trace.pcap`. + +TCP three-way handshake: + +```text +14:16:25.452682 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [S], seq 4278554227, length 0 +14:16:25.452689 IP 127.0.0.1.8080 > 127.0.0.1.47304: Flags [S.], seq 1200757750, ack 4278554228, length 0 +14:16:25.452695 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [.], ack 1, length 0 +``` + +HTTP request: + +```text +14:16:25.452898 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [P.], seq 1:176, ack 1, length 175: HTTP: POST /notes HTTP/1.1 + +POST /notes HTTP/1.1 +Host: localhost:8080 +User-Agent: curl/7.88.1 +Accept: */* +Content-Type: application/json +Content-Length: 39 + +{"title":"trace me","body":"in flight"} +``` + +HTTP response: + +```text +14:16:25.453162 IP 127.0.0.1.8080 > 127.0.0.1.47304: Flags [P.], seq 1:207, ack 176, length 206: HTTP: HTTP/1.1 201 Created + +HTTP/1.1 201 Created +Content-Type: application/json +Date: Sun, 14 Jun 2026 14:16:25 GMT +Content-Length: 93 + +{"id":5,"title":"trace me","body":"in flight","created_at":"2026-06-14T14:16:25.453003253Z"} +``` + +Connection close: + +```text +14:16:25.456667 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [F.], seq 176, ack 207, length 0 +14:16:25.456716 IP 127.0.0.1.8080 > 127.0.0.1.47304: Flags [F.], seq 207, ack 177, length 0 +14:16:25.456730 IP 127.0.0.1.47304 > 127.0.0.1.8080: Flags [.], ack 208, length 0 +``` + +Conclusion: the TCP connection opened cleanly, the request reached QuickNotes, the app returned `201 Created`, and the connection closed normally with FIN/ACK rather than RST. + +### 1.3 Five debugging commands + +#### 1. What is listening? + +```bash +ss -tlnp | grep :8080 +``` + +```text +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=2698,fd=3)) +``` + +Decision: QuickNotes is listening on port `8080`. + +#### 2. Routes from the host/container + +```bash +ip route show +``` + +```text +default via 172.17.0.1 dev eth0 +172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2 +``` + +Decision: the container has a default route through the Docker bridge gateway. The request to `localhost` stays on loopback. + +#### 3. Reachability + +```bash +mtr -rwc 5 localhost +``` + +```text +Start: 2026-06-14T14:16:26+0000 +HOST: ae8db4a22f62 Loss% Snt Last Avg Best Wrst StDev + 1.|-- localhost 0.0% 5 0.0 0.1 0.0 0.2 0.1 +``` + +Decision: localhost is reachable in one hop with no loss. + +#### 4. DNS works + +```bash +dig +short example.com @1.1.1.1 +``` + +```text +172.66.147.243 +104.20.23.154 +``` + +Decision: external DNS resolution through `1.1.1.1` works. + +#### 5. Logs + +```bash +journalctl --user -u quicknotes -n 20 || true +``` + +```text +No journal files were found. +-- No entries -- +``` + +Decision: QuickNotes was started manually with `go run`, not installed as a user systemd service, so there were no journal entries. The process logs were visible in the terminal output. + +### 1.4 If QuickNotes returned 502 + +If QuickNotes returned `502 Bad Gateway`, I would start at the proxy or load balancer because 502 usually means the proxy could not get a valid response from its upstream. My outside-in order would be: confirm that the proxy routes to the expected host and port, check that QuickNotes is listening with `ss -tlnp`, call `/health` directly with `curl -v`, and then inspect service logs for crashes, bind failures, or timeouts. If the direct health check worked, I would look at proxy timeout settings, upstream DNS resolution, and firewall/network rules between the proxy and QuickNotes. + +## Task 2 - Outside-In Debugging on a Broken Deploy + +### 2.1 Broken instance + +To reproduce the broken deploy, I left one QuickNotes process running on `:8080` and started a second copy on the same port: + +```bash +ADDR=:8080 DATA_PATH=/tmp/quicknotes-lab4-broken.json SEED_PATH=seed.json go run . +``` + +```text +2026/06/14 14:16:36 quicknotes listening on :8080 (notes loaded: 4) +2026/06/14 14:16:36 listen: listen tcp :8080: bind: address already in use +exit status 1 +``` + +Root cause: the second process could not bind `:8080` because the first process already owned that port. + +### 2.2 Outside-in chain + +#### 1. Is it running? + +```bash +ps -ef | grep quicknotes +``` + +```text +root 2698 898 0 14:16 ? 00:00:00 /tmp/go-build1352676339/b001/exe/quicknotes +``` + +Decision: one QuickNotes process was still running. + +#### 2. Is it listening? + +```bash +ss -tlnp | grep 8080 +``` + +```text +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=2698,fd=3)) +``` + +Decision: PID `2698` already owned port `8080`. + +#### 3. Reachable from host/container? + +```bash +curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/health +``` + +```text +200 +``` + +Decision: the already-running instance was healthy, so the failed deploy was a bind conflict, not a total app outage. + +#### 4. Firewall blocking? + +```bash +iptables -L -n -v 2>/dev/null || nft list ruleset 2>/dev/null || true +``` + +```text +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 +``` + +Decision: the firewall policy was accepting traffic. Since `/health` returned `200`, the failure was not caused by a firewall block. + +#### 5. DNS? + +```bash +dig +short localhost +``` + +```text +127.0.0.1 +``` + +Decision: `localhost` resolved correctly. + +### 2.3 Repair and re-verify + +I killed the conflicting instance, restarted QuickNotes, and checked `/health`: + +```bash +ADDR=:8080 DATA_PATH=/tmp/quicknotes-lab4-repaired.json SEED_PATH=seed.json go run . +curl -s http://localhost:8080/health +``` + +```text +2026/06/14 14:16:38 quicknotes listening on :8080 (notes loaded: 4) +{"notes":4,"status":"ok"} +``` + +Decision: after removing the port conflict, QuickNotes started and served health checks correctly. + +### 2.4 Mini-postmortem + +The outage was caused by two QuickNotes processes trying to own the same fixed port. This is a systemic failure because port ownership is hidden shared state: a deploy script can start a new process while an old one still exists, or a service manager can retry without cleaning up the previous instance. Better tooling would make the state explicit before deploy: a systemd unit with a clear restart policy, preflight checks for the target port, health checks that distinguish "old instance healthy" from "new instance started", and deployment automation that stops or drains the old process before binding the new one. Nobody needs blame for the bind error; the process model should make double-starts hard to create and easy to diagnose. + +## Bonus - TLS Handshake + +I attempted the bonus task with nginx as the TLS-terminating reverse proxy. QuickNotes still served plain HTTP on `:8080`, and nginx served HTTPS on `:8443` with a self-signed localhost certificate. + +### B.1 HTTPS layer + +I generated a self-signed certificate and configured nginx to proxy HTTPS traffic to QuickNotes: + +```nginx +server { + listen 8443 ssl; + server_name localhost; + ssl_certificate /tmp/lab4-tls/localhost.crt; + ssl_certificate_key /tmp/lab4-tls/localhost.key; + ssl_protocols TLSv1.2 TLSv1.3; + + location / { + proxy_pass http://127.0.0.1:8080; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto https; + } +} +``` + +Listeners: + +```text +LISTEN 0 511 0.0.0.0:8443 0.0.0.0:* users:(("nginx",pid=2661,fd=5)) +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=2686,fd=3)) +``` + +### B.2 Capture the TLS handshake + +```bash +tcpdump -i lo -nn -s 0 -w submissions/lab4-tls.pcap 'tcp port 8443' +curl -vk https://localhost:8443/health +``` + +```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_256_GCM_SHA384 +* Server certificate: +* subject: CN=localhost +* issuer: CN=localhost +< HTTP/1.1 200 OK +{"notes":4,"status":"ok"} +``` + +The TLS capture is saved as `submissions/lab4-tls.pcap`. + +### B.3 Decode the handshake + +I decoded the pcap with `tshark`: + +- `submissions/lab4-tls-clienthello.txt` - ClientHello decode. +- `submissions/lab4-tls-serverhello.txt` - ServerHello decode. +- `submissions/lab4-tls-certificate.txt` - certificate chain from `openssl s_client`. +- `submissions/lab4-tls-summary.txt` - concise summary. +- `submissions/lab4-tls-evidence.txt` - full command output. + +ClientHello highlights: + +```text +Server Name: localhost +Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302) +Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303) +Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301) +Supported Version: TLS 1.3 (0x0304) +Supported Version: TLS 1.2 (0x0303) +Supported Version: TLS 1.1 (0x0302) +Supported Version: TLS 1.0 (0x0301) +``` + +ServerHello highlights: + +```text +Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302) +Extension: supported_versions (len=2) +Supported Version: TLS 1.3 (0x0304) +Key Share Entry: Group: x25519 +``` + +Certificate chain: + +```text +Certificate chain + 0 s:CN = localhost + i:CN = localhost + a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 + v:NotBefore: Jun 14 15:06:32 2026 GMT; NotAfter: Jun 15 15:06:32 2026 GMT + +Verify return code: 18 (self-signed certificate) +``` + +The negotiation step that kills TLS 1.0 and TLS 1.1 is the protocol version negotiation in ServerHello. The client advertised multiple supported versions, including TLS 1.0 and 1.1, but nginx was configured with only `TLSv1.2 TLSv1.3`. The server selected `TLS 1.3 (0x0304)` in the `supported_versions` extension. A client that only supported TLS 1.0 or 1.1 would be rejected before any HTTP request was sent.