8个稳定版本
2.6.0 | 2024年7月24日 |
---|---|
2.5.0 | 2024年3月20日 |
2.4.0 | 2024年2月19日 |
2.3.0 | 2024年1月15日 |
2.0.1 | 2023年12月20日 |
#7 in #request-http
18,807 每月下载量
在 3 个crate中使用(通过 ic-response-verification)
325KB
6K SLoC
HTTP认证
概述
HTTP认证是ICP HTTP网关协议的一个子协议。它用于验证HTTP网关从canister接收到的HTTP响应,与相应的HTTP请求进行比较。这使得HTTP网关能够验证它们从canister接收到的响应是真实的且未被篡改的。
使用ic-http-certification
crate为在Rust canister中实现HTTP认证协议提供了基础。认证通过以下步骤实现
定义CEL表达式
CEL(通用表达式语言)是一种可移植的表达式语言,可用于不同应用轻松互操作。它可以看作是协议缓冲区的计算或表达式对应物。
CEL表达式是ICP HTTP认证系统的核心。它们用于定义请求和响应对应该被认证的条件。它们还定义了在认证中应包含相应请求和响应对象的内容。
可以通过两种方式创建CEL表达式
将CEL表达式转换为它们的String
表示形式
请注意,CelExpression
枚举本身不是CEL表达式,而是CEL表达式的Rust表示。要将CelExpression
转换为它的String
表示形式,请使用CelExpression.to_string
或create_cel_expr
。这适用于通过CEL构建器和直接创建的CEL表达式。
use ic_http_certification::cel::{CelExpression, DefaultCelExpression};
let cel_expr = CelExpression::Default(DefaultCelExpression::Skip).to_string();
或者
use ic_http_certification::cel::{CelExpression, DefaultCelExpression, create_cel_expr};
let certification = CelExpression::Default(DefaultCelExpression::Skip);
let cel_expr = create_cel_expr(&certification);
使用CEL构建器
CEL构建器界面提供了一种通过用户友好的界面来简化CEL表达式创建的方法。还可以直接 创建CEL表达式。要定义CEL表达式,从 DefaultCelBuilder
开始。这个结构提供了一组关联函数,可以用来定义如何认证请求和响应对。
当认证请求时
- 请求正文和方法总是被认证。
- 要认证请求头和查询参数,分别使用
with_request_headers
和with_request_query_parameters
。这两个关联函数都接受一个str
切片作为参数。
当认证响应时
- 响应正文和状态码总是被认证。
- 要认证响应头,使用
with_response_certification
。这个关联函数接受一个DefaultResponseCertification
枚举作为参数。- 要指定头包含,使用
DefaultResponseCertification
枚举的certified_response_headers
关联函数。 - 要认证所有响应头(有某些排除项)使用
DefaultResponseCertification
枚举的response_header_exclusions
关联函数。这两个函数都接受一个str
切片作为参数。
- 要指定头包含,使用
完全认证的请求/响应对
要定义完全认证的请求和响应对,包括请求头、查询参数和响应头,请使用 DefaultCelBuilder::full_certification
。
例如
use ic_http_certification::{DefaultCelBuilder, DefaultResponseCertification};
let cel_expr = DefaultCelBuilder::full_certification()
.with_request_headers(vec!["Accept", "Accept-Encoding", "If-None-Match"])
.with_request_query_parameters(vec!["foo", "bar", "baz"])
.with_response_certification(DefaultResponseCertification::certified_response_headers(vec![
"Cache-Control",
"ETag",
]))
.build();
部分认证的请求
可以通过 with_request_headers
和 with_request_query_parameters
分别认证任意数量的请求头或请求查询参数。这两种方法都将接受空数组,这与根本不调用它们相同。同样,对于 with_request_query_parameters
,如果它用一个空数组调用,或者根本不调用,则不会认证任何请求查询参数。如果两者都用空数组调用,或者两者都没有调用,那么只有请求正文和方法会被认证。
例如,只认证请求正文和方法
use ic_http_certification::{DefaultCelBuilder, DefaultResponseCertification};
let cel_expr = DefaultCelBuilder::full_certification()
.with_response_certification(DefaultResponseCertification::certified_response_headers(vec![
"Cache-Control",
"ETag",
]))
.build();
或者,这可以更明确地完成
use ic_http_certification::{DefaultCelBuilder, DefaultResponseCertification};
let cel_expr = DefaultCelBuilder::full_certification()
.with_request_headers(vec![])
.with_request_query_parameters(vec![])
.with_response_certification(DefaultResponseCertification::certified_response_headers(vec![
"Cache-Control",
"ETag",
]))
.build();
跳过请求认证
可以通过使用 DefaultCelBuilder::response_only_certification
而不是 DefaultCelBuilder::full_certification
完全跳过请求认证。
例如
use ic_http_certification::{DefaultCelBuilder, DefaultResponseCertification};
let cel_expr = DefaultCelBuilder::response_only_certification()
.with_response_certification(DefaultResponseCertification::response_header_exclusions(vec![
"Date",
"Cookie",
"Set-Cookie",
]))
.build();
部分认证的响应
在调用 with_response_certification
时,可以通过 DefaultResponseCertification
枚举的 certified_response_headers
关联函数提供任意数量的响应头。提供的数组也可以为空。如果数组为空,或者没有调用关联函数,则不会认证任何响应头。
例如,只认证响应正文和状态码
use ic_http_certification::DefaultCelBuilder;
let cel_expr = DefaultCelBuilder::response_only_certification().build();
这也可以更明确地完成
use ic_http_certification::{DefaultCelBuilder, DefaultResponseCertification};
let cel_expr = DefaultCelBuilder::response_only_certification()
.with_response_certification(DefaultResponseCertification::certified_response_headers(vec![]))
.build();
当同时使用 DefaultCelBuilder::response_only_certification
和 DefaultCelBuilder::full_certification
时,也适用同样的情况
use ic_http_certification::DefaultCelBuilder;
let cel_expr = DefaultCelBuilder::full_certification()
.with_request_headers(vec!["Accept", "Accept-Encoding", "If-None-Match"])
.with_request_query_parameters(vec!["foo", "bar", "baz"])
.build();
要完全跳过响应认证,必须完全跳过认证。认证请求而不认证响应是没有意义的。
跳过认证
要完全跳过认证,请使用 skip_certification
,例如
use ic_http_certification::DefaultCelBuilder;
let cel_expr = DefaultCelBuilder::skip_certification();
跳过认证看起来可能有些反直觉,但并非总是可能对请求和响应对进行认证。例如,一个会为每个用户返回不同数据的canister方法就不容易进行认证。
通常,这些请求过去通过raw
ICP URL进行路由,但这很危险,因为raw
URL允许任何响应副本决定是否需要认证。相比之下,通过使用非raw
URL的上述方法跳过认证,副本将不再能够决定是否需要认证,而是由canister本身和共识结果来决定。
创建认证
一旦定义了CEL表达式,就可以将其与HttpRequest
和HttpResponse
结合使用来创建HttpCertification
结构体的实例。HttpCertification
结构体有三个相关函数
- 相关的
full
函数用于在认证中包含HttpRequest
和相应的HttpResponse
。 - 相关的
response_only
函数用于在认证中仅包含HttpResponse
,并排除相应的HttpRequest
。 - 相关的
skip
函数用于完全跳过认证。
完整认证
执行完整认证需要从DefaultCelBuilder::full_certification
创建的CEL表达式,以及一个HttpRequest
和一个HttpResponse,以及可选的预计算响应体哈希。
例如
use ic_http_certification::{HttpCertification, HttpRequest, HttpResponse, DefaultCelBuilder, DefaultResponseCertification};
let cel_expr = DefaultCelBuilder::full_certification()
.with_request_headers(vec!["Accept", "Accept-Encoding", "If-None-Match"])
.with_request_query_parameters(vec!["foo", "bar", "baz"])
.with_response_certification(DefaultResponseCertification::certified_response_headers(vec![
"Cache-Control",
"ETag",
]))
.build();
let request = HttpRequest {
method: "GET".to_string(),
url: "/index.html?foo=a&bar=b&baz=c".to_string(),
headers: vec![
("Accept".to_string(), "application/json".to_string()),
("Accept-Encoding".to_string(), "gzip".to_string()),
("If-None-Match".to_string(), "987654321".to_string()),
],
body: vec![],
};
let response = HttpResponse {
status_code: 200,
headers: vec![
("Cache-Control".to_string(), "no-cache".to_string()),
("ETag".to_string(), "123456789".to_string()),
("IC-CertificateExpression".to_string(), cel_expr.to_string()),
],
body: vec![1, 2, 3, 4, 5, 6],
upgrade: None,
};
let certification = HttpCertification::full(&cel_expr, &request, &response, None);
仅响应认证
执行仅响应认证需要从DefaultCelBuilder::response_only_certification
创建的CEL表达式,以及一个HttpResponse,以及可选的预计算响应体哈希。
例如
use ic_http_certification::{HttpCertification, HttpResponse, DefaultCelBuilder, DefaultResponseCertification};
let cel_expr = DefaultCelBuilder::response_only_certification()
.with_response_certification(DefaultResponseCertification::certified_response_headers(vec![
"Cache-Control",
"ETag",
]))
.build();
let response = HttpResponse {
status_code: 200,
headers: vec![
("Cache-Control".to_string(), "no-cache".to_string()),
("ETag".to_string(), "123456789".to_string()),
("IC-CertificateExpression".to_string(), cel_expr.to_string()),
],
body: vec![1, 2, 3, 4, 5, 6],
upgrade: None,
};
let certification = HttpCertification::response_only(&cel_expr, &response, None).unwrap();
跳过认证
跳过认证不需要定义显式的CEL表达式,因为它始终是相同的。
例如
use ic_http_certification::HttpCertification;
let certification = HttpCertification::skip();
创建HTTP认证树
定义树路径
可以使用HttpCertificationPath
结构体定义树路径,并分为两种类型:wildcard
(通配符)和exact
(精确)。这两种类型的路径可以以或不以反斜杠结尾,但请注意,以反斜杠结尾的路径与不以反斜杠结尾的路径是不同的路径,并且它们将由树以这种方式处理。
通配符路径可用于匹配请求URL的子路径。这对于404响应、回退或重写可能很有用。它们使用wildcard
相关函数定义。
在这个例子中,使用此路径进入树的认证将适用于以/js
开头的任何请求URL,除非在树中存在更具体的路径(例如/js/example.js
)。
use ic_http_certification::HttpCertificationPath;
let path = HttpCertificationPath::wildcard("/js");
精确路径用于匹配整个请求URL。以反斜杠结尾的精确路径指向文件系统目录,而不以反斜杠结尾的路径指向单个文件。两者都是认证树中的单独路径,并将完全独立处理。
在这个例子中,具有此路径的证书将仅对精确的请求URL /js/example.js
有效。
use ic_http_certification::HttpCertificationPath;
let path = HttpCertificationPath::exact("/js/example.js");
使用HTTP证书树
HttpCertificationTree
可以很容易地通过 Default
特性进行初始化,并且可以使用 HttpCertificationTreeEntry
结构体向树中添加条目、从树中删除条目或由树生成见证。 HttpCertificationTreeEntry
需要一个 HttpCertification
和一个 HttpCertificationPath
。
例如
use ic_http_certification::{HttpCertification, HttpRequest, HttpResponse, DefaultCelBuilder, DefaultResponseCertification, HttpCertificationTree, HttpCertificationTreeEntry, HttpCertificationPath};
let cel_expr = DefaultCelBuilder::full_certification()
.with_request_headers(vec!["Accept", "Accept-Encoding", "If-None-Match"])
.with_request_query_parameters(vec!["foo", "bar", "baz"])
.with_response_certification(DefaultResponseCertification::certified_response_headers(vec![
"Cache-Control",
"ETag",
]))
.build();
let request = HttpRequest {
method: "GET".to_string(),
url: "/index.html?foo=a&bar=b&baz=c".to_string(),
headers: vec![
("Accept".to_string(), "application/json".to_string()),
("Accept-Encoding".to_string(), "gzip".to_string()),
("If-None-Match".to_string(), "987654321".to_string()),
],
body: vec![],
};
let response = HttpResponse {
status_code: 200,
headers: vec![
("Cache-Control".to_string(), "no-cache".to_string()),
("ETag".to_string(), "123456789".to_string()),
("IC-CertificateExpression".to_string(), cel_expr.to_string()),
],
body: vec![1, 2, 3, 4, 5, 6],
upgrade: None,
};
let request_url = "/example.json";
let path = HttpCertificationPath::exact(request_url);
let certification = HttpCertification::full(&cel_expr, &request, &response, None).unwrap();
let mut http_certification_tree = HttpCertificationTree::default();
let entry = HttpCertificationTreeEntry::new(&path, &certification);
// insert the entry into the tree
http_certification_tree.insert(&entry);
// generate a witness for this entry in the tree
let witness = http_certification_tree.witness(&entry, request_url);
// delete the entry from the tree
http_certification_tree.delete(&entry);
直接创建CEL表达式
要定义一个CEL表达式,从 CelExpression
枚举开始。此枚举提供了一组变体,可以用于定义ICP HTTP网关支持的CEL表达式类型。目前仅支持一个变体,称为“默认”证书表达式,但随着HTTP证书协议的演变,未来可能还会添加更多。
当认证请求时
-
请求正文和方法总是被认证。
-
要证书请求头和查询参数,请使用
DefaultRequestCertification
结构体的headers
和query_paramters
字段。这两个字段都接受一个str
切片作为参数。
当认证响应时
-
响应正文和状态码总是被认证。
-
要证书响应头,请使用
DefaultResponseCertification
枚举的certified_response_headers
相关函数。或者要证书所有响应头(带有一些排除项),请使用DefaultResponseCertification
枚举的response_header_exclusions
相关函数。这两个相关函数都接受一个str
切片作为参数。
注意,以下提供的示例CEL表达式是为了可读性而格式化的。由 CelExpression::to_string
和 create_cel_expr
产生的实际CEL表达式是压缩的。压缩的CEL表达式更受欢迎,因为它更紧凑,从而减少了HTTP网关验证证书时的有效负载,并加快了评估时间,但格式化的版本也可以接受。
完全认证的请求/响应对
要定义一个完全证书的请求和响应对,包括请求头、查询参数和响应头
use std::borrow::Cow;
use ic_http_certification::cel::{CelExpression, DefaultCelExpression, DefaultFullCelExpression, DefaultRequestCertification, DefaultResponseCertification};
let cel_expr = CelExpression::Default(DefaultCelExpression::Full(
DefaultFullCelExpression {
request: DefaultRequestCertification::new(
vec!["Accept", "Accept-Encoding", "If-None-Match"],
vec!["foo", "bar", "baz"],
),
response: DefaultResponseCertification::certified_response_headers(vec![
"ETag",
"Cache-Control",
]),
}));
这将产生以下CEL表达式
default_certification (
ValidationArgs {
request_certification: RequestCertification {
certified_request_headers: ["Accept", "Accept-Encoding", "If-None-Match"],
certified_query_parameters: ["foo", "bar", "baz"]
},
response_certification: ResponseCertification {
certified_response_headers: ResponseHeaderList {
headers: [
"ETag",
"Cache-Control"
]
}
}
}
)
部分认证的请求
可以通过 DefaultRequestCertification
结构体的 headers
和 query_parameters
字段提供任意数量的请求头或查询参数,并且两者都可以是空数组。如果 headers
字段为空,则不会证书任何请求头。同样对于 query_parameters
字段,如果它为空,则不会证书任何查询参数。如果两者都为空,则只证书请求体和方法。
例如,只认证请求正文和方法
use std::borrow::Cow;
use ic_http_certification::cel::{CelExpression, DefaultCelExpression, DefaultFullCelExpression, DefaultRequestCertification, DefaultResponseCertification};
let cel_expr = CelExpression::Default(DefaultCelExpression::Full(
DefaultFullCelExpression {
request: DefaultRequestCertification::new(
vec![],
vec![],
),
response: DefaultResponseCertification::certified_response_headers(vec![
"ETag",
"Cache-Control",
]),
}));
这将产生以下CEL表达式
default_certification (
ValidationArgs {
request_certification: RequestCertification {
certified_request_headers: [],
certified_query_parameters: []
},
response_certification: ResponseCertification {
certified_response_headers: ResponseHeaderList {
headers: [
"ETag",
"Cache-Control"
]
}
}
}
)
跳过请求认证
可以通过使用 DefaultCelExpression
结构体的 ResponseOnly
变体来完全跳过请求证书。
例如
use std::borrow::Cow;
use ic_http_certification::cel::{CelExpression, DefaultCelExpression, DefaultResponseOnlyCelExpression, DefaultResponseCertification};
let cel_expr = CelExpression::Default(DefaultCelExpression::ResponseOnly(
DefaultResponseOnlyCelExpression {
response: DefaultResponseCertification::certified_response_headers(vec![
"ETag",
"Cache-Control",
]),
}));
这将产生以下CEL表达式
default_certification (
ValidationArgs {
no_request_certification: Empty {},
response_certification: ResponseCertification {
certified_response_headers: ResponseHeaderList {
headers: [
"ETag",
"Cache-Control"
]
}
}
}
)
部分认证的响应
与请求证书类似,可以通过 DefaultResponseCertification
枚举的 certified_response_headers
相关函数提供任意数量的响应头,也可以是空数组。如果数组为空,则不会证书任何响应头。
例如
use std::borrow::Cow;
use ic_http_certification::cel::{CelExpression, DefaultCertification, DefaultRequestCertification, DefaultResponseCertification};
let cel_expr = CelExpression::DefaultCertification(Some(DefaultCertification {
request: DefaultRequestCertification::new(
vec!["Accept", "Accept-Encoding", "If-None-Match"],
vec!["foo", "bar", "baz"],
),
response_certification: DefaultResponseCertification::certified_response_headers(vec![]),
}));
这将产生以下CEL表达式
default_certification (
ValidationArgs {
request_certification: RequestCertification {
certified_request_headers: ["Accept", "Accept-Encoding", "If-None-Match"],
certified_query_parameters: ["foo", "bar", "baz"]
},
response_certification: ResponseCertification {
certified_response_headers: ResponseHeaderList {
headers: []
}
}
}
)
如果使用 response_header_exclusions
相关函数,则空数组将证书 所有 响应头。例如
use std::borrow::Cow;
use ic_http_certification::cel::{CelExpression, DefaultCelExpression, DefaultFullCelExpression, DefaultRequestCertification, DefaultResponseCertification};
let cel_expr = CelExpression::Default(DefaultCelExpression::Full(
DefaultFullCelExpression {
request: DefaultRequestCertification::new(
vec!["Accept", "Accept-Encoding", "If-None-Match"],
vec!["foo", "bar", "baz"],
),
response: DefaultResponseCertification::response_header_exclusions(vec![]),
}));
这将产生以下CEL表达式
default_certification (
ValidationArgs {
request_certification: RequestCertification {
certified_request_headers: ["Accept", "Accept-Encoding", "If-None-Match"],
certified_query_parameters: ["foo", "bar", "baz"]
},
response_certification: ResponseCertification {
response_header_exclusions: ResponseHeaderList {
headers: []
}
}
}
)
要完全跳过响应证书,则必须完全跳过证书。证书请求而不证书响应是没有用的。
跳过认证
要完全跳过证书
use ic_http_certification::cel::{CelExpression, DefaultCelExpression};
let cel_expr = CelExpression::Default(DefaultCelExpression::Skip);
这将产生以下CEL表达式
default_certification (
ValidationArgs {
no_certification: Empty {}
}
)
依赖
~2–12MB
~101K SLoC