zoukankan      html  css  js  c++  java
  • kong插件应用

    插件概述

    插件之于kong,就像Spring中的aop功能。
    在请求到达kong之后,转发给后端应用之前,你可以应用kong自带的插件对请求进行处理,合法认证,限流控制,黑白名单校验,日志采集等等。同时,你也可以按照kong的教程文档,定制开发属于自己的插件。
    kong的插件分为开源版和社区版,社区版还有更多的定制功能,但是社区版是要收费的。
    目前,KONG开源版本一共开放28个插件,如下:
    acl、aws-lambda、basic-auth、bot-detection、correlation-id、cors、datadog、file-log、galileo、hmac-auth、http-log、ip-restriction、jwt、key-auth、ldap-auth、loggly、oauth2、rate-limiting、request-size-limiting、request-termination、request-transformer、response-ratelimiting、response-transformer、runscope、statsd、syslog、tcp-log、udp-log

    以上插件,主要分五大类,Authentication认证,Security安全,Traffic Control流量控制,Analytics & Monitoring分析&监控,Logging日志,其他还有请求报文处理类。

    熔断request-termination插件

    该插件用来定义指定请求或服务不进行上层服务,而直接返回指定的内容.用来为指定的请求或指定的服务进行熔断.


    这样再访问指定的服务就会返回403错误,消息为So long and thanks for all the fish!
    可以参考:https://docs.konghq.com/hub/kong-inc/request-termination/

    限流rate-limiting插件

    为"example”的APIS添加rate-limiting插件,步骤如下:

    点击按钮如下图:



    可以不用配置redis,不过要设置限制方法,我设置了每秒不超过1次。

    没超过1次时,返回如下:


    当请求超过1次,会出现

    说明:
    根据年、月、日、时、分、秒设置限流规则,多个限制同时生效。
    比如:每天不能超过10次调用,每分不能超过3次。
    当一分钟内,访问超过3次,第四次就会报错。
    当一天内,访问次数超过10次,第十一次就会报错。

    IP黑白名单ip-restriction限制插件

    IP限制插件,是一个非常简单的插件,可以设置黑名单IP,白名单IP这个很简单。

    规则:

    IP黑白名单,支持单个,多个,范围分段IP(满足CIDR notation规则)。多个IP之间用逗号,分隔。

    CIDR notation规范如下:

    10.10.10.0/24 表示10.10.10.*的都不能访问。

    关于CIDR notation的规则,不在本文讨论范围内,请自行查阅https://zh.wikipedia.org/wiki/%E6%97%A0%E7%B1%BB%E5%88%AB%E5%9F%9F%E9%97%B4%E8%B7%AF%E7%94%B1

    1.设置黑名单IP

    在这里,我将我自己的IP设置成黑名单.

    在这里插入图片描述

    似乎我安装的kong-dashboard黑白名单写反了。
    基本认证Basic Authentication插件

    在Consumers 页面,添加Basic Auth

    在这里插入图片描述

    输入用户名和密码,我这里设置为luanpeng luanpeng。计算认证头。获取luanpeng:luanpeng字符串的base64编码。

    可以直接在linux下输出

    $ echo "luanpeng:luanpeng"|base64

    bHVhbnBlbmc6bHVhbnBlbmcK

        1
        2
        3

    在插件页面,设置Basic Auth 绑定目标service,这样请求目标service就需要在http头中添加

    Authorization          Basic bHVhbnBlbmc6bHVhbnBlbmcK

        1

    在这里插入图片描述

    设置Basic Auth表单域参数介绍:
    表单域名称     默认值     描述
    name(必填)     无     插件名称,在这里该插件名称为:basic-auth
    config.hide_credentials(选填)     false     boolean类型,告诉插件,是否对上游API服务隐藏认证信息。如果配置true,插件将会把认证信息清除,然后再把请求转发给上游api服务。
    config.anonymous(选填)     空     String类型,用来作为匿名用户,如果认证失败。如果空,当请求失败时,返回一段4xx的错误认证信息。
    key认证key-Auth插件

    该插件很简单,利用提前预设好的关键字名称,如下面设置的keynote = apices,然后为consumer设置一个key-auth 密钥,假如key-auth=test@keyauth。

    在请求api的时候,将apikey=test@keyauth,作为一个参数附加到请求url后,或者放置到headers中。

    在插件页面添加key-auth插件
    在这里插入图片描述

    配置consumer key-auth
    在这里插入图片描述

    key-auth两种方式可通过校验

    curl http://xxx.xx.xx.xx:xxx/xxx -H 'apikey: luanpeng'
    http://xxx.xxx.xxx.xxx:xxx/xxx?apikey=luanpeng

        1
        2

    如果选中key_in_body, 则必须在传递body的参数中加入{“apikey”:“xxxx”}来实现认证.
    HMAC认证

    先启动HMAC插件,设置绑定的service和rout,以启动hmac验证。然后在Consumers页面中Hmac credentials of Consumer设置中添加一个username和secret。

    在这里插入图片描述

    准备生成http的header中的签名。请求是使用该签名。这里附上python的调用包

    # kong_hmac.py

    import base64
    import hashlib
    import hmac
    import re
    from wsgiref.handlers import format_date_time
    from datetime import datetime
    from time import mktime


    def create_date_header():
        now = datetime.now()
        stamp = mktime(now.timetuple())
        return format_date_time(stamp)


    def get_headers_string(signature_headers):
        headers = ""
        for key in signature_headers:
            if headers != "":
                headers += " "
            headers += key
        return headers


    def get_signature_string(signature_headers):
        sig_string = ""

        for key, value in signature_headers.items():
            if sig_string != "":
                sig_string += " "
            if key.lower() == "request-line":
                sig_string += value
            else:
                sig_string += key.lower() + ": " + value
        return sig_string


    def md5_hash_base64(string_to_hash):
        m = hashlib.md5()
        m.update(string_to_hash)
        return base64.b64encode(m.digest())

    # sha1签名算法,字符串的签名,并进行base64编码
    def sha1_hash_base64(string_to_hash, secret):
        h = hmac.new(secret, (string_to_hash).encode("utf-8"), hashlib.sha1)
        return base64.b64encode(h.digest())


    def generate_request_headers(username, secret, url, data=None, content_type=None):
        # Set the authorization header template
        auth_header_template = (
            'hmac username="{}",algorithm="{}",headers="{}",signature="{}"'
        )
        # Set the signature hash algorithm
        algorithm = "hmac-sha1"
        # Set the date header
        date_header = create_date_header()  # 产生GMT格式时间
        # print('GMT时间:',date_header)
        # Set headers for the signature hash
        signature_headers = {"date": date_header}

        # Determine request method
        if data is None or content_type is None:
            request_method = "GET"
        else:
            request_method = "POST"
            # MD5 digest of the content
            base64md5 = md5_hash_base64(data)
            # Set the content-length header
            content_length = str(len(data))
            # Add headers for the signature hash
            signature_headers["content-type"] = content_type
            signature_headers["content-md5"] = base64md5
            signature_headers["content-length"] = content_length

        # Strip the hostname from the URL
        target_url = re.sub(r"^https?://[^/]+/", "/", url)
        # print('请求路径:',target_url)
        # Build the request-line header
        request_line = request_method + " " + target_url + " HTTP/1.1"
        # print('request_line:',request_line)
        # Add to headers for the signature hash
        signature_headers["request-line"] = request_line


        # Get the list of headers
        headers = get_headers_string(signature_headers)  # 转化为list
        # print('签名的属性名称:',headers)
        # Build the signature string
        signature_string = get_signature_string(signature_headers)  # 获取要签名的字符串
        # print('要签名的字符串:',signature_string)
        # Hash the signature string using the specified algorithm
        signature_hash = sha1_hash_base64(signature_string, secret)   # 签名
        # print('签名后字符串:',signature_hash)
        # Format the authorization header
        auth_header = auth_header_template.format(
            username, algorithm, headers, signature_hash.decode('utf-8')
        )



        if request_method == "GET":
            request_headers = {"Authorization": auth_header, "Date": date_header}
        else:
            request_headers = {
                "Authorization": auth_header,
                "Date": date_header,
                "Content-Type": content_type,
                "Content-MD5": base64md5,
                "Content-Length": content_length,
            }

        return request_headers

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111
        112
        113
        114
        115
        116

    调用该包,demo如下

    # get示例
    username = 'vesionbook'
    secret = 'vesionbook'.encode('utf-8')

    url = 'http://192.168.11.127:30309/arctern'
    request_headers = generate_request_headers(username, secret, url)
    print('请求头:',request_headers)
    r = requests.get(url, headers=request_headers)
    print('Response code: %d ' % r.status_code)
    print(r.text)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

    jwt认证插件

    先为Consumer消费者建立jwt凭证

    在这里插入图片描述

    在线JWT编码和解码https://jwt.io/

    在这里插入图片描述

    图中HEADER 部分声明了验证方式为 JWT,加密算法为 HS256

    PAYLOAD 部分原本有 5 个参数

    {
        "iss": "kirito",                   # Consumer的jwt中设置的key
        "iat": 1546853545,         #  签发时间戳
        "exp": 1546853585,       #  过期时间戳
        "nbf": 1546853585        # 生效日期
        "aud": "cnkirito.moe",
        "sub": "250577914@qq.com",
    }

        1
        2
        3
        4
        5
        6
        7
        8

    这里面的前五个字段都是由 JWT 的标准(RFC7519)所定义的。

        iss: 该 JWT 的签发者,(验证的时候判断是否是签发者)
        sub: 该 JWT 所面向的用户,(验证的时候判断是否是所有者)
        aud: 接收该 JWT 的一方,标识令牌的目标受众。(验证的时候判断我是否是其中一员)
        exp(expires): 什么时候过期,这里是一个 Unix 时间戳,精确到s, ,它必须大于jwt的签发时间
        iat(issued at): 在什么时候签发的,精确到s的时间戳, claims_to_verify配置参数不允许设置iat
        nbf:定义jwt的生效时间
        jti:jwt唯一身份标识,主要用来作为一次性token来使用,从而回避重放攻击

    iss 这一参数在 Kong 的 Jwt 插件中对应的是curl http://127.0.0.1:8001/consumers/kirito/jwt 获取的用户信息中的 key 值。

    而其他值都可以选填.

    在页面上VERIFY SIGNATURE中填入自己的secret, 也就是在kong的dashboard中消费者创建jwt证书时的secret.

    我们使用 jwt 官网(jwt.io)提供的 Debugger 功能快速生成我们的 Jwt, 由三个圆点分隔的长串便是用户身份的标识了.

    打开kong的jwt插件
    在这里插入图片描述

    在key_claim_name中定义存储key的字段名称. 我们是使用的iss字段.
    cookie_names表示如果使用cookie传递证书, 则cookie中的名称.
    claims_to_verify表示验证证书中哪些字段, 我这里验证证书的发布时间和过期时间.

    然后在header中携带证书信息就可以了.
    在这里插入图片描述

    Jwt 也可以作为 QueryString 参数携带在 get 请求中

    curl http://localhost:8000/hello/hi?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ2Y252WVNGelRJR3lNeHpLU2duTlUwdXZ4aXhkWVdCOSJ9.3iL4sXgZyvRx2XtIe2X73yplfmSSu1WPGcvyhwq7TVE

        1

    如果在插件配置中设置了cookie_names为luanpeng-cookie

    则在发送中

    --cookie luanpeng-cookie=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJYSnFRMXpSQVhUWk52dlNHZ1Nsb1FyejczOFBqT0hFZyIsImV4cCI6MTUyNTc5MzQyNSwibmJmIjoxNTI1Nzc1NDI1LCJpYXQiOjE1MjU3NzU0MjV9.0Cv8rJkXTMNKAvPTOBV1w0UYVhRx3XRb6xJofxloRuA

        1

    不同配置下,可能返回证书未生效, 证书已过期, 或者返回正常结果

    通常用户需要自己写一个服务去帮助 Consumer 生成自己的 Jwt,自然不能总是依赖于 Jwt 官方的 Debugger,当然也没必要重复造轮子(尽管这并不难),可以考虑使用开源实现,在jwt官网上Libraries for Token Signing/Verification部分 根据自己使用的语言,选择对应的包,来实现证书生成器. 最好可以直接集成到api网关中.

    这里用python实现了一个简单的签名生成器


    import sys
    import os

    dir_common = os.path.split(os.path.realpath(__file__))[0] + '/../'
    sys.path.append(dir_common)   # 将根目录添加到系统目录,才能正常引用common文件夹

    from aiohttp import web
    import asyncio

    import logging
    import uvloop
    import time,datetime

    import jwt

    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

    routes = web.RouteTableDef()

    # 返回客户的json信息
    def write_response(status,message,result):
        response={
            'status':status,      # 状态,0为成功,1为失败
            'message':message,    # 错误或成功描述。字符串
            'result':result     # 成功的返回结果,字典格式
        }
        return response


    @routes.get('/')
    async def hello(request):
        return web.Response(text="Hello, world")

    # 签名
    @routes.post('/sign')
    async def sign(request):   # 异步监听,只要一有握手就开始触发
        try:
            data = await request.json()    # 等待post数据完成接收,只有接收完成才能进行后续操作.data['key']获取参数
        except Exception as e:
            logging.error("image file too large or cannot convert to json")
            return web.json_response(write_response(1,"image file too large or cannot convert to json",{}))

        logging.info('license sign request start, data is %s,%s' % (data, datetime.datetime.now()))
        if "username" not in data or 'password' not in data:
            logging.error("username or password not in data")
            return web.json_response(write_response(2, "username or password not in data", {}))
        payload = {
            "iss": data['username'],
            "iat": int(time.time()),
            "exp": int(time.time()) + 60*60,   # 有效期一个小时
        }

        encoded_jwt = jwt.encode(payload, data['password'], algorithm='HS256')
        encoded_jwt = encoded_jwt.decode('utf-8')
        logging.info('license sign request finish %s, %s' % (datetime.datetime.now(),encoded_jwt))
        header = {"Access-Control-Allow-Origin": "*", 'Access-Control-Allow-Methods': 'GET,POST'}
        result = write_response(0, "success",encoded_jwt)
        # 同时放在cookie中
        header['cookie']='--cookie aicloud-cookie='+encoded_jwt
        return web.json_response(result,headers=header)


    # 校验
    @routes.post('/check')
    async def check(request):   # 异步监听,只要一有握手就开始触发
        try:
            data = await request.json()    # 等待post数据完成接收,只有接收完成才能进行后续操作.data['key']获取参数
        except Exception as e:
            logging.error("image file too large or cannot convert to json")
            return web.json_response(write_response(1,"image file too large or cannot convert to json",{}))

        logging.info('license check request start, data is %s,%s' % (data,datetime.datetime.now()))
        if "username" not in data or 'password' not in data or 'sign' not in data:
            logging.error("username or password or sign not in data")
            return web.json_response(write_response(2, "username or password or sign not in data", {}))
        encoded_jwt = data['sign'].encode('utf-8')
        payload = jwt.decode(encoded_jwt, data['password'], algorithms=['HS256'])
        if payload['iss']!=data['username']:
            logging.error("iss in sign != username")
            return web.json_response(write_response(3, "username error", {}))
        elif payload['iat']>time.time():
            logging.error("sign not effective")
            return web.json_response(write_response(4, "sign not effective", {}))
        elif payload['exp']<time.time():
            logging.error("sign lose effectiveness")
            return web.json_response(write_response(5, "sign lose effectiveness", {}))

        logging.info('license check request finish %s, %s' % (datetime.datetime.now(),encoded_jwt))
        header = {"Access-Control-Allow-Origin": "*", 'Access-Control-Allow-Methods': 'GET,POST'}
        result = write_response(0, "success", {})
        return web.json_response(result,headers=header)



    if __name__ == '__main__':


        logger = logging.getLogger()
        logger.setLevel(logging.INFO)   # 最低输出等级


        app = web.Application(client_max_size=int(1024))    # 创建app,设置最大接收图片大小为2M
        app.add_routes(routes)     # 添加路由映射

        web.run_app(app,host='0.0.0.0',port=8080)   # 启动app
        logging.info('server close:%s'% datetime.datetime.now())




        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111

    ACL授权插件

    该插件相当于授权插件,授权必须建立在认证的基础上,认证和授权是相互独立的。

    ACL策略插件

    策略分组规则:

    1).为用户分配授权策略组

    2).为api添加授权策略分组插件。

    3).只有拥有api授权策略分组的用户才可以调用该api。

    4).授权策略分组,必须建立在认证机制上,该策略生效的前提,api至少要开启任意一个auth认证插件。

    在这里插入图片描述

    如果为同一service启用的授权和认证,则光认证是不行的。必须还要授权。将用户设置为授权组。

    上面的设置以后,只有属于白名单组的用户才能访问该service,但是究竟哪些用户属于这些组呢,这需要去Consumers页面设置。

    在这里插入图片描述

    如果想限制某些用户访问某些路径,可以在路由处添加几个路由匹配,对不同的路由匹配设置授权
    链路跟踪Zipkin插件

    Zipkin 是一款开源的分布式实时数据追踪系统。其主要功能是聚集来自各个异构系统的实时监控数据,用来追踪微服务架构下的系统延时问题。应用系统需要向 Zipkin 报告数据。Kong的Zipkin插件作为zipkin-client就是组装好Zipkin需要的数据包,往Zipkin-server发送数据。

    所以首先要部署一个zipkin服务端:参考https://blog.csdn.net/luanpeng825485697/article/details/85772954

    部署结束后打开http://xx.xx.xx.xx:9411/api/v2/spans?servicename=test看是否能正常打开

    启动zipkin插件:

    在插件页面启动插件配置参数

    config.http_endpoint :Zipkin接收数据的地址,配置http://xx.xx.xx.xx:9411/api/v2/spans
    config.sample_ratio : 采样的频率。设为0,则不采样;设为1,则完整采样。默认为0.001也就是0.1%的采样率, 再调试阶段建议设置采样率为1.

    zipkin插件会每次请求,打上如下标签,推送到zipkin服务端

        span.kind (sent to Zipkin as “kind”)
        http.method
        http.status_code
        http.url
        peer.ipv4
        peer.ipv6
        peer.port
        peer.hostname
        peer.service

    可以参考:https://github.com/Kong/kong-plugin-zipkin

    启用后,此插件会以与zipkin兼容的方式跟踪请求。

    代码围绕一个opentracing核心构建,使用opentracing-lua库来收集每个Kong阶段的请求的时间数据。该插件使用opentracing-lua兼容的提取器,注入器和记者来实现Zipkin的协议。
    提取器和注射器

    opentracing“提取器”从传入的请求中收集信息。如果传入请求中不存在跟踪ID,则基于sample_ratio配置值概率地生成一个跟踪ID 。

    opentracing“injector”将跟踪信息添加到传出请求中。目前,仅对kong代理的请求调用注入器; 它不尚未用于请求到数据库,或通过其他插件(如HTTP日志插件)。
    日志

    目前在Kong的 free plugins中,比较常用的有这么三个:Syslog、File-Log以及Http-Log,下面对这三种插件逐一分析一下。
    Syslog

    顾名思义,这个插件是把Kong中记录的日志给打印到系统日志中,开启插件之后只需要指定需要使用的API,无需做多余的配置,即可在/var/log/message中发现对应的日志信息,d 但是系统日志鱼龙混杂,如果需要用到ELK等日志分析工具时,需要做一次数据清洗工作。
    File-Log

    与Syslog一样,File-log的配置也很方便,只需要配置日志路劲就行,开启插件之后,会在对应的对应产生一个logFile。Syslog中提到需要做一些日志清洗工作,但是换成了File-log乍一看好像解决了之前的痛点,实则不然,官方建议这个插件不适合在生产环境中使用,会带来一些性能上的开销,影响正常业务。
    Http-Log

    http-log是我比较推荐的,它的原理是设置一个log-server地址,然后Kong会把日志通过post请求发送到设置的log-server,然后通过log-server把日志给沉淀下来,相比之前两种插件,这一种只要启一个log-server就好了,出于性能考虑,我用Rust实现了一个log-server,有兴趣可以参考看一下。
    prometheus可视化

    kong自带的prometheus插件,metrics比较少, 可以网上查一下丰富版的prometheus插件.

    比如:https://github.com/yciabaud/kong-plugin-prometheus

    现在用这个插件替换kong自带的插件.

    最方便的安装方式,一般linux机器上都会自带 luarocks(lua包管理程序),这样一来我们只要把 Plugins 所在的文件夹给移动到服务器的任意目录,然后在该目录下,执行luarocks make 这样一来插件便会自动安装到系统中,不过需要注意的是,此时插件还需要进行手动开启,首先进入/etc/kong/目录,然后cp kong.conf.default kong.conf, 这里注意一定要复制一份单独的kong.conf文件,不能直接对kong.conf.default进行修改,这样是不生效的,然后取消plugin = bundled前面的注释,在这一行后面增加你的插件名,这里注意插件名是不包含前缀 kong-plugin的,重启Kong即可在可视化界面里发现

    plugins = bundled,prometheus

        1

    在使用新插件之前,需要更新一下数据库:

    bash ./resty.sh kong/bin/kong  migrations up -c kong.conf

        1

    爬虫控制插件bot-detection

    备注:

    config.whitelist :白名单,逗号分隔的正则表达式数组。正则表达式是根据 User-Agent 头部匹配的。
    config.blacklist :黑名单,逗号分隔的正则表达式数组。正则表达式是根据 User-Agent 头部匹配的。

    这个字段是用来匹配客户端身份的, 比如是浏览器还是模拟器, 还是python代码.

    这个插件已经包含了一个基本的规则列表,这些规则将在每个请求上进行检查。你可以在GitHub上找到这个列表 https://github.com/Kong/kong/blob/master/kong/plugins/bot-detection/rules.lua.
    ---------------------
    作者:数据架构师
    来源:CSDN
    原文:https://blog.csdn.net/luanpeng825485697/article/details/85326831
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    bzoj1336: [Balkan2002]Alien最小圆覆盖
    bzoj3564: [SHOI2014]信号增幅仪
    [HDU5353]
    [codeforce1072D]
    [dp001]逛公园
    树上问题泛做
    [BZOJ2599]race
    [CEOI2019]MAGIC TREE
    [BZOJ2836]魔法树
    QTREE3
  • 原文地址:https://www.cnblogs.com/duanxz/p/10385531.html
Copyright © 2011-2022 走看看