-
Notifications
You must be signed in to change notification settings - Fork 109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add inbound support for svc hostname in authority #1420
base: master
Are you sure you want to change the base?
Conversation
Signed-off-by: Jackie Elliott <[email protected]>
Signed-off-by: Jackie Elliott <[email protected]>
Signed-off-by: Jackie Elliott <[email protected]>
Signed-off-by: Jackie Elliott <[email protected]>
Signed-off-by: Jackie Elliott <[email protected]>
Signed-off-by: Jackie Elliott <[email protected]>
Skipping CI for Draft Pull Request. |
😊 Welcome @jaellio! This is either your first contribution to the Istio ztunnel repo, or it's been You can learn more about the Istio working groups, Code of Conduct, and contribution guidelines Thanks for contributing! Courtesy of your friendly welcome wagon. |
/test all |
// TODO(jaellio): Should we also validate the hbone_addr port? | ||
HboneAddress::SvcHostname(hbone_host, hbone_port) => { | ||
// Validate a service or workload exists for the hostname | ||
let parts: Vec<&str> = hbone_host.split('.').collect(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO - figure out if there is a better way to parse a hostname
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can. There is not really a mapping from hostname -> namespace. For example, I could have a hostname foo.com
. We could have a guess or heuristic, but this doesn't seem like a time when doing the check only some of the time provides much value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since its to look up the service, perhaps we do something like find all the services with the hostname and pick the first preferring one with the same namespace (as the workload) if available
Signed-off-by: Jackie Elliott <[email protected]>
/test test |
Signed-off-by: Jackie Elliott <[email protected]>
/test test |
Signed-off-by: Jackie Elliott <[email protected]>
|
||
// Determine the next hop. | ||
let hbone_addr_clone = hbone_addr.clone(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making hbone_addr
an Arc
may help with the clones
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it could probably be changed so that find_inbound_upstream takes a borrow. Not sure why it accepts so many borrows and one owned TBH. All we really care about here is the port I think which is cheap to copy on the stack vs a clone of something more complex
HboneAddress::SvcHostname(hbone_host, hbone_port) => { | ||
// Validate a service or workload exists for the hostname | ||
let parts: Vec<&str> = hbone_host.split('.').collect(); | ||
if parts.len() <= 1 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can probably be a little stronger here and require an FQDN (i.e. error if we detect a short name with less than 5 parts). I think Istio always requires FQDNs for multi-cluster addressing (/cc @howardjohn)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. Kept it simple for the draft but this isn't sufficient atm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes to require FQDN. However I am not convinced we can/should do the mapping of service --> namespace at all (see comment 2 lines up)
src/proxy/inbound.rs
Outdated
hbone_addr: SocketAddr, | ||
) -> Result<(), Error> { | ||
hbone_addr: HboneAddress, | ||
) -> Result<Option<SocketAddr>, Error> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't love the idea of returning a value from this function. I know you need it for the proxy protocol stuff so maybe the initial hbone_addr matching can be a part of a separate function that's called only if the :authority is a hostname
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, just a temporary hack. Will fix
Signed-off-by: Jackie Elliott <[email protected]>
StatusCode::BAD_REQUEST, | ||
)); | ||
} | ||
// TODO(jaellio): Intelligently select the VIP |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@keithmattix with regards to #1409 and intelligently selecting vip for a Address::Service, is it sufficient to rank vips by network comparison (higher rank if vip.network
matches conn.dst_network
) or should we also be comparing IpAddr ranges? Do we also want to implement ranking of Address::Services when finding hostnames (similar to how there would be duplicate workloads on lookup by IP)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A
const PROXY_PROTOCOL_AUTHORITY_TLV: u8 = 0xD0; | ||
// Custom TLV for including the svc hostname in the proxy protocol header | ||
const PROXY_PROTOCOL_SVC_HOSTNAME_TLV: u8 = 0xD1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm this is an interesting one. I don't see any strong reason not to do this other than I don't know of any use cases to do so, and if we do we probably can never remove it since its part of our runtime API contract.
Did you have a specific use in mind for this?
@@ -262,38 +346,90 @@ impl Inbound { | |||
} | |||
|
|||
let start = Instant::now(); | |||
|
|||
// Extract the host or IP from the authority pseudo-header of the URI | |||
let hbone_addr = req |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: you could make this a
impl TryFrom<&http::Uri> for HboneAddress {
type Error = Error;
fn try_from(value: &http::Uri) -> Result<Self, Self::Error> { ... }
Then the calling code is just req.uri().try_into()
let hbone_host = req.uri().host().unwrap(); | ||
let hbone_port = req.uri().port_u16().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think these unwraps are safe. I suspect :authority: foo
(no port) would panic here for example. Which we want to reject (probably with a BAD_REQUEST), but shouldn't unwrap.
@@ -52,6 +53,56 @@ pub(super) struct Inbound { | |||
enable_orig_src: bool, | |||
} | |||
|
|||
#[derive(Debug, Clone)] | |||
pub enum HboneAddress { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: not a big deal but given this is used outside inbound
might make sense to put it up in proxy
module instead
// TODO(jaellio): Intelligently select the VIP | ||
let svc_address = svc.vips[0].address; | ||
upstream_protocol_addr = format!("{}:{}", svc_address, hbone_addr.port()) | ||
.parse::<SocketAddr>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: No need to send to string and parse back, SocketAddr::new(svc_address, hbone_addr.port())
should work
// TODO(jaellio): Should we also validate the hbone_addr port? | ||
HboneAddress::SvcHostname(hbone_host, hbone_port) => { | ||
// Validate a service or workload exists for the hostname | ||
let parts: Vec<&str> = hbone_host.split('.').collect(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can. There is not really a mapping from hostname -> namespace. For example, I could have a hostname foo.com
. We could have a guess or heuristic, but this doesn't seem like a time when doing the check only some of the time provides much value?
HboneAddress::SvcHostname(hbone_host, hbone_port) => { | ||
// Validate a service or workload exists for the hostname | ||
let parts: Vec<&str> = hbone_host.split('.').collect(); | ||
if parts.len() <= 1 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes to require FQDN. However I am not convinced we can/should do the mapping of service --> namespace at all (see comment 2 lines up)
// TODO(jaellio): Should we also validate the hbone_addr port? | ||
HboneAddress::SvcHostname(hbone_host, hbone_port) => { | ||
// Validate a service or workload exists for the hostname | ||
let parts: Vec<&str> = hbone_host.split('.').collect(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since its to look up the service, perhaps we do something like find all the services with the hostname and pick the first preferring one with the same namespace (as the workload) if available
local_workload: &Workload, | ||
hbone_addr: HboneAddress, | ||
) -> Result<Address, Error> { | ||
match hbone_addr { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its a bit of a codesmell to have the function take in an enum but then require a specific variant is passed.
In this specific case its pretty easy to resolve though, instead of taking in HboneAddress
we can just take in a Strng
and have them pass in the hostname.
// Normal case: both are aligned. This is allowed (we really only need the HBONE address for the port.) | ||
let hbone_ip = match hbone_addr.ip() { | ||
Some(ip) => ip, | ||
None => return Err(Error::ConnectAddress(hbone_addr.to_string())), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its a bit odd to have validate_destination only called for IP, then reject non-IP. Maybe we can just call it in all cases. if we have nothing to validate for hostname cases for now (which seems reasonable and fine) then we can just return Ok(())
immediately if its a hostname
Hoping to get feedback on the overall approach for this change. I would appreciate any rust specific feedback as well.
PR still needs some minor refactoring.
Main changes:
Todo: