istio网格安全

网格安全

依赖envoy的安全机制

istio网格的安全体系涉及到多个组件

  • ca机构
  • api-server分发各envoy代理配置、策略、标识信息
  • 各sidecar envoy和边缘代理(ingress/egress-gw)作为PEP(Policy Enforcement Points)保护客户端于服务端之间安全通信
  • envoy程序上用于遥测和审计的扩展

istiod做CA,envoy做安全配置,默认CA有效期1年,证书1天

认证

istio认证

身份标识是通信安全领域的基础概念,通信过程中双方出于相互认证的目的交换身份凭据

  • 客户端:根据安全命名信息核验服务器的标识,检测是否允许运行目标服务的授权
  • 服务端:根据授权策略决定客户端可以访问哪些信息,并审核谁在什么时间访问了哪些信息

istio身份标识模型使用一等服务标识,用来标识请求者身份,支持标识人类用户、单个/一组工作负载

在不同平台中使用不同的身份标识服务:

  • k8s:k8s sa
  • gce:gcp sa
  • non-k8s:ua、csa(custom service account)、service name、istio sa、gcp sa

认证方式:

互联网中常见认证是,用户发起https请求后到达服务端,服务端对用户做权限校验认证等

tls:

在入口网关ingress-gw处,配置tls,网格内部通信可使用mtls、jwt等

mtls:

双向tls认证、服务间认证。服务与服务之间,使用数字证书相互认证(https)。常用于零信任网络

方式:

服务A、服务B。当A向B发起请求调用时,A发起tls,验证B证书是否合法,同时B会验证A发起请求时使用的证书是否合法,两个证书必须同时合法才有效

  • 服务端配置tls:使用pa资源,作出对客户端要求证书
  • 客户端配置tls:使用dr资源,在客户端发起请求时,选择性提供证书,需要服务端支持协商才行(还是以服务端为主)
jwt(Json Web Token):

最终用户认证,对用户的请求做权限认证等
使用jwt时必要有一个jwt认证服务器,也就是基于此认证服务器可以实现单点登录SSO

使用请求标头:Authorization: Bearer token值

token获取

从双方公信的认证服务端获取

  1. 客户端向目标服务发起请求时,目标服务会判断用户是否登录过,没有登录则返回登录界面或者信任的三方认证服务端
  2. 客户端再向认证服务端发请求,认证服务端返回token(包含认证服务端自身签名信息与有效期,避免被伪装)
  3. 客户端再携带token向目标服务发请求,目标服务会获取认证服务端的公钥,检查:token所处位置、issuer或请求、公共jwks

spiffe

使用spiffe框架完成向各服务授权身份,具体部分参考envoy网格安全部分

istio兼容spiffe,但citedal提供的功能比spire更强大。istio中spiffe id格式:

spiffe://可信域名/ns/命名空间/sa/sa账户

ns为default,sa为default:spiffe://cluster.local/ns/default/sa/default

两步认证:
  • node attention:控制平面如何认证所有节点,需要信任所有工作负载所处的节点
  • workload attention:控制平面如何认证所有工作负载,信任节点后再信任对应的工作负载
实现框架
SPIRE
Citedal

由于istio已经运行在k8s中,所以:

  • spiffe的第一步node认证中,api-server信任的k8s节点就已经完成了这部分,也就是kubelet
  • 由kubelet运行的pod,也是已经完成了信任,这就是第二步工作负载信任
  • 再给工作负载签发一个id,也就是SVID

证书生成

citedal自身就是一个ca,可以签发证书,而证书分发则使用SDS XDS API完成

身份标识和证书管理

istio使用x.509证书为每个工作负载提供身份标识

istio-proxy容器中与envoy相伴运行的pilot-agent负责与istiod协同完成私钥和证书的轮换

注:当属于同一个可信域,同一个命名空间中的pod,可能获取到的SVID是相同的,但证书不同

获取私钥和证书流程:
  1. istiod提供grpc服务来接收证书签名请求
  2. 启动时,istio proxy容器中的pilot-agent会创建私钥和csr,并将csr与凭据发给istiod完成签名
  3. istiod上的ca负责验证csr中携带的凭据,并在成功验证后签署csr生成证书
  4. 工作负载启动时,envoy通过sds api从同一容器中的pilot-agent请求证书和私钥
  5. pilot-agent通过envoy sds api将私钥以及从istiod上收到的证书发给本地envoy
  6. pilot-agnet周期性监视工作负载证书有效期,过期则更新

认证机制:

将身份认证策略通过k8s api存在istio配置存储中

沿用envoy的机制,提供2种身份验证机制

  • Peer authentication:服务认证,验证发起请求的客户端,也就是mtls双向认证
    • 为每个服务提供1个可表示其角色的身份标识,实现跨集群和跨云的互操作
    • 提供秘钥管理系统完成自动秘钥和证书生成、分发、轮替
  • request authentication:最终用户认证,发出请求的原始客户端认证
    • 基于jwt验证机制,验证用户权限
    • 支持自定义身份认证服务,任何OIDC认证系统,如keyclock、auth0、firebase auth等

认证架构

principal:

认证成功后,istio会将相关的身份、凭据转到后续的授权步骤,作为principal使用

  • mtls通信时:isito将pa的身份(spiffe id)提取到source.principal
  • jwt认证时:istio将jwt中的身份提取的request.auth.principal

身份认证crd资源

  • peerauthentications资源:定义服务认证策略
  • requestauthentications资源:定义最终用于认证策略

