<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Network Security on Brewed in the Cloud by Chris Hailes</title><link>https://blog.brewedinthecloud.com/tags/network-security/</link><description>Recent content in Network Security on Brewed in the Cloud by Chris Hailes</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Thu, 14 May 2026 00:00:00 +1000</lastBuildDate><atom:link href="https://blog.brewedinthecloud.com/tags/network-security/rss.xml" rel="self" type="application/rss+xml"/><item><title>When ‘Secure by Design’ Fails in Production</title><link>https://blog.brewedinthecloud.com/p/network-fails-secure-by-design-fails-in-production/</link><pubDate>Thu, 14 May 2026 00:00:00 +1000</pubDate><guid>https://blog.brewedinthecloud.com/p/network-fails-secure-by-design-fails-in-production/</guid><description>&lt;p&gt;“Secure by design” is one of those phrases that sounds definitive.&lt;/p&gt;
&lt;p&gt;It implies an end state that if you get the architecture right up front, security becomes a property of the system rather than something you constantly fight.&lt;/p&gt;
&lt;p&gt;In Azure production environments, that belief doesn’t just fail.&lt;br&gt;
It quietly dissolves, one well‑intentioned change at a time.&lt;/p&gt;
&lt;p&gt;Not because engineers forget how to design securely but because &lt;strong&gt;the assumptions that made the design secure are nobody’s job to preserve&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="the-mental-model"&gt;The Mental Model
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Common assumption:&lt;/strong&gt;&lt;br&gt;
Security fails when controls are missing, misconfigured, or ignored.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why it misleads:&lt;/strong&gt;&lt;br&gt;
In mature Azure estates, most controls exist.&lt;br&gt;
The failure happens elsewhere when &lt;strong&gt;security assumptions decay across organisational and technical boundaries&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;“Secure by design” assumes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stable ownership&lt;/li&gt;
&lt;li&gt;stable dependencies&lt;/li&gt;
&lt;li&gt;stable trust boundaries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of those survive scale.&lt;/p&gt;
&lt;h2 id="how-it-really-works"&gt;How It Really Works
&lt;/h2&gt;&lt;p&gt;In enterprise Azure environments, ownership is divided by &lt;strong&gt;control plane&lt;/strong&gt;, not by &lt;strong&gt;security outcome&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Typically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Network teams&lt;/strong&gt; own connectivity&lt;br&gt;
(VNets, peering, UDRs, Private Endpoints)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System administrators&lt;/strong&gt; own operations&lt;br&gt;
(VM access, patching, agents)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DBAs&lt;/strong&gt; own data platforms&lt;br&gt;
(SQL configuration, firewall rules)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application teams&lt;/strong&gt; own app behaviour and data usage&lt;br&gt;
(SDKs, outbound calls, feature flags)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security teams&lt;/strong&gt; own policy&lt;br&gt;
(standards, approvals, alerts not packet flow)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microsoft&lt;/strong&gt; owns the platform but not how you use it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everyone owns &lt;em&gt;something&lt;/em&gt;.&lt;br&gt;
No one owns &lt;strong&gt;the security assumptions that exist &lt;em&gt;between&lt;/em&gt; these domains&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Those assumptions include things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“This app cannot laterally reach other environments”&lt;/li&gt;
&lt;li&gt;“All outbound traffic is inspected”&lt;/li&gt;
&lt;li&gt;“Only intended consumers can reach this data store”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They are real.&lt;br&gt;
They are critical.&lt;br&gt;
And they are almost never explicitly owned.&lt;/p&gt;
&lt;h2 id="realworld-impact"&gt;Real‑World Impact
&lt;/h2&gt;&lt;p&gt;This is where “secure by design” fails in production.&lt;/p&gt;
&lt;p&gt;Not through a single bad change, but through &lt;strong&gt;coordinated innocence&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A network team enables VNet peering “for integration”&lt;/li&gt;
&lt;li&gt;A DBA adds a firewall exception “to unblock delivery”&lt;/li&gt;
&lt;li&gt;An app team introduces a new dependency “for a feature”&lt;/li&gt;
&lt;li&gt;A security team approves because policy conditions are met&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each change is defensible in isolation.&lt;br&gt;
Collectively, they invalidate the original threat model.&lt;/p&gt;
&lt;p&gt;At small scale, this works because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;people remember why things exist&lt;/li&gt;
&lt;li&gt;blast radius is limited&lt;/li&gt;
&lt;li&gt;informal review catches mistakes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At enterprise scale:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;assumptions outlive the people who made them&lt;/li&gt;
&lt;li&gt;undocumented dependencies become business‑critical&lt;/li&gt;
&lt;li&gt;trust paths multiply faster than anyone models them&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Security doesn’t fail loudly.&lt;br&gt;
It &lt;strong&gt;drifts&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="implementation-examples"&gt;Implementation Examples
&lt;/h2&gt;&lt;p&gt;Consider a common shared‑services pattern.&lt;/p&gt;
&lt;h3 id="original-design-intent"&gt;Original design intent
&lt;/h3&gt;&lt;div class="mermaid"&gt;graph TD
A[App VNet] --&gt;|Peering| B[Shared Services VNet]
B --&gt; C[Azure Firewall]
C --&gt; D[Internet]
&lt;/div&gt;
&lt;p&gt;Security assumption:&lt;br&gt;
&lt;strong&gt;All app egress is inspected and controlled centrally.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="two-years-later"&gt;Two years later
&lt;/h3&gt;&lt;div class="mermaid"&gt;graph TD
A[App VNet] --&gt;|Peering| B[Shared Services VNet]
A --&gt; E[Another App VNet]
E --&gt; F[Private Endpoint]
F --&gt; G[PaaS Service]
&lt;/div&gt;
&lt;p&gt;Nothing here violates Azure’s rules.&lt;/p&gt;
&lt;p&gt;But several assumptions are now false:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Traffic no longer consistently traverses the firewall&lt;/li&gt;
&lt;li&gt;DNS resolution now determines reachability&lt;/li&gt;
&lt;li&gt;App VNets can reach more than originally intended&lt;/li&gt;
&lt;li&gt;Firewall logs no longer represent the full picture&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A subtle but powerful example is VNet peering itself:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bicep" data-lang="bicep"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vnetPeering&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Microsoft.Network/virtualNetworks/virtualNetworkPeerings@2023-09-01&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;app-to-shared&amp;#39;&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="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;allowVirtualNetworkAccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;allowForwardedTraffic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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="w"&gt; &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="w"&gt;&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;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;That single flag establishes &lt;strong&gt;permanent lateral trust&lt;/strong&gt; unless explicitly revisited.&lt;/p&gt;
&lt;p&gt;It is rarely reviewed again — because no team owns the assumption it creates.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How would this change something I design, deploy, or operate?&lt;/strong&gt;&lt;br&gt;
You stop treating connectivity as reversible plumbing and start treating it as a long‑lived security commitment.&lt;/p&gt;
&lt;h2 id="gotchas--edge-cases"&gt;Gotchas &amp;amp; Edge Cases
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Private Endpoints don’t “break security” — they break assumptions&lt;/strong&gt;&lt;br&gt;
Especially when routing, DNS, and inspection models were designed first&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logging creates false confidence&lt;/strong&gt;&lt;br&gt;
Firewall and NSG flow logs only tell the truth about traffic they actually see&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Policy enforces existence, not intent&lt;/strong&gt;&lt;br&gt;
A rule can be compliant and still dangerous&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Temporary exceptions become structural dependencies&lt;/strong&gt;&lt;br&gt;
And nobody volunteers to remove them&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="best-practices"&gt;Best Practices
&lt;/h2&gt;&lt;p&gt;Not prescriptive steps — but mindset shifts that survive scale:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Assume security assumptions will decay&lt;/strong&gt;
Design review is not a one‑time event&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Treat network changes as security changes by default&lt;/strong&gt;
Even when raised by non‑security teams&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Minimise implicit trust&lt;/strong&gt;
Especially lateral trust created by peering and shared DNS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make connectivity decisions expensive to forget&lt;/strong&gt;
If you can’t explain &lt;em&gt;why&lt;/em&gt; it exists, it shouldn’t be permanent&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="insight"&gt;
&lt;div class="insight-icon"&gt;🍺&lt;/div&gt;
&lt;div class="insight-content"&gt;
&lt;strong&gt;Brewed Insight:&lt;/strong&gt; Secure‑by‑design fails not because no one owns security, but because &lt;strong&gt;no one is accountable for preserving security assumptions as the organisation fragments&lt;/strong&gt;. In Azure, drift isn’t an exception — it’s the default behaviour.
&lt;/div&gt;
&lt;/div&gt;
&lt;style&gt;
.insight {
display: flex;
align-items: center;
background-color: #0089e41c;
border-left: 10px solid #D69A2D;
padding: 10px;
margin: 20px 0;
border-radius: 4px;
}
.insight-icon {
font-size: 24px;
margin-right: 10px;
}
.insight-content {
flex: 1;
}
&lt;/style&gt;&lt;h2 id="learn-more"&gt;Learn More
&lt;/h2&gt;&lt;p&gt;The failure mode described here doesn’t come from a single misconfiguration, it emerges from how Azure architectures divide responsibility across teams.&lt;/p&gt;
&lt;p&gt;These references are useful not because they solve that problem, but because they implicitly assume it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Azure Shared Responsibility Model&lt;/strong&gt;&lt;br&gt;
Clearly defines where Microsoft’s responsibility ends and where ungoverned shared accountability begins.&lt;br&gt;
&lt;a class="link" href="https://learn.microsoft.com/azure/security/fundamentals/shared-responsibility" target="_blank" rel="noopener"
&gt;https://learn.microsoft.com/azure/security/fundamentals/shared-responsibility&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Azure Zero Trust Architecture (Azure)&lt;/strong&gt;&lt;br&gt;
Highlights the mismatch between static network trust and continuous verification models.&lt;br&gt;
&lt;a class="link" href="https://learn.microsoft.com/security/zero-trust/azure-infrastructure-overview" target="_blank" rel="noopener"
&gt;https://learn.microsoft.com/security/zero-trust/azure-infrastructure-overview&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hub‑Spoke Network Reference Architecture&lt;/strong&gt;&lt;br&gt;
A common starting point whose security assumptions rarely survive enterprise‑scale operation without explicit ownership.&lt;br&gt;
&lt;a class="link" href="https://learn.microsoft.com/azure/architecture/reference-architectures/hybrid-networking/hub-spoke" target="_blank" rel="noopener"
&gt;https://learn.microsoft.com/azure/architecture/reference-architectures/hybrid-networking/hub-spoke&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>