安全问题

终端用户认证失败

使用 Istio,可以通过请求认证策略 启用终端用户认证。目前,Istio 认证策略提供的终端用户凭证是 JWT。以下是排查终端用户 JWT 身份认证问题的指南。

  1. 如果 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/v1beta1"
    kind: "RequestAuthentication"
    metadata:
      name: "example-3"
    spec:
      selector:
        matchLabels:
          app: httpbin
      jwtRules:
      - issuer: "testing@secure.istio.io"
        jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.15/security/tools/jwt/samples/jwks.json"
    
  2. 如果 JWT Token 放在 HTTP 请求头 Authorization 字段值中,需要确认 JWT Token 的有效性(未过期等)。JWT 令牌中的字段可以使用在线 JWT 解析工具进行解码,例如:jwt.io

  3. 通过 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/v1beta1
kind: AuthorizationPolicy
metadata:
  name: example
  namespace: foo
spec:
  action: ALLOW
  rules:
  - to:
    - operation:
        paths:
        - /foo
  - from:
    - source:
        namespaces:
        - foo

您期望的策略所允许的请求是符合路径为 /foo and 源命名空间为 foo。但是,策略实际上允许的请求是符合路径为 /foo or 源命名空间为 foo,这显然会更加宽松。

在 YAML 的语义中,from: 前面的 - 意味着这是列表中的一个新元素。这会在策略中创建两条规则,而不是所希望的一条。在认证策略中,多条规则之间是 OR 的关系。

为了解决这个问题,只需要将多余的 - 移除,这样策略就只有一条规则来允许符合路径为 /foo and 源命名空间为 foo 的请求,这样就更加严格了。

确保您没有在 TCP 端口上使用仅适用于 HTTP 的字段

授权策略会变得更加严格因为定义了仅适用于 HTTP 的字段 (比如 hostpathheaders,JWT, 等等) 在纯 TCP 连接上是不存在的。

对于 ALLOW 类的策略来说,这些字段不会被匹配。但对于 DENY 以及 CUSTOM 类策略来说,这类字段会被认为是始终匹配的。最终结果会是一个更加严格的策略从而可能导致意外的连接拒绝。

检查 Kubernetes 服务定义来确定端口是命名中包含正确的协议名称。如果您在端口上使用了仅适用于 HTTP 的字段,要确保端口名有 http- 前缀。

确保策略配置在正确的目标上

检查工作负载的选择器和命名空间来确认策略配置在了正确的目标上。您可以通过指令 istioctl x authz check POD-NAME.POD-NAMESPACE 来检查认证策略。

留意策略中的动作

  • 如果没有声明,策略中默认动作是 ALLOW

  • 当一个工作负载上同时配置了多个动作时 (CUSTOM, ALLOWDENY), 所有的动作必须都满足。换句话说,如果有任何一个动作拒绝该请求,那么该请求会被拒绝,并且只有所有的动作都允许了该请求,该请求才会被允许。

  • 在任何情况下,AUDIT 动作不会实施控制访问权并且不会拒绝请求。

确保 Istiod 接受策略

