Skip to content

feat: all-in-one nodes for cloud engines - #11349

Open
r-birkner wants to merge 5 commits into
masterfrom
rjb/make-orchestrator-fetch-cloud-engine-config
Open

feat: all-in-one nodes for cloud engines#11349
r-birkner wants to merge 5 commits into
masterfrom
rjb/make-orchestrator-fetch-cloud-engine-config

Conversation

@r-birkner

@r-birkner r-birkner commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Cloud engines nodes will be "all-in-one" nodes. This means they don't just run the replica, but also ic-gateway and potentially also ic-boundary. The orchestrator already supports running multiple processes, but we can't just start ic-gateway, we also need to provide it with the right, engine-specific config. That's what this PR is doing:

This PR adds an additional service to the orchestrator that fetches the HTTP Gateway config from the engine operator canister and uses it to run ic-gateway.

The config includes the domain names, DNS API URLs and API key (for the DNS-01 ACME challenge to obtain the certificates), and the ACME account. That's the bare minimum for ic-gateway to run. In the future, the config will be extended.

The config is stored in the engine operator canister, which resides on the engine itself. The orchestrator first has to discover this canister using the engine management canister. Then, it needs to use the node's identity to make the call as the config is protected and can only be accessed by the nodes making up the engine.

@github-actions github-actions Bot added the feat label Aug 27, 2026
@r-birkner
r-birkner marked this pull request as ready for review August 27, 2026 12:56
@r-birkner
r-birkner requested review from a team as code owners August 27, 2026 12:56
@zeropath-ai

zeropath-ai Bot commented Aug 27, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 918cb72.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► ic-os/components/guestos/share/ic-gateway.env
    Update gateway environment for TLS and ACME settings
► rs/config/src/cloud_engine.rs
    Add cloud engine configuration (MAINNET_ENGINE_MANAGEMENT_CANISTER_ID) and Cloud Engine Config struct
► rs/config/src/config.rs
    Integrate cloud_engine config into main Config
► rs/config/src/config_sample.rs
    Add sample cloud_engine config and test asserting engine management canister id
► rs/config/src/config_sample.rs
    Add unit tests referencing MAINNET_ENGINE_MANAGEMENT_CANISTER_ID
Enhancement ► rs/ic_os/config/tool/src/guestos/generate_ic_config.rs
    Include engine_management_canister_id in IcConfigTemplate generation (derived from GuestOSConfig or deployment environment)
Enhancement ► rs/ic_os/config/tool/templates/ic.json5.template
    Extend JSON5 template to include cloud_engine block with engine_management_canister_id and adjust related ACLs in IPv4/IPv6 rule formatting
Enhancement ► rs/ic_os/config/types/src/lib.rs
    Bump CONFIG_VERSION to 1.17.0 and document new engine_management_canister_id field in GuestOSSettings
► rs/ic_os/config/types/src/lib.rs
    Add engine_management_canister_id to GuestOSSettings (optional)
Enhancement ► rs/orchestrator/BUILD.bazel
    Add dependency on ic-registry-routing_table
► rs/orchestrator/Cargo.toml
    Include ic-registry-routing-table as dependency
Enhancement ► rs/orchestrator/src/cloud_engine/agent.rs
    Add implementation for building agents to reach engine management and operator canisters (new file)
Enhancement ► rs/orchestrator/src/cloud_engine/config.rs
    Add GatewayConfig, AcmeAccount, ConfigError, TryFrom for gateway config, env_overlay, tests, and Debug/Display implementations (new file)
Enhancement ► rs/orchestrator/src/cloud_engine/discovery.rs
    Add Discovery logic for resolving engine operator canister via memory or engine management canister (new file)
Enhancement ► rs/orchestrator/src/cloud_engine/mod.rs
    Add cloud_engine module with submodules and GatewayConfig export (new file)
Enhancement ► rs/orchestrator/src/cloud_engine/discovery.rs
    Add tests for discovery logic (new file)
Enhancement ► rs/orchestrator/src/cloud_engine/operator.rs
(Not shown in diff; referenced in mod.rs)
Enhancement ► rs/orchestrator/src/cloud_engine/operator.rs
    Add operator client and related functionality (reference shown)
