跳转至

第十九节 Istio的安全加固(RBAC设置\排错)

1、设置RBAC

RBAC(Role Based Access Control)是目前较为通用的一种访问控制方法。Istio也提供了这样的方式来支持服务间的授权和鉴权。

注意,在开始之前,首先要在values.yaml中设置:

apiVersion: "authentication.istio.io/v1alpha1"
kind: "MeshPolicy"
metadata:
  name: "default"
  namespace: "default"
spec:
  peers:
  - mtls: {}

在部署成功后,就可以在网格中启动我们的sleep应用和httpbin应用了。在启动完成后, 尝试使用sleep Pod访问httpbin服务:

$ kubectl apply -f meshpolicy.yaml
meshpolicy.authentication.istio.io/default configured
$ kubectl exec -it sleep-6c9c898f6c-448v6 -c sleep bash
bash-4.4# http http://httpbin:8000/ip
HTTP/1.1 200 OK
access-control-allow-credentials: true
...

defa-destinationrule.mtls.yaml

apiVersion: networking.istio.io/v1alpha3
kind: "DestinationRule"
metadata:
  name: "httpbin"
  namespace: default
spec:
  host: httpbin.default.svc.cluster.local
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL

不出意外访问可以正常完成

下面启动一个策略, 启动RBAC

apiVersion: "rbac.istio.io/v1alpha1" 
kind: RbacConfig 
metadata: 
  name: default 
spec: 
  mode: 'ON_WITH_INCLUSION' 
  inclusion: 
    namespaces: ["default"]
$ kubectl apply -f rbac.yaml 
rbacconfig.rbac.istio.io/default created

将上述内容保存为rbac.yaml,并提交到Kubernetes集群, 这一规则的意义在于,为所有default命名空间中的服务都启动RBAC策略。

再次启动测试:

$ kubectl exec -it sleep-6c9c898f6c-448v6 -c sleep bash
bash-4.4# http http://httpbin:8000/ip
HTTP/1.1 403 Forbidden
content-length: 19
content-type: text/plain
date: Tue, 05 Nov 2019 06:23:03 GMT
server: envoy
x-envoy-upstream-service-time: 1

RBAC: access denied

问题出现了,在RBAC启动之后,在默认情况下,所有服务的调用都会被拒绝。接下来需要做的就是制定策略,开放对httpbin服务的访问。

一般来说,RBAC系统中的授权过程都是通过以下几步进行设置的:

  1. 在系统中定义原子粒度的权限;
  2. 将一个或者多个权限组合为角色;
  3. 将角色和用户进行绑定,从而让用户具备绑定的角色所拥有的权限。

Istio中使用在提到的ServiceRoleServiceRoleBinding这两个对象来完成这一过程。

首先定义一个可以使用HTTP GET访问所有服务的ServiceRole:

apiVersion: "rbac.istio.io/v1alpha1" 
kind: ServiceRole 
metadata: 
  name: service-viewer 
spec: 
  rules: 
  - services: ["*"] 
    methods: ["GET"] 
$ kubectl apply -f servicerole.yaml 
servicerole.rbac.istio.io/service-viewer created

将其保存为servicerole.yaml

这里定义了一个名称为service-viewer的角色,在rules字段中进行授权,允许该角色使用GET方法访问所有服务。

然后定义一个ServiceRoleBinding,将上面的角色绑定到所有default命名空间的ServiceAccount上:

apiVersion: "rbac.istio.io/v1alpha1" 
kind: ServiceRoleBinding 
metadata: 
  name: bind-service-viewer 
spec: 
  subjects: 
  - properties: 
      source.namespace: "default" 
  roleRef: 
    kind: ServiceRole 
    name: "service-viewer" 
$ kubectl apply -f servicerolebinding.yaml 
servicerolebinding.rbac.istio.io/bind-service-viewer created

将其保存为servicerolebinding.yaml

这里的subject用了一个属性限制来指定绑定目标:所有来自default命名空间的调用者。

符合这一条件的用户都被绑定到service-viewer这个角色上。

我们将这两个文件提交到Kubernetes集群:

$ kubectl exec -it sleep-6c9c898f6c-448v6 -c sleep bash
bash-4.4# http http://httpbin:8000/ip
HTTP/1.1 200 OK
access-control-allow-credentials: true
access-control-allow-origin: *
content-length: 28
content-type: application/json
date: Tue, 05 Nov 2019 07:24:53 GMT
...

进一步尝试post方式

bash-4.4# http -f POST http://httpbin:8000/post name=ja
HTTP/1.1 403 Forbidden
content-length: 19
content-type: text/plain
date: Tue, 05 Nov 2019 07:27:01 GMT
server: envoy
x-envoy-upstream-service-time: 0

RBAC: access denied

因为在ServiceRole中仅设置了GET方法的授权,因此POST方法还是无法通过。