配置存储

认证策略保存在istio配置存储中,由istio controller监视,任何变动,istiod都自动转换配置,异步下发

  • jwt认证时,控制平面要将公钥信息附加到相应配置上
  • mtls时,配置证书和私钥给pod

客户端遵循的验证机制

  • 对于requestauthentications,应用程序获取jwt并将其附加到请求中

  • 对于peerauthentications,istio会自动将2个pep间所有流量升级为双向tls(mtls)

  • 若身份验证策略禁用mtls,istio继续在pep间使用纯文本;也可手动在dr资源中禁用mtls

配置

peerauthentications资源

当实现mtls双向认证时,需要pa定义服务端tls(做tls终止),在dr定义客户端tls(做tls发起,如何遵循服务端tls策略)。

port级别的mtls只能在定义标签选择器时才生效

定义tls策略注意事项:

  • pa可以定义在ns,也可以用标签选择器,但要能生效到目标svc上
  • dr的tls定义要匹配目标svc上
认证策略生效范围与机制
生效范围:
  • 由命名空间和选择器决定
  • 根命名空间,且未选择器为空,则全局生效
  • 指定命名空间,且选择器为空,则指定ns中所有pod生效
  • 指定命名空间,选择器非空,则选择中的pod生效
生效机制

pa

  • 网格中策略只能生效1个,ns、pod也只能有1个pa生效
  • 若创建了多个,则最早创建的策略生效
  • 生效顺序:标签选择器(pod)->ns->根ns

ra

  • 允许多个策略组合生效,但建议每个命名空间最多定义1个策略
语法

kubectl explain pa

image-20230919112337432

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
spec:
  mtls:
    mode: str
      #UNSET,不显式定义mtls,代表继承上级
      #DISABLE,禁用mtls。dr中客户端tls也要禁用tls
      #PERMISSIVE,许可模式,要求使用mtls,没有则使用明文。默认全局使用此。许可模式时,dr中客户端tls可用任意模式
      #STRICT,严格模式,可为服务启用mtls。dr中客户端tls只能是:MUTUAL、ISTIO_MUTUAL
  portLevelMtls:
    端口:
      mode: str         #用于与上面一样
  selector:
    matchLabels: {}

requestauthentications资源

配置服务端最终用户认证(jwt),一般需要结合AuthorizationPolicy资源使用
ra验证jwt的关键值:请求中token所处位置、issuer或请求、公共jwks

检查token方法:

  • 请求报文针对ra策略中的rules提供了token,istio检查他们并拒绝无效的token
  • isito默认接收未提供token的请求;若拒绝该类请求,则通过响应的授权规则完成,由这类规则负责完成针对特定操作的机制

生效机制

  • 每个jwt均使用唯一的location时,ra策略上甚至可以指定多个jwt
  • 多个策略匹配到同一个workload时,istio会将多个策略上的规则合并
  • 请求报文上不允许携带1个以上的JWT

实现方式

jwt需要结合认证服务器,可以使用开源产品:keycloak、maxkey

keycloak

开源身份和访问管理解决方案IAM,支持基于OAuth2.0标准的openid connect协议对用户进行身份验证
应用程序可通过OAuth2.0将身份验证委托给外部系统,实现SSO:

  • 原先用户访问程序做认证时,需在代码中实现认证模块,现在可用keycloak系统,所有应用的请求,只要第一次请求时通过keycloak认证,就全部信任,实现SSO(单点信任)

支持集成其他的认证服务,如github、google、Facebook等
支持用户联邦功能,可以通过ldap、kerberos导入用户

认证方式:

客户端访问服务的请求由envoy拦截后,交给keycloak进行认证
客户端在访问目标服务时,通过OAuth2.0协议于keycloak做交互,在请求到JWT之后,携带jwt向服务端发起请求

envoy在1.19版本开始可通过: http.auth2过滤器支持IdPs(identify providers)交互,因此应用程序可以省去在内部代码实现这部分功能(目标istio还未提供这部分CRD功能,要手动配置)

语法

kubectl explain ra

apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
spec:
  jwtRules:         #允许访问的token
  - audiences: [str]        #token生效范围是哪些
    forwardOriginalToken: boolean       #token透传,允许转发token给后端
    fromHeaders:            #匹配标头
    - name: str
      prefix: str
    fromParams: [str]       #匹配请求参数
    issuer: str             #token签发者是谁
    jwks: str
    jwksUri: str            #token验证秘钥
    jwks_uri: str           #指定验证秘钥
    outputClaimToHeaders:
    - claim: str
      header: str
    outputPayloadToHeader: str      #将jwt验证结果保存到标头中
  selector:
    matchLabels: {}

鉴权

参考官方文档:https://istio.io/latest/zh/docs/concepts/security/

授权机制的特性:

istio为网格中workload提供网格级别、命名空间级别、工作负载级别的访问控制

  • workload-to-workload、end-user-to-workload授权
  • 使用AuthorizationPolicy CRD配置,语法与配置灵活
  • 鉴权仅在envoy本地执行
  • 原生支持http、https、http2,以及更底层的协议

可实现功能:类似NetworkPolicy的部分功能

  • 服务A只能通过80/tcp访问服务B的/api
  • 服务B只能接受来自命名空间N的客户端请求
  • 访问服务B的请求必须附带合规的jwt

