Skip to content

Commit 575aca5

Browse files
committed
Add matrix testing for authorizer variants
Run VSS integration tests against multiple server authorizer configurations (noop, sig, jwt). Each matrix job builds the vss-server with the appropriate flags and passes the corresponding cfg flag to the test runner. - Use release builds for vss-server - Pass authorizer-specific cfg flags for test differentiation - Add `test_utils` feature with JWT and signature-based header generation - Add RSA keypair fixtures for JWT signing - Update VSS tests to use auth-aware headers
1 parent 80fb49b commit 575aca5

File tree

8 files changed

+203
-23
lines changed

8 files changed

+203
-23
lines changed

.github/workflows/vss-integration.yml

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ concurrency:
99
jobs:
1010
build-and-test:
1111
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
authorizer: [noop, sig, jwt]
1215

1316
services:
1417
postgres:
@@ -39,10 +42,22 @@ jobs:
3942
- name: Build and Deploy VSS Server
4043
run: |
4144
cd vss-server/rust
42-
cargo run server/vss-server-config.toml&
43-
- name: Run VSS Integration tests
45+
if [ "${{ matrix.authorizer }}" == "noop" ]; then
46+
RUSTFLAGS="--cfg noop_authorizer" cargo run --release --no-default-features server/vss-server-config.toml &
47+
elif [ "${{ matrix.authorizer }}" == "jwt" ]; then
48+
# Public key for testing purposes solely.
49+
export VSS_JWT_RSA_PEM=$(cat ../../ldk-node/tests/fixtures/vss_jwt_rsa_pub.pem)
50+
cargo run --release server/vss-server-config.toml &
51+
elif [ "${{ matrix.authorizer }}" == "sig" ]; then
52+
cargo run --release server/vss-server-config.toml &
53+
else
54+
echo "Unknown authorizer: ${{ matrix.authorizer }}" && exit 1
55+
fi
56+
57+
- name: Run VSS Integration tests for ${{ matrix.authorizer }}
4458
run: |
4559
cd ldk-node
4660
export TEST_VSS_BASE_URL="http://localhost:8080/vss"
47-
RUSTFLAGS="--cfg vss_test" cargo test io::vss_store
48-
RUSTFLAGS="--cfg vss_test --cfg cycle_tests" cargo test --test integration_tests_vss
61+
RUSTFLAGS="--cfg vss_test --cfg ${{ matrix.authorizer }}_auth_test" cargo test --features test_utils io::vss_store
62+
RUSTFLAGS="--cfg vss_test --cfg cycle_tests --cfg ${{ matrix.authorizer }}_auth_test" cargo test \
63+
--features test_utils --test integration_tests_vss

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ panic = 'abort' # Abort on panic
2525

2626
[features]
2727
default = []
28+
test_utils = ["jsonwebtoken", "lightning/_test_utils"]
2829

2930
[dependencies]
3031
#lightning = { version = "0.2.0", features = ["std"] }
@@ -79,6 +80,7 @@ vss-client = { package = "vss-client-ng", version = "0.4" }
7980
prost = { version = "0.11.6", default-features = false}
8081
#bitcoin-payment-instructions = { version = "0.6" }
8182
bitcoin-payment-instructions = { git = "https://github.com/tnull/bitcoin-payment-instructions", rev = "e4d519b95b26916dc6efa22f8f1cc11a818ce7a7" }
83+
jsonwebtoken = { version = "9.3.0", optional = true, default-features = false, features = ["use_pem"] }
8284

8385
[target.'cfg(windows)'.dependencies]
8486
winapi = { version = "0.3", features = ["winbase"] }
@@ -123,6 +125,9 @@ check-cfg = [
123125
"cfg(cln_test)",
124126
"cfg(lnd_test)",
125127
"cfg(cycle_tests)",
128+
"cfg(noop_auth_test)",
129+
"cfg(jwt_auth_test)",
130+
"cfg(sig_auth_test)",
126131
]
127132

