基础认证策略

此任务涵盖启用、配置和使用 Istio 身份验证策略时可能需要执行的主要活动。认证概述中可以了解更多相关的基本概念。

开始之前

  • 理解 Istio 认证策略和相关的双向 TLS 认证概念。

  • 拥有一个安装好 Istio 的 Kubernetes 集群,并且禁用全局双向 TLS(可使用安装步骤中提供的示例配置install/kubernetes/istio.yaml,或者使用 Helm设置 global.mtls.enabled 为 false)。

安装

为了演示,需要创建两个命名空间 foobar,并且在两个空间中都部署带有 sidecar 的httpbin 应用和 sleep 应用。同时运行另外一份不带有 Sidecar 的 httpbin 和 sleep 应用(为了保证独立性,在 legacy 命名空间中运行它们)。如果您在尝试任务时想要使用相同的示例,运行以下内容:

ZipZipZipZipZipZip

  1. $ kubectl create ns foo
  2. $ kubectl apply -f <(istioctl kube-inject -f @samples/httpbin/httpbin.yaml@) -n foo
  3. $ kubectl apply -f <(istioctl kube-inject -f @samples/sleep/sleep.yaml@) -n foo
  4. $ kubectl create ns bar
  5. $ kubectl apply -f <(istioctl kube-inject -f @samples/httpbin/httpbin.yaml@) -n bar
  6. $ kubectl apply -f <(istioctl kube-inject -f @samples/sleep/sleep.yaml@) -n bar
  7. $ kubectl create ns legacy
  8. $ kubectl apply -f @samples/httpbin/httpbin.yaml@ -n legacy
  9. $ kubectl apply -f @samples/sleep/sleep.yaml@ -n legacy

通过从任意客户端(例如 sleep.foosleep.barsleep.legacy)向任意服务端(httpbin.foohttpbin.barhttpbin.legacy)发送 HTTP 请求(可以使用 curl 命令)来验证以上设置。所有请求都应该成功进行并且返回的 HTTP 状态码为 200。

以下是一个从 sleep.barhttpbin.foo 可达性的检查命令示例:

  1. $ kubectl exec $(kubectl get pod -l app=sleep -n bar -o jsonpath={.items..metadata.name}) -c sleep -n bar -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}\n"
  2. 200

以下单行命令可以方便对所有客户端和服务端的组合进行检查:

  1. $ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
  2. sleep.foo to httpbin.foo: 200
  3. sleep.foo to httpbin.bar: 200
  4. sleep.foo to httpbin.legacy: 200
  5. sleep.bar to httpbin.foo: 200
  6. sleep.bar to httpbin.bar: 200
  7. sleep.bar to httpbin.legacy: 200
  8. sleep.legacy to httpbin.foo: 200
  9. sleep.legacy to httpbin.bar: 200
  10. sleep.legacy to httpbin.legacy: 200

重要的是,验证系统目前没有认证策略:

  1. $ kubectl get policies.authentication.istio.io --all-namespaces
  2. No resources found.
  1. $ kubectl get meshpolicies.authentication.istio.io
  2. No resources found.

最后要进行的验证是,确保没有为示例服务定义匹配的目标规则。一个验证方法是:获取现存目标规则,查看 host: 字段值是否匹配示例服务的 FQDN。例如:

  1. $ kubectl get destinationrules.networking.istio.io --all-namespaces -o yaml | grep "host:"
  2. host: istio-policy.istio-system.svc.cluster.local
  3. host: istio-telemetry.istio-system.svc.cluster.local

为网格中的所有服务启用双向 TLS 认证

