mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-04-27 19:35:32 +00:00
Merge pull request #6257 from amshinde/kata-ctl-env
kata-ctl: add function to get platform protection.
This commit is contained in:
commit
07e49c63e1
@ -49,5 +49,6 @@ reqwest = { version = "0.11", default-features = false, features = ["json", "blo
|
||||
[dev-dependencies]
|
||||
semver = "1.0.12"
|
||||
tempfile = "3.1.0"
|
||||
nix = "0.25.0"
|
||||
test-utils = { path = "../../libs/test-utils" }
|
||||
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" }
|
||||
|
@ -7,6 +7,7 @@
|
||||
pub use arch_specific::*;
|
||||
|
||||
mod arch_specific {
|
||||
use crate::check;
|
||||
use crate::types::*;
|
||||
use anyhow::Result;
|
||||
use std::path::Path;
|
||||
@ -39,4 +40,10 @@ mod arch_specific {
|
||||
pub fn get_checks() -> Option<&'static [CheckItem<'static>]> {
|
||||
Some(CHECK_LIST)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
// Guest protection is not supported on ARM64.
|
||||
pub fn available_guest_protection() -> Result<check::GuestProtection, check::ProtectionError> {
|
||||
Ok(check::GuestProtection::NoProtection)
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use crate::types::*;
|
||||
pub use arch_specific::*;
|
||||
|
||||
mod arch_specific {
|
||||
use crate::check;
|
||||
use anyhow::Result;
|
||||
|
||||
pub const ARCH_CPU_VENDOR_FIELD: &str = "";
|
||||
@ -20,4 +21,19 @@ mod arch_specific {
|
||||
pub fn get_checks() -> Option<&'static [CheckItem<'static>]> {
|
||||
None
|
||||
}
|
||||
|
||||
const PEF_SYS_FIRMWARE_DIR: &str = "/sys/firmware/ultravisor/";
|
||||
|
||||
pub fn available_guest_protection() -> Result<check::GuestProtection, check::ProtectionError> {
|
||||
if !Uid::effective().is_root() {
|
||||
return Err(check::ProtectionError::NoPerms);
|
||||
}
|
||||
|
||||
let metadata = fs::metadata(PEF_SYS_FIRMWARE_DIR);
|
||||
if metadata.is_ok() && metadata.unwrap().is_dir() {
|
||||
Ok(check::GuestProtection::Pef)
|
||||
}
|
||||
|
||||
Ok(check::GuestProtection::NoProtection)
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,10 @@ mod arch_specific {
|
||||
use crate::check;
|
||||
use crate::types::*;
|
||||
use anyhow::{anyhow, Result};
|
||||
use nix::unistd::Uid;
|
||||
use std::collections::HashMap;
|
||||
use std::io::BufRead;
|
||||
use std::io::BufReader;
|
||||
|
||||
const CPUINFO_DELIMITER: &str = "processor ";
|
||||
const CPUINFO_FEATURES_TAG: &str = "features";
|
||||
@ -65,4 +69,115 @@ mod arch_specific {
|
||||
pub fn get_checks() -> Option<&'static [CheckItem<'static>]> {
|
||||
Some(CHECK_LIST)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn retrieve_cpu_facilities() -> Result<HashMap<i32, bool>> {
|
||||
let f = std::fs::File::open(check::PROC_CPUINFO)?;
|
||||
let mut reader = BufReader::new(f);
|
||||
let mut contents = String::new();
|
||||
let facilities_field = "facilities";
|
||||
let mut facilities = HashMap::new();
|
||||
|
||||
while reader.read_line(&mut contents)? > 0 {
|
||||
let fields: Vec<&str> = contents.split_whitespace().collect();
|
||||
if fields.len() < 2 {
|
||||
contents.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
if !fields[0].starts_with(facilities_field) {
|
||||
contents.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut start = 1;
|
||||
if fields[1] == ":" {
|
||||
start = 2;
|
||||
}
|
||||
|
||||
for field in fields.iter().skip(start) {
|
||||
let bit = field.parse::<i32>()?;
|
||||
facilities.insert(bit, true);
|
||||
}
|
||||
return Ok(facilities);
|
||||
}
|
||||
|
||||
Ok(facilities)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn check_cmd_line(
|
||||
kernel_cmdline_path: &str,
|
||||
search_param: &str,
|
||||
search_values: &[&str],
|
||||
) -> Result<bool> {
|
||||
let f = std::fs::File::open(kernel_cmdline_path)?;
|
||||
let reader = BufReader::new(f);
|
||||
|
||||
let check_fn = if search_values.is_empty() {
|
||||
|param: &str, search_param: &str, _search_values: &[&str]| {
|
||||
return param.eq_ignore_ascii_case(search_param);
|
||||
}
|
||||
} else {
|
||||
|param: &str, search_param: &str, search_values: &[&str]| {
|
||||
let split: Vec<&str> = param.splitn(2, "=").collect();
|
||||
if split.len() < 2 || split[0] != search_param {
|
||||
return false;
|
||||
}
|
||||
|
||||
for value in search_values {
|
||||
if value.eq_ignore_ascii_case(split[1]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
for line in reader.lines() {
|
||||
for field in line?.split_whitespace() {
|
||||
if check_fn(field, search_param, search_values) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
// Guest protection is not supported on ARM64.
|
||||
pub fn available_guest_protection() -> Result<check::GuestProtection, check::ProtectionError> {
|
||||
if !Uid::effective().is_root() {
|
||||
return Err(check::ProtectionError::NoPerms)?;
|
||||
}
|
||||
|
||||
let facilities = retrieve_cpu_facilities().map_err(|err| {
|
||||
check::ProtectionError::CheckFailed(format!(
|
||||
"Error retrieving cpu facilities file : {}",
|
||||
err.to_string()
|
||||
))
|
||||
})?;
|
||||
|
||||
// Secure Execution
|
||||
// https://www.kernel.org/doc/html/latest/virt/kvm/s390-pv.html
|
||||
let se_cpu_facility_bit: i32 = 158;
|
||||
if !facilities.contains_key(&se_cpu_facility_bit) {
|
||||
return Ok(check::GuestProtection::NoProtection);
|
||||
}
|
||||
|
||||
let cmd_line_values = vec!["1", "on", "y", "yes"];
|
||||
let se_cmdline_param = "prot_virt";
|
||||
|
||||
let se_cmdline_present =
|
||||
check_cmd_line("/proc/cmdline", se_cmdline_param, &cmd_line_values)
|
||||
.map_err(|err| check::ProtectionError::CheckFailed(err.to_string()))?;
|
||||
|
||||
if !se_cmdline_present {
|
||||
return Err(check::ProtectionError::InvalidValue(String::from(
|
||||
"Protected Virtualization is not enabled on kernel command line!",
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(check::GuestProtection::Se)
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,19 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use arch_specific::*;
|
||||
|
||||
mod arch_specific {
|
||||
use crate::check;
|
||||
use crate::check::{GuestProtection, ProtectionError};
|
||||
use crate::types::*;
|
||||
use anyhow::{anyhow, Result};
|
||||
use nix::unistd::Uid;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
const CPUINFO_DELIMITER: &str = "\nprocessor";
|
||||
const CPUINFO_FLAGS_TAG: &str = "flags";
|
||||
@ -61,4 +67,149 @@ mod arch_specific {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn retrieve_cpu_flags() -> Result<String> {
|
||||
let cpu_info = check::get_single_cpu_info(check::PROC_CPUINFO, CPUINFO_DELIMITER)?;
|
||||
|
||||
let cpu_flags = check::get_cpu_flags(&cpu_info, CPUINFO_FLAGS_TAG).map_err(|e| {
|
||||
anyhow!(
|
||||
"Error parsing CPU flags, file {:?}, {:?}",
|
||||
check::PROC_CPUINFO,
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(cpu_flags)
|
||||
}
|
||||
|
||||
pub const TDX_SYS_FIRMWARE_DIR: &str = "/sys/firmware/tdx_seam/";
|
||||
pub const TDX_CPU_FLAG: &str = "tdx";
|
||||
pub const SEV_KVM_PARAMETER_PATH: &str = "/sys/module/kvm_amd/parameters/sev";
|
||||
pub const SNP_KVM_PARAMETER_PATH: &str = "/sys/module/kvm_amd/parameters/sev_snp";
|
||||
|
||||
pub fn available_guest_protection() -> Result<GuestProtection, ProtectionError> {
|
||||
if !Uid::effective().is_root() {
|
||||
return Err(ProtectionError::NoPerms);
|
||||
}
|
||||
|
||||
arch_guest_protection(
|
||||
TDX_SYS_FIRMWARE_DIR,
|
||||
TDX_CPU_FLAG,
|
||||
SEV_KVM_PARAMETER_PATH,
|
||||
SNP_KVM_PARAMETER_PATH,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn arch_guest_protection(
|
||||
tdx_path: &str,
|
||||
tdx_flag: &str,
|
||||
sev_path: &str,
|
||||
snp_path: &str,
|
||||
) -> Result<GuestProtection, ProtectionError> {
|
||||
let flags =
|
||||
retrieve_cpu_flags().map_err(|err| ProtectionError::CheckFailed(err.to_string()))?;
|
||||
|
||||
let metadata = fs::metadata(tdx_path);
|
||||
|
||||
if metadata.is_ok() && metadata.unwrap().is_dir() && flags.contains(tdx_flag) {
|
||||
return Ok(GuestProtection::Tdx);
|
||||
}
|
||||
|
||||
let check_contents = |file_name: &str| -> Result<bool, ProtectionError> {
|
||||
let file_path = Path::new(file_name);
|
||||
if !file_path.exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let contents = fs::read_to_string(file_name).map_err(|err| {
|
||||
ProtectionError::CheckFailed(format!("Error reading file {} : {}", file_name, err))
|
||||
})?;
|
||||
|
||||
if contents == "Y" {
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
};
|
||||
|
||||
if check_contents(snp_path)? {
|
||||
return Ok(GuestProtection::Snp);
|
||||
}
|
||||
|
||||
if check_contents(sev_path)? {
|
||||
return Ok(GuestProtection::Sev);
|
||||
}
|
||||
|
||||
Ok(GuestProtection::NoProtection)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::check;
|
||||
use nix::unistd::Uid;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn test_available_guest_protection_no_privileges() {
|
||||
if !Uid::effective().is_root() {
|
||||
let res = available_guest_protection();
|
||||
assert!(res.is_err());
|
||||
assert_eq!(
|
||||
"No permission to check guest protection",
|
||||
res.unwrap_err().to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_arch_guest_protection_snp() {
|
||||
// Test snp
|
||||
let dir = tempdir().unwrap();
|
||||
let snp_file_path = dir.path().join("sev_snp");
|
||||
let path = snp_file_path.clone();
|
||||
let mut snp_file = fs::File::create(snp_file_path).unwrap();
|
||||
writeln!(snp_file, "Y").unwrap();
|
||||
|
||||
let actual =
|
||||
arch_guest_protection("/xyz/tmp", TDX_CPU_FLAG, "/xyz/tmp", path.to_str().unwrap());
|
||||
assert!(actual.is_ok());
|
||||
assert_eq!(actual.unwrap(), check::GuestProtection::Snp);
|
||||
|
||||
writeln!(snp_file, "N").unwrap();
|
||||
let actual =
|
||||
arch_guest_protection("/xyz/tmp", TDX_CPU_FLAG, "/xyz/tmp", path.to_str().unwrap());
|
||||
assert!(actual.is_ok());
|
||||
assert_eq!(actual.unwrap(), check::GuestProtection::NoProtection);
|
||||
}
|
||||
|
||||
fn test_arch_guest_protection_sev() {
|
||||
// Test sev
|
||||
let dir = tempdir().unwrap();
|
||||
let sev_file_path = dir.path().join("sev");
|
||||
let sev_path = sev_file_path.clone();
|
||||
let mut sev_file = fs::File::create(sev_file_path).unwrap();
|
||||
writeln!(sev_file, "Y").unwrap();
|
||||
|
||||
let actual = arch_guest_protection(
|
||||
"/xyz/tmp",
|
||||
TDX_CPU_FLAG,
|
||||
sev_path.to_str().unwrap(),
|
||||
"/xyz/tmp",
|
||||
);
|
||||
assert!(actual.is_ok());
|
||||
assert_eq!(actual.unwrap(), check::GuestProtection::Sev);
|
||||
|
||||
writeln!(sev_file, "N").unwrap();
|
||||
let actual = arch_guest_protection(
|
||||
"/xyz/tmp",
|
||||
TDX_CPU_FLAG,
|
||||
sev_path.to_str().unwrap(),
|
||||
"/xyz/tmp",
|
||||
);
|
||||
assert!(actual.is_ok());
|
||||
assert_eq!(actual.unwrap(), check::GuestProtection::NoProtection);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use reqwest::header::{CONTENT_TYPE, USER_AGENT};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
struct Release {
|
||||
tag_name: String,
|
||||
@ -125,6 +126,30 @@ pub fn check_cpu_attribs(
|
||||
Ok(missing_attribs)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum GuestProtection {
|
||||
NoProtection,
|
||||
Tdx,
|
||||
Sev,
|
||||
Snp,
|
||||
Pef,
|
||||
Se,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ProtectionError {
|
||||
#[error("No permission to check guest protection")]
|
||||
NoPerms,
|
||||
|
||||
#[error("Failed to check guest protection: {0}")]
|
||||
CheckFailed(String),
|
||||
|
||||
#[error("Invalid guest protection value: {0}")]
|
||||
InvalidValue(String),
|
||||
}
|
||||
|
||||
pub fn run_network_checks() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -144,6 +144,12 @@ pub fn get_generic_cpu_details(cpu_info_file: &str) -> Result<(String, String)>
|
||||
Ok((vendor, model))
|
||||
}
|
||||
|
||||
const VHOST_VSOCK_DEVICE: &str = "/dev/vhost-vsock";
|
||||
pub fn supports_vsocks(vsock_path: &str) -> Result<bool> {
|
||||
let metadata = fs::metadata(vsock_path)?;
|
||||
Ok(metadata.is_file())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -287,4 +293,30 @@ mod tests {
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_supports_vsocks_valid() {
|
||||
let dir = tempdir().unwrap();
|
||||
let file_path = dir.path().join("vhost-vsock");
|
||||
let path = file_path.clone();
|
||||
let _file = fs::File::create(file_path).unwrap();
|
||||
let res = supports_vsocks(path.to_str().unwrap()).unwrap();
|
||||
assert!(res);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_supports_vsocks_dir() {
|
||||
let dir = tempdir().unwrap();
|
||||
let file_path = dir.path().join("vhost-vsock");
|
||||
let path = file_path.clone();
|
||||
fs::create_dir(file_path).unwrap();
|
||||
let res = supports_vsocks(path.to_str().unwrap()).unwrap();
|
||||
assert!(!res);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_supports_vsocks_missing_file() {
|
||||
let res = supports_vsocks("/xyz/vhost-vsock");
|
||||
assert!(res.is_err());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user