基础认证策略
通过这项任务,你将学习:
使用认证策略设置双向 TLS。
使用认证策略进行终端用户认证。
开始之前
拥有一个安装好 Istio 的 Kubernetes 集群,并且全局双向 TLS 处于禁用状态(可使用安装步骤中提供的示例配置
install/kubernetes/istio.yaml
,或者使用 Helm 设置global.mtls.enabled
为 false)。为了演示,需要创建两个命名空间
foo
和bar
,并且在两个空间中都部署带有 sidecar 的 httpbin 应用和带 sidecar 的 sleep 应用。同时,运行另外一份不带有 sidecar 的 httpbin 和 sleep 应用(为了保证独立性,在legacy
命名空间中运行它们)。在一个常规系统中,一个服务可以是其它服务的 服务端 (接收流量),同时也可以是另外一些服务的 客户端 。为了简单起见,在这个演示中,我们只使用sleep
作为客户端,使用httpbin
作为服务端。$ 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
如果你在 istio-proxy 容器中安装了
curl
,你也可以验证从 proxy 到httpbin
服务的可达性:$ kubectl exec $(kubectl get pod -l app=sleep -n bar -o jsonpath={.items..metadata.name}) -c istio-proxy -n bar -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}\n" 200
最后且重要的是,验证系统目前不存在认证策略:
$ kubectl get meshpolicies.authentication.istio.io No resources found.
$ kubectl get policies.authentication.istio.io --all-namespaces No resources found.
$ kubectl get destinationrules.networking.istio.io --all-namespaces
你可能看到一些策略和/或由 Istio 安装时自动添加的目的地规则,具体取决于所选的安装模式。但是在
foo
、bar
和legacy
命名空间中不应该有任何的策略或规则。
为网格中的所有服务启用双向 TLS 认证
你可以提交如下 网格认证策略 和目的地规则为网格中所有服务启用双向 TLS 认证:
cat <<EOF | istioctl create -f -
apiVersion: "authentication.istio.io/v1alpha1"
kind: "MeshPolicy"
metadata:
name: "default"
spec:
peers:
- mtls: {}
EOF
cat <<EOF | istioctl create -f -
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "default"
spec:
host: "*.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
EOF
- 网格范围内的认证策略名称必须是
default
;所有其它名字的策略都会被拒绝和忽视。另外注意 CRD 类型是MeshPolicy
,它不同于命名空间范围内或服务范围内的策略类型 (Policy
)。 - 另一方面,目的地规则可以是任意名字,也可以存在于任意命名空间。为了保持一致性,我们在本示例中也将其命名为
default
并且使其仅存在于default
命名空间中。 - 目的地规则中形如
*.local
的宿主名称只匹配网格中以local
结尾的服务。 - 当处于
ISTIO_MUTUAL
TLS 模式, Istio 会依据内部实现机制设置密钥和证书的路径(例如clientCertificate
、privateKey
和caCertificates
)。 - 如果你想要为某一特定服务定义目的地规则,那么 TLS 相关的设置也必须被复制到新规则中。
这些认证策略和目的地规则有效地配置了所有服务的 sidecars,使服务在双向 TLS 模式下分别进行接收和发送请求。但是这对于没有 sidecar 的服务并不适用,例如上文中创建的 httpbin.legacy
和 sleep.legacy
服务。如果你运行上文中提供的测试命令,你会发现从 sleep.legacy
到 httpbin.foo
和 httpbin.bar
的请求开始出现失败现象,这是由于虽然在服务端启用了双向 TLS 认证,但 sleep.legacy
并没有 sidecar 来支持认证。类似的,从 sleep.foo
(或 sleep.bar
) 到 httpbin.legacy
的请求也会失败。
$ 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: 503
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 503
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
返回的 HTTP 错误码还没有统一。如果 HTTP 请求是在双向 TLS 模式下发送并且服务端只接受 HTTP 请求,则返回的错误码为 503。相反,如果请求是以纯文本的格式发送到使用双向 TLS 的服务端,则返回的错误码是 000 (同时
curl
退出码为 56,错误信息是 “failure with receiving network data”)。
为了修复从带有 sidecar 的客户端到不带 sidecar 的服务端的连接,你可以专门为这些服务端添加目的地规则来覆盖 TLS 设置。
cat <<EOF | istioctl create -f -
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "httpbin"
namespace: "legacy"
spec:
host: "httpbin.legacy.svc.cluster.local"
trafficPolicy:
tls:
mode: DISABLE
EOF
重新尝试发送请求到 httpbin.legacy
,一切都应该正常工作了。
$ for from in "foo" "bar" "legacy"; 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: 200
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.legacy: 200
当启用全局双向 TLS 认证时,这种方法也可以用来配置 Kubernetes 的 API 服务器。如下是一个示例配置:
cat <<EOF | istioctl create -f -
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "api-server"
namespace: "default"
spec:
host: "kubernetes.default.svc.cluster.local"
trafficPolicy:
tls:
mode: DISABLE
EOF
对于第二个问题,从不带 sidecar 的客户端到带有 sidecar 的服务端(工作在双向 TLS 模式)的连接,唯一的选择是从双向 TLS 模式切换到 PERMISSIVE
模式,该模式允许服务端接收 HTTP 或(双向) TLS 流量。显然,这种模式会降低安全等级,推荐只在迁移过程中使用。为了这样做,你可以更改 网格策略 (在 mtls
域下增加 mode: PERMISSIVE
)。推荐一种更保守的方法:仅在必要的情况下才为个别服务创建专属策略。下面的例子演示了这种保守的方法:
cat <<EOF | istioctl create -f -
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
name: "httpbin"
namespace: "foo"
spec:
targets:
- name: "httpbin"
peers:
- mtls:
mode: PERMISSIVE
EOF
从 sleep.legacy
到 httpbin.foo
的请求应当是成功的,但是到 httpbin.bar
的请求依然会失败。
$ 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
$ 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"
000
在进入下一小节之前,我们需要将在本小节创建的认证策略和目的地规则移除掉。
$ kubectl delete meshpolicy.authentication.istio.io default
$ kubectl delete policy.authentication.istio.io -n foo --all
$ kubectl delete destinationrules.networking.istio.io default
$ kubectl delete destinationrules.networking.istio.io -n legacy --all
为一个命名空间中的所有服务启用双向 TLS
你可以为每一个命名空间单独启用双向 TLS 而不必启用全局双向 TLS。启用的步骤是类似的,只是相关的策略只在命名空间范围内有效(类型为 Policy
)。
cat <<EOF | istioctl create -f -
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
name: "default"
namespace: "foo"
spec:
peers:
- mtls: {}
EOF
类似于 网格范围内的策略 ,命名空间范围内的策略必须命名为
default
,并且不限定任何特定的服务(没有targets
设置域)
添加相应的目的地规则:
cat <<EOF | istioctl create -f -
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
启用双向 TLS
你也可以为某个特定的服务设置认证策略和目的地规则。执行以下命令只为 httpbin.bar
服务新增一项策略。
cat <<EOF | istioctl create -n bar -f -
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
name: "httpbin"
spec:
targets:
- name: httpbin
peers:
- mtls: {}
EOF
同时增加目的地规则:
cat <<EOF | istioctl create -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 | istioctl replace -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 | istioctl replace -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
同时使用命名空间层级和服务层级的策略
假设我们已经为命名空间 foo
中所有的服务添加了启用双向 TLS 的命名空间层级的策略并且观察到从 sleep.legacy
到 httpbin.foo
的请求都失败了(见上文)。现在专门为 httpbin
服务添加额外的策略来禁用双向 TLS (peers 域留空):
cat <<EOF | istioctl create -n foo -f -
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
name: "example-3"
spec:
targets:
- name: httpbin
EOF
另外添加对应的目的地规则:
cat <<EOF | istioctl create -n foo -f -
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "example-3"
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
设置终端用户认证
你需要一个有效的 JWT (与在本例中你想使用的 JWKS endpoint 相一致)。请按照这里的说明进行操作来创建一个 JWT 。你也可以在示例中使用自己的 JWT/JWKS endpoint。创建之后,在环境变量中设置相关信息。
$ export SVC_ACCOUNT="example@my-project.iam.gserviceaccount.com"
$ export JWKS=https://www.googleapis.com/service_accounts/v1/jwk/${SVC_ACCOUNT}
$ export TOKEN=<YOUR-TOKEN>
另外,为了方便,通过入口暴露 httpbin.foo
服务(详细信息参考入口任务)。
cat <<EOF | kubectl apply -f -
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: httpbin-ingress
namespace: foo
annotations:
kubernetes.io/ingress.class: istio
spec:
rules:
- http:
paths:
- path: /headers
backend:
serviceName: httpbin
servicePort: 8000
EOF
获取入口 IP:
$ export INGRESS_HOST=$(kubectl get ing -n foo -o=jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}')
并且运行查询测试:
$ curl $INGRESS_HOST/headers -s -o /dev/null -w "%{http_code}\n"
200
现在,让我们为 httpbin.foo
添加一项策略使其必须通过用户 JWT 访问。下一步命令假设名称为 “httpbin” 的策略已经存在(如果你是按照前面小节的说明进行的操作)。你可以运行 kubectl get policies.authentication.istio.io -n foo
进行确认。如果相应资源不存在,使用 istio create
(替换 istio replace
)创建资源。注意在以下策略中,对端的认证方式(双向 TLS )也会被设置,尽管可以移除该设置同时不影响初始的认证设置。
cat <<EOF | istioctl replace -n foo -f -
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
name: "example-3"
spec:
targets:
- name: httpbin
peers:
- mtls:
origins:
- jwt:
issuer: $SVC_ACCOUNT
jwksUri: $JWKS
principalBinding: USE_ORIGIN
EOF
使用上面小节中同样的 curl 命令进行测试时会返回 401 错误状态码,这是因为服务端需要 JWT 进行认证但请求端并没有提供:
$ curl $INGRESS_HOST/headers -s -o /dev/null -w "%{http_code}\n"
401
在请求中附加上面操作生成的 token ,然后执行请求就会返回成功信息:
$ curl --header "Authorization: Bearer $TOKEN" $INGRESS_HOST/headers -s -o /dev/null -w "%{http_code}\n"
200
你也可以尝试修改 token 或 policy (例如改变 issuer、audiences、expiry date 等信息)来观察 JWT 验证的其它方面信息。
清除
清除所有资源。
$ kubectl delete ns foo bar legacy
See also
如何渐进式的为现有 Istio 服务添加双向 TLS 支持。
描述 Istio 的授权与鉴权功能。
如何在 Kubernetes 中启用 Citadel 的健康检查。
展示如何对 Istio service 进行健康检查。
展示如何在服务网格中进行基于角色的访问控制。
运维人员如何使用现有根证书配置 Citadel 进行证书以及密钥的签发。