你可以提交如下网格范围的认证策略(MeshPolicy和目的地规则为网格中所有服务启用双向 TLS 认证:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: "authentication.istio.io/v1alpha1"
  3. kind: "MeshPolicy"
  4. metadata:
  5. name: "default"
  6. spec:
  7. peers:
  8. - mtls: {}
  9. EOF

此策略指定网格中的所有工作负荷仅接受使用 TLS 的加密请求。如您所见,此身份验证策略的类型是MeshPolicy,这种策略的生效范围是整个网格内的所有服务,因此名称必须是 default,另外也不包含 targets 字段。

此时,只有接收方被配置为使用双向 TLS。如果在 Istio 服务(即那些带有 Sidecar 的服务)之间运行 curl 命令,所有请求都将失败并显示 503 错误代码,原因是客户端仍在使用明文请求。

  1. $ for from in "foo" "bar"; do for to in "foo" "bar"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
  2. sleep.foo to httpbin.foo: 503
  3. sleep.foo to httpbin.bar: 503
  4. sleep.bar to httpbin.foo: 503
  5. sleep.bar to httpbin.bar: 503

要配置客户端也使用双向 TLS,就需要设置目标规则。可以使用多个目标规则,一个一个的为每个适用的服务(或命名空间)进行配置;然而在规则中使用 * 符号来匹配所有服务会更方便,这样也就跟网格范围的认证策略一致了。

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: "networking.istio.io/v1alpha3"
  3. kind: "DestinationRule"
  4. metadata:
  5. name: "default"
  6. namespace: "default"
  7. spec:
  8. host: "*.local"
  9. trafficPolicy:
  10. tls:
  11. mode: ISTIO_MUTUAL
  12. EOF


除了认证场合之外,目标规则还有其它方面的应用,例如金丝雀部署。但是所有的目标规则都适用相同的优先顺序。因此,如果一个服务需要配置其它目标规则(例如配置负载均衡),那么新规则定义中必须包含类似的 TLS 块来定义 ISTIO_MUTUAL 模式,否则它将覆盖网格或命名空间范围的 TLS 设置并禁用 TLS。

如上所述重新运行测试命令,您将看到 Istio 服务之间的所有请求现已成功完成:

  1. $ for from in "foo" "bar"; do for to in "foo" "bar"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
  2. sleep.foo to httpbin.foo: 200
  3. sleep.foo to httpbin.bar: 200
  4. sleep.bar to httpbin.foo: 200
  5. sleep.bar to httpbin.bar: 200

从非 Istio 服务请求到 Istio 服务

非 Istio 服务,例如 sleep.legacy 没有 Sidecar,因此它无法启动与 Istio 服务通信所需的 TLS 连接。因此从 sleep.legacyhttpbin.foohttpbin.bar 的请求将失败:

  1. $ for from in "legacy"; do for to in "foo" "bar"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
  2. sleep.legacy to httpbin.foo: 000
  3. command terminated with exit code 56
  4. sleep.legacy to httpbin.bar: 000
  5. command terminated with exit code 56
  6. sleep.legacy to httpbin.legacy: 200


这一结果是符合预期的,但遗憾的是,如果不降低这些服务的身份验证要求,就无法完成这种访问。

从 Istio 服务请求非 Istio 服务

如果从 sleep.foo(或 sleep.bar)向 httpbin.legacy 发送请求,也会看到请求失败,因为 Istio 按照我们的指示配置客户端目标规则使用双向 TLS,但 httpbin.legacy 没有 Sidecar,所以它无法处理它。

  1. $ for from in "foo" "bar"; do for to in "legacy"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
  2. sleep.foo to httpbin.legacy: 503
  3. sleep.bar to httpbin.legacy: 503

要解决此问题,我们可以添加目标规则来覆盖 httpbin.legacy 的 TLS 设置。例如:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: networking.istio.io/v1alpha3
  3. kind: DestinationRule
  4. metadata:
  5. name: "httpbin-legacy"
  6. spec:
  7. host: "httpbin.legacy.svc.cluster.local"
  8. trafficPolicy:
  9. tls:
  10. mode: DISABLE
  11. EOF

从 Istio 服务请求到 Kubernetes API Server

Kubernetes API Server 没有 Sidecar,因此来自 sleep.foo 等 Istio 服务的请求会失败,出现像请求非 Istio 服务时同样的问题而失败。

  1. $ TOKEN=$(kubectl describe secret $(kubectl get secrets | grep default | cut -f1 -d ' ') | grep -E '^token' | cut -f2 -d':' | tr -d '\t')
  2. kubectl exec $(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name}) -c sleep -n foo -- curl https://kubernetes.default/api --header "Authorization: Bearer $TOKEN" --insecure -s -o /dev/null -w "%{http_code}\n"
  3. 000
  4. command terminated with exit code 35

