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

feat(permission): support suffix wildcards in --allow-env flag #25255

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
085b111
Add wildcard support to --allow-env flag for environment variable pat…
yazan-abdalrahman Aug 28, 2024
5e463d3
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Aug 28, 2024
1dae2e4
fix, test
yazan-abdalrahman Aug 28, 2024
849e194
Merge remote-tracking branch 'origin/Enhance---allow-env-to-Support-P…
yazan-abdalrahman Aug 28, 2024
574f292
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Sep 3, 2024
8ae53a6
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Sep 4, 2024
7d0b807
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Sep 4, 2024
32af9f7
New solution with support env.get and set
yazan-abdalrahman Sep 4, 2024
ac39fc9
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Sep 5, 2024
8603674
fmt
yazan-abdalrahman Sep 5, 2024
981a9ce
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Sep 5, 2024
a7469b9
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Sep 5, 2024
7a93a1a
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Sep 5, 2024
8df48c3
fix
yazan-abdalrahman Sep 5, 2024
7ed8376
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Sep 5, 2024
4c31cdd
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Sep 5, 2024
b5c8bb8
fix new solution
yazan-abdalrahman Sep 5, 2024
24e2ac5
Merge remote-tracking branch 'origin/Enhance---allow-env-to-Support-P…
yazan-abdalrahman Sep 5, 2024
3076ca9
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Sep 8, 2024
e49e0ef
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Sep 10, 2024
6a368f9
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Sep 16, 2024
afff2c8
Merge branch 'refs/heads/main' into Enhance---allow-env-to-Support-Pr…
yazan-abdalrahman Sep 18, 2024
22b568f
fmt
yazan-abdalrahman Sep 18, 2024
6153314
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Sep 24, 2024
9ef3509
Merge branch 'refs/heads/main' into Enhance---allow-env-to-Support-Pr…
yazan-abdalrahman Oct 9, 2024
c828c7c
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Oct 17, 2024
525cdbc
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Oct 21, 2024
63b3e22
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Oct 30, 2024
37c49be
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
yazan-abdalrahman Nov 12, 2024
372266b
wip
bartlomieju Nov 17, 2024
600d042
Merge branch 'env_wildcard' into Enhance---allow-env-to-Support-Prefi…
bartlomieju Nov 17, 2024
4b2b357
cleanup
bartlomieju Nov 17, 2024
365525e
test for wildcards in workers
bartlomieju Nov 20, 2024
6d12ba3
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
bartlomieju Nov 20, 2024
522a4f3
move definition beside impl
dsherret Nov 20, 2024
78d9617
allow doing a subset of a prefix when creating child perms
dsherret Nov 20, 2024
0bc4cd7
Merge branch 'main' into Enhance---allow-env-to-Support-Prefix,-Suffi…
bartlomieju Nov 20, 2024
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
203 changes: 183 additions & 20 deletions runtime/permissions/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ impl UnitPermission {
/// A normalized environment variable name. On Windows this will
/// be uppercase and on other platforms it will stay as-is.
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
struct EnvVarName {
pub struct EnvVarName {
inner: String,
}

Expand Down Expand Up @@ -1114,15 +1114,37 @@ impl ImportDescriptor {
pub struct EnvDescriptorParseError;

#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct EnvDescriptor(EnvVarName);
pub enum EnvDescriptor {
Name(EnvVarName),
PrefixPattern(EnvVarName),
}

impl EnvDescriptor {
pub fn new(env: impl AsRef<str>) -> Self {
Self(EnvVarName::new(env))
if let Some(prefix_pattern) = env.as_ref().strip_suffix('*') {
Self::PrefixPattern(EnvVarName::new(prefix_pattern))
} else {
Self::Name(EnvVarName::new(env))
}
}
}

#[derive(Clone, Eq, PartialEq, Hash, Debug)]
enum EnvQueryDescriptorInner {
Name(EnvVarName),
PrefixPattern(EnvVarName),
}

#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct EnvQueryDescriptor(EnvQueryDescriptorInner);

impl EnvQueryDescriptor {
pub fn new(env: impl AsRef<str>) -> Self {
Self(EnvQueryDescriptorInner::Name(EnvVarName::new(env)))
}
}

impl QueryDescriptor for EnvDescriptor {
impl QueryDescriptor for EnvQueryDescriptor {
type AllowDesc = EnvDescriptor;
type DenyDesc = EnvDescriptor;

Expand All @@ -1131,19 +1153,45 @@ impl QueryDescriptor for EnvDescriptor {
}

fn display_name(&self) -> Cow<str> {
Cow::from(self.0.as_ref())
Cow::from(match &self.0 {
EnvQueryDescriptorInner::Name(env_var_name) => env_var_name.as_ref(),
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
env_var_name.as_ref()
}
})
}

fn from_allow(allow: &Self::AllowDesc) -> Self {
allow.clone()
match allow {
Self::AllowDesc::Name(s) => {
Self(EnvQueryDescriptorInner::Name(s.clone()))
}
Self::AllowDesc::PrefixPattern(s) => {
Self(EnvQueryDescriptorInner::PrefixPattern(s.clone()))
}
}
}

fn as_allow(&self) -> Option<Self::AllowDesc> {
Some(self.clone())
Some(match &self.0 {
EnvQueryDescriptorInner::Name(env_var_name) => {
Self::AllowDesc::Name(env_var_name.clone())
}
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
Self::AllowDesc::PrefixPattern(env_var_name.clone())
}
})
}

fn as_deny(&self) -> Self::DenyDesc {
self.clone()
match &self.0 {
EnvQueryDescriptorInner::Name(env_var_name) => {
Self::DenyDesc::Name(env_var_name.clone())
}
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
Self::DenyDesc::PrefixPattern(env_var_name.clone())
}
}
}

fn check_in_permission(
Expand All @@ -1156,29 +1204,94 @@ impl QueryDescriptor for EnvDescriptor {
}

fn matches_allow(&self, other: &Self::AllowDesc) -> bool {
self == other
match other {
Self::AllowDesc::Name(n) => match &self.0 {
EnvQueryDescriptorInner::Name(env_var_name) => n == env_var_name,
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
env_var_name.as_ref().starts_with(n.as_ref())
}
},
Self::AllowDesc::PrefixPattern(p) => match &self.0 {
EnvQueryDescriptorInner::Name(env_var_name) => {
env_var_name.as_ref().starts_with(p.as_ref())
}
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
env_var_name.as_ref().starts_with(p.as_ref())
}
},
}
}

fn matches_deny(&self, other: &Self::DenyDesc) -> bool {
self == other
match other {
Self::AllowDesc::Name(n) => match &self.0 {
EnvQueryDescriptorInner::Name(env_var_name) => n == env_var_name,
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
env_var_name.as_ref().starts_with(n.as_ref())
}
},
Self::AllowDesc::PrefixPattern(p) => match &self.0 {
EnvQueryDescriptorInner::Name(env_var_name) => {
env_var_name.as_ref().starts_with(p.as_ref())
}
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
p == env_var_name
}
},
}
}

