1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

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

This commit adds support for suffix wildcard for `--allow-env` flag.

Specifying flag like `--allow-env=DENO_*` will enable access to all
environmental variables starting with `DENO_*`.

Closes #24847

---------

Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
This commit is contained in:
Yazan AbdAl-Rahman 2024-11-21 01:30:43 +02:00 committed by GitHub
parent 56f31628f7
commit b729bf0ad9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 250 additions and 20 deletions

View file

@ -294,7 +294,7 @@ impl UnitPermission {
/// A normalized environment variable name. On Windows this will /// A normalized environment variable name. On Windows this will
/// be uppercase and on other platforms it will stay as-is. /// be uppercase and on other platforms it will stay as-is.
#[derive(Clone, Eq, PartialEq, Hash, Debug)] #[derive(Clone, Eq, PartialEq, Hash, Debug)]
struct EnvVarName { pub struct EnvVarName {
inner: String, inner: String,
} }
@ -1114,15 +1114,37 @@ impl ImportDescriptor {
pub struct EnvDescriptorParseError; pub struct EnvDescriptorParseError;
#[derive(Clone, Eq, PartialEq, Hash, Debug)] #[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct EnvDescriptor(EnvVarName); pub enum EnvDescriptor {
Name(EnvVarName),
PrefixPattern(EnvVarName),
}
impl EnvDescriptor { impl EnvDescriptor {
pub fn new(env: impl AsRef<str>) -> Self { 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))
}
} }
} }
impl QueryDescriptor for EnvDescriptor { #[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 EnvQueryDescriptor {
type AllowDesc = EnvDescriptor; type AllowDesc = EnvDescriptor;
type DenyDesc = EnvDescriptor; type DenyDesc = EnvDescriptor;
@ -1131,19 +1153,45 @@ impl QueryDescriptor for EnvDescriptor {
} }
fn display_name(&self) -> Cow<str> { 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 { 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> { 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 { 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( fn check_in_permission(
@ -1156,19 +1204,79 @@ impl QueryDescriptor for EnvDescriptor {
} }
fn matches_allow(&self, other: &Self::AllowDesc) -> bool { 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 { 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 { 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 { 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 { fn overlaps_deny(&self, _other: &Self::DenyDesc) -> bool {
@ -1176,9 +1284,14 @@ impl QueryDescriptor for EnvDescriptor {
} }
} }
impl AsRef<str> for EnvDescriptor { impl AsRef<str> for EnvQueryDescriptor {
fn as_ref(&self) -> &str { 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()
}
}
} }
} }
@ -1749,20 +1862,20 @@ impl UnaryPermission<ImportDescriptor> {
} }
} }
impl UnaryPermission<EnvDescriptor> { impl UnaryPermission<EnvQueryDescriptor> {
pub fn query(&self, env: Option<&str>) -> PermissionState { pub fn query(&self, env: Option<&str>) -> PermissionState {
self.query_desc( self.query_desc(
env.map(EnvDescriptor::new).as_ref(), env.map(EnvQueryDescriptor::new).as_ref(),
AllowPartial::TreatAsPartialGranted, AllowPartial::TreatAsPartialGranted,
) )
} }
pub fn request(&mut self, env: Option<&str>) -> PermissionState { 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 { 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( pub fn check(
@ -1771,7 +1884,7 @@ impl UnaryPermission<EnvDescriptor> {
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), PermissionDeniedError> { ) -> Result<(), PermissionDeniedError> {
skip_check_if_is_permission_fully_granted!(self); 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> { pub fn check_all(&mut self) -> Result<(), PermissionDeniedError> {
@ -1905,7 +2018,7 @@ pub struct Permissions {
pub read: UnaryPermission<ReadQueryDescriptor>, pub read: UnaryPermission<ReadQueryDescriptor>,
pub write: UnaryPermission<WriteQueryDescriptor>, pub write: UnaryPermission<WriteQueryDescriptor>,
pub net: UnaryPermission<NetDescriptor>, pub net: UnaryPermission<NetDescriptor>,
pub env: UnaryPermission<EnvDescriptor>, pub env: UnaryPermission<EnvQueryDescriptor>,
pub sys: UnaryPermission<SysDescriptor>, pub sys: UnaryPermission<SysDescriptor>,
pub run: UnaryPermission<RunQueryDescriptor>, pub run: UnaryPermission<RunQueryDescriptor>,
pub ffi: UnaryPermission<FfiQueryDescriptor>, pub ffi: UnaryPermission<FfiQueryDescriptor>,
@ -4564,6 +4677,56 @@ mod tests {
assert_eq!(perms.env.revoke(Some("HomE")), PermissionState::Prompt); 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] #[test]
fn test_check_partial_denied() { fn test_check_partial_denied() {
let parser = TestPermissionDescriptorParser; let parser = TestPermissionDescriptorParser;

View file

@ -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"
}
]
}
}
}

View file

@ -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"));

View file

@ -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
}

View file

@ -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_*"],
},
},
});

View file

@ -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]

View file

@ -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"));