安全问题
终端用户认证失败
使用 Istio,您可以通过请求认证策略启用终端用户认证。 目前,Istio 认证策略提供的终端用户凭证是 JWT。以下是排查终端用户 JWT 身份认证问题的指南。
如果
jwksUri
未设置,确保 JWT 发行者是 url 格式并且url + /.well-known/openid-configuration
可以在浏览器中打开; 例如,如果 JWT 发行者是https://accounts.google.com
,确保https://accounts.google.com/.well-known/openid-configuration
是有效的 url,并且可以在浏览器中打开。apiVersion: security.istio.io/v1 kind: RequestAuthentication metadata: name: "example-3" spec: selector: matchLabels: app: httpbin jwtRules: - issuer: "testing@secure.istio.io" jwksUri: "https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/jwks.json"
如果 JWT 令牌放在 HTTP 请求头 Authorization 字段值中,需要确认 JWT 令牌的有效性(未过期等)。JWT 令牌中的字段可以使用 jwt.io 这类在线 JWT 解析工具进行解码。
通过
istioctl proxy-config
命令来验证目标负载的 Envoy 代理配置是否正确。当配置完成上面提到的策略示例后,可以使用以下的指令来检查
listener
在入站端口80
上的配置。您应该可以看到envoy.filters.http.jwt_authn
过滤器包含我们在策略中已经声明的发行者和 JWKS 信息。$ POD=$(kubectl get pod -l app=httpbin -n foo -o jsonpath={.items..metadata.name}) $ istioctl proxy-config listener ${POD} -n foo --port 80 --type HTTP -o json <redacted> { "name": "envoy.filters.http.jwt_authn", "typedConfig": { "@type": "type.googleapis.com/envoy.config.filter.http.jwt_authn.v2alpha.JwtAuthentication", "providers": { "origins-0": { "issuer": "testing@secure.istio.io", "localJwks": { "inlineString": "*redacted*" }, "payloadInMetadata": "testing@secure.istio.io" } }, "rules": [ { "match": { "prefix": "/" }, "requires": { "requiresAny": { "requirements": [ { "providerName": "origins-0" }, { "allowMissing": {} } ] } } } ] } }, <redacted>
授权过于严格或者宽松
确保策略 YAML 文件中没有输入错误
一个常见的错误是无意中在 YAML 文件中定义了多个项,例如下面的策略:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: example
namespace: foo
spec:
action: ALLOW
rules:
- to:
- operation:
paths:
- /foo
- from:
- source:
namespaces:
- foo
您期望的策略所允许的请求是符合路径为 /foo
且源命名空间为 foo
。
但是,策略实际上允许的请求是符合路径为 /foo
或源命名空间为 foo
,
这显然会更加宽松。
在 YAML 的语义中,from:
前面的 -
意味着这是列表中的一个新元素。
这会在策略中创建两条规则,而不是所希望的一条。在认证策略中,多条规则之间是
OR
的关系。
为了解决这个问题,只需要将多余的 -
移除,这样策略就只有一条规则来允许符合路径为
/foo
且源命名空间为 foo
的请求,这样就更加严格了。
确保您没有在 TCP 端口上使用仅适用于 HTTP 的字段
授权策略会变得更加严格因为定义了仅适用于 HTTP 的字段
(比如 host
、path
、headers
、JWT 等等) 在纯 TCP 连接上是不存在的。
对于 ALLOW
类的策略来说,这些字段不会被匹配。但对于 DENY
以及 CUSTOM
类策略来说,这类字段会被认为是始终匹配的。最终结果会是一个更加严格的策略从而可能导致意外的连接拒绝。
检查 Kubernetes 服务定义来确定端口是命名中包含正确的协议名称。
如果您在端口上使用了仅适用于 HTTP 的字段,要确保端口名有 http-
前缀。
确保策略配置在正确的目标上
检查工作负载的选择器和命名空间来确认策略配置在了正确的目标上。您可以通过指令
istioctl x authz check POD-NAME.POD-NAMESPACE
来检查认证策略。
留意策略中的动作
如果没有声明,策略中默认动作是
ALLOW
。当一个工作负载上同时配置了多个动作时(
CUSTOM
、ALLOW
和DENY
), 所有的动作必须都满足。换句话说,如果有任何一个动作拒绝该请求,那么该请求会被拒绝, 并且只有所有的动作都允许了该请求,该请求才会被允许。在任何情况下,
AUDIT
动作不会实施控制访问权并且不会拒绝请求。
阅读授权隐式启用了解有关评估顺序的更多详细信息。
确保 Istiod 接受策略
Istiod 负责对授权策略进行转换,并将其分发给 Sidecar。下面的步骤可以用于确认 Istiod 是否按预期在工作:
运行以下命令启用 Istiod 的调试日志记录:
$ istioctl admin log --level authorization:debug
通过以下命令获取 Istio 日志:
$ kubectl logs $(kubectl -n istio-system get pods -l app=istiod -o jsonpath='{.items[0].metadata.name}') -c discovery -n istio-system
检查输出并验证是否出现错误,例如您可能会看到类似这样的内容:
2021-04-23T20:53:29.507314Z info ads Push debounce stable[31] 1: 100.981865ms since last change, 100.981653ms since last push, full=true 2021-04-23T20:53:29.507641Z info ads XDS: Pushing:2021-04-23T20:53:29Z/23 Services:15 ConnectedEndpoints:2 Version:2021-04-23T20:53:29Z/23 2021-04-23T20:53:29.507911Z debug authorization Processed authorization policy for httpbin-74fb669cc6-lpscm.foo with details: * found 0 CUSTOM actions 2021-04-23T20:53:29.508077Z debug authorization Processed authorization policy for curl-557747455f-6dxbl.foo with details: * found 0 CUSTOM actions 2021-04-23T20:53:29.508128Z debug authorization Processed authorization policy for httpbin-74fb669cc6-lpscm.foo with details: * found 1 DENY actions, 0 ALLOW actions, 0 AUDIT actions * generated config from rule ns[foo]-policy[deny-path-headers]-rule[0] on HTTP filter chain successfully * built 1 HTTP filters for DENY action * added 1 HTTP filters to filter chain 0 * added 1 HTTP filters to filter chain 1 2021-04-23T20:53:29.508158Z debug authorization Processed authorization policy for curl-557747455f-6dxbl.foo with details: * found 0 DENY actions, 0 ALLOW actions, 0 AUDIT actions 2021-04-23T20:53:29.509097Z debug authorization Processed authorization policy for curl-557747455f-6dxbl.foo with details: * found 0 CUSTOM actions 2021-04-23T20:53:29.509167Z debug authorization Processed authorization policy for curl-557747455f-6dxbl.foo with details: * found 0 DENY actions, 0 ALLOW actions, 0 AUDIT actions 2021-04-23T20:53:29.509501Z debug authorization Processed authorization policy for httpbin-74fb669cc6-lpscm.foo with details: * found 0 CUSTOM actions 2021-04-23T20:53:29.509652Z debug authorization Processed authorization policy for httpbin-74fb669cc6-lpscm.foo with details: * found 1 DENY actions, 0 ALLOW actions, 0 AUDIT actions * generated config from rule ns[foo]-policy[deny-path-headers]-rule[0] on HTTP filter chain successfully * built 1 HTTP filters for DENY action * added 1 HTTP filters to filter chain 0 * added 1 HTTP filters to filter chain 1 * generated config from rule ns[foo]-policy[deny-path-headers]-rule[0] on TCP filter chain successfully * built 1 TCP filters for DENY action * added 1 TCP filters to filter chain 2 * added 1 TCP filters to filter chain 3 * added 1 TCP filters to filter chain 4 2021-04-23T20:53:29.510903Z info ads LDS: PUSH for node:curl-557747455f-6dxbl.foo resources:18 size:85.0kB 2021-04-23T20:53:29.511487Z info ads LDS: PUSH for node:httpbin-74fb669cc6-lpscm.foo resources:18 size:86.4kB
以上输出说明 Istiod 生成了:
适用于工作负载
httpbin-74fb669cc6-lpscm.foo
且带有策略ns[foo]-policy[deny-path-headers]-rule[0]
的 HTTP 过滤器配置。适用于工作负载
httpbin-74fb669cc6-lpscm.foo
且带有策略ns[foo]-policy[deny-path-headers]-rule[0]
的 TCP 过滤器配置。
确认 Istiod 正确地将策略分发给了代理
Pilot 负责向代理分发授权策略。下面的步骤用来确认 Pilot 按照预期工作:
运行下面的命令,获取
httpbin
工作负载的代理配置信息:$ kubectl exec $(kubectl get pods -l app=httpbin -o jsonpath='{.items[0].metadata.name}') -c istio-proxy -- pilot-agent request GET config_dump
校验日志内容:
- 日志中包含了一个
envoy.filters.http.rbac
过滤器,会对每一个进入的请求强制执行授权策略。 - 您更新授权策略之后,Istio 会据此更新过滤器。
- 日志中包含了一个
下面的输出表明,
httpbin
的代理启用了envoy.filters.http.rbac
过滤器, 配置的规则为拒绝所有人访问/headers
路径。{ "name": "envoy.filters.http.rbac", "typed_config": { "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC", "rules": { "action": "DENY", "policies": { "ns[foo]-policy[deny-path-headers]-rule[0]": { "permissions": [ { "and_rules": { "rules": [ { "or_rules": { "rules": [ { "url_path": { "path": { "exact": "/headers" } } } ] } } ] } } ], "principals": [ { "and_ids": { "ids": [ { "any": true } ] } } ] } } }, "shadow_rules_stat_prefix": "istio_dry_run_allow_" } },
确认策略在代理中正确执行
代理是授权策略的最终实施者。下面的步骤帮助用户确认代理是否按预期工作:
使用以下命令,在代理中打开授权调试日志:
$ istioctl proxy-config log deploy/httpbin --level "rbac:debug"
确认可以看到以下输出:
active loggers: ... ... rbac: debug ... ...
发送一些请求到
httpbin
工作负载以生成一些日志。使用以下命令打印代理日志:
$ kubectl logs $(kubectl get pods -l app=httpbin -o jsonpath='{.items[0].metadata.name}') -c istio-proxy
检查输出,并验证:
根据请求被允许或者被拒绝,分别输出日志包含
enforced allowed
或这enforced denied
。授权策略需要从请求中获取数据。
以下是在
/httpbin
路径处请求的输出示例:... 2021-04-23T20:43:18.552857Z debug envoy rbac checking request: requestedServerName: outbound_.8000_._.httpbin.foo.svc.cluster.local, sourceIP: 10.44.3.13:46180, directRemoteIP: 10.44.3.13:46180, remoteIP: 10.44.3.13:46180,localAddress: 10.44.1.18:80, ssl: uriSanPeerCertificate: spiffe://cluster.local/ns/foo/sa/curl, dnsSanPeerCertificate: , subjectPeerCertificate: , headers: ':authority', 'httpbin:8000' ':path', '/headers' ':method', 'GET' ':scheme', 'http' 'user-agent', 'curl/7.76.1-DEV' 'accept', '*/*' 'x-forwarded-proto', 'http' 'x-request-id', '672c9166-738c-4865-b541-128259cc65e5' 'x-envoy-attempt-count', '1' 'x-b3-traceid', '8a124905edf4291a21df326729b264e9' 'x-b3-spanid', '21df326729b264e9' 'x-b3-sampled', '0' 'x-forwarded-client-cert', 'By=spiffe://cluster.local/ns/foo/sa/httpbin;Hash=d64cd6750a3af8685defbbe4dd8c467ebe80f6be4bfe9ca718e81cd94129fc1d;Subject="";URI=spiffe://cluster.local/ns/foo/sa/curl' , dynamicMetadata: filter_metadata { key: "istio_authn" value { fields { key: "request.auth.principal" value { string_value: "cluster.local/ns/foo/sa/curl" } } fields { key: "source.namespace" value { string_value: "foo" } } fields { key: "source.principal" value { string_value: "cluster.local/ns/foo/sa/curl" } } fields { key: "source.user" value { string_value: "cluster.local/ns/foo/sa/curl" } } } } 2021-04-23T20:43:18.552910Z debug envoy rbac enforced denied, matched policy ns[foo]-policy[deny-path-headers]-rule[0] ...
日志
enforced denied, matched policy ns[foo]-policy[deny-path-headers]-rule[0]
意味着请求被策略ns[foo]-policy[deny-path-headers]-rule[0]
拒绝。以下是模拟运行模式下授权策略的输出示例:
... 2021-04-23T20:59:11.838468Z debug envoy rbac checking request: requestedServerName: outbound_.8000_._.httpbin.foo.svc.cluster.local, sourceIP: 10.44.3.13:49826, directRemoteIP: 10.44.3.13:49826, remoteIP: 10.44.3.13:49826,localAddress: 10.44.1.18:80, ssl: uriSanPeerCertificate: spiffe://cluster.local/ns/foo/sa/curl, dnsSanPeerCertificate: , subjectPeerCertificate: , headers: ':authority', 'httpbin:8000' ':path', '/headers' ':method', 'GET' ':scheme', 'http' 'user-agent', 'curl/7.76.1-DEV' 'accept', '*/*' 'x-forwarded-proto', 'http' 'x-request-id', 'e7b2fdb0-d2ea-4782-987c-7845939e6313' 'x-envoy-attempt-count', '1' 'x-b3-traceid', '696607fc4382b50017c1f7017054c751' 'x-b3-spanid', '17c1f7017054c751' 'x-b3-sampled', '0' 'x-forwarded-client-cert', 'By=spiffe://cluster.local/ns/foo/sa/httpbin;Hash=d64cd6750a3af8685defbbe4dd8c467ebe80f6be4bfe9ca718e81cd94129fc1d;Subject="";URI=spiffe://cluster.local/ns/foo/sa/curl' , dynamicMetadata: filter_metadata { key: "istio_authn" value { fields { key: "request.auth.principal" value { string_value: "cluster.local/ns/foo/sa/curl" } } fields { key: "source.namespace" value { string_value: "foo" } } fields { key: "source.principal" value { string_value: "cluster.local/ns/foo/sa/curl" } } fields { key: "source.user" value { string_value: "cluster.local/ns/foo/sa/curl" } } } } 2021-04-23T20:59:11.838529Z debug envoy rbac shadow denied, matched policy ns[foo]-policy[deny-path-headers]-rule[0] 2021-04-23T20:59:11.838538Z debug envoy rbac no engine, allowed by default ...
日志
shadow denied, matched policy ns[foo]-policy[deny-path-headers]-rule[0]
意味着请求将会被模拟运行策略ns[foo]-policy[deny-path-headers]-rule[0]
拒绝。日志
no engine, allowed by default
意味着请求实际上是被允许的, 因为模拟运行策略是工作负载上唯一的策略。
密钥和证书错误
如果您怀疑 Istio 使用的某些密钥或证书不正确,您可以检查任何 Pod 的内容信息。
$ istioctl proxy-config secret curl-8f795f47d-4s4t7
RESOURCE NAME TYPE STATUS VALID CERT SERIAL NUMBER NOT AFTER NOT BEFORE
default Cert Chain ACTIVE true 138092480869518152837211547060273851586 2020-11-11T16:39:48Z 2020-11-10T16:39:48Z
ROOTCA CA ACTIVE true 288553090258624301170355571152070165215 2030-11-08T16:34:52Z 2020-11-10T16:34:52Z
通过 -o json
标记,您可以将证书的全部内容传递给 openssl
来分析其内容:
$ istioctl proxy-config secret curl-8f795f47d-4s4t7 -o json | jq '[.dynamicActiveSecrets[] | select(.name == "default")][0].secret.tlsCertificate.certificateChain.inlineBytes' -r | base64 -d | openssl x509 -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
99:59:6b:a2:5a:f4:20:f4:03:d7:f0:bc:59:f5:d8:40
Signature Algorithm: sha256WithRSAEncryption
Issuer: O = k8s.cluster.local
Validity
Not Before: Jun 4 20:38:20 2018 GMT
Not After : Sep 2 20:38:20 2018 GMT
...
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Subject Alternative Name:
URI:spiffe://cluster.local/ns/my-ns/sa/my-sa
...
确保显示的证书包含有效信息。特别是,Subject Alternative Name
字段应为
URI:spiffe://cluster.local/ns/my-ns/sa/my-sa
。
双向 TLS 错误
如果怀疑双向 TLS 出现了问题,首先要确认 istiod 的健康状态, 接下来要查看的是密钥和证书正确下发到了 Sidecar。
如果上述检查都正确无误,下一步就应该验证认证策略已经创建, 并且对应的目标规则是否正确应用。
如果您怀疑客户端 Sidecar 可能不正确地发送双向 TLS 或明文流量, 请检查 Grafana 工作负载仪表盘。 无论是否使用 mTLS,都对出站请求添加注解。检查后,如果您认为客户端 Sidecar 是错误的, 请在 GitHub 上提交一个 Issue。