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、oauth二、rate-limiting、request-size-limiting、request-termination、request-transformer、response-ratelimiting、response-transformer、runscope、statsd、syslog、tcp-log、udp-logpython

以上插件,主要分五大類,Authentication認證,Security安全,Traffic Control流量控制,Analytics & Monitoring分析&監控,Logging日誌,其餘還有請求報文處理類。linux

熔斷request-termination插件

該插件用來定義指定請求或服務不進行上層服務,而直接返回指定的內容.用來爲指定的請求或指定的服務進行熔斷.git


這樣再訪問指定的服務就會返回403錯誤,消息爲So long and thanks for all the fish!
能夠參考:https://docs.konghq.com/hub/kong-inc/request-termination/github

限流rate-limiting插件

爲"example」的APIS添加rate-limiting插件,步驟以下:web

點擊按鈕以下圖:正則表達式



能夠不用配置redis,不過要設置限制方法,我設置了每秒不超過1次。redis

沒超過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
版權聲明:本文爲博主原創文章,轉載請附上博文連接!json

相關文章
相關標籤/搜索