通过使用SDS(Secret Discovery Service)为Istio网关提供https安全支持、证书动态加载,从而提升Istio网关安全性。在Istio1.6版本中,默认启用SDS。

前提条件

背景信息

在文档通过Istio网关启用https安全中介绍了如何通过secret卷挂载的方式,将证书以文件方式挂载到Sidecar容器中。这种情况下,当证书发生轮转时需要重启服务让Sidecar代理重新加载证书。SDS(Secrete Discovery Service)是Istio提供的另外一种动态加载证书的方式,TLS (Transport Layer Security)所需的私钥、服务器证书以及根证书都可以由SDS完成配置。
本文主要介绍通过SDS配置TLS入口网关,SDS从入口网关代理中获取Secret。入口网关代理与入口网关在同一Pod中运行,并监视与入口网关相同的命名空间中创建的Secret。在入口网关上启用SDS具有以下好处:
  • 入口网关可以在不需要重启的情况下,动态添加、删除或更新所需要的证书、私钥或者对应的根证书。
  • 不需要secret卷挂载。创建Kubernetes secret后,网关代理会捕获该secret,并将其包含的证书、私钥或根证书发送到入口网关。
  • 网关代理可以监视多个证书、私钥对,只需要为多个主机创建secret并更新网关定义。

步骤一:在入口网关中启用SDS

在Istio1.6版本中,默认启用SDS,无需执行以下步骤。
  1. 登录ASM控制台
  2. 数据平面区域,单击入口网关服务页签。
  3. 在对应的已部署的入口网关的操作列,单击YAML
  4. 在入口网关服务对应的YAML定义文件中,在spec下添加以下信息。
      sds:
        enabled: true
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 2000m
            memory: 1024Mi
    说明 其中enabled必须设置为true, resources部分可以根据实际情况设置不同于默认值的资源配置。
    添加之后的YAML文件内容类似如下。
    apiVersion: istio.alibabacloud.com/v1beta1
    kind: IstioGateway
    metadata:
      name: ingressgateway
      namespace: istio-system
    spec:
      clusterIds:
        - c58638b491a5248669b59609e0a17****
      cpu: {}
      externalTrafficPolicy: Local
      maxReplicas: 1
      minReplicas: 1
      ports:
        - name: status-port
          port: 15020
          targetPort: 15020
        - name: http2
          nodePort: 31380
          port: 80
          targetPort: 80
        - name: https
          nodePort: 31390
          port: 443
          targetPort: 443
        - name: tls
          port: 15443
          targetPort: 15443
      replicaCount: 1
      sds:
        enabled: true
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 2000m
            memory: 1024Mi
      serviceAnnotations:
        service.beta.kubernetes.io/alibaba-cloud-loadbalancer-spec: slb.s1.small
      serviceType: LoadBalancer

步骤二:为多个主机准备服务器证书和私钥

为aliyun.com生成证书和私钥,并保存为Secret。

如果您已经拥有针对aliyun.com可用的证书和私钥,需要将密钥命名为aliyun.com.key,证书命名为aliyun.com.crt。如果没有,可以使用openssl通过如下操作来生成证书和密钥,操作步骤如下。

  1. 执行以下命令,创建根证书和私钥。
    openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=myexample Inc./CN=aliyun.com' -keyout aliyun.root.key -out aliyun.root.crt 
  2. 执行以下命令,为aliyun.com服务器生成证书和私钥。
    openssl req -out aliyun.com.csr -newkey rsa:2048 -nodes -keyout aliyun.com.key -subj "/CN=aliyun.com/O=myexample organization"
    openssl x509 -req -days 365 -CA aliyun.root.crt -CAkey aliyun.root.key -set_serial 0 -in aliyun.com.csr -out aliyun.com.crt
  3. 通过以下命令(注意是在入口网关Pod所在的集群对应的kubeconfig环境下)将在istio-system命名空间中创建包含证书和私钥的Secret。
    kubectl create -n istio-system secret generic myexample-credential --from-file=key=aliyun.com.key --from-file=cert=aliyun.com.crt
    注意 secret名称不应以istio或prometheus开头,且不应包含token字段。

步骤三:为a.aliyun.com定义内部服务

示例中的内部服务是基于Nginx实现的,首先为Nginx服务器创建配置文件。以域名a.aliyun.com的内部服务为例,定义请求根路径直接返回字样Welcome to a.aliyun.com! 及状态码200。myexample-nginx.conf的具体内容如下。

