linkerd2/policy-test/tests/e2e_server_authorization.rs

358 lines
12 KiB
Rust

use linkerd_policy_controller_k8s_api::{
self as k8s, policy::server_authorization::Client as ClientAuthz, ResourceExt,
};
use linkerd_policy_test::{
await_condition, create, create_ready_pod, curl, endpoints_ready, web, with_temp_ns,
LinkerdInject,
};
#[tokio::test(flavor = "current_thread")]
async fn meshtls() {
with_temp_ns(|client, ns| async move {
let srv = create(&client, web::server(&ns, None)).await;
create(
&client,
server_authz(
&ns,
"web",
&srv,
ClientAuthz {
mesh_tls: Some(k8s::policy::server_authorization::MeshTls {
identities: Some(vec!["*".to_string()]),
..Default::default()
}),
..Default::default()
},
),
)
.await;
// Create the web pod and wait for it to be ready.
tokio::join!(
create(&client, web::service(&ns)),
create_ready_pod(&client, web::pod(&ns))
);
await_condition(&client, &ns, "web", endpoints_ready).await;
let curl = curl::Runner::init(&client, &ns).await;
let (injected, uninjected) = tokio::join!(
curl.run("curl-injected", "http://web", LinkerdInject::Enabled),
curl.run("curl-uninjected", "http://web", LinkerdInject::Disabled),
);
let (injected_status, uninjected_status) =
tokio::join!(injected.exit_code(), uninjected.exit_code());
assert_eq!(injected_status, 0, "injected curl must succeed");
assert_eq!(uninjected_status, 22, "uninjected curl must fail");
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn network() {
with_temp_ns(|client, ns| async move {
let curl = curl::Runner::init(&client, &ns).await;
// Create a lock that prevents `curl` pods from running. This allows us
// to create a curl pod so that its IP can be obtained to be used in an
// authorization policy.
curl.create_lock().await;
// Create a curl pod and wait for it to get an IP.
let blessed = curl
.run("curl-blessed", "http://web", LinkerdInject::Disabled)
.await;
let blessed_ip = blessed.ip().await;
tracing::debug!(curl.blessed.ip = %blessed_ip);
// Once we know the IP of the (blocked) pod, create an web
// authorization policy that permits connections from this pod.
let srv = create(&client, web::server(&ns, None)).await;
create(
&client,
server_authz(
&ns,
"web",
&srv,
ClientAuthz {
networks: Some(vec![k8s::policy::Network {
cidr: blessed_ip.into(),
except: None,
}]),
unauthenticated: true,
..Default::default()
},
),
)
.await;
// Start web with the policy.
tokio::join!(
create(&client, web::service(&ns)),
create_ready_pod(&client, web::pod(&ns))
);
await_condition(&client, &ns, "web", endpoints_ready).await;
// Once the web pod is ready, delete the `curl-lock` configmap to
// unblock curl from running.
curl.delete_lock().await;
tracing::info!("unblocked curl");
// The blessed pod should be able to connect to the web pod.
let status = blessed.exit_code().await;
assert_eq!(status, 0, "blessed curl must succeed");
// Create another curl pod that is not included in the authorization. It
// should fail to connect to the web pod.
let status = curl
.run("curl-cursed", "http://web", LinkerdInject::Disabled)
.await
.exit_code()
.await;
assert_eq!(status, 22, "cursed curl must fail");
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn both() {
with_temp_ns(|client, ns| async move {
let curl = curl::Runner::init(&client, &ns).await;
// Create a lock that prevents `curl` pods from running. This allows us
// to create a curl pod so that its IP can be obtained to be used in an
// authorization policy.
curl.create_lock().await;
let (blessed_injected, blessed_uninjected) = tokio::join!(
curl.run(
"curl-blessed-injected",
"http://web",
LinkerdInject::Enabled,
),
curl.run(
"curl-blessed-uninjected",
"http://web",
LinkerdInject::Disabled,
)
);
let (blessed_injected_ip, blessed_uninjected_ip) =
tokio::join!(blessed_injected.ip(), blessed_uninjected.ip(),);
tracing::debug!(curl.blessed.injected.ip = ?blessed_injected_ip);
tracing::debug!(curl.blessed.uninjected.ip = ?blessed_uninjected_ip);
// Once we know the IP of the (blocked) pod, create an web
// authorization policy that permits connections from this pod.
let srv = create(&client, web::server(&ns, None)).await;
create(
&client,
server_authz(
&ns,
"web",
&srv,
ClientAuthz {
networks: Some(vec![
k8s::policy::Network {
cidr: blessed_injected_ip.into(),
except: None,
},
k8s::policy::Network {
cidr: blessed_uninjected_ip.into(),
except: None,
},
]),
mesh_tls: Some(k8s::policy::server_authorization::MeshTls {
identities: Some(vec!["*".to_string()]),
..Default::default()
}),
..Default::default()
},
),
)
.await;
// Start web with the policy.
tokio::join!(
create(&client, web::service(&ns)),
create_ready_pod(&client, web::pod(&ns))
);
await_condition(&client, &ns, "web", endpoints_ready).await;
// Once the web pod is ready, delete the `curl-lock` configmap to
// unblock curl from running.
curl.delete_lock().await;
tracing::info!("unblocked curl");
let (blessed_injected_status, blessed_uninjected_status) =
tokio::join!(blessed_injected.exit_code(), blessed_uninjected.exit_code());
// The blessed and injected pod should be able to connect to the web pod.
assert_eq!(
blessed_injected_status, 0,
"blessed injected curl must succeed"
);
// The blessed and uninjected pod should NOT be able to connect to the web pod.
assert_eq!(
blessed_uninjected_status, 22,
"blessed uninjected curl must fail"
);
let (cursed_injected, cursed_uninjected) = tokio::join!(
curl.run("curl-cursed-injected", "http://web", LinkerdInject::Enabled,),
curl.run(
"curl-cursed-uninjected",
"http://web",
LinkerdInject::Disabled,
)
);
let (cursed_injected_status, cursed_uninjected_status) =
tokio::join!(cursed_injected.exit_code(), cursed_uninjected.exit_code(),);
assert_eq!(cursed_injected_status, 22, "cursed injected curl must fail");
assert_eq!(
cursed_uninjected_status, 22,
"cursed uninjected curl must fail"
);
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn either() {
with_temp_ns(|client, ns| async move {
let curl = curl::Runner::init(&client, &ns).await;
tracing::info!("Blocking curl");
curl.create_lock().await;
let (blessed_injected, blessed_uninjected) = tokio::join!(
curl.run(
"curl-blessed-injected",
"http://web",
LinkerdInject::Enabled,
),
curl.run(
"curl-blessed-uninjected",
"http://web",
LinkerdInject::Disabled,
)
);
let (blessed_injected_ip, blessed_uninjected_ip) =
tokio::join!(blessed_injected.ip(), blessed_uninjected.ip());
tracing::debug!(curl.blessed.injected.ip = ?blessed_injected_ip);
tracing::debug!(curl.blessed.uninjected.ip = ?blessed_uninjected_ip);
// Once we know the IP of the (blocked) pod, create an web
// authorization policy that permits connections from this pod.
let srv = create(&client, web::server(&ns, None)).await;
tokio::join!(
create(
&client,
server_authz(
&ns,
"web-from-ip",
&srv,
ClientAuthz {
unauthenticated: true,
networks: Some(vec![
k8s::policy::Network {
cidr: blessed_injected_ip.into(),
except: None,
},
k8s::policy::Network {
cidr: blessed_uninjected_ip.into(),
except: None,
},
]),
..Default::default()
},
)
),
create(
&client,
server_authz(
&ns,
"web-from-id",
&srv,
ClientAuthz {
mesh_tls: Some(k8s::policy::server_authorization::MeshTls {
identities: Some(vec!["*".to_string()]),
..Default::default()
}),
..Default::default()
},
)
),
);
// Start web with the policy.
tokio::join!(
create(&client, web::service(&ns)),
create_ready_pod(&client, web::pod(&ns)),
);
await_condition(&client, &ns, "web", endpoints_ready).await;
// Once the web pod is ready, delete the `curl-lock` configmap to
// unblock curl from running.
curl.delete_lock().await;
tracing::info!("unblocked curl");
let (blessed_injected_status, blessed_uninjected_status) =
tokio::join!(blessed_injected.exit_code(), blessed_uninjected.exit_code());
assert_eq!(
blessed_injected_status, 0,
"blessed injected curl must succeed"
);
assert_eq!(
blessed_uninjected_status, 0,
"blessed uninjected curl must succeed"
);
let (cursed_injected, cursed_uninjected) = tokio::join!(
curl.run("curl-cursed-injected", "http://web", LinkerdInject::Enabled,),
curl.run(
"curl-cursed-uninjected",
"http://web",
LinkerdInject::Disabled,
),
);
let (cursed_injected_status, cursed_uninjected_status) =
tokio::join!(cursed_injected.exit_code(), cursed_uninjected.exit_code());
assert_eq!(
cursed_injected_status, 0,
"cursed injected curl must succeed"
);
assert_eq!(
cursed_uninjected_status, 22,
"cursed uninjected curl must fail"
);
})
.await;
}
// === helpers ===
fn server_authz(
ns: &str,
name: &str,
target: &k8s::policy::Server,
client: ClientAuthz,
) -> k8s::policy::ServerAuthorization {
k8s::policy::ServerAuthorization {
metadata: k8s::ObjectMeta {
namespace: Some(ns.to_string()),
name: Some(name.to_string()),
..Default::default()
},
spec: k8s::policy::ServerAuthorizationSpec {
server: k8s::policy::server_authorization::Server {
name: Some(target.name_unchecked()),
selector: None,
},
client,
},
}
}