授权测量强制生效在服务端的envoy的入站流量上,每个envoy都运行一个授权引擎,当请求到达envoy时,授权引擎根据当前AuthorizationPolicy 评估请求上下文,并返回授权结果:ALLOW、DENY

鉴权顺序:

  • 先给服务端配置ra资源 和、或关系 配置pa资源完成认证策略
  • 认证完成后,服务端获取到的用户信息有:RA的用户名、PA的Subject CN,再结合用户身份完成鉴权

授权启用:

istio的授权功能默认开启,且默认允许
如果有1个授权策略应用到workload,默认的策略就自动变为拒绝
当多个策略关联到同一个工作负载时,istio会将相关的策略进行组合

生效机制

当3个策略allow、deny、custom同时生效在工作负载时,评估顺序为: custom --> deny --> allow

  • 工作负载不存在任何策略时,默认允许请求,一旦配置策略,则默认拒绝所有请求
  • 若存在与请求相匹配的任何custom策略,授权结果为deny时,则拒绝请求
  • 若存在与请求相匹配的任何deny策略,则拒绝请求
  • 存在与请求相匹配的任意allow策略时,允许请求

image-20231010164132367

配置方式

  • rbac:envoy内置支持
  • abac(extauthz):配置使用外部的鉴权服务,将鉴权机制委托给外部第三方。此时鉴权策略配置在外部服务上
外部鉴权服务

当AuthorizationPolicy资源的action: CUSTOM时,工作流程如图
目前常用的外部授权服务是OPA,如keycloak、maxkey等

image-20231224190106416

配置

AuthorizationPolicy资源

rbac实现,对pa和ra资源的allow、deny

可用授权策略参考官方文档:https://istio.io/latest/zh/docs/reference/config/security/conditions/

语法

kubectl explain authorizationpolicies

image-20231224175119565

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
spec:
  action: 策略        #如果只定义为ALLOW,且rules不定义,则表示拒绝所有,一般在安全性高的场景中,这么定义一个全局的,然后再一个个allow单独放行
    #ALLOW
    #DENY
    #AUDIT,审计。仅记录,不做额外操作
    #CUSTOM,自定义鉴权引擎,istio v1.9加入,允许交给第三方授权服务处理,如keycloak
  provider:       #外部提供的名称,action策略配置为CUSTOM时,在此处定义第三方
    name: str
  rules:          #授权策略定义。from是请求发起者,to是被请求者与请求方法,when是条件满足则继续。为空时,表示允许对所有目标workload请求
  - from:         #允许谁。多个from时,或关系
    - source:     #白名单和黑名单,多个source列表时,或关系。未定义source字段时,无需验证客户端身份都可以进行访问,若只允许验证的客户端,在source中应用principals: ['*']
        ipBlocks: [str]         #指定网段
        notIpBlocks: [str]      #除了指定的网段
        namespaces: [str]         #来自此命名空间
        notNamespaces: [str]      #不来自此命名空间
        remoteIpBlocks: [str]     #远程网段
        notRemoteIpBlocks: [str]  #除了指定的远程网段
        requestPrincipals: [str]    #jwt认证通过
        notRequestPrincipals: [str]   #jwt认证未通过
        principals: [str]       #认证通过
        notPrincipals: [str]    #认证未通过
    to:         #被操作内容,不指定则表示所有资源
    - operation:
        hosts: [str]
        methods: [str]      #此处定义执行操作的方法,如GET
        notHosts: [str]
        notMethods: [str]
        notPaths: [str]
        notPorts: [str]
        paths: [str]		#请求路径
        ports: [str]
    when:         #额外限定,请求中必须匹配的。参考文档:https://istio.io/latest/zh/docs/reference/config/security/conditions
    - key: str	#如果是jwt,则这是字典类型,获取的key,必须是jwt的token中有的key
      values: [str]
      notValues: [str]
  selector:		#生效是,配合metadata.namespace一起生效,ns是istio-system或空时,全网格生效(默认配置)
    matchLabels: {}
配置示例
例1:允许default命名空间中所有pod访问
apiVersion: v1
kind: AuthorizationPolicy
metadata:
  name: allow-all
  namespace: default
spec:
  rules:	#存在策略,空规则表示匹配并允许任意请求,默认DENY动作不生效
  - {}
例2:拒绝default命名空间中所有pod访问
apiVersion: v1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: default
spec:	#存在策略,无规则表示不能匹配任何请求,默认DENY动作生效
  {}
例3:允许请求并不认证客户端
apiVersion: v1
kind: AuthorizationPolicy
metadata:
  name: default
  namespace: default
spec:
  selector:
    matchLabels:
      app: demoapp
      version: v1.0
  rules:	#未定义source字段,表示经过验证、未经验证的客户端都可以访问
  - to:
    - operation:
        methods:
        - 'GET'
        - 'POST'
例4:允许请求并验证客户端
apiVersion: v1
kind: AuthorizationPolicy
metadata:
  name: default
  namespace: default
spec:
  selector:
    matchLabels:
      app: demoapp
      version: v1.0
  rules:	
  - from:
    - source:
        principals:
        - '*'
    to:
    - operation:
        methods:
        - 'GET'
        - 'POST'
例5:推荐配置

安全性较高的场景中,可以在命名空间中定义一个默认的拒绝所有,在逐步开放

apiVersion: v1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: default
spec:
  action: ALLOW   #无规则表示不能匹配任何工作负载的请求,ALLOW动作无法发挥作用,导致生效的是默认的DENY动作
