-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add policy validation methods; refactor into smaller files
- Loading branch information
1 parent
09fc7d3
commit ea3e4a8
Showing
10 changed files
with
235 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
use color_eyre::Result; | ||
|
||
use crate::policy::LocalPolicy; | ||
|
||
mod fs; | ||
mod validating; | ||
|
||
pub use fs::FsPolicyLoader; | ||
pub use validating::ValidatingPolicyLoader; | ||
|
||
// This module holds functionality related to loading policies. | ||
// This could be fetching them from the file system, environment, remote bucket, etc. | ||
// Loaders implement the PolicyLoader trait. | ||
|
||
pub trait PolicyLoader { | ||
fn load_policies(&self) -> Result<Vec<LocalPolicy>>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
use color_eyre::Result; | ||
|
||
// Loads policies from a wrapped PolicyLoader, | ||
// ensuring they're valid before passing them up to the caller. | ||
|
||
use crate::policy::LocalPolicy; | ||
|
||
use super::PolicyLoader; | ||
|
||
pub struct ValidatingPolicyLoader { | ||
wrapped: Box<dyn PolicyLoader>, | ||
} | ||
|
||
impl ValidatingPolicyLoader { | ||
pub fn new(wrapped: Box<dyn PolicyLoader>) -> ValidatingPolicyLoader { | ||
ValidatingPolicyLoader { wrapped } | ||
} | ||
} | ||
|
||
impl PolicyLoader for ValidatingPolicyLoader { | ||
fn load_policies(&self) -> Result<Vec<LocalPolicy>> { | ||
let raw_policies = self.wrapped.load_policies()?; | ||
|
||
for policy in raw_policies.iter() { | ||
policy.validate()?; | ||
} | ||
|
||
Ok(raw_policies) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
use color_eyre::{eyre::bail, Report, Result}; | ||
use jsonwebtoken::Algorithm; | ||
use serde::Deserialize; | ||
|
||
#[derive(Debug, Clone, Deserialize)] | ||
pub struct AcceptedAlgorithm(Algorithm); | ||
|
||
impl AcceptedAlgorithm { | ||
pub fn new(alg: Algorithm) -> Result<AcceptedAlgorithm> { | ||
if !ACCEPTABLE_ALGORITHMS.contains(&alg) { | ||
bail!("algorithm is not supported: {:?}", alg); | ||
} | ||
|
||
Ok(AcceptedAlgorithm(alg)) | ||
} | ||
|
||
pub fn wrapped(&self) -> Algorithm { | ||
self.0.clone() | ||
} | ||
} | ||
|
||
pub const ACCEPTABLE_ALGORITHMS: [Algorithm; 3] = | ||
[Algorithm::RS256, Algorithm::RS384, Algorithm::RS512]; | ||
|
||
impl TryFrom<String> for AcceptedAlgorithm { | ||
type Error = Report; | ||
fn try_from(value: String) -> Result<Self, Self::Error> { | ||
let alg: Result<Algorithm> = match value.to_lowercase().as_str() { | ||
"rs256" => Ok(Algorithm::RS256), | ||
"rs384" => Ok(Algorithm::RS384), | ||
"rs512" => Ok(Algorithm::RS512), | ||
_ => { | ||
bail!("unsupported algorithm") | ||
} | ||
}; | ||
AcceptedAlgorithm::new(alg?) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
use std::collections::{HashMap, HashSet}; | ||
|
||
use color_eyre::{ | ||
eyre::{bail, Context}, | ||
Result, | ||
}; | ||
use jsonwebtoken::jwk::JwkSet; | ||
use reqwest::Url; | ||
use serde::Deserialize; | ||
|
||
use super::{algorithm::AcceptedAlgorithm, policy_with_jwks::PolicyWithJWKS}; | ||
|
||
// A policy without its JWKS loaded | ||
#[derive(Deserialize, Debug)] | ||
pub struct LocalPolicy { | ||
pub issuer: HashSet<String>, | ||
pub subject: String, | ||
pub algorithm: String, | ||
pub jwks_url: String, | ||
pub allowed_scopes: HashMap<String, String>, | ||
} | ||
|
||
impl LocalPolicy { | ||
pub fn attach_jwks(self, jwks: JwkSet) -> Result<PolicyWithJWKS> { | ||
let alg: AcceptedAlgorithm = self.algorithm.try_into()?; | ||
|
||
Ok(PolicyWithJWKS { | ||
issuer: self.issuer, | ||
jwks, | ||
algorithm: alg, | ||
allowed_scopes: self.allowed_scopes, | ||
subject: self.subject, | ||
}) | ||
} | ||
|
||
pub fn validate(&self) -> Result<()> { | ||
self.validate_iss()?; | ||
self.validate_alg()?; | ||
self.validate_sub()?; | ||
self.validate_jwks_url()?; | ||
self.validate_allowed_scopes()?; | ||
|
||
Ok(()) | ||
} | ||
|
||
fn validate_iss(&self) -> Result<()> { | ||
if self.issuer.is_empty() { | ||
bail!("no issuer set") | ||
} | ||
|
||
for iss in self.issuer.iter() { | ||
if iss.is_empty() { | ||
bail!("issuer has empty entry") | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
fn validate_sub(&self) -> Result<()> { | ||
if self.subject.is_empty() { | ||
bail!("subject is empty") | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
fn validate_alg(&self) -> Result<()> { | ||
let alg: Result<AcceptedAlgorithm> = self.algorithm.clone().try_into(); | ||
if let Err(e) = alg { | ||
bail!("algorithm is invalid: {}", e); | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
fn validate_jwks_url(&self) -> Result<()> { | ||
if self.jwks_url.is_empty() { | ||
bail!("jwks_url is empty"); | ||
} | ||
|
||
Url::parse(&self.jwks_url).wrap_err("jwks_url is not a valid url")?; | ||
Ok(()) | ||
} | ||
|
||
fn validate_allowed_scopes(&self) -> Result<()> { | ||
if self.allowed_scopes.is_empty() { | ||
bail!("no scopes are allowed, nothing can satisfy this policy"); | ||
} | ||
|
||
for (key, value) in self.allowed_scopes.iter() { | ||
if key.is_empty() { | ||
bail!("allowed scope with empty key"); | ||
} else if value.is_empty() { | ||
bail!("allowed scope {} has empty value", key); | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
mod algorithm; | ||
mod local_policy; | ||
mod policy_with_jwks; | ||
|
||
pub use algorithm::ACCEPTABLE_ALGORITHMS; | ||
pub use local_policy::LocalPolicy; | ||
pub use policy_with_jwks::PolicyWithJWKS; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
use std::collections::{HashMap, HashSet}; | ||
|
||
use jsonwebtoken::jwk::JwkSet; | ||
use serde::Deserialize; | ||
|
||
use super::algorithm::AcceptedAlgorithm; | ||
|
||
// A policy without its JWKS loaded | ||
#[derive(Deserialize, Debug, Clone)] | ||
pub struct PolicyWithJWKS { | ||
pub issuer: HashSet<String>, | ||
pub subject: String, | ||
pub algorithm: AcceptedAlgorithm, | ||
pub jwks: JwkSet, | ||
pub allowed_scopes: HashMap<String, String>, // TODO: I don't like using strings to represent scopes, but don't want to update this app when new scopes are made available. Any way to look at the Tailscale API to get available scopes?? | ||
} | ||
|
||
impl PolicyWithJWKS { | ||
pub fn check_scope_allowed(&self, scope_name: &str, scope_value: &str) -> bool { | ||
match self.allowed_scopes.get(scope_name) { | ||
Some(allowed_value) => { | ||
(allowed_value == "read" && scope_value == "read") | ||
|| (allowed_value == "write" && scope_value == "write") | ||
|| (allowed_value == "write" && scope_value == "read") | ||
} | ||
None => false, | ||
} | ||
} | ||
} |
Oops, something went wrong.