Skip to content

Commit fa5a794

Browse files
authored
test: update signature on OpAMP integration tests (#1693)
1 parent 8fdaa0f commit fa5a794

File tree

32 files changed

+242
-33
lines changed

32 files changed

+242
-33
lines changed

agent-control/tests/common/opamp.rs

Lines changed: 93 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::runtime::tokio_runtime;
22
use actix_web::{App, HttpResponse, HttpServer, web};
33
use base64::Engine;
4-
use base64::prelude::BASE64_STANDARD;
4+
use base64::prelude::{BASE64_STANDARD, BASE64_URL_SAFE_NO_PAD};
55
use newrelic_agent_control::opamp::instance_id::InstanceID;
66
use newrelic_agent_control::opamp::remote_config::signature::{
77
ED25519, SIGNATURE_CUSTOM_CAPABILITY, SIGNATURE_CUSTOM_MESSAGE_TYPE, SignatureFields,
@@ -15,6 +15,9 @@ use opamp_client::opamp::proto::{
1515
use opamp_client::operation::instance_uid::InstanceUid;
1616
use prost::Message;
1717
use rcgen::{CertificateParams, KeyPair, PKCS_ED25519, PublicKeyData};
18+
use ring::rand::SystemRandom;
19+
use ring::signature::{Ed25519KeyPair, KeyPair as _};
20+
use serde_json::json;
1821
use std::hash::{DefaultHasher, Hash, Hasher};
1922
use std::path::PathBuf;
2023
use std::sync::Mutex;
@@ -23,13 +26,17 @@ use tempfile::TempDir;
2326
use tokio::task::JoinHandle;
2427

2528
const FAKE_SERVER_PATH: &str = "/opamp-fake-server";
29+
const JWKS_SERVER_PATH: &str = "/jwks";
2630
const CERT_FILE: &str = "server.crt";
2731

2832
/// Represents the state of the FakeServer.
2933
struct ServerState {
3034
agent_state: HashMap<InstanceID, AgentState>,
31-
// Server private key to sign the remote config
32-
key_pair: KeyPair,
35+
// Key pair to sign remote configuration
36+
key_pair: Ed25519KeyPair,
37+
// Use the legacy system (instead of key_pair) to sign remote configuration
38+
use_legacy_signatures: bool,
39+
legacy_key_pair: KeyPair, // TODO: cleanup when no longer used
3340
}
3441

3542
#[derive(Default)]
@@ -43,10 +50,12 @@ struct AgentState {
4350
}
4451

4552
impl ServerState {
46-
fn new(key_pair: KeyPair) -> Self {
53+
fn new(cert_key_pair: KeyPair, use_legacy_signatures: bool) -> Self {
4754
Self {
4855
agent_state: HashMap::new(),
49-
key_pair,
56+
key_pair: generate_key_pair(),
57+
use_legacy_signatures,
58+
legacy_key_pair: cert_key_pair,
5059
}
5160
}
5261
}
@@ -85,22 +94,36 @@ impl FakeServer {
8594
format!("http://localhost:{}{}", self.port, self.path)
8695
}
8796

97+
pub fn jwks_endpoint(&self) -> String {
98+
format!("http://localhost:{}{}", self.port, JWKS_SERVER_PATH)
99+
}
100+
88101
/// Starts and returns new FakeServer in a random port.
89102
pub fn start_new() -> Self {
103+
Self::start_new_with_legacy_signatures(false)
104+
}
105+
106+
/// If `use_legacy_signatures` is set to true, the jwks endpoint is still available but
107+
/// configs are signed using the legacy system.
108+
pub fn start_new_with_legacy_signatures(use_legacy_signatures: bool) -> Self {
90109
// While binding to port 0, the kernel gives you a free ephemeral port.
91110
let listener = net::TcpListener::bind("0.0.0.0:0").unwrap();
92111
let port = listener.local_addr().unwrap().port();
93112

94-
let key_pair = KeyPair::generate_for(&PKCS_ED25519).unwrap();
113+
// Legacy certificate-based key pair
114+
let legacy_key_pair = KeyPair::generate_for(&PKCS_ED25519).unwrap();
95115
let cert = CertificateParams::new(vec!["localhost".to_string()])
96116
.unwrap()
97-
.self_signed(&key_pair)
117+
.self_signed(&legacy_key_pair)
98118
.unwrap();
99119

100120
let tmp_dir = tempfile::tempdir().unwrap();
101121
std::fs::write(tmp_dir.path().join(CERT_FILE), cert.pem()).unwrap();
102122

103-
let state = Arc::new(Mutex::new(ServerState::new(key_pair)));
123+
let state = Arc::new(Mutex::new(ServerState::new(
124+
legacy_key_pair,
125+
use_legacy_signatures,
126+
)));
104127

105128
let handle = tokio_runtime().spawn(Self::run_http_server(listener, state.clone()));
106129

@@ -118,6 +141,7 @@ impl FakeServer {
118141
App::new()
119142
.app_data(web::Data::new(state.clone()))
120143
.service(web::resource(FAKE_SERVER_PATH).to(opamp_handler))
144+
.service(web::resource(JWKS_SERVER_PATH).to(jwks_handler))
121145
})
122146
.listen(listener)
123147
.unwrap_or_else(|err| panic!("Could not bind the HTTP server to the listener: {err}"))
@@ -193,61 +217,96 @@ async fn opamp_handler(state: web::Data<Arc<Mutex<ServerState>>>, req: web::Byte
193217

194218
let mut server_state = state.lock().unwrap();
195219

196-
let state = server_state
220+
let agent_state = server_state
197221
.agent_state
198222
.entry(identifier.clone())
199223
.or_default();
200224

201225
// Check sequence number
202226
let mut flags = ServerToAgentFlags::Unspecified as u64;
203-
if message.sequence_num == (state.sequence_number + 1) {
227+
if message.sequence_num == (agent_state.sequence_number + 1) {
204228
// case 1: first opamp connection start with seq number 1
205229
// case 2: Any valid new sequence number
206-
state.sequence_number += 1;
230+
agent_state.sequence_number += 1;
207231
} else {
208232
flags = ServerToAgentFlags::ReportFullState as u64;
209233
// upon report full state the opamp client will send a new AgentToServer
210234
// increasing the seq number so current should be the valid
211-
state.sequence_number = message.sequence_num;
235+
agent_state.sequence_number = message.sequence_num;
212236
}
213237

214238
if let Some(health) = message.health {
215-
state.health_status = Some(health);
239+
agent_state.health_status = Some(health);
216240
}
217241

218242
if let Some(attributes) = message.agent_description {
219-
state.attributes = attributes;
243+
agent_state.attributes = attributes;
220244
}
221245

222246
if let Some(effective_cfg) = message.effective_config {
223-
state.effective_config = effective_cfg;
247+
agent_state.effective_config = effective_cfg;
224248
}
225249

226250
// Process config status:
227251
// Stop sending the RemoteConfig once we got a RemoteConfigStatus response associated with the hash.
228252
// emulating what FC currently does.
229253
if let Some(cfg_status) = message.remote_config_status {
230-
if state.remote_config.as_ref().is_some_and(|config_response| {
231-
config_response.hash.encode_to_vec() == cfg_status.last_remote_config_hash
232-
}) {
233-
state.remote_config = None;
254+
if agent_state
255+
.remote_config
256+
.as_ref()
257+
.is_some_and(|config_response| {
258+
config_response.hash.encode_to_vec() == cfg_status.last_remote_config_hash
259+
})
260+
{
261+
agent_state.remote_config = None;
234262
}
235-
state.config_status = cfg_status;
263+
agent_state.config_status = cfg_status;
236264
}
237265

238-
let server_to_agent = build_response(
239-
identifier,
240-
state.remote_config.clone(),
241-
&server_state.key_pair,
242-
flags,
243-
);
266+
let remote_config = agent_state.remote_config.clone();
267+
268+
let _ = agent_state; // We need to get rid of the mutable reference before leveraging another immutable.
269+
270+
let (key_pair, key_id) = if server_state.use_legacy_signatures {
271+
(
272+
&Ed25519KeyPair::from_pkcs8(&server_state.legacy_key_pair.serialize_der()).unwrap(),
273+
public_key_fingerprint(&server_state.legacy_key_pair.subject_public_key_info()),
274+
)
275+
} else {
276+
let public_key = server_state.key_pair.public_key().as_ref().to_vec();
277+
(&server_state.key_pair, public_key_fingerprint(&public_key))
278+
};
279+
280+
let server_to_agent = build_response(identifier, remote_config, key_pair, key_id, flags);
244281
HttpResponse::Ok().body(server_to_agent)
245282
}
246283

284+
async fn jwks_handler(state: web::Data<Arc<Mutex<ServerState>>>, _req: web::Bytes) -> HttpResponse {
285+
let server_state = state.lock().unwrap();
286+
let public_key = server_state.key_pair.public_key().as_ref().to_vec();
287+
let enc_public_key = BASE64_URL_SAFE_NO_PAD.encode(&public_key);
288+
let payload = json!({
289+
"keys": [
290+
{
291+
"kty": "OKP",
292+
"alg": null,
293+
"use": "sig",
294+
"kid": public_key_fingerprint(&public_key),
295+
"n": null,
296+
"x": enc_public_key,
297+
"y": null,
298+
"crv": "Ed25519"
299+
}
300+
]
301+
});
302+
HttpResponse::Ok().json(payload)
303+
}
304+
247305
fn build_response(
248306
instance_id: InstanceID,
249307
agent_remote_config: Option<RemoteConfig>,
250-
key_pair: &KeyPair,
308+
key_pair: &Ed25519KeyPair,
309+
key_id: String,
251310
flags: u64,
252311
) -> Vec<u8> {
253312
let mut remote_config = None;
@@ -267,16 +326,14 @@ fn build_response(
267326
}),
268327
});
269328

270-
let key_pair_ring =
271-
ring::signature::Ed25519KeyPair::from_pkcs8(&key_pair.serialize_der()).unwrap();
272-
let signature = key_pair_ring.sign(config.raw_body.as_bytes());
329+
let signature = key_pair.sign(config.raw_body.as_bytes());
273330

274331
let custom_message_data = HashMap::from([(
275332
"fakeCRC".to_string(), //AC is not using the CRC.
276333
vec![SignatureFields {
277334
signature: BASE64_STANDARD.encode(signature.as_ref()),
278335
signing_algorithm: ED25519,
279-
key_id: public_key_fingerprint(&key_pair.subject_public_key_info()),
336+
key_id,
280337
}],
281338
)]);
282339

@@ -295,3 +352,8 @@ fn build_response(
295352
}
296353
.encode_to_vec()
297354
}
355+
356+
fn generate_key_pair() -> Ed25519KeyPair {
357+
let pkcs8 = Ed25519KeyPair::generate_pkcs8(&SystemRandom::new()).unwrap();
358+
Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).unwrap()
359+
}

agent-control/tests/k8s/data/k8s_fail_remote_config_missing_required_values/local-data-agent-control.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ fleet_control:
33
poll_interval: 5s
44
signature_validation:
55
certificate_pem_file_path: <cert-path>
6+
public_key_server_url: <jwks-endpoint>
67
k8s:
78
namespace: <ns>
89
namespace_agents: <agents-ns>

agent-control/tests/k8s/data/k8s_garbage_collector_triggers_on_ac_startup/local-data-agent-control.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ fleet_control:
33
poll_interval: 5s
44
signature_validation:
55
certificate_pem_file_path: <cert-path>
6+
public_key_server_url: <jwks-endpoint>
67
k8s:
78
namespace: <ns>
89
namespace_agents: <agents-ns>

agent-control/tests/k8s/data/k8s_opamp_add_sub_agent/local-data-agent-control.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ fleet_control:
33
poll_interval: 5s
44
signature_validation:
55
certificate_pem_file_path: <cert-path>
6+
public_key_server_url: <jwks-endpoint>
67
k8s:
78
namespace: <ns>
89
namespace_agents: <agents-ns>

agent-control/tests/k8s/data/k8s_opamp_attributes_existing_agent_type/local-data-agent-control.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ fleet_control:
33
poll_interval: 5s
44
signature_validation:
55
certificate_pem_file_path: <cert-path>
6+
public_key_server_url: <jwks-endpoint>
67
k8s:
78
namespace: <ns>
89
namespace_agents: <agents-ns>

agent-control/tests/k8s/data/k8s_opamp_cr_subagent_installed_before_crd/local-data-agent-control.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ fleet_control:
33
poll_interval: 5s
44
signature_validation:
55
certificate_pem_file_path: <cert-path>
6+
public_key_server_url: <jwks-endpoint>
67
k8s:
78
namespace: <ns>
89
namespace_agents: <agents-ns>

agent-control/tests/k8s/data/k8s_opamp_foo_cr_subagent/local-data-agent-control.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ fleet_control:
33
poll_interval: 5s
44
signature_validation:
55
certificate_pem_file_path: <cert-path>
6+
public_key_server_url: <jwks-endpoint>
67
k8s:
78
namespace: <ns>
89
namespace_agents: <agents-ns>

agent-control/tests/k8s/data/k8s_opamp_modify_subagent_config/local-data-agent-control.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ fleet_control:
33
poll_interval: 5s
44
signature_validation:
55
certificate_pem_file_path: <cert-path>
6+
public_key_server_url: <jwks-endpoint>
67
k8s:
78
namespace: <ns>
89
namespace_agents: <agents-ns>

agent-control/tests/k8s/data/k8s_opamp_remove_subagent/local-data-agent-control.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ fleet_control:
33
poll_interval: 5s
44
signature_validation:
55
certificate_pem_file_path: <cert-path>
6+
public_key_server_url: <jwks-endpoint>
67
k8s:
78
namespace: <ns>
89
namespace_agents: <agents-ns>

agent-control/tests/k8s/data/k8s_opamp_subagent_configuration_change_after_ac_restarts/local-data-agent-control.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ fleet_control:
33
poll_interval: 5s
44
signature_validation:
55
certificate_pem_file_path: <cert-path>
6+
public_key_server_url: <jwks-endpoint>
67
k8s:
78
namespace: <ns>
89
namespace_agents: <agents-ns>

0 commit comments

Comments
 (0)