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: impersonating sender of requests to a local PocketIC instance #4013

Merged
merged 31 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0ce5097
feat: impersonating sender of requests to a local PocketIC instance
mraszyk Nov 25, 2024
ed34785
windows
mraszyk Nov 25, 2024
2a51eb6
no max_request_time_ms
mraszyk Nov 25, 2024
32cfce0
bump pocket-ic
mraszyk Nov 26, 2024
58dcbe5
request-status
mraszyk Nov 27, 2024
c8180c2
cleanup
mraszyk Nov 28, 2024
f984597
Merge branch 'master' into mraszyk/pic-impersonate
mraszyk Nov 28, 2024
724abf6
cleanup
mraszyk Nov 28, 2024
75f52b8
fix
mraszyk Nov 28, 2024
49ade37
Merge branch 'master' into mraszyk/pic-impersonate
mraszyk Nov 28, 2024
acfba3b
typo
mraszyk Nov 28, 2024
6b5743a
impersonate.bash
mraszyk Nov 29, 2024
3376540
dfx canister request-status
mraszyk Nov 29, 2024
5bcc897
smoke
mraszyk Nov 29, 2024
ee07a66
smoke
mraszyk Nov 29, 2024
4b79a1c
smoke
mraszyk Nov 29, 2024
6371b7d
smoke
mraszyk Nov 29, 2024
05b7408
chore: update replica version to d9fe2076f677a08734bed90c67b1c3f4056e…
mraszyk Dec 6, 2024
914db7f
Merge branch 'master' into mraszyk/pic-impersonate
mraszyk Dec 6, 2024
7c2ecf9
Merge branch 'chore-update-replica-d9fe2076f677a08734bed90c67b1c3f405…
mraszyk Dec 6, 2024
c9e9828
no e2e.yml changes
mraszyk Dec 7, 2024
21bd166
move impersonate.bash to call.bash
mraszyk Dec 7, 2024
bb6ac12
Merge branch 'master' into mraszyk/pic-impersonate
mraszyk Dec 18, 2024
7fb51fb
docs
mraszyk Dec 19, 2024
c3893ea
unnecessary DfxResult
mraszyk Dec 19, 2024
fd5192c
harden tests
mraszyk Dec 19, 2024
eeab1dd
fix dfx identity new in tests
mraszyk Dec 19, 2024
e6483ce
Merge branch 'master' into mraszyk/pic-impersonate
mraszyk Dec 19, 2024
a92ddea
Merge branch 'master' into mraszyk/pic-impersonate
mraszyk Jan 6, 2025
fc344fd
no changes to request-status command
mraszyk Jan 6, 2025
4f54f23
Merge branch 'master' into mraszyk/pic-impersonate
mraszyk Jan 6, 2025
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

# UNRELEASED

### feat: impersonating sender of requests to a local PocketIC instance

`dfx canister call`, `dfx canister status`, and `dfx canister update-settings` take
ericswanson-dfinity marked this conversation as resolved.
Show resolved Hide resolved
an additional CLI argument `--impersonate` to specify a principal
on behalf of which requests to a local PocketIC instance are sent.

### feat: `dfx canister [create|update-settings] --wasm-memory-threshold`

