Skip to content
Merged
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions crates/aisix-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,57 @@ pub struct EtcdTlsConfig {
#[serde(deny_unknown_fields, default)]
pub struct ManagedConfig {
pub enabled: bool,

/// When set, aisix performs a one-shot `POST /dp/register` against
/// `cp_base_url` at boot to exchange this token for an mTLS bundle
/// and a dp_id. Subsequent boots detect the existing bundle at
/// `mtls_dir` and skip re-registration (the token is single-use).
///
/// Leave empty if the mTLS bundle is already on disk — typical for
/// configs installed via an out-of-band "download bundle" flow.
#[serde(default)]
pub registration_token: Option<String>,

/// aisix.cloud CP base URL, e.g. "https://api.us.aisix.cloud".
/// Required whenever `registration_token` is set.
#[serde(default)]
pub cp_base_url: Option<String>,

/// Directory where the DP persists `ca.crt`, `client.crt`,
/// `client.key` received from the register response. Files are
/// written `0600`. Parent directory must already exist and be
/// writable by the aisix process user.
Comment on lines +133 to +134

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

Docs vs behavior: this comment says the mtls_dir parent directory must already exist, but persist_mtls() currently calls create_dir_all. Either update the documentation to match (directory will be created) or change the code to enforce the documented requirement.

Suggested change
/// written `0600`. Parent directory must already exist and be
/// writable by the aisix process user.
/// written `0600`. The directory will be created if it does not
/// already exist and must be writable by the aisix process user.

Copilot uses AI. Check for mistakes.
#[serde(default = "ManagedConfig::default_mtls_dir")]
pub mtls_dir: String,

/// File where the DP persists its `dp_id`. Read back on restart
/// for heartbeat / telemetry payloads. Same permission rules as
/// the mTLS files.
#[serde(default = "ManagedConfig::default_dp_id_file")]
pub dp_id_file: String,
}

impl ManagedConfig {
/// True if the DP should behave as an aisix.cloud tenant.
pub const fn is_managed(&self) -> bool {
self.enabled
}

/// True when both the token and CP URL are set — i.e. the DP
/// should attempt `/dp/register` at boot.
pub fn registration_enabled(&self) -> bool {
self.registration_token
.as_deref()
.is_some_and(|s| !s.is_empty())
&& self.cp_base_url.as_deref().is_some_and(|s| !s.is_empty())
}

fn default_mtls_dir() -> String {
"/var/lib/aisix/mtls".into()
}
fn default_dp_id_file() -> String {
"/var/lib/aisix/dp_id".into()
}
}

impl EtcdConfig {
Expand Down
5 changes: 5 additions & 0 deletions crates/aisix-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,8 @@ thiserror.workspace = true
anyhow.workspace = true
tracing.workspace = true
etcd-client.workspace = true
reqwest.workspace = true

[dev-dependencies]
tempfile = "3"
wiremock = "0.6"
Comment on lines +49 to +50

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

Repo convention: other crates use workspace-pinned test deps (e.g., wiremock.workspace = true, tempfile.workspace = true). Consider switching these dev-dependencies to workspace to avoid version drift across crates.

Suggested change
tempfile = "3"
wiremock = "0.6"
tempfile.workspace = true
wiremock.workspace = true

Copilot uses AI. Check for mistakes.
39 changes: 37 additions & 2 deletions crates/aisix-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
use std::path::PathBuf;
use std::sync::Arc;

mod register;

use aisix_admin::{AdminState, ConfigStore, EtcdConfigStore};
use aisix_cache::{Cache, MemoryCache, RedisCache};
use aisix_core::models::Provider;
use aisix_core::{CacheBackend, Config, EtcdConfig};
use aisix_core::{CacheBackend, Config, EtcdConfig, EtcdTlsConfig};
use aisix_etcd::{EtcdConfigProvider, Supervisor};
use aisix_gateway::Hub;
use aisix_obs::{init_tracing, install_otlp_tracer, langfuse, Metrics};
Expand Down Expand Up @@ -58,7 +60,40 @@ async fn main() -> anyhow::Result<()> {

/// Factored out of `main` so the integration tests can drive the full
/// startup with a real config struct and still use `#[tokio::test]`.
async fn run(cfg: Config) -> anyhow::Result<()> {
async fn run(mut cfg: Config) -> anyhow::Result<()> {
// If this is a managed tenant and the mTLS bundle isn't on disk
// yet, perform the one-shot `POST /dp/register` exchange against
// the aisix.cloud control plane. The response fills in
// `cfg.etcd.endpoints` + `cfg.etcd.tls` so the regular etcd
// connect path below is oblivious to whether certs came from an
// out-of-band install or just-now registration.
if cfg.managed.is_managed()
&& !register::bundle_exists(&cfg.managed.mtls_dir)
&& cfg.managed.registration_enabled()
{
tracing::info!("managed mode: registering with aisix.cloud CP");
let r = register::register_and_persist(&cfg.managed)
.await
.map_err(|e| anyhow::anyhow!("DP registration failed: {e:#}"))?;
tracing::info!(
dp_id = %r.dp_id,
gateway_id = %r.gateway_id,
etcd = %r.etcd_endpoint,
"registered with control plane",
);
// Override the static config with what the CP handed back.
// Endpoints get the https:// scheme re-attached (the CP sends
// a bare host:port, but tonic-based etcd-client expects a
// full URL for TLS endpoints).
cfg.etcd.endpoints = vec![format!("https://{}", r.etcd_endpoint)];
cfg.etcd.tls = Some(EtcdTlsConfig {
ca_cert_file: r.ca_cert_path.to_string_lossy().into_owned(),
client_cert_file: r.client_cert_path.to_string_lossy().into_owned(),
client_key_file: r.client_key_path.to_string_lossy().into_owned(),
domain_name: None, // derive from endpoint host
});
}

// Steps 4-6: etcd + supervisor.
let connect_options = build_etcd_connect_options(&cfg.etcd)?;
let provider = Arc::new(
Expand Down
Loading
Loading