---
apiVersion: v1
kind: AuthorizationPolicy
metadata:
  name: allow-demoapp
  namespace: default
spec:
  selector:
    matchLabels:
      app: demoapp
      version: v1.0
  rules:	
  - from:
    - source:
        principals:
        - '*'
    to:
    - operation:
        methods:
        - 'GET'
        - 'POST'

SSO单点登录

单点登录需要在ingress-gw上实现

方法1:ingress tls透传,sidecar做jwt验证

  1. 用户自行向SSO进行身份验证并获取JWT
  2. ingress-gw将请求和JWT转到到目标服务相关的工作负载的istio-proxy容器
  3. istio-proxy根据ra、AuthorizationPolicy验证jwt,有效则开发业务容器黑用户,无效则返回错误RBAC: access denied
优点:
  • 便捷,只需要配置2个CRD
  • 基于jwt,实现细粒度授权
缺点:
  • 无OIDC工作流,用户需要自己获取JWT,并自行携带在http请求中
  • 需要为每个应用自定义ra、AuthorizationPolicy

方法2:ingress-gw完成OIDC工作流

常用方式:
  • 为ingress-gw添加oauth2-proxy sidecar,或者将oauth2-proxy独立部署
  • 使用新版本envoy自带的oauth2 filter进行
流程:
  1. 用户发起http请求,由oauth2-proxy启动OIDC工作流程,用户完成身份验证
  2. 用户执行经过身份验证的http请求,oauth2-proxy基于cookie验证用户身份
  3. oauth2-proxy将请求回转给ingress-gw,再由gw转给工作负载pod上的istio-proxy容器,最后到业务容器
优点:
  • 在ingress-gw强制完成身份认证
  • 自动化OIDC工作流
缺点:
  • 粗粒度授权(已认证==已授权),且配置较复杂

方法3:组合JWT和oauth2-proxy自动化OIDC

身份认证由oauth2-proxy自动完成。oauth2-proxy从cookie中自动提取jwt,并将其通过http请求上特定标头(X-Forwarded-Access-Token)转到istio-proxy

image-20231010180802487

流程:
  1. 用户发起http请求,由oauth2-proxy启动OIDC工作流程,由用户参与完成身份验证
  2. 用户执行经过身份验证的http请求,而oauth2-proxy基于cookie验证用户身份
  3. oauth-proxy从请求的cookie中提取jwt,并将特定标头转发给ingess-gw,ingress-gw将请求和jwt标头转发到目标工作负载的istio-proxy容器
  4. 工作负载上的istio-proxy,根据ra和AuthorizationPolicy确认jwt的有效性
  5. 若JWT有效,则开发目标服务给用户,否则,将返回错误消息(RBAC denied)

案例

例1:命名空间启用mtls

1)将default命名空间设为PERMISSIVE

istio默认全局为PERMISSIVE,也就是默认对支持mtls的启用,否则明文

kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: default
spec:
  mtls:
    mode: PERMISSIVE
EOF
2)测试
加密验证:
#查看客户端pod ip与所在节点ip
kubectl get po -o wide |awk '/admin/{print $6,$7}'

#在admin所在节点(2.2.2.35),抓包
inc=`route -n |awk '/10.20.135.19/{print $NF}'`
tcpdump -i $inc -nn tcp port 80 -X

#回到控制节点,进入admin po,发起请求
kubectl exec -it admin -- curl demoapp

image-20231223210722052

抓包显示的数据已是加密内容image-20231223210340837

明文验证:
#查看demoapp pod ip与所在节点ip
kubectl get po -o wide |awk '/demoapp-10/{print $6,$7}'

#在demoapp所在节点(2.2.2.35),抓包
inc=`route -n |awk '/10.20.135.17/{print $NF}'`
tcpdump -i $inc -nn tcp port 80 -X

#回到控制节点,进入admin po,发起请求
curl 10.20.135.17

此时,由于是网格外发起的请求,实现不了mtls,所以数据是明文通信。前3包是tcp三次握手,后面可以看到UA等请求头
image-20231223211114502

例2:demoapp强制mtls

注:虽然全局、例1的命名空间级别都配置使用许可模式,但例2使用标签选择器,固定了pod,所以按生效顺序来,此处定义的优先

1)标签选择器选中工作负载
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: demoapp
  namespace: default
spec:
  selector:
    matchLabels:
      app: demoapp
  mtls:
    mode: STRICT
2)测试
kubectl get po -o wide |awk '/demoapp-10/{print $6,$7}'

#网格外直接访问pod地址,此时因为不是mtls通信,会拒绝
curl 10.20.135.17

#但网格内,访问是正常
kubectl exec -it admin -- curl demoapp

image-20231223212354599

image-20231223212420186

例3:8080端口禁用mtls

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: proxy-pa
  namespace: default
spec:
  selector:
    matchLabels:
      app: proxy
  mtls:
    mode: UNSET
  portLevelMtls:
    8080:
      mode: DISABLE

例4:为kiali与proxy配置tls

1)生成证书
#获取博主的证书自签脚本下载完成后修改脚本内容如图所示即可
wget 'https://files-cdn.cnblogs.com/files/blogs/731344/cert.sh?t=1703330209&download=true' -O cert.sh

#配置自定义名称后,执行脚本
vim cert.sh
sh cert.sh

#把证书配置为secret,必须创建在根命名空间(istio-system),否则会找不到证书
kubectl create secret tls hj.com.crt --key=hj.com.key --cert=hj.com.crt -n istio-system