This adds support for the WASM memory threshold, used in conjunction with `--wasm-memory-limit`.
Expand Down
11 changes: 7 additions & 4 deletions docs/cli-reference/dfx-canister.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ You can use the following options with the `dfx canister call` command.
| `--argument-file <argument-file>` | Specifies the file from which to read the argument to pass to the method. Stdin may be referred to as `-`. |
| `--async` | Specifies not to wait for the result of the call to be returned by polling the replica. Instead return a response ID. |
| `--candid <file.did>` | Provide the .did file with which to decode the response. Overrides value from dfx.json for project canisters. |
| `--impersonate <principal>` | Specifies a principal on behalf of which requests to a local PocketIC instance are sent. |
| `--output <output>` | Specifies the output format to use when displaying a method’s return result. The valid values are `idl`, 'json', `pp` and `raw`. The `pp` option is equivalent to `idl`, but is pretty-printed. |
| `--query` | Sends a query request instead of an update request. For information about the difference between query and update calls, see [Canisters include both program and state](/docs/current/concepts/canisters-code#canister-state). |
| `--random <random>` | Specifies the config for generating random arguments. |
Expand Down Expand Up @@ -1005,10 +1006,11 @@ dfx canister status [--all | canister_name]

You can use the following arguments with the `dfx canister status` command.

| Argument | Description |
|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--all` | Returns status information for all of the canisters configured in the `dfx.json` file. Note that you must specify `--all` or an individual canister name. |
| `canister_name` | Specifies the name of the canister you want to return information for. Note that you must specify either a canister name or the `--all` option. |
| Argument | Description |
|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--all` | Returns status information for all of the canisters configured in the `dfx.json` file. Note that you must specify `--all` or an individual canister name. |
| `--impersonate <principal>` | Specifies a principal on behalf of which requests to a local PocketIC instance are sent. |
| `canister_name` | Specifies the name of the canister you want to return information for. Note that you must specify either a canister name or the `--all` option. |

### Examples

Expand Down Expand Up @@ -1142,6 +1144,7 @@ You can specify the following options for the `dfx canister update-settings` com
| `--add-log-viewer <principal>` | Add a principal to the list of log viewers of the canister. Can be specified more than once to add multiple log viewers. If current log visibility is `public` or `controllers`, it will be changed to the custom allowed viewer list. |
| `-c`, `--compute-allocation <allocation>` | Specifies the canister's compute allocation. This should be a percent in the range [0..100]. |
| `--confirm-very-long-freezing-threshold` | Freezing thresholds above ~1.5 years require this option as confirmation. |
| `--impersonate <principal>` | Specifies a principal on behalf of which requests to a local PocketIC instance are sent. |
| `--set-controller <principal>` | Specifies the identity name or the principal of the new controller. Can be specified more than once, indicating the canister will have multiple controllers. If any controllers are set with this parameter, any other controllers will be removed. |
| `--set-log-viewer <principal>` | Specifies the the principal of the log viewer of the canister. Can be specified more than once, indicating the canister will have multiple log viewers. If any log viewers are set with this parameter, any other log viewers will be removed. If current log visibility is `public` or `controllers`, it will be changed to the custom allowed viewer list. |
| `--memory-allocation <allocation>` | Specifies how much memory the canister is allowed to use in total. This should be a value in the range [0..12 GiB]. A setting of 0 means the canister will have access to memory on a “best-effort” basis: It will only be charged for the memory it uses, but at any point in time may stop running if it tries to allocate more memory when there isn’t space available on the subnet. |
Expand Down
85 changes: 85 additions & 0 deletions e2e/tests-dfx/call.bash
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,88 @@ teardown() {
assert_command dfx canister call inter2_mo read
assert_match '(8 : nat)'
}

function impersonate_sender() {
IDENTITY_PRINCIPAL="${1}"

dfx_start
assert_command dfx deploy hello_backend
CANISTER_ID="$(dfx canister id hello_backend)"

# set the management canister as the only controller
assert_command dfx canister update-settings hello_backend --set-controller "${IDENTITY_PRINCIPAL}" --yes

# updating settings now fails because the default identity does not control the canister anymore
assert_command_fail dfx canister update-settings hello_backend --freezing-threshold 0
assert_contains "Only controllers of canister $CANISTER_ID can call ic00 method update_settings"

# updating settings succeeds when impersonating the management canister as the sender
assert_command dfx canister update-settings hello_backend --freezing-threshold 0 --impersonate "${IDENTITY_PRINCIPAL}"

# test management canister call failure (setting memory allocation to a low value)
assert_command_fail dfx canister update-settings hello_backend --memory-allocation 1 --impersonate "${IDENTITY_PRINCIPAL}"
assert_contains "Management canister call failed: IC0402: Canister was given 1 B memory allocation but at least"

# canister status fails because the default identity does not control the canister anymore
assert_command_fail dfx canister status hello_backend
assert_contains "Only controllers of canister $CANISTER_ID can call ic00 method canister_status"

# canister status succeeds when impersonating the management canister as the sender
assert_command dfx canister status hello_backend --impersonate "${IDENTITY_PRINCIPAL}"
assert_contains "Controllers: ${IDENTITY_PRINCIPAL}"
assert_contains "Freezing threshold: 0"

# freeze the canister
assert_command dfx canister update-settings hello_backend --freezing-threshold 9223372036854775808 --confirm-very-long-freezing-threshold --impersonate "${IDENTITY_PRINCIPAL}"

# test management canister call submission failure
assert_command_fail dfx canister status hello_backend --impersonate "${IDENTITY_PRINCIPAL}"
assert_contains "Failed to submit management canister call: IC0207: Canister $CANISTER_ID is out of cycles"

# test update call submission failure
assert_command_fail dfx canister call aaaaa-aa canister_status "(record { canister_id=principal\"$CANISTER_ID\" })" --update --impersonate "${IDENTITY_PRINCIPAL}"
assert_contains "Failed to submit canister call: IC0207: Canister $CANISTER_ID is out of cycles"

# test async call submission failure
assert_command_fail dfx canister call aaaaa-aa canister_status "(record { canister_id=principal\"$CANISTER_ID\" })" --async --impersonate "${IDENTITY_PRINCIPAL}"
assert_contains "Failed to submit canister call: IC0207: Canister $CANISTER_ID is out of cycles"

# unfreeze the canister
assert_command dfx canister update-settings hello_backend --freezing-threshold 0 --impersonate "${IDENTITY_PRINCIPAL}"

# test update call failure
assert_command_fail dfx canister call aaaaa-aa delete_canister "(record { canister_id=principal\"$CANISTER_ID\" })" --update --impersonate "${IDENTITY_PRINCIPAL}"
assert_contains "Canister call failed: IC0510: Canister $CANISTER_ID must be stopped before it is deleted."

# test update call
assert_command dfx canister call aaaaa-aa start_canister "(record { canister_id=principal\"$CANISTER_ID\" })" --update --impersonate "${IDENTITY_PRINCIPAL}"
assert_contains "()"

# test async call
assert_command dfx canister call aaaaa-aa canister_status "(record { canister_id=principal\"$CANISTER_ID\" })" --async --impersonate "${IDENTITY_PRINCIPAL}"
assert_contains "Request ID:"

# test query call failure
assert_command_fail dfx canister call aaaaa-aa fetch_canister_logs "(record { canister_id=principal\"$CANISTER_ID\" })" --query --impersonate "$CANISTER_ID"
assert_contains "Failed to perform query call: IC0406: Caller $CANISTER_ID is not allowed to query ic00 method fetch_canister_logs"

# test query call
assert_command dfx canister call aaaaa-aa fetch_canister_logs "(record { canister_id=principal\"$CANISTER_ID\" })" --query --impersonate "${IDENTITY_PRINCIPAL}"
assert_contains "(record { 1_754_302_831 = vec {} })"
}

@test "impersonate management canister as sender" {
[[ ! "$USE_POCKETIC" ]] && skip "skipped for replica: impersonating sender is only supported for PocketIC"

impersonate_sender "aaaaa-aa"
}

@test "impersonate new random identity as sender" {
[[ ! "$USE_POCKETIC" ]] && skip "skipped for replica: impersonating sender is only supported for PocketIC"

dfx identity new impersonated_identity --storage-mode plaintext
IDENTITY_PRINCIPAL="$(dfx --identity impersonated_identity identity get-principal)"
dfx identity remove impersonated_identity

impersonate_sender "${IDENTITY_PRINCIPAL}"
}
3 changes: 3 additions & 0 deletions src/dfx-core/src/canister/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ YOU WILL LOSE ALL DATA IN THE CANISTER.
.await
.map_err(CanisterInstallError::InstallWasmError)
}
CallSender::Impersonate(_) => {
unreachable!("Impersonating sender when installing canisters is not supported.")
}
CallSender::Wallet(wallet_id) => {
let wallet = build_wallet_canister(*wallet_id, agent).await?;
let install_args = CanisterInstall {
Expand Down
33 changes: 24 additions & 9 deletions src/dfx-core/src/config/model/local_server_descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,23 +360,38 @@ impl LocalServerDescriptor {
logger: Option<&Logger>,
) -> Result<Option<u16>, NetworkConfigError> {
let replica_port_path = self.replica_port_path();
let pocketic_port_path = self.pocketic_port_path();
match read_port_from(&replica_port_path)? {
Some(port) => {
if let Some(logger) = logger {
info!(logger, "Found local replica running on port {}", port);
}
Ok(Some(port))
}
None => match read_port_from(&pocketic_port_path)? {
Some(port) => {
if let Some(logger) = logger {
info!(logger, "Found local PocketIC running on port {}", port);
}
Ok(Some(port))
None => {
let port = self
.get_running_pocketic_port(logger)?
.or(self.replica.port);
Ok(port)
}
}
}

/// Gets the port of a local PocketIC instance.
///
/// # Prerequisites
/// - A local PocketIC instance needs to be running, e.g. with `dfx start --pocketic`.
pub fn get_running_pocketic_port(
&self,
logger: Option<&Logger>,
) -> Result<Option<u16>, NetworkConfigError> {
match read_port_from(&self.pocketic_port_path())? {
Some(port) => {
if let Some(logger) = logger {
info!(logger, "Found local PocketIC running on port {}", port);
}
None => Ok(self.replica.port),
},
Ok(Some(port))
}
None => Ok(None),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/dfx-core/src/identity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ impl AsRef<Identity> for Identity {
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum CallSender {
SelectedId,
Impersonate(Principal),
Wallet(Principal),
}

Expand Down
4 changes: 1 addition & 3 deletions src/dfx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ os_str_bytes = { version = "6.3.0", features = ["conversions"] }
patch = "0.7.0"
pem.workspace = true
petgraph = "0.6.0"
pocket-ic = { git = "https://github.com/dfinity/ic", rev = "3e24396441e4c7380928d4e8b4ccff7de77d0e7e" }
rand = "0.8.5"
regex = "1.5.5"
reqwest = { workspace = true, features = ["blocking", "json"] }
Expand Down Expand Up @@ -125,9 +126,6 @@ ci_info = "0.14"
[target.'cfg(windows)'.dependencies]
junction = "1.0.0"

[target.'cfg(unix)'.dependencies]
pocket-ic = { git = "https://github.com/dfinity/ic", rev = "3e24396441e4c7380928d4e8b4ccff7de77d0e7e" }

[dev-dependencies]
env_logger = "0.10"
proptest = "1.0"
Expand Down
Loading
Loading