{"id":51654,"date":"2024-04-30T10:05:00","date_gmt":"2024-04-30T17:05:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=51654"},"modified":"2024-04-30T11:12:52","modified_gmt":"2024-04-30T18:12:52","slug":"secure-your-container-build-and-publish-with-dotnet-8","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/secure-your-container-build-and-publish-with-dotnet-8\/","title":{"rendered":"Secure your container build and publish with .NET 8"},"content":{"rendered":"<p>.NET 8 raises the bar for container security for .NET container images and SDK tools. The SDK produces application images that align with <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/security\/pod-security-standards\/#restricted\">industry best practices and standards<\/a>, by default. We also offer additional security hardening with <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/announcing-dotnet-chiseled-containers\/\">Chiseled images<\/a> for an extra layer of protection.<\/p>\n<p><code>dotnet publish<\/code> will generate a container image for you and configure it as non-root by default. It&#8217;s easy with .NET to quickly improve the security posture of your production apps.<\/p>\n<p>In this post, you will learn how to:<\/p>\n<ul>\n<li>Produce non-root container images<\/li>\n<li>Configure Kubernetes pods to require non-root images<\/li>\n<li>Inspect images and containers<\/li>\n<li>Use <code>root<\/code> (or other users)<\/li>\n<\/ul>\n<p>This post is a continuation of <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/streamline-container-build-dotnet-8\/\">Streamline your container build and publish with .NET 8<\/a>, published earlier this month. It builds on <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/securing-containers-with-rootless\/\">Secure your .NET cloud apps with rootless Linux Containers<\/a> and <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/running-nonroot-kubernetes-with-dotnet\/\">Running non-root .NET containers with Kubernetes<\/a>, published last year.<\/p>\n<h2>Threat model<\/h2>\n<p>It&#8217;s good to start any security related conversation with a clear view of the threats at play.<\/p>\n<p>There are two primary threats to consider:<\/p>\n<ul>\n<li><a href=\"https:\/\/www.aquasec.com\/cloud-native-academy\/container-security\/container-escape\/\">Container breakout<\/a> &#8212; An attacker is able to break out of the container and execute operations within the host.<\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/Arbitrary_code_execution\">Remote Code Execution (RCE)<\/a> &#8212; An attacker is able to cause an app to execute operations within the container. Not specific to containers.<\/li>\n<\/ul>\n<blockquote>\n<p>Despite Docker not being marketed as sandboxing software, its default setup is meant to secure host resources from being accessed by processes inside of a container.<\/p>\n<\/blockquote>\n<p>That&#8217;s a great framing of the situation, from <a href=\"https:\/\/blog.dragonsector.pl\/2019\/02\/cve-2019-5736-escape-from-docker-and.html\">Escape from Docker and Kubernetes containers to root on host<\/a> (in reference to <a href=\"https:\/\/seclists.org\/oss-sec\/2019\/q1\/119\">CVE-2019-5736<\/a>). The author is saying that we&#8217;re collectively relying a lot on the &#8220;default setup&#8221; of the various container solutions we use, implying that container breakout is a real threat.<\/p>\n<p>From the same post, under &#8220;Mitigations&#8221;:<\/p>\n<blockquote>\n<p>Use a low privileged user inside the container<\/p>\n<\/blockquote>\n<p>Here, the author is effectively saying that you need to do your part to more safely rely on the pseudo-sandboxing nature of container solutions. If you don&#8217;t and another container breakout vulnerability is discovered, then part of the burden falls to developers hosting their apps as <code>root<\/code>. Put another way, &#8220;caveat emptor.&#8221;<\/p>\n<p>The security and vulnerability landscape can be tough to navigate at the best of times. Keeping dependencies up to date is the first and most critical mitigation to these risks, for both container host and guest. Non-root hosting is an excellent <a href=\"https:\/\/en.wikipedia.org\/wiki\/Defense_in_depth_(computing)\">defense in depth<\/a> measure that may protect against unknown future vulnerabilities.<\/p>\n<h2>Container ecosystem: <code>root<\/code> by default<\/h2>\n<p>Base images are configured as the <code>root<\/code> user by default.<\/p>\n<pre><code class=\"language-bash\">$ docker run --rm alpine whoami\nroot\n$ docker run --rm debian whoami\nroot\n$ docker run --rm ubuntu whoami\nroot\n$ docker run --rm mcr.microsoft.com\/dotnet\/runtime-deps:8.0 whoami\nroot<\/code><\/pre>\n<p>After just explaining that images should be configured as non-root as an important security measure, I&#8217;m demonstrating that most base images are published using <code>root<\/code>, including the ones we publish. Why?<\/p>\n<p>Usability trumps security for general-purpose base images and always will. It&#8217;s important that package management and other privileged operations are straightforward and that higher-level images can choose the user they want.<\/p>\n<p>However, it remains true that an attacker with an active RCE vulnerability will be able to do anything they want with <code>root<\/code> permission.<\/p>\n<p>In contrast, <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/announcing-dotnet-chiseled-containers\/\">Ubuntu Chiseled<\/a> and <a href=\"https:\/\/www.chainguard.dev\/chainguard-images\">Chainguard<\/a> base images are appliance-like, taking a different approach than general purpose images. They trade usability and compatibility for security. We endorse this design point.<\/p>\n<p>Note: See <a href=\"https:\/\/www.chainguard.dev\/unchained\/hardened-container-images-images-for-a-secure-supply-chain\">Hardened Container Images: Images for a Secure Supply Chain<\/a>.<\/p>\n<p>That&#8217;s a lot of context about base images and a great segue to application images, which (we think) should be built with a security-first philosophy.<\/p>\n<h2>.NET ecosystem: Non-root by default<\/h2>\n<p><code>dotnet publish<\/code> produces non-root images by default. Let&#8217;s take a look with a simple console app. I&#8217;m going to skip a number of steps that are covered in <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/streamline-container-build-dotnet-8\/\">Streamline your container build and publish with .NET 8<\/a>.<\/p>\n<p>This is the source code for the app.<\/p>\n<pre><code class=\"language-csharp\">using System.Runtime.InteropServices;\nConsole.WriteLine($\"Hello {Environment.UserName}, using {RuntimeInformation.OSDescription} on {RuntimeInformation.OSArchitecture}\");<\/code><\/pre>\n<p>It is very easy to produce a container image.<\/p>\n<pre><code class=\"language-bash\">$ dotnet publish -t:PublishContainer\nMSBuild version 17.9.8+b34f75857 for .NET\n  Determining projects to restore...\n  All projects are up-to-date for restore.\n  my-app -&gt; \/Users\/rich\/my-app\/bin\/Release\/net8.0\/my-app.dll\n  my-app -&gt; \/Users\/rich\/my-app\/bin\/Release\/net8.0\/publish\/\n  Building image 'my-app' with tags 'latest' on top of base image 'mcr.microsoft.com\/dotnet\/runtime:8.0'.\n  Pushed image 'my-app:latest' to local registry via 'docker'.<\/code><\/pre>\n<p>The app will say hello to the user that starts the process, as you can see from the source code.<\/p>\n<pre><code class=\"language-bash\">$ docker run --rm my-app\nHello app, using Debian GNU\/Linux 12 (bookworm) on Arm64<\/code><\/pre>\n<p>We see <code>Hello app<\/code>, as expected.<\/p>\n<p>We can also run <code>whoami<\/code> just like was done with the base images.<\/p>\n<pre><code class=\"language-bash\">$ docker run --rm --entrypoint bash my-app -c \"whoami\"\napp<\/code><\/pre>\n<p>As can be seen, this image is not using <code>root<\/code>, in contrast to the base images we looked at.<\/p>\n<p>Running <code>whoami<\/code> requires launching the image. Kubernetes doesn&#8217;t do that; it looks at container image metadata to determine the user.<\/p>\n<p>Let&#8217;s look at container metadata.<\/p>\n<pre><code class=\"language-bash\">$ docker inspect --format='{{.Config.User}}' my-app\n1654<\/code><\/pre>\n<p>The SDK sets the user via UID because that&#8217;s required by Kubernetes to enforce its <code>runAsNonRoot<\/code> property.<\/p>\n<p>We can look a bit more under the covers to see where the <code>1654<\/code> value comes from.<\/p>\n<pre><code class=\"language-bash\">$ docker run --rm --entrypoint bash my-app -c \"cat \/etc\/passwd | tail -n 1\"\napp:x:1654:1654::\/home\/app:\/bin\/sh\n$ docker inspect --format='{{.Config.Env}}' my-app\n[PATH=\/usr\/local\/sbin:\/usr\/local\/bin:\/usr\/sbin:\/usr\/bin:\/sbin:\/bin APP_UID=1654 ASPNETCORE_HTTP_PORTS=8080 DOTNET_RUNNING_IN_CONTAINER=true DOTNET_VERSION=8.0.4]\n$ docker run --rm --entrypoint bash my-app -c \"echo \\$APP_UID\"\n1654<\/code><\/pre>\n<p>We define a user called <code>app<\/code> and give it a UID &gt; 1000 to avoid <a href=\"https:\/\/en.wikipedia.org\/wiki\/User_identifier#Reserved_ranges\">reserved ranges<\/a>. <code>1654<\/code> is <code>1000<\/code> + the ASCII values of each of the characters in <code>dotnet<\/code>. We also set an environment variable &#8212; <code>APP_UID<\/code> &#8212; with this same value. That avoids anyone needing to remember or use this value (without the environment variable) for common scenarios.<\/p>\n<p>In a previous post, I included a set of fun <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/securing-containers-with-rootless\/#non-root-in-action\">non-root in action<\/a> demos. You can look at that post to go deeper.<\/p>\n<h2>Non-root Dockerfiles<\/h2>\n<p>The model with Dockerfiles is similar, but requires one extra step, setting the <code>USER<\/code> instruction.<\/p>\n<p>I&#8217;ll show you what that looks like, using this <a href=\"https:\/\/github.com\/dotnet\/dotnet-docker\/blob\/2746ed050286ed81b1b404def75c7c6d06c80bde\/samples\/dotnetapp\/Dockerfile\">sample Dockerfile<\/a>.<\/p>\n<p>That Dockerfile uses the environment variable we just looked at to define the user. This is the pattern we intend everyone to use, to switch to a non-root user with Dockerfiles. Again, this pattern avoids magic numbers being plastered everywhere and works best with Kubernetes.<\/p>\n<pre><code class=\"language-bash\">$ cat Dockerfile | tail -n 2 \nUSER $APP_UID\nENTRYPOINT [\".\/dotnetapp\"]<\/code><\/pre>\n<p>Note: Lots of developers will have already made their own user. Continuing with your own user or switching to the built-in one are both fine options.<\/p>\n<p>We can then build and run an image.<\/p>\n<pre><code class=\"language-bash\">$ docker build --pull -t my-app .\n$ docker run --rm my-app\n         42                                                    \n         42              ,d                             ,d     \n         42              42                             42     \n ,adPPYb,42  ,adPPYba, MM42MMM 8b,dPPYba,   ,adPPYba, MM42MMM  \na8\"    `Y42 a8\"     \"8a  42    42P'   `\"8a a8P_____42   42     \n8b       42 8b       d8  42    42       42 8PP!!!!!!!   42     \n\"8a,   ,d42 \"8a,   ,a8\"  42,   42       42 \"8b,   ,aa   42,    \n `\"8bbdP\"Y8  `\"YbbdP\"'   \"Y428 42       42  `\"Ybbd8\"'   \"Y428  \n\nOSArchitecture: Arm64\nOSDescription: Debian GNU\/Linux 12 (bookworm)\nFrameworkDescription: .NET 8.0.4\n\nUserName: app\nHostName : 8da0d81720f8\n\nProcessorCount: 8\nTotalAvailableMemoryBytes: 4113563648 (3.83 GiB)<\/code><\/pre>\n<p>As you can see, the application is running as the <code>app<\/code> user.<\/p>\n<p>The switch to enable non-root hosting (in Dockerfiles) is just a one line change.<\/p>\n<h2>Ubuntu Chiseled images<\/h2>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/announcing-dotnet-chiseled-containers\/\">Ubuntu Chiseled images<\/a> are appliance-like, providing a locked-down default experience. They are compatible with regular Ubuntu, however, they have sharp edges where whole sections of the operating system have been cut out. Notably, they are configured as non-root. That means that you don&#8217;t even have to configure the user since it is already set.<\/p>\n<p>You can inspect a chiseled image to see the user is set.<\/p>\n<pre><code class=\"language-bash\">$ docker inspect --format='{{.Config.User}}' mcr.microsoft.com\/dotnet\/runtime:8.0-jammy-chiseled\n1654<\/code><\/pre>\n<p>We have a different <a href=\"https:\/\/github.com\/dotnet\/dotnet-docker\/blob\/2746ed050286ed81b1b404def75c7c6d06c80bde\/samples\/dotnetapp\/Dockerfile.chiseled\">sample Dockerfile<\/a> that relies on the user being set in these images.<\/p>\n<pre><code class=\"language-bash\">$ cat Dockerfile.chiseled | tail -n 4\nFROM mcr.microsoft.com\/dotnet\/runtime:8.0-jammy-chiseled\nWORKDIR \/app\nCOPY --from=build \/app .\nENTRYPOINT [\".\/dotnetapp\"]<\/code><\/pre>\n<p>As you can see, the <code>USER<\/code> is not set in this Dockerfile. Let&#8217;s build and run it.<\/p>\n<pre><code class=\"language-bash\">$ docker build --pull  -t my-app -f Dockerfile.chiseled .\n$ docker run --rm my-app\n         42                                                    \n         42              ,d                             ,d     \n         42              42                             42     \n ,adPPYb,42  ,adPPYba, MM42MMM 8b,dPPYba,   ,adPPYba, MM42MMM  \na8\"    `Y42 a8\"     \"8a  42    42P'   `\"8a a8P_____42   42     \n8b       42 8b       d8  42    42       42 8PP!!!!!!!   42     \n\"8a,   ,d42 \"8a,   ,a8\"  42,   42       42 \"8b,   ,aa   42,    \n `\"8bbdP\"Y8  `\"YbbdP\"'   \"Y428 42       42  `\"Ybbd8\"'   \"Y428  \n\nOSArchitecture: Arm64\nOSDescription: Ubuntu 22.04.4 LTS\nFrameworkDescription: .NET 8.0.4\n\nUserName: app\nHostName : 293212d2eaba\n\nProcessorCount: 8\nTotalAvailableMemoryBytes: 4113563648 (3.83 GiB)<\/code><\/pre>\n<p>Again, the application is running as the <code>app<\/code> user. If you use chiseled images, you get better results and there is less to do in your Dockerfile.<\/p>\n<p>You can just as easily use Chiseled images with SDK publish.<\/p>\n<pre><code class=\"language-bash\">dotnet publish -t:PublishContainer -p:ContainerFamily=jammy-chiseled<\/code><\/pre>\n<p>This command will produce a non-root image, both because our Chiseled images are configured as non-root and <code>dotnet publish<\/code> creates non-root images by default.<\/p>\n<h2>Kubernetes<\/h2>\n<p>Kubernetes has a <a href=\"https:\/\/kubernetes.io\/docs\/reference\/generated\/kubernetes-api\/v1.24\/#securitycontext-v1-core\"><code>runAsNonRoot<\/code> mechanism<\/a> that is part of its <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/security\/pod-security-standards\/#restricted\">security standards<\/a>. When set (to <code>true<\/code>), Kubernetes will fail loading a pod manifest if a container image is <code>root<\/code>.<\/p>\n<p>I think of <code>runAsNonRoot<\/code> as a &#8220;roles and responsibilities&#8221; type feature. It is the role of the container image to set the user. It is the responsibility of the orchestrator to validate that the user is set as expected, as non-root.<\/p>\n<p>Recall the &#8220;like Kubernetes does it&#8221; look at container metadata earlier.<\/p>\n<pre><code class=\"language-bash\">$ docker inspect --format='{{.Config.User}}' my-app\n1654<\/code><\/pre>\n<p>Kubernetes doesn&#8217;t use <code>docker inspect<\/code> but the idea is the same. It looks at this same <code>User<\/code> value, determines if the value is a UID, and if so does a <code>value &gt; 0<\/code> check. If that expression resolves to true, then the <code>runAsNonRoot<\/code> check passes. For context <code>root<\/code> has a UID of <code>0<\/code>, so this check is the analog of <code>user != root<\/code>.<\/p>\n<p>Let&#8217;s take a quick look at how non-root works with Kubernetes. There is much more detail in <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/running-nonroot-kubernetes-with-dotnet\/\">Running non-root .NET containers with Kubernetes<\/a> if you want to learn more.<\/p>\n<p>Here&#8217;s an example of how to set <code>runAsNonRoot<\/code> in a Pod manifest.<\/p>\n<pre><code class=\"language-yaml\">    spec:\n      securityContext:\n        runAsNonRoot: true\n      containers:\n      - name: aspnetapp\n        image: mcr.microsoft.com\/dotnet\/samples:aspnetapp-chiseled\n        ports:\n        - containerPort: 8080<\/code><\/pre>\n<p>In this example, every container listed (even though there is only one in the example) must be non-root. <code>securityContext<\/code> can also be set on a container. You can see these settings in broader context in <a href=\"https:\/\/github.com\/dotnet\/dotnet-docker\/blob\/0535bf7c536483266d36066bf2e87115106f2985\/samples\/kubernetes\/non-root\/non-root.yaml#L15-L16\">non-root.yaml<\/a>.<\/p>\n<p>It&#8217;s really only interesting to see what happens if <code>runAsNonRoot<\/code> is set to <code>true<\/code> and we try to load an image that uses the <code>root<\/code> users.<\/p>\n<p>At the time of writing, the <code>mcr.microsoft.com\/dotnet\/samples:aspnetapp-chiseled<\/code> image (used above) is configured as non-root and the <code>mcr.microsoft.com\/dotnet\/samples:aspnetapp<\/code> is <code>root<\/code>. I&#8217;ll change the <code>image<\/code> value in the manifest to <code>mcr.microsoft.com\/dotnet\/samples:aspnetapp<\/code> and then see if the load fails.<\/p>\n<pre><code class=\"language-bash\">$ kubectl apply -f non-root.yaml\ndeployment.apps\/dotnet-non-root created\nservice\/dotnet-non-root created\n$ kubectl get po\nNAME                            READY   STATUS                       RESTARTS   AGE\ndotnet-non-root-6df9cb77d8-74t96   0\/1     CreateContainerConfigError   0          5s<\/code><\/pre>\n<p>As you can see, the load fails.<\/p>\n<p>Digging a little deeper, we can see the cause.<\/p>\n<pre><code class=\"language-bash\">$ kubectl describe po | grep Error\n      Reason:       CreateContainerConfigError\n  Warning  Failed     7s (x2 over 8s)  kubelet            Error: container has runAsNonRoot and image will run as root (pod: \"dotnet-non-root-6df9cb77d8-74t96_default(d4df0889-4a69-481a-adc4-56f41fb41c63)\", container: aspnetapp)<\/code><\/pre>\n<blockquote>\n<p>Error: container has runAsNonRoot and image will run as root<\/p>\n<\/blockquote>\n<p>That matches expectations. Good.<\/p>\n<h2>Change the user to <code>root<\/code><\/h2>\n<p>There may be cases where the user needs to be set to <code>root<\/code>. That&#8217;s straightforward to do.<\/p>\n<p>It is possible (using Docker) to run a command as <code>root<\/code> in a running container with <code>docker exec -u<\/code>. The command will be often be <code>bash<\/code>, but we&#8217;ll use <code>whoami<\/code> since it offers a better demonstration.<\/p>\n<pre><code class=\"language-bash\">$ docker exec 5d56a4a1cb97 whoami\napp\n$ docker exec -u root 5d56a4a1cb97 whoami\nroot<\/code><\/pre>\n<p>Note that <code>kubectl exec<\/code> doesn&#8217;t offer a <code>-u<\/code> argument (for good reason).<\/p>\n<p>Similarly, a container can be started with a specific user, overriding the user set in image metadata.<\/p>\n<pre><code class=\"language-bash\">$ docker run --rm -it -u root myapp\nHello root, using Debian GNU\/Linux 12 (bookworm) on X64<\/code><\/pre>\n<p>Last, a specific user can be used when building the image, with <code>ContainerUser<\/code>.<\/p>\n<pre><code class=\"language-bash\">$ dotnet publish -t:PublishContainer -p:ContainerUser=root\n  Building image 'myapp' with tags 'latest' on top of base image 'mcr.microsoft.com\/dotnet\/runtime:8.0'.\n  Pushed image 'myapp:latest' to local registry via 'docker'.\n$ docker run --rm -it myapp\nHello root, using Debian GNU\/Linux 12 (bookworm) on X64<\/code><\/pre>\n<p>The <code>ContainerUser<\/code> specified needs to exist.<\/p>\n<pre><code class=\"language-bash\">$ dotnet publish -t:PublishContainer -p:ContainerUser=rich\n  Building image 'myapp' with tags 'latest' on top of base image 'mcr.microsoft.com\/dotnet\/runtime:8.0'.\n  Pushed image 'myapp:latest' to local registry via 'docker'.\n$ docker run --rm -it myapp\ndocker: Error response from daemon: unable to find user rich: no matching entries in passwd file.<\/code><\/pre>\n<p>You can, however, use a valid UID.<\/p>\n<pre><code class=\"language-bash\">$ dotnet publish -t:PublishContainer -p:ContainerUser=1654\n  Building image 'myapp' with tags 'latest' on top of base image 'mcr.microsoft.com\/dotnet\/runtime:8.0'.\n  Pushed image 'myapp:latest' to local registry via 'docker'.\n$ docker run --rm myapp\nHello app, using Debian GNU\/Linux 12 (bookworm) on X64<\/code><\/pre>\n<p>As you can see, both the <code>root<\/code> and <code>app<\/code> users are defined in the container images we publish.<\/p>\n<pre><code class=\"language-bash\">$ docker run --rm mcr.microsoft.com\/dotnet\/runtime-deps bash -c \"cat \/etc\/passwd | head -n 1\"\nroot:x:0:0:root:\/root:\/bin\/bash\n$ docker run --rm mcr.microsoft.com\/dotnet\/runtime-deps bash -c \"cat \/etc\/passwd | tail -n 1\"\napp:x:1654:1654::\/home\/app:\/bin\/sh<\/code><\/pre>\n<h2>Closing<\/h2>\n<p>The user for production apps is a key part of any security plan. Unfortunately, it is easy to miss since everything works without specifying the user. In fact, it works too well. One could say this is the root of the problem.<\/p>\n<p>Adding a user to a Dockerfile is easy. Creating end-to-end workflows that reliably establish the desired security outcomes is a lot harder. As you can see, it is now straightforward to produce non-root container images, with <code>dotnet publish<\/code> or with Dockerfiles. The images will work correctly with Kubernetes security features, which is critical in enforcing your desired security policies.<\/p>\n<p>There will always be additional security settings that are needed. Non-root hosting is one of the most impactful changes you can make.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>.NET 8 has new security features for containers, including non-root images and SDK tools. Discover how to create non-root container images, configure Kubernetes pods, and inspect images and containers for enhanced security.<\/p>\n","protected":false},"author":1312,"featured_media":51683,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,7689,7237],"tags":[7828,7174,57,7829,7827],"class_list":["post-51654","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-cloud-native","category-containers","tag-net-sdk","tag-cli","tag-containers","tag-dotnet-cli","tag-sdk"],"acf":[],"blog_post_summary":"<p>.NET 8 has new security features for containers, including non-root images and SDK tools. Discover how to create non-root container images, configure Kubernetes pods, and inspect images and containers for enhanced security.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/51654","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/1312"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=51654"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/51654\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/51683"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=51654"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=51654"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=51654"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}