Skip to content

Commit

Permalink
feat: add prometheus authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
leolin49 authored and weimch committed Dec 16, 2024
1 parent 1bcfa53 commit 4c7d963
Show file tree
Hide file tree
Showing 21 changed files with 319 additions and 12 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ include(snappy)
include(lz4)
include(toml11)
include(flatbuffers)
include(jwt_cpp)

#---------------------------------------------------------------------------------------
# Set complie options and include other libs if options are ON
Expand Down
15 changes: 15 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,21 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
As an exception, if, as a result of your compiling your source code, portions of this Software are embedded into a machine-executable object form of such source code, you may redistribute such embedded portions in such object form without including the above copyright and permission notices.


Open Source Software Licensed under the MIT with exceptions:
--------------------------------------------------------------------
1. jwt-cpp
Copyright (c) 2018 Dominik Thalhammer


Terms of the MIT with exceptions:
--------------------------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Open Source Software Licensed under the MIT:
--------------------------------------------------------------------
1. murmurhash3
Expand Down
6 changes: 4 additions & 2 deletions cmake/config/trpc_config.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ set(INCLUDE_PATHS ${TRPC_ROOT_PATH}
${TRPC_ROOT_PATH}/cmake_third_party/nghttp2/lib/includes
${TRPC_ROOT_PATH}/cmake_third_party/picohttpparser
${TRPC_ROOT_PATH}/cmake_third_party/snappy
${TRPC_ROOT_PATH}/cmake_third_party/lz4)
${TRPC_ROOT_PATH}/cmake_third_party/lz4
${TRPC_ROOT_PATH}/cmake_third_party/jwt_cpp)

# When use tRPC as a third-party library, selectively inject the header files at including any-lib.cmake.
set(TARGET_INCLUDE_PATHS ${TRPC_ROOT_PATH})
Expand Down Expand Up @@ -117,4 +118,5 @@ set(LIB_SSL ssl crypto)

set(LIB_METRICS_PROMETHEUS prometheus-cpp-core
prometheus-cpp-pull
prometheus-cpp-push)
prometheus-cpp-push
jwt-cpp)
38 changes: 38 additions & 0 deletions cmake/jwt_cpp.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#
#
# Tencent is pleased to support the open source community by making tRPC available.
#
# Copyright (C) 2023 THL A29 Limited, a Tencent company.
# All rights reserved.
#
# If you have downloaded a copy of the tRPC source code from Tencent,
# please note that tRPC source code is licensed under the Apache 2.0 License,
# A copy of the Apache 2.0 License is included in this file.
#
#

include(FetchContent)

if(NOT DEFINED JWT_VET)
set(JWT_VET 0.7.0)
endif()

