use std::sync::Arc;
use serde::Deserialize;
use crate::claims::StandardClaims;
use crate::config::JwtVerifierConfig;
use crate::error::Error;
use crate::error::Result;
use crate::extractor::IdentityExtractor;
use crate::verifier::JwtVerifier;
#[derive(Debug, Deserialize)]
pub struct KubernetesClaims {
pub iss: String,
pub sub: String,
pub aud: Vec<String>,
pub exp: i64,
#[serde(rename = "kubernetes.io")]
pub kubernetes_io: Option<KubernetesIo>,
}
impl StandardClaims for KubernetesClaims {
fn iss(&self) -> &str {
&self.iss
}
fn sub(&self) -> &str {
&self.sub
}
fn aud(&self) -> &[String] {
&self.aud
}
fn exp(&self) -> i64 {
self.exp
}
}
#[derive(Debug, Deserialize)]
pub struct KubernetesIo {
pub serviceaccount: ServiceAccount,
pub namespace: Arc<str>,
}
#[derive(Debug, Deserialize)]
pub struct ServiceAccount {
pub name: Arc<str>,
}
#[derive(Debug, Deserialize, Hash, Eq, PartialEq, Clone)]
pub struct KubernetesIdentity {
pub service_account: Arc<str>,
pub namespace: Arc<str>,
}
impl TryFrom<String> for KubernetesIdentity {
type Error = Error;
fn try_from(subject: String) -> Result<Self> {
let split = subject.split(':').rev().collect::<Vec<_>>();
if split.is_empty() || split.len() < 2 {
return Err(Error::ServiceAccountNotPresentInSubject);
}
let service_account = Arc::from(
*split
.first()
.ok_or(Error::ServiceAccountNotPresentInSubject)?,
);
let namespace = Arc::from(
*split
.get(1)
.ok_or(Error::ServiceAccountNotPresentInSubject)?,
);
Ok(Self {
service_account,
namespace,
})
}
}
#[derive(Clone, Debug)]
pub struct KubernetesExtractor;
impl IdentityExtractor for KubernetesExtractor {
type Claims = KubernetesClaims;
type Identity = KubernetesIdentity;
fn extract_identity(&self, claims: &Self::Claims) -> Result<Self::Identity> {
if let Some(k8s) = &claims.kubernetes_io {
return Ok(KubernetesIdentity {
service_account: k8s.serviceaccount.name.clone(),
namespace: k8s.namespace.clone(),
});
}
extract_identity_from_sub(&claims.sub)
}
}
pub type KubernetesJwtVerifier = JwtVerifier<KubernetesExtractor>;
impl KubernetesJwtVerifier {
pub async fn with_issuer(
expected_issuer: impl Into<String>,
audience: impl Into<String>,
) -> Result<Self> {
let config = JwtVerifierConfig::new(expected_issuer, audience);
Self::new(config, KubernetesExtractor).await
}
}
fn extract_identity_from_sub(sub: &str) -> Result<KubernetesIdentity> {
let parts: Vec<&str> = sub.split(':').collect();
if parts.len() != 4 || parts[0] != "system" || parts[1] != "serviceaccount" {
return Err(Error::ServiceAccountNotPresentInSubject);
}
Ok(KubernetesIdentity {
namespace: Arc::from(parts[2]),
service_account: Arc::from(parts[3]),
})
}