Skip to content
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
15 changes: 14 additions & 1 deletion crates/alien-infra/src/remote_stack_management/azure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1272,7 +1272,20 @@ fn generate_stack_management_grant_plan(
);
continue;
};
if permission_set.platforms.azure.is_none() {
// Skipped on the same condition the Terraform emitter uses, so push and runtime agree.
// A set can decline the stack target deliberately — the sandbox sets do, because at
// the only stack-level scope Azure RBAC can express, the resource group, their grants
// would reach every sibling sandbox group. Asking the generator for a target a set
// does not declare is a hard error, so without this a stack containing a sandbox fails
// management-role generation outright.
let declines_stack_scope = permission_set
.platforms
.azure
.as_ref()
.is_none_or(|azure| {
azure.is_empty() || azure.iter().all(|entry| entry.binding.stack.is_none())
});
if declines_stack_scope {
continue;
}

Expand Down
71 changes: 71 additions & 0 deletions crates/alien-permissions/permission-sets/sandbox/execute.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"id": "sandbox/execute",
"description": "Allows running commands and moving files inside an existing sandbox session",
"platforms": {
"aws": [
{
"grant": {
// Minting a MicroVM auth token is the action that grants access to session contents,
// so it belongs in this set alone — management, provision and heartbeat must never
// reach inside a session. CreateMicrovmShellAuthToken is deliberately excluded: an
// interactive shell is not part of the resource's surface.
//
// Authorized against the image the session was launched from, which is what bounds
// it: a workload can only mint tokens for sessions of its own sandbox.
"actions": ["lambda:CreateMicrovmAuthToken"]
},
"binding": {
"stack": {
"resources": [
"arn:aws:lambda:${awsRegion}:${awsAccountId}:microvm-image:${stackPrefix}-*"
]
},
"resource": {
"resources": [
"arn:aws:lambda:${awsRegion}:${awsAccountId}:microvm-image:${stackPrefix}-${resourceName}"
]
}
}
},
{
"grant": {
// State and endpoint of a session this workload already holds a token for. Metadata:
// it reveals nothing of what runs inside.
"actions": ["lambda:GetMicrovm"]
},
"binding": {
"stack": {
"resources": [
"arn:aws:lambda:${awsRegion}:${awsAccountId}:microvm-image:${stackPrefix}-*"
]
},
"resource": {
"resources": [
"arn:aws:lambda:${awsRegion}:${awsAccountId}:microvm-image:${stackPrefix}-${resourceName}"
]
}
}
}
],
"azure": [
{
"grant": {
// Azure gates its whole data plane behind this one role, so exec and session
// lifecycle cannot be separated. The merge lands here rather than in
// sandbox/management, because a set that reaches inside a session must not be one of
// the implicit-management sets. Subscription Owner returns 403 against this plane.
"predefinedRoles": ["Container Apps SandboxGroup Data Owner"]
},
// Resource-scoped only. This role reaches inside a session, and the sole stack-level
// scope Azure RBAC can express is the resource group — where it would reach inside every
// sibling sandbox group too. Nothing needs it: a workload is handed this set through a
// resource link, which binds to the sandbox's own group.
"binding": {
"resource": {
"scope": "/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.App/sandboxGroups/${stackPrefix}-${resourceName}"
}
}
}
]
}
}
49 changes: 49 additions & 0 deletions crates/alien-permissions/permission-sets/sandbox/heartbeat.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"id": "sandbox/heartbeat",
"description": "Allows reading sandbox parent state",
"platforms": {
"aws": [
{
"grant": {
// Parent state: whether the image exists and which versions are live. A rolled
// version stays a cleanup scope until its own MicroVMs are gone, so the heartbeat
// reads versions rather than assuming the newest.
//
// No session count here: counting sessions means `lambda:ListMicrovms`, which AWS
// authorizes against no resource type, so granting it would mean an account-wide grant.
"actions": ["lambda:GetMicrovmImage", "lambda:ListMicrovmImageVersions"]
},
"binding": {
"stack": {
"resources": [
"arn:aws:lambda:${awsRegion}:${awsAccountId}:microvm-image:${stackPrefix}-*"
]
},
"resource": {
"resources": [
"arn:aws:lambda:${awsRegion}:${awsAccountId}:microvm-image:${stackPrefix}-${resourceName}"
]
}
}
}
],
"azure": [
{
"grant": {
// Control-plane read only. Microsoft.App/sandboxGroups/sandboxes/stats/read returns cpu, memory and network counters with no
// session contents, and AppEnvSessionConsoleLogs is deliberately absent — it captures
// whatever the sandbox writes.
"actions": ["Microsoft.App/sandboxGroups/read"]
},
// Resource-scoped only. At the resource group — the sole stack-level scope Azure RBAC
// can express — this read would enumerate sibling sandbox groups. Nothing needs it: the
// heartbeat reports on the sandbox it is bound to.
"binding": {
"resource": {
"scope": "/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.App/sandboxGroups/${stackPrefix}-${resourceName}"
}
}
}
]
}
}
91 changes: 91 additions & 0 deletions crates/alien-permissions/permission-sets/sandbox/management.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"id": "sandbox/management",
"description": "Allows creating and terminating sandbox sessions (no access to session contents)",
"platforms": {
"aws": [
{
"grant": {
// Creating a session acts on the image it is launched from, so this statement carries
// the real ${stackPrefix} scope: a workload can only start MicroVMs from its own
// sandbox's image.
"actions": ["lambda:RunMicrovm"]
},
"binding": {
"stack": {
"resources": [
"arn:aws:lambda:${awsRegion}:${awsAccountId}:microvm-image:${stackPrefix}-*"
]
},
"resource": {
"resources": [
"arn:aws:lambda:${awsRegion}:${awsAccountId}:microvm-image:${stackPrefix}-${resourceName}"
]
}
}
},
{
// Name-scoped: PassRole is evaluated against the role being passed, which carries no
// resource tag to bound it with.
"label": "pass-sandbox-execution-role",
"description": "Allow a MicroVM to run under the sandbox's own execution role.",
"grant": {
"actions": ["iam:PassRole"]
},
"binding": {
"stack": {
"resources": ["arn:aws:iam::${awsAccountId}:role/${stackPrefix}-*"]
},
"resource": {
"resources": ["arn:aws:iam::${awsAccountId}:role/${stackPrefix}-${resourceName}-*"]
}
}
},
{
"grant": {
// Addressing an already-running MicroVM. Suspending or terminating does not grant
// access to what runs inside — that needs CreateMicrovmAuthToken, which is
// sandbox/execute.
"actions": ["lambda:TerminateMicrovm", "lambda:SuspendMicrovm", "lambda:ResumeMicrovm"]
},
"binding": {
"stack": {
"resources": [
"arn:aws:lambda:${awsRegion}:${awsAccountId}:microvm-image:${stackPrefix}-*"
]
},
"resource": {
"resources": [
"arn:aws:lambda:${awsRegion}:${awsAccountId}:microvm-image:${stackPrefix}-${resourceName}"
]
}
}
}
],
// Azure separates session lifecycle from the actions that reach inside a session, so this
// set can start and stop sessions without being able to read one.
"azure": [
{
"grant": {
"dataActions": [
"Microsoft.App/sandboxGroups/sandboxes/write",
"Microsoft.App/sandboxGroups/sandboxes/delete",
"Microsoft.App/sandboxGroups/sandboxes/read",
"Microsoft.App/sandboxGroups/sandboxes/count/read"
]
},
// Resource-scoped only, unlike every other grant here. An Azure RBAC scope has to name a
// concrete resource, so the stack-level scope can only be the whole resource group — and
// there it would let a holder terminate sessions in a sibling sandbox group. Nothing
// needs it: sessions are created and destroyed by the application through its resource
// binding at runtime, and the sandbox group's own lifecycle is a control-plane action.
"binding": {
"resource": {
"scope": "/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.App/sandboxGroups/${stackPrefix}-${resourceName}"
}
}
}
]
// No GCP entry, and nothing to add: a GCP sandbox is a launcher subprocess inside the app's
// own Cloud Run instance, so it creates no GCP resource and makes no GCP API call.
}
}
136 changes: 136 additions & 0 deletions crates/alien-permissions/permission-sets/sandbox/provision.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
{
"id": "sandbox/provision",
"description": "Allows creating and deleting the durable sandbox parent (image or sandbox group)",
"platforms": {
"aws": [
{
"grant": {
// The Frozen parent on AWS is a MicroVM image and its versions. run-microvm rejects
// the AWS-managed base image, so every AWS sandbox builds one — provision therefore
// covers the build and its status reads, not just create and delete.
"actions": [
"lambda:DeleteMicrovmImage",
"lambda:DeleteMicrovmImageVersion",
"lambda:GetMicrovmImage",
"lambda:GetMicrovmImageVersion",
"lambda:GetMicrovmImageBuild",
"lambda:ListMicrovmImageVersions",
"lambda:ListMicrovmImageBuilds"
]
},
"binding": {
"stack": {
"resources": [
"arn:aws:lambda:${awsRegion}:${awsAccountId}:microvm-image:${stackPrefix}-*"
]
},
"resource": {
"resources": [
"arn:aws:lambda:${awsRegion}:${awsAccountId}:microvm-image:${stackPrefix}-${resourceName}"
]
}
}
},
{
"grant": {
// AWS authorizes creation against no resource type, so an ARN here would deny rather
// than narrow. The boundary tags are what bound it instead.
"actions": ["lambda:CreateMicrovmImage"]
},
"binding": {
"stack": {
"resources": ["*"],
"condition": {
"StringEquals": {
"aws:RequestTag/${stackTag}": "${stackPrefix}",
"aws:RequestTag/${managedByTag}": "runtime"
}
}
},
"resource": {
"resources": ["*"],
"condition": {
"StringEquals": {
"aws:RequestTag/${stackTag}": "${stackPrefix}",
"aws:RequestTag/${managedByTag}": "runtime"
}
}
}
}
},
{
"grant": {
// The image tag is the boundary every other sandbox statement scopes against, so it
// has to be settable here and readable for teardown.
"actions": ["lambda:TagResource", "lambda:ListTags"]
},
"binding": {
"stack": {
"resources": [
"arn:aws:lambda:${awsRegion}:${awsAccountId}:microvm-image:${stackPrefix}-*"
]
},
"resource": {
"resources": [
"arn:aws:lambda:${awsRegion}:${awsAccountId}:microvm-image:${stackPrefix}-${resourceName}"
]
}
}
},
{
"grant": {
// Also authorized against no resource type. Names only, so a wildcard read.
"actions": ["lambda:ListMicrovmImages"]
},
"binding": {
"stack": {
"resources": ["*"]
},
"resource": {
"resources": ["*"]
}
}
}
],
"azure": [
{
"grant": {
// Create, which is authorized against the parent, so the resource group is the floor.
// Not because the group cannot be named — the resource binding below names it before it
// exists — but because ARM has no scope between the group and its parent, no wildcard
// scope, and no condition that reaches a control-plane write.
//
// `write` is create *and* update in one ARM verb, so a holder at this scope can also
// reconfigure a sibling group in the same resource group — including one that is not
// Alien's, since a deployment can be pointed at a resource group its owner already
// uses. Under Azure RBAC that is not reducible: scope has four levels and none sits
// between a group and its resource group, conditions are evaluated only on data-plane
// operations, and a deny assignment cannot be authored by the deployment.
//
// `delete` and `read` name the group and would not need the parent. They stay here
// anyway: a provision set is only ever compiled at stack scope, and the per-resource
// Azure path skips `/provision` outright, so a resource-only grant would reach nobody
// and leave the identity that created the group unable to read or destroy it.
// Narrowing them needs a per-resource delivery path first.
//
// The role assignment that opens the data plane is not granted here — this set could
// otherwise assign itself the sandbox data-plane role, which is the boundary
// sandbox/execute exists to hold.
"actions": [
"Microsoft.App/sandboxGroups/write",
"Microsoft.App/sandboxGroups/delete",
"Microsoft.App/sandboxGroups/read"
]
},
"binding": {
"stack": {
"scope": "/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}"
},
Comment thread
greptile-apps[bot] marked this conversation as resolved.
"resource": {
"scope": "/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.App/sandboxGroups/${stackPrefix}-${resourceName}"
}
}
}
]
}
}
Loading
Loading