image-20231223193724000

2)配置kiali-gw
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: kiali-gw
  namespace: istio-system
spec:
  selector:
    app: istio-ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "kiali.hj.com"
    tls:
      httpsRedirect: true
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: hj.com.crt
    hosts:
    - "kiali.hj.com"
EOF
3)配置proxy-gw
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: proxy-gw
  namespace: istio-system
spec:
  selector:
    app: istio-ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "ft.hj.com"
    tls:
      httpsRedirect: true
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: hj.com.crt
    hosts:
    - "ft.hj.com"
EOF
4)测试
curl -Lv -k -H 'host: ft.hj.com' 2.2.2.17

echo 2.2.2.17 ft.hj.com >> /etc/hosts
openssl s_client -connect ft.hj.com:443

浏览器访问kiali

image-20231223194632138

image-20231223194643597

命令行访问proxy的域名

image-20231223194446424

image-20231223194922969

例5:实现jwt认证

1)部署keycloak

keycloak从17版本后做了兼容性变更

kubectl apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
  name: keycloak
  labels:
  
---
apiVersion: v1
kind: Service
metadata:
  name: keycloak
  namespace: keycloak
  labels:
    app: keycloak
spec:
  ports:
  - name: http
    port: 8080
    targetPort: 8080
  selector:
    app: keycloak
  type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
  namespace: keycloak
  labels:
    app: keycloak
spec:
  replicas: 1
  selector:
    matchLabels:
      app: keycloak
  template:
    metadata:
      labels:
        app: keycloak
    spec:
      containers:
      - name: keycloak
        image: quay.io/keycloak/keycloak:16.1.1
        env:
        - name: KEYCLOAK_USER
          value: "admin"
        - name: KEYCLOAK_PASSWORD
          value: "admin"
        - name: PROXY_ADDRESS_FORWARDING
          value: "true"
        ports:
        - name: http
          containerPort: 8080
        - name: https
          containerPort: 8443
        readinessProbe:
          httpGet:
            path: /auth/realms/master
            port: 8080
---
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: keycloak-gw
  namespace: istio-system
spec:
  selector:
    app: istio-ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "keycloak.hj.com"
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: keycloak-vs
  namespace: keycloak
spec:
  hosts:
  - "keycloak.hj.com"
  gateways:
  - "istio-system/keycloak-gw"
  http:
  - route:
    - destination:
        host: keycloak
        port:
          number: 8080
EOF

image-20231224153259977

添加新可信域,默认的Master是keycloak创建的管理域,管理所有配置,需要创建其他域做自定义配置
image-20231224153917774

创建客户端,客户端表示允许向keycloak发起身份验证的实体,一般指那些希望使用keycloak完成sso的程序或服务。在网格中,发起认证请求的客户端就是sidecar envoy
image-20231224154544588

添加用于jwt认证的用户,并配置用户的认证方式是密码
image-20231224154855455

image-20231224155400319

2)访问keycloak,测试token获取

需要先在界面中,获取访问路径,keycloak内置了相关url信息,点开即可查看

image-20231224160705707
image-20231224160835101

yum install -y jq

#集群外访问
echo 2.2.2.17 keycloak.hj.com >> /etc/hosts
curl -sS -d 'username=test&password=123456&grant_type=password&client_id=istio-client' http://keycloak.hj.com/auth/realms/istio/protocol/openid-connect/token | jq

#集群内访问
kubectl exec -it admin -- sh
curl -sS -d 'username=test&password=123456&grant_type=password&client_id=istio-client' http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/istio/protocol/openid-connect/token | jq

请求参数说明:

  • username为keycloak中配置的用户
  • password为keycloak中配置的用户密码
  • grant_type为密码类型,这个根据第一步中配置用户凭据有关
  • client_id为keycloak中配置的客户端名

返回信息说明:

  • access_token为有效token,主要关注这个,可以在:https://jwt.io 上解析
  • expires_in为有效期,300s为5分钟
  • refresh_expires_in为刷新时间
  • refresh_token刷新token时,使用的token
  • token_type为承载令牌

image-20231224162042403
access_token解析后内容
image-20231224162607645

3)配置jwt认证
#正常访问demoapp
kubectl exec -it admin -- sh
curl demoapp

未配置ra资源时,可以正常访问image-20231224163108932

kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: demoapp
  namespace: default
spec:
  selector:
    matchLabels:
      app: demoapp
  jwtRules:		#将jwt认证功能委托给keycloak,客户端请求时需要先从keycloak获取token才能访问,否则会拒绝
  - issuer: "http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/istio"
    jwksUri: "http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/istio/protocol/openid-connect/certs"
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: demoapp
  namespace: default
spec:
  selector:
    matchLabels:
      app: demoapp
  rules:
  - from:
    - source:
        requestPrincipals: ["*"]
    to:
    - operation:
        methods: ["GET"]
        paths: ["/*"]
EOF
4)测试demoapp访问
#访问拒绝
curl demoapp ;echo

#携带token访问
token=`curl -sS -d 'username=test&password=123456&grant_type=password&client_id=istio-client' http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/istio/protocol/openid-connect/token | jq .access_token`
curl -H "Authorization: Bearer $token" demoapp

#测试post方法会被拒绝,因为只授权了GET方法
curl -H "Authorization: Bearer $token" -X POST -d 'livez=err' demoapp

