主流开源Serverless平台OpenFaaS安全性研究

Posted by     "Pu Ming" on Thursday, February 4, 2021

专题信息

专题:Serverless安全研究

往期回顾:

《Serverless安全研究 — Serverless概述》

《Serverless安全研究 — Serverless安全风险》

《Serverless安全研究 — Serverless安全防护》

一.引言

本篇为Serverless安全研究系列的完结篇,通过本系列的前三篇,我们对什么是Serverless,Serverless存在的安全风险及如何防护进行了较为全面的介绍。公有云Serverless借助AWS、Microsoft、Google等庞大的安全研发团队使其安全性得到保障,除此之外,私有化的Serverless安全也备受关注,通常来说,私有化Serverless部署方式兼容Kubernetes,目前市场关注度较高的有OpenFaaS、Knative、Kubeless、Openwhisk、Fission等。本文笔者以OpenFaaS平台作为研究对象,主要对其内置的安全机制进行了分析解读,除此之外,还通过实验进一步验证了Kubernetes安全机制对OpenFaaS函数的深度防护;希望本文可以引发各位读者更多的思考。

二.OpenFaaS简介

OpenFaaS作为一款开源的云原生Kubernetes Serverless平台,在2017年1月发布了第一个Release版本,其使用容器作为Serverless函数的承载体。值得一提的是,OpenFaaS在函数模板上做出的努力,其支持多种语言模板包括.NET、Dockerfile、Go、Java、NodeJS、PHP、Python、Ruby等,创建函数时,OpenFaaS为其自动生成相应的语言模板,其中包含依赖库文件、镜像Dockerfile、函数部署的yaml文件等,这样开发者只需关注于函数的逻辑实现。

image
图1. OpenFaaS基础架构

图1我们可以看出,OpenFaaS的基础架构体系可分为三层,每一层负责不同的部分:

基础架构层:OpenFaaS部署在Kubernetes上,以Docker运行Serverless函数,函数镜像存储至镜像仓库中;

应用层:通过OpenFaaS网关,外部用户可对Serverless函数进行CRUD和调用操作,内置的Prometheus可对函数调用行为进行监控,NATS则提供函数的异步调用;

GitOps层:OpenFaaS Cloud建立在OpenFaaS的基础上,可通过Github或自行托管的Gitlab进行DevOps交付;

除上述介绍外,OpenFaaS的正常运行还需依赖以下组件:

faas-provider: OpenFaaS提供商,可为Serverless函数提供CRUD API及调用能力

Watchdog: 负责为OpenFaaS启动和监控函数的组件;

三.OpenFaaS安全功能分析

OpenFaaS提供的安全能力可覆盖认证、数据安全、安全配置三个方面。

3.1 认证防护

OpenFaaS的认证主要包含API网关认证和函数认证两个部分。

3.1.1 API网关认证

基本认证方式

基本认证方式使用了Kubernetes的secret机制,需要注意的是,我们需在部署OpenFaaS之前首先创建secrets资源,具体操作如下:

kubectl  -n openfaas create secret generic basic-auth \
    --from-literal=basic-auth-user=admin \
    --from-literal=basic-auth-password=admin

部署OpenFaaS时,该secrets会被挂载至API网关的pod内部,如下所示:

nsfocus@openfaas-master:~$ kubectl exec -it gateway-df9df88df-bxlbz sh -n openfaas
/home/app $ cd /var/secrets/
/var/secrets $ cat basic-auth-password
G09ZqGc9dfprr3t87281jV5bi
/var/secrets $ cat basic-auth-user
admin

通过faas-cli登录之后,API网关Pod内部会做校验,校验成功即可访问API 网关:

root@openfaas-master:~# cat ~/secret/secret-api-key.txt | faas-cli login -u admin --password-stdin
credentials saved for admin http://192.168.19.197:31112

上述登录过程中,笔者未对API网关进行加密,为保证数据安全,生产环境中建议配置HTTPS证书。

认证插件

OpenFaaS支持用户自行构建认证插件,实现方式可通过在网关模块的yaml文件中指定auth_proxy_url、auth_pass_body环境变量实现,如下图所示:

