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 += "\n"
        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\n' % 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
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM