Skip to content

Commit 559e790

Browse files
committed
refactor
1 parent 0e33626 commit 559e790

7 files changed

Lines changed: 239 additions & 28 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Triggers a build in api7/aisix-ee whenever this repo is pushed to main or tagged.
2+
# On main push: dev-build event (aisix-ee uses branch="main" in Cargo.toml)
3+
# On v* tag: release-build event (aisix-ee CI patches Cargo.toml to use the tag)
4+
name: Notify EE Build
5+
6+
on:
7+
push:
8+
branches: ["main"]
9+
tags: ["v*"]
10+
11+
jobs:
12+
notify:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Dispatch to aisix-ee (dev build)
16+
if: github.ref == 'refs/heads/main'
17+
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # 4.0.1
18+
with:
19+
# PAT with repo scope for api7/aisix-ee, stored in api7/aisix secrets
20+
token: ${{ secrets.EE_DISPATCH_TOKEN }}
21+
repository: api7/aisix-ee
22+
event-type: dev-build
23+
client-payload: '{"sha": "${{ github.sha }}"}'
24+
25+
- name: Dispatch to aisix-ee (release build)
26+
if: startsWith(github.ref, 'refs/tags/v')
27+
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # 4.0.1
28+
with:
29+
token: ${{ secrets.EE_DISPATCH_TOKEN }}
30+
repository: api7/aisix-ee
31+
event-type: release-build
32+
client-payload: '{"tag": "${{ github.ref_name }}"}'