fn revokes(&self, other: &Self::AllowDesc) -> bool {
self == other
match other {
Self::AllowDesc::Name(n) => match &self.0 {
EnvQueryDescriptorInner::Name(env_var_name) => n == env_var_name,
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
env_var_name.as_ref().starts_with(n.as_ref())
}
},
Self::AllowDesc::PrefixPattern(p) => match &self.0 {
EnvQueryDescriptorInner::Name(env_var_name) => {
env_var_name.as_ref().starts_with(p.as_ref())
}
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
p == env_var_name
}
},
}
}

fn stronger_than_deny(&self, other: &Self::DenyDesc) -> bool {
self == other
match other {
Self::AllowDesc::Name(n) => match &self.0 {
EnvQueryDescriptorInner::Name(env_var_name) => n == env_var_name,
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
env_var_name.as_ref().starts_with(n.as_ref())
}
},
Self::AllowDesc::PrefixPattern(p) => match &self.0 {
EnvQueryDescriptorInner::Name(env_var_name) => {
env_var_name.as_ref().starts_with(p.as_ref())
}
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
p == env_var_name
}
},
}
}

fn overlaps_deny(&self, _other: &Self::DenyDesc) -> bool {
false
}
}

impl AsRef<str> for EnvDescriptor {
impl AsRef<str> for EnvQueryDescriptor {
fn as_ref(&self) -> &str {
self.0.as_ref()
match &self.0 {
EnvQueryDescriptorInner::Name(env_var_name) => env_var_name.as_ref(),
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
env_var_name.as_ref()
}
}
}
}