配置ra资源后,由于没有携带token,会被拒绝
image-20231224163425223

携带token后,2个demoapp服务均可以响应请求
image-20231224165339854

post请求被拒绝
image-20231224165513532

例6:实现jwt认证并做访问限制

在例5基础上继续配置

1)配置AuthorizationPolicy
kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: demoapp
  namespace: default
spec:
  selector:
    matchLabels:
      app: demoapp
  rules:    
  - from:
    - source:
        principals: ["cluster.local/ns/default/sa/default"]   #必须通过pa认证,且可信域是cluster.local,ns是default,sa账号必须是default的pod(pod运行默认使用default)
    - source:
        namespaces: ["dev", "istio-system"]    #并且只允许ns是default、dev、istio-system的pod发起请求
    to:
    - operation:    #允许对demoapp发起GET方法请求所有资源
        methods: ["GET"]
        paths: ["/*"]
  - from:
    - source:
        requestPrincipals: ["*"]    #通过jwt认证
        principals: ["cluster.local/ns/default/sa/default"]   #通过pa认证
    to:
    - operation:    #允许对/livez、/readyz发起POST请求
        methods: ["POST"]
        paths: ["/livez", "/readyz"]
    when:   #当jwt的issue签发者,与ra资源定义的issuer匹配时,做鉴权
    - key: request.auth.claims[iss]
      values: ["http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/istio"]
EOF
2)测试
#查看admin的sa是default
kubectl get po admin -o template={{.spec.serviceAccount}}

#发起测试请求
kubectl exec -it admin -- sh
token=`curl -sS -d 'username=test&password=123456&grant_type=password&client_id=istio-client' http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/istio/protocol/openid-connect/token | jq .access_token`
curl -H "Authorization: Bearer $token" demoapp/livez
curl -H "Authorization: Bearer $token" -XPOST -d 'livez=err' demoapp/livez
curl -H "Authorization: Bearer $token" demoapp/livez

image-20231224172902452

例7:SSO单点登录

image-20231010180802487

keycloak的访问类型说明:

  • confidential:适用于需要执行浏览器登录的应用,客户端会通过client secret来获取access_token,多用于服务端渲染的web场景(kiali、grafana、普罗米修斯等属于服务端渲染web系统)
  • public:适用于需要执行浏览器登录的应用,多用于使用vue和react实现的前端项目
  • bearer-only:适用于不需要执行浏览器登录的应用,只允许携带bearer token访问,多用于Restful API的场景
1)keycloak配置

添加新的客户端ingress-gwimage-20231224225446627

将客户端ingress-gw的访问类型修改为confidential,并将登录重定向url配置为:*,然后保存
image-20231224230914115
image-20231224231059056

再获取客户端ingress-gw的秘钥(客户端认证器为client id and secret,表示通过该client访问keycloak的客户端程序需要提供client id和secret,来认证到keycloak上),秘钥需要保存一下
image-20231224230843785

添加映射器(可选步骤),可以在access_token信息中将想要的信息显示成键值对(需解码),如果不需要查看信息可以不做image-20231224231555279

2)部署oauth2-proxy

注意事项:

  • 本次采用独立部署oauth2-proxy,但使用envoy代理
  • 配置项中的oidc-issuer-url指向的是keycloak访问地址,由于没有域名系统,只能配置svc地址,但集群外访问不了svc,所以需要确保外部能访问
  • oauth2-proxy没有配置tls时,需要配置--cookie-secure=false
  • 配置项可以使用容器args参数传递,也可以使用cm挂载
  • 使用secret保存从keycloak配置的client id、client secret、cookie secret
kubectl apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
  name: oauth2-proxy
  labels:
    istio-injection: enabled
---
apiVersion: v1
kind: Secret
metadata:
  name: oauth2-proxy
  namespace: oauth2-proxy
stringData:
  #keycloak中建的客户端id
  OAUTH2_PROXY_CLIENT_ID: ingress-gw
  #前面保存的ingress-gw客户端的secret
  OAUTH2_PROXY_CLIENT_SECRET: sSLQESOaQJDZ6CQ6t2KQ0TdZFpjymetp
  #自定义cookie secret,生成命令: openssl rand -base64 32 | tr -- '+/' '-_'
  OAUTH2_PROXY_COOKIE_SECRET: vEBMxbw7NXfaUIJR4klhdvB678GUPxWTd7tR9hq2m8w=
---
apiVersion: v1
kind: Service
metadata:
  name: oauth2-proxy
  namespace: oauth2-proxy
spec:
  selector:
    app: oauth2-proxy
  ports:
  - name: http
    port: 4180
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: oauth2-proxy
  namespace: oauth2-proxy