Bug Fix / Refactor ► rs/ic_os/config/types/compatibility_tests/fixtures/guestos_v1.17.0.json
    Add new fixture reflecting new engine_management_canister_id field (data change)
Other ► Cargo.lock: no explicit changes detected to dependencies here beyond addition of routing_table in registry dependencies (implied by build/system changes)

@basvandijk basvandijk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve for the files under rs/tests/driver/src/driver/.

A PR description would be nice for a >2k diff ;)

@zeropath-ai

zeropath-ai Bot commented Aug 27, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 918cb72.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► ic-os/components/guestos/share/ic-gateway.env
    Update gateway environment for TLS and ACME settings
► rs/config/src/cloud_engine.rs
    Add cloud engine configuration (MAINNET_ENGINE_MANAGEMENT_CANISTER_ID) and Cloud Engine Config struct
► rs/config/src/config.rs
    Integrate cloud_engine config into main Config
► rs/config/src/config_sample.rs
    Add sample cloud_engine config and test asserting engine management canister id
► rs/config/src/config_sample.rs
    Add unit tests referencing MAINNET_ENGINE_MANAGEMENT_CANISTER_ID
Enhancement ► rs/ic_os/config/tool/src/guestos/generate_ic_config.rs
    Include engine_management_canister_id in IcConfigTemplate generation (derived from GuestOSConfig or deployment environment)
Enhancement ► rs/ic_os/config/tool/templates/ic.json5.template
    Extend JSON5 template to include cloud_engine block with engine_management_canister_id and adjust related ACLs in IPv4/IPv6 rule formatting
Enhancement ► rs/ic_os/config/types/src/lib.rs
    Bump CONFIG_VERSION to 1.17.0 and document new engine_management_canister_id field in GuestOSSettings
► rs/ic_os/config/types/src/lib.rs
    Add engine_management_canister_id to GuestOSSettings (optional)
Enhancement ► rs/orchestrator/BUILD.bazel
    Add dependency on ic-registry-routing_table
► rs/orchestrator/Cargo.toml
    Include ic-registry-routing-table as dependency
Enhancement ► rs/orchestrator/src/cloud_engine/agent.rs
    Add implementation for building agents to reach engine management and operator canisters (new file)
Enhancement ► rs/orchestrator/src/cloud_engine/config.rs
    Add GatewayConfig, AcmeAccount, ConfigError, TryFrom for gateway config, env_overlay, tests, and Debug/Display implementations (new file)
Enhancement ► rs/orchestrator/src/cloud_engine/discovery.rs
    Add Discovery logic for resolving engine operator canister via memory or engine management canister (new file)
Enhancement ► rs/orchestrator/src/cloud_engine/mod.rs
    Add cloud_engine module with submodules and GatewayConfig export (new file)
Enhancement ► rs/orchestrator/src/cloud_engine/discovery.rs
    Add tests for discovery logic (new file)
Enhancement ► rs/orchestrator/src/cloud_engine/operator.rs
(Not shown in diff; referenced in mod.rs)
Enhancement ► rs/orchestrator/src/cloud_engine/operator.rs
    Add operator client and related functionality (reference shown)
Bug Fix / Refactor ► rs/ic_os/config/types/compatibility_tests/fixtures/guestos_v1.17.0.json
    Add new fixture reflecting new engine_management_canister_id field (data change)
Other ► Cargo.lock: no explicit changes detected to dependencies here beyond addition of routing_table in registry dependencies (implied by build/system changes)

@pierugo-dfinity pierugo-dfinity left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we confident launching this without a system test?

Would there be a way to test that the different arguments to different methods of different canisters do not drift away from production?