Cargo.lock

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ axum = { version = "0.8.9", features = [
3333
], default-features = false }
3434
serde = "1.0.228"
3535
serde_json = "1.0"
36-
etcd-client = "0.18.0"
36+
etcd-client = { version = "0.18.0", features = ["tls-openssl"] }
3737
async-trait = "0.1"
3838
futures = "0.3"
3939
bytes = "1.0"
@@ -80,6 +80,11 @@ axum-server = { version = "0.8.0", default-features = false, features = [
8080
backon = { version = "1.6.0", default-features = false, features = [
8181
"tokio-sleep",
8282
] }
83+
base64 = "0.22"
84+
85+
[build-dependencies]
86+
vergen-git2 = { version = "9.1.0" }
87+
anyhow = "1"
8388

8489
[dev-dependencies]
8590
tokio-test = "0.4"
@@ -88,10 +93,6 @@ pretty_assertions = "1.4"
8893
rstest = "0.26"
8994
tempfile = "3"
9095

91-
[build-dependencies]
92-
anyhow = "1"
93-
vergen-git2 = { version = "9.1.0" }
94-
9596
[patch.crates-io]
9697
opentelemetry = { git = "https://github.com/open-telemetry/opentelemetry-rust.git", rev = "965078315b58ae14725721735f1c8e2bc2d3b445" }
9798
opentelemetry_sdk = { git = "https://github.com/open-telemetry/opentelemetry-rust.git", rev = "965078315b58ae14725721735f1c8e2bc2d3b445" }

build.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
11
use std::{env, fs, process};
22

3-
use anyhow::{Result, anyhow};
3+
use anyhow::{anyhow, Result};
44
use vergen_git2::{Emitter, Git2Builder};
55

66
fn main() -> Result<()> {
77
build_ui()?;
88

9-
Emitter::default()
9+
// When built as a git dependency (no .git directory present), vergen_git2
10+
// cannot read the SHA. We emit a fallback value and continue rather than
11+
// failing the build.
12+
let result = Emitter::default()
1013
.add_instructions(&Git2Builder::default().sha(true).build()?)?
11-
.emit()?;
14+
.emit();
15+
16+
if result.is_err() {
17+
// Emit a placeholder so that code referencing VERGEN_GIT_SHA compiles.
18+
println!("cargo:rustc-env=VERGEN_GIT_SHA=unknown");
19+
}
1220

1321
println!("cargo:rerun-if-changed=build.rs");
1422

1523
Ok(())
1624
}
1725

1826
fn build_ui() -> Result<()> {
27+
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
28+
1929
if env::var("CARGO_FEATURE_BUILD_UI").is_ok() {
2030
println!("cargo:rerun-if-changed=ui");
2131

2232
let status = process::Command::new("pnpm")
2333
.args(["install", "--frozen-lockfile"])
24-
.current_dir("ui")
34+
.current_dir(format!("{manifest_dir}/ui"))
2535
.stdout(std::process::Stdio::inherit())
2636
.stderr(std::process::Stdio::inherit())
2737
.status()?;
@@ -35,7 +45,7 @@ fn build_ui() -> Result<()> {
3545

3646
let status = process::Command::new("pnpm")
3747
.args(["run", "build"])
38-
.current_dir("ui")
48+
.current_dir(format!("{manifest_dir}/ui"))
3949
.stdout(std::process::Stdio::inherit())
4050
.stderr(std::process::Stdio::inherit())
4151
.status()?;
@@ -44,7 +54,7 @@ fn build_ui() -> Result<()> {
4454
return Err(anyhow!("failed to build ui with status: {}", status));
4555
}
4656
} else {
47-
fs::create_dir_all("ui/dist")?;
57+
fs::create_dir_all(format!("{manifest_dir}/ui/dist"))?;
4858
}
4959
Ok(())
5060
}

src/config/etcd.rs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,21 @@ const MAX_BACKOFF: Duration = Duration::from_secs(60);
2020
/// Initial backoff delay.
2121
const INITIAL_BACKOFF: Duration = Duration::from_secs(1);
2222

23+
#[derive(Clone, Debug, Default, Deserialize)]
24+
pub struct EtcdTlsConfig {
25+
pub ca_pem: Option<String>,
26+
pub cert_pem: Option<String>,
27+
pub key_pem: Option<String>,
28+
}
29+
2330
#[derive(Clone, Debug, Deserialize)]
2431
pub struct Config {
2532
pub host: Vec<String>,
2633
pub prefix: String,
2734
pub timeout: u32,
2835
pub user: Option<String>,
2936
pub password: Option<String>,
37+
pub tls: Option<EtcdTlsConfig>,
3038
}
3139

3240
impl Default for Config {
@@ -37,6 +45,7 @@ impl Default for Config {
3745
timeout: 5,
3846
user: None,
3947
password: None,
48+
tls: None,
4049
}
4150
}
4251
}
@@ -89,6 +98,64 @@ impl EtcdConfigProvider {
8998
opts = opts.with_user(user, password);
9099
}
91100

101+
// Enable TLS when any host uses the https:// scheme.
102+
//
103+
// We use the openssl-tls backend (not rustls) because the control-plane
104+
// etcd certificate may have a misconfigured SAN (e.g. only an empty DNS
105+
// name instead of the server IP). With OpenSSL we can skip hostname
106+
// verification while still validating the certificate chain against the
107+
// supplied CA, which is equivalent to `curl --cacert` without `-k`.
108+
let use_tls = config.host.iter().any(|h| h.starts_with("https://"));
109+
if use_tls {
110+
use openssl::ssl::SslVerifyMode;
111+
112+
let mut ssl_cfg = etcd_client::OpenSslClientConfig::default();
113+
114+
if let Some(tls_cfg) = &config.tls {
115+
if let Some(ca_pem) = &tls_cfg.ca_pem {
116+
// ca_cert_pem() only loads the first certificate in a PEM
117+
// bundle. Use stack_from_pem so that multi-cert bundles
118+
// (e.g. intermediate + root) are all added to the store.
119+
let ca_bytes = ca_pem.as_bytes().to_vec();
120+
ssl_cfg = ssl_cfg.manually(move |b| {
121+
for cert in openssl::x509::X509::stack_from_pem(&ca_bytes)? {
122+
b.cert_store_mut().add_cert(cert)?;
123+
}
124+
Ok(())
125+
});
126+
}
127+
128+
if let (Some(cert_pem), Some(key_pem)) =
129+
(&tls_cfg.cert_pem, &tls_cfg.key_pem)
130+
{
131+
ssl_cfg =
132+
ssl_cfg.client_cert_pem_and_key(cert_pem.as_bytes(), key_pem.as_bytes());
133+
}
134+
}
135+
136+
// Skip hostname/IP verification: the CP certificate SAN may not match
137+
// the endpoint IP/hostname. We still validate the certificate chain
138+
// (preverify_ok covers chain errors). Error code 64 is
139+
// X509_V_ERR_IP_ADDRESS_MISMATCH; we allow it explicitly so that
140+
// IP-addressed endpoints with a non-matching SAN still connect.
141+
ssl_cfg = ssl_cfg.manually(|b| {
142+
b.set_verify_callback(SslVerifyMode::PEER, |preverify_ok, ctx| {
143+
if preverify_ok {
144+
return true;
145+
}
146+
// Allow IP address mismatch and hostname mismatch errors.
147+
matches!(
148+
ctx.error().as_raw(),
149+
64 // X509_V_ERR_IP_ADDRESS_MISMATCH
150+
| 62 // X509_V_ERR_HOSTNAME_MISMATCH
151+
)
152+
});
153+
Ok(())
154+
});
155+
156+
opts = opts.with_openssl_tls(ssl_cfg);
157+
}
158+
92159
let mut client = etcd_client::Client::connect(
93160
config
94161
.host
@@ -99,7 +166,10 @@ impl EtcdConfigProvider {
99166
)
100167
.await?;
101168

102-
client.status().await.map(|_| Ok(client))?
169+
// Use a KV.Get as the connectivity probe instead of Maintenance.Status.
170+
// The API7 dp-manager only implements KV/Watch/Lease; calling Status
171+
// returns HTTP 404 which tonic misparses as a gRPC framing error.
172+
client.get("__probe__", None).await.map(|_| Ok(client))?
103173
}
104174

105175
/// Spawn the long-running supervisor task that manages the watch stream

0 commit comments

Comments
 (0)