spec:
  selector:
    matchLabels:
      app: oauth2-proxy
  template:
    metadata:
      labels:
        app: oauth2-proxy
    spec:
      containers:
      - name: oauth2-proxy
        image: quay.io/oauth2-proxy/oauth2-proxy:v7.2.1
        args:
        - --provider=oidc
        - --oidc-issuer-url=http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/istio
        - --profile-url=http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/istio/protocol/openid-connect/userinfo
        - --validate-url=http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/istio/protocol/openid-connect/userinfo
        - --set-authorization-header=true
        - --http-address=0.0.0.0:4180
        - --pass-host-header=true
        - --reverse-proxy=true
        - --auth-logging=true
        - --cookie-httponly=true
        - --cookie-refresh=4m
        - --cookie-secure=false
        - --email-domain="*"
        - --pass-access-token=true
        - --pass-authorization-header=true
        - --request-logging=true
        - --set-xauthrequest=true
        - --silence-ping-logging=true
        - --skip-provider-button=true
        - --skip-auth-strip-headers=false
        - --ssl-insecure-skip-verify=true
        - --standard-logging=true
        - --upstream="static://200"
        - --whitelist-domain=".hj.com,.cluster.local"
        env:
        - name: OAUTH2_PROXY_CLIENT_ID
          valueFrom:
            secretKeyRef:
              name: oauth2-proxy
              key: OAUTH2_PROXY_CLIENT_ID
        - name: OAUTH2_PROXY_CLIENT_SECRET
          valueFrom:
            secretKeyRef:
              name: oauth2-proxy
              key: OAUTH2_PROXY_CLIENT_SECRET
        - name: OAUTH2_PROXY_COOKIE_SECRET
          valueFrom:
            secretKeyRef:
              name: oauth2-proxy
              key: OAUTH2_PROXY_COOKIE_SECRET
        resources:
          requests:
            cpu: 10m
            memory: 100Mi
        ports:
        - containerPort: 4180
          protocol: TCP
        readinessProbe:
          periodSeconds: 3
          httpGet:
            path: /ping
            port: 4180
EOF

kubectl exec -it admin -- curl -IL oauth2-proxy.oauth2-proxy.svc.cluster.local:4180
#配置外部ip
ip add a 2.2.2.16/32 dev vip0
echo '@reboot /usr/sbin/ip add a 2.2.2.16/32 dev vip0' >> /var/spool/cron/root
kubectl patch svc -n keycloak keycloak -p '{"spec":{"externalIPs":["2.2.2.16"]}}'

echo 2.2.2.16 keycloak.keycloak.svc.cluster.local >> /etc/hosts
curl -I keycloak.keycloak.svc.cluster.local:8080
3)配置IstioOperator,将oauth2-proxy设为provider

注意事项:

  • 在istiooperators资源的meshConfig字段添加extensionProviders配置
istioctl install -y -f - <<EOF
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  meshConfig:
    extensionProviders:
    - name: oauth2-proxy
      envoyExtAuthzHttp:
        service: oauth2-proxy.oauth2-proxy.svc.cluster.local    #oauth2-proxy的svc名
        port: 4180
        timeout: 1.5s
        includeHeadersInCheck:    #要检查的标头,用于确认用户是否完成身份认证
        - "authorization"
        - "cookie"
        headersToUpstreamOnAllow:     #身份认证检测成功时,向上游转发或添加的标头
        - "x-forwarded-access-token"
        - "authorization"
        - "path"
        - "x-auth-request-user"
        - "x-auth-request-email"
        - "x-auth-request-access-token"
        headersToDownstreamOnDeny:    #身份认证检测失败时,向下游转发或添加的标头
        - "content-type"
        - "set-cookie"
EOF
4)为ingress-gw配置认证授权策略
kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: istio-ingressgateway
  namespace: istio-system
spec:
  selector:
    matchLabels:
      app: istio-ingressgateway
  jwtRules:
    #集群内访问keycloak的路径
  - issuer: http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/istio
    jwksUri: http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/istio/protocol/openid-connect/certs
    #audiences: ["ingress-gateway","istio-ingress-gateway"]
    # Forward JWT to Envoy Sidecar
    forwardOriginalToken: true
    #集群外访问keycloak的路径
  - issuer: http://keycloak.hj.com:8080/auth/realms/istio
    jwksUri: http://keycloak.hj.com:8080/auth/realms/istio/protocol/openid-connect/certs
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: ext-authz-oauth2-proxy
  namespace: istio-system
spec:
  selector:
    matchLabels:
      app: istio-ingressgateway
  action: CUSTOM
  provider:   #使用网格中定义的外部provider
    name: oauth2-proxy
  rules:
  - to:   #允许访问的主机名,当访问这几个主机时,将认证请求转给上面定义的provider
    - operation:
        hosts:
        - "kiali.hj.com"
        - "prometheus.hj.com"
        - "bookinfo.hj.com"
        - "ft.hj.com"
        notPaths: ["/auth/*"]
  #- to:
  #  - operation:
  #      hosts: ["*.hj.com"]
  #      notPaths: ["/auth/*"]
EOF
5)为kiali服务添加gw、vs策略

注意事项:

  • 必须配置2个路由匹配,才能处理认证请求:/auth、/oauth2
  • 其他部分正常路由
  • 必须在ingress-gw开启https
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: kiali-gw
  namespace: istio-system
spec:
  selector:
    app: istio-ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "kiali.hj.com"
    tls:
      httpsRedirect: true
  - port:   #必须开启https,才能生效
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: hj.com.crt
    hosts:
    - "kiali.hj.com"
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: kiali-vs
  namespace: istio-system
spec:
  hosts:
  - "kiali.hj.com"
  gateways:
  - kiali-gw
  http:
  - match:  #必须配置/auth前缀路由,将认证请求转给keycloak服务
    - uri:
        prefix: /auth
    route:
    - destination:
        host: keycloak.keycloak.svc.cluster.local
        port:
          number: 8080
  - match:  ##必须配置/oauth2前缀路由,将认证请求转给oauth2-proxy服务
    - uri:
        prefix: /oauth2
    route:
    - destination:
        host: oauth2-proxy.oauth2-proxy.svc.cluster.local
        port:
          number: 4180
  - route:  #其余url,正常处理请求
    - destination:
        host: kiali
        port:
          number: 20001
