Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add policy for file ops. #2997

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
377 changes: 212 additions & 165 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions mirrord/layer/src/detour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ pub(crate) enum Bypass {
/// Useful for operations that are version gated, and we want to bypass when the protocol
/// doesn't support them.
NotImplemented,

/// File `open` (any `open`-ish operation) was forced to be local, instead of remote, most
/// likely due to an operator fs policy.
OpenLocal,
}

impl Bypass {
Expand Down
1 change: 1 addition & 0 deletions mirrord/layer/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ impl From<HookError> for i64 {
HookError::BincodeEncode(_) => libc::EINVAL,
HookError::ResponseError(response_fail) => match response_fail {
ResponseError::IdsExhausted(_) => libc::ENOMEM,
ResponseError::OpenLocal => libc::ENOENT,
ResponseError::NotFound(_) => libc::ENOENT,
ResponseError::NotDirectory(_) => libc::ENOTDIR,
ResponseError::NotFile(_) => libc::EISDIR,
Expand Down
7 changes: 6 additions & 1 deletion mirrord/layer/src/file/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,12 @@ pub(crate) fn open(path: Detour<PathBuf>, open_options: OpenOptionsInternal) ->

ensure_not_ignored!(path, open_options.is_write());

let OpenFileResponse { fd: remote_fd } = RemoteFile::remote_open(path.clone(), open_options)?;
let OpenFileResponse { fd: remote_fd } = RemoteFile::remote_open(path.clone(), open_options)
.or_else(|fail| match fail {
// The operator has a policy that matches this `path` as local-only.
HookError::ResponseError(ResponseError::OpenLocal) => Detour::Bypass(Bypass::OpenLocal),
other => Detour::Error(other),
})?;

// TODO: Need a way to say "open a directory", right now `is_dir` always returns false.
// This requires having a fake directory name (`/fake`, for example), instead of just converting
Expand Down
38 changes: 38 additions & 0 deletions mirrord/operator/src/crd/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ pub struct MirrordPolicySpec {
/// target.
#[serde(default)]
pub env: EnvPolicy,

/// Overrides fs ops behaviour, granting control over them to the operator policy, instead of
/// the user config.
#[serde(default)]
pub fs: FsPolicy,
}

/// Custom cluster-wide resource for policies that limit what mirrord features users can use.
Expand Down Expand Up @@ -90,6 +95,11 @@ pub struct MirrordClusterPolicySpec {
/// target.
#[serde(default)]
pub env: EnvPolicy,

/// Overrides fs ops behaviour, granting control over them to the operator policy, instead of
/// the user config.
#[serde(default)]
pub fs: FsPolicy,
}

/// Policy for controlling environment variables access from mirrord instances.
Expand All @@ -104,9 +114,37 @@ pub struct EnvPolicy {
/// Variable names can be matched using `*` and `?` where `?` matches exactly one occurrence of
/// any character and `*` matches arbitrary many (including zero) occurrences of any character,
/// e.g. `DATABASE_*` will match `DATABASE_URL` and `DATABASE_PORT`.
#[serde(default)]
pub exclude: HashSet<String>,
}

/// File operations policy that mimics the mirrord fs config.
///
/// Allows the operator control over remote file ops behaviour, overriding what the user has set in
/// their mirrord config file, if it matches something in one of the lists (regex sets) of this
/// struct.
#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub struct FsPolicy {
/// The file can only be opened in read-only mode, otherwise the operator returns an IO error.
#[serde(default)]
pub read_only: HashSet<String>,

/// The file may be opened in read-write mode.
#[serde(default)]
pub read_write: HashSet<String>,

/// The file cannot be opened in the remote target.
///
/// `open` calls that match this are forced to be opened in the local user's machine.
#[serde(default)]
pub local: HashSet<String>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would local do? client will see that and fallback to local?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea, but not what I had in mind.

I thought that local here would mean that this file has to be read locally, so the operator returns NotFound if the user does not put it under config.fs.local.

Returning a OpenLocal (or something like that) and having mirrord turn this open call into a local one would be neat. Should I do this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd assume the notfound to be returned on the next proeprty (not_found) - but yeah I like that idea:)


/// Any file that matches this returns a file not found error from the operator.
#[serde(default)]
pub not_found: HashSet<String>,
}

#[test]
fn check_one_api_group() {
use kube::Resource;
Expand Down
2 changes: 1 addition & 1 deletion mirrord/protocol/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mirrord-protocol"
version = "1.13.2"
version = "1.13.3"
authors.workspace = true
description.workspace = true
documentation.workspace = true
Expand Down
3 changes: 3 additions & 0 deletions mirrord/protocol/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ pub enum ResponseError {

#[error("Failed stripping path with `{0}`!")]
StripPrefix(String),

#[error("File has to be opened locally!")]
OpenLocal,
}

impl From<StripPrefixError> for ResponseError {
Expand Down
3 changes: 3 additions & 0 deletions mirrord/protocol/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ pub static READDIR_BATCH_VERSION: LazyLock<VersionReq> =
pub static MKDIR_VERSION: LazyLock<VersionReq> =
LazyLock::new(|| ">=1.13.0".parse().expect("Bad Identifier"));

pub static OPEN_LOCAL_VERSION: LazyLock<VersionReq> =
LazyLock::new(|| ">=1.13.3".parse().expect("Bad Identifier"));

/// Internal version of Metadata across operating system (macOS, Linux)
/// Only mutual attributes
#[derive(Encode, Decode, Debug, PartialEq, Clone, Copy, Eq, Default)]
Expand Down
7 changes: 7 additions & 0 deletions tests/src/operator/policies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ fn block_steal_without_qualifiers() -> PolicyTestCase {
selector: None,
block: vec![BlockedFeature::Steal],
env: Default::default(),
fs: Default::default(),
},
),
service_b_can_steal: No,
Expand All @@ -147,6 +148,7 @@ fn block_steal_with_path_pattern() -> PolicyTestCase {
selector: None,
block: vec![BlockedFeature::Steal],
env: Default::default(),
fs: Default::default(),
},
),
service_b_can_steal: EvenWithoutFilter,
Expand All @@ -166,6 +168,7 @@ fn block_unfiltered_steal_with_path_pattern() -> PolicyTestCase {
selector: None,
block: vec![BlockedFeature::StealWithoutFilter],
env: Default::default(),
fs: Default::default(),
},
),
service_b_can_steal: EvenWithoutFilter,
Expand All @@ -185,6 +188,7 @@ fn block_unfiltered_steal_with_deployment_path_pattern() -> PolicyTestCase {
selector: None,
block: vec![BlockedFeature::StealWithoutFilter],
env: Default::default(),
fs: Default::default(),
},
),
service_a_can_steal: OnlyWithFilter,
Expand All @@ -210,6 +214,7 @@ fn block_steal_with_label_selector() -> PolicyTestCase {
}),
block: vec![BlockedFeature::Steal],
env: Default::default(),
fs: Default::default(),
},
),
service_b_can_steal: EvenWithoutFilter,
Expand All @@ -236,6 +241,7 @@ fn block_steal_with_unmatching_policy() -> PolicyTestCase {
}),
block: vec![BlockedFeature::Steal],
env: Default::default(),
fs: Default::default(),
},
),
service_b_can_steal: EvenWithoutFilter,
Expand Down Expand Up @@ -377,6 +383,7 @@ pub async fn create_cluster_policy_and_try_to_mirror(
selector: None,
block: vec![BlockedFeature::Mirror],
env: Default::default(),
fs: Default::default(),
},
),
)
Expand Down
Loading