image
图2 API网关配置文件

OIDC和OAuth2认证

OpenFaaS商业版本提供OIDC和OAuth2认证机制,用户只需将认证提供商信息配置至OpenFaaS部署的yaml文件中即可。部署完成后,我们可以通过UI以及OpenFaaS命令行工具对认证服务进行访问,更为详细的配置页面可参考OpenFaaS官方文档[1]。

3.1.2 函数认证

针对函数认证,OpenFaaS考虑到开发者通常部署函数用于响应外部Webhooks,由于这些Webhooks中许多都不支持OAuth或基本的认证策略,例如Github,因此OpenFaaS转为使用HMAC认证方式,关于HMAC的相应实践可以参考官方提供的workshop[2],此处由于篇幅不再做赘述。

3.2 数据安全防护

OpenFaaS数据安全防护包括密钥管理及函数/API网关的TLS加密。

3.2.1 密钥管理

关于密钥防护,OpenFaaS底层使用Kubernetes的Secret机制进行密钥存储,为避免用户直接操作Kubernetes资源,OpenFaaS进行了相应封装,如下所示:

provider:
    name: openfaas
functions:
    protectedapi:
    lang: dockerfile
    skip_build: true
    image: functions/api-key-protected:latest
    secrets:
    - secret-api-key#可指定多个secret

3.2.2 API网关TLS加密

关于TLS加密,OpenFaaS为API网关提供了TLS证书,用户需要在OpenFaaS安装包中进行相应配置,此外还需要添加IngressController资源和证书管理器,其中,IngressController用于接入外部的LoadBalance,证书管理器 用于对TLS证书进行生命周期管理,更详细的配置可以参考官方文档[4]

3.2.3 函数TLS加密

与API网关TLS加密基本类似,唯一不同为OpenFaaS不使用Ingress Controller资源,而使用其正在孵化的IngressOperator项目,IngressOperator实则提供了一种自定义资源(CRD)FunctionIngress, 用于提供函数的TLS,下例为“nodeinfo“函数配置了TLS加密:

apiVersion: openfaas.com/v1alpha2
kind: FunctionIngress
metadata:
name: nodeinfo-tls
namespace: openfaas
spec:
domain: "nodeinfo-tls.myfaas.club"
function: "nodeinfo"
ingressType: "nginx"
tls:
    enabled: true
    issuerRef:
    name: "letsencrypt-staging"
    kind: "Issuer"

3.3 安全配置

笔者对OpenFaaS提供的安全配置进行了汇总,主要包括如下几个部分:

1.函数文件系统只读 开发者可在函数部署文件中配置“readonly_root_filesystem: true“实现,默认为false;

2.函数超时时长 开发者可在函数部署文件中配置环境变量实现,下例展示了如何进行配置:

provider:
……
functions:
……
    environment:
        read_timeout: 20s #允许函数通过HTTP读取请求的时间为20s
        exec_timeout: 20s #允许函数被终止之前最多运行20s
        write_timeout: 20s#允许函数通过HTTP写入响应的时间为20s

3.函数弹性扩展 开发者可从“每秒请求数”和“CPU/内存利用率”两方面实现函数的弹性扩展,详细的配置可以参考官方文档[5]。

4.非特权模式部署容器及non-root用户运行容器

OpenFaaS函数默认以非特权模式进行部署,且以non-root用户运行,详细信息我们可通过查看函数Dockerfile,下面列举了Dockerfile部分内容:

#Add non root user
RUN addgroup -S app && adduser app -S -G app

四.针对OpenFaaS函数的进一步防护

通过以上OpenFaaS安全功能介绍,我们可以大致看出,除了认证、安全配置、数据安全之外,OpenFaaS在访问控制、函数运行时防护方面缺乏相应的安全能力,官方文档也未提供相关信息。

OpenFaaS部署在Kubernetes之上,其部署的函数也是作为一个Pod运行在集群中,那么理论上讲,Kubernetes的安全机制也应该可以复用在OpenFaaS函数上,笔者从此思路出发,分别从Kubernetes的RBAC、Pod安全策略(Pod Security Policy)、网络策略(NetworkPolicy)三方面对OpenFaaS函数进行了相关实验。