128133
[[bench]]

src/io/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88
//! Objects and traits for data persistence.
99
1010
pub mod sqlite_store;
11-
#[cfg(test)]
11+
#[cfg(any(test, feature = "test_utils"))]
1212
pub(crate) mod test_utils;
13+
14+
#[cfg(feature = "test_utils")]
15+
pub use test_utils::get_fixed_headers;
16+
1317
pub(crate) mod utils;
1418
pub mod vss_store;
1519

src/io/test_utils.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
66
// accordance with one or both of these licenses.
77

8+
#![allow(dead_code)]
9+
810
use std::collections::{hash_map, HashMap};
911
use std::future::Future;
1012
use std::panic::RefUnwindSafe;
@@ -350,3 +352,111 @@ pub(crate) fn do_test_store<K: KVStoreSync + Sync>(store_0: &K, store_1: &K) {
350352
// Make sure everything is persisted as expected after close.
351353
check_persisted_data!(persister_0_max_pending_updates * 2 * EXPECTED_UPDATES_PER_PAYMENT + 1);
352354
}
355+
356+
#[cfg(all(feature = "test_utils", jwt_auth_test))]
357+
mod jwt_auth {
358+
use super::*;
359+
360+
use std::time::SystemTime;
361+
362+
use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
363+
use serde::{Deserialize, Serialize};
364+
365+
// Private key for testing purposes solely.
366+
const VSS_PRIVATE_PEM: &str = include_str!("../../tests/fixtures/vss_jwt_rsa_prv.pem");
367+
368+
#[derive(Serialize, Deserialize)]
369+
struct TestClaims {
370+
sub: String,
371+
iat: i64,
372+
nbf: i64,
373+
exp: i64,
374+
}
375+
376+
pub fn generate_test_jwt(private_pem: &str, user_id: &str) -> String {
377+
let now =
378+
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() as i64;
379+
380+
let claims =
381+
TestClaims { sub: user_id.to_owned(), iat: now, nbf: now, exp: now + (60 * 10) };
382+
383+
let encoding_key = EncodingKey::from_rsa_pem(private_pem.as_bytes())
384+
.expect("Failed to create EncodingKey");
385+
386+
encode(&Header::new(Algorithm::RS256), &claims, &encoding_key).unwrap()
387+
}
388+
389+
pub fn get_fixed_headers() -> HashMap<String, String> {
390+
let token = generate_test_jwt(VSS_PRIVATE_PEM, "test");
391+
let mut headers = HashMap::new();
392+
headers.insert("Authorization".to_string(), format!("Bearer {}", token));
393+
return headers;
394+
}
395+
}
396+
397+
#[cfg(all(feature = "test_utils", sig_auth_test))]
398+
mod sig_auth {
399+
use super::*;
400+
401+
use std::time::SystemTime;
402+
use std::time::UNIX_EPOCH;
403+
404+
use bitcoin::hashes::sha256::Hash;
405+
use bitcoin::hashes::Hash as _;
406+
use bitcoin::secp256k1::{self, SecretKey};
407+
408+
use crate::hex_utils;
409+
410+
// Must match vss-server's SignatureAuthorizer constant.
411+
// See: https://github.com/lightningdevkit/vss-server/blob/main/rust/auth-impls/src/signature.rs#L21
412+
const SIGNING_CONSTANT: &'static [u8] =
413+
b"VSS Signature Authorizer Signing Salt Constant..................";
414+
415+
fn build_auth_token(secret_key: &SecretKey) -> String {
416+
let secp = secp256k1::Secp256k1::new();
417+
let pubkey = secret_key.public_key(&secp);
418+
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
419+
420+
let mut bytes_to_sign = Vec::new();
421+
bytes_to_sign.extend_from_slice(SIGNING_CONSTANT);
422+
bytes_to_sign.extend_from_slice(&pubkey.serialize());
423+
bytes_to_sign.extend_from_slice(format!("{now}").as_bytes());
424+
425+
let hash = Hash::hash(&bytes_to_sign);
426+
let msg = secp256k1::Message::from_digest(hash.to_byte_array());
427+
let sig = secp.sign_ecdsa(&msg, &secret_key);
428+
429+
format!("{pubkey:x}{}{now}", hex_utils::to_string(&sig.serialize_compact()))
430+
}
431+
432+
pub fn get_fixed_headers() -> HashMap<String, String> {
433+
let secret_key = SecretKey::from_slice(&[42; 32]).unwrap();
434+
let token = build_auth_token(&secret_key);
435+
let mut headers = HashMap::new();
436+
headers.insert("Authorization".to_string(), token);
437+
return headers;
438+
}
439+
}
440+
441+
/// Returns a hashmap of fixed headers, where, depending on configuration,
442+
/// corresponds to valid headers for no-op, signature-based, or jwt-based
443+
/// authorizers on vss-server.
444+
pub fn get_fixed_headers() -> HashMap<String, String> {
445+
#[cfg(noop_auth_test)]
446+
{
447+
HashMap::new()
448+
}
449+
450+
#[cfg(all(jwt_auth_test, feature = "test_utils"))]
451+
{
452+
jwt_auth::get_fixed_headers()
453+
}
454+
455+
#[cfg(all(feature = "test_utils", sig_auth_test))]
456+
{
457+
sig_auth::get_fixed_headers()
458+
}
459+
460+
#[cfg(not(any(noop_auth_test, all(jwt_auth_test, feature = "test_utils"), sig_auth_test)))]
461+
HashMap::new()
462+
}