同样,我们可以通过覆盖 API 服务器的目标规则来更正此问题( kubernetes.default

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: networking.istio.io/v1alpha3
  3. kind: DestinationRule
  4. metadata:
  5. name: "api-server"
  6. spec:
  7. host: "kubernetes.default.svc.cluster.local"
  8. trafficPolicy:
  9. tls:
  10. mode: DISABLE
  11. EOF


重新运行上面的测试命令,确认在添加规则后能够成功返回 200:

  1. $ TOKEN=$(kubectl describe secret $(kubectl get secrets | grep default | cut -f1 -d ' ') | grep -E '^token' | cut -f2 -d':' | tr -d '\t')
  2. $ kubectl exec $(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name}) -c sleep -n foo -- curl https://kubernetes.default/api --header "Authorization: Bearer $TOKEN" --insecure -s -o /dev/null -w "%{http_code}\n"
  3. 200

清理第 1 部分

删除在上述步骤中创建的策略和目标规则:

  1. $ kubectl delete meshpolicy default
  2. $ kubectl delete destinationrules default httpbin-legacy api-server

为每个命名空间或服务启用双向 TLS

除了为整个网格指定身份验证策略之外,Istio 还允许您为特定命名空间或服务指定策略。一个命名空间范围的策略优先于网格范围的策略,而特定于服务的策略仍具有更高的优先级。

命名空间范围的策略

下面的示例显示了为命名空间 foo 中的所有服务启用双向 TLS 的策略。正如你所看到的,它使用的类别是 Policy 而不是 MeshPolicy,并指定了一个命名空间,在本例中为 foo。如果未指定命名空间值,则策略将应用于默认命名空间。

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: "authentication.istio.io/v1alpha1"
  3. kind: "Policy"
  4. metadata:
  5. name: "default"
  6. namespace: "foo"
  7. spec:
  8. peers:
  9. - mtls: {}
  10. EOF


添加相应的目的地规则:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: "networking.istio.io/v1alpha3"
  3. kind: "DestinationRule"
  4. metadata:
  5. name: "default"
  6. namespace: "foo"
  7. spec:
  8. host: "*.foo.svc.cluster.local"
  9. trafficPolicy:
  10. tls:
  11. mode: ISTIO_MUTUAL
  12. EOF


由于这些策略和目的地规则只对命名空间 foo 中的服务有效,你应该看到只有从不带 Sidecar 的客户端(sleep.legacy)到 httpbin.foo 的请求会出现失败。

  1. $ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
  2. sleep.foo to httpbin.foo: 200
  3. sleep.foo to httpbin.bar: 200
  4. sleep.foo to httpbin.legacy: 200
  5. sleep.bar to httpbin.foo: 200
  6. sleep.bar to httpbin.bar: 200
  7. sleep.bar to httpbin.legacy: 200
  8. sleep.legacy to httpbin.foo: 000
  9. command terminated with exit code 56
  10. sleep.legacy to httpbin.bar: 200
  11. sleep.legacy to httpbin.legacy: 200

特定于服务的策略

也可以为某个特定的服务设置认证策略和目的地规则。执行以下命令,只为 httpbin.bar 服务新增一项策略。

  1. $ cat <<EOF | kubectl apply -n bar -f -
  2. apiVersion: "authentication.istio.io/v1alpha1"
  3. kind: "Policy"
  4. metadata:
  5. name: "httpbin"
  6. spec:
  7. targets:
  8. - name: httpbin
  9. peers:
  10. - mtls: {}
  11. EOF

同时增加目的地规则:

  1. $ cat <<EOF | kubectl apply -n bar -f -
  2. apiVersion: "networking.istio.io/v1alpha3"
  3. kind: "DestinationRule"
  4. metadata:
  5. name: "httpbin"
  6. spec:
  7. host: "httpbin.bar.svc.cluster.local"
  8. trafficPolicy:
  9. tls:
  10. mode: ISTIO_MUTUAL
  11. EOF


同样地,运行上文中提供的测试命令。和预期一致,从 sleep.legacyhttpbin.bar 的请求因为同样的原因开始出现失败。
  1. ...
  2. sleep.legacy to httpbin.bar: 000
  3. command terminated with exit code 56

如果在命名空间 bar 中还存在其他服务,我们会发现目标为这些服务的流量不会受到影响。验证这一行为有两种方法:一种是加入更多服务;另一种是把这一策略限制到某个端口。这里我们展示第二种方法:

  1. $ cat <<EOF | kubectl apply -n bar -f -
  2. apiVersion: "authentication.istio.io/v1alpha1"
  3. kind: "Policy"
  4. metadata:
  5. name: "httpbin"
  6. spec:
  7. targets:
  8. - name: httpbin
  9. ports:
  10. - number: 1234
  11. peers:
  12. - mtls: {}
  13. EOF

