Skip to content

Commit bf46a74

Browse files
committed
kbs: add http config to fetch token verification key for admin
In dev cases, this patch allows users to configure KBS to get admin token verification public key from HTTP endpoint. Signed-off-by: Xynnn007 <xynnn@linux.alibaba.com>
1 parent 1cbc9ef commit bf46a74

7 files changed

Lines changed: 53 additions & 14 deletions

File tree

kbs/docs/admin.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ This lets you keep identity issuance external while keeping KBS-side authorizati
7979
`[admin.authentication.bearer_jwt]` accepts:
8080

8181
- `identity_providers` (array): list of trusted identity providers
82+
- `insecure_public_key_from_uri` (optional bool, default `false`): allow fetching`public_key_uri` and `jwk_set_uri` over `http://`
8283

8384
Each `identity_providers` entry:
8485

@@ -92,5 +93,6 @@ Each entry must provide at least one of `public_key_uri` or `jwk_set_uri`.
9293
Supported source formats:
9394

9495
- `https://...` (remote fetch)
96+
- `http://...` (remote fetch, only when `insecure_public_key_from_uri=true`)
9597
- `file://...` (local file URI)
9698
- local path without scheme (for example `./keys/admin.pem`)

kbs/docs/config.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,18 +192,24 @@ For `authorization_mode = "AuthenticatedAuthorization"`, configure:
192192
| Property | Type | Description | Required | Default |
193193
|----------|------|-------------|----------|---------|
194194
| `identity_providers` | Array | Trusted issuer entries for JWT verification | No | Empty |
195+
| `insecure_public_key_from_uri` | Boolean | Allow loading `public_key_uri` via plaintext `http://` | No | `false` |
195196

196197
Each `identity_providers` item:
197198

198199
| Property | Type | Description | Required |
199200
|----------|------|-------------|----------|
200201
| `issuer` | String | Expected JWT `iss` value (leave empty to skip issuer check) | No |
201202
| `audience` | String | Expected JWT `aud` value (leave empty to skip audience check) | No |
202-
| `public_key_uri` | String | PEM public key source (`https://`, `file://`, local path) | No* |
203+
| `public_key_uri` | String | PEM public key source (`https://`, `file://`, local path, or `http://` when `insecure_public_key_from_uri=true`) | No* |
203204
| `jwk_set_uri` | String | JWKS source (`https://`, `file://`, or local path) | No* |
204205

205206
\* At least one of `public_key_uri` or `jwk_set_uri` is required.
206207

208+
> [!NOTE]
209+
> When `insecure_public_key_from_uri=false` the KBS will allow to fetch public keys from a non-HTTPS server.
210+
> Ensure this only happens in controlled network environment or dev case, or there will be
211+
> security risk.
212+
207213
JWTs used for admin access **MUST** include a `role` claim.
208214

209215
`regex_acl` properties:

kbs/src/admin/authentication/bearer_jwt.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@ use crate::crypto::jwt::JwtVerifier;
1818
#[serde(default, deny_unknown_fields)]
1919
pub struct BearerJwtConfig {
2020
pub identity_providers: Vec<IssuerConfig>,
21+
/// Allow loading admin PEM keys from plaintext HTTP sources.
22+
/// Keep disabled by default and only enable in controlled environments.
23+
pub insecure_public_key_from_uri: bool,
2124
}
2225

2326
/// Issuer config used to verify admin JWT tokens.
2427
///
25-
/// - `public_key_uri`: a PEM file source (`https://`, `file://`, local path)
28+
/// - `public_key_uri`: a PEM file source (`https://`, `file://`, local path,
29+
/// or `http://` when `insecure_public_key_from_uri=true`)
2630
/// - `jwk_set_uri`: a JWKS source (https://, file:// or local path)
2731
/// - `issuer`: the issuer of the JWT token. If given, This field will be checked when a token is verified successfully
2832
/// with given public key or JWKS. If the token's issuer is matched, the token will be verified successfully.
@@ -69,6 +73,7 @@ impl BearerJwtTokenVerifier {
6973
&[],
7074
&trusted_pem_public_key_uris,
7175
false,
76+
config.insecure_public_key_from_uri,
7277
)
7378
.await
7479
.map_err(|e| Error::InvalidTokenVerifierConfig(e.to_string()))?;

