一、镜像的检测
镜像中包含程序,其中也可能包含漏洞,所以我们需要检查镜像中是否存在漏洞。
首先,准备一台装有docker的设备,提前拉取nginx和redis镜像做检查。
docker pull nginx
docker pull redis
接着下载Trivy这个工具。Trivy是一个简单而全面的扫描器,用于检测容器镜像、文件系统和 Git 存储库中的漏洞,以及配置问题。 Trivy检测操作系统包(Alpine、RHEL、CentOS 等)和特定语言包(Bundler、Composer、npm、yarn 等)的漏洞。此外,Trivy扫描基础设施即代码 (IaC) 文件,例如 Terraform、Dockerfile 和 Kubernetes,以检测可能使我们的部署面临攻击风险的潜在配置问题。 Trivy易于使用。只需安装二进制文件,我们就可以开始扫描了。安装步骤具体请参考:https://aquasecurity.github.io/trivy/v0.18.3/installation/
这里以Centos宿主机为例子。将仓库设置添加到/etc/yum.repos.d
[trivy]
name=Trivy repository
baseurl=https://aquasecurity.github.io/trivy-repo/rpm/releases/$releasever/$basearch/
gpgcheck=0
enabled=1
安装trivy:
yum -y update
yum -y install trivy
Trivy工作的原理如下:读取互联网上CVE(Common Vulnerabilities and Exposures)库,CVE中记录了漏洞的信息。然后Trivy会下载这些信息到本地数据库中。然后当我们检测时,Trivy会读取其中的信息,并且与本地的数据库进行对比,从而判断出此程序是否存在漏洞。接下来,我们分别使用trivy检查下载的两个镜像:
首先检查nginx镜像:
trivy image nginx
部分截图,可以看到其中存在83个low risk漏洞,6个medium risk,6个high risk和4个critical的漏洞:
检查redis镜像:
trivy image redis
高危的漏洞不多,主要是56个low risk的漏洞。那么可以看到,redis相对于nginx,漏洞会少一些。
我们后续可以根据检测的结果,决定哪些漏洞需要进行修补。
二、DockerFile的优化
DcokerFileke可以帮助我们在原始镜像上进行自定义修改或者更新原有镜像。DockerFile的原理和语法请参考:官网文档https://docs.docker.com/develop/develop-images/dockerfile_best-practices/或总结的笔记https://blog.csdn.net/tushanpeipei/article/details/115837183
DockerFile的优化原则:越小越好、提高安全性。具体的方式有:
1.减少层数:
尽量减少镜像中的层数以确保它们的性能非常重要。添加了以下功能以减少此限制:只有指令RUN, COPY,ADD创建层。其他指令会创建临时中间镜像,并且不会增加构建的大小,也就是说多个命令放在一个RUN 里写,比多个RUN要小。
2.清理无用数据:
清理缓存可以有效减少镜像的大小。例如我们可以可以构建镜像是加上如下命令:
yum clean all && rm -rf /var/cache/yum/*
3.多阶段构建:
多阶段构建能够帮助我们大幅减少最终镜像的大小,而无需费力减少中间层和文件的数量。因为镜像是在构建过程的最后阶段构建的,所以我们可以通过利用构建缓存来最小化镜像层。例如,如果我们的构建包含多个层,可以将它们从更改较不频繁(以确保构建缓存可重用)到更改较频繁的排序:
- 安装构建应用程序所需的工具;
- 安装或更新库依赖项;
- 生成我们的应用程序。
Go 应用程序的 Dockerfile 可能如下所示:
# syntax=docker/dockerfile:1
FROM golang:1.16-alpine AS build
# Install tools required for project
# Run `docker build --no-cache .` to update dependencies
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep
# List project dependencies with Gopkg.toml and Gopkg.lock
# These layers are only re-built when Gopkg files are updated
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
# Install library dependencies
RUN dep ensure -vendor-only
# Copy the entire project and build it
# This layer is rebuilt when a file changes in the project directory
COPY . /go/src/project/
RUN go build -o /bin/project
# This results in a single layer image
FROM scratch
COPY --from=build /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
CMD ["--help"]
其中涉及到2个镜像的构建。我们将第一个临时镜像命名为build。最终镜像使用–from=build,从第一个临时的镜像中拷贝信息到本地目录。那么最终镜像其实就这由COPY创建的一层镜像。构建完成后,我们只需要删除第一个临时镜像即可。
4.利用构建缓存:
构建镜像时,Docker 会逐步执行 Dockerfile 中的指令,并按照指定的顺序执行每个指令。在检查每条指令时,Docker 会在其缓存中查找可以重用的现有镜像,而不是创建新的(重复)镜像。
对于 ADD 和 COPY 指令,检查镜像中文件的内容并计算每个文件的校验和。这些校验和不考虑文件的最后修改和最后访问时间。在缓存查找期间,校验和与现有镜像中的校验和进行比较。如果文件中的任何内容(例如内容和元数据)发生了更改,则缓存将失效。
除了 ADD 和 COPY 命令之外,缓存检查不会查看容器中的文件来确定缓存匹配。例如,在处理 RUN apt-get -y update 命令时,不会检查容器中更新的文件以确定是否存在缓存命中。在这种情况下,仅使用命令字符串本身来查找匹配项。
5.尽量避免使用root登录
保证容器运行所使用的权限不要超过其需要使用的需要。因此,我们需要创建一个新的用户,然后赋予它有限的权限。在构建镜像时,我们可以还有USER XXX指定默认使用什么用户。
6.不要安装多余的包
了减少复杂性、依赖性、文件大小和构建时间,避免安装额外或不必要的包。例如,我们不需要在数据库镜像中包含文本编辑器。
7.对多行参数进行排序
只要有可能,通过按字母数字顺序对多行参数进行排序来简化以后的更改。这有助于避免包重复并使列表更易于更新。这也使 PR 更易于阅读和审查。在反斜杠 (\) 前添加一个空格也有帮助。以下是来自 buildpack-deps 镜像的示例:
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion \
&& rm -rf /var/lib/apt/lists/*
三、yaml文件安全
很多时候我们都以最简单的方式来应用yaml文件,而忽略了yaml文件中存在的很多安全设置,例如securityContext等。那么,如何检查我们的yaml文件是否安全?可以使用kubsec这个工具来帮助我们完成这个任务。下载地址:https://github.com/controlplaneio/kubesec/releases
下载适合自己的版本即可。我这里下载的是kubesec_linux_amd64.tar。在master设备上解压后,扫描如下的pod2.yaml文件:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod2
name: pod2
spec:
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: pod2
resources: {}
env:
- name: aa
value: xxx
- name: bb
value: '999'
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
扫描的命令:
./kubesec scan pod2.yaml
扫描结果:结果中给我们了很多相关的修改建议,例如在spec下添加ServiceAccountName字段可以严格控制kubernets API的访问。
我们可以按照它给的提示和实际的需求修改我们的yaml文件。
四、使用Secret拉取镜像
很多私有镜像仓库,都必须要求客户端登陆后才能拉取镜像,无法匿名拉取。所以在K8s中我们可以设置一个Secret,保存镜像仓库的账号和密码,例如:
kubectl create secret docker-registry mysec1(secret名称) --docker-server=x.x.x.x(仓库地址) --docker-username=yyy(仓库用户名) --docker-password=zzz(仓库密码)
然后再创建pod的yaml文件中使用此secret即可,例如:
root@vms71:~/yaml_sec# cat pod1.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod1
name: pod1
spec:
terminationGracePeriodSeconds: 0
imagePullSecrets:
- name: mysec1
containers:
- image: x.x.x.x/nginx
imagePullPolicy: IfNotPresent
name: pod1
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
仅需在原始的yaml文件中,与containers对其,添加如下参数即可:
imagePullSecrets:
- name: mysec1 # secret名称
这样设置的话,当我们使用此yaml文件创建pod时,如果pod本地不存在,则会使用secret中的账号密码登录到我们的私人仓库拉取镜像了。
整理资料来源:
《老段CKS课程》
trivy github:https://github.com/aquasecurity/trivy
DockerFile:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/