EOF
6)在keycloak为kiali创建登陆用户

注意事项:

  • 根据oauth2-proxy的配置,在创建用户时,必须开启“电子邮件认证”

image-20231225135309024

image-20231225135350838

此时,在浏览器访问:https://kiali.hj.com,由于使用的是自签证书,所以浏览器不识别,手动点继续访问,会自动跳转到keycloak登录界面

image-20231225140008550

输入刚刚在keycloak创建的用户,以及密码,再登陆image-20231225135631554

此时,登陆后,会跳转到kiali界面,正常访问
image-20231225140134631

7)为proxy服务添加gw、vs策略
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: proxy-gw
  namespace: istio-system
spec:
  selector:
    app: istio-ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - ft.hj.com
    tls:
      httpsRedirect: true
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: hj.com.crt
    hosts:
    - ft.hj.com
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: proxy-vs
  namespace: default
spec:
  hosts:
  - ft.hj.com
  - proxy
  - mesh
  gateways:
  - istio-system/proxy-gw
  - mesh
  http:
  - match:
    - uri:
        prefix: /auth
    route:
    - destination:
        host: keycloak.keycloak.svc.cluster.local
        port:
          number: 8080
  - match:
    - uri:
        prefix: /oauth2
    route:
    - destination:
        host: oauth2-proxy.oauth2-proxy.svc.cluster.local
        port:
          number: 4180
  - route:
    - destination:
        host: proxy
EOF
8)在keycloak新建用户,配置角色与组

为proxy配置,目前可以直接使用前面创建的kiali账号就可以登陆访问proxy了,但这里演示一下项目环境中常见用法,角色和组

新建角色:proxy-roleimage-20231225150628023

新建组,模拟管理员组:proxy-admin
image-20231225150728034

新建用户:proxy,并关联到proxy-admin组、proxy-role角色
image-20231225150941474
image-20231225151115617image-20231225152849867

在客户端ingress-gw中,再定义2个映射器,goups、role
image-20231225151511718image-20231225155102157

测试访问,检查token是否有效,能否输出配置的组和角色信息

#用户名为:proxy,密码为:123456,授权类型为:password,客户端ID为:ingress-gw,客户端Secret为需要在keycloak中查看ingress-gw的凭据
curl -sS -d "username=proxy&password=123456&grant_type=password&client_id=ingress-gw&client_secret=sSLQESOaQJDZ6CQ6t2KQ0TdZFpjymetp" http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/istio/protocol/openid-connect/token |jq .access_token

将token字符串放到http://jwt.io中查看,此时可以显示出groups和role信息
image-20231225155213771

9)配置AuthorizationPolicy,使proxy基于角色访问控制
kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: istio-ingressgateway
  namespace: istio-system
spec:
  selector:
    matchLabels:
      app: istio-ingressgateway
  action: ALLOW
  rules:
  - to:
    - operation:
        hosts:
        - ft.hj.com
        paths:
        - "/*"
    when:
    - key: request.auth.claims[iss]
      values: 
      - "http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/istio"
      - "http://keycloak.magedu.com:8080/auth/realms/istio"
    - key: request.auth.claims[role]    #这是字典类型,获取的key,必须是jwt的token中有的key
      values: 
      - "proxy-role"
EOF

request.auth.claims[iss],获取的是token中image-20231225161353315

request.auth.claims[role],获取的是token中
image-20231225161505891

10)测试访问proxy,验证角色控制

检查基于角色访问控制是否生效,此时,proxy用户可以正常访问
image-20231225161828521

此时,kiali用户访问时被拒绝,因为没有关联角色
image-20231225162011944

11)配置AuthorizationPolicy,使proxy基于角色和组访问控制
kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: istio-ingressgateway
  namespace: istio-system
spec:
  selector:
    matchLabels:
      app: istio-ingressgateway
  action: ALLOW
  rules:
  - to:
    - operation:
        hosts:
        - ft.hj.com
        paths:
        - "/*"
    when:
    - key: request.auth.claims[iss]
      values: 
      - "http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/istio"
      - "http://keycloak.magedu.com:8080/auth/realms/istio"
    - key: request.auth.claims[roles]
      values: 
      - "proxy-role"
    - key: request.auth.claims[groups]
      values:
      - "/proxy-admin"
EOF

并在keycloak中,将kiali加入到proxy-role角色、proxy-admin角色

image-20231225163020417

image-20231225163230965

12)再次测试proxy访问

此时,因为第10步中将kiali也加入到proxy用户一样的角色和组,所以现在可以正常访问了image-20231225163337589

13)清理测试数据
kubectl delete ra -n istio-system istio-ingressgateway --wait=false
kubectl delete authorizationpolicies -n istio-system ext-authz-oauth2-proxy --wait=false
kubectl delete authorizationpolicies -n istio-system istio-ingressgateway --wait=false

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: kiali-vs
  namespace: istio-system
spec:
  hosts:
  - "kiali.hj.com"
  gateways:
  - kiali-gw
  http:
  - route:
    - destination:
        host: kiali
        port:
          number: 20001
EOF

其他案例

参考发布在稀土掘金上的内容:https://juejin.cn/spost/7317201268190298147

posted @ 2023-12-25 18:00  suyanhj  阅读(131)  评论(0编辑  收藏  举报