第十九节 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系统中的授权过程都是通过以下几步进行设置的:
- 在系统中定义原子粒度的权限;
- 将一个或者多个权限组合为角色;
- 将角色和用户进行绑定,从而让用户具备绑定的角色所拥有的权限。
在Istio中使用在提到的ServiceRole和ServiceRoleBinding这两个对象来完成这一过程。
首先定义一个可以使用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-v2和service-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
...
根据日志内容中的source、 destination等进行过滤,就可以清楚地看到在访问过程中出现的问题了。