use crate::cloud::CloudOptions; use crate::config::HazeConfig; use crate::exec::exec; use crate::image::pull_image; use crate::service::{split_cmnd, ServiceTrait}; use crate::Result; use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; use miette::{IntoDiagnostic, WrapErr}; use tokio::fs::write; #[derive(Debug, Clone, Eq, PartialEq)] pub struct ClamIcap; #[async_trait::async_trait] impl ServiceTrait for ClamIcap { fn name(&self) -> &str { "clamav-icap" } fn env(&self) -> &[&str] { &[ "ICAP_HOST=clamav-icap", "ICAP_PORT=1344", "ICAP_REQUEST=avscan", "ICAP_HEADER=X-Infection-Found", "ICAP_MODE=reqmod", ] } async fn spawn( &self, docker: &Docker, cloud_id: &str, network: &str, _config: &HazeConfig, _options: &CloudOptions, ) -> Result> { let image = "ghcr.io/icewind1991/icap-clamav-service-tls"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); let config = ContainerCreateBody { image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { "haze-type".into() => self.name().into(), "haze-cloud-id".into() => cloud_id.into() }), networking_config: Some(NetworkingConfig { endpoints_config: Some(hashmap! { network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } }), }), ..Default::default() }; let id = docker .create_container(options, config) .await .into_diagnostic()? .id; docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } fn container_name(&self, cloud_id: &str) -> Option { Some(format!("{}-clamav-icap", cloud_id)) } fn apps(&self) -> &'static [&'static str] { &["files_antivirus"] } async fn post_setup( &self, _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, ) -> Result>> { Ok(vec![ split_cmnd("occ config:app:set files_antivirus av_mode --value=icap"), split_cmnd("occ config:app:set files_antivirus av_host --value=clamav-icap"), split_cmnd("occ config:app:set files_antivirus av_port --value=1344"), split_cmnd("occ config:app:set files_antivirus av_icap_request_service --value=avscan"), split_cmnd("occ config:app:set files_antivirus av_icap_response_header --value=X-Infection-Found"), ]) } } #[derive(Debug, Clone, Eq, PartialEq)] pub struct ClamIcapTls; #[async_trait::async_trait] impl ServiceTrait for ClamIcapTls { fn name(&self) -> &str { "clamav-icap-tls" } fn env(&self) -> &[&str] { &[ "ICAP_HOST=clamav-icap-tls", "ICAP_PORT=1345", "ICAP_REQUEST=avscan", "ICAP_HEADER=X-Infection-Found", ] } async fn spawn( &self, docker: &Docker, cloud_id: &str, network: &str, _config: &HazeConfig, _options: &CloudOptions, ) -> Result> { let image = "ghcr.io/icewind1991/icap-clamav-service-tls"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); let config = ContainerCreateBody { image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { "haze-type".into() => self.name().into(), "haze-cloud-id".into() => cloud_id.into() }), networking_config: Some(NetworkingConfig { endpoints_config: Some(hashmap! { network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } }), }), ..Default::default() }; let id = docker .create_container(options, config) .await .into_diagnostic()? .id; docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } fn container_name(&self, cloud_id: &str) -> Option { Some(format!("{}-{}", cloud_id, self.name())) } fn apps(&self) -> &'static [&'static str] { &["files_antivirus"] } async fn post_setup( &self, docker: &Docker, cloud_id: &str, config: &HazeConfig, ) -> Result>> { let mut cert = Vec::new(); exec( docker, self.container_name(cloud_id).unwrap(), "root", vec!["cat", "/local/cert.pem"], Vec::::new(), Some(&mut cert), ) .await .wrap_err("Failed to get icap certificate")?; let cert_path = config.work_dir.join(cloud_id).join("data/icap-cert.pem"); write(cert_path, cert) .await .into_diagnostic() .wrap_err("Failed to write icap certificate")?; Ok(vec![ split_cmnd("occ config:app:set files_antivirus av_mode --value=icap"), split_cmnd("occ config:app:set files_antivirus av_icap_tls --value=1"), split_cmnd("occ config:app:set files_antivirus av_host --value=clamav-icap-tls"), split_cmnd("occ config:app:set files_antivirus av_port --value=1345"), split_cmnd("occ config:app:set files_antivirus av_icap_request_service --value=avscan"), split_cmnd("occ config:app:set files_antivirus av_icap_response_header --value=X-Infection-Found"), split_cmnd("occ security:certificates:import data/icap-cert.pem"), ]) } } #[derive(Debug, Clone, Eq, PartialEq)] pub struct Clam; #[async_trait::async_trait] impl ServiceTrait for Clam { fn name(&self) -> &str { "clamav" } fn apps(&self) -> &'static [&'static str] { &["files_antivirus"] } async fn post_setup( &self, _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, ) -> Result>> { Ok(vec![ split_cmnd("occ config:app:set files_antivirus av_mode --value=executable"), split_cmnd("occ config:app:set files_antivirus av_path --value=/bin/clamscan"), ]) } } #[derive(Debug, Clone, Eq, PartialEq)] pub struct ClamSocket; #[async_trait::async_trait] impl ServiceTrait for ClamSocket { fn name(&self) -> &str { "clamav-socket" } async fn spawn( &self, docker: &Docker, cloud_id: &str, network: &str, _config: &HazeConfig, _options: &CloudOptions, ) -> Result> { let image = "clamav/clamav"; pull_image(docker, image).await?; let options = Some(CreateContainerOptions { name: self.container_name(cloud_id), ..CreateContainerOptions::default() }); let config = ContainerCreateBody { image: Some(image.into()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), ..Default::default() }), labels: Some(hashmap! { "haze-type".into() => self.name().into(), "haze-cloud-id".into() => cloud_id.into() }), networking_config: Some(NetworkingConfig { endpoints_config: Some(hashmap! { network.into() => EndpointSettings { aliases: Some(vec![self.name().to_string()]), ..Default::default() } }), }), ..Default::default() }; let id = docker .create_container(options, config) .await .into_diagnostic()? .id; docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } fn container_name(&self, cloud_id: &str) -> Option { Some(format!("{}-{}", cloud_id, self.name())) } fn apps(&self) -> &'static [&'static str] { &["files_antivirus"] } async fn post_setup( &self, _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, ) -> Result>> { Ok(vec![ split_cmnd("occ config:app:set files_antivirus av_mode --value=socket"), split_cmnd( "occ config:app:set files_antivirus av_socket --value=tcp://clamav-socket:3310", ), ]) } }