Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

identity: add spire identity client #2580

Merged
merged 17 commits into from
Jan 13, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,7 @@ dependencies = [
"linkerd-proxy-identity-client",
"linkerd-proxy-resolve",
"linkerd-proxy-server-policy",
"linkerd-proxy-spire-client",
"linkerd-proxy-tap",
"linkerd-proxy-tcp",
"linkerd-proxy-transport",
Expand Down Expand Up @@ -1593,6 +1594,7 @@ dependencies = [
"linkerd-tls-test-util",
"linkerd-tracing",
"pin-project",
"rcgen",
"tokio",
"tracing",
]
Expand Down Expand Up @@ -1913,6 +1915,29 @@ dependencies = [
"thiserror",
]

[[package]]
name = "linkerd-proxy-spire-client"
version = "0.1.0"
dependencies = [
"futures",
"linkerd-error",
"linkerd-exp-backoff",
"linkerd-identity",
"linkerd-proxy-http",
"linkerd-stack",
"linkerd-tonic-watch",
"rcgen",
"simple_asn1",
"spiffe-proto",
"thiserror",
"tokio",
"tokio-test",
"tonic",
"tower",
"tracing",
"x509-parser",
]

[[package]]
name = "linkerd-proxy-tap"
version = "0.1.0"
Expand Down Expand Up @@ -3061,6 +3086,18 @@ dependencies = [
"libc",
]

[[package]]
name = "simple_asn1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
dependencies = [
"num-bigint",
"num-traits",
"thiserror",
"time",
]

[[package]]
name = "slab"
version = "0.4.8"
Expand Down Expand Up @@ -3096,6 +3133,17 @@ dependencies = [
"windows-sys 0.48.0",
]

[[package]]
name = "spiffe-proto"
version = "0.1.0"
dependencies = [
"bytes",
"prost",
"prost-types",
"tonic",
"tonic-build",
]

[[package]]
name = "spin"
version = "0.5.2"
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ members = [
"linkerd/proxy/dns-resolve",
"linkerd/proxy/http",
"linkerd/proxy/identity-client",
"linkerd/proxy/spire-client",
"linkerd/proxy/resolve",
"linkerd/proxy/server-policy",
"linkerd/proxy/tap",
Expand All @@ -73,6 +74,7 @@ members = [
"linkerd/transport-metrics",
"linkerd2-proxy",
"opencensus-proto",
"spiffe-proto",
"tools",
]

Expand Down
1 change: 1 addition & 0 deletions linkerd/app/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ linkerd-proxy-client-policy = { path = "../../proxy/client-policy" }
linkerd-proxy-dns-resolve = { path = "../../proxy/dns-resolve" }
linkerd-proxy-http = { path = "../../proxy/http" }
linkerd-proxy-identity-client = { path = "../../proxy/identity-client" }
linkerd-proxy-spire-client = { path = "../../proxy/spire-client" }
linkerd-proxy-resolve = { path = "../../proxy/resolve" }
linkerd-proxy-server-policy = { path = "../../proxy/server-policy" }
linkerd-proxy-tap = { path = "../../proxy/tap" }
Expand Down
5 changes: 4 additions & 1 deletion linkerd/app/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ pub use linkerd_transport_header as transport_header;
pub mod identity {
pub use linkerd_identity::*;
pub use linkerd_meshtls::*;
pub use linkerd_proxy_identity_client as client;
pub mod client {
pub use linkerd_proxy_identity_client as linkerd;
pub use linkerd_proxy_spire_client as spire;
}
}

pub const CANONICAL_DST_HEADER: &str = "l5d-dst-canonical";
Expand Down
33 changes: 21 additions & 12 deletions linkerd/app/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -800,7 +800,7 @@ pub fn parse_config<S: Strings>(strings: &S) -> Result<super::Config, EnvError>
.unwrap_or(super::tap::Config::Disabled);

let identity = {
let (addr, certify, params) = identity_config?;
let (addr, certify, tls) = identity_config?;
// If the address doesn't have a server identity, then we're on localhost.
let connect = if addr.addr.is_loopback() {
inbound.proxy.connect.clone()
Expand All @@ -812,17 +812,17 @@ pub fn parse_config<S: Strings>(strings: &S) -> Result<super::Config, EnvError>
} else {
outbound.http_request_queue.failfast_timeout
};
identity::Config {
identity::Config::Linkerd {
certify,
control: ControlConfig {
tls,
client: ControlConfig {
addr,
connect,
buffer: QueueConfig {
capacity: DEFAULT_CONTROL_QUEUE_CAPACITY,
failfast_timeout,
},
},
params,
}
};

Expand Down Expand Up @@ -1222,7 +1222,14 @@ pub fn parse_control_addr<S: Strings>(

pub fn parse_identity_config<S: Strings>(
strings: &S,
) -> Result<(ControlAddr, identity::certify::Config, identity::TlsParams), EnvError> {
) -> Result<
(
ControlAddr,
identity::client::linkerd::Config,
identity::TlsParams,
),
EnvError,
> {
let control = parse_control_addr(strings, ENV_IDENTITY_SVC_BASE);
let ta = parse(strings, ENV_IDENTITY_TRUST_ANCHORS, |s| {
if s.is_empty() {
Expand All @@ -1232,7 +1239,7 @@ pub fn parse_identity_config<S: Strings>(
});
let dir = parse(strings, ENV_IDENTITY_DIR, |ref s| Ok(PathBuf::from(s)));
let tok = parse(strings, ENV_IDENTITY_TOKEN_FILE, |ref s| {
identity::TokenSource::if_nonempty_file(s.to_string()).map_err(|e| {
identity::client::linkerd::TokenSource::if_nonempty_file(s.to_string()).map_err(|e| {
error!("Could not read {}: {}", ENV_IDENTITY_TOKEN_FILE, e);
ParseError::InvalidTokenSource
})
Expand Down Expand Up @@ -1263,17 +1270,19 @@ pub fn parse_identity_config<S: Strings>(
min_refresh,
max_refresh,
) => {
let certify = identity::certify::Config {
let certify = identity::client::linkerd::Config {
token,
min_refresh: min_refresh.unwrap_or(DEFAULT_IDENTITY_MIN_REFRESH),
max_refresh: max_refresh.unwrap_or(DEFAULT_IDENTITY_MAX_REFRESH),
documents: identity::certify::Documents::load(dir).map_err(|error| {
error!(%error, "Failed to read identity documents");
EnvError::InvalidEnvVar
})?,
documents: identity::client::linkerd::certify::Documents::load(dir).map_err(
|error| {
error!(%error, "Failed to read identity documents");
EnvError::InvalidEnvVar
},
)?,
};
let params = identity::TlsParams {
server_id: identity::Id::Dns(local_name.clone()),
id: identity::Id::Dns(local_name.clone()),
server_name: local_name,
trust_anchors_pem,
};
Expand Down
145 changes: 93 additions & 52 deletions linkerd/app/src/identity.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
pub use linkerd_app_core::identity::{
client::{certify, TokenSource},
Id,
};
use crate::spire;

pub use linkerd_app_core::identity::{client, Id};
use linkerd_app_core::{
control, dns,
exp_backoff::{ExponentialBackoff, ExponentialBackoffStream},
identity::{client::Certify, creds, CertMetrics, Credentials, DerX509, Mode, WithCertMetrics},
identity::{
client::linkerd::Certify, creds, CertMetrics, Credentials, DerX509, Mode, WithCertMetrics,
},
metrics::{prom, ControlHttp as ClientMetrics},
Error, Result,
};
Expand All @@ -14,21 +15,27 @@
use tracing::Instrument;

#[derive(Clone, Debug)]
pub struct Config {
pub control: control::Config,
pub certify: certify::Config,
pub params: TlsParams,
#[allow(clippy::large_enum_variant)]
pub enum Config {
Linkerd {
client: control::Config,
certify: client::linkerd::Config,
tls: TlsParams,
},
Spire {
client: spire::Config,
tls: TlsParams,
},
}

#[derive(Clone, Debug)]
pub struct TlsParams {
pub server_id: Id,
pub id: Id,
pub server_name: dns::Name,
pub trust_anchors_pem: String,
}

pub struct Identity {
addr: control::ControlAddr,
receiver: creds::Receiver,
ready: watch::Receiver<bool>,
task: Task,
Expand All @@ -55,47 +62,85 @@
client_metrics: ClientMetrics,
registry: &mut prom::Registry,
) -> Result<Identity> {
let name = self.params.server_name.clone();
let (store, receiver) = Mode::default().watch(
name.clone().into(),
name.clone(),
&self.params.trust_anchors_pem,
)?;

let certify = Certify::from(self.certify);

let addr = self.control.addr.clone();

let (tx, ready) = watch::channel(false);

// Save to be spawned on an auxiliary runtime.
let task = Box::pin({
let addr = addr.clone();
let svc = self.control.build(
dns,
client_metrics,
registry.sub_registry_with_prefix("control_identity"),
receiver.new_client(),
);

let cert_metrics =
CertMetrics::register(registry.sub_registry_with_prefix("identity_cert"));
let cred = WithCertMetrics::new(cert_metrics, NotifyReady { store, tx });

certify
.run(name, cred, svc)
.instrument(tracing::debug_span!("identity", server.addr = %addr).or_current())
});

Ok(Identity {
addr,
receiver,
ready,
task,
let cert_metrics =
CertMetrics::register(registry.sub_registry_with_prefix("identity_cert"));

Ok(match self {
Self::Linkerd {
client,
certify,
tls,
} => {
// TODO: move this validation into env.rs
let name = match (&tls.id, &tls.server_name) {
(Id::Dns(id), sni) if id == sni => id.clone(),
(_id, _sni) => {
return Err(
"Linkerd identity requires a TLS Id and server name to be the same"

Check warning on line 79 in linkerd/app/src/identity.rs

View check run for this annotation

Codecov / codecov/patch

linkerd/app/src/identity.rs#L77-L79

Added lines #L77 - L79 were not covered by tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we'd use thiserror again here. Since is up at the app layer I don't care as much as I would in a lib.

.into(),
);
}
};

let certify = Certify::from(certify);
let (store, receiver, ready) = watch(tls, cert_metrics)?;

let task = {
let addr = client.addr.clone();
let svc = client.build(
dns,
client_metrics,
registry.sub_registry_with_prefix("control_identity"),
receiver.new_client(),
);

Box::pin(certify.run(name, store, svc).instrument(
tracing::info_span!("identity", server.addr = %addr).or_current(),
))
};
Identity {
receiver,
ready,
task,
}
}
Self::Spire { client, tls } => {
let addr = client.socket_addr.clone();
let spire = spire::client::Spire::new(tls.id.clone());

Check warning on line 109 in linkerd/app/src/identity.rs

View check run for this annotation

Codecov / codecov/patch

linkerd/app/src/identity.rs#L107-L109

Added lines #L107 - L109 were not covered by tests

let (store, receiver, ready) = watch(tls, cert_metrics)?;

Check warning on line 111 in linkerd/app/src/identity.rs

View check run for this annotation

Codecov / codecov/patch

linkerd/app/src/identity.rs#L111

Added line #L111 was not covered by tests
let task =
Box::pin(spire.run(store, spire::Client::from(client)).instrument(
tracing::info_span!("spire", server.addr = %addr).or_current(),

Check warning on line 114 in linkerd/app/src/identity.rs

View check run for this annotation

Codecov / codecov/patch

linkerd/app/src/identity.rs#L113-L114

Added lines #L113 - L114 were not covered by tests
));

Identity {
receiver,
ready,

Check warning on line 119 in linkerd/app/src/identity.rs

View check run for this annotation

Codecov / codecov/patch

linkerd/app/src/identity.rs#L117-L119

Added lines #L117 - L119 were not covered by tests
task,
}
}
})
}
}

fn watch(
tls: TlsParams,
metrics: CertMetrics,
) -> Result<(
WithCertMetrics<NotifyReady>,
creds::Receiver,
watch::Receiver<bool>,
)> {
let (tx, ready) = watch::channel(false);
let (store, receiver) =
Mode::default().watch(tls.id, tls.server_name, &tls.trust_anchors_pem)?;
let cred = WithCertMetrics::new(metrics, NotifyReady { store, tx });
Ok((cred, receiver, ready))
}

// === impl NotifyReady ===

impl Credentials for NotifyReady {
fn set_certificate(
&mut self,
Expand All @@ -113,10 +158,6 @@
// === impl Identity ===

impl Identity {
pub fn addr(&self) -> control::ControlAddr {
self.addr.clone()
}

/// Returns a future that is satisfied once certificates have been provisioned.
pub fn ready(&self) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>> {
let mut ready = self.ready.clone();
Expand Down
Loading
Loading