Skip to main content

Running non-root .NET containers with Kubernetes

Rootless or non-root Linux containers have been the most requested feature for the .NET container team. We recently announced that all .NET 8 container images will be configurable as non-root with a single line of code. This change is a welcome improvement in security posture. In that last post, I promised a follow-up on how to approach non-root hosting with Kubernetes. That’s what we’ll cover today.

You can try hosting a non-root container on your cluster with our non-root Kubernetes sample. It is part of a larger set of Kubernetes samples we’re working on.

This post will help you follow Kubernetes “Restricted” hardening best practices. Non-root hosting is a key requirement of the guidance.

runAsNonRoot

Most of what we’ll be discussing relates to the SecurityContext section of a Kubernetes manifest. It holds security configuration that is applied by Kubernetes.

    spec:
      containers:
      - name: aspnetapp
        image: dotnetnonroot.azurecr.io/aspnetapp
        securityContext:
          runAsNonRoot: true

This securityContext object validates that the container will be run with a non-root user. It really is as simple as that.

You can see this in a broader context in non-root.yaml.

runAsNonRoot tests that the user (via UID) is a non-root user (> 0), otherwise pod creation will fail. Kubernetes only reads container image metadata for this test. It doesn’t read /etc/passwd since that would require launching the container (which defeats the purpose of the test). That means that USER (in a Dockerfile) must be set by UID. It will fail if USER is set by name.

We can simulate this same test with docker inspect.

% docker inspect dotnetnonroot.azurecr.io/aspnetapp -f ""
64198

As you can see, our sample image sets the users by UID. However, whoami will still report the user as app.

runAsUser is a related setting, although not used in the example above. runAsUser should only be used if USER in the container image is unset, set by name rather than UID, or otherwise undesired. We’ve made it very easy to use the new app user as a UID, such that runAsUser should never be needed for .NET apps.

USER best practices

We recommend using the following pattern for setting USER in a Dockerfile.

USER $APP_UID

The USER instruction is often placed just before the ENTRYPOINT, although the order doesn’t matter.

This pattern results in the USER being set as a UID, while avoiding magic numbers in your Dockerfile. The environment variable already defines the UID value as declared by the .NET image.

You can see the environment variable set in .NET images.

% docker run mcr.microsoft.com/dotnet/runtime-deps:8.0-preview bash -c "export | grep UID"
declare -x APP_UID="64198"

Our non-root sample sets the user by UID according to this pattern. As a result, it works well with runAsNonRoot.

Non-root hosting in action

Let’s take a look at the experience of non-root container hosting using our non-root Kubernetes sample.

I’m using minikube for my local cluster, but any Kubernetes-compatible environment should work well with kubectl.

$ kubectl apply -f https://raw.githubusercontent.com/dotnet/dotnet-docker/main/samples/kubernetes/non-root/non-root.yaml
deployment.apps/dotnet-non-root created
service/dotnet-non-root created
$ kubectl get po
NAME                           READY   STATUS    RESTARTS   AGE
dotnet-non-root-68f4cd45c-687zp   1/1     Running   0          13s

The app is running. Let’s check the user.

$ kubectl exec dotnet-non-root-68f4cd45c-687zp -- whoami
app

We can also call an endpoint on the app. First, we need to create a proxy to it.

% kubectl port-forward service/dotnet-non-root 8080

We can now call the endpoint, which also reports the user as app.

% curl http://localhost:8080/Environment
{"runtimeVersion":".NET 8.0.0-preview.3.23174.8","osVersion":"Linux 5.15.49-linuxkit #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022","osArchitecture":"Arm64","user":"app","processorCount":4,"totalAvailableMemoryBytes":4124512256,"memoryLimit":0,"memoryUsage":35004416}

Delete the resources.

$ kubectl delete -f https://raw.githubusercontent.com/dotnet/dotnet-docker/main/samples/kubernetes/non-root/non-root.yaml
deployment.apps "dotnet-non-root" deleted
service "dotnet-non-root" deleted

At the time of writing, our official samples have not yet moved to use a non-root user. We’ll do that when we move the samples to .NET 8, probably with .NET 8 RC1. We can use our aspnetapp image to demonstrate what happens when runAsNonRoot is used with an image that uses root. It should fail, right?

I’ll change the manifest a bit. Let’s start without the securityContext section.

    spec:
      containers:
      - name: aspnetapp
        image: mcr.microsoft.com/dotnet/samples:aspnetapp

Let’s check the user.