events {
}
http {
  log_format main '$remote_addr - $remote_user [$time_local]  $status '
  '"$request" $body_bytes_sent "$http_referer" '
  '"$http_user_agent" "$http_x_forwarded_for"';
  access_log /var/log/nginx/access.log main;
  error_log  /var/log/nginx/error.log;
  server {
    listen 80;
    location /hello {
        return 200 'Welcome to a.aliyun.com!';
        add_header Content-Type text/plain;
    }
  }
}
  1. 执行以下命令(注意是在入口网关Pod所在的集群对应的kubeconfig环境下),创建Kubernetes ConfigMap存储Nginx服务器的配置。
    kubectl create configmap myexample-nginx-configmap --from-file=nginx.conf=./myexample-nginx.conf
  2. 执行以下命令(注意是在入口网关Pod所在的集群对应的kubeconfig环境下),设置命名空间default,启用sidecar自动注入。
    kubectl label namespace default istio-injection=enabled
  3. 创建并拷贝以下内容到myexampleapp.yaml文件中,并执行kubectl apply -f myexampleapp.yaml命令,创建域名a.aliyun.com的内部服务。
    apiVersion: v1
    kind: Service
    metadata:
      name: myexampleapp
      labels:
        app: myexampleapp
    spec:
      ports:
      - port: 80
        protocol: TCP
      selector:
        app: myexampleapp
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: myexampleapp
    spec:
      selector:
        matchLabels:
          app: myexampleapp
      replicas: 1
      template:
        metadata:
          labels:
            app: myexampleapp
        spec:
          containers:
          - name: nginx
            image: nginx
            ports:
            - containerPort: 80
            volumeMounts:
            - name: nginx-config
              mountPath: /etc/nginx
              readOnly: true
          volumes:
          - name: nginx-config
            configMap:
              name: myexample-nginx-configmap

步骤四:为b.aliyun.com定义内部服务

本示例中的内部服务是基于httpbin实现的,创建并拷贝如下内容到httpbin.example.yaml文件中,并执行kubectl apply -f httpbin.example.yaml命令(注意是在入口网关Pod所在的集群对应的kubeconfig环境下),创建域名b.aliyun.com的内部服务。

apiVersion: v1
kind: Service
metadata:
  name: httpbin
  labels:
    app: httpbin
spec:
  ports:
  - name: http
    port: 8000
  selector:
    app: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin
      version: v1
  template:
    metadata:
      labels:
        app: httpbin
        version: v1
    spec:
      containers:
      - image: docker.io/citizenstig/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        ports:
        - containerPort: 8000

步骤五:创建自定义网关配置对象

  1. 登录ASM控制台
  2. 控制平面区域,单击Istio网关页签,然后单击新建
  3. 新建页面,按以下步骤定义Istio网关,然后单击确定
    1. 选择相应的命名空间。本文以选择default命名空间为例。
    2. 在文本框中,定义Istio网关。可参考以下YAML定义:
      apiVersion: networking.istio.io/v1alpha3
      kind: Gateway
      metadata:
        name: mysdsgateway
      spec:
        selector:
          istio: ingressgateway # use istio default ingress gateway
        servers:
        - port:
            number: 443
            name: https
            protocol: HTTPS
          tls:
            mode: SIMPLE
            credentialName: myexample-credential # must be the same as secret
          hosts:
          - '*.aliyun.com'
      								
    Istio网关页签可以看到新建的虚拟服务。

步骤六:创建虚拟服务

  1. 登录ASM控制台
  2. 在左侧导航栏中,选择网格实例,在右侧打开的页面中,选择要配置的ASM实例。
  3. 控制平面区域,单击虚拟服务页签,然后单击新建
  4. 新建页面,按以下步骤定义虚拟服务,然后单击确定
    1. 选择相应的命名空间。本文以选择default命名空间为例。
    2. 在文本框中,定义Istio虚拟服务。可参考以下YAML定义。
      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: mysdsgateway-myexamplevs
      spec:
        hosts:
        - "a.aliyun.com"
        gateways:
        - mysdsgateway
        http:
        - match:
          - uri:
              exact: /hello
          route:
          - destination:
              host: myexampleapp.default.svc.cluster.local
              port:
                number: 80      

      同样地,为httpbin.example.com提供定义相应的虚拟服务。

      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: mysdsgateway-httpbinvs
      spec:
        hosts:
        - "b.aliyun.com"
        gateways:
        - mysdsgateway
        http:
        - match:
          - uri:
              prefix: /status
          - uri:
              prefix: /delay
          route:
          - destination:
              port:
                number: 8000
              host: httpbin.default.svc.cluster.local        
    虚拟服务页签可以看到新建的虚拟服务。

预期结果

可以通过以下任意一种方法获取入口网关服务的地址:

  • 通过控制台查看,请参见查看入口网关的服务信息
  • 执行以下命令(注意是在入口网关Pod所在的集群对应的kubeconfig环境下),获取入口网关服务的地址。
    kubectl get svc -n istio-system -l istio=ingressgateway
  • 执行以下命令,通过HTTPS协议访问aliyun.com服务。
    curl -k -H Host:a.aliyun.com --resolve a.aliyun.com:443:{替换成真实的入口网关IP地址}  https://a.aliyun.com/hello
    Welcome to aliyun.com!
  • 执行以下命令,通过HTTPS协议访问httpbin.example.com服务。
    curl -k -H Host:b.aliyun.com --resolve b.aliyun.com:443:{替换成真实的入口网关IP地址}  https://b.aliyun.com/status/418  
    
        -=[ teapot ]=-
    
           _...._
         .'  _ _ `.
        | ."` ^ `". _,
        \_;`"---"`|//
          |       ;/
          \_     _/
            `"""`