<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Engineering Practice · Brennenstuhl on Security</title><link>https://www.janbrennenstuhl.eu/topics/engineering-practice/</link><description>Notes from running software: cloud infrastructure, European hosting, tooling and the occasional migration.</description><language>en-GB</language><copyright>Original writing CC BY 4.0, Jan Brennenstuhl</copyright><atom:link href="https://www.janbrennenstuhl.eu/topics/engineering-practice/index.xml" rel="self" type="application/rss+xml"/><item><title>Goodbye Big Tech: My European JAMstack Migration</title><link>https://www.janbrennenstuhl.eu/european-jamstack/</link><guid isPermaLink="true">https://www.janbrennenstuhl.eu/european-jamstack/</guid><pubDate>Sun, 06 Jul 2025 00:00:00 +0200</pubDate><description>&lt;p&gt;I recently migrated my personal JAMstack projects from Github and Netlify to European alternatives. In this article, I&amp;rsquo;ll share which European service providers I went for and why I sleep much better now. Before we begin, though, let&amp;rsquo;s quickly review what we mean when we use the term &lt;em&gt;JAMstack&lt;/em&gt;!&lt;/p&gt;
&lt;aside class="callout"&gt;&lt;p class="callout-title"&gt;Update, October 2026: this blog now lives on GitHub&lt;/p&gt;I moved the source of this blog from Codeberg to &lt;a href="https://github.com/jbspeakr/janbrennenstuhl.eu" title="My blog sources on GitHub" class="ext"&gt;GitHub&lt;/a&gt;, and GitHub Actions now runs the deployment. In July 2026, Codeberg&amp;rsquo;s members voted to &lt;a href="https://blog.codeberg.org/protecting-our-floss-commons-from-llms.html" title="Codeberg: Protecting our FLOSS commons from LLMs" class="ext"&gt;exclude LLM-heavy projects&lt;/a&gt; from the platform: projects that LLM agents create autonomously, projects written and maintained with heavy use of LLMs, and projects closely tied to the LLM ecosystem. I write and maintain this blog with a lot of help from coding agents, so it belongs in that group. I respect Codeberg&amp;rsquo;s decision. Bunny CDN and Plausible Analytics stay as described below.&lt;/aside&gt;
&lt;h2 id="what-is-jamstack"&gt;What is JAMstack? &lt;a class="heading-anchor" href="#what-is-jamstack" aria-label="Link to section: What is JAMstack?"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A JAMstack is a software architectural approach that is solely based on frontend JavaScript, APIs and Markup (typically generated by a static site generator). This blog is an excellent example of a JAMstack website, as the client-side contains all the application logic (primarily search), while the backbone is composed entirely of static files. There is no classical database or backend application – everything is static files delivered to user agents. Following this architectural design yields significant benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;minimal attack surface,&lt;/li&gt;
&lt;li&gt;effortless maintainability,&lt;/li&gt;
&lt;li&gt;blazingly fast performance, and&lt;/li&gt;
&lt;li&gt;easy scalability.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The term JAMstack &lt;a href="https://vimeo.com/163522126" title="Mathias Biilmann: The New Front-end Stack. Javascript, APIs and Markup" class="ext"&gt;was initially coined&lt;/a&gt; by the US SaaS provider Netlify. They and Github Pages are two of the most prominent service providers in that ecosystem. However, there are European alternatives.&lt;/p&gt;
&lt;h2 id="my-european-jamstack"&gt;My European JAMstack &lt;a class="heading-anchor" href="#my-european-jamstack" aria-label="Link to section: My European JAMstack"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For my European JAMstack setup that is based on the excellent static site generator &lt;a href="https://gohugo.io/" title="Hugo: The world’s fastest framework for building websites" class="ext"&gt;Hugo&lt;/a&gt;, I looked for service providers that not only provide great service but also support data privacy and open-source principles, where applicable.&lt;/p&gt;
&lt;h3 id="codeberg-my-new-home-for-code"&gt;Codeberg: My New Home for Code &lt;a class="heading-anchor" href="#codeberg-my-new-home-for-code" aria-label="Link to section: Codeberg: My New Home for Code"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For code hosting and deployment, I opted for &lt;a href="https://codeberg.org" title="Codeberg: European Code Hosting" class="ext"&gt;Codeberg&lt;/a&gt;. Github, while ubiquitous, is a US-based company owned by Microsoft, and I wanted to move my repositories to a European alternative. Codeberg is a non-profit organization based in Germany dedicated to providing a free and open-source Git code hosting platform. It&amp;rsquo;s powered by Forgejo, which in turn is &lt;a href="https://forgejo.org/compare-to-gitea/" title="Comparison: Forgejo vs Gitea" class="ext"&gt;a hard-fork of Gitea&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Codeberg.org offers a familiar interface for anyone used to Github and Github Actions. It is incredibly reassuring to know that my code resides on a community-driven and open-source platform rather than a &amp;ldquo;Big Tech&amp;rdquo; for-profit one.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;→ The source files of this blog have moved to &lt;a href="https://github.com/jbspeakr/janbrennenstuhl.eu" title="My blog sources on GitHub" class="ext"&gt;GitHub&lt;/a&gt;. The update at the top of this article explains why.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="bunny-cdn-fast-and-european-hosting"&gt;Bunny CDN: Fast and European Hosting &lt;a class="heading-anchor" href="#bunny-cdn-fast-and-european-hosting" aria-label="Link to section: Bunny CDN: Fast and European Hosting"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Replacing Netlify was a crucial step, as it used to handle the deployment and hosting of my static files. Based in Slovenia, &lt;a href="https://bunny.net/" title="Bunny.net: European edge platform" class="ext"&gt;Bunny CDN&lt;/a&gt; offers amazing CDN capabilities that easily outperform Netlify. It&amp;rsquo;s a super robust and comprehensible content delivery platform and an ideal solution for hosting static sites. Their global network ensures excellent performance with the added benefit of being a European company.&lt;/p&gt;
&lt;p&gt;Bunny&amp;rsquo;s user interfaces are generally easy to use, and their prices are reasonable – I am paying single digit Euros every month for serving all my projects. Knowing my websites are hosted with a European-headquartered provider, with a clear focus on GDPR compliance, significantly contributes to my peace of mind.&lt;/p&gt;
&lt;p&gt;As JAMstack deployment essentially means triggering a static site generator build process and copying the resulting static files onto a file server or CDN, it was straightforward to replace Netlify builds with Forgejo Actions provided by Codeberg. The resulting &lt;a href="https://codeberg.org/brennenstuhl/janbrennenstuhl.eu/src/branch/main/.forgejo/workflows/deploy.yml" title="My Forgejo Actions config" class="ext"&gt;Forgejo Actions deployment config&lt;/a&gt; is almost identical to a Github Actions deploy job.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;→ I began sharing some of my Bunny onboarding learnings as part of &lt;a href="https://www.janbrennenstuhl.eu/topics/engineering-practice/" title="EuroStack &amp;amp; European Alternatives"&gt;my Eurostack posts&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="plausible-analytics-privacy-first-insights"&gt;Plausible Analytics: Privacy-First Insights &lt;a class="heading-anchor" href="#plausible-analytics-privacy-first-insights" aria-label="Link to section: Plausible Analytics: Privacy-First Insights"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Website analytics are an integral part of understanding user engagement. Using a third-party analytics provider fits nicely into the JAMstack architecture. I replaced Google Analytics with &lt;a href="https://plausible.io/" title="European Web Analytics" class="ext"&gt;Plausible Analytics&lt;/a&gt; already years ago. Based in Estonia, Plausible offers a lightweight, open-source, and privacy-focused analytics solution.&lt;/p&gt;
&lt;p&gt;Unlike &amp;ldquo;Big Tech&amp;rdquo; analytics solutions, it doesn&amp;rsquo;t use cookies and is fully GDPR-compliant by default. This means I can gather valuable insights into my website&amp;rsquo;s performance without compromising on user privacy or worrying about complex consent banners. It&amp;rsquo;s a testament to how privacy-conscious alternatives can deliver powerful functionality.&lt;/p&gt;
&lt;h2 id="jamstack-migration--service-responsibilities"&gt;JAMstack Migration &amp;amp; Service Responsibilities &lt;a class="heading-anchor" href="#jamstack-migration--service-responsibilities" aria-label="Link to section: JAMstack Migration &amp;amp; Service Responsibilities"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class="table-scroll" tabindex="0" role="region" aria-label="Table 1"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th scope="col" style="text-align: left"&gt;Service Responsibility&lt;/th&gt;&lt;th scope="col" style="text-align: left"&gt;Previous Provider&lt;/th&gt;&lt;th scope="col" style="text-align: left"&gt;New Provider&lt;/th&gt;&lt;th scope="col" style="text-align: left"&gt;Key Benefit of New Provider&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: left"&gt;&lt;strong&gt;Code Hosting&lt;/strong&gt;&lt;/td&gt;&lt;td style="text-align: left"&gt;Github (USA)&lt;/td&gt;&lt;td style="text-align: left"&gt;Codeberg (Germany)&lt;/td&gt;&lt;td style="text-align: left"&gt;Non-profit, open-source, privacy-friendly, community-driven&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="text-align: left"&gt;&lt;strong&gt;Static Site Deployment&lt;/strong&gt;&lt;/td&gt;&lt;td style="text-align: left"&gt;Netlify (USA)&lt;/td&gt;&lt;td style="text-align: left"&gt;Codeberg (Germany)&lt;/td&gt;&lt;td style="text-align: left"&gt;Codeberg leverages Forgejo Actions, providing CI/CD similar (but not identical) to GitHub Actions&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="text-align: left"&gt;&lt;strong&gt;Static Site Hosting &amp;amp; CDN&lt;/strong&gt;&lt;/td&gt;&lt;td style="text-align: left"&gt;Netlify (USA)&lt;/td&gt;&lt;td style="text-align: left"&gt;Bunny CDN (Slovenia)&lt;/td&gt;&lt;td style="text-align: left"&gt;European-headquartered, competitive pricing, GDPR compliant&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="text-align: left"&gt;&lt;strong&gt;Web Analytics&lt;/strong&gt;&lt;/td&gt;&lt;td style="text-align: left"&gt;Google Analytics (USA)&lt;/td&gt;&lt;td style="text-align: left"&gt;Plausible Analytics (Estonia)&lt;/td&gt;&lt;td style="text-align: left"&gt;Privacy-focused, no cookies, GDPR compliant&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;In conclusion, my European JAMstack migration reflects a deliberate move towards a more privacy-conscious and open-source friendly internet. By choosing European providers like Codeberg, Bunny CDN, and Plausible Analytics, I&amp;rsquo;ve improved the performance of my projects and gained significant peace of mind.&lt;/p&gt;
&lt;p&gt;For anyone looking to minimize their reliance on large US tech companies and embrace services with strong privacy commitments, exploring these European alternatives is a highly recommended step towards a more secure and ethically aligned online presence.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="faq"&gt;FAQ &lt;a class="heading-anchor" href="#faq" aria-label="Link to section: FAQ"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="why-is-it-important-to-switch-to-european-alternatives-for-personal-projects-even-if-they-dont-handle-highly-sensitive-data"&gt;Why is it important to switch to European alternatives for personal projects, even if they don&amp;rsquo;t handle highly sensitive data? &lt;a class="heading-anchor" href="#why-is-it-important-to-switch-to-european-alternatives-for-personal-projects-even-if-they-dont-handle-highly-sensitive-data" aria-label="Link to section: Why is it important to switch to European alternatives for personal projects, even if they don’t handle highly sensitive data?"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While personal projects might not always involve highly sensitive data, switching to European alternatives has several benefits beyond just immediate data privacy concerns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firstly, it aligns with the principles of data sovereignty, meaning data is governed by European regulations like GDPR, which are generally more stringent than those in other regions. This proactive approach can give greater peace of mind and prepare projects for potential future growth where data sensitivity might increase.&lt;/li&gt;
&lt;li&gt;Secondly, it supports a more diverse and decentralised tech ecosystem, reducing reliance on a few dominant US-based &amp;ldquo;Big Tech&amp;rdquo; companies. This creates healthy competition and encourages the development of open-source and community-driven platforms, which can lead to more innovation and ethical practices in the long run.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="are-there-any-performance-trade-offs-when-switching-from-us-based-providers-like-netlify-to-european-alternatives"&gt;Are there any performance trade-offs when switching from US-based providers like Netlify to European alternatives? &lt;a class="heading-anchor" href="#are-there-any-performance-trade-offs-when-switching-from-us-based-providers-like-netlify-to-european-alternatives" aria-label="Link to section: Are there any performance trade-offs when switching from US-based providers like Netlify to European alternatives?"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Not really. A European content delivery network like Bunny CDN can actually outperform Netlify in terms of raw CDN capabilities due to its robust network and focus on content delivery. As of 2025, Bunny CDN operates over 100 global Points of Presence (PoPs) while &lt;a href="https://getdeploying.com/bunnycdn-vs-netlify" title="Comparison: Bunny CDN vs Netlify" class="ext"&gt;Netlify leverages 16 data centers&lt;/a&gt;. Offering broader geographical coverage results in lower latency and faster load times.&lt;/p&gt;
&lt;h3 id="what-are-potential-challenges-when-migrating-existing-jamstack-projects-from-netlifys-build-process-to-codebergs-forgejo-actions"&gt;What are potential challenges when migrating existing JAMstack projects from Netlify&amp;rsquo;s build process to Codeberg&amp;rsquo;s Forgejo Actions? &lt;a class="heading-anchor" href="#what-are-potential-challenges-when-migrating-existing-jamstack-projects-from-netlifys-build-process-to-codebergs-forgejo-actions" aria-label="Link to section: What are potential challenges when migrating existing JAMstack projects from Netlify’s build process to Codeberg’s Forgejo Actions?"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Moving from Netlify&amp;rsquo;s built-in environment, which often offers zero-config setups for common static site generators, to a more explicit CI/CD setup with Forgejo Actions is a fundamentally different deployment approach. Challenges might include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Environment Variables:&lt;/b&gt; Ensuring all necessary environment variables used in Netlify builds are correctly configured in Forgejo Actions.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Build Commands:&lt;/b&gt; While similar, specific commands or paths might need adjustments.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Netlify-specific Features:&lt;/b&gt; If a project heavily relies on Netlify-specific features like Netlify Functions, Netlify Forms, or specific redirects/headers configurations, it is necessafy to find alternatives or implement similar logic within Forgejo Actions and Bunny CDN setup.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="what-other-european-jamstack-services-or-categories-should-be-considered-for-a-fully-european-setup"&gt;What other European JAMstack services or categories should be considered for a fully European setup? &lt;a class="heading-anchor" href="#what-other-european-jamstack-services-or-categories-should-be-considered-for-a-fully-european-setup" aria-label="Link to section: What other European JAMstack services or categories should be considered for a fully European setup?"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;More complex projects might need additional solutions for dynamic functionalities that go beyond just static site hosting and analytics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Serverless Functions:&lt;/b&gt; Keep an eye out for European cloud providers offering Function-as-a-Service (FaaS) or serverless computing, like Clever Cloud (France), Exoscale (Switzerland), or specific services from IONOS (Germany).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Form Handling:&lt;/b&gt; Use solutions like Formspark (Estonia) or build on serverless functions (hosted on a European FaaS platform) to receive form submissions to replace services like Netlify Forms.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Authentication:&lt;/b&gt; When it comes to user authentication, consider Identity as a Service (IDaaS) providers based in Europe, or maybe self-hosting an open-source solution like Keycloak on a European server.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Headless CMS:&lt;/b&gt; It&amp;rsquo;s not exactly a &amp;ldquo;migration&amp;rdquo; from a US provider but for projects based on a Headless CMS, consider to check out some European options like DatoCMS (Italy), Storyblok (Austria), or Strapi (France - open-source, can be self-hosted).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="what-are-the-main-benefits-of-open-source-principles-for-users-of-providers-like-codeberg-compared-to-proprietary-platforms"&gt;What are the main benefits of &amp;ldquo;open-source principles&amp;rdquo; for users of providers like Codeberg, compared to proprietary platforms? &lt;a class="heading-anchor" href="#what-are-the-main-benefits-of-open-source-principles-for-users-of-providers-like-codeberg-compared-to-proprietary-platforms" aria-label="Link to section: What are the main benefits of “open-source principles” for users of providers like Codeberg, compared to proprietary platforms?"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The open-source nature of platforms like Codeberg has a lot of advantages for individual users:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Transparency and Trust:&lt;/b&gt; The code is publicly auditable, meaning everyone can check it for security vulnerabilities or unwanted tracking. This makes people more likely to trust it than proprietary systems, where the ins and outs are kept secret.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Community-Driven Development:&lt;/b&gt; Codeberg is a non-profit, community-driven organisation, so its priorities are more aligned with its users than profit maximisation. This often means features that are developed based on what the community needs and a focus on making sure it&amp;rsquo;ll last.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Longevity and Control:&lt;/b&gt; It is less likely to get caught in a &amp;ldquo;vendor lock-in&amp;rdquo; situation or to have service terms or prices suddenly changed. If the platform changes direction, the open-source code (theoretically) allows to fork the project and continue development independently.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Learning and Contribution:&lt;/b&gt; For developers, hosting code on an open-source platform can be a great way to learn from others&amp;rsquo; code, contribute back to the platform itself, and gain experience in a truly open development environment.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Bunny Shield Review: 3 Things Need Improvement</title><link>https://www.janbrennenstuhl.eu/bunny-shield-review/</link><guid isPermaLink="true">https://www.janbrennenstuhl.eu/bunny-shield-review/</guid><pubDate>Sat, 07 Jun 2025 00:00:00 +0200</pubDate><description>&lt;p&gt;Bunny.net launched the &lt;strong&gt;Bunny Shield Preview&lt;/strong&gt; earlier this year. It&amp;rsquo;s an integrated Web Application Firewall (WAF), DDoS mitigation and rate-limiting controls solution all bundled into their Content Delivery Network (CDN) platform. I&amp;rsquo;ve been testing it on a few real-world projects over the last few weeks to see how ready it is and how easy it is to use.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;b&gt;Spoiler:&lt;/b&gt; Bunny Shield is straightforward to use and makes the most of your first experience. It&amp;rsquo;s a solid start for an accessible WAF, but there are a few key improvements needed to make it a production-ready web security product.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a quick rundown of the things I noticed and the problems I came across. It&amp;rsquo;s meant to show what&amp;rsquo;s working well now and where Bunny.net could improve their security tooling before it leaves the &amp;ldquo;Preview&amp;rdquo; stage.&lt;/p&gt;
&lt;h2 id="what-bunny-shield-gets-right"&gt;What Bunny Shield Gets Right &lt;a class="heading-anchor" href="#what-bunny-shield-gets-right" aria-label="Link to section: What Bunny Shield Gets Right"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="initial-user-experience--setup-simplicity"&gt;Initial User Experience &amp;amp; Setup Simplicity &lt;a class="heading-anchor" href="#initial-user-experience--setup-simplicity" aria-label="Link to section: Initial User Experience &amp;amp; Setup Simplicity"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Bunny Shield carries on the tradition of Bunny.net of offering clean and usable interfaces. As always, you only need to do the bare minimum to get things done. It was easy enough to enable the service and set up firewall or rate limit rules. For smaller sites and teams without dedicated security personnel, this kind of usability is invaluable.&lt;/p&gt;
&lt;h3 id="default-security-coverage"&gt;Default Security Coverage &lt;a class="heading-anchor" href="#default-security-coverage" aria-label="Link to section: Default Security Coverage"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The out-of-the-box rules catalogue seems pretty reasonable to me, catching common threats without overwhelming users with complex tuning. For most users, this makes Bunny Shield a no-brainer for &lt;strong&gt;basic web protection&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="what-bunny-shield-needs-to-improve"&gt;What Bunny Shield Needs to Improve &lt;a class="heading-anchor" href="#what-bunny-shield-needs-to-improve" aria-label="Link to section: What Bunny Shield Needs to Improve"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="certificate-renewal-blocked-by-waf"&gt;Certificate Renewal Blocked by WAF &lt;a class="heading-anchor" href="#certificate-renewal-blocked-by-waf" aria-label="Link to section: Certificate Renewal Blocked by WAF"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One of the most critical issues I encountered was Bunny Shield &lt;strong&gt;blocking HTTP-01 challenges from Let&amp;rsquo;s Encrypt&lt;/strong&gt;. This sometimes seems to stop the automatic TLS certificate renewals through Bunny&amp;rsquo;s own certificate mechanism, which could cause service disruptions if not caught in time.&lt;/p&gt;
&lt;p&gt;The good news is that Bunny&amp;rsquo;s support team have already said they know about the problem and are working on a fix. For now, users relying on &lt;a href="https://letsencrypt.org/docs/challenge-types/" title="Let&amp;#39;s Encrypt Challenge Types" class="ext"&gt;HTTP-01 validation&lt;/a&gt; (e.g. by using the built-in Bunny CDN certificate management) should proceed with caution or disable the WAF.&lt;/p&gt;
&lt;h3 id="no-bot-allowlisting-for-good-bots-like-ahrefs-or-semrush"&gt;No Bot Allowlisting for Good Bots like Ahrefs or Semrush &lt;a class="heading-anchor" href="#no-bot-allowlisting-for-good-bots-like-ahrefs-or-semrush" aria-label="Link to section: No Bot Allowlisting for Good Bots like Ahrefs or Semrush"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Currently, Bunny Shield doesn&amp;rsquo;t offer any particular bot management solution. This means there&amp;rsquo;s no way to tell the difference between &lt;strong&gt;harmful bots and legit crawlers&lt;/strong&gt; (like Ahrefs, Semrush, and other SEO tools). I saw this causing these &amp;ldquo;good bots&amp;rdquo; to be blocked, which harmed my SEO audits and (potentially) visibility.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;d be great to add a bot management layer or at least some simple allow listing functionality for expected crawlers. The opposite is also true: I&amp;rsquo;m sure some people would love to trap rogue or misbehaving &lt;a href="https://zadzmo.org/code/nepenthes/" title="Nepenthes: A web crawler tarpit" class="ext"&gt;AI scraper bots in a maze&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Overall, I&amp;rsquo;d love to see Bunny hop on board with the &lt;a href="https://blog.cloudflare.com/web-bot-auth/" title="Forget IPs: using cryptography to verify bot and agent traffic" class="ext"&gt;HTTP Message Signatures&lt;/a&gt; bandwagon that Cloudflare recently kicked off.&lt;/p&gt;
&lt;h3 id="limited-logging-and-visibility-into-waf-events"&gt;Limited Logging and Visibility into WAF Events &lt;a class="heading-anchor" href="#limited-logging-and-visibility-into-waf-events" aria-label="Link to section: Limited Logging and Visibility into WAF Events"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There&amp;rsquo;s a clear difference between the 403 traffic logs and the WAF event logs. I can see that requests were blocked, but there&amp;rsquo;s &lt;strong&gt;no clear traceability&lt;/strong&gt; to what specific WAF rule was triggered and why. This makes it tricky to debug false positives or fine-tune firewall behaviour, especially when you&amp;rsquo;re dealing with a lot of data.&lt;/p&gt;
&lt;h3 id="missing-waf-error-code-lookup-tool"&gt;Missing WAF Error Code Lookup Tool &lt;a class="heading-anchor" href="#missing-waf-error-code-lookup-tool" aria-label="Link to section: Missing WAF Error Code Lookup Tool"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When Bunny Shield blocks a request, it returns a 403 with a &lt;strong&gt;unique deny code&lt;/strong&gt; (e.g. &lt;code&gt;WAF403-XXXX&lt;/code&gt;). Unfortunately, there doesn&amp;rsquo;t seem to be a lookup tool for these codes at the moment. If we could get a dashboard-integrated lookup, that&amp;rsquo;d make a big difference to &lt;strong&gt;WAF rule debugging&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="final-thoughts-promising-foundations-but-still-in-beta"&gt;Final Thoughts: Promising Foundations, But Still in Beta &lt;a class="heading-anchor" href="#final-thoughts-promising-foundations-but-still-in-beta" aria-label="Link to section: Final Thoughts: Promising Foundations, But Still in Beta"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;All in all, the Bunny Shield Preview is doing really well, especially when it comes to how easy it is to use. It&amp;rsquo;s accessible, integrated tightly into the Bunny.net platform, and it&amp;rsquo;s already solving basic security needs for many developers.&lt;/p&gt;
&lt;p&gt;That said, there are a few key things that need to be sorted for Bunny Shield to become a great product. For example, observability, bot management and configurability need to be improved.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m excited to see how this product develops, and I&amp;rsquo;ll definitely be keeping an eye on the next updates.&lt;/p&gt;
&lt;h2 id="got-feedback-or-similar-experiences"&gt;Got Feedback or Similar Experiences? &lt;a class="heading-anchor" href="#got-feedback-or-similar-experiences" aria-label="Link to section: Got Feedback or Similar Experiences?"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Have you had a chance to try out Bunny Shield yet? Did you encounter similar issues? Found any workarounds yet? I&amp;rsquo;d love to hear about your experience – just send me a message on &lt;a href="https://bsky.app/profile/janbrennenstuhl.eu" title="Jan Brennenstuhl on Bluesky" class="ext"&gt;Bluesky&lt;/a&gt; or &lt;a href="https://infosec.exchange/@jbspeakr" title="Jan Brennenstuhl on Mastodon" class="ext"&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s help make Bunny Shield a secure, modern WAF that everyone can use.&lt;/p&gt;</description></item><item><title>Bunny CDN: How to redirect Root Domain to Subdomain</title><link>https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/</link><guid isPermaLink="true">https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/</guid><pubDate>Tue, 22 Apr 2025 00:00:00 +0200</pubDate><description>&lt;p&gt;Although technically no longer necessary, the &lt;code&gt;www&lt;/code&gt; subdomain is deeply ingrained in internet history and user habits. Redirecting root domains (like &lt;em&gt;janbrennenstuhl.eu&lt;/em&gt;) to its &lt;code&gt;www&lt;/code&gt; subdomain (&lt;em&gt;&lt;a href="https://www.janbrennenstuhl.eu"&gt;www.janbrennenstuhl.eu&lt;/a&gt;&lt;/em&gt;) offers benefits, especially &lt;a href="https://bunny.net/blog/how-aname-dns-records-affect-cdn-routing/" title="Bunny CDN: How ANAME DNS records affect CDN routing" class="ext"&gt;when using a content delivery network (CDN)&lt;/a&gt; or caring about search engine optimization (SEO).&lt;/p&gt;
&lt;p&gt;In this article, I share three different approaches to redirect a root domain to its &lt;code&gt;www&lt;/code&gt; subdomain when using Bunny CDN with Bunny DNS. All three approaches are technically sound and working solutions, returning proper A records for the domain root.&lt;/p&gt;
&lt;h2 id="option-1-redirect-record-rdr"&gt;Option 1: Redirect Record (RDR) &lt;a class="heading-anchor" href="#option-1-redirect-record-rdr" aria-label="Link to section: Option 1: Redirect Record (RDR)"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bunny DNS supports the creation of special redirect records. While the initial resolution happens at the DNS level, Bunny automatically applies HTTP redirects, instructing the browser ultimately to make a request to the redirected location.&lt;/p&gt;
&lt;p&gt;Configuring a new redirect record is as simple as heading over to Bunny DNS and choosing &lt;code&gt;RDR&lt;/code&gt; as record type in the &lt;em&gt;&amp;ldquo;Add DNS Record&amp;rdquo;&lt;/em&gt; prompt. Setting up a redirect record works as long as you don&amp;rsquo;t already have the hostname registered.&lt;/p&gt;
&lt;p&gt;&lt;figure class="figure"&gt;&lt;picture&gt;&lt;source type="image/avif" srcset="https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-redirect-record_hu_2986157a9991b9ed.avif 480w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-redirect-record_hu_11367a6a7597d755.avif 960w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-redirect-record_hu_3a68d5565a3979b7.avif 1358w" sizes="(min-width: 760px) 720px, calc(100vw - 2.5rem)"&gt;&lt;img src="https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-redirect-record_hu_68fd24c7400c13cf.webp" srcset="https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-redirect-record_hu_454bd53db87f956b.webp 480w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-redirect-record_hu_425ac1186e61b103.webp 960w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-redirect-record_hu_68fd24c7400c13cf.webp 1358w" sizes="(min-width: 760px) 720px, calc(100vw - 2.5rem)" width="1358" height="992" alt="Bunny DNS – Redirect Record config" loading="lazy" decoding="async"&gt;&lt;/picture&gt;&lt;figcaption&gt;Bunny DNS – How to add a redirect record&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="option-2-pull-zone-record-pz"&gt;Option 2: Pull Zone Record (PZ) &lt;a class="heading-anchor" href="#option-2-pull-zone-record-pz" aria-label="Link to section: Option 2: Pull Zone Record (PZ)"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A &amp;ldquo;Pull Zone&amp;rdquo; in Bunny CDN is a fundamental concept that defines how static assets are delivered using Bunny&amp;rsquo;s network of edge servers.
A &lt;code&gt;PZ&lt;/code&gt; record is a Bunny-custom, special DNS record type that provides some level of abstraction when glueing a domain to a &amp;ldquo;Pull Zone&amp;rdquo;. Instead of directly resolving to an IP address, Bunny DNS understands that requests for that particular domain should be directed to its CDN infrastructure associated with the corresponding &amp;ldquo;Pull Zone&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Configuring a new &amp;ldquo;Pull Zone&amp;rdquo; record is as simple as heading over to Bunny DNS and selecting &lt;code&gt;PZ&lt;/code&gt; as record type in the &lt;em&gt;&amp;ldquo;Add DNS Record&amp;rdquo;&lt;/em&gt; prompt. Prerequisite is an existing &amp;ldquo;Pull Zone&amp;rdquo; that the root domain should point to. The actual HTTP redirect would then be handled by &lt;a href="#dedicated-edge-rule"&gt;a dedicated &amp;ldquo;Edge Rule&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;figure class="figure"&gt;&lt;picture&gt;&lt;source type="image/avif" srcset="https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-pullzone-record_hu_518f66d5e13d6198.avif 480w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-pullzone-record_hu_f02b34f91a0642f4.avif 960w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-pullzone-record_hu_22947e07f4ed95f3.avif 1346w" sizes="(min-width: 760px) 720px, calc(100vw - 2.5rem)"&gt;&lt;img src="https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-pullzone-record_hu_e6bab51cfddf2d62.webp" srcset="https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-pullzone-record_hu_f5bb9c2ac1f6541.webp 480w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-pullzone-record_hu_82fa566d8f8bc081.webp 960w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-pullzone-record_hu_e6bab51cfddf2d62.webp 1346w" sizes="(min-width: 760px) 720px, calc(100vw - 2.5rem)" width="1346" height="814" alt="Bunny DNS – Pullzone Record config" loading="lazy" decoding="async"&gt;&lt;/picture&gt;&lt;figcaption&gt;Bunny DNS – How to add a pullzone record&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="option-3-canonical-name-record-cname"&gt;Option 3: Canonical Name Record (CNAME) &lt;a class="heading-anchor" href="#option-3-canonical-name-record-cname" aria-label="Link to section: Option 3: Canonical Name Record (CNAME)"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Finally, Bunny DNS also allows creating &lt;code&gt;CNAME&lt;/code&gt; records for root domains. This is possible (and also returns an A record) as Bunny transparently performs &lt;code&gt;CNAME&lt;/code&gt; flattening under the hood.&lt;/p&gt;
&lt;p&gt;Configuring a new canonical name record is as simple as heading over to Bunny DNS and choosing &lt;code&gt;CNAME&lt;/code&gt; as record type in the &lt;em&gt;&amp;ldquo;Add DNS Record&amp;rdquo;&lt;/em&gt; prompt. The actual HTTP redirect would then be handled by &lt;a href="#dedicated-edge-rule"&gt;a dedicated &amp;ldquo;Edge Rule&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;figure class="figure"&gt;&lt;picture&gt;&lt;source type="image/avif" srcset="https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-cname-record_hu_be4b78e02f997cf2.avif 480w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-cname-record_hu_8d7d7ed8088507e6.avif 960w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-cname-record_hu_a8ab377814a08a38.avif 1348w" sizes="(min-width: 760px) 720px, calc(100vw - 2.5rem)"&gt;&lt;img src="https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-cname-record_hu_8b4ce45ef66cf0c4.webp" srcset="https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-cname-record_hu_87590a5931c267d9.webp 480w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-cname-record_hu_5a1ec5e37408271b.webp 960w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/bunnycdn-cname-record_hu_8b4ce45ef66cf0c4.webp 1348w" sizes="(min-width: 760px) 720px, calc(100vw - 2.5rem)" width="1348" height="822" alt="Bunny DNS – CNAME Record config" loading="lazy" decoding="async"&gt;&lt;/picture&gt;&lt;figcaption&gt;Bunny DNS – How to add a CNAME record&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="dedicated-edge-rule"&gt;Dedicated Edge Rule &lt;a class="heading-anchor" href="#dedicated-edge-rule" aria-label="Link to section: Dedicated Edge Rule"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In case a root domain points to a Bunny CDN &amp;ldquo;Pull Zone&amp;rdquo; either by using either a &lt;code&gt;PZ&lt;/code&gt; or &lt;code&gt;CNAME&lt;/code&gt; record (Option 2 or Option 3), the actual HTTP redirect (301) needs to be applied using a dedicated &amp;ldquo;Edge Rule&amp;rdquo; like this:&lt;/p&gt;
&lt;div class="table-scroll" tabindex="0" role="region" aria-label="Table 1"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th scope="col"&gt;Action&lt;/th&gt;&lt;th scope="col"&gt;Value&lt;/th&gt;&lt;th scope="col"&gt;Status Code&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Redirect to URL&lt;/td&gt;&lt;td&gt;&lt;code&gt;https://www.janbrennenstuhl.eu{{path}}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;301&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;div class="table-scroll" tabindex="0" role="region" aria-label="Table 2"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th scope="col"&gt;Condition&lt;/th&gt;&lt;th scope="col"&gt;Match&lt;/th&gt;&lt;th scope="col"&gt;Value&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Request URL&lt;/td&gt;&lt;td&gt;Any&lt;/td&gt;&lt;td&gt;&lt;code&gt;*://janbrennenstuhl.eu/*&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;&lt;figure class="figure"&gt;&lt;picture&gt;&lt;source type="image/avif" srcset="https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/root-domain-edge-rule_hu_4ef3f5000000e136.avif 480w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/root-domain-edge-rule_hu_965b3f6184e46dbe.avif 960w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/root-domain-edge-rule_hu_5b1d679deec19a5e.avif 1440w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/root-domain-edge-rule_hu_6e1e23df4c714af7.avif 1752w" sizes="(min-width: 760px) 720px, calc(100vw - 2.5rem)"&gt;&lt;img src="https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/root-domain-edge-rule_hu_5f583e1c32da1f97.webp" srcset="https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/root-domain-edge-rule_hu_ad684fcd838da785.webp 480w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/root-domain-edge-rule_hu_5c60737616102596.webp 960w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/root-domain-edge-rule_hu_fdecfbf4f84c1d87.webp 1440w, https://www.janbrennenstuhl.eu/bunny-cdn-domain-redirect/root-domain-edge-rule_hu_5f583e1c32da1f97.webp 1752w" sizes="(min-width: 760px) 720px, calc(100vw - 2.5rem)" width="1752" height="676" alt="Bunny CDN – Edge Rule" loading="lazy" decoding="async"&gt;&lt;/picture&gt;&lt;figcaption&gt;Bunny CDN – How to redirect Root Domain to Subdomain&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary &lt;a class="heading-anchor" href="#summary" aria-label="Link to section: Summary"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In summary, while the &lt;code&gt;www&lt;/code&gt; subdomain isn&amp;rsquo;t a technical necessity in the modern web, redirecting root domains to it is a best practice for SEO consistency, technical flexibility, and user experience. It ensures that websites are treated as a single entity by search engines and provides a consistent and potentially more reliable experience for visitors.&lt;/p&gt;
&lt;p&gt;Bunny.net DNS supports three different approaches for redirecting a root domain to its &lt;code&gt;www&lt;/code&gt; subdomain. All result in perfectly fine DNS configs and high-speed resolution when paired with Bunny CDN.&lt;/p&gt;</description></item><item><title>Learning AssertJ: Null ain't Blank</title><link>https://www.janbrennenstuhl.eu/assertj-null-not-blank/</link><guid isPermaLink="true">https://www.janbrennenstuhl.eu/assertj-null-not-blank/</guid><pubDate>Tue, 05 Sep 2017 00:00:00 +0200</pubDate><description>&lt;p&gt;As programmers, we probably use strings more than any other type of data structure. The cross-language String environment is characterized by shared vocabulary and well-established concepts. And it is especially important to follow these established naming conventions when it comes to &lt;a href="https://web.archive.org/web/20200809121851/https://josjong.com/2017/10/16/null-vs-empty-strings-why-oracle-was-right-and-apple-is-not/" class="ext"&gt;null strings&lt;/a&gt; vs empty strings (and blank strings).&lt;/p&gt;
&lt;p&gt;&lt;figure class="figure"&gt;&lt;picture&gt;&lt;source type="image/avif" srcset="https://www.janbrennenstuhl.eu/assertj-null-not-blank/toilet-rolls_hu_63ff2e7af28dd1ee.avif 480w, https://www.janbrennenstuhl.eu/assertj-null-not-blank/toilet-rolls_hu_7f4bb07701cccd0c.avif 600w" sizes="(min-width: 760px) 720px, calc(100vw - 2.5rem)"&gt;&lt;img src="https://www.janbrennenstuhl.eu/assertj-null-not-blank/toilet-rolls_hu_76d98eb9ef045618.webp" srcset="https://www.janbrennenstuhl.eu/assertj-null-not-blank/toilet-rolls_hu_6807b29b1791150e.webp 480w, https://www.janbrennenstuhl.eu/assertj-null-not-blank/toilet-rolls_hu_76d98eb9ef045618.webp 600w" sizes="(min-width: 760px) 720px, calc(100vw - 2.5rem)" width="600" height="336" alt="Toilet rolls representing blank and null states" loading="lazy" decoding="async"&gt;&lt;/picture&gt;&lt;figcaption&gt;AssertJ – Blank vs Null Strings&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;With &lt;a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html#isBlank-java.lang.CharSequence-" class="ext"&gt;Apache Commons StringUtils&lt;/a&gt; and &lt;a href="https://docs.jboss.org/hibernate/validator/5.4/api/org/hibernate/validator/constraints/NotBlank.html" class="ext"&gt;Hibernate Validator Constraints&lt;/a&gt;, two popular and &lt;a href="https://mvnrepository.com/artifact/org.apache.commons/commons-lang3/usages" class="ext"&gt;widely adapted&lt;/a&gt; projects established a de facto definition for a blank string:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“A blank String is a CharSequence that is empty (&amp;quot;&amp;quot;), null or whitespace only.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While recently investigating a bug in one of our systems, I stumbled upon a slight divergent and unexpected string handling in AssertJ, a fluent assertions framework for Java. This &lt;a href="https://github.com/assertj/assertj/issues/1069" class="ext"&gt;AssertJ misbehaviour&lt;/a&gt; has the potential to lead to severe troubles – which is why I consider it to be a bug. As it turns out …&lt;/p&gt;
&lt;figure class="code" data-lang="java"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" aria-label="Code (java)" class="chroma"&gt;&lt;code data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;isNotBlank&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;… passes. This is definitely not expected behaviour, since (following the common definition above) a null string is considered to be a blank string and vice versa. So if you&amp;rsquo;re used to Apache Commons StringUtils and Hibernate Validators behaviour, you want to make sure that also your tests expect the correct behaviour.&lt;/p&gt;
&lt;p&gt;Do you know further libraries that follow a different blank string definition or if you have a different opinion about all of this, drop me a message &lt;a href="https://infosec.exchange/@jbspeakr" class="ext"&gt;via Mastodon&lt;/a&gt;!&lt;/p&gt;</description></item><item><title>On Making Spring Security OAuth RFC-compliant</title><link>https://www.janbrennenstuhl.eu/spring-security-oauth-rfc/</link><guid isPermaLink="true">https://www.janbrennenstuhl.eu/spring-security-oauth-rfc/</guid><pubDate>Wed, 01 Feb 2017 00:00:00 +0100</pubDate><description>&lt;p&gt;Often supporting open source software is just fixing one tiny thing you stumbled upon. However, getting ready and diving into even the smallest piece of source-code can lead to surprising results. I fixed a small HTTP header extractor for the &lt;a href="https://github.com/spring-projects/spring-security-oauth" class="ext"&gt;Spring Security OAuth&lt;/a&gt; project recently. Here&amp;rsquo;s what happened.&lt;/p&gt;
&lt;h2 id="i-fixed-something-for-me"&gt;I fixed something for me &lt;a class="heading-anchor" href="#i-fixed-something-for-me" aria-label="Link to section: I fixed something for me"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At the very least, fixing this issue has made me happier. The problem was that the Spring Security OAuth &lt;code&gt;BearerTokenExtractor&lt;/code&gt; was not acting in compliance to &lt;a href="https://tools.ietf.org/html/rfc7230#section-3.2.2" class="ext"&gt;RFC7230&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Every time someone sends a request with multiple, comma-separated Authorization-Header values towards an application I am currently developing the extractor would fail in doing its job. The application would simply not accept the yet valid (and RFC compliant) request.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;RFC7230, Section 3.2.2&lt;/strong&gt; - A sender MUST NOT generate multiple header fields with the same field name in a message
unless either the entire field value for that header field is defined as a comma-separated list [i.e., #(values)] &amp;hellip;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="i-fixed-something-for-someone-else"&gt;I fixed something for someone else &lt;a class="heading-anchor" href="#i-fixed-something-for-someone-else" aria-label="Link to section: I fixed something for someone else"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is a guess, but if I found this an issue then we can presume at least one other person out there on the internet did. Also, being RFC-compliant usually is a good thing and every library should handle user-generated input as resilient as possible.&lt;/p&gt;
&lt;h2 id="i-learned-more-about-library-maintenance"&gt;I learned more about Library Maintenance &lt;a class="heading-anchor" href="#i-learned-more-about-library-maintenance" aria-label="Link to section: I learned more about Library Maintenance"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I started by making a mistake. My original fix was a custom implementation of the &lt;code&gt;TokenExtractor&lt;/code&gt; interface that is also implemented by the original &lt;code&gt;BearerTokenExtractor&lt;/code&gt; within my application. This however would require me to maintain and test this piece of code that is not part of my actual business logic. I would totally loose the advantages of abstraction through a battle-proven library. It also ignored the problems true essence.&lt;/p&gt;
&lt;p&gt;That thought lead me to try again. This time I cloned the Spring Security OAuth project and tried copy-and-pasting my slim Java8 Streams API based &lt;code&gt;TokenExtractor&lt;/code&gt; and its tests into the library. This lead to a bunch of discoveries about library maintenance — Spring Security OAuth&amp;hellip;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;hellip; is written in Java6,&lt;/li&gt;
&lt;li&gt;&amp;hellip; has only other Spring projects as dependencies,&lt;/li&gt;
&lt;li&gt;&amp;hellip; needs to be backward compatible.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, if you plan to support a project that is out there for a while, keep in mind to leave all your fancy tooling at home. Also try to read and follow the projects code and style guidelines before receiving requests for change in your pull-requests.&lt;/p&gt;
&lt;p&gt;Of course, as with anything around the globe, &lt;a href="https://github.com/spring-projects/spring-security-oauth/blob/master/CODE_OF_CONDUCT.adoc" class="ext"&gt;there is even more to this than meet the eye&lt;/a&gt;. However, this was &lt;a href="https://github.com/spring-projects/spring-security-oauth/pull/895" class="ext"&gt;my resulting pull request&lt;/a&gt; &lt;del&gt;and it is waiting to get merged&lt;/del&gt;.&lt;/p&gt;
&lt;h2 id="i-learned-more-about-http-headers"&gt;I learned more about HTTP Headers &lt;a class="heading-anchor" href="#i-learned-more-about-http-headers" aria-label="Link to section: I learned more about HTTP Headers"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the beginning, I learned about the diversity of HTTP Headers. Being spoiled by all these framework abstraction layers nowadays, it hit me that HTTP Headers are simply yet another interface. And we want to handle all interface interaction in the most resilient way.&lt;/p&gt;
&lt;p&gt;Inspired by some of my colleagues, I started to delve into RFCs. I started with &lt;a href="https://tools.ietf.org/html/rfc6750" class="ext"&gt;RFC6750&lt;/a&gt; (describing the Bearer Token Usage within OAuth 2.0) and ended up in the even more fundamental &lt;a href="https://tools.ietf.org/html/rfc7230" class="ext"&gt;RFC7230&lt;/a&gt; (on Message Syntax and Routing of the Hypertext Transfer Protocol).&lt;/p&gt;
&lt;p&gt;Now I am more familiar with the possible appearances of HTTP Headers and the work behind the Spring Security OAuth project.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;With my fix in one of the next releases, Spring Security OAuth is going to also be able to handle requests like this:&lt;/del&gt;&lt;/p&gt;
&lt;figure class="code" data-lang="text"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" aria-label="Code" class="chroma"&gt;&lt;code data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GET /presence/alice HTTP/1.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Host: server.example.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Authorization: Basic YXNkZnNhZGZzYWRmOlZLdDVOMVhk, Bearer mF_9.B5f-4.1JqM&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Update 16.02.2017:&lt;/strong&gt; As Joe Grandja (Spring Security Engineer) argued, the user agent is supposed to choose &lt;em&gt;&amp;ldquo;only 1 of the challenges (if there is more than 1 challenge) to authenticate against&amp;rdquo;&lt;/em&gt;, so my pull request was rejected. Feel free to read all the comments in the &lt;a href="https://github.com/spring-projects/spring-security-oauth/issues/894" class="ext"&gt;corresponding issue&lt;/a&gt; for all the details.&lt;/p&gt;
&lt;h2 id="the-simplest-piece-of-work-can-lead-you-anywhere"&gt;The simplest piece of work can lead you anywhere &lt;a class="heading-anchor" href="#the-simplest-piece-of-work-can-lead-you-anywhere" aria-label="Link to section: The simplest piece of work can lead you anywhere"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I just wanted to make the Bearer Token extraction more resilient. Yet, this lead to a ticket and a pull request, a rabbit hole of discovery about HTTP Headers and open source library maintenance and the motivation to write a blog post about it. Ultimately, I did get what I wanted in the first place though: More fun during work.&lt;/p&gt;
&lt;p&gt;Open source is a lot of things to a lot of people. To me, it&amp;rsquo;s a way to learn while I help, however small the task may seem. I&amp;rsquo;d love for you to share with &lt;a href="https://twitter.com/jbspeakr" class="ext"&gt;me on Twitter&lt;/a&gt; what open source means to you.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;This post was heavily &lt;a href="https://philna.sh/blog/2017/01/27/on-fixing-a-favicon/" class="ext"&gt;inspired by Phil Nash&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>AWS DynamoDB: Backup &amp; Recovery Strategy</title><link>https://www.janbrennenstuhl.eu/dynamodb-backup/</link><guid isPermaLink="true">https://www.janbrennenstuhl.eu/dynamodb-backup/</guid><pubDate>Mon, 18 Jan 2016 00:00:00 +0100</pubDate><description>&lt;p&gt;&lt;em&gt;&amp;ldquo;Amazon DynamoDB is a fully managed NoSQL database service [&amp;hellip;]&amp;rdquo;&lt;/em&gt;. That&amp;rsquo;s what Amazon states in the &lt;a href="https://aws.amazon.com/dynamodb/faqs/" class="ext"&gt;DynamoDB FAQs&lt;/a&gt;. However, their definition is not quite what I would understand as fully managed. It&amp;rsquo;s missing counter-measures against the human error. For that reason, we need a DynamoDB backup and recovery solution, our DynamoDB safety-net&amp;hellip;&lt;/p&gt;
&lt;h2 id="dynamodb-threat-scenario"&gt;DynamoDB Threat Scenario &lt;a class="heading-anchor" href="#dynamodb-threat-scenario" aria-label="Link to section: DynamoDB Threat Scenario"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Imagine your entire AWS infrastructure, including your DynamoDB tables, is defined and set up via AWS Cloudformation (btw, make sure to read &lt;a href="https://www.janbrennenstuhl.eu/aws-cloudformation/" title="Abusing JSON: Why AWS CloudFormation sucks!"&gt;why Cloudformation sucks&lt;/a&gt;) or even worse by hand. Further imagine you actually use your tables to store more or less important data. And finally imagine someone deleting your tables and/ or stacks by accident or your software behaving somehow unexpected, dropping tables. See?! DynamoDB does not ship with built-in backup and recovery implementations, so we had to set up our own protective strategy.&lt;/p&gt;
&lt;p&gt;The solution we came up with basically orchestrates AWS DynamoDB with Datapipelines, S3 and Amazons Lambdas to establish not just a daily backup mechanism but also to allow simple staging (production to testing environment) and recovery possibilities. This is how it works:&lt;/p&gt;
&lt;p&gt;&lt;figure class="figure"&gt;&lt;picture&gt;&lt;source type="image/avif" srcset="https://www.janbrennenstuhl.eu/dynamodb-backup/DynamoDB-backup-staging_hu_bb1bad334b60fbb8.avif 480w, https://www.janbrennenstuhl.eu/dynamodb-backup/DynamoDB-backup-staging_hu_bf55e9e0bd9d18f7.avif 754w" sizes="(min-width: 760px) 720px, calc(100vw - 2.5rem)"&gt;&lt;img src="https://www.janbrennenstuhl.eu/dynamodb-backup/DynamoDB-backup-staging_hu_d1eb437d5aba2906.webp" srcset="https://www.janbrennenstuhl.eu/dynamodb-backup/DynamoDB-backup-staging_hu_373dd8984c125b2b.webp 480w, https://www.janbrennenstuhl.eu/dynamodb-backup/DynamoDB-backup-staging_hu_d1eb437d5aba2906.webp 754w" sizes="(min-width: 760px) 720px, calc(100vw - 2.5rem)" width="754" height="524" alt="A backup &amp;amp; recovery strategy for DynamoDB" loading="lazy" decoding="async"&gt;&lt;/picture&gt;&lt;figcaption&gt;DynamoDB Backup Components&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="dynamodb-w-aws-datapipeline--lambda"&gt;DynamoDB w\ AWS Datapipeline &amp;amp; Lambda &lt;a class="heading-anchor" href="#dynamodb-w-aws-datapipeline--lambda" aria-label="Link to section: DynamoDB w\ AWS Datapipeline &amp;amp; Lambda"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In addition to the table definitions, our Cloudformation-based infrastructure setup also describes Datapipelines for the tables in our production environment which nightly backup DynamoDB tables to S3. Furthermore, these Datapipelines&amp;rsquo; ObjectCreated-Events trigger a small AWS Lambda function we also have in place, which is responsible for deletion and recreation of the corresponding tables in our testing environment. Separated in time, additional Datapipelines refill these empty tables with the productive backups created earlier.&lt;/p&gt;
&lt;p&gt;In that way, we established a battle-proven backup and staging solution for AWS DynamoDB. But is this of any help when we have to deal with a real DynamoDB disaster, e.g. accidental table deletion or data corruption? Yes, it is:&lt;/p&gt;
&lt;p&gt;Due to the infrastructure as code approach, we can easily recreate entire stacks of DynamoDB tables and Datapipelines (yes, we run tables and pipelines in separated stacks) and use our latest backups to recreate a stable state. Everything with just two or three commands.&lt;/p&gt;
&lt;p&gt;However, we don&amp;rsquo;t have any automation in disaster recognition yet as in my eyes it&amp;rsquo;s quite hard to identify possible disasters and (especially) distinguish those from intentional table changes. Also, with this setup we don&amp;rsquo;t have any chance to properly react on DynamoDB (region, AZ, etc.) not reachable/ available issues.&lt;/p&gt;
&lt;p&gt;Further downsides: Our approach is heavily dependent on Datapipelines (I already have a &amp;lsquo;Why I hate AWS Datapipelines&amp;rsquo; post in my pipeline ;D) and due to the orchestration of four Amazon Web Services it is not as simple nor cost-efficient as I would want a proper solution for this common problem to be.&lt;/p&gt;
&lt;p&gt;Hope you got some inspiration for your future DynamoDB setup. If you have any questions or improvement suggestions make sure to leave a comment or contact me directly.&lt;/p&gt;</description></item><item><title>Evolution of a Microservice: A Git History Visualisation</title><link>https://www.janbrennenstuhl.eu/microservice-evolution/</link><guid isPermaLink="true">https://www.janbrennenstuhl.eu/microservice-evolution/</guid><pubDate>Thu, 21 May 2015 00:00:00 +0200</pubDate><description>&lt;p&gt;Microservices have revolutionized the way we develop and deploy software applications. But with great power comes great complexity. As microservices become more and more ubiquitous, developers must grapple with new challenges, such as managing dependencies, ensuring scalability, and maintaining consistency across services. In this article, I take a deep dive into the evolution of a microservice, from its inception as a small, independent module to its growth into a complex kraken…&lt;/p&gt;
&lt;p&gt;Following the common pendulum theory, every tech debate oscillates between at least two currently trending topics. The same is true when it comes to micro-services (vs. integrated systems). One of my former teams decided to follow a radical microservices approach, favoring the benefits of smallest possible deployment units over integration (and its advantages) and preferring infrastructure as well as communication complexity over tight coupling. Why we chose to do so and how we make sure to keep our services micro, I&amp;rsquo;m going to explain in more detail&amp;hellip;&lt;/p&gt;
&lt;h2 id="microservices-self-contained-systems--integrated-applications"&gt;Microservices, Self-contained Systems &amp;amp; Integrated Applications &lt;a class="heading-anchor" href="#microservices-self-contained-systems--integrated-applications" aria-label="Link to section: Microservices, Self-contained Systems &amp;amp; Integrated Applications"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Back then my team was responsible for parts of one a core selling flows. With thousands of customers-to-be every day and direct major business impact, we not just need to be reliable as well as always up-and-running, but also fairly flexible and (even more important) fast, allowing product improvements, bug fixes and multi-variant tests to be rolled out in near-realtime. Helping us to achieve those goals are our continuous live deployment (CLD) infrastructure, our near-realtime monitoring as well as alarming setup and most importantly our constantly evolving, stateless micro-services&amp;hellip;&lt;/p&gt;
&lt;p&gt;The &lt;a href="http://microservices.io/patterns/microservices.html" class="ext"&gt;micro-services architecture&lt;/a&gt; I am referring to in this article is the probably most remote antipole of what is commonly known as monolithic, integrated system (or what some companies call legacy, heritage or mothership). Between those two extrema, there is a whole bunch of manifestations, variations and adoptions like the so-called &lt;a href="http://scs-architecture.org/index.html" class="ext"&gt;Self-contained System&lt;/a&gt; (SCS) architecture. However, I am usually not a big fan of some technical middle courses as in my experience they often combine not the intended advantages but the downsides of two radical different ideas. Furthermore, if a framework (and an architecture is nothing else) allows people to implement their first gut feelings instead of understanding the greater ideas, concepts and reasons, they tend to do so.&lt;/p&gt;
&lt;p&gt;On the other hand, sticking with and optimizing your infrastructure for one clean approach (not in a religious but explaining manner) generally helps creating easy repeatable and comprehensible software architecture - as long as you can handle possible drawbacks. Having implemented a strict microservices-based deployment architecture in our core business, we now benefit from a couple of great possibilities even our business people find it great to work with:&lt;/p&gt;
&lt;p class="video-link"&gt;&lt;a class="ext" href="https://www.youtube.com/watch?v=12IU4m16bJM"&gt;Watch the accompanying video on YouTube&lt;/a&gt; (external site, not embedded).&lt;/p&gt;
&lt;h2 id="microservices-dont-letem-grow"&gt;Microservices, Don&amp;rsquo;t let&amp;rsquo;em grow! &lt;a class="heading-anchor" href="#microservices-dont-letem-grow" aria-label="Link to section: Microservices, Don’t let’em grow!"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We have tiny, decoupled applications that are deployed independently. Due to their small footprint, each of them is easier to understand (even by new colleagues) and can be cloned and built in almost no time, which speeds up not just our dev productivity but also our deployments. Additionally, our microservices enable us to exchange parts of our tech stack pretty easy and make us in that way eager to try out new things. Whether it&amp;rsquo;s about server-side vs. client-side rendering, SQL vs. NoSQL databases, Play vs. Spring or any other major design decision, we don&amp;rsquo;t need to compromise, we can use whatever fits best and helps us to solve the microservices single purpose. It&amp;rsquo;s a great feeling and even better: It helps us reducing complexity and dependencies in each of our deployment units. However, to be able to enjoy these advantages you need to be beware of its drawbacks&amp;hellip;&lt;/p&gt;
&lt;p&gt;In my eyes the major challenge of a microservices architecture is not that shifted complexity of its distribution, but that you need to cut. It&amp;rsquo;s a big drawback, because every software tends to grow bigger - it&amp;rsquo;s just a matter of time until a single-purpose microservice becomes a multi-purpose legacy app. There&amp;rsquo;s no way to prevent your software from doing so. Whether you cut your services functional or by following business use-cases, you have no other chance than to be fully-aware of that fact, and you need to cut carefully and (most importantly) you need to cut often.&lt;/p&gt;
&lt;p&gt;The video above visualizes the evolution of one of our microservices on a source-code level. As almost every green field software, it starts fairly simple but quickly outgrow its originally intended size and (even worse) its single-purpose approach. Although fighting back with more or less effective refactorings, the service grew from 5k up to 15k lines-of-code (LoC) within a year until we started to actively enforce our microservice-policy. As the &lt;a href="http://gource.io/" class="ext"&gt;Gource&lt;/a&gt; visualization shows, we started to decouple our service&amp;rsquo;s back- and frontend functionality by migrating from server- to client-side rendering and by introducing a separate, Grunt-based build-process for our frontend-resources (see 01:30).&lt;/p&gt;
&lt;p&gt;Next, we where able to identify further decoupling possibilities more easily and started moving major backend functionality to a new, separately created microservice, shrinking down the code-base of this particular service (which basically serves a couple of public webpages) back to round about 5k LoC (see 01:50). Being done with that, our service now is in a state where we can directly benefit from all the advantages we discussed before. Furthermore, we ended up with a clean decoupling between mainly enduser-centric code and a more business-administration REST-API.&lt;/p&gt;
&lt;h2 id="tldr---take-a-radical-approach"&gt;tl;dr - Take a radical approach! &lt;a class="heading-anchor" href="#tldr---take-a-radical-approach" aria-label="Link to section: tl;dr - Take a radical approach!"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When it comes to tech decisions, going all-in and optimizing for one strict approach (one pole of the spectrum) and being aware of its possible drawbacks is always better than mixing contradictory ideas, patterns or philosophies. Although in theory, combining contradictory approaches might sometimes sound like a great idea (&lt;em&gt;&amp;ldquo;hey, let&amp;rsquo;s combine the best of&amp;rsquo;em all!&amp;rdquo;&lt;/em&gt;), in practice they&amp;rsquo;re usually not.&lt;/p&gt;
&lt;p&gt;Whether it&amp;rsquo;s about server-side vs. client-side rendering, &lt;a href="https://aws.amazon.com/blogs/compute/microservices-without-the-servers/" class="ext"&gt;serverless&lt;/a&gt; vs. servers-as-cattle, or the red vs. blue pill, you want to settle for exactly one extrema. However, you need to know and understand the drawbacks. Establishing and maintaining e.g. an microservices architecture is continuous work. Evaluating the actual size as well as purpose of your services (and service landscape) is essential and cutting down your services regularly is a must if you want to benefit from your architectural decision for microservices.&lt;/p&gt;</description></item><item><title>Abusing JSON: Why AWS CloudFormation sucks?!</title><link>https://www.janbrennenstuhl.eu/aws-cloudformation/</link><guid isPermaLink="true">https://www.janbrennenstuhl.eu/aws-cloudformation/</guid><pubDate>Thu, 21 May 2015 00:00:00 +0200</pubDate><description>&lt;p&gt;&lt;a href="https://aws.amazon.com/cloudformation/" class="ext"&gt;AWS CloudFormation&lt;/a&gt; (CFN) is a file-based interface for provisioning AWS resources and in that way is Amazons answer on the &amp;lsquo;&lt;a href="http://www.thoughtworks.com/insights/blog/infrastructure-code-reason-smile" class="ext"&gt;Infrastructure as Code&lt;/a&gt;&amp;rsquo; paradigm. I am a huge fan of this approach and CFN is a big step into the right direction as it supports not just common DevOps ideas, by allowing devs to create, update and delete the operational infrastructure they need with their own tools (a text editor, version control and tests).&lt;/p&gt;
&lt;p&gt;It also enables you to think different about your IT infrastructure and how to work with it (e.g. just think about immutable stacks). However, I see some issues that become more important if you work with CloudFormation on a daily basis &amp;hellip;&lt;/p&gt;
&lt;h2 id="json-lang-wtf"&gt;JSON Lang, WTF?! &lt;a class="heading-anchor" href="#json-lang-wtf" aria-label="Link to section: JSON Lang, WTF?!"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;CloudFormation templates are simple JSON-formatted text files that (in theory) can easily be created and maintained by hand as nowadays every dev (and/ or ops) should be able to read and write valid JSON. However, AWS CloudFormation uses the originally static &lt;em&gt;&lt;a href="https://www.json.org/json-en.html" class="ext"&gt;lightweight data-interchange format&lt;/a&gt;&lt;/em&gt; as a scripting language, allowing e.g. the definition and usage of parameter and (somehow crazy) &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html" class="ext"&gt;intrinsic function&lt;/a&gt; references.&lt;/p&gt;
&lt;p&gt;In my eyes, this is a heavy misusage that leads to several problems regarding complexity and comprehensibility. You can find several deterrent examples that illustrate this right in the official AWS developers guide. If you still don&amp;rsquo;t think JSON is a syntax-bloated, hard-to-read data format just have a look on how to define &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datapipeline-pipeline.html#d0e32425" class="ext"&gt;DataPipelines using CloudFormation&lt;/a&gt; and tell me you like it and that you are able to easily understand all the definitions as well as their interactions. It took me some time&amp;hellip;&lt;/p&gt;
&lt;p&gt;But that&amp;rsquo;s not even the worst part. JSON also lacks some feature substance that some devs would regard heavily essential. In contrast to its technical superset YAML, JSON is not aware of e.g. comments or user defined data types. In my eyes, this is a huge downside and a massive argument against Amazons classical CloudFormation design.&lt;/p&gt;
&lt;p&gt;All in all I don&amp;rsquo;t quite get the whys and reasons which made Amazons engineers believe that the abuse of JSON for scripting would be a better and more innovative idea than go for something like an established scripting language like Python or JavaScript.&lt;/p&gt;
&lt;p&gt;But enough JSON-bashing for now. Let&amp;rsquo;s solve at least some of our AWS CloudFormation issues.&lt;/p&gt;
&lt;h2 id="cfn-sphere"&gt;CFN-Sphere &lt;a class="heading-anchor" href="#cfn-sphere" aria-label="Link to section: CFN-Sphere"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Currently there are a couple of tools around that sit on top of CFN as an abstraction layer and offer not just a YAML-based config approach, but also further functionality that comes in handy if you work with infrastructure as code during your everyday business.&lt;/p&gt;
&lt;p&gt;One of those tools is &lt;a href="https://github.com/cfn-sphere/cfn-sphere" class="ext"&gt;CFN-Sphere&lt;/a&gt;, a lightweight Python-tool that aims to simplify AWS CloudFormation handling not just for infrasturcture automation but also for human interaction. Initially built by &lt;a href="https://github.com/marco-hoyer" class="ext"&gt;Marco Hoyer&lt;/a&gt; (a former colleague and friend of mine) CFN-Sphere comes with fundamental support for e.g. cross stack parameter references and automatic stack dependency resolution including circular dependency detection. Both are amazing features also for smaller AWS infrastructure definitions like the backup and staging environment for DynamoDB tables I am currently working on (More on that topic in one of the upcoming posts).&lt;/p&gt;
&lt;p&gt;So if you are struggling with your with your CloudFormation config or if you have a certain feeling that CFN files having around several hundred lines of code are almost unmaintainable, make sure to check out CFN-Sphere and its stack template feature. And help making it even greater by becoming a contributor!&lt;/p&gt;</description></item><item><title>Python Docopt: Convert Tuple List to Dictionary</title><link>https://www.janbrennenstuhl.eu/python-tuples-to-dict/</link><guid isPermaLink="true">https://www.janbrennenstuhl.eu/python-tuples-to-dict/</guid><pubDate>Tue, 28 Apr 2015 00:00:00 +0200</pubDate><description>&lt;p&gt;Using &lt;a href="https://github.com/docopt/docopt" class="ext"&gt;Python Docopt&lt;/a&gt; (for beautiful command-line interfaces) I recently had the problem of converting a list of tuples into a dictionary. In this post I&amp;rsquo;d like to document &lt;a href="https://gist.github.com/jbspeakr/c7c4fcb9ee143cf7e9eb" class="ext"&gt;the solution&lt;/a&gt;…&lt;/p&gt;
&lt;figure class="code" data-lang="python"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" aria-label="Code (python)" class="chroma"&gt;&lt;code data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;usage:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; mytool [options]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;options:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; --foo-bar=PARAM1,PARAM2,... params to do stuff with
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; [default: key1.value1,key1.value2,key2.value2]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;docopt&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;docopt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--foo-bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;,&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;convert_tuple_list_to_dictionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ignored_resources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;convert_tuple_list_to_dictionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_of_tuple&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dictionary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;list_of_tuple&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dictionary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dictionary&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;This code is able to convert the parameter value string &lt;code&gt;key1.value1,key1.value2,key2.value2&lt;/code&gt; into the following dictionary:&lt;/p&gt;
&lt;figure class="code" data-lang="bash"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" aria-label="Code (bash)" class="chroma"&gt;&lt;code data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;key1&amp;#39;&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;value1&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;value2&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;key2&amp;#39;&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;value2&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;If you know a leaner solution to solve this problem, let me know!&lt;/p&gt;</description></item><item><title>How to Migrate SVN to GIT with History?</title><link>https://www.janbrennenstuhl.eu/svn-to-git/</link><guid isPermaLink="true">https://www.janbrennenstuhl.eu/svn-to-git/</guid><pubDate>Wed, 15 Apr 2015 00:00:00 +0200</pubDate><description>&lt;p&gt;Today I&amp;rsquo;d like to share my solution for a common problem a lot of IT companies experience when finally moving from Subversion (SVN) to Git as their central version control system (VCS): Converting a SVN repository into a Git repo including its commit history and authors.&lt;/p&gt;
&lt;p&gt;To achieve this, I decided to split this issue into smaller pieces:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Extracting all authors of the SVN repository,&lt;/li&gt;
&lt;li&gt;Creating a proper authors file mapping SVN users to Git users,&lt;/li&gt;
&lt;li&gt;Converting SVN repo to Git including its history and authors.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here is what I did:&lt;/p&gt;
&lt;h2 id="converting-svn-to-git-w-history--authors"&gt;Converting SVN to GIT w/ History &amp;amp; Authors &lt;a class="heading-anchor" href="#converting-svn-to-git-w-history--authors" aria-label="Link to section: Converting SVN to GIT w/ History &amp;amp; Authors"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Checkout your SVN repository.&lt;/p&gt;
&lt;figure class="code" data-lang="bash"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" aria-label="Code (bash)" class="chroma"&gt;&lt;code data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;svn co SVN_REPO_URL&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Go into the repo directory and extract authors from the SVN repo &lt;a href="http://stackoverflow.com/questions/2159567/what-is-the-format-of-an-authors-file-for-git-svn-specifically-for-special-char" class="ext"&gt;using this statement&lt;/a&gt;.&lt;/p&gt;
&lt;figure class="code" data-lang="bash"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" aria-label="Code (bash)" class="chroma"&gt;&lt;code data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;svn log --xml &lt;span class="p"&gt;|&lt;/span&gt; grep author &lt;span class="p"&gt;|&lt;/span&gt; sort -u &lt;span class="p"&gt;|&lt;/span&gt; perl -pe &lt;span class="s1"&gt;&amp;#39;s/.&amp;gt;(.?)&amp;lt;./$1 = /&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Using this information, create an authors.txt file like this:&lt;/p&gt;
&lt;figure class="code" data-lang="bash"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" aria-label="Code (bash)" class="chroma"&gt;&lt;code data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# SVN_AUTHOR_HANDLE = GIT_AUTHOR_HANDLE &amp;lt;GIT_AUTHOR_EMAIL&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;jbrennenstuhl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; janbrennenstuhl &amp;lt;xyz@brnnnsthl.eu&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Using this file you now can start converting your SVN repo into a Git repo.&lt;/p&gt;
&lt;figure class="code" data-lang="bash"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" aria-label="Code (bash)" class="chroma"&gt;&lt;code data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git svn clone -A authors.txt SVN_REPO_URL&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;This will convert the old SVN repo into a new Git repo including the complete SVN history and mapping its authors following the mapping in authors.txt. Depending on the dimension of the SVN repo history, this can take several minutes or even hours!&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t want to have the entire SVN history you could choose to start from a specific revision like this:&lt;/p&gt;
&lt;figure class="code" data-lang="bash"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" aria-label="Code (bash)" class="chroma"&gt;&lt;code data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git svn clone -rSVN_REVISION:HEAD -A authors.txt SVN_REPO_URL&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Still using SVN? Start converting to Git now!&lt;/p&gt;</description></item><item><title>How to Check Github Licenses</title><link>https://www.janbrennenstuhl.eu/github-license-checker/</link><guid isPermaLink="true">https://www.janbrennenstuhl.eu/github-license-checker/</guid><pubDate>Wed, 25 Mar 2015 00:00:00 +0100</pubDate><description>&lt;p&gt;On Github, every organization with a fast growing open-source codebase quickly figures out a couple of shortcomings regarding the Github web interface. Especially, when it comes to Github organizations that are already around for some time, you often find some information missing that could probably help managing and controlling your organizations repositories&amp;hellip;&lt;/p&gt;
&lt;h2 id="github--licenses"&gt;Github &amp;amp; Licenses &lt;a class="heading-anchor" href="#github--licenses" aria-label="Link to section: Github &amp;amp; Licenses"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For months now, Github addresses in particular the often discussed license-problematic by implementing the &lt;a href="https://help.github.com/articles/open-source-licensing/" class="ext"&gt;License Picker&lt;/a&gt; feature and introducing the &lt;a href="http://choosealicense.com/" class="ext"&gt;choosealicense.com&lt;/a&gt; information page. That&amp;rsquo;s great if you start with new projects and repositories, but what&amp;rsquo;s missing is some kind of license overview and maintaining functionality that allows dealing with licenses of already existing repos.&lt;/p&gt;
&lt;p&gt;For that reason, I just built a small Python tool to help you getting an overview of your repos licenses: &lt;a href="https://github.com/jbspeakr/github-licker" class="ext"&gt;Github-Licker&lt;/a&gt;, a Github License Checker!&lt;/p&gt;
&lt;h2 id="github-license-checker"&gt;Github License Checker &lt;a class="heading-anchor" href="#github-license-checker" aria-label="Link to section: Github License Checker"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using the &lt;a href="https://developer.github.com/v3/" class="ext"&gt;Github API v3&lt;/a&gt; including the &lt;a href="https://developer.github.com/v3/licenses/" class="ext"&gt;Licenses API&lt;/a&gt; developer preview, Licker is able to check licenses of all repositories in your Github organization. It searches all your own repositories (no forks!) and especially helps identifying repos without license files.&lt;/p&gt;
&lt;figure class="code" data-lang="bash"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" aria-label="Code (bash)" class="chroma"&gt;&lt;code data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pip install github-licker
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;licker -h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;usage:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; licker &lt;span class="o"&gt;[&lt;/span&gt;TOKEN&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;options&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;options:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o, --organisation&lt;span class="o"&gt;=&lt;/span&gt;ORGANIZATION the Github organization you want to check &lt;span class="o"&gt;[&lt;/span&gt;default: ImmobilienScout24&lt;span class="o"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Github-Licker itself is licensed under &lt;a href="https://github.com/ImmobilienScout24/aws-monocyte/blob/master/LICENSE.txt" class="ext"&gt;Apache License, Version 2.0&lt;/a&gt;, for what reason you can easily &lt;a href="https://github.com/jbspeakr/github-licker" class="ext"&gt;help me to improve it&lt;/a&gt;!&lt;/p&gt;</description></item><item><title>AWS Monocyte: Let's Build a Cloud Immune System</title><link>https://www.janbrennenstuhl.eu/aws-cloud-immune-system/</link><guid isPermaLink="true">https://www.janbrennenstuhl.eu/aws-cloud-immune-system/</guid><pubDate>Wed, 18 Mar 2015 00:00:00 +0100</pubDate><description>&lt;p&gt;With Ireland and Frankfurt being available as AWS regions nowadays, Amazon (more or less) tried to extinguish EU and especially German legal concerns regarding storage and processing of privacy-related data. However, for European companies it remains difficult to prevent (accidental) usage of services and resources outside the EU, as there is still no standardized way to restrict AWS-account rights on this region-level. Especially in open, &lt;a href="https://en.wikipedia.org/wiki/DevOps" class="ext"&gt;DevOps&lt;/a&gt;-inspired company cultures this becomes a major issue:&lt;/p&gt;
&lt;h2 id="aws--ybiyri"&gt;AWS &amp;amp; YBIYRI &lt;a class="heading-anchor" href="#aws--ybiyri" aria-label="Link to section: AWS &amp;amp; YBIYRI"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The majority of European companies my colleagues and I recently interviewed about their AWS infrastructure, implemented (or plan to implement) some kind of multi-account setup, meaning that single product teams own their own accounts (which usually are aggregated via &lt;a href="http://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/consolidated-billing.html" class="ext"&gt;consolidated billing&lt;/a&gt;). One reason for this decision is (in addition to hard AWS API limits) that they try to implement the so-called &lt;em&gt;&amp;ldquo;You built it; You run it&amp;rdquo;&lt;/em&gt;-approach, which is also &lt;a href="https://www.safaribooksonline.com/library/view/programming-amazon-ec2/9781449303617/ch01s03.html" class="ext"&gt;propagated by Amazon&lt;/a&gt;. This trending approach implies that every AWS account is managed entirely and autonomously by its corresponding development team, which eventually is not anymore just responsible for building software components and products but also becomes responsible for operating the required infrastructure.&lt;/p&gt;
&lt;p&gt;This (of course!) leads to several conflicts of goals. On the one hand companies often want their teams to work with AWS independently and manage their own accounts mostly autonomously. On the other hand they are usually bound to legal frameworks (e.g. the &lt;a href="https://en.wikipedia.org/wiki/Data_Protection_Directive" class="ext"&gt;EU Data Protection Directive&lt;/a&gt; and/ or German privacy laws) and for that reason want to search and destroy non-EU AWS resources relentlessly.&lt;/p&gt;
&lt;h2 id="a-cloud-immune-system"&gt;A Cloud Immune System &lt;a class="heading-anchor" href="#a-cloud-immune-system" aria-label="Link to section: A Cloud Immune System"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To accomplish that, we built our own base-layer of what we call a cloud immune system: &lt;a href="https://github.com/ImmobilienScout24/aws-monocyte" class="ext"&gt;AWS Monocyte&lt;/a&gt;. The name Monocyte is related to a &lt;a href="https://en.wikipedia.org/wiki/Monocyte" class="ext"&gt;type of white blood cells&lt;/a&gt; that are part of e.g. a human&amp;rsquo;s innate immune system, the first line of defense being responsible for searching and destroying alien organisms to prevent unwanted infections.&lt;/p&gt;
&lt;p&gt;As &lt;a href="https://arnehilmann.github.io/" class="ext"&gt;Arne Hilmann&lt;/a&gt; and I explained during the &lt;a href="http://www.meetup.com/AWS-Berlin/events/220609022/" class="ext"&gt;AWS UserGroup Meetup&lt;/a&gt; this March at the Immobilien Scout HQ in Berlin, our tool is (in that biological sense) able to find and delete AWS resources in regions outside the EU. This allows companies to make sure that (accidentally) created non-EU resources are destroyed on a regular basis and helps in that way to (unfortunately reactively) enforce legal requirements.&lt;/p&gt;
&lt;p&gt;Right now, AWS Monocyte is able to handle just a subset of the plenty Amazon Services, including Cloudformation Stacks, EC2 Instances, EC2 Volumes, RDS Instances, RDS Snapshots, DynamoDB Tables and S3 Bucket. Implementing support for further Amazon Web Services is pretty easy and I hereby ask you to &lt;a href="https://github.com/ImmobilienScout24/aws-monocyte" class="ext"&gt;fork our project&lt;/a&gt; and help extending its power!&lt;/p&gt;</description></item></channel></rss>