认证策略

本任务涵盖了您在启用、配置和使用 Istio 认证策略时可能需要做的主要工作。更多基本概念介绍请查看认证总览

开始之前

  1. $ istioctl install --set profile=default

设置

我们将在 foobar命名空间下各自创建带有 Envoy 代理(sidecar)的 httpbinsleep服务。我们还会在 legacy 命名空间下创建不带 Envoy 代理(sidecar)的 httpbinsleep 服务。如果您希望使用相同的示例来完成这些任务,执行如下命令:

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

现在您可以在 foobarlegacy 三个命名空间下的任意 sleep pod 使用 curlhttpbin.foohttpbin.barhttpbin.legacy 发送 HTTP 请求来验证部署结果。所有请求都应该成功返回 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 -s "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 peerauthentication --all-namespaces
  2. No resources found

最后同样重要的是,确认验证示例服务没有应用 destination rule。您可以检查现有 destination rule 中的 host: 值并确保它们不匹配。例如:

  1. $ kubectl get destinationrules.networking.istio.io --all-namespaces -o yaml | grep "host:"

您可能会看到 destination rules 配置了除上面显示以外的其他 hosts,这依赖于 Istio 的版本。但是,应该没有 destination rules 配置 foobarlegacy 命名空间中的 hosts,也没有配置通配符 *

自动双向 TLS

默认情况下,Istio 会跟踪迁移到 Istio 代理的服务器工作负载并配置客户端代理,将双向 TLS 流量自动发送到这些工作负载,并将 plain-text 流量发送到没有 sidecar 的工作负载。

因此,您无需做额外操作,具有代理的工作负载之间的所有流量即可启用双向 TLS。例如检查请求 httpbin/header 的响应。 使用双向 TLS 时,代理会将 X-Forwarded-Client-Cert 标头注入到后端的上游请求。存在该标头则说启用了双向 TLS。例如:

  1. $ kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl -s http://httpbin.foo:8000/headers -s | grep X-Forwarded-Client-Cert | sed 's/Hash=[a-z0-9]*;/Hash=<redacted>;/'
  2. "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/foo/sa/httpbin;Hash=<redacted>;Subject=\"\";URI=spiffe://cluster.local/ns/foo/sa/sleep"

当服务器没有 sidecar 时, X-Forwarded-Client-Cert 标头将不会存在,这意味着请求是 plain-text 的。

  1. $ kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl http://httpbin.legacy:8000/headers -s | grep X-Forwarded-Client-Cert

全局以严格模式启用 Istio 双向 TLS

当 Istio 自动将代理和工作负载之间的所有流量升级到双向 TLS 时,工作负载仍然可以接收 plain-text 流量。为了阻止整个网格的服务以非双向 TLS 通信,需要将整个服务网格的对等认证策略设置为 STRICT 模式。 在整个服务网格范围内,对等认证策略不应该有一个 selector,它必须应用于 根命名空间,例如:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1beta1
  3. kind: PeerAuthentication
  4. metadata:
  5. name: "default"
  6. namespace: "istio-system"
  7. spec:
  8. mtls:
  9. mode: STRICT
  10. EOF

该示例假定命名空间 istio-system 是根命名空间。如果在安装过程中使用了不同的值,请将 istio-system 替换为所使用的值。

此对等身份验证策略将工作负载配置为仅接受使用 TLS 加密的请求。由于未对 selector 字段指定值,因此该策略适用于服务网格中的所有工作负载。

再次运行测试指令:

  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: 000
  11. command terminated with exit code 56
  12. sleep.legacy to httpbin.legacy: 200

您会发现除了从没有 sidecar 的服务(sleep.legacy) 到有 sidecar 的服务(httpbin.foohttpbin.bar) 的请求外,其他请求依然是返回成功的。

清除部分 1

删除在会话中添加的全局身份验证策略和 destination rules

  1. $ kubectl delete peerauthentication -n istio-system default

为每个命名空间或者工作负载启用双向 TLS

命名空间级别策略