kbs/src/attestation/intel_trust_authority/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ impl IntelTrustAuthority {
449449
&trusted_certs_paths,
450450
&trusted_pem_public_keys,
451451
true,
452+
false,
452453
)
453454
.await
454455
.context("Failed to initialize token verifier")?;

kbs/src/crypto/jwk.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use reqwest::{get, Url};
77
use serde::Deserialize;
88
use std::fs;
99
use thiserror::Error;
10-
use tracing::info;
10+
use tracing::{info, warn};
1111

1212
pub(crate) const OPENID_CONFIG_URL_SUFFIX: &str = ".well-known/openid-configuration";
1313

@@ -29,19 +29,25 @@ pub(crate) struct OpenIDConfig {
2929
jwks_uri: String,
3030
}
3131

32-
pub async fn read_jwk_from_uri(uri: &str) -> Result<JwkSet, JwksGetError> {
32+
pub async fn read_jwk_from_uri(
33+
uri: &str,
34+
insecure_public_key_from_uri: bool,
35+
) -> Result<JwkSet, JwksGetError> {
3336
let url = Url::parse(uri).map_err(|e| JwksGetError::InvalidSourcePath(e.to_string()))?;
3437
match url.scheme() {
35-
"https" => {
38+
"https" | "http" if insecure_public_key_from_uri => {
3639
let openid_config_url = url
3740
.join(OPENID_CONFIG_URL_SUFFIX)
3841
.map_err(|e| JwksGetError::InvalidSourcePath(e.to_string()))?;
3942

43+
if url.scheme() == "http" {
44+
warn!("Getting OpenID configuration from insecure HTTP source, please ensure the source is trusted or it's only used in a controlled/test environment");
45+
}
46+
4047
info!(
4148
"Getting OpenID configuration from {}",
4249
openid_config_url.as_str()
4350
);
44-
4551
let oidc = get(openid_config_url.as_str())
4652
.await
4753
.map_err(|e| JwksGetError::AccessFailed(e.to_string()))?
@@ -88,7 +94,10 @@ mod tests {
8894
#[case("/does/not/exist/keys.jwks", true)]
8995
#[tokio::test]
9096
async fn test_source_path_validation(#[case] source_path: &str, #[case] expect_error: bool) {
91-
assert_eq!(expect_error, read_jwk_from_uri(source_path).await.is_err())
97+
assert_eq!(
98+
expect_error,
99+
read_jwk_from_uri(source_path, false).await.is_err()
100+
)
92101
}
93102

94103
#[rstest]
@@ -108,7 +117,7 @@ mod tests {
108117
std::fs::write(&jwks_file, json).expect("to get testdata written to tmpdir");
109118

110119
let p = "file://".to_owned() + jwks_file.to_str().expect("to get path as str");
111-
let jwtks = read_jwk_from_uri(&p).await.expect("to get jwks");
120+
let jwtks = read_jwk_from_uri(&p, false).await.expect("to get jwks");
112121
assert_eq!(jwtks.keys.len(), 1);
113122
assert_eq!(jwtks.keys[0].common.key_algorithm, Some(alg));
114123
}

kbs/src/crypto/jwt.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ fn path_to_file_uri(path: &str) -> Result<String> {
3636
}
3737

3838
fn normalize_jwk_set_source(source: &str) -> Result<String> {
39-
if source.starts_with("https://") || source.starts_with("file://") {
39+
if source.starts_with("https://")
40+
|| source.starts_with("http://")
41+
|| source.starts_with("file://")
42+
{
4043
return Ok(source.to_string());
4144
}
4245

@@ -47,12 +50,21 @@ fn normalize_jwk_set_source(source: &str) -> Result<String> {
4750
path_to_file_uri(source)
4851
}
4952

50-
/// Read a PEM public key from a URI (`https://`, `file://`, or local path).
51-
pub(crate) async fn read_pem_public_key_from_uri(uri: &str) -> Result<DecodingKey> {
53+
/// Read a PEM public key from a URI.
54+
///
55+
/// # Arguments
56+
///
57+
/// * `uri` - The URI of the PEM public key.
58+
/// * `allow_insecure_http` - Whether to allow HTTP address as uri.
59+
pub(crate) async fn read_pem_public_key_from_uri(
60+
uri: &str,
61+
allow_insecure_http: bool,
62+
) -> Result<DecodingKey> {
5263
let maybe_url = Url::parse(uri);
5364
let data = if let Ok(url) = maybe_url {
5465
match url.scheme() {
5566
"https" => reqwest::get(uri).await?.bytes().await?.to_vec(),
67+
"http" if allow_insecure_http => reqwest::get(uri).await?.bytes().await?.to_vec(),
5668
"file" => std::fs::read(url.path())?,
5769
_ => {
5870
bail!("unsupported scheme in {uri}");
@@ -127,16 +139,18 @@ impl JwtVerifier {
127139
/// * `trusted_cert_paths` - The paths of the trusted certificates.
128140
/// * `trusted_pem_public_key_uris` - The URIs of the trusted PEM public keys.
129141
/// * `insecure_public_key_from_jwt` - Whether to verify the endorsement of the public key from JWT header.
142+
/// * `insecure_public_key_from_uri` - Whether to allow insecure HTTP address in trusted_pem_public_key_uris.
130143
pub async fn new(
131144
trusted_jwk_set_uris: &[String],
132145
trusted_cert_paths: &[String],
133146
trusted_pem_public_key_uris: &[String],
134147
insecure_public_key_from_jwt: bool,
148+
insecure_public_key_from_uri: bool,
135149
) -> Result<Self> {
136150
let mut trusted_jwk_sets = JwkSet { keys: Vec::new() };
137151
for uri in trusted_jwk_set_uris {
138152
let uri = normalize_jwk_set_source(&uri[..])?;
139-
let mut jwk_set = read_jwk_from_uri(&uri[..]).await?;
153+
let mut jwk_set = read_jwk_from_uri(&uri[..], insecure_public_key_from_uri).await?;
140154
trusted_jwk_sets.keys.append(&mut jwk_set.keys);
141155
}
142156

@@ -156,7 +170,8 @@ impl JwtVerifier {
156170

157171
let mut trusted_pem_public_keys = Vec::new();
158172
for uri in trusted_pem_public_key_uris {
159-
let public_key = read_pem_public_key_from_uri(uri).await?;
173+
let public_key =
174+
read_pem_public_key_from_uri(uri, insecure_public_key_from_uri).await?;
160175
trusted_pem_public_keys.push(public_key);
161176
}
162177

@@ -356,7 +371,7 @@ mod tests {
356371

357372
use crate::crypto::jwt::JwtVerifier;
358373

359-
let verifier = JwtVerifier::new(&[], &[trusted_pem_path.to_string()], &[], false)
374+
let verifier = JwtVerifier::new(&[], &[trusted_pem_path.to_string()], &[], false, false)
360375
.await
361376
.expect("verifier init");
362377
let jwk_json = std::fs::read_to_string(jwk_json_path).expect("read jwk json");

kbs/src/token/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ impl TokenVerifier {
7777
&config.trusted_certs_paths,
7878
&Vec::new(),
7979
config.insecure_key,
80+
false,
8081
)
8182
.await
8283
.map_err(|e| Error::TokenVerifierInitialization { source: e })?;

0 commit comments

Comments
 (0)