set(JWT_URL https://github.com/Thalhammer/jwt-cpp/archive/refs/tags/v${JWT_VET}.tar.gz)

FetchContent_Declare(
jwt-cpp
URL ${JWT_URL}
SOURCE_DIR ${TRPC_ROOT_PATH}/cmake_third_party/jwt_cpp
)

FetchContent_GetProperties(jwt-cpp)

if(NOT jwt_cpp_POPULATED)
FetchContent_Populate(jwt-cpp)
add_subdirectory(${TRPC_ROOT_PATH}/cmake_third_party/jwt_cpp)
add_library(trpc_jwt_cpp ALIAS jwt-cpp)
set(TARGET_INCLUDE_PATHS ${TARGET_INCLUDE_PATHS}
${TRPC_ROOT_PATH}/cmake_third_party/jwt_cpp)
set(TARGET_LINK_LIBS ${TARGET_LINK_LIBS} jwt_cpp)
endif()

57 changes: 57 additions & 0 deletions docs/zh/prometheus_metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,60 @@ std::vector<::prometheus::MetricFamily> Collect();
## 通过 admin 获取

如果服务开启了 [admin 功能](./admin_service.md),则可以通过访问 `http://admin_ip:admin_port/metrics` 获取序列化为字符串后的 Prometheus 数据。

# 鉴权

Prometheus插件鉴权分为两种模式:pull模式 和 push模式,不同模式下的配置方式有所区别。

## pull模式

在pull模式下,使用Json Web Token(JWT)方式来鉴权。需要同时配置**trpc的Prometheus插件**和**Prometheus服务器**。

### 插件配置

插件配置样例如下:

```yaml
plugins:
metrics:
prometheus:
auth_cfg:
iss: admin # issuer 签发人
sub: prometheus-pull # subject 主题
aud: trpc-server # audience 受众
secret: test # 密钥
```

需要配置**bearer_token**字段,该token可以通过[JWT官方工具](https://jwt.io/)生成。在payload中填写相应的iss,sub和aud字段,verify signature中填写secret字段,加密算法使用默认的 HS256。

## push模式

在push模式下,为了和pushgateway兼容,鉴权使用**username**和**password**的形式。

### 插件配置

插件配置样例如下:

```yaml
plugins:
metrics:
prometheus:
auth_cfg:
username: admin
password: test
```

### Pushgateway服务器配置

需要在Pushgateway服务器启动时,通过带有通过**bcrypt**加密的密文的配置文件启动。Pushgateway启动的配置文件如下:

```yaml
basic_auth_users:
admin: $2y$05$5uq4H5p8JyfQm.e16o3xduW6tkI2bTRpArTK4MF4dEuvncpz/bqy.
```

密码的密文可以通过htpasswd工具生成:
```shell
> htpasswd -nbB admin test
admin:$2y$05$5uq4H5p8JyfQm.e16o3xduW6tkI2bTRpArTK4MF4dEuvncpz/bqy.
```
Empty file.
12 changes: 12 additions & 0 deletions third_party/com_github_thalhammer_jwt_cpp/jwt_cpp.BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package(
default_visibility = ["//visibility:public"],
)

cc_library(
name = "jwt-cpp",
hdrs = glob(["**/*.h"]),
deps = [
"@com_github_openssl_openssl//:libcrypto",
"@com_github_openssl_openssl//:libssl",
],
)
8 changes: 8 additions & 0 deletions trpc/admin/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -354,13 +354,21 @@ cc_library(
":admin_handler",
":base_funcs",
"//trpc/util:prometheus",
"//trpc/common/config:trpc_config",
"//trpc/log:trpc_log",
"//trpc/util/http:base64",
"//trpc/util/string:string_helper",
] + select({
"//conditions:default": [],
"//trpc:trpc_include_prometheus": [
"@com_github_jupp0r_prometheus_cpp//pull",
"@com_github_thalhammer_jwt_cpp//:jwt-cpp",
"//trpc/metrics/prometheus:prometheus_metrics",
],
"//trpc:include_metrics_prometheus": [
"@com_github_jupp0r_prometheus_cpp//pull",
"@com_github_thalhammer_jwt_cpp//:jwt-cpp",
"//trpc/metrics/prometheus:prometheus_metrics",
],
}),
)
Expand Down
4 changes: 3 additions & 1 deletion trpc/admin/admin_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ AdminService::AdminService() {

#ifdef TRPC_BUILD_INCLUDE_PROMETHEUS
// Prometheus metrics.
RegisterCmd(http::OperationType::GET, "/metrics", std::make_shared<admin::PrometheusHandler>());
auto prometheus_handle_ptr = std::make_shared<admin::PrometheusHandler>();
prometheus_handle_ptr->Init();
RegisterCmd(http::OperationType::GET, "/metrics", prometheus_handle_ptr);
#endif

RegisterCmd(http::OperationType::POST, "/client_detach", std::make_shared<admin::ClientDetachHandler>());
Expand Down
105 changes: 105 additions & 0 deletions trpc/admin/prometheus_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,120 @@

#include "prometheus/text_serializer.h"

namespace {
bool JwtValidate(const std::string& token, std::map<std::string, std::string>& auth_cfg) {
try {
auto decoded_token = jwt::decode(token);

auto verifier = jwt::verify().allow_algorithm(jwt::algorithm::hs256{auth_cfg["secret"]});

// Verify if the config exists.
if (auth_cfg.count("iss")) {
verifier.with_issuer(auth_cfg["iss"]);
}
if (auth_cfg.count("sub")) {
verifier.with_subject(auth_cfg["sub"]);
}
if (auth_cfg.count("aud")) {
verifier.with_audience(auth_cfg["aud"]);
}

verifier.verify(decoded_token);
return true;
} catch (const std::exception& e) {
TRPC_FMT_ERROR("Jwt validate error: {}", e.what());
return false;
}
}
} // namespace

namespace trpc::admin {

PrometheusHandler::PrometheusHandler() { description_ = "[GET /metrics] get prometheus metrics"; }

void PrometheusHandler::Init() {
PrometheusConfig prometheus_conf;
bool ret = TrpcConfig::GetInstance()->GetPluginConfig<PrometheusConfig>(
"metrics", trpc::prometheus::kPrometheusMetricsName, prometheus_conf);
if (!ret) {
TRPC_LOG_WARN(
"Failed to obtain Prometheus plugin configuration from the framework configuration file. Default configuration "
"will be used.");
}
auth_cfg_ = prometheus_conf.auth_cfg;
}

bool PrometheusHandler::CheckTokenAuth(std::string bearer_token) {
auto splited = Split(bearer_token, ' ');
if (splited.size() != 2) {
TRPC_FMT_ERROR("error token: {}", bearer_token);
return false;
}
auto method = splited[0];
if (method != "Bearer") {
TRPC_FMT_ERROR("error auth method: {}", method);
return false;
}
std::string token = std::string(splited[1]);
if (!JwtValidate(token, auth_cfg_)) {
TRPC_FMT_ERROR("error token: {}", token);
return false;
}
return true;
}

bool PrometheusHandler::CheckBasicAuth(std::string token) {
auto splited = Split(token, ' ');
if (splited.size() != 2) {
TRPC_FMT_ERROR("error token: {}", token);
return false;
}
if (splited[0] != "Basic") {
TRPC_FMT_ERROR("error token: {}", token);
return false;
}

std::string username_pwd = http::Base64Decode(std::begin(splited[1]), std::end(splited[1]));
auto sp = Split(username_pwd, ':');
if (sp.size() != 2) {
TRPC_FMT_ERROR("error token: {}", token);
return false;
}

auto username = sp[0], pwd = sp[1];
if (username != auth_cfg_["username"] || pwd != auth_cfg_["password"]) {
TRPC_FMT_ERROR("error username or password: username: {}, password: {}", username, pwd);
return false;
}
return true;
}

void PrometheusHandler::CommandHandle(http::HttpRequestPtr req, rapidjson::Value& result,
rapidjson::Document::AllocatorType& alloc) {
static std::unique_ptr<::prometheus::Serializer> serializer = std::make_unique<::prometheus::TextSerializer>();

std::string prometheus_str = serializer->Serialize(trpc::prometheus::Collect());

std::string token = req->GetHeader("authorization");

if (!auth_cfg_.empty()) {
if (auth_cfg_.count("username") && auth_cfg_.count("password")) {
// push mode
// use the basic auth if already config the username and password.
if (!CheckBasicAuth(token)) {
result.AddMember("message", "wrong request without right username or password", alloc);
return;
}
} else {
// pull mode
// use the json web token auth.
if (!CheckTokenAuth(token)) {
result.AddMember("message", "wrong request without right token", alloc);
return;
}
}
}

result.AddMember(rapidjson::StringRef("trpc-html"), rapidjson::Value(prometheus_str, alloc).Move(), alloc);
}

Expand Down
18 changes: 18 additions & 0 deletions trpc/admin/prometheus_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,16 @@
#ifdef TRPC_BUILD_INCLUDE_PROMETHEUS
#pragma once

#include <jwt-cpp/jwt.h>

#include "trpc/admin/admin_handler.h"
#include "trpc/common/config/trpc_config.h"
#include "trpc/log/trpc_log.h"
#include "trpc/metrics/prometheus/prometheus_metrics.h"
#include "trpc/util/http/base64.h"
#include "trpc/util/prometheus.h"
#include "trpc/util/string/string_helper.h"
#include "trpc/util/time.h"

namespace trpc::admin {

Expand All @@ -24,8 +32,18 @@ class PrometheusHandler : public AdminHandlerBase {
public:
PrometheusHandler();

void Init();

void CommandHandle(http::HttpRequestPtr req, rapidjson::Value& result,
rapidjson::Document::AllocatorType& alloc) override;

private:
bool CheckTokenAuth(std::string token);

bool CheckBasicAuth(std::string token);

// Yaml-cpp only supports conversion to std::map.
std::map<std::string, std::string> auth_cfg_;
};

} // namespace trpc::admin
Expand Down
25 changes: 25 additions & 0 deletions trpc/metrics/prometheus/prometheus_conf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,28 @@ void PrometheusConfig::Display() const {
}

} // namespace trpc

namespace YAML {

YAML::Node convert<trpc::PrometheusConfig>::encode(const trpc::PrometheusConfig& config) {
YAML::Node node;
node["histogram_module_cfg"] = config.histogram_module_cfg;
node["const_labels"] = config.const_labels;
node["auth_cfg"] = config.auth_cfg;
return node;
}

bool convert<trpc::PrometheusConfig>::decode(const YAML::Node& node, trpc::PrometheusConfig& config) {
if (node["histogram_module_cfg"]) {
config.histogram_module_cfg = node["histogram_module_cfg"].as<std::vector<double>>();
}
if (node["const_labels"]) {
config.const_labels = node["const_labels"].as<std::map<std::string, std::string>>();
}
if (node["auth_cfg"]) {
config.auth_cfg = node["auth_cfg"].as<std::map<std::string, std::string>>();
}
return true;
}

} // namespace YAML
12 changes: 12 additions & 0 deletions trpc/metrics/prometheus/prometheus_conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,19 @@ struct PrometheusConfig {
/// The default label attached to each RPC metrics data
std::map<std::string, std::string> const_labels;

std::map<std::string, std::string> auth_cfg;

void Display() const;
};

} // namespace trpc

namespace YAML {

template <>
struct convert<trpc::PrometheusConfig> {
static YAML::Node encode(const trpc::PrometheusConfig& config);
static bool decode(const YAML::Node& node, trpc::PrometheusConfig& config);
};

} // namespace YAML
Loading

0 comments on commit 4c7d963

Please sign in to comment.