<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Yann Pretot</title>
    <link>https://yann.pt/</link>
    <description>Recent content on Yann Pretot</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Fri, 10 May 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://yann.pt/index.xml" rel="self" type="application/rss+xml" />
    <item>
    <title>One Rule Made Our Non-cacheable Cloudflare Traffic 50% Faster</title>
    <link>https://yann.pt/posts/one-rule-cloudflare-traffic-50-percent-faster/</link>
    <pubDate>Fri, 10 May 2024 00:00:00 +0000</pubDate>
    
    <guid>https://yann.pt/posts/one-rule-cloudflare-traffic-50-percent-faster/</guid>
    <description>
        &lt;p&gt;&lt;em&gt;tl;dr: Ensure your requests are eligible for caching (even if responses aren&amp;rsquo;t) so that they use Tiered Cache&amp;rsquo;s faster architecture. For example, you can create a Cache Rule with the &lt;code&gt;Use cache-control header if present, bypass cache if not&lt;/code&gt; action for Edge TTL.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://ttfb.yann.pt&#34;&gt;Checking on&lt;/a&gt; a webapp&amp;rsquo;s performance, I found latency results from farther away countries underwhelming. This app runs on AWS in &lt;code&gt;eu-west-3&lt;/code&gt; (Paris), with Cloudflare in front for DDoS protection and caching of static assets.&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; 
    src=&#34;https://yann.pt/posts/one-rule-cloudflare-traffic-50-percent-faster/images/latency-before.png&#34; 
    alt=&#34;Latencies before&#34; 
     
    width=2674 
    height=&#34;2159&#34;  /&gt;
