Skip to content

Commit d72a148

Browse files
authored
[nexus] Add HTTPS support, plumbing x509 certificates (#1500)
Another attempt at #1287 In addition to launching an HTTPS server, this also launches an HTTP server so we can smoothly migrate clients (like the CLI). Part of #249
1 parent 6cdb6b4 commit d72a148

File tree

16 files changed

+148
-70
lines changed

16 files changed

+148
-70
lines changed

.github/buildomat/jobs/package.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ cargo --version
1717
rustc --version
1818

1919
ptime -m ./tools/install_builder_prerequisites.sh -yp
20+
ptime -m ./tools/create_self_signed_cert.sh -yp
2021

2122
ptime -m cargo run --locked --release --bin omicron-package -- package
2223

README.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ Supported config properties include:
8181
|
8282
|Yes
8383
|Dropshot configuration for the external server (i.e., the one that operators and developers using the Oxide rack will use). Specific properties are documented below, but see the Dropshot README for details.
84+
| Note that this is an array of external address configurations; multiple may be supplied.
8485

8586
|`dropshot_external.bind_address`
8687
|`"127.0.0.1:12220"`

common/src/nexus_config.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,12 @@ pub struct DeploymentConfig {
104104
pub id: Uuid,
105105
/// Uuid of the Rack where Nexus is executing.
106106
pub rack_id: Uuid,
107-
/// Dropshot configuration for external API server
108-
pub dropshot_external: ConfigDropshot,
109-
/// Dropshot configuration for internal API server
107+
/// Dropshot configurations for external API server.
108+
///
109+
/// Multiple configurations may be supplied to request
110+
/// combinations of HTTP / HTTPS servers.
111+
pub dropshot_external: Vec<ConfigDropshot>,
112+
/// Dropshot configuration for internal API server.
110113
pub dropshot_internal: ConfigDropshot,
111114
/// Portion of the IP space to be managed by the Rack.
112115
pub subnet: Ipv6Subnet<RACK_PREFIX>,

docs/how-to-run.adoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ This script requires Omicron be uninstalled, e.g., with `pfexec
5353
that is not the case. The script will then remove the file-based vdevs and the
5454
VNICs created by `create_virtual_hardware.sh`.
5555

56+
=== Make me a certificate!
57+
58+
Nexus's external interface will typically be served using public-facing x.509
59+
certificate. While we are still configuring the mechanism to integrate this real
60+
certificate into the package system, `./tools/create_self_signed_cert.sh` can be
61+
used to generate an equivalent self-signed certificate.
62+
5663
== Deploying Omicron
5764

5865
The control plane repository contains a packaging tool which bundles binaries

nexus/examples/config.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ address = "[::1]:8123"
3838
id = "e6bff1ff-24fb-49dc-a54e-c6a350cd4d6c"
3939
rack_id = "c19a698f-c6f9-4a17-ae30-20d711b8f7dc"
4040

41-
[deployment.dropshot_external]
42-
# IP address and TCP port on which to listen for the external API
41+
[[deployment.dropshot_external]]
42+
# IP Address and TCP port on which to listen for the external API
4343
bind_address = "127.0.0.1:12220"
4444
# Allow larger request bodies (1MiB) to accomodate firewall endpoints (one
4545
# rule is ~500 bytes)
4646
request_body_max_bytes = 1048576
4747

4848
[deployment.dropshot_internal]
49-
# IP address and TCP port on which to listen for the internal API
49+
# IP Address and TCP port on which to listen for the internal API
5050
bind_address = "127.0.0.1:12221"
5151

5252
[deployment.subnet]

nexus/src/config.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ mod test {
336336
[deployment]
337337
id = "28b90dc4-c22a-65ba-f49a-f051fe01208f"
338338
rack_id = "38b90dc4-c22a-65ba-f49a-f051fe01208f"
339-
[deployment.dropshot_external]
339+
[[deployment.dropshot_external]]
340340
bind_address = "10.1.2.3:4567"
341341
request_body_max_bytes = 1024
342342
[deployment.dropshot_internal]
@@ -358,12 +358,12 @@ mod test {
358358
rack_id: "38b90dc4-c22a-65ba-f49a-f051fe01208f"
359359
.parse()
360360
.unwrap(),
361-
dropshot_external: ConfigDropshot {
361+
dropshot_external: vec![ConfigDropshot {
362362
bind_address: "10.1.2.3:4567"
363363
.parse::<SocketAddr>()
364364
.unwrap(),
365365
..Default::default()
366-
},
366+
},],
367367
dropshot_internal: ConfigDropshot {
368368
bind_address: "10.1.2.3:4568"
369369
.parse::<SocketAddr>()
@@ -418,7 +418,7 @@ mod test {
418418
[deployment]
419419
id = "28b90dc4-c22a-65ba-f49a-f051fe01208f"
420420
rack_id = "38b90dc4-c22a-65ba-f49a-f051fe01208f"
421-
[deployment.dropshot_external]
421+
[[deployment.dropshot_external]]
422422
bind_address = "10.1.2.3:4567"
423423
request_body_max_bytes = 1024
424424
[deployment.dropshot_internal]
@@ -460,7 +460,7 @@ mod test {
460460
[deployment]
461461
id = "28b90dc4-c22a-65ba-f49a-f051fe01208f"
462462
rack_id = "38b90dc4-c22a-65ba-f49a-f051fe01208f"
463-
[deployment.dropshot_external]
463+
[[deployment.dropshot_external]]
464464
bind_address = "10.1.2.3:4567"
465465
request_body_max_bytes = 1024
466466
[deployment.dropshot_internal]
@@ -516,7 +516,7 @@ mod test {
516516
[deployment]
517517
id = "28b90dc4-c22a-65ba-f49a-f051fe01208f"
518518
rack_id = "38b90dc4-c22a-65ba-f49a-f051fe01208f"
519-
[deployment.dropshot_external]
519+
[[deployment.dropshot_external]]
520520
bind_address = "10.1.2.3:4567"
521521
request_body_max_bytes = 1024
522522
[deployment.dropshot_internal]

nexus/src/lib.rs

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ pub fn run_openapi_internal() -> Result<(), String> {
7171
pub struct Server {
7272
/// shared state used by API request handlers
7373
pub apictx: Arc<ServerContext>,
74-
/// dropshot server for external API
75-
pub http_server_external: dropshot::HttpServer<Arc<ServerContext>>,
74+
/// dropshot servers for external API
75+
pub http_servers_external: Vec<dropshot::HttpServer<Arc<ServerContext>>>,
7676
/// dropshot server for internal API
7777
pub http_server_internal: dropshot::HttpServer<Arc<ServerContext>>,
7878
}
@@ -92,26 +92,36 @@ impl Server {
9292
ServerContext::new(config.deployment.rack_id, ctxlog, &config)
9393
.await?;
9494

95-
let http_server_starter_external = dropshot::HttpServerStarter::new(
96-
&config.deployment.dropshot_external,
97-
external_api(),
98-
Arc::clone(&apictx),
99-
&log.new(o!("component" => "dropshot_external")),
100-
)
101-
.map_err(|error| format!("initializing external server: {}", error))?;
102-
103-
let http_server_starter_internal = dropshot::HttpServerStarter::new(
95+
// Launch the internal server.
96+
let server_starter_internal = dropshot::HttpServerStarter::new(
10497
&config.deployment.dropshot_internal,
10598
internal_api(),
10699
Arc::clone(&apictx),
107100
&log.new(o!("component" => "dropshot_internal")),
108101
)
109102
.map_err(|error| format!("initializing internal server: {}", error))?;
110-
111-
let http_server_external = http_server_starter_external.start();
112-
let http_server_internal = http_server_starter_internal.start();
113-
114-
Ok(Server { apictx, http_server_external, http_server_internal })
103+
let http_server_internal = server_starter_internal.start();
104+
105+
// Launch the external server(s).
106+
let http_servers_external = config
107+
.deployment
108+
.dropshot_external
109+
.iter()
110+
.map(|cfg| {
111+
let server_starter_external = dropshot::HttpServerStarter::new(
112+
&cfg,
113+
external_api(),
114+
Arc::clone(&apictx),
115+
&log.new(o!("component" => "dropshot_external")),
116+
)
117+
.map_err(|error| {
118+
format!("initializing external server: {}", error)
119+
})?;
120+
Ok(server_starter_external.start())
121+
})
122+
.collect::<Result<Vec<dropshot::HttpServer<_>>, String>>()?;
123+
124+
Ok(Server { apictx, http_servers_external, http_server_internal })
115125
}
116126

117127
/// Wait for the given server to shut down
@@ -120,18 +130,20 @@ impl Server {
120130
/// immediately after calling `start()`, the program will block indefinitely
121131
/// or until something else initiates a graceful shutdown.
122132
pub async fn wait_for_finish(self) -> Result<(), String> {
123-
let errors = vec![
124-
self.http_server_external
125-
.await
126-
.map_err(|e| format!("external: {}", e)),
133+
let mut errors = vec![];
134+
for server in self.http_servers_external {
135+
errors.push(server.await.map_err(|e| format!("external: {}", e)));
136+
}
137+
errors.push(
127138
self.http_server_internal
128139
.await
129140
.map_err(|e| format!("internal: {}", e)),
130-
]
131-
.into_iter()
132-
.filter(Result::is_err)
133-
.map(|r| r.unwrap_err())
134-
.collect::<Vec<String>>();
141+
);
142+
let errors = errors
143+
.into_iter()
144+
.filter(Result::is_err)
145+
.map(|r| r.unwrap_err())
146+
.collect::<Vec<String>>();
135147

136148
if errors.len() > 0 {
137149
let msg = format!("errors shutting down: ({})", errors.join(", "));

nexus/test-utils/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ pub struct ControlPlaneTestContext {
4646

4747
impl ControlPlaneTestContext {
4848
pub async fn teardown(mut self) {
49-
self.server.http_server_external.close().await.unwrap();
49+
for server in self.server.http_servers_external {
50+
server.close().await.unwrap();
51+
}
5052
self.server.http_server_internal.close().await.unwrap();
5153
self.database.cleanup().await.unwrap();
5254
self.clickhouse.cleanup().await.unwrap();
@@ -119,7 +121,7 @@ pub async fn test_setup_with_config(
119121
.expect("Nexus never loaded users");
120122

121123
let testctx_external = ClientTestContext::new(
122-
server.http_server_external.local_addr(),
124+
server.http_servers_external[0].local_addr(),
123125
logctx.log.new(o!("component" => "external client test context")),
124126
);
125127
let testctx_internal = ClientTestContext::new(

nexus/tests/config.test.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,13 @@ max_vpc_ipv4_subnet_prefix = 29
4141
id = "e6bff1ff-24fb-49dc-a54e-c6a350cd4d6c"
4242
rack_id = "c19a698f-c6f9-4a17-ae30-20d711b8f7dc"
4343

44-
#
44+
[[deployment.dropshot_external]]
4545
# NOTE: for the test suite, the port MUST be 0 (in order to bind to any
4646
# available port) because the test suite will be running many servers
4747
# concurrently.
48-
#
49-
[deployment.dropshot_external]
5048
bind_address = "127.0.0.1:0"
5149
request_body_max_bytes = 1048576
5250

53-
# port must be 0. see above
5451
[deployment.dropshot_internal]
5552
bind_address = "127.0.0.1:0"
5653
request_body_max_bytes = 1048576

nexus/tests/integration_tests/authn_http.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ async fn start_whoami_server(
299299
TestContext::new(
300300
whoami_api,
301301
server_state,
302-
&config.deployment.dropshot_external,
302+
&config.deployment.dropshot_external[0],
303303
Some(logctx),
304304
log,
305305
)

0 commit comments

Comments
 (0)