同时对目的地规则做出相应的改变:

  1. $ cat <<EOF | kubectl apply -n bar -f -
  2. apiVersion: "networking.istio.io/v1alpha3"
  3. kind: "DestinationRule"
  4. metadata:
  5. name: "httpbin"
  6. spec:
  7. host: httpbin.bar.svc.cluster.local
  8. trafficPolicy:
  9. tls:
  10. mode: DISABLE
  11. portLevelSettings:
  12. - port:
  13. number: 1234
  14. tls:
  15. mode: ISTIO_MUTUAL
  16. EOF

新的策略只作用于 httpbin 服务的 1234 端口上。结果是,双向 TLS 在端口 8000 上(又)被禁用并且从 sleep.legacy 发出的请求会恢复工作。

  1. $ kubectl exec $(kubectl get pod -l app=sleep -n legacy -o jsonpath={.items..metadata.name}) -c sleep -n legacy -- curl http://httpbin.bar:8000/ip -s -o /dev/null -w "%{http_code}\n"
  2. 200

策略优先级

服务级策略是优先于命名空间级策略的,为了证实这一行为,可以新建一条策略,禁止 httpbin.foo 的双向 TLS 设置;而此时网格中已经创建了一个命名空间级的策略,该策略为 foo 命名空间中的所有服务启用了双向 TLS,下面就来观察一下从 sleep.legacyhttpbin.foo 的请求是如何失败的:

  1. $ cat <<EOF | kubectl apply -n foo -f -
  2. apiVersion: "authentication.istio.io/v1alpha1"
  3. kind: "Policy"
  4. metadata:
  5. name: "overwrite-example"
  6. spec:
  7. targets:
  8. - name: httpbin
  9. EOF

另外添加对应的目的地规则:

  1. $ cat <<EOF | kubectl apply -n foo -f -
  2. apiVersion: "networking.istio.io/v1alpha3"
  3. kind: "DestinationRule"
  4. metadata:
  5. name: "overwrite-example"
  6. spec:
  7. host: httpbin.foo.svc.cluster.local
  8. trafficPolicy:
  9. tls:
  10. mode: DISABLE
  11. EOF

重新从 sleep.legacy 发送请求,我们应当看到请求成功返回的状态码(200),表明服务级的策略覆盖了命名空间层级的策略。

  1. $ kubectl exec $(kubectl get pod -l app=sleep -n legacy -o jsonpath={.items..metadata.name}) -c sleep -n legacy -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}\n"
  2. 200

清理第 2 部分

删除在上述步骤中创建的策略和目标规则:

  1. $ kubectl delete policy default overwrite-example -n foo
  2. $ kubectl delete policy httpbin -n bar
  3. $ kubectl delete destinationrules default overwrite-example -n foo
  4. $ kubectl delete destinationrules httpbin -n bar

设置终端用户认证

要验证这一功能,首先要有一个有效的 JWT。JWT 必须与本例中的 JWKS 端点相匹配。本例中我们使用 Istio 代码库中的 JWT test 以及JWKS endpoint 进行演示。

另外为了方便起见,我们使用 ingressgateway 开放了 httpbin.foo 服务(这部分内容可以参看控制 Ingress 流量任务中的介绍)。

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: networking.istio.io/v1alpha3
  3. kind: Gateway
  4. metadata:
  5. name: httpbin-gateway
  6. namespace: foo
  7. spec:
  8. selector:
  9. istio: ingressgateway # use Istio default gateway implementation
  10. servers:
  11. - port:
  12. number: 80
  13. name: http
  14. protocol: HTTP
  15. hosts:
  16. - "*"
  17. EOF
  1. $ kubectl apply -f - <<EOF
  2. apiVersion: networking.istio.io/v1alpha3
  3. kind: VirtualService
  4. metadata:
  5. name: httpbin
  6. namespace: foo
  7. spec:
  8. hosts:
  9. - "*"
  10. gateways:
  11. - httpbin-gateway
  12. http:
  13. - route:
  14. - destination:
  15. port:
  16. number: 8000
  17. host: httpbin.foo.svc.cluster.local
  18. EOF

获取入口 IP:

  1. $ export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

并且运行查询测试:

  1. $ curl $INGRESS_HOST/headers -s -o /dev/null -w "%{http_code}\n"
  2. 200