&lt;em&gt;&lt;center&gt;That&amp;rsquo;s a lot of orange. Orange is slow. Slow is bad.&lt;/center&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The configuration was close to the defaults, and Tiered Cache was enabled (set to &lt;code&gt;Smart Tiered Caching Topology&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id=&#34;an-aside-on-cloudflares-tiered-cache&#34;&gt;
    &lt;a href=&#34;#an-aside-on-cloudflares-tiered-cache&#34; class=&#34;anchor&#34;&gt;
        &lt;svg class=&#34;icon&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34; height=&#34;16&#34; version=&#34;1.1&#34; viewBox=&#34;0 0 16 16&#34; width=&#34;16&#34;&gt;
            &lt;path fill-rule=&#34;evenodd&#34;
                d=&#34;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&#34;&gt;
            &lt;/path&gt;
        &lt;/svg&gt;
    &lt;/a&gt;
    An aside on Cloudflare’s Tiered Cache
&lt;/h2&gt;
&lt;p&gt;Well, Tiered Cache is pretty amazing, and free. According to Cloudflare:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;By enabling Tiered Cache, Cloudflare will dynamically find the single best upper tier for an origin using Argo performance and routing data.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;This practice improves bandwidth efficiency by &lt;strong&gt;limiting the number of data centers that can ask the origin for content&lt;/strong&gt;, reduces origin load, and makes websites more cost-effective to operate.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Also, and this is of importance, Cloudflare carries requests and responses very efficiently between the client data center and the upper tier, which in turn communicates with the origin on cache misses. In fact, way more efficiently than if the client data center stretched a TCP session all around the world to speak directly to the origin. This makes the request-response round trip far faster, even for non-cacheable content, as things like TLS handshakes are way quicker on short distances.&lt;/p&gt;
&lt;p&gt;Reading this, we&amp;rsquo;d think that it applies to all requests. Well, we&amp;rsquo;d be wrong.&lt;/p&gt;
&lt;h2 id=&#34;the-problem&#34;&gt;
    &lt;a href=&#34;#the-problem&#34; class=&#34;anchor&#34;&gt;
        &lt;svg class=&#34;icon&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34; height=&#34;16&#34; version=&#34;1.1&#34; viewBox=&#34;0 0 16 16&#34; width=&#34;16&#34;&gt;
            &lt;path fill-rule=&#34;evenodd&#34;
                d=&#34;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&#34;&gt;
            &lt;/path&gt;
        &lt;/svg&gt;
    &lt;/a&gt;
    The problem
&lt;/h2&gt;
&lt;p&gt;Cloudflare&amp;rsquo;s request processing logic is made of multiple phases each responsible for a specific product like WAF, Rate Limiting or Cache. As I understand it, at or before the beginning of the Cache phase, Cache Rules and the default policy&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; are used to determine whether the request looks like it is for cacheable content. If not, caching is bypassed entirely. This is what happens when you see &lt;code&gt;DYNAMIC&lt;/code&gt; in the &lt;code&gt;CF-Cache-Status&lt;/code&gt; response header. In theory, that&amp;rsquo;s good because it prevents expensive-ish cache lookups and useless processing for requests that have no hope to ever be cached anyway.&lt;/p&gt;
&lt;p&gt;However, as Tiered Cache is part of the Cache phase, it doesn&amp;rsquo;t get to engage for those ineligible requests. Origin requests then come from the Cloudflare data center that received the client request rather than Tiered Cache&amp;rsquo;s upper tier data center. This is suboptimal:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As both Cloudflare and many hosting providers are doing hot potato routing, packets travel freely over the Internet through paths that no one really monitors nor optimizes for performance&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, leading to high latency.&lt;/li&gt;
&lt;li&gt;The client data center might be very far away from the origin, making even the most direct non-congested path tens to hundreds of milliseconds long, which TCP doesn&amp;rsquo;t like very much.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;cache-rules-to-the-rescue&#34;&gt;
    &lt;a href=&#34;#cache-rules-to-the-rescue&#34; class=&#34;anchor&#34;&gt;
        &lt;svg class=&#34;icon&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34; height=&#34;16&#34; version=&#34;1.1&#34; viewBox=&#34;0 0 16 16&#34; width=&#34;16&#34;&gt;
            &lt;path fill-rule=&#34;evenodd&#34;
                d=&#34;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&#34;&gt;
            &lt;/path&gt;
        &lt;/svg&gt;
    &lt;/a&gt;
    Cache Rules to the rescue
&lt;/h2&gt;
&lt;p&gt;Even if we know our content is not cacheable and never will be, we want the client data center to think there&amp;rsquo;s still a chance so that the Cache phase isn&amp;rsquo;t skipped and all&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; traffic goes through Tiered Cache, cold potato-style&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;, only exiting to the origin from the upper tier data center.&lt;/p&gt;
&lt;p&gt;To do that without altering the existing behavior, we created a Cache Rule targeting only requests not covered by the default policy and applied the &lt;code&gt;Use cache-control header if present, bypass cache if not&lt;/code&gt; action for Edge TTL.&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; 
    src=&#34;https://yann.pt/posts/one-rule-cloudflare-traffic-50-percent-faster/images/cache-rule.png&#34; 
    alt=&#34;The cache rule&#34; 
     
    width=1782 
    height=&#34;1674&#34;  /&gt;&lt;/p&gt;
&lt;p&gt;The expression used to match traffic not covered by the default caching policy is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cf&#34; data-lang=&#34;cf&#34;&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;not&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;uri&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;extension&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;7z&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;csv&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;gif&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;midi&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;png&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;tif&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;zip&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;avi&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;doc&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;gz&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;mkv&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;ppt&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;tiff&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;zst&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;avif&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;docx&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;ico&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;mp3&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;pptx&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;ttf&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;apk&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;dmg&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;iso&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;mp4&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;ps&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;webm&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;bin&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;ejs&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;jar&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;ogg&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;rar&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;webp&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;bmp&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;eot&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;jpg&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;otf&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;svg&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;woff&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;bz2&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;eps&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;jpeg&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;pdf&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;svgz&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;woff2&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;class&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;exe&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;js&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;pict&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;swf&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;xls&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;css&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;flac&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;mid&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;pls&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;tar&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;xlsx&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;and&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;uri&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;path&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;ne&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;/robots.txt&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;conclusion&#34;&gt;
    &lt;a href=&#34;#conclusion&#34; class=&#34;anchor&#34;&gt;
        &lt;svg class=&#34;icon&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34; height=&#34;16&#34; version=&#34;1.1&#34; viewBox=&#34;0 0 16 16&#34; width=&#34;16&#34;&gt;
            &lt;path fill-rule=&#34;evenodd&#34;
                d=&#34;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&#34;&gt;
            &lt;/path&gt;
        &lt;/svg&gt;
    &lt;/a&gt;
    Conclusion
&lt;/h2&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; 
    src=&#34;https://yann.pt/posts/one-rule-cloudflare-traffic-50-percent-faster/images/latency-after.png&#34; 
    alt=&#34;Latencies after&#34; 
     
    width=2660 
    height=&#34;2082&#34;  /&gt;
&lt;em&gt;&lt;center&gt;Results vary between measurements, but overall, that&amp;rsquo;s way greener.&lt;/center&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We did get a ~50% decrease in latency from the farther locations, which is pretty good for a single rule change. However, this only works for &lt;code&gt;GET&lt;/code&gt; requests as other HTTP methods are never eligible for caching no matter what.&lt;/p&gt;
&lt;p&gt;It would be awesome if Cloudflare made all requests, cacheable or not, go through Tiered Cache when it&amp;rsquo;s enabled. This behavior would be simpler in my opinion and more aligned with customer expectations, albeit rendering the more advanced Argo Smart Routing a bit less compelling.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;By default, Cloudflare caches resources if they match &lt;a href=&#34;https://developers.cloudflare.com/cache/concepts/default-cache-behavior/#default-cached-file-extensions&#34;&gt;a list of 56 file extensions&lt;/a&gt; (plus &lt;code&gt;/robots.txt&lt;/code&gt;) that are usually static assets (images, CSS, JS, etc.) and the origin response headers don&amp;rsquo;t forbid it.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Cloudflare does monitor origin reachability and try to failover to a secondary path if the natural one goes down &lt;a href=&#34;https://blog.cloudflare.com/orpheus#orpheus-solving-origin-reachability-problems-for-everyone&#34;&gt;thanks to their Orpheus system&lt;/a&gt;. This is however resilience- and not performance-oriented.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;All &lt;code&gt;GET&lt;/code&gt; requests, that is, as other methods are not cacheable at all and will always bypass the Cache phase. To make those faster, you&amp;rsquo;d have to use Argo Smart Routing (which is not so cheap) or build something similar yourself with Workers (which I&amp;rsquo;ve also done and will probably post about later).&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Or, more accurately, lukewarm potato-style, as the traffic between Cloudflare data centers does go traverse the Internet but through, one assumes, monitored and optimized paths. Anyway, the HTTP request is not merely tunneled but transmitted more efficiently instead, so we still win.&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;

    </description>
    </item>
    
    <item>
    <title>A (More) Secure Workstation</title>
    <link>https://yann.pt/posts/a-more-secure-workstation/</link>
    <pubDate>Fri, 15 Sep 2023 00:00:00 +0000</pubDate>
    
    <guid>https://yann.pt/posts/a-more-secure-workstation/</guid>
    <description>
        &lt;p&gt;In a world where CI pipelines are (mostly) run in isolated disposable VMs, where every new workload is containerized and where supply chain attacks are increasingly frequent, I find mind-boggling that a lot of dev/ops workstations are still a nightmarish melting pot of &lt;code&gt;brew&lt;/code&gt;-, &lt;code&gt;npm&lt;/code&gt;- and &lt;code&gt;pip&lt;/code&gt;-installed tools. Those come with tens if not hundreds of dependencies (which invariably cause headaches a few months down the line), and this whole zoo runs in your &lt;code&gt;$HOME&lt;/code&gt; with your own privileges. What&amp;rsquo;s to prevent any of these to go rogue and install a backdoor somewhere in your &lt;code&gt;.zshrc&lt;/code&gt; or anywhere else?&lt;/p&gt;
&lt;p&gt;Two years ago, when I bought my current laptop and started fresh, I decided to try to keep it as clean as possible. The more minimalist the setup, the longer I would go without dealing with dependency hell, python version discrepancies and, hopefully, security issues. It&amp;rsquo;s a Mac, so the ideas here are geared towards macOS, but they should work on any UNIX-like system.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m no security expert, so I won&amp;rsquo;t pretend I have all the answers: this piece is more of an RFC than a how-to, and I&amp;rsquo;d love to hear your thoughts and tips on the matter.&lt;/p&gt;
&lt;h2 id=&#34;the-idea&#34;&gt;
    &lt;a href=&#34;#the-idea&#34; class=&#34;anchor&#34;&gt;
        &lt;svg class=&#34;icon&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34; height=&#34;16&#34; version=&#34;1.1&#34; viewBox=&#34;0 0 16 16&#34; width=&#34;16&#34;&gt;
            &lt;path fill-rule=&#34;evenodd&#34;
                d=&#34;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&#34;&gt;
            &lt;/path&gt;
        &lt;/svg&gt;
    &lt;/a&gt;
    The idea
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Install as few third-party apps as possible&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;pip&lt;/code&gt;, no &lt;code&gt;npm&lt;/code&gt;, no &lt;code&gt;brew&lt;/code&gt;, and certainly no &lt;code&gt;curl | sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Every tool gets its own Docker image, and runs in an ad-hoc disposable container&lt;/li&gt;
&lt;li&gt;Dev projects also run inside containers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I find containers ideal to deal with dev tools, as they&amp;rsquo;re mostly isolated from the host by default and enable the user to define explicit interfaces: share specific environment variables, precise bind mounts (possibly read-only), port mappings, etc. They&amp;rsquo;re also disposable, so you can easily make them single-use to prevent any leak between projects or environments and avoid long-term compromission.&lt;/p&gt;
&lt;h2 id=&#34;how-it-works&#34;&gt;
    &lt;a href=&#34;#how-it-works&#34; class=&#34;anchor&#34;&gt;
        &lt;svg class=&#34;icon&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34; height=&#34;16&#34; version=&#34;1.1&#34; viewBox=&#34;0 0 16 16&#34; width=&#34;16&#34;&gt;
            &lt;path fill-rule=&#34;evenodd&#34;
                d=&#34;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&#34;&gt;
            &lt;/path&gt;
        &lt;/svg&gt;
    &lt;/a&gt;
    How it works
&lt;/h2&gt;
&lt;p&gt;An example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-zsh&#34; data-lang=&#34;zsh&#34;&gt;&lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; aws &lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
    docker run --rm -it &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    -v &lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt;:&lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt; -w &lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    -v &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.aws/config:/root/.aws/config:ro&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    -e AWS_ACCESS_KEY_ID &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    -e AWS_SECRET_ACCESS_KEY &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    -e AWS_SESSION_TOKEN &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    -e AWS_PAGER &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    --entrypoint &lt;span class=&#34;s2&#34;&gt;&amp;#34;/usr/local/bin/aws&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    amazon/aws-cli:2.13.13 &lt;span class=&#34;nv&#34;&gt;$@&lt;/span&gt;
&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This function lives in my &lt;code&gt;~/.zshenv&lt;/code&gt;. It&amp;rsquo;s a small wrapper around &lt;code&gt;docker run&lt;/code&gt;, which runs the AWS CLI from the official image and mimicks the host environment, except it&amp;rsquo;s scoped to the current directory (useful for &lt;code&gt;s3 cp&lt;/code&gt;) and has read-only access to my &lt;code&gt;~/.aws/config&lt;/code&gt; file. The credentials come from &lt;code&gt;craws&lt;/code&gt;, another small tool I wrote that gets vended temporary ones from AWS IAM Identity Center (formerly SSO) and exports them in the current terminal session.&lt;/p&gt;
&lt;p&gt;Every tool I use (&lt;code&gt;ansible&lt;/code&gt;, &lt;code&gt;terraform&lt;/code&gt;, &lt;code&gt;kubectl&lt;/code&gt;&amp;hellip;) has its own function, sometimes with a custom image, and always with as little privileges as possible. For those I use less frequently, there&amp;rsquo;s always &lt;code&gt;docker run --rm -it -v $PWD:$PWD -w $PWD ubuntu bash&lt;/code&gt; to get a shell in a clean temporary Ubuntu container, &lt;code&gt;apt install&lt;/code&gt; what I need and run it.&lt;/p&gt;
&lt;p&gt;The UNIX philosophy of composability still applies as &lt;code&gt;stdout&lt;/code&gt; and &lt;code&gt;stderr&lt;/code&gt; are forwarded: I can pipe one tool&amp;rsquo;s output to another, and to that end, &lt;code&gt;jq&lt;/code&gt; also has its own function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-zsh&#34; data-lang=&#34;zsh&#34;&gt;&lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; jq &lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
    docker run --rm -it --pull never localhost:5000/sw/jq &lt;span class=&#34;nv&#34;&gt;$@&lt;/span&gt;
&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;and Dockerfile:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-Dockerfile&#34; data-lang=&#34;Dockerfile&#34;&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;s&#34;&gt; alpine:3.15&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;RUN&lt;/span&gt; apk add --no-cache jq&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;ENTRYPOINT&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/usr/bin/jq&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The image&amp;rsquo;s tag is prefixed with &lt;code&gt;localhost:5000&lt;/code&gt;, a registry which doesn&amp;rsquo;t exist, to ensure I never pull it from the Docker Hub and only use the locally built one.&lt;/p&gt;
&lt;p&gt;Things sometimes get funky, for example with port mappings, as tools should bind to 0.0.0.0 for Docker to be able to forward them traffic. Example with &lt;code&gt;kubectl&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-zsh&#34; data-lang=&#34;zsh&#34;&gt;&lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; kubectl &lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
    &lt;span class=&#34;c1&#34;&gt;# If command is &amp;#39;kubectl proxy&amp;#39;, bind to 0.0.0.0 and forward port 8001&lt;/span&gt;
    &lt;span class=&#34;nv&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$@&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
    &lt;span class=&#34;nv&#34;&gt;docker_args&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=()&lt;/span&gt;
    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; ! -z &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;proxy&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
        &lt;span class=&#34;nv&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+=(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;--address=0.0.0.0&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
        &lt;span class=&#34;nv&#34;&gt;docker_args&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+=(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;-p&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;127.0.0.1:8001:8001&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
    &lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;

    docker run --rm -it &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    -v &lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;/.kube:/root/.kube &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    -v &lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt;:&lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt; -w &lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;docker_args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[@]&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    localhost:5000/sw/k8s-tools:latest kubectl &lt;span class=&#34;nv&#34;&gt;$args&lt;/span&gt;
&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;whats-next&#34;&gt;
    &lt;a href=&#34;#whats-next&#34; class=&#34;anchor&#34;&gt;
        &lt;svg class=&#34;icon&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34; height=&#34;16&#34; version=&#34;1.1&#34; viewBox=&#34;0 0 16 16&#34; width=&#34;16&#34;&gt;
            &lt;path fill-rule=&#34;evenodd&#34;
                d=&#34;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&#34;&gt;
            &lt;/path&gt;
        &lt;/svg&gt;
    &lt;/a&gt;
    What&amp;#39;s next
&lt;/h2&gt;
&lt;p&gt;While this has been working pretty well for me so far, it&amp;rsquo;s highly custom to fit my own workflow and not very portable. While writing this post, I thought that maybe this could become a project in its own right, with a central repository of &amp;ldquo;recipes&amp;rdquo; (Dockerfiles or images + wrapper code) and some form of CLI to manage them.&lt;/p&gt;
&lt;p&gt;So I&amp;rsquo;ve streamlined what I had a bit, and here it is: &lt;a href=&#34;https://github.com/yapret/toolship&#34;&gt;Toolship is now on GitHub&lt;/a&gt;!&lt;/p&gt;

    </description>
    </item>
    
    <item>
    <title>Where Are the Parisians?</title>
    <link>https://yann.pt/posts/where-are-the-parisians/</link>
    <pubDate>Thu, 23 Feb 2023 00:00:00 +0000</pubDate>
    
    <guid>https://yann.pt/posts/where-are-the-parisians/</guid>
    <description>
        &lt;p&gt;Since I settled in the French capital, I&amp;rsquo;ve always been curious to know what other Parisians were doing. Part FOMO, part simple desire to discover cool places.&lt;/p&gt;
&lt;p&gt;SEO articles recommending the same touristy spots get old quickly. The Snapmap is not very relevant (anymore?), while other social networks give quite a peacemeal view of what&amp;rsquo;s going on: don&amp;rsquo;t expect to hear about a Porte de Versailles conference on a day of protests at République.&lt;/p&gt;
&lt;p&gt;So I decided to build my own map using Open Data: here&amp;rsquo;s &lt;a href=&#34;https://bustlemaps.com&#34;&gt;BustleMaps&lt;/a&gt; 🎉 Updated every minute and accessible from your own browser, it makes you feel the heartbeat of Paris at a glance.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://bustlemaps.com&#34;&gt;&lt;img loading=&#34;lazy&#34; 
    src=&#34;https://yann.pt/posts/where-are-the-parisians/images/bustlemaps-1.png&#34; 
    alt=&#34;BustleMaps&#34; 
     
    width=1380 
    height=&#34;1019&#34;  /&gt;&lt;/a&gt;
&lt;em&gt;&lt;center&gt;The map, as seen on February 10th, 2023, at 10:10 PM&lt;/center&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;how-it-works&#34;&gt;
    &lt;a href=&#34;#how-it-works&#34; class=&#34;anchor&#34;&gt;
        &lt;svg class=&#34;icon&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34; height=&#34;16&#34; version=&#34;1.1&#34; viewBox=&#34;0 0 16 16&#34; width=&#34;16&#34;&gt;
            &lt;path fill-rule=&#34;evenodd&#34;
                d=&#34;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&#34;&gt;
            &lt;/path&gt;
        &lt;/svg&gt;
    &lt;/a&gt;
    How it works
&lt;/h2&gt;
&lt;p&gt;Smoove, which operates the Velib&#39; bike-sharing service, offers a public API that indicates in near real-time the number of bikes available at each station.&lt;/p&gt;
&lt;p&gt;Velib&#39; handles more than 100,000 rides a day on average, between the 1,447 stations in its network. Probably enough to get an idea of what&amp;rsquo;s going on in the city.&lt;/p&gt;
&lt;p&gt;By comparing the number of bikes available in a given station from one minute to the next, we can approximate how many bikes have been taken or returned. Doing so for all stations and accumulating the results over 30-minute windows gets us everything we need to build a pretty heatmap!&lt;/p&gt;
&lt;p&gt;The algorithm is written in Python and runs on an AWS Lambda, triggered every minute by an EventBridge rule. The data is stored in an S3 bucket, and the map is displayed on the client side with MapLibre GL JS.&lt;/p&gt;
&lt;h2 id=&#34;limitations&#34;&gt;
    &lt;a href=&#34;#limitations&#34; class=&#34;anchor&#34;&gt;
        &lt;svg class=&#34;icon&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34; height=&#34;16&#34; version=&#34;1.1&#34; viewBox=&#34;0 0 16 16&#34; width=&#34;16&#34;&gt;
            &lt;path fill-rule=&#34;evenodd&#34;
                d=&#34;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&#34;&gt;
            &lt;/path&gt;
        &lt;/svg&gt;
    &lt;/a&gt;
    Limitations
&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;This is an approximation&lt;/strong&gt;, biased towards transportation and skewed by some uses of Velib&#39;, such as intermodality (bike + train, for example, which boosts areas around train stations at rush hour). From a sociological point of view, it is centered on cyclists who use Velib&#39;, so quite far from representing the entire population.&lt;/p&gt;
&lt;p&gt;The map would probably be much more interesting with data from Google Maps or CityMapper, or those collected by a data broker for advertising targeting or even those contained in the HLR of a major mobile carrier. But these are very valuable datasets, which are therefore not available.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bike rebalancing operations&lt;/strong&gt; across stations bias the figures: a station being refilled gives the impression that the area is more lively than in reality. Algorithmic adjustments limit the impact but do not completely curb it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The number of slots in a station is limited&lt;/strong&gt;: if a station is full, cyclists cannot stop there, which suggests that the area is less frequented than it really is. Some code partially corrects the problem, and we can assume that these trips end at other nearby stations.&lt;/p&gt;
&lt;h2 id=&#34;the-future&#34;&gt;
    &lt;a href=&#34;#the-future&#34; class=&#34;anchor&#34;&gt;
        &lt;svg class=&#34;icon&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34; height=&#34;16&#34; version=&#34;1.1&#34; viewBox=&#34;0 0 16 16&#34; width=&#34;16&#34;&gt;
            &lt;path fill-rule=&#34;evenodd&#34;
                d=&#34;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&#34;&gt;
            &lt;/path&gt;
        &lt;/svg&gt;
    &lt;/a&gt;
    The future
&lt;/h2&gt;
&lt;p&gt;A few ideas for further development:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Display daily timelapses&lt;/li&gt;
&lt;li&gt;Allow to consult the map at a specific point in the past&lt;/li&gt;
&lt;li&gt;Add other data sources to reduce bias and better detect areas of high activity&lt;/li&gt;
&lt;li&gt;Add other cities in France and around the world&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the meantime, I have walks to do :-)&lt;/p&gt;

    </description>
    </item>
    
    <item>
    <title>Measuring TTFB Worldwide</title>
    <link>https://yann.pt/posts/measuring-ttfb-worldwide/</link>
    <pubDate>Thu, 27 Oct 2022 00:00:00 +0000</pubDate>
    
    <guid>https://yann.pt/posts/measuring-ttfb-worldwide/</guid>
    <description>
        &lt;p&gt;While I was trying to assess HTTP latency around the world for a small project I&amp;rsquo;ve yet to talk about, I grew frustrated by the limits of the online tool I was using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It was slow&lt;/strong&gt; because it collects results from all tested locations before starting to show them to the client (with a slow animation, on top of that)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It didn&amp;rsquo;t work very well&lt;/strong&gt;: a third of my tests were failing for no known reason&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It had quotas&lt;/strong&gt; per 12h period, preventing me to do all the experiments I needed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That was enough to prompt me to start rebuilding the tool my way. As luck would have it, I was at the same time playing with Cloudflare Workers, which I only had been watching from afar until then. This little project would be a good way to explore Workers further.&lt;/p&gt;