Expand Down Expand Up @@ -1749,20 +1862,20 @@ impl UnaryPermission<ImportDescriptor> {
}
}

impl UnaryPermission<EnvDescriptor> {
impl UnaryPermission<EnvQueryDescriptor> {
pub fn query(&self, env: Option<&str>) -> PermissionState {
self.query_desc(
env.map(EnvDescriptor::new).as_ref(),
env.map(EnvQueryDescriptor::new).as_ref(),
AllowPartial::TreatAsPartialGranted,
)
}

pub fn request(&mut self, env: Option<&str>) -> PermissionState {
self.request_desc(env.map(EnvDescriptor::new).as_ref())
self.request_desc(env.map(EnvQueryDescriptor::new).as_ref())
}

pub fn revoke(&mut self, env: Option<&str>) -> PermissionState {
self.revoke_desc(env.map(EnvDescriptor::new).as_ref())
self.revoke_desc(env.map(EnvQueryDescriptor::new).as_ref())
}

pub fn check(
Expand All @@ -1771,7 +1884,7 @@ impl UnaryPermission<EnvDescriptor> {
api_name: Option<&str>,
) -> Result<(), PermissionDeniedError> {
skip_check_if_is_permission_fully_granted!(self);
self.check_desc(Some(&EnvDescriptor::new(env)), false, api_name)
self.check_desc(Some(&EnvQueryDescriptor::new(env)), false, api_name)
}

pub fn check_all(&mut self) -> Result<(), PermissionDeniedError> {
Expand Down Expand Up @@ -1905,7 +2018,7 @@ pub struct Permissions {
pub read: UnaryPermission<ReadQueryDescriptor>,
pub write: UnaryPermission<WriteQueryDescriptor>,
pub net: UnaryPermission<NetDescriptor>,
pub env: UnaryPermission<EnvDescriptor>,
pub env: UnaryPermission<EnvQueryDescriptor>,
pub sys: UnaryPermission<SysDescriptor>,
pub run: UnaryPermission<RunQueryDescriptor>,
pub ffi: UnaryPermission<FfiQueryDescriptor>,
Expand Down Expand Up @@ -4564,6 +4677,56 @@ mod tests {
assert_eq!(perms.env.revoke(Some("HomE")), PermissionState::Prompt);
}

#[test]
fn test_env_wildcards() {
set_prompter(Box::new(TestPrompter));
let _prompt_value = PERMISSION_PROMPT_STUB_VALUE_SETTER.lock();
let mut perms = Permissions::allow_all();
perms.env = UnaryPermission {
granted_global: false,
..Permissions::new_unary(
Some(HashSet::from([EnvDescriptor::new("HOME_*")])),
None,
false,
)
};
assert_eq!(perms.env.query(Some("HOME")), PermissionState::Prompt);
assert_eq!(perms.env.query(Some("HOME_")), PermissionState::Granted);
assert_eq!(perms.env.query(Some("HOME_TEST")), PermissionState::Granted);

// assert no privilege escalation
let parser = TestPermissionDescriptorParser;
assert!(perms
.env
.create_child_permissions(
ChildUnaryPermissionArg::GrantedList(vec!["HOME_SUB".to_string()]),
|value| parser.parse_env_descriptor(value).map(Some),
)
.is_ok());
assert!(perms
.env
.create_child_permissions(
ChildUnaryPermissionArg::GrantedList(vec!["HOME*".to_string()]),
|value| parser.parse_env_descriptor(value).map(Some),
)
.is_err());
assert!(perms
.env
.create_child_permissions(
ChildUnaryPermissionArg::GrantedList(vec!["OUTSIDE".to_string()]),
|value| parser.parse_env_descriptor(value).map(Some),
)
.is_err());
assert!(perms
.env
.create_child_permissions(
// ok because this is a subset of HOME_*
ChildUnaryPermissionArg::GrantedList(vec!["HOME_S*".to_string()]),
|value| parser.parse_env_descriptor(value).map(Some),
)
.is_ok());
}

#[test]
fn test_check_partial_denied() {
let parser = TestPermissionDescriptorParser;
Expand Down
26 changes: 26 additions & 0 deletions tests/specs/permission/process_env_permissions/__test__.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"tempDir": true,
"tests": {
"deno_env_wildcard_tests": {
"envs": {
"MYAPP_HELLO": "Hello\tworld,",
"MYAPP_GOODBYE": "farewell",
"OTHER_VAR": "ignore"
},
"steps": [
{
"args": "run --allow-env=MYAPP_* main.js",
"output": "Hello\tworld,\nfarewell\ndone\n"
},
{
"args": "run --allow-env main.js",
"output": "Hello\tworld,\nfarewell\ndone\n"
},
{
"args": "run --allow-env=MYAPP_HELLO,MYAPP_GOODBYE,MYAPP_TEST,MYAPP_DONE main.js",
"output": "Hello\tworld,\nfarewell\ndone\n"
}
]
}
}
}
5 changes: 5 additions & 0 deletions tests/specs/permission/process_env_permissions/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
console.log(Deno.env.get("MYAPP_HELLO"));
console.log(Deno.env.get("MYAPP_GOODBYE"));
Deno.env.set("MYAPP_TEST", "done");
Deno.env.set("MYAPP_DONE", "done");
console.log(Deno.env.get("MYAPP_DONE"));
10 changes: 10 additions & 0 deletions tests/specs/run/allow_env_wildcard_worker/__test__.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"envs": {
"DENO_HELLO": "hello",
"DENO_BYE": "bye",
"AWS_HELLO": "aws"
},
"args": "run --allow-env --allow-read --unstable-worker-options main.js",
"output": "main.out",
"exitCode": 1
}
12 changes: 12 additions & 0 deletions tests/specs/run/allow_env_wildcard_worker/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
console.log("main1", Deno.env.get("DENO_HELLO"));
console.log("main2", Deno.env.get("DENO_BYE"));
console.log("main3", Deno.env.get("AWS_HELLO"));

new Worker(import.meta.resolve("./worker.js"), {
type: "module",
deno: {
permissions: {
env: ["DENO_*"],
},
},
});
11 changes: 11 additions & 0 deletions tests/specs/run/allow_env_wildcard_worker/main.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
main1 hello
main2 bye
main3 aws
worker1 hello
worker2 bye
error: Uncaught (in worker "") (in promise) NotCapable: Requires env access to "AWS_HELLO", run again with the --allow-env flag
console.log("worker3", Deno.env.get("AWS_HELLO"));
^
[WILDCARD]
error: Uncaught (in promise) Error: Unhandled error in child worker.
[WILDCARD]
3 changes: 3 additions & 0 deletions tests/specs/run/allow_env_wildcard_worker/worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
console.log("worker1", Deno.env.get("DENO_HELLO"));
console.log("worker2", Deno.env.get("DENO_BYE"));
console.log("worker3", Deno.env.get("AWS_HELLO"));