安全网关
“控制入口流量任务” 描述了如何配置入口网关以将 HTTP 服务公开给外部流量。此任务展示了如何使用简单 TLS 或双向 TLS 公开安全的 HTTPS 服务。
开始之前
按照 安装指南 中的说明设置 Istio。
启动 httpbin 示例。
$ kubectl apply -f @samples/httpbin/httpbin.yaml@
对于 macOS 用户,请验证您是否使用的是使用 LibreSSL 库编译的
curl
。$ curl --version | grep LibreSSL curl 7.54.0 (x86_64-apple-darwin17.0) libcurl/7.54.0 LibreSSL/2.0.20 zlib/1.2.11 nghttp2/1.24.0
如果前面的命令输出 LibreSSL 版本(如所示),则您的
curl
命令应与本任务中的说明一起正常工作。否则,请尝试使用curl
的其他实现,例如在 Linux 机器上。
生成客户端和服务器证书和密钥
此任务需要多组证书和密钥,这些证书和密钥在以下示例中使用。您可以使用您喜欢的工具创建它们,也可以使用以下命令使用 openssl 生成它们。
创建根证书和私钥以签署服务的证书。
$ mkdir example_certs1 $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example_certs1/example.com.key -out example_certs1/example.com.crt
为
httpbin.example.com
生成证书和私钥。$ openssl req -out example_certs1/httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs1/httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization" $ openssl x509 -req -sha256 -days 365 -CA example_certs1/example.com.crt -CAkey example_certs1/example.com.key -set_serial 0 -in example_certs1/httpbin.example.com.csr -out example_certs1/httpbin.example.com.crt
创建第二组相同类型的证书和密钥。
$ mkdir example_certs2 $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example_certs2/example.com.key -out example_certs2/example.com.crt $ openssl req -out example_certs2/httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs2/httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization" $ openssl x509 -req -sha256 -days 365 -CA example_certs2/example.com.crt -CAkey example_certs2/example.com.key -set_serial 0 -in example_certs2/httpbin.example.com.csr -out example_certs2/httpbin.example.com.crt
为
helloworld.example.com
生成证书和私钥。$ openssl req -out example_certs1/helloworld.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs1/helloworld.example.com.key -subj "/CN=helloworld.example.com/O=helloworld organization" $ openssl x509 -req -sha256 -days 365 -CA example_certs1/example.com.crt -CAkey example_certs1/example.com.key -set_serial 1 -in example_certs1/helloworld.example.com.csr -out example_certs1/helloworld.example.com.crt
生成客户端证书和私钥。
$ openssl req -out example_certs1/client.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs1/client.example.com.key -subj "/CN=client.example.com/O=client organization" $ openssl x509 -req -sha256 -days 365 -CA example_certs1/example.com.crt -CAkey example_certs1/example.com.key -set_serial 1 -in example_certs1/client.example.com.csr -out example_certs1/client.example.com.crt
为单个主机配置 TLS 入口网关
为入口网关创建密钥。
$ kubectl create -n istio-system secret tls httpbin-credential \ --key=example_certs1/httpbin.example.com.key \ --cert=example_certs1/httpbin.example.com.crt
配置入口网关。
首先,定义一个网关,其中包含用于端口 443 的 servers:
部分,并为 credentialName
指定值为 httpbin-credential
。这些值与密钥的名称相同。TLS 模式应具有 SIMPLE
值。
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: mygateway
spec:
selector:
istio: ingressgateway # use istio default ingress gateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: httpbin-credential # must be the same as secret
hosts:
- httpbin.example.com
EOF
接下来,通过定义相应的虚拟服务来配置网关的入口流量路由。
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: httpbin
spec:
hosts:
- "httpbin.example.com"
gateways:
- mygateway
http:
- match:
- uri:
prefix: /status
- uri:
prefix: /delay
route:
- destination:
port:
number: 8000
host: httpbin
EOF
最后,按照 这些说明 设置 INGRESS_HOST
和 SECURE_INGRESS_PORT
变量以访问网关。
首先,创建一个 Kubernetes 网关
$ cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: mygateway
namespace: istio-system
spec:
gatewayClassName: istio
listeners:
- name: https
hostname: "httpbin.example.com"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: httpbin-credential
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: default
EOF
接下来,通过定义相应的 HTTPRoute
配置网关的入口流量路由。
$ cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: httpbin
spec:
parentRefs:
- name: mygateway
namespace: istio-system
hostnames: ["httpbin.example.com"]
rules:
- matches:
- path:
type: PathPrefix
value: /status
- path:
type: PathPrefix
value: /delay
backendRefs:
- name: httpbin
port: 8000
EOF
最后,从 Gateway
资源获取网关地址和端口。
$ kubectl wait --for=condition=programmed gtw mygateway -n istio-system
$ export INGRESS_HOST=$(kubectl get gtw mygateway -n istio-system -o jsonpath='{.status.addresses[0].value}')
$ export SECURE_INGRESS_PORT=$(kubectl get gtw mygateway -n istio-system -o jsonpath='{.spec.listeners[?(@.name=="https")].port}')
发送 HTTPS 请求以通过 HTTPS 访问
httpbin
服务。$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" ... HTTP/2 418 ... I'm a teapot! ...
httpbin
服务将返回 418 我是一个茶壶 代码。通过删除网关的密钥,然后使用不同的证书和密钥重新创建它来更改网关的凭据。
$ kubectl -n istio-system delete secret httpbin-credential $ kubectl create -n istio-system secret tls httpbin-credential \ --key=example_certs2/httpbin.example.com.key \ --cert=example_certs2/httpbin.example.com.crt
使用新的证书链使用
curl
访问httpbin
服务。$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs2/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" ... HTTP/2 418 ... I'm a teapot! ...
如果您尝试使用以前的证书链访问
httpbin
,则尝试现在将失败。$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" ... * TLSv1.2 (OUT), TLS handshake, Client hello (1): * TLSv1.2 (IN), TLS handshake, Server hello (2): * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (OUT), TLS alert, Server hello (2): * curl: (35) error:04FFF06A:rsa routines:CRYPTO_internal:block type is not 01
为多个主机配置 TLS 入口网关
您可以为多个主机(例如,httpbin.example.com
和 helloworld.example.com
)配置入口网关。入口网关配置有对应于每个主机的唯一凭据。
通过删除并使用原始证书和密钥重新创建密钥来恢复先前示例中的
httpbin
凭据。$ kubectl -n istio-system delete secret httpbin-credential $ kubectl create -n istio-system secret tls httpbin-credential \ --key=example_certs1/httpbin.example.com.key \ --cert=example_certs1/httpbin.example.com.crt
启动
helloworld-v1
示例。$ kubectl apply -f @samples/helloworld/helloworld.yaml@ -l service=helloworld $ kubectl apply -f @samples/helloworld/helloworld.yaml@ -l version=v1
创建一个
helloworld-credential
密钥。$ kubectl create -n istio-system secret tls helloworld-credential \ --key=example_certs1/helloworld.example.com.key \ --cert=example_certs1/helloworld.example.com.crt
配置具有主机
httpbin.example.com
和helloworld.example.com
的入口网关。
定义一个网关,其中包含两个用于端口 443 的服务器部分。分别将每个端口上的 credentialName
值设置为 httpbin-credential
和 helloworld-credential
。将 TLS 模式设置为 SIMPLE
。
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: mygateway
spec:
selector:
istio: ingressgateway # use istio default ingress gateway
servers:
- port:
number: 443
name: https-httpbin
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: httpbin-credential
hosts:
- httpbin.example.com
- port:
number: 443
name: https-helloworld
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: helloworld-credential
hosts:
- helloworld.example.com
EOF
通过定义相应的虚拟服务来配置网关的流量路由。
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: helloworld
spec:
hosts:
- helloworld.example.com
gateways:
- mygateway
http:
- match:
- uri:
exact: /hello
route:
- destination:
host: helloworld
port:
number: 5000
EOF
配置一个 Gateway
,其中包含两个用于端口 443 的侦听器。分别将每个侦听器上的 certificateRefs
值设置为 httpbin-credential
和 helloworld-credential
。
$ cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: mygateway
namespace: istio-system
spec:
gatewayClassName: istio
listeners:
- name: https-httpbin
hostname: "httpbin.example.com"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: httpbin-credential
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: default
- name: https-helloworld
hostname: "helloworld.example.com"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: helloworld-credential
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: default
EOF
为 helloworld
服务配置网关的流量路由。
$ cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: helloworld
spec:
parentRefs:
- name: mygateway
namespace: istio-system
hostnames: ["helloworld.example.com"]
rules:
- matches:
- path:
type: Exact
value: /hello
backendRefs:
- name: helloworld
port: 5000
EOF
发送 HTTPS 请求到
helloworld.example.com
。$ curl -v -HHost:helloworld.example.com --resolve "helloworld.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt "https://helloworld.example.com:$SECURE_INGRESS_PORT/hello" ... HTTP/2 200 ...
发送 HTTPS 请求到
httpbin.example.com
并仍然获得 HTTP 418 作为返回。$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" ... HTTP/2 418 ... server: istio-envoy ...
配置双向 TLS 入口网关
您可以扩展网关的定义以支持 双向 TLS。
通过删除其密钥并创建一个新的密钥来更改入口网关的凭据。服务器使用 CA 证书验证其客户端,并且我们必须使用密钥
ca.crt
来保存 CA 证书。$ kubectl -n istio-system delete secret httpbin-credential $ kubectl create -n istio-system secret generic httpbin-credential \ --from-file=tls.key=example_certs1/httpbin.example.com.key \ --from-file=tls.crt=example_certs1/httpbin.example.com.crt \ --from-file=ca.crt=example_certs1/example.com.crt
配置入口网关。
更改网关的定义以将 TLS 模式设置为 MUTUAL
。
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: mygateway
spec:
selector:
istio: ingressgateway # use istio default ingress gateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: MUTUAL
credentialName: httpbin-credential # must be the same as secret
hosts:
- httpbin.example.com
EOF
由于 Kubernetes 网关 API 目前不支持在 网关 中终止双向 TLS,因此我们使用 Istio 特定的选项 gateway.istio.io/tls-terminate-mode: MUTUAL
来配置它。
$ cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: mygateway
namespace: istio-system
spec:
gatewayClassName: istio
listeners:
- name: https
hostname: "httpbin.example.com"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: httpbin-credential
options:
gateway.istio.io/tls-terminate-mode: MUTUAL
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: default
EOF
尝试使用之前的方法发送 HTTPS 请求,并查看它如何失败。
$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" * TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): * TLSv1.3 (IN), TLS handshake, Request CERT (13): * TLSv1.3 (IN), TLS handshake, Certificate (11): * TLSv1.3 (IN), TLS handshake, CERT verify (15): * TLSv1.3 (IN), TLS handshake, Finished (20): * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.3 (OUT), TLS handshake, Certificate (11): * TLSv1.3 (OUT), TLS handshake, Finished (20): * TLSv1.3 (IN), TLS alert, unknown (628): * OpenSSL SSL_read: error:1409445C:SSL routines:ssl3_read_bytes:tlsv13 alert certificate required, errno 0
将客户端证书和私钥传递给
curl
并重新发送请求。使用--cert
标志将客户端的证书传递给curl
,并使用--key
标志传递私钥。$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt --cert example_certs1/client.example.com.crt --key example_certs1/client.example.com.key \ "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" ... HTTP/2 418 ... server: istio-envoy ... I'm a teapot! ...
更多信息
密钥格式
Istio 支持读取几种不同的密钥格式,以支持与各种工具(例如 cert-manager)的集成。
- 具有密钥
tls.key
和tls.crt
的 TLS 密钥,如上所述。对于双向 TLS,可以使用ca.crt
密钥。 - 具有密钥
key
和cert
的通用密钥。对于双向 TLS,可以使用cacert
密钥。 - 具有密钥
key
和cert
的通用密钥。对于双向 TLS,一个单独的通用密钥名为<secret>-cacert
,其中包含cacert
密钥。例如,httpbin-credential
包含key
和cert
,而httpbin-credential-cacert
包含cacert
。 cacert
密钥值可以是包含连接的各个 CA 证书的 CA 捆绑包。
SNI 路由
HTTPS Gateway
将在转发请求之前对其配置的主机执行 SNI 匹配,这可能会导致某些请求失败。有关详细信息,请参阅 配置 SNI 路由。
故障排除
检查
INGRESS_HOST
和SECURE_INGRESS_PORT
环境变量的值。确保它们具有有效值,根据以下命令的输出。$ kubectl get svc -n istio-system $ echo "INGRESS_HOST=$INGRESS_HOST, SECURE_INGRESS_PORT=$SECURE_INGRESS_PORT"
确保
INGRESS_HOST
的值是 IP 地址。在某些云平台(例如 AWS)中,您可能会获得域名而不是 IP 地址。此任务需要 IP 地址,因此您需要使用类似以下的命令将其转换。$ nslookup ab52747ba608744d8afd530ffd975cbf-330887905.us-east-1.elb.amazonaws.com $ export INGRESS_HOST=3.225.207.109
检查网关控制器的日志以获取错误消息。
$ kubectl logs -n istio-system <gateway-service-pod>
验证密钥是否已成功在
istio-system
命名空间中创建。$ kubectl -n istio-system get secrets
httpbin-credential
和helloworld-credential
应显示在密钥列表中。检查日志以验证入口网关代理是否已将密钥/证书对推送到入口网关。
$ kubectl logs -n istio-system <gateway-service-pod>
日志应显示已添加
httpbin-credential
密钥。如果使用双向 TLS,则httpbin-credential-cacert
密钥也应出现。验证日志是否显示网关代理从入口网关接收 SDS 请求,资源名称为httpbin-credential
,并且入口网关已获取密钥/证书对。如果使用双向 TLS,则日志应显示密钥/证书已发送到入口网关,网关代理已接收具有httpbin-credential-cacert
资源名称的 SDS 请求,并且入口网关已获取根证书。
清理
- 删除网关配置和路由。
$ kubectl delete gateway mygateway
$ kubectl delete virtualservice httpbin helloworld
$ kubectl delete -n istio-system gtw mygateway
$ kubectl delete httproute httpbin helloworld
删除密钥、证书和密钥。
$ kubectl delete -n istio-system secret httpbin-credential helloworld-credential $ rm -rf ./example_certs1 ./example_certs2
关闭
httpbin
和helloworld
服务。$ kubectl delete -f samples/httpbin/httpbin.yaml $ kubectl delete deployment helloworld-v1 $ kubectl delete service helloworld