4.1 使用RBAC实现函数访问控制

实验目的

通过在默认的函数部署命名空间“openfaas-fn”中部署RBAC策略,,进而验证RBAC能否实现对OpenFaaS函数的访问控制。

实验过程 1.部署test-bot函数

root@openfass-master:~# faas-cli up -f test-bot.yml
[0] > Building test-bot.
... ...
[0] Worker done.
Deployed. 202 Accepted.
URL: http://192.168.19.211:31112/function/test-bot.openfaas-fn

2.查看部署函数默认使用的ServiceAccount

root@openfass-master:~# kubectl get pods test-bot-5c9cdd699c-x64g5 -n openfaas-fn -o yaml
apiVersion: v1
......
spec:
containers:
......
serviceAccount: default  ##默认ServiceAccount
serviceAccountName: default ##默认ServiceAccount
......

可以看出该函数使用的为Kubernetes默认为其创建的ServiceAccount,下一步需要建立一个RBAC策略,并将策略中的ServiceAccount绑定至test-bot中。

3.创建并部署RBAC策略

RBAC策略文件内容如下所示:

---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
    app: openfaas
    component: faas-test-rbac
name: faas-test-rbac
namespace: "openfaas-fn"
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
labels:
    app: openfaas
    component: faas-test-rbac
name: faas-test-rbac
namespace: "openfaas-fn"
rules:
- apiGroups:
    - ""
    resources:
    - secrets
    - pods
    verbs:
    - get
    - list
    - watch
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
labels:
    app: openfaas
    component: faas-test-rbac
name: faas-test-rbac
namespace: "openfaas-fn"
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: faas-test-rbac
subjects:
- kind: ServiceAccount

该策略具体实现为:

(1) “openfaas-fn"命名空间下创建一个名为faas-test-rbac的ServiceAccount;

(2)“openfaas-fn"命名空间下创建一个名为faas-test-rbac 的Role,该Role具有对secrets及pods资源的“get”、“list”、“watch”权限;

(3)“openfaas-fn"命名空间下创建一个名为faas-test-rbac的RoleBinding,赋予faas-test-rbac ServiceAccount实体faas-test-rbac Role的权限; 部署策略后通过kubectl的“access-matrix “插件可以看出策略生效了,如下图所示:

image
图3. 查看RBAC权限

4.将RBAC策略中的ServiceAccount绑定至test-bot Pod

由于Kubernetes禁止通过kubectl edit直接修改Pod中的ServiceAccount和ServiceAccountName,因此我们可以通过打补丁的方式替换掉test-bot中默认的ServiceAccount,需要注意的是由于Kubernetes的Pod资源由ReplicaSet控制器下发的,而ReplicaSet又是由Deployment资源下发,因此我们需要从Deployment资源中打补丁,具体操作如下:

kubectl patch -n openfaas-fn deploy test-bot -p '{"spec":{"template":{"spec":{"serviceAccountName":"faas-test-rbac"}}}}'    ##通过打补丁的形式

再次查看test-pod的yaml文件我们可以发现default ServiceAccount已替换为RBAC策略中的faas-test-rbac ServiceAccount。

至此,RBAC策略已经添加完毕,若函数容器中安装了curl命令,可通过如下命令验证Pod能否访问指定命名空间的Sercret。

curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt --header "Authorization:Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api/v1/namespaces/$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)/secret

实验结论

通过上述实验可看出,OpenFaaS函数支持Kubernetes的RBAC策略,不过无法通过faas-cli直接创建RBAC策略,而需使用原生的kubectl创建,并将ServiceAccount以打补丁的形式绑定至函数中。

4.2 使用Pod安全策略实现函数细粒度权限控制

实验目的

通过在Kubernetes集群中部署Pod安全策略,进而观察函数创建过程是否满足条件,若不满足则调整策略,最终验证Pod安全策略能否对函数进行细粒度权限控制。

实验过程

1.为Kubernetes的kube-apiserver配置添加Pod安全策略项

