Files
lighthouse/common/filesystem/src/lib.rs
ethDreamer ba55e140ae Enable Compatibility with Windows (#2333)
## Issue Addressed

Windows incompatibility.

## Proposed Changes

On windows, lighthouse needs to default to STDIN as tty doesn't exist. Also Windows uses ACLs for file permissions. So to mirror chmod 600, we will remove every entry in a file's ACL and add only a single SID that is an alias for the file owner.

Beyond that, there were several changes made to different unit tests because windows has slightly different error messages as well as frustrating nuances around killing a process :/

## Additional Info

Tested on my Windows VM and it appears to work, also compiled & tested on Linux with these changes. Permissions look correct on both platforms now. Just waiting for my validator to activate on Prater so I can test running full validator client on windows.

Co-authored-by: ethDreamer <37123614+ethDreamer@users.noreply.github.com>
Co-authored-by: Michael Sproul <micsproul@gmail.com>
2021-05-19 23:05:16 +00:00

145 lines
5.0 KiB
Rust

use std::fs::File;
use std::io;
use std::io::Write;
use std::path::Path;
#[cfg(windows)]
use winapi::um::winnt::{FILE_GENERIC_READ, FILE_GENERIC_WRITE, STANDARD_RIGHTS_ALL};
/// This is the security identifier in Windows for the owner of a file. See:
/// - https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/security-identifiers-in-windows#well-known-sids-all-versions-of-windows
#[cfg(windows)]
const OWNER_SID_STR: &str = "S-1-3-4";
/// We don't need any of the `AceFlags` listed here:
/// - https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header
#[cfg(windows)]
const OWNER_ACL_ENTRY_FLAGS: u8 = 0;
/// Generic Rights:
/// - https://docs.microsoft.com/en-us/windows/win32/fileio/file-security-and-access-rights
/// Individual Read/Write/Execute Permissions (referenced in generic rights link):
/// - https://docs.microsoft.com/en-us/windows/win32/wmisdk/file-and-directory-access-rights-constants
/// STANDARD_RIGHTS_ALL
/// - https://docs.microsoft.com/en-us/windows/win32/secauthz/access-mask
#[cfg(windows)]
const OWNER_ACL_ENTRY_MASK: u32 = FILE_GENERIC_READ | FILE_GENERIC_WRITE | STANDARD_RIGHTS_ALL;
#[derive(Debug)]
pub enum Error {
/// The file could not be created
UnableToCreateFile(io::Error),
/// The file could not be copied
UnableToCopyFile(io::Error),
/// The file could not be opened
UnableToOpenFile(io::Error),
/// The file could not be renamed
UnableToRenameFile(io::Error),
/// Failed to set permissions
UnableToSetPermissions(io::Error),
/// Failed to retrieve file metadata
UnableToRetrieveMetadata(io::Error),
/// Failed to write bytes to file
UnableToWriteFile(io::Error),
/// Failed to obtain file path
UnableToObtainFilePath,
/// Failed to convert string to SID
UnableToConvertSID(u32),
/// Failed to retrieve ACL for file
UnableToRetrieveACL(u32),
/// Failed to enumerate ACL entries
UnableToEnumerateACLEntries(u32),
/// Failed to add new ACL entry
UnableToAddACLEntry(String),
/// Failed to remove ACL entry
UnableToRemoveACLEntry(String),
}
/// Creates a file with `600 (-rw-------)` permissions.
pub fn create_with_600_perms<P: AsRef<Path>>(path: P, bytes: &[u8]) -> Result<(), Error> {
let path = path.as_ref();
let mut file = File::create(&path).map_err(Error::UnableToCreateFile)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perm = file
.metadata()
.map_err(Error::UnableToRetrieveMetadata)?
.permissions();
perm.set_mode(0o600);
file.set_permissions(perm)
.map_err(Error::UnableToSetPermissions)?;
}
file.write_all(bytes).map_err(Error::UnableToWriteFile)?;
#[cfg(windows)]
{
restrict_file_permissions(path)?;
}
Ok(())
}
pub fn restrict_file_permissions<P: AsRef<Path>>(path: P) -> Result<(), Error> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let file = File::open(path.as_ref()).map_err(Error::UnableToOpenFile)?;
let mut perm = file
.metadata()
.map_err(Error::UnableToRetrieveMetadata)?
.permissions();
perm.set_mode(0o600);
file.set_permissions(perm)
.map_err(Error::UnableToSetPermissions)?;
}
#[cfg(windows)]
{
use winapi::um::winnt::PSID;
use windows_acl::acl::{AceType, ACL};
use windows_acl::helper::sid_to_string;
let path_str = path
.as_ref()
.to_str()
.ok_or(Error::UnableToObtainFilePath)?;
let mut acl = ACL::from_file_path(&path_str, false).map_err(Error::UnableToRetrieveACL)?;
let owner_sid =
windows_acl::helper::string_to_sid(OWNER_SID_STR).map_err(Error::UnableToConvertSID)?;
let entries = acl.all().map_err(Error::UnableToEnumerateACLEntries)?;
// add single entry for file owner
acl.add_entry(
owner_sid.as_ptr() as PSID,
AceType::AccessAllow,
OWNER_ACL_ENTRY_FLAGS,
OWNER_ACL_ENTRY_MASK,
)
.map_err(|code| {
Error::UnableToAddACLEntry(format!(
"Failed to add ACL entry for SID {} error={}",
OWNER_SID_STR, code
))
})?;
// remove all AccessAllow entries from the file that aren't the owner_sid
for entry in &entries {
if let Some(ref entry_sid) = entry.sid {
let entry_sid_str = sid_to_string(entry_sid.as_ptr() as PSID)
.unwrap_or_else(|_| "BadFormat".to_string());
if entry_sid_str != OWNER_SID_STR {
acl.remove(entry_sid.as_ptr() as PSID, Some(AceType::AccessAllow), None)
.map_err(|_| {
Error::UnableToRemoveACLEntry(format!(
"Failed to remove ACL entry for SID {}",
entry_sid_str
))
})?;
}
}
}
}
Ok(())
}