vlambda博客
学习文章列表

istio业务权限控制,原来可以这么玩

istio业务权限控制,原来可以这么玩

1用jwt获取认证信息

1.1jwt介绍

jwt 是 JSON web token ,为了在网络应用环境中传递声明而执行的一种基于json的开放标准。非常适用于分布式的单点登录场景。主要是用来校验身份提供者和服务提供者之间传递的用户身份信息。

1.2istio如何用jwt

1.2.1写死

cat << EOF > ra-productpage-jwtrules-audiences.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
  name: "productpage"
spec:
  selector:
    matchLabels:
      app: productpage
  jwtRules:
  - issuer: "[email protected]"
    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"
           }
         ]
      }
EOF

kubectl apply -f ra-productpage-jwtrules-audiences.yaml -n istio

创建RequestAuthentication,实现jwt

测试

TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjM1MzczOTExMDQsImdyb3VwcyI6WyJncm91cDEiLCJncm91cDIiXSwiaWF0IjoxNTM3MzkxMTA0LCJpc3MiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyIsInNjb3BlIjpbInNjb3BlMSIsInNjb3BlMiJdLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.EdJnEZSH6X8hcyEii7c8H5lnhgjB5dwo07M5oheC8Xz8mOllyg--AHCFWHybM48reunF--oGaG6IXVngCEpVF0_P5DwsUoBgpPmK1JOaKN6_pe9sh0ZwTtdgK_RP01PuI7kUdbOTlkuUi2AO-qUyOm7Art2POzo36DLQlUXv8Ad7NBOqfQaKjE9ndaPWT7aexUsBHxmgiGbz1SyLH879f7uHYPbPKlpHU6P9S-DaKnGLaEchnoKnov7ajhrEhGXAQRukhDPKUHO9L30oPIr5IJllEQfHYtt6IZvlNUGeLUcif3wpry1R5tBXRicx2sXMQ7LyuDremDbcNy_iE76Upg

curl 192.168.229.134:30986/productpage -H "Authorization: Bearer ${TOKEN}"

1.2.2url的方式

cat << EOF > ra-productpage-jwtrules-jwksUri.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
  name: "productpage"
spec:
  selector:
    matchLabels:
      app: productpage
  jwtRules:
  - issuer: "[email protected]"
    jwksUri: http://jwt-server.istio.svc.cluster.local:8000
    outputPayloadToHeader: auth
EOF

kubectl apply -f ra-productpage-jwtrules-jwksUri.yaml -n istio

创建RequestAuthentication,实现jwt

cat << EOF > jwt-server.yaml
apiVersion: v1
kind: Service
metadata:
  name: jwt-server
  labels:
    app: jwt-server
spec:
  ports:
  - name: http
    port: 8000
    targetPort: 8000
  selector:
    app: jwt-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jwt-server
spec:
  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
---
EOF

kubectl apply -f jwt-server.yaml -n istio

创建jwt服务器

测试

TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjM1MzczOTExMDQsImdyb3VwcyI6WyJncm91cDEiLCJncm91cDIiXSwiaWF0IjoxNTM3MzkxMTA0LCJpc3MiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyIsInNjb3BlIjpbInNjb3BlMSIsInNjb3BlMiJdLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.EdJnEZSH6X8hcyEii7c8H5lnhgjB5dwo07M5oheC8Xz8mOllyg--AHCFWHybM48reunF--oGaG6IXVngCEpVF0_P5DwsUoBgpPmK1JOaKN6_pe9sh0ZwTtdgK_RP01PuI7kUdbOTlkuUi2AO-qUyOm7Art2POzo36DLQlUXv8Ad7NBOqfQaKjE9ndaPWT7aexUsBHxmgiGbz1SyLH879f7uHYPbPKlpHU6P9S-DaKnGLaEchnoKnov7ajhrEhGXAQRukhDPKUHO9L30oPIr5IJllEQfHYtt6IZvlNUGeLUcif3wpry1R5tBXRicx2sXMQ7LyuDremDbcNy_iE76Upg

curl 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.authz

import input.attributes.request.http as http_request

default allow = false

token = {"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.yaml
apiVersion: v1
kind: Service
metadata:
  name: opa
  labels:
    app: opa
spec:
  ports:
  - name: grpc
    port: 9191
    targetPort: 9191
  selector:
    app: opa
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: opa
  labels:
    app: opa
spec:
  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.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: ext-authz
 namespace: istio-system
spec:
 selector:
   matchLabels:
     app: istio-ingressgateway
 action: CUSTOM
 provider:
   name: "opa.istio"
 rules:
 - to:
   - operation:
       paths: ["/productpage"]
EOF

kubectl apply -f ext-authz.yaml -n istio-system

3基于LuaFilter实现

3.1先进行jwt认证

cat << EOF > ra-productpage-jwtrules-audiences.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
  name: "productpage"
spec:
  selector:
    matchLabels:
      app: productpage
  jwtRules:
  - issuer: "[email protected]"
    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"
           }
         ]
      }
EOF

kubectl apply -f ra-productpage-jwtrules-audiences.yaml -n istio

3.2进行权限认证

cat << EOF > ef-http-filter-lua.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: apply-to
spec:
  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
            
EOF

kubectl 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 扩展。

下载wasme
https://github.com/solo-io/wasm/releases
mkdir .wasme/bin -p
mv wasme-linux-amd64 ./.wasme/bin/wasme
chmod +x .wasme/bin/wasme
vi /etc/profile
export PATH=$HOME/.wasme/bin:$PATH
. /etc/profile

[root@master01 istio-teaching]# wasme --version
wasme version 0.0.33

4.2.2先进行jwt认证

cat << EOF > ra-productpage-jwtrules-audiences.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
  name: "productpage"
spec:
  selector:
    matchLabels:
      app: productpage
  jwtRules:
  - issuer: "[email protected]"
    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"
           }
         ]
      }
EOF

kubectl 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'
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
@RestController
public 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.1

docker push registry.cn-hangzhou.aliyuncs.com/hxpdocker/auth-simple:1.1
[root@master01 auth-simple]# cat Dockerfile 
FROM java:8

ADD  ./auth-simple.jar /app.jar

ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

部署auth-simple

cat << EOF > k8s.yaml 
apiVersion: v1
kind: Service
metadata:
  name: auth-simple
  labels:
    app: auth-simple
spec:
  ports:
    - port: 8080
      name: http
      protocol: TCP
  selector:
    app: auth-simple
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: auth-simple
  labels:
    app: auth-simple
    version: v1
spec:
  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}"