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:
parent
56f31628f7
commit
b729bf0ad9
7 changed files with 250 additions and 20 deletions
|
@ -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;
|
||||||
|
|
|
@ -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
tests/specs/permission/process_env_permissions/main.js
Normal file
5
tests/specs/permission/process_env_permissions/main.js
Normal 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"));
|
10
tests/specs/run/allow_env_wildcard_worker/__test__.jsonc
Normal file
10
tests/specs/run/allow_env_wildcard_worker/__test__.jsonc
Normal 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
|
||||||
|
}
|
12
tests/specs/run/allow_env_wildcard_worker/main.js
Normal file
12
tests/specs/run/allow_env_wildcard_worker/main.js
Normal 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_*"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
11
tests/specs/run/allow_env_wildcard_worker/main.out
Normal file
11
tests/specs/run/allow_env_wildcard_worker/main.out
Normal 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]
|
3
tests/specs/run/allow_env_wildcard_worker/worker.js
Normal file
3
tests/specs/run/allow_env_wildcard_worker/worker.js
Normal 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"));
|
Loading…
Reference in a new issue