root@openfass-master:~# vi /etc/kubernetes/manifests/kube-apiserver.yaml
apiVersion: v1
... ...
spec:
... ...
    - --enable-admission-plugins=NodeRestriction,PodSecurityPolicy ##添加PodSecurityPolicy项
... ...

2.部署一个相对严谨的Pod安全策略,策略部分内容如下所示:

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
... ...
spec:
privileged: false
# 禁止特权提升至root权限
allowPrivilegeEscalation: false
# 这与非root及不允许权限升级是多余的。
# 但我们可以提供它的防御深度。
requiredDropCapabilities:
    - ALL
# 允许的核心挂载卷类型
volumes:
    - 'configMap'
    - 'emptyDir'
    - 'projected'
    - 'secret'
    - 'downwardAPI'
# 假设集群管理员设置的persistentVolumes可以安全使用。
    - 'persistentVolumeClaim'
hostNetwork: false
hostIPC: false
hostPID: false
runAsUser:
    # 要求容器在没有root权限的情况下运行。
    rule: 'MustRunAsNonRoot'
seLinux:
    # 该策略假设节点使用的是AppArmor而不是SELinux。
    rule: 'RunAsAny'
supplementalGroups:
    rule: 'MustRunAs'
    ranges:
    # 禁止添加root组。
    - min: 1
        max: 65535
fsGroup:
    rule: 'MustRunAs'
    ranges:
    # 禁止添加root组。
    - min: 1
        max: 65535
#要求容器必须以只读方式挂载根文件系统来运行,(即不允许存在可写入层)
readOnlyRootFilesystem: false

3.授权Pod安全策略

可通过RBAC授权Pod安全策略至“openfaas-fn“命名空间。

(1)创建Role

root@openfass-master:~# kubectl -n openfaas-fn create role psp:restricted \
> --verb=use \
> --resource=podsecuritypolicy \
> --resource-name=restricted
role.rbac.authorization.k8s.io/psp:restricted created

该角色定义一个名为psp:restricted的Role并给予openfaas-fn命名空间中名为restricted的Pod安全策略的权限。

(2)创建RoleBinding

root@openfass-master:~# kubectl -n openfaas-fn create rolebinding default:psp:restricted \
> --role=psp:restricted \
> --serviceaccount=openfaas-fn:default
rolebinding.rbac.authorization.k8s.io/default:psp:restricted created

该角色绑定义一个名为default:psp:restricted的rolebinding并将Pod安全策略psp:restricted 绑定至openfaas-fn命名空间下的默认default账户

4.部署OpenFaaS函数,验证Pod安全策略

与4.1中部署过程相同,此处不再赘述。

部署完成后,通过kubectl查看此函数的部署状态,发现未部署成功,如下所示:

root@openfass-master:~# kubectl get pod -n openfaas-fn -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
callfromanother-6db9fd5f69-8z7nb 0/1 CreateContainerConfigError 0 53s 10.244.0.83 openfass-master <none> <none>

错误信息为“CreateContainerConfigError“,通过kubectl get event进一步查看,输出如下信息:

52m Warning Failed pod/callfromanother-6db9fd5f69-8z7nb Error: container has runAsNonRoot and image has non-numeric user (app), cannot verify user is non-root

至此,结合之前下发的Pod安全策略,我们可以看出函数容器虽然使用非root用户app运行,但Pod安全策略无法验证app为非root用户,因此OpenFaaS应用启动失败。

此时,我们可将Pod安全策略的“runAsUser“规则适当放松一些,如下图所示:

image
图4. Pod安全策略修改

再次部署Pod安全策略后,通过删除该Pod可以重新启动一个Pod,不久可以看到Pod正常部署了:

root@openfass-master:~# kubectl delete pod callfromanother-6db9fd5f69-8z7nb -n openfaas-fn
pod "callfromanother-6db9fd5f69-8z7nb" deleted
root@openfass-master:~# kubectl get pods -n openfaas-fn
NAME READY STATUS RESTARTS AGE
callfromanother-6db9fd5f69-8z7nb 1/1 Running 0 4s