&lt;p&gt;A few hours, a bit of JS, and a drop of Bootstrap later, here&amp;rsquo;s &lt;a href=&#34;https://ttfb.yann.pt&#34;&gt;TTFB Tester&lt;/a&gt;, free for all to use 🎉&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; 
    src=&#34;https://yann.pt/posts/measuring-ttfb-worldwide/images/ttfb-tester.png&#34; 
    alt=&#34;TTFB Tester&#34; 
     
    width=1380 
    height=&#34;1039&#34;  /&gt;
Of course, when the CDN&amp;rsquo;s cache isn&amp;rsquo;t warm, it&amp;rsquo;s not particularly pretty. Another example:
&lt;img loading=&#34;lazy&#34; 
    src=&#34;https://yann.pt/posts/measuring-ttfb-worldwide/images/ttfb-tester-2.png&#34; 
    alt=&#34;TTFB Tester 2&#34; 
     
    width=1380 
    height=&#34;1039&#34;  /&gt;&lt;/p&gt;
&lt;p&gt;Tech-wise, until I expand on the architecture, here are a few remedies to the problems I had with the other tool:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Slowness&lt;/strong&gt;: Rather than a lengthy HTTP call waiting for results from all locations, every test run opens a WebSocket connection to the orchestrator Cloudflare Worker. This Worker then launches tests concurrently from the 28 datacenter locations and gradually sends results to the client through the WebSocket, as soon as they arrive.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reliability issues&lt;/strong&gt;: It&amp;rsquo;s hard to pinpoint why tests failed on the other tool. Mine seems to work more reliably in general, maybe because each location is independent: if one fails to run the test&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, it doesn&amp;rsquo;t affect the others, its line just disappears from the results table.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quotas&lt;/strong&gt;: Of course, when you&amp;rsquo;re the one in charge of limits, things get a lot easier on this part. They are less restrictive here, mainly because the serverless architecture I&amp;rsquo;m using entails a virtually null cost as long as usage stays at the level I&amp;rsquo;m predicting.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If a picture is worth a thousand words, a trial is probably ten times more&amp;hellip; Why don&amp;rsquo;t you &lt;a href=&#34;https://ttfb.yann.pt&#34;&gt;take the tester for a spin&lt;/a&gt; yourself? Or use it for real needs, for that matter: that&amp;rsquo;s what it&amp;rsquo;s here for!&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;It happens (very) often for a few locations because of the way the tests are run at this time. Those locations still stay in the pool because the nature of the tool means that their results are still good to take, even if they&amp;rsquo;re sporadic: here, I&amp;rsquo;m looking for comprehensiveness more than reliability of individual locations. It&amp;rsquo;s also a trade-off I&amp;rsquo;m willing to make for now, in exchange for the tool costing virtually nothing to run. This indeed happened between the two screenshots above, as Toronto left and Madrid appeared.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;

    </description>
    </item>
    
    <item>
    <title>Prevent Indexing of *.cloudfront.net URLs</title>
    <link>https://yann.pt/posts/prevent-indexing-of-cloudfront-net-urls/</link>
    <pubDate>Sat, 23 Apr 2022 00:00:00 +0000</pubDate>
    
    <guid>https://yann.pt/posts/prevent-indexing-of-cloudfront-net-urls/</guid>
    <description>
        &lt;p&gt;When creating a CloudFront distribution, AWS assigns it a generic domain name, such as &lt;code&gt;d2d7frhciy9p7h.cloudfront.net&lt;/code&gt;. You can add your own domain as an alias, but the original one cannot be disabled. Your website is then available using either your custom domain or the default &lt;code&gt;.cloudfront.net&lt;/code&gt; endpoint.&lt;/p&gt;
