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获取
从双方公信的认证服务端获取
- 客户端向目标服务发起请求时,目标服务会判断用户是否登录过,没有登录则返回登录界面或者信任的三方认证服务端
- 客户端再向认证服务端发请求,认证服务端返回token(包含认证服务端自身签名信息与有效期,避免被伪装)
- 客户端再携带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是相同的,但证书不同
获取私钥和证书流程:
- istiod提供grpc服务来接收证书签名请求
- 启动时,istio proxy容器中的pilot-agent会创建私钥和csr,并将csr与凭据发给istiod完成签名
- istiod上的ca负责验证csr中携带的凭据,并在成功验证后签署csr生成证书
- 工作负载启动时,envoy通过sds api从同一容器中的pilot-agent请求证书和私钥
- pilot-agent通过envoy sds api将私钥以及从istiod上收到的证书发给本地envoy
- 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
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策略时,允许请求
配置方式
- rbac:envoy内置支持
- abac(extauthz):配置使用外部的鉴权服务,将鉴权机制委托给外部第三方。此时鉴权策略配置在外部服务上
外部鉴权服务
当AuthorizationPolicy资源的action: CUSTOM
时,工作流程如图
目前常用的外部授权服务是OPA,如keycloak、maxkey等
配置
AuthorizationPolicy资源
rbac实现,对pa和ra资源的allow、deny
可用授权策略参考官方文档:https://istio.io/latest/zh/docs/reference/config/security/conditions/
语法
kubectl explain authorizationpolicies
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验证
- 用户自行向SSO进行身份验证并获取JWT
- ingress-gw将请求和JWT转到到目标服务相关的工作负载的istio-proxy容器
- 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进行
流程:
- 用户发起http请求,由oauth2-proxy启动OIDC工作流程,用户完成身份验证
- 用户执行经过身份验证的http请求,oauth2-proxy基于cookie验证用户身份
- 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
流程:
- 用户发起http请求,由oauth2-proxy启动OIDC工作流程,由用户参与完成身份验证
- 用户执行经过身份验证的http请求,而oauth2-proxy基于cookie验证用户身份
- oauth-proxy从请求的cookie中提取jwt,并将特定标头转发给ingess-gw,ingress-gw将请求和jwt标头转发到目标工作负载的istio-proxy容器
- 工作负载上的istio-proxy,根据ra和AuthorizationPolicy确认jwt的有效性
- 若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
抓包显示的数据已是加密内容
明文验证:
#查看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等请求头
例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
例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
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
命令行访问proxy的域名
例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
添加新可信域,默认的Master是keycloak创建的管理域,管理所有配置,需要创建其他域做自定义配置
创建客户端,客户端表示允许向keycloak发起身份验证的实体,一般指那些希望使用keycloak完成sso的程序或服务。在网格中,发起认证请求的客户端就是sidecar envoy
添加用于jwt认证的用户,并配置用户的认证方式是密码
2)访问keycloak,测试token获取
需要先在界面中,获取访问路径,keycloak内置了相关url信息,点开即可查看
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为承载令牌
access_token解析后内容
3)配置jwt认证
#正常访问demoapp
kubectl exec -it admin -- sh
curl demoapp
未配置ra资源时,可以正常访问
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,会被拒绝
携带token后,2个demoapp服务均可以响应请求
post请求被拒绝
例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
例7:SSO单点登录
keycloak的访问类型说明:
- confidential:适用于需要执行浏览器登录的应用,客户端会通过client secret来获取access_token,多用于服务端渲染的web场景(kiali、grafana、普罗米修斯等属于服务端渲染web系统)
- public:适用于需要执行浏览器登录的应用,多用于使用vue和react实现的前端项目
- bearer-only:适用于不需要执行浏览器登录的应用,只允许携带bearer token访问,多用于Restful API的场景
1)keycloak配置
添加新的客户端ingress-gw
将客户端ingress-gw的访问类型修改为confidential,并将登录重定向url配置为:*
,然后保存
再获取客户端ingress-gw的秘钥(客户端认证器为client id and secret,表示通过该client访问keycloak的客户端程序需要提供client id和secret,来认证到keycloak上),秘钥需要保存一下
添加映射器(可选步骤),可以在access_token信息中将想要的信息显示成键值对(需解码),如果不需要查看信息可以不做
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的配置,在创建用户时,必须开启“电子邮件认证”
此时,在浏览器访问:https://kiali.hj.com,由于使用的是自签证书,所以浏览器不识别,手动点继续访问,会自动跳转到keycloak登录界面
输入刚刚在keycloak创建的用户,以及密码,再登陆
此时,登陆后,会跳转到kiali界面,正常访问
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-role
新建组,模拟管理员组:proxy-admin
新建用户:proxy,并关联到proxy-admin组、proxy-role角色
在客户端ingress-gw中,再定义2个映射器,goups、role
测试访问,检查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信息
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中
request.auth.claims[role],获取的是token中
10)测试访问proxy,验证角色控制
检查基于角色访问控制是否生效,此时,proxy用户可以正常访问
此时,kiali用户访问时被拒绝,因为没有关联角色
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角色
12)再次测试proxy访问
此时,因为第10步中将kiali也加入到proxy用户一样的角色和组,所以现在可以正常访问了
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