Skip to content
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

Open
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

jaellio
Copy link

@jaellio jaellio commented Jan 10, 2025

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:

  • hbone_address supports hostname or ip when reading from the authority header
  • when a hostname is included in the authority pseudo header in is included as header in the proxy protocol. The hbone address included in the address header is set to the destination service vip
  • hostname is validated by looking up the respective service and verifying the destination pod ip is a member of that service.

Todo:

  • refactor hbone address validation to improve testability
  • update error types
  • confirm if port should also be included with hostname in custom protocol type
  • intelligently select service vip for proxy protocol address header
  • strictly validate hostname fqdn

@istio-testing
Copy link
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@istio-testing istio-testing added the do-not-merge/work-in-progress Block merging of a PR because it isn't ready yet. label Jan 10, 2025
@istio-policy-bot
Copy link

😊 Welcome @jaellio! This is either your first contribution to the Istio ztunnel repo, or it's been
a while since you've been here.

You can learn more about the Istio working groups, Code of Conduct, and contribution guidelines
by referring to Contributing to Istio.

Thanks for contributing!

Courtesy of your friendly welcome wagon.

@istio-testing istio-testing added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Jan 10, 2025
@jaellio
Copy link
Author

jaellio commented Jan 10, 2025

/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();
Copy link
Author

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

Copy link
Member

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?

Copy link
Member

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

Copy link
Author

Choose a reason for hiding this comment

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

Took a shot at this with ranking - might be a simpler way to do it in rust but I am not at all familiar :) Let me know if it's similar to what you had in mind.

@jaellio
Copy link
Author

jaellio commented Jan 10, 2025

/test test

1 similar comment
@jaellio
Copy link
Author

jaellio commented Jan 10, 2025

/test test

src/proxy/inbound.rs Outdated Show resolved Hide resolved
src/proxy/inbound.rs Outdated Show resolved Hide resolved
src/proxy/inbound.rs Outdated Show resolved Hide resolved
src/proxy/inbound.rs Outdated Show resolved Hide resolved
src/proxy/inbound.rs Outdated Show resolved Hide resolved

// Determine the next hop.
let hbone_addr_clone = hbone_addr.clone();
Copy link
Contributor

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

Copy link
Contributor

@ilrudie ilrudie Jan 15, 2025

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 {
Copy link
Contributor

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)

Copy link
Author

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.

Copy link
Member

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 Show resolved Hide resolved
hbone_addr: SocketAddr,
) -> Result<(), Error> {
hbone_addr: HboneAddress,
) -> Result<Option<SocketAddr>, Error> {
Copy link
Contributor

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

Copy link
Author

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

@jaellio jaellio marked this pull request as ready for review January 14, 2025 23:28
@jaellio jaellio requested a review from a team as a code owner January 14, 2025 23:28
@istio-testing istio-testing removed the do-not-merge/work-in-progress Block merging of a PR because it isn't ready yet. label Jan 14, 2025
StatusCode::BAD_REQUEST,
));
}
// TODO(jaellio): Intelligently select the VIP
Copy link
Author

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)?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, missed this. Are you still blocked here?

Copy link
Author

Choose a reason for hiding this comment

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

Based on this comment we can't rely on the hostname to include the namespace. So right now we'll need to change some of the logic for finding a service by hostname and implement a ranking as suggested here

Copy link
Author

Choose a reason for hiding this comment

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

Not blocked, I'll implement the service ranking and vip ranking as suggested above. What just curious if you had any thoughts on the approach.

Copy link
Author

Choose a reason for hiding this comment

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

@keithmattix Implemented simple ranking for service (when searching without a namespace) and vip. There might be a simpler way to accomplish the ranking but took inspiration from #1409

@jaellio jaellio added the do-not-merge/hold Block automatic merging of a PR. label Jan 15, 2025
Copy link
Member

@howardjohn howardjohn left a 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;
Copy link
Member

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?

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean, I imagine that if there's custom waypoint sandwiched with the ztunnel, it would want to use the hostname for routing (vs. an IP address we lookup) , right?

Copy link
Author

Choose a reason for hiding this comment

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

@keithmattix correct me if I am wrong here. We were concerned about not preserving the hostname in the proxy protocol. Not positive about the exact implications of this for logging, but maybe it has more implications for security tools?

@@ -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
Copy link
Member

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()

Copy link
Author

Choose a reason for hiding this comment

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

Took a shot at it, let me know if it's what you had in mind

Comment on lines 357 to 309
let hbone_host = req.uri().host().unwrap();
let hbone_port = req.uri().port_u16().unwrap();
Copy link
Member

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.

Copy link
Author

Choose a reason for hiding this comment

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

Moved this logic to the TryFrom implementation for HboneAddress. Returns an error result if port or host are not set.

src/proxy/inbound.rs Outdated Show resolved Hide resolved
src/proxy/inbound.rs Outdated Show resolved Hide resolved
// 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();
Copy link
Member

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 {
Copy link
Member

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();
Copy link
Member

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

src/proxy/inbound.rs Outdated Show resolved Hide resolved
src/proxy/inbound.rs Outdated Show resolved Hide resolved
@istio-testing istio-testing added the needs-rebase Indicates a PR needs to be rebased before being merged label Jan 22, 2025
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]>
Signed-off-by: Jackie Elliott <[email protected]>
@jaellio jaellio force-pushed the jaellio/rcvsvchostname branch from cf45ea3 to 5a9a0b7 Compare January 22, 2025 23:33
@istio-testing istio-testing removed the needs-rebase Indicates a PR needs to be rebased before being merged label Jan 22, 2025
Implement TryFrom from uri to hboneAddress

Signed-off-by: Jackie Elliott <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
do-not-merge/hold Block automatic merging of a PR. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add service hostname HBONE :authority support to ztunnel servers
7 participants