&lt;p&gt;From a security standpoint, this is not a big deal: this endpoint doesn&amp;rsquo;t give access to more data than your own domain. However, things start to get messy when search engines add those URLs to their index:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Users can stumble upon these plain-looking links when searching for your content, which isn&amp;rsquo;t good for their experience&lt;/li&gt;
&lt;li&gt;Search engines like Google can detect that the same content is served from multiple domains, and penalize you for that&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Internet has various answers to that problem. For example, if your origin is &lt;code&gt;Host&lt;/code&gt; header aware, you could make sure CloudFront forwards the &lt;code&gt;Host&lt;/code&gt; header and then configure your web server to only answer to requests with the right &lt;code&gt;Host&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, if you don&amp;rsquo;t want this kind of coupling or if you&amp;rsquo;re using simpler origins like S3 buckets, there&amp;rsquo;s another way to do it: &lt;strong&gt;CloudFront Functions&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To block access through the &lt;code&gt;.cloudfront.net&lt;/code&gt; domain, first create a CloudFront Function with the following code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;handler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
    &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;request&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
    &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;host&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;headers&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;host&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;

    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;host&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;endsWith&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;.cloudfront.net&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
            &lt;span class=&#34;nx&#34;&gt;statusCode&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;403&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
            &lt;span class=&#34;nx&#34;&gt;statusDescription&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Forbidden&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
            &lt;span class=&#34;nx&#34;&gt;headers&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
                &lt;span class=&#34;s1&#34;&gt;&amp;#39;x-reason&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Access via .cloudfront.net distribution domain name forbidden&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
            &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
        &lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can then attach that function to &lt;code&gt;Viewer request&lt;/code&gt; events in your distribution&amp;rsquo;s Behaviors:
&lt;img loading=&#34;lazy&#34; 
    src=&#34;https://yann.pt/posts/prevent-indexing-of-cloudfront-net-urls/images/behavior_function_association.png&#34; 
    alt=&#34;CloudFront Behavior function associations example&#34; 
     
    width=1658 
    height=&#34;714&#34;  /&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s it! Now, when trying to access your &lt;code&gt;.cloudfront.net&lt;/code&gt; domain, CloudFront should return HTTP 403 errors:
&lt;img loading=&#34;lazy&#34; 
    src=&#34;https://yann.pt/posts/prevent-indexing-of-cloudfront-net-urls/images/result_403.png&#34; 
    alt=&#34;Result example&#34; 
     
    width=1676 
    height=&#34;404&#34;  /&gt;&lt;/p&gt;
&lt;p&gt;A drawback is that you can&amp;rsquo;t set a response body with CloudFront Functions, so the user gets a blank page (which shouldn&amp;rsquo;t be a problem as nobody&amp;rsquo;s supposed to reach that domain in the first place).&lt;/p&gt;
&lt;p&gt;Cost-wise, the free tier currently includes 2M function executions per month. After that, you&amp;rsquo;ll get billed $0.10 per million requests.&lt;/p&gt;

    </description>
    </item>
    
    <item>
    <title>Custom Domains for Lambda Function URLs</title>
    <link>https://yann.pt/posts/custom-domain-for-lambda-function-url/</link>
    <pubDate>Thu, 14 Apr 2022 00:00:00 +0000</pubDate>
    
    <guid>https://yann.pt/posts/custom-domain-for-lambda-function-url/</guid>
    <description>
        &lt;p&gt;AWS has recently released a breakthrough feature for Lambda: Function URLs. You tick the box, you&amp;rsquo;re given an URL, and you can then invoke your Lambda function just by making requests to that URL. I think that for the right use-cases, it&amp;rsquo;s a great addition, especially given the complexity of setting up an API Gateway without using third-party Terraform modules, where you&amp;rsquo;d have to worry about IAM permissions, logs, CORS, etc.&lt;/p&gt;
