当前位置: 首页 > Linux

使用无根Linux容器保护您的.NET云应用程序

时间:2023-04-07 00:50:53 Linux

从.NET8开始,我们所有的Linux容器映像都将包含一个非根用户。只需一行代码,即可以非根用户身份托管您的.NET容器。这种平台级别的更改将使您的应用程序更加安全,并使.NET成为最安全的开发人员生态系统之一。这是一个很小的变化,但对深度防御有巨大的影响。此更改的灵感来自我们早期在UbuntuChiseled容器中启用.NET的项目。Chiseled(又名“distroless”)图像被设计为像设备一样运行,因此非root是这些图像最简单的设计选择。我们意识到我们可以将Chiseled容器的非根功能应用于我们发布的所有容器镜像。通过这样做,我们提高了.NET容器映像的安全标准。本文介绍非根容器的优势、创建它们的工作流程以及它们的工作原理。在后续的文章中,我们还将讨论如何在Kubernetes中更好地使用这些镜像。或者,如果您想要更简单的选择,请查看.NETSDK的内置容器支持。最小权限以非root身份托管容器符合最小权限原则。这是操作系统提供的免费安全性。如果应用程序以root身份运行,应用程序进程可以在容器中执行任何操作,例如修改文件、安装包或运行任意可执行文件。如果您的应用程序受到威胁,这是一个隐患。但是如果你以非root身份运行你的应用程序,你的应用程序进程将不能做太多事情,这极大地限制了攻击者的恶意操作。非根容器也可以被视为对安全供应链的贡献。通常,人们从防止不良依赖更新或排除组件源故障的角度谈论安全供应链。非根容器在两者之??后。如果您的进程中出现不良依赖项(这种情况很可能发生),非根容器可能是最好的最后一道防线。也是出于这个原因,Kubernetes加固最佳实践要求以非root用户身份运行容器。关于应用程序的一点点我们所有以.NET8开头的Linux映像都将包含一个应用程序用户。应用程序用户将能够运行您的应用程序,但无法删除或更改容器映像中的任何文件(除非您明确允许它这样做)。命名也是不言自明的,应用程序用户除了运行您的应用程序外几乎不能做任何事情。这个应用程序用户实际上并不是新用户。它与我们用于UbuntuChiseled图像的程序相同。这是一个关键的设计点。从.NET8开始,我们所有的Linux容器镜像都将包含应用程序用户。这意味着您可以在我们提供的图像之间切换,并且user和uid是相同的。接下来我将描述dockerCLI的新体验。$dockerrun--rmmcr.microsoft.com/dotnet/aspnet:8.0-previewcat/etc/passwd|tail-n1app:x:64198:64198::/home/app:/bin/sh这个在镜像/etc/passwd文件的最后一行。这是Linux用来管理用户的文件。根据行业指南,我们选择了一个相对较高的uid,接近2^16。我们还决定这个用户应该有一个主目录。$dockerrun--rm-uappmcr.microsoft.com/dotnet/aspnet:8.0-previewbash-c"cd&&pwd"/home/app我们看了一下,发现Node.js、Ubuntu23.04+和Chainguard都在同一个计划中。好的!$dockerrun--rmnodecat/etc/passwd|tail-n1node:x:1000:1000::/home/node:/bin/bash$docker运行ubuntu:lunarcat/etc/passwd|tail-n1ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash$catout/layers/ruby/etc/passwd|tail-n1nonroot:x:65532:65532:apko创建的账户:/home/nonroot:/bin/sh最后一个是链卫士镜像(Chainguard)。这些图像的结构不同,因此使用不同的模式。每个人都可以创建自己的用户。关键是要避免重叠,尤其是UID重叠。容器镜像中有很多用户,但没有一个适合这个用例。减少用户数量是件好事,但不太可能,这也是使用distroless/Chiseled镜像的好处之一。Windows容器已经具有非管理员功能和ContainerUser用户。我们选择不将应用程序添加到Windows容器映像。您应该遵循Windows团队关于如何最好地保护Windows容器映像的指南。使用“非root用户”应用程序:使用单行USER指令将您的容器配置为非root用户。Docker和Kubernetes可以轻松指定用于容器的用户。这是一条单行指令。根据我们的定义,“非root用户”意味着您可以使用单个命令切换到非root用户。这是非常强大的,因为简单的一行指令消除了任何不安全运行的理由。注意:aspnetapp始终用作您的应用程序的替代品。您可以使用-u$dockerrun--rm-uappmcr.microsoft.com/dotnet/runtime-deps:8.0-previewwhoamiapp通过CLI设置用户通过CLI指定用户很好,但更多用于测试或诊断协议。制作应用时最好在Dockerfile中定义USER为username或uid。作为用户:USERapp作为UID:USER64198我们正在为UID添加环境变量。这将启用以下模式。USER$APP_UID我们认为这种模式是最佳实践,因为它可以清楚地表明您正在使用哪个用户,避免重复的幻数,并使用UID,如果您使用Kubernetes,所有这些都可以很好地工作。如果您什么都不做,一切都会和以前一样,您的映像将继续以root身份运行。我们希望您采取额外的步骤以应用程序用户身份运行您的容器。您可能想知道为什么默认情况下我们不切换到非根用户。切换到端口8080这个项目最大的症结是我们暴露的端口。事实上,这是一个如此困难的问题,我们不得不做出重大改变。我们已决定对未来所有容器镜像的端口进行标准化。这个决定是基于我们早期使用Chiseled图像的经验,它已经在端口8080上侦听,并且所有图像现在都匹配。但是,ASP.NET核心应用程序(使用我们的.NET7和更早版本的容器映像)侦听端口80。问题是端口80是需要权限的特权端口(至少在某些地方)。本质上这与非根容器不兼容。您可以在我们的图像中看到端口是如何配置的。对于.NET8:$dockerrun--rmmcr.microsoft.com/dotnet/aspnet:8.0-previewbash-c"export|grepASPNETCORE"declare-xASPNETCORE_HTTP_PORTS="8080"对于.NET7及更早版本:$dockerrun--rmmcr.microsoft.com/dotnet/aspnet:7.0bash-c"export|grepASPNETCORE"declare-xASPNETCORE_URLS="http://+:80"接下来,您需要更改端口映射。您可以通过CLI执行此操作。您需要在地图的右侧设置8080。左边的可以匹配,也可以是其他值。dockerrun--rm-it-p8080:8080aspnetapp有些用户可能希望继续使用端口80和root。没问题,你仍然可以做到。您可以在Dockerfile中或通过CLI重新定义ASPNETCORE_HTTP_PORTS。对于Dockerfile:ENVASPNETCORE_HTTP_PORTS=80对于DockerCLI:dockerrun--rm-eASPNETCORE_HTTP_PORTS=80-p8000:80aspnetapp.NET8Windows容器映像也使用端口8080。>dockerrun--rmmcr.microsoft.com/dotnet/aspnet:8.0-preview-nanoserver-ltsc2022cmd/c"set|findstrASPNETCORE"ASPNETCORE_HTTP_PORTS=8080ASPNETCORE_HTTP_PORTS是一个新的环境变量,用来指定ASP.NETcore(实际上是Kestrel应该侦听的端口(或多个端口)。它需要一个以分号分隔的端口值列表。.NET8图像使用这个新的环境变量而不是ASPNETCORE_URLS(在.NET6和7图像中使用)。ASPNETCORE_URLS仍然是一个有用的高级功能。它可以在一个配置中同时指定原始HTTP和TLS端口,并覆盖ASPNETCORE_HTTP_PORTS和ASPNETCORE_HTTPS_PORTS。非root操作让我们从几个不同的角度看一下非root是什么样的,以便您更好地了解到底发生了什么。我在WSL22中使用Ubuntu10.2。我们添加了一个Dockerfile,因此您可以自己尝试这个解决方案。它将容器配置为始终作为应用程序运行。它使用我们的aspnetapp示例。$pwd/家/rich/git/dotnet-docker/samples/aspnetapp$catDockerfile.alpine-non-root|tail-n2USERappENTRYPOINT["./aspnetapp"]$dockerbuild--pull-taspnetapp-fDockerfile.alpine-non-root。让我们看看是否可以观察到用户的行为,这在Dockerfile中已经设置好了。$dockerrun--rm-d-p8000:8080aspnetapp5bde77feebdf76ff370815f41a8989a880d51a4037c91e2ac8c6f2c269b759ad$curlhttp://localhost:8000/Environment{"runtimeVersion":.NET8.0.0-preview.2.3"2:"3"LVersion.3"5.15.90.1-microsoft-standard-WSL2#1SMPFriJan2702:56:13UTC2023","osArchitecture":"X64","user":"app","processorCount":16,"totalAvailableMemoryBytes":67429986304,"memoryLimit":9223372036854771712,"memoryUsage":30220288}$dockerexec5bde77feebdf76ff370815f41a8989a880d51a4037c91e2ac8c6f2c269b759adls-ltotal188-rw-r--r--1rootroot127Jan2017:14appsettings.Development.json-rw-r--r--1rootroot151Oct1921:59appsettings.json-rwxr-xr-x1rootroot78320Mar1616:51aspnetapp-rw-r--r--1rootroot463Mar1616:51aspnetapp.deps.json-rw-r--r--1rootroot51200Mar1616:51aspnetapp.dll-rw-r--r--1rootroot35316Mar1616:51aspnetapp.pdb-rw-r--r--1rootroot469Mar1616:51aspnetapp.runtimeconfig.jsondrwxr-xr-x5根40963月16日16:51wwwwoot$dockerexec5bde77feebdf76ff76ff37081515f415f41a898989898989880d1a40d1a400d37c91epid22c61epid2c61acmantanapd22act2c91e2ac8c22actbbd2ac8c22actbbd2acanp。端点返回的JSON内容中的User属性您可以看到应用程序作为app运行并且文件归root所有。这意味着应用程序文件受到保护,不会被该用户更改。这种分离是我们继续以root身份发布图像的原因之一。如果我们将它们作为应用程序分发,那么默认情况下您的应用程序二进制文件将不会受到应用程序用户的保护。如果我们将图像作为应用程序发布,您仍然可以实现这种分离,但是您的Docker文件(以及我们的)将因大量用户切换而变得混乱,但无济于事。我们认为,基础镜像创建者应该完全以root身份发布平台镜像。这是唯一好的通用模型。应用程序的二进制文件由root拥有,因为它们是在以root用户身份运行的构建/SDK阶段生成的。不是因为在docker文件中最后一次COPY后用户被改成了app。请注意,COPY具有语义。请参阅Dockerfile:所有新文件和目录都使用UID和GID0创建,除非可选的-chown标志指定给定的用户名、组名或UID/GID组合以要求拥有复制内容的特定所有权。让我们在同一个容器上使用dockerexec在此容器上尝试一些有根操作。$dockerexec5bde77feebdf76ff370815f41a8989a880d51a4037c91e2ac8c6f2c269b759adrmaspnetapp.pdbrm:can'tremove'aspnetapp.pdb':Permissiondenied$dockerexec5bde77feebdf76ff370815f41a8989a880d51a4037c91e2ac8c6f2c269b759adtouch/filetouch:/file:Permissiondenied$dockerexec5bde77feebdf76ff370815f41a8989a880d51a4037c91e2ac8c6f2c269b759adwhichdotnet/usr/bin/dotnet$dockerexec5bde77feebdf76ff370815f41a8989a880d51a4037c91e2ac8c6f2c269b759adrm/usr/bin/dotnetrm:无法删除'/usr/bin/dotnet':权限被拒绝$dockerexec5bde77feebdf76ff370815f41a8989a880d51a4037c91e2ac8c6f2c269b759adapkaddcurlERROR:无法锁定数据库:权限被拒绝ERROR:权限被拒绝:apk数据库被拒绝被拒绝。这正是我们想要的。让我们再试一次,但提升到root。$dockerexec-uroot5bde77feebdf76ff370815f41a8989a880d51a4037c91e2ac8c6f2c269b759adash-c"rmaspnetapp.pdb&&lsaspnetapp.pdb"ls:aspnetapp.pdb:Nosuchfileordirectory$dockerexec-uroot5bde77feebdf76ff370815f41a8989a880d51a4037c91e2ac8c6f2c269b759adash-c"touch/file&&ls/file"/file$dockerexec-uroot5bde77feebdf76ff370815f41a8989a880d51a4037c91e2ac8c6f2c269b759adash-c"rm/usr/bin/dotnet&&ls/usr/bin/dotnet"ls:/usr/bin/dotnet:没有这样的文件或目录$dockerexec-uroot5bde77feebdf76ff370815f41a8989a880d51a4037c91e2ac8c6f2c269b759adapk添加curlfetchhttps://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/APKINDEX.tar.gzfetchhttps://dl-cdn.alpinelinux.org/1/x86_64/APKINDEX.tar.gz(1/4)安装brotli-libs(1.0.9-r9)(2/4)安装nghttp2-libs(1.51.0-r0)(3/4)安装libcurl(7.87.0-r2)(4/4)安装curl(7.87.0-r2)执行busybox-1.35.0-r29.triggerOK:14MiBin28packages你可以看到root可以做的更多,事实上,它可以做任何它想做的事安装curl后,攻击者可以从他们的任何网络服务器上做开始执行脚本。在Alpinelinux上,它附带wget,它删除了链中的一个步骤。请注意,我在这里使用的是Alpine,因此使用ash而不是bash作为shell,但这不会改变演示的任何内容。当然,解决方法是删除root用户以避免这些风险。然而,事实上,除非您采用我们的Chiseled图像,否则删除root用户会产生未定义的行为。最好的选择是以非root用户身份运行,这可以通过明确定义的机制消除一整类攻击。使用dockerexec-uroot可能看起来很吓人。如果攻击者可以在您正在运行的容器上运行dockerexec-uroot,则意味着他们已经可以访问主机。sudo不包含在我们的镜像中,永远不会。$dockerexec5BDE77FEEBDF76FF370815F41A8989A8880D51A4037C91E2AC8C6F2C269B759ADSUDOOOCIundoociRuntimeruntimeruntimeexecexecexecexecexecexecexecexecexecexecexecexecexecexecexecexecexecexecexecexecexecexecexecexecexecexpectprocess:exec:exec..有两个方面需要考虑:端口和用户。一些容器服务提供比Kubernetes更高级的体验,需要不同的配置选项。AzureAppService要求WEBSITES_PORT使用端口80以外的端口。它可以通过CLI或在门户中进行设置。Azure容器应用程序允许在资源创建期间更改端口。Azure容器实例允许在资源创建期间更改端口。这些服务都没有提供明显的方式来改变用户。如果您在Dockerfile中设置您的用户(这是最佳实践),则您不需要此功能。下一步下一步是调查非root的潜在挑战情况,例如诊断场景。一些示例使用dockerexec-uroot。这在本地运行良好,但kubectlexec不提供用户参数。我们还将在以后的文章中更深入地研究非根Kubernetes工作流。我们还将继续与容器托管服务合作,以确保.NET开发人员可以轻松迁移到.NET8容器映像,尤其是那些提供更高级别体验的容器映像,例如AzureAppService。我们.NET团队使命的一个关键部分是纵深防御。每个人都需要考虑安全性,但是,我们的业务是通过单个更改或功能来关闭整个类别的攻击。大约十年前,当我们第一次开始运输容器图像时,这种变化是可能的。多年来,我们一直被要求提供非根指南和非根容器镜像。老实说,我们真的不知道如何处理这个问题,主要是因为我们使用的模式在我们开始时并不存在。在安全容器托管方面,没有一位领导者是我们可以学习的。正是我们与Canonical合作处理轮廓分明的图像的经验促使我们发现并塑造了这种方法。我们希望此举将使整个.NET容器生态系统切换到非根托管。我们致力于使.NET应用程序在云中具有更高的性能和安全性。