wick_config/config/
lockdown_config.rs

1#![allow(missing_docs)] // delete when we move away from the `property` crate.
2use std::collections::{HashMap, HashSet};
3use std::path::{Path, PathBuf};
4
5use asset_container::AssetManager;
6use tracing::trace;
7use wick_asset_reference::AssetReference;
8
9use crate::audit::Audit;
10use crate::config::template_config::Renderable;
11use crate::{config, Result};
12mod resources;
13pub use resources::*;
14
15#[derive(
16  Debug, Clone, derive_builder::Builder, derive_asset_container::AssetManager, property::Property, serde::Serialize,
17)]
18#[property(get(public), set(public), mut(public, suffix = "_mut"))]
19#[asset(asset(AssetReference))]
20#[must_use]
21/// A Wick lockdown configuration.
22///
23/// A lockdown configuration defines what a wick application can or can't do.
24pub struct LockdownConfiguration {
25  /// The source (i.e. url or file on disk) of the configuration.
26  #[asset(skip)]
27  #[property(skip)]
28  #[serde(skip_serializing_if = "Option::is_none")]
29  pub(crate) source: Option<PathBuf>,
30
31  /// Any metadata associated with the configuration.
32  #[asset(skip)]
33  #[builder(default)]
34  #[serde(skip_serializing_if = "Option::is_none")]
35  pub(crate) metadata: Option<config::Metadata>,
36
37  /// Resources and how to restrict them.
38  #[asset(skip)]
39  #[serde(skip_serializing_if = "Vec::is_empty")]
40  pub(crate) resources: Vec<ResourceRestriction>,
41
42  /// The environment this configuration has access to.
43  #[asset(skip)]
44  #[builder(default)]
45  #[property(skip)]
46  #[serde(skip_serializing_if = "Option::is_none")]
47  pub(crate) env: Option<HashMap<String, String>>,
48}
49
50impl LockdownConfiguration {
51  /// Return the version of the application.
52  #[must_use]
53  pub fn version(&self) -> Option<&str> {
54    self.metadata.as_ref().map(|m| m.version.as_str())
55  }
56
57  /// Initialize the configuration.
58  pub(crate) fn initialize(&mut self) -> Result<&Self> {
59    // This pre-renders the component config's resources without access to the environment.
60    trace!(
61      resource_restrictions = ?self.resources,
62      "initializing lockdown configuration"
63    );
64    self
65      .resources
66      .render_config(self.source.as_deref(), None, self.env.as_ref())?;
67
68    Ok(self)
69  }
70
71  /// Set the source location of the configuration.
72  pub fn set_source(&mut self, source: &Path) {
73    let source = source.to_path_buf();
74    self.source = Some(source);
75  }
76
77  pub(super) fn update_baseurls(&self) {
78    #[allow(clippy::expect_used)]
79    let mut source = self.source.clone().expect("No source set for this configuration");
80    // Source is (should be) a file, so pop the filename before setting the baseurl.
81    if !source.is_dir() {
82      source.pop();
83    }
84    self.set_baseurl(&source);
85  }
86
87  /// Return the environment variables for this configuration.
88  #[must_use]
89  pub const fn env(&self) -> Option<&HashMap<String, String>> {
90    None
91  }
92
93  /// Validate this configuration is good.
94  #[allow(clippy::missing_const_for_fn)]
95  pub fn validate(&self) -> Result<()> {
96    /* placeholder */
97    Ok(())
98  }
99}
100
101impl Renderable for LockdownConfiguration {
102  fn render_config(
103    &mut self,
104    source: Option<&Path>,
105    root_config: Option<&wick_packet::RuntimeConfig>,
106    env: Option<&HashMap<String, String>>,
107  ) -> Result<()> {
108    self.resources.render_config(source, root_config, env)?;
109    Ok(())
110  }
111}
112
113impl From<Vec<Audit>> for LockdownConfiguration {
114  fn from(value: Vec<Audit>) -> Self {
115    let mut url_restrictions: Vec<UrlRestriction> = Vec::new();
116    let mut volume_restrictions: Vec<VolumeRestriction> = Vec::new();
117    let mut tcpport_restrictions: Vec<PortRestriction> = Vec::new();
118    let mut udpport_restrictions: Vec<PortRestriction> = Vec::new();
119    let mut restrictions: Vec<ResourceRestriction> = Vec::new();
120    let mut reverse_map: HashMap<&crate::audit::AuditedResource, HashSet<&String>> = HashMap::new();
121
122    for audit in &value {
123      for resource in &audit.resources {
124        let entry = reverse_map.entry(&resource.resource).or_insert_with(HashSet::new);
125        entry.insert(&audit.name);
126      }
127    }
128
129    for (resource, components) in reverse_map {
130      let components: Vec<_> = components.iter().map(|s| (*s).clone()).collect();
131      match resource {
132        crate::audit::AuditedResource::TcpPort(v) => tcpport_restrictions.push(PortRestriction::new_from_template(
133          components,
134          &v.address,
135          v.port.to_string(),
136        )),
137        crate::audit::AuditedResource::UdpPort(v) => udpport_restrictions.push(PortRestriction::new_from_template(
138          components,
139          &v.address,
140          v.port.to_string(),
141        )),
142        crate::audit::AuditedResource::Url(v) => {
143          url_restrictions.push(UrlRestriction::new_from_template(components, v.url.as_str()));
144        }
145        crate::audit::AuditedResource::Volume(v) => volume_restrictions.push(VolumeRestriction::new_from_template(
146          components,
147          v.path.to_string_lossy(),
148        )),
149      }
150    }
151
152    restrictions.extend(tcpport_restrictions.into_iter().map(ResourceRestriction::TcpPort));
153    restrictions.extend(udpport_restrictions.into_iter().map(ResourceRestriction::UdpPort));
154    restrictions.extend(url_restrictions.into_iter().map(ResourceRestriction::Url));
155    restrictions.extend(volume_restrictions.into_iter().map(ResourceRestriction::Volume));
156
157    LockdownConfiguration {
158      source: None,
159      metadata: None,
160      resources: restrictions,
161      env: None,
162    }
163  }
164}