&lt;p&gt;Lambda URLs indeed have the following advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They&amp;rsquo;re free (API Gateways make you pay per request)&lt;/li&gt;
&lt;li&gt;They&amp;rsquo;re (very) easy to use&lt;/li&gt;
&lt;li&gt;They don&amp;rsquo;t have a time limit other than your Lambda&amp;rsquo;s (which means requests can take up to 15 minutes)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But there&amp;rsquo;s a slight inconvenience: as &lt;a href=&#34;https://news.ycombinator.com/item?id=30937899&#34;&gt;users noticed&lt;/a&gt;, you&amp;rsquo;re given a long, meaningless URL that can&amp;rsquo;t be customized nor changed.&lt;/p&gt;
&lt;h2 id=&#34;the-solution&#34;&gt;
    &lt;a href=&#34;#the-solution&#34; class=&#34;anchor&#34;&gt;
        &lt;svg class=&#34;icon&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34; height=&#34;16&#34; version=&#34;1.1&#34; viewBox=&#34;0 0 16 16&#34; width=&#34;16&#34;&gt;
            &lt;path fill-rule=&#34;evenodd&#34;
                d=&#34;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&#34;&gt;
            &lt;/path&gt;
        &lt;/svg&gt;
    &lt;/a&gt;
    The Solution