如果要将特定命名空间内的所有工作负载更改双向 TLS,请使用命名空间级别策略。该策略的规范与整个服务网格级别规范相同,但是您可以在 metadata 字段指定命名空间的名称。例如,以下对等身份验证策略在 foo 命名空间上启用了严格的双向 TLS:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1beta1
  3. kind: PeerAuthentication
  4. metadata:
  5. name: "default"
  6. namespace: "foo"
  7. spec:
  8. mtls:
  9. mode: STRICT
  10. EOF

由于这些策略只应用于命名空间 foo 中的服务,您会看到只有从没有 sidecar 的客户端(sleep.legacy)到有 sidecar 的客户端(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

为每个工作负载启用双向 TLS

要为特定工作负载设置对等身份验证策略,必须配置 selector 字段并指定与所需工作负载匹配的标签。然而 Istio 不能将出站双向 TLS 流量的工作负载策略聚合到服务。您应该配置 destination rule 来管理该行为。

例如,以下对等身份验证策略和 destination rule 将为 httpbin.bar 服务启用严格的双向 TLS:

  1. $ cat <<EOF | kubectl apply -n bar -f -
  2. apiVersion: security.istio.io/v1beta1
  3. kind: PeerAuthentication
  4. metadata:
  5. name: "httpbin"
  6. namespace: "bar"
  7. spec:
  8. selector:
  9. matchLabels:
  10. app: httpbin
  11. mtls:
  12. mode: STRICT
  13. EOF

添加一个 destination rule:

  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. $ 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: 000
  11. command terminated with exit code 56
  12. sleep.legacy to httpbin.legacy: 200
  1. ...
  2. sleep.legacy to httpbin.bar: 000
  3. command terminated with exit code 56

要优化每个端口的 双向 TLS 设置,必须配置 portLevelMtls 字段。 例如,以下对等身份验证策略要求在除 80 端口以外的所有端口上都使用双向 TLS:

  1. $ cat <<EOF | kubectl apply -n bar -f -
  2. apiVersion: security.istio.io/v1beta1
  3. kind: PeerAuthentication
  4. metadata:
  5. name: "httpbin"
  6. namespace: "bar"
  7. spec:
  8. selector:
  9. matchLabels:
  10. app: httpbin
  11. mtls:
  12. mode: STRICT
  13. portLevelMtls:
  14. 80:
  15. mode: DISABLE
  16. EOF

对 destination rule 也做相应修改:

  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. portLevelSettings:
  12. - port:
  13. number: 8000
  14. tls:
  15. mode: DISABLE
  16. EOF
  1. 对等身份验证策略中的端口值为容器的端口。destination rule 的值是服务的端口。
  2. 如果端口绑定到服务则只能使用 portLevelMtls 配置,其他配置将被 Istio 忽略。
  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.foo 添加一个禁用双向 TLS 的策略。 注意您已经为所有在命名空间 foo 中的服务创建了命名空间范围的策略来启用双向 TLS 并观察到从 sleep.legacyhttpbin.foo 的请求都会失败(如上所示)。

  1. $ cat <<EOF | kubectl apply -n foo -f -
  2. apiVersion: security.istio.io/v1beta1
  3. kind: PeerAuthentication
  4. metadata:
  5. name: "overwrite-example"
  6. namespace: "foo"
  7. spec:
  8. selector:
  9. matchLabels:
  10. app: httpbin
  11. mtls:
  12. mode: DISABLE
  13. EOF

同时修改 destination rule:

  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

删除之前步骤中创建的策略和 destination rules:

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

终端用户认证

为了体验这个特性,您需要一个有效的 JWT。该 JWT 必须和您用于该示例的 JWKS 终端对应。在这个教程中,我们使用来自 Istio 代码基础库的 JWT testJWKS 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 # 使用 Istio 的默认网关实现
  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

参考确定 ingress IP 和端口章节配置环境变量 INGRESS_HOSTINGRESS_PORT 的值。

执行测试指令

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

现在添加一个身份验证策略,该策略要求入口网关需要指定终端用户的 JWT。

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1beta1
  3. kind: RequestAuthentication
  4. metadata:
  5. name: "jwt-example"
  6. namespace: istio-system
  7. spec:
  8. selector:
  9. matchLabels:
  10. istio: ingressgateway
  11. jwtRules:
  12. - issuer: "testing@secure.istio.io"
  13. jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.10/security/tools/jwt/samples/jwks.json"
  14. EOF

策略生效的命名空间是 istio-system,生效的工作负载由 selector 字段决定,上面的配置值是带有 app: ingressgateway 标签的工作负载。

如果您在授权标头(默认位置)中提供了令牌,则 Istio 将使用 public key set 验证令牌,bearer 令牌无效会被拒绝,然而没有 bearer 令牌的请求会被接收。因此要观察此行为,请在没有令牌,令牌错误和有效令牌的情况下重试请求:

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

为了观察 JWT 验证的其它方面,使用脚本 gen-jwt.py 生成新 tokens 带上不同的发行人、受众、有效期等等进行测试。可以从 Istio 库下载此脚本:

  1. $ wget --no-verbose https://raw.githubusercontent.com/istio/istio/release-1.10/security/tools/jwt/samples/gen-jwt.py

您还需要 key.pem 文件:

  1. $ wget --no-verbose https://raw.githubusercontent.com/istio/istio/release-1.10/security/tools/jwt/samples/key.pem

如果您的系统尚未安装 jwcrypto 库,你需要从 jwcrypto 下载并安装。

JWT 认证有 60 秒的时钟偏移(clock skew),这意味着 JWT 令牌会比其配置 nbf 早 60 秒成为有效的,其配置 exp后 60 秒后仍然有效。

例如,下面的命令创建一个令牌,该令牌在5秒钟后过期。 如您所见,Istio 会一直通过认证直到 65 秒后才拒绝这些令牌:

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

您也可以给一个 ingress gateway 添加一个 JWT 策略(例如,服务 istio-ingressgateway.istio-system.svc.cluster.local)。 这个常用于为绑定到这个 gateway 的所有服务定义一个 JWT 策略而不是为单独的服务绑定策略。

提供有效令牌

拒绝没有有效的令牌的请求,需要增加名为 DENY 认证策略,可参考以下例子中的 notRequestPrincipals:["*"] 配置。仅当提供有效的JWT令牌时请求主体才可用,因此该规则将拒绝没有有效令牌的请求。

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1beta1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: "frontend-ingress"
  6. namespace: istio-system
  7. spec:
  8. selector:
  9. matchLabels:
  10. istio: ingressgateway
  11. action: DENY
  12. rules:
  13. - from:
  14. - source:
  15. notRequestPrincipals: ["*"]
  16. EOF

再次尝试不使用 token 请求服务,结果返回 403

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

按路径提供有效令牌

为了按路径(路径指 host、path 或者 method)提供有效令牌我们需要在其认证策略中指定这些路径,如下列配置中的 /headers。待规则生效后,对$INGRESS_HOST:$INGRESS_PORT/headers 的请求将失败,错误代码为 403。而到其他所有路径的请求 —— 例如:$INGRESS_HOST:$INGRESS_PORT/ip —— 都会成功。

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1beta1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: "frontend-ingress"
  6. namespace: istio-system
  7. spec:
  8. selector:
  9. matchLabels:
  10. istio: ingressgateway
  11. action: DENY
  12. rules:
  13. - from:
  14. - source:
  15. notRequestPrincipals: ["*"]
  16. to:
  17. - operation:
  18. paths: ["/headers"]
  19. EOF
  1. $ curl "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{http_code}\n"
  2. 403
  1. $ curl "$INGRESS_HOST:$INGRESS_PORT/ip" -s -o /dev/null -w "%{http_code}\n"
  2. 200

清除部分 3

  1. 删除认证策略:

    1. $ kubectl -n istio-system delete requestauthentication jwt-example
  2. 删除授权策略:

    1. $ kubectl -n istio-system delete authorizationpolicy frontend-ingress
  3. 删除生成令牌的脚本和密钥文件:

    1. $ rm -f ./gen-jwt.py ./key.pem
  4. 如果您不不打算继续后续章节的任务,您可以通过删除命名空间的方式清除所有资源:

    1. $ kubectl delete ns foo bar legacy