实验结论

通过上述实验可以看出,Pod安全策略可对OpenFaaS函数进行细粒度的安全控制。

4.3 使用网络策略实现函数隔离

实验目的

本实验通过对函数添加网络策略,进而验证函数能否在网络层面被有效隔离。

实验前提

Kubernetes的网络策略需要指定的CNI插件,笔者使用的Cillum插件。

实验过程

1.部署函数

笔者部署了三个函数,用于后续实验 如下所示:

root@openfaas-master-cillum:~/serverless-function/network_policy# faas-cli list
Function            Invocations  Replicas
callfromanother     28           1
nodeinfo            4           1
sentimentanalysis   45          1

2.添加网络策略并验证

本实验场景为在openfaas-fn命名空间下,仅指定某函数可对另一函数进行访问 下发策略内容如下:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: access-sentimentanalysis
namespace: openfaas-fn
spec:
podSelector:
    matchLabels:
    faas_function: sentimentanalysis
ingress:
- from:
    - podSelector:
        matchLabels:
        faas_function: callfromanother

该策略实现为:openfaas-fn命名空间下仅允许label字段为“faas_function=callfromanother” 的Pod访问label字段为“faas_function=sentimentanalysis”的Pod。

此处以sentimentanalysis、callfromanother 、nodeinfo函数举例,在未添加策略之前通过nodeinfo函数(nodeinfo Pod的lebel字段为faas_function=nodeinfo)访问sentimentanalysis函数,如下所示:

root@openfaas-master-cillum:~/serverless-function/network_policy# kubectl exec -it nodeinfo-5544dcd45d-4nkms sh -n openfaas-fn
~$wget --post-data="xxxx" --spider --timeout=1 10.99.21.80:8080                    ###10.99.21.80:8080  为sentimentanalysis的clusterIP访问方式
Connecting to 10.99.21.80:8080 (10.99.21.80:8080)

可以看到访问成功

添加策略

root@openfaas-master-cillum:~/serverless-function/network_policy# kubectl apply -f openfaas-policy-1.yaml
networkpolicy.networking.k8s.io/access-sentimentanalysis created

再次尝试在nodeinfo函数中访问sentimentanalysis函数,如下所示:

root@openfaas-master-cillum:~/serverless-function/network_policy# kubectl exec -it nodeinfo-5544dcd45d-4nkms sh -n openfaas-fn
~ $ wget --post-data="xxxx" --spider --timeout=1 10.99.21.80:8080
Connecting to 10.99.21.80:8080 (10.99.21.80:8080)
wget: download timed out

可以看到访问超时,拒绝访问。

再次尝试在网络策略中允许的label为“faas_function=callfromanother”的Pod中访问sentimentanalysis函数,如下所示:

root@openfaas-master-cillum:~/serverless-function/network_policy# kubectl exec -it callfromanother-c5689794c-c5hj6 sh -n openfaas-fn
~ $ wget --post-data="xxx" --spider --timeout=1 10.99.21.80:8080
Connecting to 10.99.21.80:8080 (10.99.21.80:8080)
remote file exists

访问成功,策略生效

实验结论

通过上述实验可以看出,网络策略可实现OpenFaaS函数在网络层面的隔离。

五.总结

本文对OpenFaaS的安全机制进行了分析解读,其中不难看出OpenFaaS提供的安全能力是有限的,依托私有化Serverless平台兼容Kubernetes的特点,我们可以考虑使用Kubernetes安全机制对Serverless函数进行深度防护,从而使开源Serverless平台受到更为全面的防护。

六.参考文献

  1. https://docs.openfaas.com/reference/authentication/#oidc-and-oauth2-for-the-openfaas-api
  2. https://github.com/openfaas/workshop/blob/master/lab11.md
  3. https://www.jetstack.io/
  4. https://docs.openfaas.com/reference/ssl/kubernetes-with-cert-manager/#10-tls-for-the-gateway
  5. https://docs.openfaas.com/architecture/autoscaling/#scaling-by-requests-per-second

「真诚赞赏,手留余香」

Ming Blog

真诚赞赏,手留余香