Skip to content

Commit 1263366

Browse files
feat: add K8sSecretProvider (#1473)
* feat: add k8s secret provider * chore: generalize retry * nits
1 parent 7235b7d commit 1263366

File tree

17 files changed

+285
-208
lines changed

17 files changed

+285
-208
lines changed

agent-control/src/agent_control/run/k8s.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::agent_control::run::AgentControlRunner;
1515
use crate::agent_control::version_updater::k8s::K8sACUpdater;
1616
use crate::agent_type::render::renderer::TemplateRenderer;
1717
use crate::agent_type::variable::Variable;
18+
use crate::agent_type::variable::namespace::Namespace;
1819
#[cfg_attr(test, mockall_double::double)]
1920
use crate::k8s::client::SyncK8sClient;
2021
use crate::opamp::effective_config::loader::DefaultEffectiveConfigLoaderBuilder;
@@ -23,7 +24,8 @@ use crate::opamp::instance_id::k8s::getter::{Identifiers, get_identifiers};
2324
use crate::opamp::operations::build_opamp_with_channel;
2425
use crate::opamp::remote_config::validators::SupportedRemoteConfigValidator;
2526
use crate::opamp::remote_config::validators::regexes::RegexValidator;
26-
use crate::secrets_provider::SecretsProvidersRegistry;
27+
use crate::secrets_provider::k8s_secret::K8sSecretProvider;
28+
use crate::secrets_provider::{SecretsProviderType, SecretsProvidersRegistry};
2729
use crate::sub_agent::effective_agents_assembler::LocalEffectiveAgentsAssembler;
2830
use crate::sub_agent::identity::AgentIdentity;
2931
use crate::sub_agent::k8s::builder::SupervisorBuilderK8s;
@@ -133,7 +135,7 @@ impl AgentControlRunner {
133135
let template_renderer = TemplateRenderer::default()
134136
.with_agent_control_variables(agent_control_variables.clone().into_iter());
135137

136-
let secrets_providers = if let Some(config) = &agent_control_config.secrets_providers {
138+
let mut secrets_providers = if let Some(config) = &agent_control_config.secrets_providers {
137139
SecretsProvidersRegistry::try_from(config.clone()).map_err(|e| {
138140
AgentError::ConfigResolve(AgentControlConfigError::Load(format!(
139141
"Failed to load secrets providers: {e}"
@@ -142,6 +144,10 @@ impl AgentControlRunner {
142144
} else {
143145
HashMap::default()
144146
};
147+
secrets_providers.insert(
148+
Namespace::K8sSecret,
149+
SecretsProviderType::K8sSecret(K8sSecretProvider::new(k8s_client.clone())),
150+
);
145151

146152
let agents_assembler = Arc::new(LocalEffectiveAgentsAssembler::new(
147153
self.agent_type_registry.clone(),

agent-control/src/agent_type/variable/namespace.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub enum Namespace {
1515
// These are loaded every time a remote config is received.
1616
EnvironmentVariable,
1717
Vault,
18+
K8sSecret,
1819
}
1920

2021
impl Namespace {
@@ -32,6 +33,8 @@ impl Namespace {
3233
const ENVIRONMENT_VARIABLE: &'static str = "env";
3334
/// Encapsulates the secrets retrieved from a HashiCorp Vault
3435
const VAULT_SECRET: &'static str = "vault";
36+
/// Encapsulates the secrets retrieved from K8s Secrets
37+
const K8S_SECRET: &'static str = "kubesec";
3538

3639
pub fn namespaced_name(&self, name: &str) -> NamespacedVariableName {
3740
format!("{}{}{}", self, Self::PREFIX_NS_SEPARATOR, name)
@@ -52,6 +55,7 @@ impl Display for Namespace {
5255
Self::AgentControl => Self::AC,
5356
Self::EnvironmentVariable => Self::ENVIRONMENT_VARIABLE,
5457
Self::Vault => Self::VAULT_SECRET,
58+
Self::K8sSecret => Self::K8S_SECRET,
5559
};
5660
write!(f, "{}{ns}", Self::PREFIX)
5761
}
@@ -78,5 +82,13 @@ mod tests {
7882
"nr-ac:test".to_string(),
7983
Namespace::AgentControl.namespaced_name("test")
8084
);
85+
assert_eq!(
86+
"nr-vault:test".to_string(),
87+
Namespace::Vault.namespaced_name("test")
88+
);
89+
assert_eq!(
90+
"nr-kubesec:test".to_string(),
91+
Namespace::K8sSecret.namespaced_name("test")
92+
);
8193
}
8294
}

agent-control/src/agent_type/variable/secret_variables.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ impl SecretVariables {
8484
SecretsProviderType::Vault(provider) => {
8585
self.load_secrets_at(namespace, provider)?
8686
}
87+
SecretsProviderType::K8sSecret(provider) => {
88+
self.load_secrets_at(namespace, provider)?
89+
}
8790
};
8891
result.extend(secrets_map);
8992
}

agent-control/src/cli/uninstall_agent_control.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ use crate::agent_control::config::{
33
};
44
use crate::cli::errors::CliError;
55
use crate::cli::install_agent_control::{RELEASE_NAME, REPOSITORY_NAME};
6-
use crate::cli::utils::{retry, try_new_k8s_client};
6+
use crate::cli::utils::try_new_k8s_client;
77
#[cfg_attr(test, mockall_double::double)]
88
use crate::k8s::client::SyncK8sClient;
99
use crate::k8s::labels::Labels;
10+
use crate::utils::retry::retry;
1011
use clap::Parser;
1112
use either::Either;
1213
use kube::api::{DynamicObject, ObjectList, TypeMeta};

agent-control/src/cli/utils.rs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use crate::cli::errors::CliError;
33
use crate::k8s::client::SyncK8sClient;
44
use std::collections::BTreeMap;
55
use std::sync::Arc;
6-
use std::time::Duration;
76
use tracing::debug;
87

98
/// Parses a string of key-value pairs separated by commas.
@@ -48,21 +47,6 @@ pub fn try_new_k8s_client() -> Result<SyncK8sClient, CliError> {
4847
SyncK8sClient::try_new(runtime).map_err(|err| CliError::K8sClient(err.to_string()))
4948
}
5049

51-
pub fn retry<F>(max_attempts: usize, interval: Duration, mut f: F) -> Result<(), CliError>
52-
where
53-
F: FnMut() -> Result<(), CliError>,
54-
{
55-
let mut last_err = Ok(());
56-
for _ in 0..max_attempts {
57-
let Err(err) = f() else {
58-
return Ok(());
59-
};
60-
last_err = Err(err);
61-
std::thread::sleep(interval);
62-
}
63-
last_err
64-
}
65-
6650
#[cfg(test)]
6751
mod tests {
6852
use super::*;

agent-control/src/k8s/client.rs

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::k8s::dynamic_object::TypeMetaNamespaced;
66
use crate::k8s::utils::{get_namespace, get_type_meta};
77
use either::Either;
88
use k8s_openapi::api::apps::v1::{DaemonSet, Deployment, StatefulSet};
9-
use k8s_openapi::api::core::v1::ConfigMap;
9+
use k8s_openapi::api::core::v1::{ConfigMap, Secret};
1010
use k8s_openapi::apimachinery::pkg::apis::meta::v1::{APIResourceList, ObjectMeta};
1111
use kube::api::ObjectList;
1212
use kube::api::entry::Entry;
@@ -157,6 +157,17 @@ impl SyncK8sClient {
157157
.block_on(self.async_client.get_configmap_key(name, namespace, key))
158158
}
159159

160+
// Gets the decoded secret key assuming it contains a String.
161+
pub fn get_secret_key(
162+
&self,
163+
name: &str,
164+
namespace: &str,
165+
key: &str,
166+
) -> Result<Option<String>, K8sError> {
167+
self.runtime
168+
.block_on(self.async_client.get_secret_key(name, namespace, key))
169+
}
170+
160171
pub fn set_configmap_key(
161172
&self,
162173
name: &str,
@@ -256,6 +267,35 @@ impl AsyncK8sClient {
256267
Ok(list)
257268
}
258269

270+
/// Gets the decoded secret key assuming it contains a String.
271+
pub async fn get_secret_key(
272+
&self,
273+
name: &str,
274+
namespace: &str,
275+
key: &str,
276+
) -> Result<Option<String>, K8sError> {
277+
let secret_client = Api::<Secret>::namespaced(self.client.clone(), namespace);
278+
279+
let Some(secret) = secret_client.get_opt(name).await? else {
280+
debug!("Secret {}:{} not found", namespace, name);
281+
return Ok(None);
282+
};
283+
284+
let Some(data) = secret.data else {
285+
debug!("Secret {}:{} missing data", namespace, name);
286+
return Ok(None);
287+
};
288+
289+
let Some(value) = data.get(key) else {
290+
debug!("Secret {}:{} missing key {}", namespace, name, key);
291+
return Ok(None);
292+
};
293+
294+
let v = std::str::from_utf8(&value.0)
295+
.map_err(|e| K8sError::Generic(format!("decoding secret key: {}", e)))?;
296+
Ok(Some(v.to_string()))
297+
}
298+
259299
pub async fn delete_configmap_collection(
260300
&self,
261301
namespace: &str,
@@ -276,20 +316,22 @@ impl AsyncK8sClient {
276316
let cm_client: Api<ConfigMap> =
277317
Api::<ConfigMap>::namespaced(self.client.clone(), namespace);
278318

279-
if let Some(cm) = cm_client.get_opt(name).await? {
280-
if let Some(data) = cm.data {
281-
if let Some(key) = data.get(key) {
282-
return Ok(Some(key.clone()));
283-
}
284-
debug!("ConfigMap {} missing key {}", name, key)
285-
} else {
286-
debug!("ConfigMap {} missing data", name)
287-
}
288-
} else {
289-
debug!("ConfigMap {} not found", name)
290-
}
319+
let Some(cm) = cm_client.get_opt(name).await? else {
320+
debug!("ConfigMap {}:{} not found", namespace, name);
321+
return Ok(None);
322+
};
323+
324+
let Some(data) = cm.data else {
325+
debug!("ConfigMap {}:{} missing data", namespace, name);
326+
return Ok(None);
327+
};
328+
329+
let Some(value) = data.get(key) else {
330+
debug!("ConfigMap {}:{} missing key {}", namespace, name, key);
331+
return Ok(None);
332+
};
291333

292-
Ok(None)
334+
Ok(Some(value.clone()))
293335
}
294336

295337
pub async fn set_configmap_key(

agent-control/src/k8s/error.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ use kube::{api, config::KubeconfigError};
66

77
#[derive(thiserror::Error, Debug)]
88
pub enum K8sError {
9+
#[error("{0}")]
10+
Generic(String),
11+
912
#[error("the kube client returned an error: `{0}`")]
10-
Generic(#[from] kube::Error),
13+
KubeRs(#[from] kube::Error),
1114

1215
#[error("it is not possible to read kubeconfig: `{0}`")]
1316
UnableToSetupClientKubeconfig(#[from] KubeconfigError),

agent-control/src/k8s/store.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ pub mod tests {
225225
k8s_client
226226
.expect_get_configmap_key()
227227
.once()
228-
.returning(move |_, _, _| Err(K8sError::Generic(kube::Error::TlsRequired)));
228+
.returning(move |_, _, _| Err(K8sError::KubeRs(kube::Error::TlsRequired)));
229229

230230
let k8s_store = K8sStore::new(Arc::new(k8s_client), TEST_NAMESPACE.to_string());
231231

@@ -288,7 +288,7 @@ pub mod tests {
288288
k8s_client
289289
.expect_set_configmap_key()
290290
.once()
291-
.returning(move |_, _, _, _, _| Err(K8sError::Generic(kube::Error::TlsRequired)));
291+
.returning(move |_, _, _, _, _| Err(K8sError::KubeRs(kube::Error::TlsRequired)));
292292
let k8s_store = K8sStore::new(Arc::new(k8s_client), TEST_NAMESPACE.to_string());
293293

294294
let id = k8s_store.set_opamp_data(

agent-control/src/opamp/remote_config/validators/signature/certificate_fetcher.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ mod tests {
7979
use crate::http::config::ProxyConfig;
8080
use crate::http::tls::install_rustls_default_crypto_provider;
8181
use crate::opamp::remote_config::validators::signature::certificate_store::tests::TestSigner;
82-
use crate::utils::tests::retry;
82+
use crate::utils::retry::retry;
8383
use assert_matches::assert_matches;
8484

8585
const DEFAULT_CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
@@ -108,13 +108,12 @@ mod tests {
108108
CertificateFetcher::Https(Url::parse(self.url).unwrap(), client.clone())
109109
.fetch()
110110
{
111-
return Err(
112-
format!("fetching cert err '{}', case: '{}'", e, self.name).into()
113-
);
111+
return Err(format!("fetching cert err '{}', case: '{}'", e, self.name));
114112
}
115113

116114
Ok(())
117-
});
115+
})
116+
.unwrap();
118117
}
119118
}
120119
let test_cases = vec![

0 commit comments

Comments
 (0)