Istiod 负责对授权策略进行转换,并将其分发给 Sidecar。下面的的步骤可以用于确认 Istiod 是否按预期在工作:

  1. 运行下列命令,导出 Istiod 的 ControlZ

    $ istioctl dashboard controlz $(kubectl -n istio-system get pods -l app=istiod -o jsonpath='{.items[0].metadata.name}').istio-system
    
  2. 等待浏览器打开后,点击左侧菜单 Logging Scopes

  3. authorization 输出级别修改为 debug

  4. 在步骤 1 中打开的终端窗口中输入 Ctrl+C,终止端口转发进程。

  5. 执行以下命令,输出 Pilot 日志并搜索 authorization

    $ kubectl logs $(kubectl -n istio-system get pods -l app=istiod -o jsonpath='{.items[0].metadata.name}') -c discovery -n istio-system | grep authorization
    
  6. 检查输出并验证:

    • 没有出现错误。
    • 出现 building v1beta1 policy 内容,意味着为目标服务生成了过滤器。
  7. 例如你可能会看到类似这样的内容:

    2020-03-05T23:43:21.621339Z   debug   authorization   found authorization allow policies for workload [app=ext-authz-server,pod-template-hash=5fd587cc9d,security.istio.io/tlsMode=istio,service.istio.io/canonical-name=ext-authz-server,service.istio.io/canonical-revision=latest] in foo
    2020-03-05T23:43:21.621348Z   debug   authorization   building filter for HTTP listener protocol
    2020-03-05T23:43:21.621351Z   debug   authorization   building v1beta1 policy
    2020-03-05T23:43:21.621399Z   debug   authorization   constructed internal model: &{Permissions:[{Services:[] Hosts:[] NotHosts:[] Paths:[] NotPaths:[] Methods:[] NotMethods:[] Ports:[] NotPorts:[] Constraints:[] AllowAll:true v1beta1:true}] Principals:[{Users:[] Names:[cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account] NotNames:[] Group: Groups:[] NotGroups:[] Namespaces:[] NotNamespaces:[] IPs:[] NotIPs:[] RequestPrincipals:[] NotRequestPrincipals:[] Properties:[] AllowAll:false v1beta1:true}]}
    2020-03-05T23:43:21.621528Z   info    ads    LDS: PUSH for node:sleep-6bdb595bcb-vmchz.foo listeners:38
    2020-03-05T23:43:21.621997Z   debug   authorization   generated policy ns[foo]-policy[ext-authz-server]-rule[0]: permissions:<and_rules:<rules:<any:true > > > principals:<and_ids:<ids:<or_ids:<ids:<metadata:<filter:"istio_authn" path:<key:"source.principal" > value:<string_match:<exact:"cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account" > > > > > > > >
    2020-03-05T23:43:21.622052Z   debug   authorization   added HTTP filter to filter chain 0
    2020-03-05T23:43:21.623532Z   debug   authorization   found authorization allow policies for workload [app=ext-authz-server,pod-template-hash=5fd587cc9d,security.istio.io/tlsMode=istio,service.istio.io/canonical-name=ext-authz-server,service.istio.io/canonical-revision=latest] in foo
    2020-03-05T23:43:21.623543Z   debug   authorization   building filter for TCP listener protocol
    2020-03-05T23:43:21.623546Z   debug   authorization   building v1beta1 policy
    2020-03-05T23:43:21.623572Z   debug   authorization   constructed internal model: &{Permissions:[{Services:[] Hosts:[] NotHosts:[] Paths:[] NotPaths:[] Methods:[] NotMethods:[] Ports:[] NotPorts:[] Constraints:[] AllowAll:true v1beta1:true}] Principals:[{Users:[] Names:[cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account] NotNames:[] Group: Groups:[] NotGroups:[] Namespaces:[] NotNamespaces:[] IPs:[] NotIPs:[] RequestPrincipals:[] NotRequestPrincipals:[] Properties:[] AllowAll:false v1beta1:true}]}
    2020-03-05T23:43:21.623625Z   debug   authorization   generated policy ns[foo]-policy[ext-authz-server]-rule[0]: permissions:<and_rules:<rules:<any:true > > > principals:<and_ids:<ids:<or_ids:<ids:<authenticated:<principal_name:<exact:"spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account" > > > > > > >
    2020-03-05T23:43:21.623645Z   debug   authorization   added TCP filter to filter chain 0
    2020-03-05T23:43:21.623648Z   debug   authorization   added TCP filter to filter chain 1
    

    说明 Istiod 生成了:

    • 对于带 app=ext-authz-server,... 标签的负载生成了带有 ns[foo]-policy[ext-authz-server]-rule[0] 策略的 HTTP 过滤器配置。

    • 对于带 app=ext-authz-server,... 标签的负载生成了带有 ns[foo]-policy[ext-authz-server]-rule[0] 策略的 TCP 过滤器配置。

确认 Istiod 正确的将策略分发给了代理服务器

Pilot 负责向代理服务器分发授权策略。下面的步骤用来确认 Pilot 按照预期工作:

  1. 运行下面的命令,获取 productpage 服务的代理配置信息:

    $ kubectl exec  $(kubectl get pods -l app=productpage -o jsonpath='{.items[0].metadata.name}') -c istio-proxy -- pilot-agent request GET config_dump
    
  2. 校验日志内容:

    • 日志中包含了一个 envoy.filters.http.rbac 过滤器,会对每一个进入的请求执行授权策略。
    • 授权策略更新之后,Istio 会据此更新过滤器。
  3. 下面的输出表明,productpage 的代理启用了 envoy.filters.http.rbac 过滤器,配置的规则为允许任何人通过 GET 方法进行访问 productpage 服务。shadow_rules 没有生效,可以放心的忽略它。

    {
     "name": "envoy.filters.http.rbac",
     "config": {
      "rules": {
       "policies": {
        "productpage-viewer": {
         "permissions": [
          {
           "and_rules": {
            "rules": [
             {
              "or_rules": {
               "rules": [
                {
                 "header": {
                  "exact_match": "GET",
                  "name": ":method"
                 }
                }
               ]
              }
             }
            ]
           }
          }
         ],
         "principals": [
          {
           "and_ids": {
            "ids": [
             {
              "any": true
             }
            ]
           }
          }
         ]
        }
       }
      },
      "shadow_rules": {
       "policies": {}
      }
     }
    },
    

确认策略在代理服务器中正确执行

