istio业务权限控制,原来可以这么玩
1用jwt获取认证信息
1.1jwt介绍
jwt 是 JSON web token ,为了在网络应用环境中传递声明而执行的一种基于json的开放标准。非常适用于分布式的单点登录场景。主要是用来校验身份提供者和服务提供者之间传递的用户身份信息。
1.2istio如何用jwt
1.2.1写死
cat << EOF > ra-productpage-jwtrules-audiences.yamlapiVersion: "security.istio.io/v1beta1"kind: "RequestAuthentication"metadata: name: "productpage"spec: selector: matchLabels: app: productpage jwtRules: - issuer: "testing@secure.istio.io" outputPayloadToHeader: auth audiences: - "app" jwks: | { "keys": [ { "e":"AQAB", "kid":"DHFbpoIUqrY8t2zpA2qXfCmr5VO5ZEr4RzHU_-envvQ", "kty":"RSA", "n":"xAE7eB6qugXyCAG3yhh7pkDkT65pHymX-P7KfIupjf59vsdo91bSP9C8H07pSAGQO1MV_xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg_pbhLdKXbi66GlVeK6ABZOUW3WYtnNHD-91gVuoeJT_DwtGGcp4ignkgXfkiEm4sw-4sfb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo_-4WTiULmmHSGZHOjzwa8WtrtOQGsAFjIbno85jp6MnGGGZPYZbDAa_b3y5u-YpW7ypZrvD8BgtKVjgtQgZhLAGezMt0ua3DRrWnKqTZ0BJ_EyxOGuHJrLsn00fnMQ" } ] }EOFkubectl apply -f ra-productpage-jwtrules-audiences.yaml -n istio
创建RequestAuthentication,实现jwt
测试
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjM1MzczOTExMDQsImdyb3VwcyI6WyJncm91cDEiLCJncm91cDIiXSwiaWF0IjoxNTM3MzkxMTA0LCJpc3MiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyIsInNjb3BlIjpbInNjb3BlMSIsInNjb3BlMiJdLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.EdJnEZSH6X8hcyEii7c8H5lnhgjB5dwo07M5oheC8Xz8mOllyg--AHCFWHybM48reunF--oGaG6IXVngCEpVF0_P5DwsUoBgpPmK1JOaKN6_pe9sh0ZwTtdgK_RP01PuI7kUdbOTlkuUi2AO-qUyOm7Art2POzo36DLQlUXv8Ad7NBOqfQaKjE9ndaPWT7aexUsBHxmgiGbz1SyLH879f7uHYPbPKlpHU6P9S-DaKnGLaEchnoKnov7ajhrEhGXAQRukhDPKUHO9L30oPIr5IJllEQfHYtt6IZvlNUGeLUcif3wpry1R5tBXRicx2sXMQ7LyuDremDbcNy_iE76Upgcurl 192.168.229.134:30986/productpage -H "Authorization: Bearer ${TOKEN}"
1.2.2url的方式
cat << EOF > ra-productpage-jwtrules-jwksUri.yamlapiVersion: "security.istio.io/v1beta1"kind: "RequestAuthentication"metadata: name: "productpage"spec: selector: matchLabels: app: productpage jwtRules: - issuer: "testing@secure.istio.io" jwksUri: http://jwt-server.istio.svc.cluster.local:8000 outputPayloadToHeader: authEOFkubectl apply -f ra-productpage-jwtrules-jwksUri.yaml -n istio
创建RequestAuthentication,实现jwt
cat << EOF > jwt-server.yamlapiVersion: v1kind: Servicemetadata: name: jwt-server labels: app: jwt-serverspec: ports: - name: http port: 8000 targetPort: 8000 selector: app: jwt-server---apiVersion: apps/v1kind: Deploymentmetadata: name: jwt-serverspec: replicas: 1 selector: matchLabels: app: jwt-server template: metadata: labels: app: jwt-server spec: containers: - image: docker.io/istio/jwt-server:0.5 imagePullPolicy: IfNotPresent name: jwt-server ports: - containerPort: 8000---EOFkubectl apply -f jwt-server.yaml -n istio
创建jwt服务器
测试
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjM1MzczOTExMDQsImdyb3VwcyI6WyJncm91cDEiLCJncm91cDIiXSwiaWF0IjoxNTM3MzkxMTA0LCJpc3MiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyIsInNjb3BlIjpbInNjb3BlMSIsInNjb3BlMiJdLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.EdJnEZSH6X8hcyEii7c8H5lnhgjB5dwo07M5oheC8Xz8mOllyg--AHCFWHybM48reunF--oGaG6IXVngCEpVF0_P5DwsUoBgpPmK1JOaKN6_pe9sh0ZwTtdgK_RP01PuI7kUdbOTlkuUi2AO-qUyOm7Art2POzo36DLQlUXv8Ad7NBOqfQaKjE9ndaPWT7aexUsBHxmgiGbz1SyLH879f7uHYPbPKlpHU6P9S-DaKnGLaEchnoKnov7ajhrEhGXAQRukhDPKUHO9L30oPIr5IJllEQfHYtt6IZvlNUGeLUcif3wpry1R5tBXRicx2sXMQ7LyuDremDbcNy_iE76Upgcurl 192.168.198.154:30986/productpage -H "Authorization: Bearer ${TOKEN}"
2基于AuthorizationPolicy Custom Action实现
1创建opa策略
opa介绍
http://blog.newbmiao.com/2020/03/13/opa-quick-start.html
验证opa
https://play.openpolicyagent.org/p/ZXkIlAEPCY
cat << EOF > policy.rego package envoy.authzimport input.attributes.request.http as http_requestdefault allow = falsetoken = {"payload": payload} { [_, encoded] := split(http_request.headers.authorization, " ") [_, payload, _] := io.jwt.decode(encoded)}allow { action_allowed}bar := "bar"action_allowed { bar ==token.payload.foo}EOF
2创建secret
kubectl create secret generic opa-policy --from-file policy.rego -n istio
3创建opa
cat << EOF > opa-deployment.yamlapiVersion: v1kind: Servicemetadata: name: opa labels: app: opaspec: ports: - name: grpc port: 9191 targetPort: 9191 selector: app: opa---kind: DeploymentapiVersion: apps/v1metadata: name: opa labels: app: opaspec: replicas: 1 selector: matchLabels: app: opa template: metadata: labels: app: opa spec: containers: - name: opa image: openpolicyagent/opa:latest-envoy securityContext: runAsUser: 1111 volumeMounts: - readOnly: true mountPath: /policy name: opa-policy args: - "run" - "--server" - "--addr=localhost:8181" - "--diagnostic-addr=0.0.0.0:8282" - "--set=plugins.envoy_ext_authz_grpc.addr=:9191" - "--set=plugins.envoy_ext_authz_grpc.query=data.envoy.authz.allow" - "--set=decision_logs.console=true" - "--ignore=.*" - "/policy/policy.rego" ports: - containerPort: 9191 livenessProbe: httpGet: path: /health?plugins scheme: HTTP port: 8282 initialDelaySeconds: 5 periodSeconds: 5 readinessProbe: httpGet: path: /health?plugins scheme: HTTP port: 8282 initialDelaySeconds: 5 periodSeconds: 5 volumes: - name: opa-policy secret: secretName: opa-policy kubectl apply -f opa-deployment.yaml -n istio
4编辑meshconfig
kubectl edit configmap istio -n istio-system
mesh: |- # Add the following contents: extensionProviders: - name: "opa.istio" envoyExtAuthzGrpc: service: "opa.istio.svc.cluster.local" port: "9191"
5创建ap
cat << EOF >ext-authz.yamlapiVersion: security.istio.io/v1beta1kind: AuthorizationPolicymetadata: name: ext-authz namespace: istio-systemspec: selector: matchLabels: app: istio-ingressgateway action: CUSTOM provider: name: "opa.istio" rules: - to: - operation: paths: ["/productpage"]EOFkubectl apply -f ext-authz.yaml -n istio-system
3基于LuaFilter实现
3.1先进行jwt认证
cat << EOF > ra-productpage-jwtrules-audiences.yamlapiVersion: "security.istio.io/v1beta1"kind: "RequestAuthentication"metadata: name: "productpage"spec: selector: matchLabels: app: productpage jwtRules: - issuer: "testing@secure.istio.io" outputPayloadToHeader: auth audiences: - "app" jwks: | { "keys": [ { "e":"AQAB", "kid":"DHFbpoIUqrY8t2zpA2qXfCmr5VO5ZEr4RzHU_-envvQ", "kty":"RSA", "n":"xAE7eB6qugXyCAG3yhh7pkDkT65pHymX-P7KfIupjf59vsdo91bSP9C8H07pSAGQO1MV_xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg_pbhLdKXbi66GlVeK6ABZOUW3WYtnNHD-91gVuoeJT_DwtGGcp4ignkgXfkiEm4sw-4sfb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo_-4WTiULmmHSGZHOjzwa8WtrtOQGsAFjIbno85jp6MnGGGZPYZbDAa_b3y5u-YpW7ypZrvD8BgtKVjgtQgZhLAGezMt0ua3DRrWnKqTZ0BJ_EyxOGuHJrLsn00fnMQ" } ] }EOFkubectl apply -f ra-productpage-jwtrules-audiences.yaml -n istio
3.2进行权限认证
cat << EOF > ef-http-filter-lua.yamlapiVersion: networking.istio.io/v1alpha3kind: EnvoyFiltermetadata: name: apply-tospec: workloadSelector: labels: app: productpage configPatches: - applyTo: HTTP_FILTER match: context: SIDECAR_INBOUND listener: portNumber: 9080 filterChain: filter: name: "envoy.filters.network.http_connection_manager" subFilter: name: "envoy.filters.http.router" patch: operation: INSERT_BEFORE value: name: envoy.filters.http.lua typed_config: "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua" inlineCode: | function envoy_on_request(handle) handle:logWarn(" ============= envoy_on_request ============= ") local headers = handle:headers() local authToken = headers:get("auth") handle:logWarn(authToken) local headers, body = request_handle:httpCall( "outbound|8080||auth-simple.istio.svc.cluster.local", { [":method"] = "GET", [":path"] = "/auth", [":authority"] = "auth-simple:8080" }, authToken, 5000) if(body=="fail") then request_handle:respond( {[":status"] = "403", ["upstream_foo"] = headers["foo"]}, "nope") end handle:logWarn(" ============================================= ") end EOFkubectl apply -f ef-http-filter-lua.yaml -n istio
从jwt传过来的auth都里获取用户信息,传到认证服务器进行认证
4基于wasm实现
4.1什么是wasm
WASM 的诞生源自前端,是一种为了解决日益复杂的前端 web 应用以及有限的 JavaScript 性能而诞生的技术。它本身并不是一种语言,而是一种字节码标准,一个“编译目标”。WASM 字节码和机器码非常接近,因此可以非常快速的装载运行。任何一种语言,都可以被编译成 WASM 字节码,然后在 WASM 虚拟机中执行(本身是为 web 设计,必然天然跨平台,同时为了沙箱运行保障安全,所以直接编译成机器码并不是最佳选择)。理论上,所有语言,包括 JavaScript、C、C++、Rust、Go、Java 等都可以编译成 WASM 字节码并在 WASM 虚拟机中执行。
istio中的wasm,是一种扩展机制,主要用来扩展envoy的功能,以wasm filter的方式运行在envoy中。
4.2怎么用wasm实现权限控制
4.2.1安装wasme
wasme 是 solo.io 提供的一个命令行工具,一个简单的类比就是:docker cli 之于容器镜像,wasme 之于 WASM 扩展。
下载wasmehttps://github.com/solo-io/wasm/releasesmkdir .wasme/bin -pmv wasme-linux-amd64 ./.wasme/bin/wasmechmod +x .wasme/bin/wasmevi /etc/profileexport PATH=$HOME/.wasme/bin:$PATH. /etc/profile[root@master01 istio-teaching]# wasme --versionwasme version 0.0.33
4.2.2先进行jwt认证
cat << EOF > ra-productpage-jwtrules-audiences.yamlapiVersion: "security.istio.io/v1beta1"kind: "RequestAuthentication"metadata: name: "productpage"spec: selector: matchLabels: app: productpage jwtRules: - issuer: "testing@secure.istio.io" outputPayloadToHeader: auth audiences: - "app" jwks: | { "keys": [ { "e":"AQAB", "kid":"DHFbpoIUqrY8t2zpA2qXfCmr5VO5ZEr4RzHU_-envvQ", "kty":"RSA", "n":"xAE7eB6qugXyCAG3yhh7pkDkT65pHymX-P7KfIupjf59vsdo91bSP9C8H07pSAGQO1MV_xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg_pbhLdKXbi66GlVeK6ABZOUW3WYtnNHD-91gVuoeJT_DwtGGcp4ignkgXfkiEm4sw-4sfb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo_-4WTiULmmHSGZHOjzwa8WtrtOQGsAFjIbno85jp6MnGGGZPYZbDAa_b3y5u-YpW7ypZrvD8BgtKVjgtQgZhLAGezMt0ua3DRrWnKqTZ0BJ_EyxOGuHJrLsn00fnMQ" } ] }EOFkubectl apply -f ra-productpage-jwtrules-audiences.yaml -n istio
4.2.3进行权限认证
创建项目
wasme init . --language=rust --platform=istio --platform-version=1.9.x
修改程序
// Copyright 2020 Google LLC//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.use log::debug;use proxy_wasm::traits::*;use proxy_wasm::types::*;use std::time::Duration;#[no_mangle]pub fn _start() { proxy_wasm::set_http_context(|_, _| -> Box<dyn HttpContext> { Box::new(HttpAuth) });}struct HttpAuth;impl HttpAuth { fn fail(&mut self,message: &str) { debug!("auth: allowed"); //self.send_http_response(403, vec![], Some(b"not authorized")); self.send_http_response(403, vec![], Some(message.as_bytes())); }}// Implement http functions related to this request.// This is the core of the filter code.impl HttpContext for HttpAuth { // This callback will be invoked when request headers arrive fn on_http_request_headers(&mut self, _: usize) -> Action { // get all the request headers let userId = self.get_http_request_header("auth").unwrap_or(String::from("")); // transform them from Vec<(String,String)> to Vec<(&str,&str)>; as dispatch_http_call needs // Vec<(&str,&str)>. //let ref_headers : Vec<(&str,&str)> = headers.iter().map(|(ref k,ref v)|(k.as_str(),v.as_str())).collect(); // Dispatch a call to the auth-cluster. Here we assume that envoy's config has a cluster // named auth-cluster. We send the auth cluster all our headers, so it has context to // perform auth decisions. let res = self.dispatch_http_call( "outbound|8080||auth-simple.istio.svc.cluster.local", // cluster name vec![ (":method", "GET"), (":path", "/auth"), (":authority", "auth-simple:8080"), ("userId", userId.as_str()), ], // headers None, // no body vec![], // no trailers Duration::from_secs(5), // one second timeout ); // If dispatch reutrn an error, fail the request. match res { Err(_) =>{ self.fail(""); } Ok(_) => {} } // the dispatch call is asynchronous. This means it returns immediatly, while the request // happens in the background. When the response arrives `on_http_call_response` will be // called. In the mean time, we need to pause the request, so it doesn't continue upstream. Action::Pause } fn on_http_response_headers(&mut self, _: usize) -> Action { // Add a header on the response. //self.set_http_response_header("Hello", Some("world")); Action::Continue }}impl Context for HttpAuth { fn on_http_call_response(&mut self, _ : u32, header_size: usize, _body_size: usize, _num_trailers: usize) { // We have a response to the http call! // if we have no headers, it means the http call failed. Fail the incoming request as well. //if header_size == 0 { // self.fail(); // return; //} // Check if the auth server returned "200", if so call `resume_http_request` so request is // sent upstream. // Otherwise, fail the incoming request. //match self.get_http_request_header(":status") { // Some(ref status) if status == "200" => { // self.resume_http_request(); // return; // } // _ => { // debug!("auth: not authorized"); // self.fail(); // } //} if let Some(body) = self.get_http_call_response_body(0, _body_size) { if let Ok(body) = std::str::from_utf8(&body) { //self.set_http_response_header("Hello", Some(body)); if body == "ok" { self.resume_http_request(); return; } debug!("auth: not authorized"); //self.send_http_response(403, vec![], Some(body.as_bytes())); self.fail(body); } } }}
代码说明:
let res = self.dispatch_http_call( "outbound|8080||auth-simple.istio.svc.cluster.local", // cluster name vec![ (":method", "GET"), (":path", "/auth"), (":authority", "auth-simple:8080"), ("userId", userId.as_str()), ], // headers None, // no body vec![], // no trailers Duration::from_secs(5), // one second timeout );这一段向远程服务器发送验证权限请求,第一个参数必须envoy的cluster名称,这个要注意方法get,请求路径是/auth,userId是我们传过去的认证参数if let Some(body) = self.get_http_call_response_body(0, _body_size) { if let Ok(body) = std::str::from_utf8(&body) { //self.set_http_response_header("Hello", Some(body)); if body == "ok" { self.resume_http_request(); return; } debug!("auth: not authorized"); //self.send_http_response(403, vec![], Some(body.as_bytes())); self.fail(body); } }这一段从远程认证服务器获取返回,如果返回内容是ok,就通过权限,否则就返回错误给前端。
构建
wasme build rust . -t webassemblyhub.io/hxpmark/auth-rs:0.07 --image=quay.mirrors.ustc.edu.cn/solo-io/ee-builder:0.0.33
push
wasme push webassemblyhub.io/hxpmark/auth-rs:0.07
部署
wasme deploy istio webassemblyhub.io/hxpmark/auth-rs:0.07 --id=auth-rs --labels app=productpage --namespace=istio
auth服务器代码
package com.mark;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;import org.springframework.web.bind.annotation.RequestHeader;@SpringBootApplication@RestControllerpublic class SpringBootDemoHelloworldApplication { public static void main(String[] args) { SpringApplication.run(SpringBootDemoHelloworldApplication.class, args); } @GetMapping("/auth") public String auth(@RequestHeader("userId") String userId) { //String userId=request.getHeader("UserId"); if ("admin".equals(userId)){ return "ok"; } return "fail"; }}
构建auth镜像
docker build . --tag registry.cn-hangzhou.aliyuncs.com/hxpdocker/auth-simple:1.1docker push registry.cn-hangzhou.aliyuncs.com/hxpdocker/auth-simple:1.1
[root@master01 auth-simple]# cat Dockerfile FROM java:8ADD ./auth-simple.jar /app.jarENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
部署auth-simple
cat << EOF > k8s.yaml apiVersion: v1kind: Servicemetadata: name: auth-simple labels: app: auth-simplespec: ports: - port: 8080 name: http protocol: TCP selector: app: auth-simple---apiVersion: apps/v1kind: Deploymentmetadata: name: auth-simple labels: app: auth-simple version: v1spec: replicas: 1 selector: matchLabels:
app: auth-simple
version: v1
template:
metadata:
labels:
app: auth-simple
version: v1
spec:
containers:
- name: auth-simple
image: registry.cn-hangzhou.aliyuncs.com/hxpdocker/auth-simple:1.5
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
---
EOF
kubectl apply -f k8s.yaml -n istio
测试:
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjM1MzczOTExMDQsImdyb3VwcyI6WyJncm91cDEiLCJncm91cDIiXSwiaWF0IjoxNTM3MzkxMTA0LCJpc3MiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyIsInNjb3BlIjpbInNjb3BlMSIsInNjb3BlMiJdLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.EdJnEZSH6X8hcyEii7c8H5lnhgjB5dwo07M5oheC8Xz8mOllyg--AHCFWHybM48reunF--oGaG6IXVngCEpVF0_P5DwsUoBgpPmK1JOaKN6_pe9sh0ZwTtdgK_RP01PuI7kUdbOTlkuUi2AO-qUyOm7Art2POzo36DLQlUXv8Ad7NBOqfQaKjE9ndaPWT7aexUsBHxmgiGbz1SyLH879f7uHYPbPKlpHU6P9S-DaKnGLaEchnoKnov7ajhrEhGXAQRukhDPKUHO9L30oPIr5IJllEQfHYtt6IZvlNUGeLUcif3wpry1R5tBXRicx2sXMQ7LyuDremDbcNy_iE76Upg
curl 192.168.229.134:30986/productpage -H "Authorization: Bearer ${TOKEN}"




