1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#![allow(missing_docs)] // delete when we move away from the `property` crate.
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};

use asset_container::AssetManager;
use tracing::trace;
use wick_asset_reference::AssetReference;

use crate::audit::Audit;
use crate::config::template_config::Renderable;
use crate::{config, Result};
mod resources;
pub use resources::*;

#[derive(
  Debug, Clone, derive_builder::Builder, derive_asset_container::AssetManager, property::Property, serde::Serialize,
)]
#[property(get(public), set(public), mut(public, suffix = "_mut"))]
#[asset(asset(AssetReference))]
#[must_use]
/// A Wick lockdown configuration.
///
/// A lockdown configuration defines what a wick application can or can't do.
pub struct LockdownConfiguration {
  /// The source (i.e. url or file on disk) of the configuration.
  #[asset(skip)]
  #[property(skip)]
  #[serde(skip_serializing_if = "Option::is_none")]
  pub(crate) source: Option<PathBuf>,

  /// Any metadata associated with the configuration.
  #[asset(skip)]
  #[builder(default)]
  #[serde(skip_serializing_if = "Option::is_none")]
  pub(crate) metadata: Option<config::Metadata>,

  /// Resources and how to restrict them.
  #[asset(skip)]
  #[serde(skip_serializing_if = "Vec::is_empty")]
  pub(crate) resources: Vec<ResourceRestriction>,

  /// The environment this configuration has access to.
  #[asset(skip)]
  #[builder(default)]
  #[property(skip)]
  #[serde(skip_serializing_if = "Option::is_none")]
  pub(crate) env: Option<HashMap<String, String>>,
}

impl LockdownConfiguration {
  /// Return the version of the application.
  #[must_use]
  pub fn version(&self) -> Option<&str> {
    self.metadata.as_ref().map(|m| m.version.as_str())
  }

  /// Initialize the configuration.
  pub(crate) fn initialize(&mut self) -> Result<&Self> {
    // This pre-renders the component config's resources without access to the environment.
    trace!(
      resource_restrictions = ?self.resources,
      "initializing lockdown configuration"
    );
    self
      .resources
      .render_config(self.source.as_deref(), None, self.env.as_ref())?;

    Ok(self)
  }

  /// Set the source location of the configuration.
  pub fn set_source(&mut self, source: &Path) {
    let source = source.to_path_buf();
    self.source = Some(source);
  }

  pub(super) fn update_baseurls(&self) {
    #[allow(clippy::expect_used)]
    let mut source = self.source.clone().expect("No source set for this configuration");
    // Source is (should be) a file, so pop the filename before setting the baseurl.
    if !source.is_dir() {
      source.pop();
    }
    self.set_baseurl(&source);
  }

  /// Return the environment variables for this configuration.
  #[must_use]
  pub const fn env(&self) -> Option<&HashMap<String, String>> {
    None
  }

  /// Validate this configuration is good.
  #[allow(clippy::missing_const_for_fn)]
  pub fn validate(&self) -> Result<()> {
    /* placeholder */
    Ok(())
  }
}

impl Renderable for LockdownConfiguration {
  fn render_config(
    &mut self,
    source: Option<&Path>,
    root_config: Option<&wick_packet::RuntimeConfig>,
    env: Option<&HashMap<String, String>>,
  ) -> Result<()> {
    self.resources.render_config(source, root_config, env)?;
    Ok(())
  }
}

impl From<Vec<Audit>> for LockdownConfiguration {
  fn from(value: Vec<Audit>) -> Self {
    let mut url_restrictions: Vec<UrlRestriction> = Vec::new();
    let mut volume_restrictions: Vec<VolumeRestriction> = Vec::new();
    let mut tcpport_restrictions: Vec<PortRestriction> = Vec::new();
    let mut udpport_restrictions: Vec<PortRestriction> = Vec::new();
    let mut restrictions: Vec<ResourceRestriction> = Vec::new();
    let mut reverse_map: HashMap<&crate::audit::AuditedResource, HashSet<&String>> = HashMap::new();

    for audit in &value {
      for resource in &audit.resources {
        let entry = reverse_map.entry(&resource.resource).or_insert_with(HashSet::new);
        entry.insert(&audit.name);
      }
    }

    for (resource, components) in reverse_map {
      let components: Vec<_> = components.iter().map(|s| (*s).clone()).collect();
      match resource {
        crate::audit::AuditedResource::TcpPort(v) => tcpport_restrictions.push(PortRestriction::new_from_template(
          components,
          &v.address,
          v.port.to_string(),
        )),
        crate::audit::AuditedResource::UdpPort(v) => udpport_restrictions.push(PortRestriction::new_from_template(
          components,
          &v.address,
          v.port.to_string(),
        )),
        crate::audit::AuditedResource::Url(v) => {
          url_restrictions.push(UrlRestriction::new_from_template(components, v.url.as_str()));
        }
        crate::audit::AuditedResource::Volume(v) => volume_restrictions.push(VolumeRestriction::new_from_template(
          components,
          v.path.to_string_lossy(),
        )),
      }
    }

    restrictions.extend(tcpport_restrictions.into_iter().map(ResourceRestriction::TcpPort));
    restrictions.extend(udpport_restrictions.into_iter().map(ResourceRestriction::UdpPort));
    restrictions.extend(url_restrictions.into_iter().map(ResourceRestriction::Url));
    restrictions.extend(volume_restrictions.into_iter().map(ResourceRestriction::Volume));

    LockdownConfiguration {
      source: None,
      metadata: None,
      resources: restrictions,
      env: None,
    }
  }
}