src/io/vss_store.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -962,14 +962,12 @@ impl VssStoreBuilder {
962962
#[cfg(test)]
963963
#[cfg(vss_test)]
964964
mod tests {
965-
use std::collections::HashMap;
966-
967965
use rand::distr::Alphanumeric;
968966
use rand::{rng, Rng, RngCore};
969967
use vss_client::headers::FixedHeaders;
970968

971969
use super::*;
972-
use crate::io::test_utils::do_read_write_remove_list_persist;
970+
use crate::io::test_utils::{do_read_write_remove_list_persist, get_fixed_headers};
973971

974972
#[test]
975973
fn vss_read_write_remove_list_persist() {
@@ -978,9 +976,14 @@ mod tests {
978976
let rand_store_id: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
979977
let mut vss_seed = [0u8; 32];
980978
rng.fill_bytes(&mut vss_seed);
981-
let header_provider = Arc::new(FixedHeaders::new(HashMap::new()));
982-
let vss_store =
983-
VssStore::new(vss_base_url, rand_store_id, vss_seed, header_provider).unwrap();
979+
let header_provider = get_fixed_headers();
980+
let vss_store = VssStore::new(
981+
vss_base_url,
982+
rand_store_id,
983+
vss_seed,
984+
Arc::new(FixedHeaders::new(header_provider)),
985+
)
986+
.unwrap();
984987
do_read_write_remove_list_persist(&vss_store);
985988
}
986989

@@ -991,9 +994,14 @@ mod tests {
991994
let rand_store_id: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
992995
let mut vss_seed = [0u8; 32];
993996
rng.fill_bytes(&mut vss_seed);
994-
let header_provider = Arc::new(FixedHeaders::new(HashMap::new()));
995-
let vss_store =
996-
VssStore::new(vss_base_url, rand_store_id, vss_seed, header_provider).unwrap();
997+
let header_provider = get_fixed_headers();
998+
let vss_store = VssStore::new(
999+
vss_base_url,
1000+
rand_store_id,
1001+
vss_seed,
1002+
Arc::new(FixedHeaders::new(header_provider)),
1003+
)
1004+
.unwrap();
9971005

9981006
do_read_write_remove_list_persist(&vss_store);
9991007
drop(vss_store)

tests/fixtures/vss_jwt_rsa_prv.pem

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCuIYJJ1dzNmuct
3+
fzj+W4EeJXic/A6gkHJOS7MHqAqOqMg49aZfOj6y4kmhU3/fal5OccJ4299ohSnJ
4+
rMKwWoL5YHYD1Y742Ez/trpjETTV6CwjhLovCVtWbZrjivUnYb1fEeGoQ8p+COOK
5+
yMM3s/iQVlhzqo86kZ9fcaGFizrykcRimSDOuN3V/AWcB3fcKufLS76z6ysNTwiK
6+
X/wnBUEtofALIV1i9rcyFpumRGi/9lo80eVcC1TErxA3C5IEwJQ18XCatM2Hf2xi
7+
4D35JDK80/0MY3u7TJgYrFpaThuKd3lnSSPx51enJUEaNgXuUNvTdKkFEn1ASRi5
8+
LNbNLKhXAgMBAAECggEATokCcDaqjXjNxzFYDTBL/cK8sWDlX/mF9FYj+tIJYOoy
9+
063HSa/FU3zH5KD6TVN2ET8xjLzt+AAHJtRqQouwArVExNnuz8EOiU5qpf++qrM6
10+
JRLZvhkkPsjUUMf9ZbOpa1VvRyq8CzgLGC8QDPF4q/ClmBVW3/2JucxQIyD2hywE
11+
MDc8on3nEvCzMGSxUm2EIQn30iF8W2WZyNLk4RK+UUOUHGsFUN0PRfsHFQCAar0V
12+
ZnSxlJgDKETXGjkX8G+H+EyPd+oFH3QtZFTzxk6ghLEZV1vetqMI9WEFnMLKfmFW
13+
tBGYuGcq+72G5mlJqlnix69A93SJziymlS/QWgKrQQKBgQDki5E++NoUyslKMZca
14+
yYWokqxs6T0y15Cig7lDuRfcfDNDjbf9AkRpUZ0O9VESWhCiRG0NKUbHVlAlAb1h
15+
EbWPc2QCs+Tzt2uqeavohbVb9NMsEFSKgx8oUuIisAgUo9Xui+80A+7s54PoeqnT
16+
ULE7KYCbs5oMJjGwpuiolYFYpwKBgQDDDIsEyxdloE6MxpwRshYKzOWR0ADeKuIf
17+
kwEpF1ap5Ng0PdAvIfQS/aRpeb/Tx8Uv2+YMTwX2RyvgbMBFjJa3TEqagFcS4Chu
18+
tabLuyjMq9LlUQdsh4xqfGFF3vaT9coDpbzNvHcmLkK6gnrbnG6mG06xFybqBQ8I
19+
k8dPpnN40QKBgQDUBqc5RKUNpRQZQOhucYcOXQSaBchA4rvMCWhW6+C3LIJiqZeH
20+
ohLVomGS/wO3gtbrs494JlMDm4++xV5sL4HBE8w0tbAyanf4L+jMTz9xkDBZMM09
21+
s2e0gTBJ/gWBIH3YUPoZx4xhPGejxijHYpUJzfcCfBzuKIDw4ef2fr0BAQKBgEcB
22+
X/KExKW4cCALhXFjtWaFJOWqJUa7scnwyDFfT6tVpeeOwSUHZUUslRfYvJ6qUPyV
23+
PvAoLHF1g2GV9YDcJ1nfKiGIqyox9EYpVuk/3yBzRLk6gEtgJRv236qB+p3uknY1
24+
dcAn5fA+Uwh2y6b7EcTimAkb9oym/swOkDZM0CihAoGBAJ9W91zU9H5rMBwiWMKP
25+
ppReTRxxN8oJNk0Cirxr58YHQNXtGWkno316/SPJZzML29c8+QAoJ8uatwzaZzt9
26+
S6Cq2bYEyO7LPqs3SLRrK802QGvV7Y4P2rX4pjYOMM9qddOnT+qkVyyqVguazfDJ
27+
xrhmGsrdBu3nBkwwpCBps6KZ
28+
-----END PRIVATE KEY-----

tests/fixtures/vss_jwt_rsa_pub.pem

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAriGCSdXczZrnLX84/luB
3+
HiV4nPwOoJByTkuzB6gKjqjIOPWmXzo+suJJoVN/32peTnHCeNvfaIUpyazCsFqC
4+
+WB2A9WO+NhM/7a6YxE01egsI4S6LwlbVm2a44r1J2G9XxHhqEPKfgjjisjDN7P4
5+
kFZYc6qPOpGfX3GhhYs68pHEYpkgzrjd1fwFnAd33Crny0u+s+srDU8Iil/8JwVB
6+
LaHwCyFdYva3MhabpkRov/ZaPNHlXAtUxK8QNwuSBMCUNfFwmrTNh39sYuA9+SQy
7+
vNP9DGN7u0yYGKxaWk4bind5Z0kj8edXpyVBGjYF7lDb03SpBRJ9QEkYuSzWzSyo
8+
VwIDAQAB
9+
-----END PUBLIC KEY-----

tests/integration_tests_vss.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
66
// accordance with one or both of these licenses.
77

8-
#![cfg(vss_test)]
8+
#![cfg(all(vss_test, feature = "test_utils"))]
99

1010
mod common;
1111

12-
use std::collections::HashMap;
12+
use rand::{rng, Rng};
1313

1414
use ldk_node::entropy::NodeEntropy;
15+
use ldk_node::io::get_fixed_headers;
1516
use ldk_node::Builder;
16-
use rand::{rng, Rng};
1717

1818
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1919
async fn channel_full_cycle_with_vss_store() {
@@ -29,7 +29,7 @@ async fn channel_full_cycle_with_vss_store() {
2929
config_a.node_entropy,
3030
vss_base_url.clone(),
3131
"node_1_store".to_string(),
32-
HashMap::new(),
32+
get_fixed_headers(),
3333
)
3434
.unwrap();
3535
node_a.start().unwrap();
@@ -43,7 +43,7 @@ async fn channel_full_cycle_with_vss_store() {
4343
config_b.node_entropy,
4444
vss_base_url,
4545
"node_2_store".to_string(),
46-
HashMap::new(),
46+
get_fixed_headers(),
4747
)
4848
.unwrap();
4949
node_b.start().unwrap();
@@ -84,7 +84,7 @@ async fn vss_v0_schema_backwards_compatibility() {
8484
.build_with_vss_store_and_fixed_headers(
8585
vss_base_url.clone(),
8686
store_id.clone(),
87-
HashMap::new(),
87+
get_fixed_headers(),
8888
)
8989
.unwrap();
9090

@@ -123,7 +123,7 @@ async fn vss_v0_schema_backwards_compatibility() {
123123
node_entropy,
124124
vss_base_url,
125125
store_id,
126-
HashMap::new(),
126+
get_fixed_headers(),
127127
)
128128
.unwrap();
129129

@@ -158,12 +158,13 @@ async fn vss_node_restart() {
158158
builder.set_network(bitcoin::Network::Regtest);
159159
builder.set_storage_dir_path(storage_path.clone());
160160
builder.set_chain_source_esplora(esplora_url.clone(), None);
161+
161162
let node = builder
162163
.build_with_vss_store_and_fixed_headers(
163164
node_entropy,
164165
vss_base_url.clone(),
165166
store_id.clone(),
166-
HashMap::new(),
167+
get_fixed_headers(),
167168
)
168169
.unwrap();
169170

@@ -197,7 +198,7 @@ async fn vss_node_restart() {
197198
node_entropy,
198199
vss_base_url,
199200
store_id,
200-
HashMap::new(),
201+
get_fixed_headers(),
201202
)
202203
.unwrap();
203204

0 commit comments

Comments
 (0)