现在添加一条策略,强制 httpbin.org 使用终端用户 JWT。下一个命令假设没有为 httpbin.org 指定服务级策略(也就是说成功运行了上面清理第 2 部分的内容)。可以用命令 kubectl get policies.authentication.istio.io -n foo 来验证这一假设:

  1. $ cat <<EOF | kubectl apply -n foo -f -
  2. apiVersion: "authentication.istio.io/v1alpha1"
  3. kind: "Policy"
  4. metadata:
  5. name: "jwt-example"
  6. spec:
  7. targets:
  8. - name: httpbin
  9. origins:
  10. - jwt:
  11. issuer: "testing@secure.istio.io"
  12. jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.1/security/tools/jwt/samples/jwks.json"
  13. principalBinding: USE_ORIGIN
  14. EOF

使用上面小节中同样的 curl 命令进行测试时会返回 401 错误状态码,这是因为服务端需要 JWT 进行认证但请求端并没有提供:

  1. $ curl $INGRESS_HOST/headers -s -o /dev/null -w "%{http_code}\n"
  2. 401

用下面的 curl 命令生成 Token 并附加在请求中,然后执行请求就会返回成功信息:

  1. $ TOKEN=$(curl https://raw.githubusercontent.com/istio/istio/release-1.1/security/tools/jwt/samples/demo.jwt -s)
  2. $ curl --header "Authorization: Bearer $TOKEN" $INGRESS_HOST/headers -s -o /dev/null -w "%{http_code}\n"
  3. 200

要观察 JWT 验证的其他方面,请使用脚本 gen-jwt.py 生成新的令牌以测试不同的发行者、受众和到期日期等。例如下面的命令创建一个 5 秒后到期的 Token。Istio 首先成功通过该令牌的验证请求,但在 5 秒后就会拒绝这一 Token 了:

ZipZip

  1. $ TOKEN=$(@security/tools/jwt/samples/gen-jwt.py@ @security/tools/jwt/samples/key.pem@ --expire 5)
  2. $ for i in `seq 1 10`; do curl --header "Authorization: Bearer $TOKEN" $INGRESS_HOST/headers -s -o /dev/null -w "%{http_code}\n"; sleep 1; done
  3. 200
  4. 200
  5. 200
  6. 200
  7. 200
  8. 401
  9. 401
  10. 401
  11. 401
  12. 401

使用双向 TLS 进行最终用户身份验证

最终用户身份验证和双向 TLS 可以一起使用。修改上面的策略以定义双向 TLS 和最终用户 JWT 身份验证:

  1. $ cat <<EOF | kubectl apply -n foo -f -
  2. apiVersion: "authentication.istio.io/v1alpha1"
  3. kind: "Policy"
  4. metadata:
  5. name: "jwt-example"
  6. spec:
  7. targets:
  8. - name: httpbin
  9. peers:
  10. - mtls: {}
  11. origins:
  12. - jwt:
  13. issuer: "testing@secure.istio.io"
  14. jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.1/security/tools/jwt/samples/jwks.json"
  15. principalBinding: USE_ORIGIN
  16. EOF


并添加目标规则:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: "networking.istio.io/v1alpha3"
  3. kind: "DestinationRule"
  4. metadata:
  5. name: "httpbin"
  6. namespace: "foo"
  7. spec:
  8. host: "httpbin.foo.svc.cluster.local"
  9. trafficPolicy:
  10. tls:
  11. mode: ISTIO_MUTUAL
  12. EOF


在这些更改之后,来自 Istio 服务(包括 ingress gateway)到 httpbin.foo 的流量将使用双向 TLS。上面的测试命令仍然有效。在使用正确的令牌的情况下,Istio 服务直接向 httpbin.foo 发出的请求也可以正常工作:

  1. $ kubectl exec $(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name}) -c sleep -n foo -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}\n" --header "Authorization: Bearer $TOKEN"
  2. 200

但是,来自非 Istio 服务的明文请求将失败:

  1. $ kubectl exec $(kubectl get pod -l app=sleep -n legacy -o jsonpath={.items..metadata.name}) -c sleep -n legacy -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}\n" --header "Authorization: Bearer $TOKEN"
  2. 401

清理第 3 部分

  • 删除身份验证策略:
  1. $ kubectl delete policy jwt-example
  • 删除目标规则:
  1. $ kubectl delete policy httpbin
  • 如果您不打算探索任何后续任务,则只需删除测试命名空间即可删除所有资源:
  1. $ kubectl delete ns foo bar legacy