基础认证策略
此任务涵盖启用、配置和使用 Istio 身份验证策略时可能需要执行的主要活动。认证概述中可以了解更多相关的基本概念。
开始之前
拥有一个安装好 Istio 的 Kubernetes 集群,并且禁用全局双向 TLS(可使用安装步骤中提供的示例配置
install/kubernetes/istio.yaml
,或者使用 Helm设置global.mtls.enabled
为 false)。
安装
为了演示,需要创建两个命名空间 foo
和 bar
,并且在两个空间中都部署带有 sidecar 的httpbin 应用和 sleep 应用。同时运行另外一份不带有 Sidecar 的 httpbin 和 sleep 应用(为了保证独立性,在 legacy
命名空间中运行它们)。如果您在尝试任务时想要使用相同的示例,运行以下内容:
$ kubectl create ns foo
$ kubectl apply -f <(istioctl kube-inject -f @samples/httpbin/httpbin.yaml@) -n foo
$ kubectl apply -f <(istioctl kube-inject -f @samples/sleep/sleep.yaml@) -n foo
$ kubectl create ns bar
$ kubectl apply -f <(istioctl kube-inject -f @samples/httpbin/httpbin.yaml@) -n bar
$ kubectl apply -f <(istioctl kube-inject -f @samples/sleep/sleep.yaml@) -n bar
$ kubectl create ns legacy
$ kubectl apply -f @samples/httpbin/httpbin.yaml@ -n legacy
$ kubectl apply -f @samples/sleep/sleep.yaml@ -n legacy
通过从任意客户端(例如 sleep.foo
、sleep.bar
和 sleep.legacy
)向任意服务端(httpbin.foo
、httpbin.bar
或 httpbin.legacy
)发送 HTTP 请求(可以使用 curl 命令)来验证以上设置。所有请求都应该成功进行并且返回的 HTTP 状态码为 200。
以下是一个从 sleep.bar
到 httpbin.foo
可达性的检查命令示例:
$ 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"
200
以下单行命令可以方便对所有客户端和服务端的组合进行检查:
$ 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
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 200
sleep.legacy to httpbin.bar: 200
sleep.legacy to httpbin.legacy: 200
重要的是,验证系统目前没有认证策略:
$ kubectl get policies.authentication.istio.io --all-namespaces
No resources found.
$ kubectl get meshpolicies.authentication.istio.io
No resources found.
最后要进行的验证是,确保没有为示例服务定义匹配的目标规则。一个验证方法是:获取现存目标规则,查看 host:
字段值是否匹配示例服务的 FQDN。例如:
$ kubectl get destinationrules.networking.istio.io --all-namespaces -o yaml | grep "host:"
host: istio-policy.istio-system.svc.cluster.local
host: istio-telemetry.istio-system.svc.cluster.local
你可能看到一些策略和/或由 Istio 安装时自动添加的目的地规则,具体取决于所选的安装模式。但是在 foo
、bar
和 legacy
命名空间中没有任何的策略或规则。
为网格中的所有服务启用双向 TLS 认证
你可以提交如下网格范围的认证策略(MeshPolicy
)和目的地规则为网格中所有服务启用双向 TLS 认证:
$ kubectl apply -f - <<EOF
apiVersion: "authentication.istio.io/v1alpha1"
kind: "MeshPolicy"
metadata:
name: "default"
spec:
peers:
- mtls: {}
EOF
此策略指定网格中的所有工作负荷仅接受使用 TLS 的加密请求。如您所见,此身份验证策略的类型是MeshPolicy
,这种策略的生效范围是整个网格内的所有服务,因此名称必须是 default
,另外也不包含 targets
字段。
此时,只有接收方被配置为使用双向 TLS。如果在 Istio 服务(即那些带有 Sidecar 的服务)之间运行 curl
命令,所有请求都将失败并显示 503 错误代码,原因是客户端仍在使用明文请求。
$ 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
sleep.foo to httpbin.foo: 503
sleep.foo to httpbin.bar: 503
sleep.bar to httpbin.foo: 503
sleep.bar to httpbin.bar: 503
要配置客户端也使用双向 TLS,就需要设置目标规则。可以使用多个目标规则,一个一个的为每个适用的服务(或命名空间)进行配置;然而在规则中使用 *
符号来匹配所有服务会更方便,这样也就跟网格范围的认证策略一致了。
$ kubectl apply -f - <<EOF
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "default"
namespace: "default"
spec:
host: "*.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
EOF
- 主机值
*.local
仅限于与集群中的服务匹配,而不是外部服务。另请注意,目标规则中没有针对名称或命名空间的限制。 - 在
ISTIO_MUTUAL
TLS 模式中,Istio 将根据其内部实现设置密钥和证书(例如客户端证书、私钥和 CA 证书)的路径。
除了认证场合之外,目标规则还有其它方面的应用,例如金丝雀部署。但是所有的目标规则都适用相同的优先顺序。因此,如果一个服务需要配置其它目标规则(例如配置负载均衡),那么新规则定义中必须包含类似的 TLS 块来定义 ISTIO_MUTUAL
模式,否则它将覆盖网格或命名空间范围的 TLS 设置并禁用 TLS。
如上所述重新运行测试命令,您将看到 Istio 服务之间的所有请求现已成功完成:
$ 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
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
从非 Istio 服务请求到 Istio 服务
非 Istio 服务,例如 sleep.legacy
没有 Sidecar,因此它无法启动与 Istio 服务通信所需的 TLS 连接。因此从 sleep.legacy
到 httpbin.foo
或 httpbin.bar
的请求将失败:
$ 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
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 000
command terminated with exit code 56
sleep.legacy to httpbin.legacy: 200
在这种情况下,由于 Envoy 拒绝了明文请求,因此 curl
会以错误代码 56(接收网络数据失败)退出。
这一结果是符合预期的,但遗憾的是,如果不降低这些服务的身份验证要求,就无法完成这种访问。
从 Istio 服务请求非 Istio 服务
如果从 sleep.foo
(或 sleep.bar
)向 httpbin.legacy
发送请求,也会看到请求失败,因为 Istio 按照我们的指示配置客户端目标规则使用双向 TLS,但 httpbin.legacy
没有 Sidecar,所以它无法处理它。
$ 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
sleep.foo to httpbin.legacy: 503
sleep.bar to httpbin.legacy: 503
要解决此问题,我们可以添加目标规则来覆盖 httpbin.legacy
的 TLS 设置。例如:
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: "httpbin-legacy"
spec:
host: "httpbin.legacy.svc.cluster.local"
trafficPolicy:
tls:
mode: DISABLE
EOF
从 Istio 服务请求到 Kubernetes API Server
Kubernetes API Server 没有 Sidecar,因此来自 sleep.foo
等 Istio 服务的请求会失败,出现像请求非 Istio 服务时同样的问题而失败。
$ TOKEN=$(kubectl describe secret $(kubectl get secrets | grep default | cut -f1 -d ' ') | grep -E '^token' | cut -f2 -d':' | tr -d '\t')
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"
000
command terminated with exit code 35
同样,我们可以通过覆盖 API 服务器的目标规则来更正此问题( kubernetes.default
)
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: "api-server"
spec:
host: "kubernetes.default.svc.cluster.local"
trafficPolicy:
tls:
mode: DISABLE
EOF
如果使用默认双向 TLS 选项安装 Istio,此规则与上述全局身份验证策略和目标规则一起将在安装过程中注入系统。
重新运行上面的测试命令,确认在添加规则后能够成功返回 200:
$ TOKEN=$(kubectl describe secret $(kubectl get secrets | grep default | cut -f1 -d ' ') | grep -E '^token' | cut -f2 -d':' | tr -d '\t')
$ 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"
200
清理第 1 部分
删除在上述步骤中创建的策略和目标规则:
$ kubectl delete meshpolicy default
$ kubectl delete destinationrules default httpbin-legacy api-server
为每个命名空间或服务启用双向 TLS
除了为整个网格指定身份验证策略之外,Istio 还允许您为特定命名空间或服务指定策略。一个命名空间范围的策略优先于网格范围的策略,而特定于服务的策略仍具有更高的优先级。
命名空间范围的策略
下面的示例显示了为命名空间 foo
中的所有服务启用双向 TLS 的策略。正如你所看到的,它使用的类别是 Policy
而不是 MeshPolicy
,并指定了一个命名空间,在本例中为 foo
。如果未指定命名空间值,则策略将应用于默认命名空间。
$ kubectl apply -f - <<EOF
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
name: "default"
namespace: "foo"
spec:
peers:
- mtls: {}
EOF
类似于网格范围的策略,命名空间范围的策略必须命名为 default
,并且不限定任何特定的服务(没有 targets
字段)。
添加相应的目的地规则:
$ kubectl apply -f - <<EOF
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "default"
namespace: "foo"
spec:
host: "*.foo.svc.cluster.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
EOF
主机名称 *.foo.svc.cluster.local
限制了只能匹配命名空间 foo
中的服务。
由于这些策略和目的地规则只对命名空间 foo
中的服务有效,你应该看到只有从不带 Sidecar 的客户端(sleep.legacy
)到 httpbin.foo
的请求会出现失败。
$ 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
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 200
sleep.legacy to httpbin.legacy: 200
特定于服务的策略
也可以为某个特定的服务设置认证策略和目的地规则。执行以下命令,只为 httpbin.bar
服务新增一项策略。
$ cat <<EOF | kubectl apply -n bar -f -
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
name: "httpbin"
spec:
targets:
- name: httpbin
peers:
- mtls: {}
EOF
同时增加目的地规则:
$ cat <<EOF | kubectl apply -n bar -f -
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "httpbin"
spec:
host: "httpbin.bar.svc.cluster.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
EOF
- 在这个例子中,我们不在 metadata 中指定命名空间而是放在命令行(
-n bar
)中。它们的效果是一样的。 - 对于认证策略和目的地规则的名称并没有任何限定。在本例中为了简单,使用服务本身的名称为策略和规则命名。
同样地,运行上文中提供的测试命令。和预期一致,从 sleep.legacy
到 httpbin.bar
的请求因为同样的原因开始出现失败。
...
sleep.legacy to httpbin.bar: 000
command terminated with exit code 56
如果在命名空间 bar
中还存在其他服务,我们会发现目标为这些服务的流量不会受到影响。验证这一行为有两种方法:一种是加入更多服务;另一种是把这一策略限制到某个端口。这里我们展示第二种方法:
$ cat <<EOF | kubectl apply -n bar -f -
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
name: "httpbin"
spec:
targets:
- name: httpbin
ports:
- number: 1234
peers:
- mtls: {}
EOF
同时对目的地规则做出相应的改变:
$ cat <<EOF | kubectl apply -n bar -f -
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "httpbin"
spec:
host: httpbin.bar.svc.cluster.local
trafficPolicy:
tls:
mode: DISABLE
portLevelSettings:
- port:
number: 1234
tls:
mode: ISTIO_MUTUAL
EOF
新的策略只作用于 httpbin
服务的 1234
端口上。结果是,双向 TLS 在端口 8000
上(又)被禁用并且从 sleep.legacy
发出的请求会恢复工作。
$ 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"
200
策略优先级
服务级策略是优先于命名空间级策略的,为了证实这一行为,可以新建一条策略,禁止 httpbin.foo
的双向 TLS 设置;而此时网格中已经创建了一个命名空间级的策略,该策略为 foo
命名空间中的所有服务启用了双向 TLS,下面就来观察一下从 sleep.legacy
到 httpbin.foo
的请求是如何失败的:
$ cat <<EOF | kubectl apply -n foo -f -
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
name: "overwrite-example"
spec:
targets:
- name: httpbin
EOF
另外添加对应的目的地规则:
$ cat <<EOF | kubectl apply -n foo -f -
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "overwrite-example"
spec:
host: httpbin.foo.svc.cluster.local
trafficPolicy:
tls:
mode: DISABLE
EOF
重新从 sleep.legacy
发送请求,我们应当看到请求成功返回的状态码(200
),表明服务级的策略覆盖了命名空间层级的策略。
$ 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"
200
清理第 2 部分
删除在上述步骤中创建的策略和目标规则:
$ kubectl delete policy default overwrite-example -n foo
$ kubectl delete policy httpbin -n bar
$ kubectl delete destinationrules default overwrite-example -n foo
$ kubectl delete destinationrules httpbin -n bar
设置终端用户认证
要验证这一功能,首先要有一个有效的 JWT。JWT 必须与本例中的 JWKS 端点相匹配。本例中我们使用 Istio 代码库中的 JWT test 以及JWKS endpoint 进行演示。
另外为了方便起见,我们使用 ingressgateway
开放了 httpbin.foo
服务(这部分内容可以参看控制 Ingress 流量任务中的介绍)。
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: httpbin-gateway
namespace: foo
spec:
selector:
istio: ingressgateway # use Istio default gateway implementation
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
EOF
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin
namespace: foo
spec:
hosts:
- "*"
gateways:
- httpbin-gateway
http:
- route:
- destination:
port:
number: 8000
host: httpbin.foo.svc.cluster.local
EOF
获取入口 IP:
$ export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
并且运行查询测试:
$ curl $INGRESS_HOST/headers -s -o /dev/null -w "%{http_code}\n"
200
现在添加一条策略,强制 httpbin.org
使用终端用户 JWT。下一个命令假设没有为 httpbin.org
指定服务级策略(也就是说成功运行了上面清理第 2 部分的内容)。可以用命令 kubectl get policies.authentication.istio.io -n foo
来验证这一假设:
$ cat <<EOF | kubectl apply -n foo -f -
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
name: "jwt-example"
spec:
targets:
- name: httpbin
origins:
- jwt:
issuer: "testing@secure.istio.io"
jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.2/security/tools/jwt/samples/jwks.json"
principalBinding: USE_ORIGIN
EOF
使用上面小节中同样的 curl 命令进行测试时会返回 401 错误状态码,这是因为服务端需要 JWT 进行认证但请求端并没有提供:
$ curl $INGRESS_HOST/headers -s -o /dev/null -w "%{http_code}\n"
401
用下面的 curl
命令生成 Token 并附加在请求中,然后执行请求就会返回成功信息:
$ TOKEN=$(curl https://raw.githubusercontent.com/istio/istio/release-1.2/security/tools/jwt/samples/demo.jwt -s)
$ curl --header "Authorization: Bearer $TOKEN" $INGRESS_HOST/headers -s -o /dev/null -w "%{http_code}\n"
200
要观察 JWT 验证的其他方面,请使用脚本 gen-jwt.py
生成新的令牌以测试不同的发行者、受众和到期日期等。例如下面的命令创建一个 5 秒后到期的 Token。Istio 首先成功通过该令牌的验证请求,但在 5 秒后就会拒绝这一 Token 了:
$ TOKEN=$(@security/tools/jwt/samples/gen-jwt.py@ @security/tools/jwt/samples/key.pem@ --expire 5)
$ 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
200
200
200
200
200
401
401
401
401
401
使用双向 TLS 进行最终用户身份验证
最终用户身份验证和双向 TLS 可以一起使用。修改上面的策略以定义双向 TLS 和最终用户 JWT 身份验证:
$ cat <<EOF | kubectl apply -n foo -f -
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
name: "jwt-example"
spec:
targets:
- name: httpbin
peers:
- mtls: {}
origins:
- jwt:
issuer: "testing@secure.istio.io"
jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.2/security/tools/jwt/samples/jwks.json"
principalBinding: USE_ORIGIN
EOF
如果尚未提交 jwt-example
策略,请使用 istio create
。
并添加目标规则:
$ kubectl apply -f - <<EOF
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "httpbin"
namespace: "foo"
spec:
host: "httpbin.foo.svc.cluster.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
EOF
如果已经在网格级或者命名空间级的策略中启用了双向 TLS,则主机 httpbin.foo
已被其它目标规则覆盖。因此就不需要添加此目标规则了。另一方面,仍然需要将 mtls
节添加到身份验证策略,因为服务级的策略将完全覆盖网格级(或命名空间级)的策略。
在这些更改之后,来自 Istio 服务(包括 ingress gateway)到 httpbin.foo
的流量将使用双向 TLS。上面的测试命令仍然有效。在使用正确的令牌的情况下,Istio 服务直接向 httpbin.foo
发出的请求也可以正常工作:
$ 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"
200
但是,来自非 Istio 服务的明文请求将失败:
$ 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"
401
清理第 3 部分
- 删除身份验证策略:
$ kubectl delete policy jwt-example
- 删除目标规则:
$ kubectl delete policy httpbin
- 如果您不打算探索任何后续任务,则只需删除测试命名空间即可删除所有资源:
$ kubectl delete ns foo bar legacy
相关内容
如何渐进式的为现有 Istio 服务添加双向 TLS 支持。
描述 Istio 的授权与鉴权功能。
基于 Istio 的 Micro-Segmentation 授权
描述 Istio 的授权功能以及如何在各种用例中使用它。
如何在 Kubernetes 中启用 Citadel 的健康检查。
展示为 HTTP 服务设置基于角色的访问控制方法。
展示如何对 Istio service 进行健康检查。