$ kubectl apply -f non-root.yaml
deployment.apps/dotnet-non-root created
service/dotnet-non-root created
$ kubectl get po
NAME                            READY   STATUS    RESTARTS   AGE
dotnet-non-root-85768f6c55-pb5gh   1/1     Running   0          1s
$ kubectl exec dotnet-non-root-85768f6c55-pb5gh -- whoami
root

That’s expected. Now, let’s add runAsNonRoot back.

    spec:
      containers:
      - name: aspnetapp
        image: mcr.microsoft.com/dotnet/samples:aspnetapp
        securityContext:
          allowPrivilegeEscalation: false
          runAsNonRoot: true

Let’s see how this check works.

$ kubectl apply -f non-root.yaml
deployment.apps/dotnet-non-root created
service/dotnet-non-root created
$ kubectl get po
NAME                            READY   STATUS                       RESTARTS   AGE
dotnet-non-root-6df9cb77d8-74t96   0/1     CreateContainerConfigError   0          5s

That failed, which is what we wanted. We can get a bit more information on the reason.

$ kubectl describe po | grep Error
      Reason:       CreateContainerConfigError
  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)

We can try to kubectl exec to the pod, but it will fail. That demonstrates that Kubernetes blocked the container from being created (as the error states).

$ kubectl exec dotnet-non-root-6df9cb77d8-74t96 -- whoami
error: unable to upgrade connection: container not found ("aspnetapp")

dotnet-monitor

dotnet-monitor is a diagnostic tool for capturing diagnostic artifacts from running applications. We offer a dotnet/monitor container image for it. Does it work well with non-root hosting? Yes.

The hello-dotnet Kubernetes sample demonstrates both ASP.NET and dotnet-monitor running as non-root. It also goes on to demonstrate collecting Prometheus metrics data, both in the cloud and locally.

Summary

You can switch to non-root hosting in Kubernetes with a few straightforward configuration changes. Your app will be more secure and more resilient to attack. This approach also brings your app into compliance with Kubernetes Pod hardening best practices. It’s a small change with a big impact for defense in depth.

We hope that our container security initiative enables the entire .NET container ecosystem to switch to non-root hosting. We’re invested in .NET apps in the cloud being high-performance and safe.

To learn more about .NET 8 and other features coming to .NET head to dot.net/next.

The post Running non-root .NET containers with Kubernetes appeared first on .NET Blog.



source https://devblogs.microsoft.com/dotnet/running-nonroot-kubernetes-with-dotnet/

Comments

Popular posts from this blog

Fake CVR Generator Denmark

What Is Danish CVR The Central Business Register (CVR) is the central register of the state with information on all Danish companies. Since 1999, the Central Business Register has been the authoritative register for current and historical basic data on all registered companies in Denmark. Data comes from the companies' own registrations on Virk Report. There is also information on associations and public authorities in the CVR. As of 2018, CVR also contains information on Greenlandic companies, associations and authorities. In CVR at Virk you can do single lookups, filtered searches, create extracts and subscriptions, and retrieve a wide range of company documents and transcripts. Generate Danish CVR For Test (Fake) Click the button below to generate the valid CVR number for Denmark. You can click multiple times to generate several numbers. These numbers can be used to Test your sofware application that uses CVR, or Testing CVR APIs that Danish Govt provide. Generate

How To Iterate Dictionary Object

Dictionary is a object that can store values in Key-Value pair. its just like a list, the only difference is: List can be iterate using index(0-n) but not the Dictionary . Generally when we try to iterate the dictionary we get below error: " Collection was modified; enumeration operation may not execute. " So How to parse a dictionary and modify its values?? To iterate dictionary we must loop through it's keys or key - value pair. Using keys

How To Append Data to HTML5 localStorage or sessionStorage?

The localStorage property allows you to access a local Storage object. localStorage is similar to sessionStorage. The only difference is that, while data stored in localStorage has no expiration time untill unless user deletes his cache, data stored in sessionStorage gets cleared when the originating window or tab get closed. These are new HTML5 objects and provide these methods to deal with it: The following snippet accesses the current domain's local Storage object and adds a data item to it using Storage.setItem() . localStorage.setItem('myFav', 'Taylor Swift'); or you can use the keyname directly as : localStorage.myFav = 'Taylor Swift'; To grab the value set in localStorage or sessionStorage, we can use localStorage.getItem("myFav"); or localStorage.myFav There's no append function for localStorage or sessionStorage objects. It's not hard to write one though.The simplest solution goes here: But we can kee