Skip to content

Commit

Permalink
Merge branch 'master' into spofford/safari-message
Browse files Browse the repository at this point in the history
  • Loading branch information
adamspofford-dfinity authored Jan 6, 2025
2 parents 8872b2d + 79de1d0 commit 5b27d83
Show file tree
Hide file tree
Showing 15 changed files with 338 additions and 20 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ DFX's default security headers cause Safari to break when viewing local canister
have been added to the frontend project templates when the page is broken that indicate to try switching
browsers.

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

`dfx canister call`, `dfx canister status`, and `dfx canister update-settings` take
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

0 comments on commit 5b27d83

Please sign in to comment.