&lt;/h2&gt;
&lt;p&gt;Annoyed users, rejoice! You can front your Lambda with CloudFront using your own beautiful domain name. That&amp;rsquo;s arguably easier than setting up an API Gateway, bumps the runtime limit from 30 to 60 seconds&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; and you get to use the included customizable caching, among other things. You lose some of the benefits I&amp;rsquo;ve outlined before, though.&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; 
    src=&#34;https://yann.pt/posts/custom-domain-for-lambda-function-url/images/lambda_url_with_a_custom_domain.png&#34; 
    alt=&#34;Lambda URL with a custom domain&#34; 
     
    width=980 
    height=&#34;216&#34;  /&gt;&lt;/p&gt;
&lt;p&gt;To do that, you&amp;rsquo;ll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your Lambda function and its Function URL, in whichever region you want&lt;/li&gt;
&lt;li&gt;An ACM certificate for your domain or subdomain in the &lt;code&gt;us-east-1&lt;/code&gt; region&lt;/li&gt;
&lt;li&gt;A CloudFront distribution, with &lt;code&gt;Host&lt;/code&gt; header forwarding &lt;strong&gt;disabled&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Route53 ALIAS A and AAAA records pointing to your distribution (or a third-party DNS CNAME)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&amp;rsquo;ve already set up CloudFront distributions before, the most tricky part might be disabling &lt;code&gt;Host&lt;/code&gt; header forwarding: you can&amp;rsquo;t ignore specific client headers in CloudFront, so you have to whitelist those that you &lt;strong&gt;do&lt;/strong&gt; want to send to Lambda in your Origin Request Policy, or send none at all. For example:&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; 
    src=&#34;https://yann.pt/posts/custom-domain-for-lambda-function-url/images/cf-originrequest.png&#34; 
    alt=&#34;CloudFront Origin Request Policy configuration&#34; 
     
    width=1654 
    height=&#34;628&#34;  /&gt;&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;And even more if you ask AWS Support for a quota increase.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;

    </description>
    </item>
    
    <item>
    <title>Hi there!</title>
    <link>https://yann.pt/posts/hi-there/</link>
    <pubDate>Sun, 20 Feb 2022 00:00:00 +0000</pubDate>
    
    <guid>https://yann.pt/posts/hi-there/</guid>
    <description>
        &lt;p&gt;I&amp;rsquo;m &lt;del&gt;Janet&lt;/del&gt; Yann.&lt;/p&gt;
