use crate::{
scheduler::Scheduler,
signer::Signer,
tls::{self, TlsConfig},
utils::get_node_id_from_tls_config,
};
use log::debug;
use std::{convert::TryFrom, path::Path};
use thiserror;
const CRED_VERSION: u32 = 1u32;
const CA_RAW: &[u8] = include_str!("../.resources/tls/ca.pem").as_bytes();
const NOBODY_CRT: &[u8] = include_str!(env!("GL_NOBODY_CRT")).as_bytes();
const NOBODY_KEY: &[u8] = include_str!(env!("GL_NOBODY_KEY")).as_bytes();
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("could not get from identity: {}", .0)]
GetFromIdentityError(String),
#[error("identity mismatch: {}", .0)]
IsIdentityError(String),
#[error("could not decode into credentials")]
DecodeCredentialsError(#[from] prost::DecodeError),
#[error("could not encode credentials")]
EncodeCredentialError(#[from] prost::EncodeError),
#[error("could not upgrade credentials: {}", .0)]
UpgradeCredentialsError(String),
#[error("could not build credentials {}", .0)]
BuildCredentialsError(String),
#[error("could not create create credentials from data: {}", .0)]
TransformDataIntoCredentialsError(String),
#[error("could not create tls config {}", .0)]
CreateTlsConfigError(#[source] anyhow::Error),
#[error("could not read from file: {}", .0)]
ReadFromFileError(#[from] std::io::Error),
#[error("could not fetch default nobody credentials: {}", .0)]
FetchDefaultNobodyCredentials(#[source] anyhow::Error),
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub trait TlsConfigProvider: Send + Sync {
fn tls_config(&self) -> TlsConfig;
}
pub trait RuneProvider {
fn rune(&self) -> String;
}
pub trait NodeIdProvider {
fn node_id(&self) -> Result<Vec<u8>>;
}
#[derive(Clone, Debug)]
struct Identity {
cert: Vec<u8>,
key: Vec<u8>,
}
impl Default for Identity {
fn default() -> Self {
let key = load_file_or_default("GL_NOBODY_KEY", NOBODY_KEY)
.expect("Could not load file from GL_NOBODY_KEY");
let cert = load_file_or_default("GL_NOBODY_CRT", NOBODY_CRT)
.expect("Could not load file from GL_NOBODY_CRT");
Self { cert, key }
}
}
#[derive(Clone, Debug)]
pub struct Nobody {
pub cert: Vec<u8>,
pub key: Vec<u8>,
pub ca: Vec<u8>,
}
impl Nobody {
pub fn new() -> Self {
Self::default()
}
pub fn with<V>(cert: V, key: V) -> Self
where
V: Into<Vec<u8>>,
{
let ca =
load_file_or_default("GL_CA_CRT", CA_RAW).expect("Could not load file from GL_CA_CRT");
Self {
cert: cert.into(),
key: key.into(),
ca,
}
}
pub fn with_ca<V>(self, ca: V) -> Self
where
V: Into<Vec<u8>>,
{
Nobody {
ca: ca.into(),
..self
}
}
}
impl TlsConfigProvider for Nobody {
fn tls_config(&self) -> TlsConfig {
tls::TlsConfig::with(&self.cert, &self.key, &self.ca)
}
}
impl Default for Nobody {
fn default() -> Self {
let ca =
load_file_or_default("GL_CA_CRT", CA_RAW).expect("Could not load file from GL_CA_CRT");
let identity = Identity::default();
Self {
cert: identity.cert,
key: identity.key,
ca,
}
}
}
#[derive(Clone, Debug)]
pub struct Device {
pub version: u32,
pub cert: Vec<u8>,
pub key: Vec<u8>,
pub ca: Vec<u8>,
pub rune: String,
}
impl Device {
pub fn from_bytes(data: impl AsRef<[u8]>) -> Self {
let mut creds = Self::default();
log::trace!("Build authenticated credentials from: {:?}", data.as_ref());
if let Ok(data) = model::Data::try_from(data.as_ref()) {
creds.version = data.version;
if let Some(cert) = data.cert {
creds.cert = cert
}
if let Some(key) = data.key {
creds.key = key
}
if let Some(ca) = data.ca {
creds.ca = ca
}
if let Some(rune) = data.rune {
creds.rune = rune
}
}
creds
}
pub fn from_path(path: impl AsRef<Path>) -> Self {
debug!("Read credentials data from {:?}", path.as_ref());
let data = std::fs::read(path).unwrap_or_default();
Device::from_bytes(data)
}
pub fn with<V, S>(cert: V, key: V, rune: S) -> Self
where
V: Into<Vec<u8>>,
S: Into<String>,
{
let ca =
load_file_or_default("GL_CA_CRT", CA_RAW).expect("Could not load file from GL_CA_CRT");
Self {
version: CRED_VERSION,
cert: cert.into(),
key: key.into(),
rune: rune.into(),
ca
}
}
pub fn with_ca<V>(self, ca: V) -> Self
where
V: Into<Vec<u8>>,
{
Device {
ca: ca.into(),
..self
}
}
pub async fn upgrade<T>(mut self, _scheduler: &Scheduler<T>, signer: &Signer) -> Result<Self>
where
T: TlsConfigProvider,
{
use Error::*;
self.version = CRED_VERSION;
if self.rune.is_empty() {
let node_id = self
.node_id()
.map_err(|e| UpgradeCredentialsError(e.to_string()))?;
let alt = runeauth::Alternative::new(
"pubkey".to_string(),
runeauth::Condition::Equal,
hex::encode(node_id),
false,
)
.map_err(|e| UpgradeCredentialsError(e.to_string()))?;
self.rune = signer
.create_rune(None, vec![vec![&alt.encode()]])
.map_err(|e| UpgradeCredentialsError(e.to_string()))?;
};
Ok(self)
}
pub fn to_bytes(&self) -> Vec<u8> {
self.to_owned().into()
}
}
impl TlsConfigProvider for Device {
fn tls_config(&self) -> TlsConfig {
tls::TlsConfig::with(&self.cert, &self.key, &self.ca)
}
}
impl RuneProvider for Device {
fn rune(&self) -> String {
self.to_owned().rune
}
}
impl NodeIdProvider for Device {
fn node_id(&self) -> Result<Vec<u8>> {
get_node_id_from_tls_config(&self.tls_config()).map_err(|_e| {
Error::GetFromIdentityError(
"node_id could not be retrieved from the certificate".to_string(),
)
})
}
}
impl From<Device> for Vec<u8> {
fn from(value: Device) -> Self {
let data: model::Data = value.into();
data.into()
}
}
impl From<Device> for model::Data {
fn from(device: Device) -> Self {
model::Data {
version: CRED_VERSION,
cert: Some(device.cert),
key: Some(device.key),
ca: Some(device.ca),
rune: Some(device.rune),
}
}
}
impl Default for Device {
fn default() -> Self {
let ca =
load_file_or_default("GL_CA_CRT", CA_RAW).expect("Could not load file from GL_CA_CRT");
let identity = Identity::default();
Self {
version: 0,
cert: identity.cert,
key: identity.key,
ca,
rune: Default::default(),
}
}
}
mod model {
use prost::Message;
use std::convert::TryFrom;
#[derive(Message, Clone)]
pub struct Data {
#[prost(uint32, tag = "1")]
pub version: u32,
#[prost(bytes, optional, tag = "2")]
pub cert: Option<Vec<u8>>,
#[prost(bytes, optional, tag = "3")]
pub key: Option<Vec<u8>>,
#[prost(bytes, optional, tag = "4")]
pub ca: Option<Vec<u8>>,
#[prost(string, optional, tag = "5")]
pub rune: Option<String>,
}
impl TryFrom<&[u8]> for Data {
type Error = super::Error;
fn try_from(buf: &[u8]) -> std::prelude::v1::Result<Self, Self::Error> {
let data: Data = Data::decode(buf)?;
Ok(data)
}
}
impl From<Data> for Vec<u8> {
fn from(value: Data) -> Self {
value.encode_to_vec()
}
}
}
fn load_file_or_default(varname: &str, default: &[u8]) -> Result<Vec<u8>> {
match std::env::var(varname) {
Ok(fname) => {
debug!("Loading file {} for envvar {}", fname, varname);
let f = std::fs::read(fname.clone())?;
Ok(f)
}
Err(_) => Ok(default.to_vec()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode() {
let cert: Vec<u8> = vec![99, 98];
let key = vec![97, 96];
let ca = vec![95, 94];
let rune = "non_functional_rune".to_string();
let data = model::Data {
version: 1,
cert: Some(cert.clone()),
key: Some(key.clone()),
ca: Some(ca.clone()),
rune: Some(rune.clone()),
};
let buf: Vec<u8> = data.into();
print!("{:?}", buf);
for n in cert {
assert!(buf.contains(&n));
}
for n in key {
assert!(buf.contains(&n));
}
for n in ca {
assert!(buf.contains(&n));
}
for n in rune.as_bytes() {
assert!(buf.contains(n));
}
}
#[test]
fn test_decode() {
let data: Vec<u8> = vec![
8, 1, 18, 2, 99, 98, 26, 2, 97, 96, 34, 2, 95, 94, 42, 19, 110, 111, 110, 95, 102, 117,
110, 99, 116, 105, 111, 110, 97, 108, 95, 114, 117, 110, 101,
];
let data = model::Data::try_from(&data[..]).unwrap();
assert!(data.version == 1);
assert!(data.cert.is_some_and(|d| d == vec![99, 98]));
assert!(data.key.is_some_and(|d| d == vec![97, 96]));
assert!(data.ca.is_some_and(|d| d == vec![95, 94]));
assert!(data.rune.is_some_and(|d| d == *"non_functional_rune"));
}
}