代理是授权策略的最终实施者。下面的步骤帮助用户确认代理的工作情况:

  1. 使用以下命令,在代理中打开授权调试日志:

    $ kubectl exec $(kubectl get pods -l app=productpage -o jsonpath='{.items[0].metadata.name}') -c istio-proxy -- pilot-agent request POST 'logging?rbac=debug'
    
  2. 确认可以看到以下输出:

    active loggers:
      ... ...
      rbac: debug
      ... ...
    
  3. 在浏览器中打开 productpage,以便生成日志。

  4. 使用以下命令打印代理日志:

    $ kubectl logs $(kubectl get pods -l app=productpage -o jsonpath='{.items[0].metadata.name}') -c istio-proxy
    
  5. 检查输出,并验证:

    • 根据请求被允许或者被拒绝,分别输出日志包含 enforced allowed 或这 enforced denied

    • 授权策略需要从请求中获取数据。

  6. 下面的输出表示,对 productpageGET 请求被策略放行。shadow denied 没有什么影响,你可以放心的忽略它。

    ...
    [2018-07-26 20:39:18.060][152][debug][rbac] external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:79] checking request: remoteAddress: 10.60.0.139:51158, localAddress: 10.60.0.93:9080, ssl: uriSanPeerCertificate: spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account, subjectPeerCertificate: O=, headers: ':authority', '35.238.0.62'
    ':path', '/productpage'
    ':method', 'GET'
    'upgrade-insecure-requests', '1'
    'user-agent', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
    'dnt', '1'
    'accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
    'accept-encoding', 'gzip, deflate'
    'accept-language', 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7'
    'x-forwarded-for', '10.60.0.1'
    'x-forwarded-proto', 'http'
    'x-request-id', 'e23ea62d-b25d-91be-857c-80a058d746d4'
    'x-b3-traceid', '5983108bf6d05603'
    'x-b3-spanid', '5983108bf6d05603'
    'x-b3-sampled', '1'
    'x-istio-attributes', 'CikKGGRlc3RpbmF0aW9uLnNlcnZpY2UubmFtZRINEgtwcm9kdWN0cGFnZQoqCh1kZXN0aW5hdGlvbi5zZXJ2aWNlLm5hbWVzcGFjZRIJEgdkZWZhdWx0Ck8KCnNvdXJjZS51aWQSQRI/a3ViZXJuZXRlczovL2lzdGlvLWluZ3Jlc3NnYXRld2F5LTc2NjY0Y2NmY2Ytd3hjcjQuaXN0aW8tc3lzdGVtCj4KE2Rlc3RpbmF0aW9uLnNlcnZpY2USJxIlcHJvZHVjdHBhZ2UuZGVmYXVsdC5zdmMuY2x1c3Rlci5sb2NhbApDChhkZXN0aW5hdGlvbi5zZXJ2aWNlLmhvc3QSJxIlcHJvZHVjdHBhZ2UuZGVmYXVsdC5zdmMuY2x1c3Rlci5sb2NhbApBChdkZXN0aW5hdGlvbi5zZXJ2aWNlLnVpZBImEiRpc3RpbzovL2RlZmF1bHQvc2VydmljZXMvcHJvZHVjdHBhZ2U='
    'content-length', '0'
    'x-envoy-internal', 'true'
    'sec-istio-authn-payload', 'CkVjbHVzdGVyLmxvY2FsL25zL2lzdGlvLXN5c3RlbS9zYS9pc3Rpby1pbmdyZXNzZ2F0ZXdheS1zZXJ2aWNlLWFjY291bnQSRWNsdXN0ZXIubG9jYWwvbnMvaXN0aW8tc3lzdGVtL3NhL2lzdGlvLWluZ3Jlc3NnYXRld2F5LXNlcnZpY2UtYWNjb3VudA=='
    , dynamicMetadata: filter_metadata {
      key: "istio_authn"
      value {
        fields {
          key: "request.auth.principal"
          value {
            string_value: "cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"
          }
        }
        fields {
          key: "source.principal"
          value {
            string_value: "cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"
          }
        }
      }
    }
    
    [2018-07-26 20:39:18.060][152][debug][rbac] external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:88] shadow denied
    [2018-07-26 20:39:18.060][152][debug][rbac] external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:98] enforced allowed
    ...
    

密钥和证书错误

如果您怀疑 Istio 使用的某些密钥或证书不正确,您可以检查任何 Pod 的内容信息。

$ istioctl proxy-config secret sleep-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 sleep-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 出现了问题,首先要确认 Citadel 健康,接下来要查看的是密钥和证书正确下发 Sidecar.

如果上述检查都正确无误,下一步就应该验证认证策略已经创建,并且对应的目标规则是否正确应用。

如果您怀疑客户端 sidecar 可能不正确地发送双向 TLS 或明文流量,请检查Grafana Workload dashboard。无论是否使用 mTLS,都对出站请求进行注释。检查后,如果你认为客户端 sidecar 是错误的,报一个 issue 在 GitHub。

这些信息有用吗?
Do you have any suggestions for improvement?

Thanks for your feedback!