&lt;p&gt;This is my new, shiny corner of the Web. I&amp;rsquo;m French, currently 22 and as passionate about IT and tech as I&amp;rsquo;ve ever been.  While I have no plans to limit the scope of the content I&amp;rsquo;ll post here, there&amp;rsquo;s a high chance that most of it will be tech-related.&lt;/p&gt;
&lt;p&gt;Although I&amp;rsquo;ve already posted pieces here and there over the past few years, there wasn&amp;rsquo;t a place to glue them together&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. I&amp;rsquo;ve also been willing to write about a thing or two during the past few months, but didn&amp;rsquo;t feel like publishing on yet another Medium blog. So, here we are!&lt;/p&gt;
&lt;h2 id=&#34;technical-bits-for-nerds-like-me&#34;&gt;
    &lt;a href=&#34;#technical-bits-for-nerds-like-me&#34; class=&#34;anchor&#34;&gt;
        &lt;svg class=&#34;icon&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34; height=&#34;16&#34; version=&#34;1.1&#34; viewBox=&#34;0 0 16 16&#34; width=&#34;16&#34;&gt;
            &lt;path fill-rule=&#34;evenodd&#34;
                d=&#34;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&#34;&gt;
            &lt;/path&gt;
        &lt;/svg&gt;
    &lt;/a&gt;
    Technical bits for nerds like me
&lt;/h2&gt;
&lt;p&gt;This website&amp;rsquo;s architecture seems boring — even if I find its elegance fascinating — which is kind of its whole point:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An Amazon S3 bucket configured for static website hosting&lt;/li&gt;
&lt;li&gt;A Cloudfront distribution&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gohugo.io&#34;&gt;Hugo&lt;/a&gt; as a static site generator&lt;/li&gt;
&lt;li&gt;A custom theme, based on &lt;a href=&#34;https://github.com/WingLim/hugo-tania&#34;&gt;WingLim&amp;rsquo;s re-implementation&lt;/a&gt; of Tania Rascia&amp;rsquo;s &lt;a href=&#34;https://www.taniarascia.com&#34;&gt;beautiful website&lt;/a&gt; (many thanks!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Why so? Because I&amp;rsquo;ve already wrestled with blogging in the past, and WordPress&amp;rsquo;s bells, whistles and maintenance needs or Ghost&amp;rsquo;s resource consumption meant those previous initiatives didn&amp;rsquo;t stand the test of time.&lt;/p&gt;
&lt;p&gt;In IT, minimalism and simplicity often make for very robust solutions. Here, they also enable my small blog to be essentially free, extremely secure and lightning fast.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;d be right to point out that I could have used Netlify, GitHub Pages or the likes, but I try to avoid free riding, and I already had custom Terraform templates that make the whole thing a breeze on AWS.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; Turns out this wasn&amp;rsquo;t elegant enough as the site now runs out of Cloudflare Pages, which does look a fair bit like Netlify :-) This got me more automated builds/deployments and simpler infrastructure, plus I do pay a Cloudflare invoice every month so still no free-riding.&lt;/p&gt;
&lt;h2 id=&#34;credit-where-its-due&#34;&gt;
    &lt;a href=&#34;#credit-where-its-due&#34; class=&#34;anchor&#34;&gt;
        &lt;svg class=&#34;icon&#34; aria-hidden=&#34;true&#34; focusable=&#34;false&#34; height=&#34;16&#34; version=&#34;1.1&#34; viewBox=&#34;0 0 16 16&#34; width=&#34;16&#34;&gt;
            &lt;path fill-rule=&#34;evenodd&#34;
                d=&#34;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&#34;&gt;
            &lt;/path&gt;
        &lt;/svg&gt;
    &lt;/a&gt;
    Credit where it&amp;#39;s due
&lt;/h2&gt;
&lt;p&gt;Even if I&amp;rsquo;ve encountered many blogs during my countless hours browsing HN or Twitter, some of them have proven particularly inspiring, namely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.taniarascia.com&#34;&gt;Tania Rascia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stanislas.blog&#34;&gt;Stan&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.joelonsoftware.com&#34;&gt;Joel on Software&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Well, here&amp;rsquo;s to writing and elegant solutions!&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Especially since Twitter closed my previous account when I tried to make my birth date right&amp;hellip; Oops.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;

    </description>
    </item>
    
  </channel>
</rss>