Comment on lines +10 to +16
pub struct Config {
/// Principal of the engine management canister, which an all-in-one node
/// needs to discover the operator canister of its own engine. When unset,
/// the node cannot find its operator and therefore does not run `ic-gateway`.
#[serde(default)]
pub engine_management_canister_id: Option<CanisterId>,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we consider putting that information to the registry instead of the config? Personally, having a hard-coded canister ID inside the config feels weird + the registry can be modified if we need to + I think it would be easier to mock in tests.

And the orchestrator would know whether to launch the CloudEngineManager or not based on its node reward type, the same way it tells it is a cloud-engine node for the registry replicator & firewall.

But this is not a strong opinion, just interested in what you think

Comment on lines +204 to +217
let engine_management_canister_id = match guestos_config
.guestos_settings
.engine_management_canister_id
.as_deref()
.or(matches!(
guestos_config.icos_settings.deployment_environment,
DeploymentEnvironment::Mainnet
)
.then_some(ic_config::cloud_engine::MAINNET_ENGINE_MANAGEMENT_CANISTER_ID))
{
Some(id) => serde_json::to_string(id)
.context("Failed to encode the engine management canister id")?,
None => "null".to_string(),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let engine_management_canister_id = match guestos_config
.guestos_settings
.engine_management_canister_id
.as_deref()
.or(matches!(
guestos_config.icos_settings.deployment_environment,
DeploymentEnvironment::Mainnet
)
.then_some(ic_config::cloud_engine::MAINNET_ENGINE_MANAGEMENT_CANISTER_ID))
{
Some(id) => serde_json::to_string(id)
.context("Failed to encode the engine management canister id")?,
None => "null".to_string(),
};
let engine_management_canister_id = match guestos_config
.guestos_settings
.engine_management_canister_id
.as_deref() {
Some(id) => id,
None if guestos_config.icos_settings.deployment_environment == DeploymentEnvironment::Mainnet => ic_config::cloud_engine::MAINNET_ENGINE_MANAGEMENT_CANISTER_ID,
None => "null",
}
.to_string();

Maybe something like this would clarify the control flow. I'm not sure why use serde_json::to_string either

DomainNameMissingError(NodeId, RegistryVersion),

/// A step of the cloud engine configuration lookup failed.
CloudEngineError(String),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a lot of different Error enums (FetchError, OperatorError, ConfigError), yet not complete (AgentFactory and Discovery return OrchestratorErrors).

  • Would it be feasible to flatten the existing errors into one big CloudEngineManagementError enum holding every possible variant? Maybe in its own orchestrator/src/cloud_engine/error.rs
  • I don't think we need a new variant in OrchestratorError. Existing callers of cloud_engine_error can have their own enums (AgentFactory/Discovery) or use a different variant (in registry_helper.rs and in fn env_overlay.

registry_helper::RegistryHelper,
};
use config::ConfigError;
pub(crate) use config::GatewayConfig;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to re-export, I think callers can import the submodule directly

/// needs to discover the operator canister of its own engine. When unset,
/// the node cannot find its operator and therefore does not run `ic-gateway`.
#[serde(default)]
pub engine_management_canister_id: Option<CanisterId>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that despite the very misleading documentation, CanisterId's implementation of Deserialize does not check that it is a valid canister ID (it could be any principal) because CanisterId::try_from does not actually try, it just casts without validating.

impl<'de> Deserialize<'de> for CanisterId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
// Not all principals are valid inside a CanisterId.
// Therefore, deserialization must explicitly
// transform the PrincipalId into a CanisterId.
// A derived implementation of Deserialize would open
// the door to invariant violation.
CanisterId::try_from(PrincipalId::deserialize(deserializer)?).map_err(D::Error::custom)
}
}

fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GatewayConfig")
.field("base_domains", &self.base_domains)
.field("dns_api_url", &self.dns_api_url.as_str())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure: the DNS API URL doesn't contain anything sensitive (like query parameters or whatnot)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all as intended: the DNS API config consists of the URL and the key (below). The key is sensitive, the URL not.

}

#[test]
fn every_missing_field_is_incomplete() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style nit: we could use rstest here (but this is fine too)

assert_eq!(
discovery.validated_from_memory(SUBNET_1, VERSION),
Some(operator())
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert_eq!(discovery.resolved, Some(operator()));

assert_matches!(
discovery.validate(operator().get(), SUBNET_1, VERSION),
Ok(accepted) if accepted == operator()
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert_eq!(discovery.resolved, Some(operator()));

SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_or(0, |since_epoch| since_epoch.as_secs() as i64)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some unit tests for this module would be more than welcome 🙏 (especially the invalidation logic)

@frankdavid frankdavid left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻 for node

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants