认证策略

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

开始之前

  1. $ istioctl install --set profile=default

设置

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

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/curl/curl.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/curl/curl.yaml@) -n bar
  7. $ kubectl create ns legacy
  8. $ kubectl apply -f @samples/httpbin/httpbin.yaml@ -n legacy
  9. $ kubectl apply -f @samples/curl/curl.yaml@ -n legacy

现在您可以在 foobarlegacy 三个命名空间下的任意 curl Pod 中使用 curlhttpbin.foohttpbin.barhttpbin.legacy 发送 HTTP 请求来验证部署结果。所有请求都应该成功并返回 HTTP 200。

例如,检查 curl.barhttpbin.foo 可达性的指令如下:

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

使用以下指令确认系统中没有对等认证策略:

  1. $ kubectl get peerauthentication --all-namespaces
  2. No resources found

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

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

您可能会看到目标规则配置了除上面显示以外的其他 host,这依赖于 Istio 的版本。 然而,在 foobarlegacy 命名空间中不应有任何 host 相关的目标规则, 也不应配置匹配所有的通配符 *

自动双向 TLS

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

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

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

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

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

全局以 STRICT 模式启用 Istio 双向 TLS

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

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  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=curl -n ${from} -o jsonpath={.items..metadata.name})" -c curl -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{http_code}\n"; done; done
  2. curl.foo to httpbin.foo: 200
  3. curl.foo to httpbin.bar: 200
  4. curl.foo to httpbin.legacy: 200
  5. curl.bar to httpbin.foo: 200
  6. curl.bar to httpbin.bar: 200
  7. curl.bar to httpbin.legacy: 200
  8. curl.legacy to httpbin.foo: 000
  9. command terminated with exit code 56
  10. curl.legacy to httpbin.bar: 000
  11. command terminated with exit code 56
  12. curl.legacy to httpbin.legacy: 200

您会发现除了从没有 Sidecar 的服务(curl.legacy)到有 Sidecar 的服务(httpbin.foohttpbin.bar)的请求外,其他请求依然是成功的。 这是符合预期的结果,因为现在严格要求使用双向 TLS,但没有 Sidecar 的工作负载无法满足这一要求。

清理第 1 部分

删除在会话中添加的全局认证策略:

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

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

命名空间级别策略

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

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

由于这些策略只应用于命名空间 foo 中的服务,您会看到只有从没有 Sidecar 的客户端(curl.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=curl -n ${from} -o jsonpath={.items..metadata.name})" -c curl -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{http_code}\n"; done; done
  2. curl.foo to httpbin.foo: 200
  3. curl.foo to httpbin.bar: 200
  4. curl.foo to httpbin.legacy: 200
  5. curl.bar to httpbin.foo: 200
  6. curl.bar to httpbin.bar: 200
  7. curl.bar to httpbin.legacy: 200
  8. curl.legacy to httpbin.foo: 000
  9. command terminated with exit code 56
  10. curl.legacy to httpbin.bar: 200
  11. curl.legacy to httpbin.legacy: 200

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

要为特定工作负载设置对等认证策略,您必须配置 selector 字段并指定与所需工作负载匹配的标签。例如,以下对等认证策略和目标规则将为 httpbin.bar 服务启用严格的双向 TLS:

  1. $ cat <<EOF | kubectl apply -n bar -f -
  2. apiVersion: security.istio.io/v1
  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

再次执行测试命令。跟预期一样,从 curl.legacyhttpbin.bar 的请求因为同样的原因失败。

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

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

  1. $ cat <<EOF | kubectl apply -n bar -f -
  2. apiVersion: security.istio.io/v1
  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. 8080:
  15. mode: DISABLE
  16. EOF
  1. 对等认证策略中的端口值为容器的端口。目标规则的值是服务的端口。
  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=curl -n ${from} -o jsonpath={.items..metadata.name})" -c curl -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{http_code}\n"; done; done
  2. curl.foo to httpbin.foo: 200
  3. curl.foo to httpbin.bar: 200
  4. curl.foo to httpbin.legacy: 200
  5. curl.bar to httpbin.foo: 200
  6. curl.bar to httpbin.bar: 200
  7. curl.bar to httpbin.legacy: 200
  8. curl.legacy to httpbin.foo: 000
  9. command terminated with exit code 56
  10. curl.legacy to httpbin.bar: 200
  11. curl.legacy to httpbin.legacy: 200

策略优先级

为了演示特定服务策略比命名空间范围的策略优先级高,您可以像下面一样为 httpbin.foo 添加一个禁用双向 TLS 的策略。 注意您已经为所有在命名空间 foo 中的服务创建了命名空间范围的策略来启用双向 TLS,发现从 curl.legacyhttpbin.foo 的请求都会失败(如上所示)。

  1. $ cat <<EOF | kubectl apply -n foo -f -
  2. apiVersion: security.istio.io/v1
  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

重新执行来自 curl.legacy 的请求,您应该又会看到请求成功并返回 200 代码, 证明了特定服务策略覆盖了命名空间范围的策略。

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

清理第 2 部分

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

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

终端用户认证

为了体验这个特性,您需要一个有效的 JWT。该 JWT 必须和您用于该示例的 JWKS 终端对应。 在这个教程中,我们使用来自 Istio 代码基础库的 JWT testJWKS endpoint

同时为了方便访问,通过 Ingress 网关暴露 httpbin.foo (详细细节请查看 Ingress 任务)。

Istio 支持 Kubernetes Gateway API, 并计划将其作为未来流量管理的默认 API。 以下说明指导您在网格中配置流量管理时如何选择使用 Gateway API 或 Istio 配置 API。 请按照您的首选项遵循 Gateway APIIstio APIs 页签中的指示说明。

请注意,Kubernetes Gateway API CRD 不会默认安装在大多数 Kubernetes 集群上, 因此请确保在使用 Gateway API 之前已安装好这些 CRD:

  1. $ kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \
  2. { kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml; }

配置网关:

Zip

  1. $ kubectl apply -f @samples/httpbin/httpbin-gateway.yaml@ -n foo

按照确定 Ingress IP 和端口中的说明, 设置 INGRESS_PORTINGRESS_HOST 环境变量。

创建网关:

Zip

  1. $ kubectl apply -f @samples/httpbin/gateway-api/httpbin-gateway.yaml@ -n foo
  2. $ kubectl wait --for=condition=programmed gtw -n foo httpbin-gateway

设置 INGRESS_PORTINGRESS_HOST 环境变量:

  1. $ export INGRESS_HOST=$(kubectl get gtw httpbin-gateway -n foo -o jsonpath='{.status.addresses[0].value}')
  2. $ export INGRESS_PORT=$(kubectl get gtw httpbin-gateway -n foo -o jsonpath='{.spec.listeners[?(@.name=="http")].port}')

通过网关运行测试查询:

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

现在添加一个认证策略,该策略要求 Ingress 网关指定终端用户的 JWT。

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  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.24/security/tools/jwt/samples/jwks.json"
  14. EOF
  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  3. kind: RequestAuthentication
  4. metadata:
  5. name: "jwt-example"
  6. namespace: foo
  7. spec:
  8. targetRef:
  9. kind: Gateway
  10. group: gateway.networking.k8s.io
  11. name: httpbin-gateway
  12. jwtRules:
  13. - issuer: "testing@secure.istio.io"
  14. jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.24/security/tools/jwt/samples/jwks.json"
  15. EOF

在所选的工作负载的命名空间中应用该策略,本例中是 Ingress 网关。

如果您在授权标头中提供了一个令牌,并且其位置是隐式默认的,Istio 将使用公钥集验证令牌, 并拒绝无效的令牌请求。但是,没有令牌的请求会被接受。 为了观察这种行为,请尝试重新发出没有令牌、有错误令牌以及含有效令牌的请求。

  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.24/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 生成新 token 带上不同的发行人、受众、有效期等等进行测试。可以从 Istio 库下载此脚本:

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

您还需要 key.pem 文件:

  1. $ wget --no-verbose https://raw.githubusercontent.com/istio/istio/release-1.24/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"; curl 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/v1
  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
  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: "frontend-ingress"
  6. namespace: foo
  7. spec:
  8. targetRef:
  9. kind: Gateway
  10. group: gateway.networking.k8s.io
  11. name: httpbin-gateway
  12. action: DENY
  13. rules:
  14. - from:
  15. - source:
  16. notRequestPrincipals: ["*"]
  17. EOF

重新发送没有令牌的请求。请求失败并返回错误码 403

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

按路径提供有效令牌

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

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  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. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: "frontend-ingress"
  6. namespace: foo
  7. spec:
  8. targetRef:
  9. kind: Gateway
  10. group: gateway.networking.k8s.io
  11. name: httpbin-gateway
  12. action: DENY
  13. rules:
  14. - from:
  15. - source:
  16. notRequestPrincipals: ["*"]
  17. to:
  18. - operation:
  19. paths: ["/headers"]
  20. 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