下面将这个角色进行细化。假设我们的两个版本的sleep应用使用不同的 ServiceAccount运行: v1版本使用sleep, v2版本使用sleep-v2。我们对sleep服务的v1版本开放POST方法。 首先创建新的Service Account:

$ kubectl create sa sleep
serviceaccount/sleep created

$ kubectl create sa sleep-v2
serviceaccount/sleep-v2 created

接下来更新sleep.yaml,在其中增加ServiceAccount:

...
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sleep-v1
  # annotations: 
  #   traffic.sidecar.istio.io/includeOutboundIPRanges: 10.96.0.0/12
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: sleep
        version: v1
    spec:
      ServiceAccountName: sleep
      containers:
      - name: sleep
        image: dustise/sleep
        imagePullPolicy: Always
---
...
      labels:
        app: sleep
        version: v2
    spec:
      ServiceAccountName: sleep-v2
      containers:
      ...

删除原有部署,重新启动并注人sleep应用,继续后续的测试操作:

$ kubectl exec -it sleep-v2-6b7d67797b-48pk6 -c sleep bash

http -f POST http://httpbin:8000/post name=ja
HTTP/1.1 403 Forbidden
content-length: 19
content-type: text/plain
date: Tue, 05 Nov 2019 08:01:33 GMT
server: envoy
x-envoy-upstream-service-time: 5

RBAC: access denied




http -f  http://httpbin:8000/ip
HTTP/1.1 200 OK
access-control-allow-credentials: true
access-control-allow-origin: *
content-length: 28
content-type: application/json
date: Tue, 05 Nov 2019 08:02:05 GMT
server: envoy
x-envoy-upstream-service-time: 2

{
    "origin": "127.0.0.1"
}

可以看到, sleep服务的v2版本目前和其他服务权限是一致的。 接下来创建一个新的Service Role:

apiVersion: "rbac.istio.io/v1alpha1" 
kind: ServiceRole 
metadata: 
  name: service-owner 
spec: 
  rules: 
  - services: ["*"] 
    methods: ["GET","POST"] 
  • servicerole-owner.yaml
$ kubectl apply -f servicerole-owner.yaml 
servicerole.rbac.istio.io/service-owner created

然后创建新的servicerolebinding.yaml的绑定关系, ,将sleep-v2service-owner 关联起来:

apiVersion: "rbac.istio.io/v1alpha1" 
kind: ServiceRoleBinding 
metadata: 
  name: bind-service-owner 
spec: 
  subjects: 
  - user: "cluster.local/ns/default/sa/sleep" 
  roleRef: 
    kind: ServiceRole 
    name: "service-owner" 
$ kubectl apply -f servicerolebinding-owner.yaml 
servicerolebinding.rbac.istio.io/bind-service-owner created
$ kubectl exec -it sleep-v2-6b7d67797b-48pk6 -c sleep bash 
bash-4.4# http -f POST http://httpbin:8000/post name=ja
HTTP/1.1 403 Forbidden
content-length: 19
content-type: text/plain
...

RBAC: access denied
$ kubectl exec -it sleep-v1-64fddd5d85-nmgl5  -c sleep bash

bash-4.4# http -f POST http://httpbin:8000/post name=ja
HTTP/1.1 200 OK
access-control-allow-credentials: true
access-control-allow-origin: *
content-length: 793
...

如此一来,使用不同Service Account运行的服务版本,就分别具备了不同的权限。因为服务和ServiceAccount的关系可以由管理员进行指定有非常大的灵活性,所以在授权方面会

2、 RBAC的除错过程

RBAC的设置过程是非常容易出错的,这里可以使用自定义日志的方式,在Mixer Telemetry中监控

这里给出一个简单样例:

apiVersion: "config.istio.io/v1alpha2" 
kind: logentry 
metadata: 
  name: rbaclog 
spec: 
  severity: '"warning"' 
  timestamp: request.time 
  variables: 
    source: source.labels["app"] | source.workload.name | "unknown" 
    user: source.user | "unknown" 
    destination: destination.labels["app"] | destination.workload.name | "unknown" 
    responseCode: response.code | 0 
    responseSize: response.size | 0 
    latency: response.duration |  "Oms" 
  monitored_resource_type: '"UNSPECIFIED"' 
---
apiVersion: "config.istio.io/v1alpha2" 
kind: stdio 
metadata: 
  name: rbachandler 
spec: 
  outputAsJson: true 
---
apiVersion: "config.istio.io/v1alpha2" 
kind: rule 
metadata: 
  name: rabcstdio 
spec: 
  actions: 
  - handler: rbachandler.stdio 
    instances: 
    - rbaclog.logentry 
$ kubectl apply -f rbaclog.yaml 
logentry.config.istio.io/rbaclog unchanged
stdio.config.istio.io/rbachandler unchanged
rule.config.istio.io/rabcstdio created
kubectl logs -n  istio-system istio-telemetry-7d7845478d-d2h5f -c mixer | grep rbaclog
...

根据日志内容中的sourcedestination等进行过滤,就可以清楚地看到在访问过程中出现的问题了。