I write stuff here.

I'm a teapot

January 08, 2024


I love tea… It’s not only my prefered source of caffine, it’s also a good excuse to have tiny sandwiches and pastries like this excessive high tea I had at the Delft Blue Museum:

Maybe because of my tea addiction, one of my favorite “easter eggs” in networking is the 418 status code. The teapot from earlier was actually from Google’s teapot page. If you curl it with the -v (verbose) flag you’ll actually get (a very secure) 418 response back:

❯ curl https://www.google.com/teapot -v
* Host www.google.com:443 was resolved.
* IPv6: 2607:f8b0:4005:814::2004
* IPv4: 142.251.46.164
*   Trying [2607:f8b0:4005:814::2004]:443...
* Connected to www.google.com (2607:f8b0:4005:814::2004) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / x25519 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=www.google.com
*  start date: Nov 20 08:09:47 2023 GMT
*  expire date: Feb 12 08:09:46 2024 GMT
*  subjectAltName: host "www.google.com" matched cert's "www.google.com"
*  issuer: C=US; O=Google Trust Services LLC; CN=GTS CA 1C3
*  SSL certificate verify ok.
*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha384WithRSAEncryption
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://www.google.com/teapot
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: www.google.com]
* [HTTP/2] [1] [:path: /teapot]
* [HTTP/2] [1] [user-agent: curl/8.5.0]
* [HTTP/2] [1] [accept: */*]
> GET /teapot HTTP/2
> Host: www.google.com
> User-Agent: curl/8.5.0
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
< HTTP/2 418

If you keep digging down this teapot rabbit hole, there’s also an http version of the teapot page (http://www.google.com/teapot) that you can hit as well. However this still uses HTTP/1.1 so you’ll get a slightly different response back:

❯ curl http://www.google.com/teapot -v
* Host www.google.com:80 was resolved.
* IPv6: 2607:f8b0:4005:814::2004
* IPv4: 142.251.46.164
*   Trying [2607:f8b0:4005:814::2004]:80...
* Connected to www.google.com (2607:f8b0:4005:814::2004) port 80
> GET /teapot HTTP/1.1
> Host: www.google.com
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 418 I'm a Teapot

The same behavior happens when you hit http://httpbin.org/status/418 vs http://httpbin.org/status/418 except you get a cute ascii teapot in the response body:

❯ curl http://httpbin.org/status/418 -v
* Host httpbin.org:80 was resolved.
* IPv6: (none)
* IPv4: 44.197.233.190, 3.224.157.95, 184.73.216.86, 52.206.94.89
*   Trying 44.197.233.190:80...
* Connected to httpbin.org (44.197.233.190) port 80
> GET /status/418 HTTP/1.1
> Host: httpbin.org
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 418 I'M A TEAPOT
< Date: Mon, 08 Jan 2024 20:14:54 GMT
< Content-Length: 135
< Connection: keep-alive
< Server: gunicorn/19.9.0
< x-more-info: http://tools.ietf.org/html/rfc2324
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
<

    -=[ teapot ]=-

       _...._
     .'  _ _ `.
    | ."` ^ `". _,
    \_;`"---"`|//
      |       ;/
      \_     _/
        `"""`

One thing you might notice is the text in the HTTP/1.1 protocols is slightly different (Google’s calm, adult “I’m a Teapot” vs. httpbin’s aggressive “I’M A TEAPOT”). The reason for this different is you can actually set the available http status codes to whatever you want in HTTP/1.1. However in HTTP/2.2 the 418 status code is now dedicated as unused but not assignable since so many people have been using the 418 for testing and it exists else where in the wild (See the Save 418 Movement).

So how what does 418 have to do with teapots? Backstory time…

The HTTP 418: I'm a teapot client error response code comes from an April Fool’s joke. In 1998, RFC 2324 defined a communication protocol called Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0) designed for “controlling, monitoring and diagnosing coffee pots”. Logically any attempt to brew coffee with a teapot should result in the error code “418 I’m a teapot”. There have even been implementations of the HTCPC protocol such as http://error418.net/ (although they intentionally use a teapot and raspberry pi instead of a coffee pot to highlight the 418 response).

Well, back in October 2023, Marino Wijay and I had some fun hacking with Istio and Raspberry Pis as part of Kubecon NA’s colocated event, Kubernetes on Edge Day. Our talk was aptly titled Pi in the Sky: Onboarding Edge Workloads Into the Service Mesh! and we onboarded a bare-metal Raspberry Pi into the Istio mesh by running the ambient ztunnel directly on the Pi.

We only had thirty minutes to give our lightning talk so some of our demo needed to be cut. One of those demos was our mTLS secured teapot onboarded onto the mesh.

As part of our demo, we use a MSNSwitch to control outlets remotely. This part was honestly a little hacky because I ended up not being able to get the secure requests to work over the MSNSwitch apis, so I ended up having to inspect the MSNSwitch source code to figure out how requests on the admin panel were being made. Then I scrapped the cookie from the login page and made the call directly with the cookie in the header like you see on the GitHub source code.

Then I wrapped all of the MSNSwitch APIs with a simple Flask webserver. I ran the server on the Raspberry Pi that was connected to the MSNSwitch:

sudo python3 ./msn_switch_server/switch_app.py 

This has serveral paths:

switchOne/on
switchOne/off 
switchOne/toggle
switchTwo/on
switchTwo/off 
switchTwo/toggle

This will run on port 80 and will be reachable via:

http://<raspberry-pi>:80/switchTwo/on

On the cluster side, I needed to create a Kubernetes service that we could use to apply Istio policies to:

kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: teapot-pi
  namespace: pi-namespace
  labels:
    app: teapot-pi
spec:
  ports:
  - port: 80
    name: http-pi
    targetPort: 80
  selector:
    app: "${PI_APP}"
EOF

Now for the fun Istio part- we want to define the 418 status code without implementing HTCPCP. Istio’s VirtualServices defines a set of traffic routing rules and configuration to apply when a host is addressed. One of the configurations a user can make is adding HTTPFaultInjection to test the resiliency of a system to certain failure codes.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: teapot-app-fault-injection
  namespace: pi-namespace
spec:
  exportTo: 
  - "*"
  hosts:
  - teapot-pi
  http:
  - match:
    - headers:
        tea-drinker:
          exact: nina
    headers:
     response:
      add:
       test: "I'm a teapot!"
    fault:
     abort:
        httpStatus: 418
        percentage:
          value: 100
    route:
    - destination:
        host: teapot-pi
        port:
          number: 80

In this example we’ll also add some header manipulation to injection a simple “test: I’m a teapot!” header. Both the header manipulation and fault injection is only triggered when the tea-drinker: nina header is provided. All other requests will go through as expected.

I applied this policy with kubectl apply in the namespace the Kubernetes service is defined in.

Finally I could curl with this and turn on the teapot:

curl teapot-pi.pi-namespace:80/switchTwo/on

However, adding the -H "tea-drinker: nina" header results in a fault injection with the 418 status:

curl -H "tea-drinker: nina" teapot-pi.pi-namespace:80/switchOne/on -v 

1/31/2024 Update!*

I finally got a chance to show my ✨ambient✨ teapot in action with Peter in Solo.io’s weekly Hoot webinar!