-
Notifications
You must be signed in to change notification settings - Fork 91
Expand file tree
/
Copy pathallow_list.rs
More file actions
138 lines (130 loc) · 5.42 KB
/
Copy pathallow_list.rs
File metadata and controls
138 lines (130 loc) · 5.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! Nexus methods for operating on source IP allowlists.
use nexus_db_queries::context::OpContext;
use nexus_networking::MAX_ALLOWLIST_LENGTH;
use nexus_types::external_api::system;
use omicron_common::api::external;
use omicron_common::api::external::Error;
use std::net::IpAddr;
use crate::context::ServerKind;
impl super::Nexus {
/// Fetch the allowlist of source IPs that can reach user-facing services.
pub async fn allow_list_view(
&self,
opctx: &OpContext,
) -> Result<system::AllowList, Error> {
self.db_datastore
.allow_list_view(opctx)
.await
.and_then(system::AllowList::try_from)
}
/// Upsert the allowlist of source IPs that can reach user-facing services.
pub async fn allow_list_upsert(
&self,
opctx: &OpContext,
remote_addr: IpAddr,
server_kind: ServerKind,
params: system::AllowListUpdate,
) -> Result<system::AllowList, Error> {
if let external::AllowedSourceIps::List(list) = ¶ms.allowed_ips {
if list.len() > MAX_ALLOWLIST_LENGTH {
let message = format!(
"Source IP allowlist is limited to {} entries, found {}",
MAX_ALLOWLIST_LENGTH,
list.len(),
);
return Err(Error::invalid_request(message));
}
// Some basic sanity-checks on the addresses in the allowlist.
//
// The most important part here is checking that the source address
// the request came from is on the allowlist. This is our only real
// guardrail to prevent accidentally preventing any future access to
// the rack!
//
// Note that we elide this check when handling a request proxied
// from `wicketd`. This is intentional and used as a safety
// mechanism in the even of lockout or other recovery scenarios.
let check_remote_addr = match server_kind {
ServerKind::External => true,
ServerKind::Techport | ServerKind::Internal => false,
};
let mut contains_remote = false;
for entry in list.iter() {
contains_remote |= entry.contains(remote_addr);
if entry.addr().is_unspecified() {
return Err(Error::invalid_request(
"Source IP allowlist may not contain the \
unspecified address. Use \"any\" to allow \
any source to connect to user-facing services.",
));
}
if entry.width() == 0 {
return Err(Error::invalid_request(
"Source IP allowlist entries may not have \
a netmask of /0.",
));
}
}
if check_remote_addr && !contains_remote {
return Err(Error::invalid_request(
"The source IP allow list would prevent access \
from the current client! Ensure that the allowlist \
contains an entry that continues to allow access \
from this peer.",
));
}
};
// Actually insert the new allowlist.
let list = self
.db_datastore
.allow_list_upsert(opctx, params.allowed_ips.clone())
.await
.and_then(system::AllowList::try_from)?;
// Notify the sled-agents of the updated firewall rules.
//
// Importantly, we need to use a different `opctx` from that we're
// passed in here. This call requires access to Oxide-internal data
// around our VPC, and so we must use a context that's authorized for
// that.
//
// TODO-debugging: It's unfortunate that we're using this new logger,
// since that means we lose things like the original actor and request
// ID. It would be great if we could insert additional key-value pairs
// into the logger itself here, or "merge" the two in some other way.
info!(
opctx.log,
"updated user-facing services allow list, switching to \
internal opcontext to plumb rules to sled-agents";
"new_allowlist" => ?params.allowed_ips,
);
let new_opctx = self.opctx_for_internal_api();
match nexus_networking::plumb_service_firewall_rules(
self.datastore(),
&new_opctx,
&[],
&new_opctx,
&new_opctx.log,
)
.await
{
Ok(_) => {
info!(self.log, "plumbed updated IP allowlist to sled-agents");
Ok(list)
}
Err(e) => {
error!(
self.log,
"failed to update sled-agents with new allowlist";
"error" => ?e
);
let message = "Failed to plumb allowlist as firewall rules \
to relevant sled agents. The request must be retried for them \
to take effect.";
Err(Error::unavail(message))
}
}
}
}