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

feat(translator): implement backend API #3495

Merged
merged 13 commits into from
Jun 4, 2024
Merged

Conversation

guydc
Copy link
Contributor

@guydc guydc commented May 28, 2024

What this PR does / why we need it:
Implements backend API

  • Support translation of fqdn, ip and unix endpoints in IR and XDS
  • Support H2C upstream application protocol hint
  • Support HTTPRoute and EnvoyExtensionPolicy references to Backend
  • Extend BackendTLSPolicy translation tests for HTTPRoute and Backend use cases
  • Minor refactor of test manifests and translation to increase code reuse

Which issue(s) this PR fixes:
Fixes #2997
Relates to #36

Copy link

codecov bot commented May 28, 2024

Codecov Report

Attention: Patch coverage is 65.94595% with 126 lines in your changes missing coverage. Please review.

Project coverage is 68.14%. Comparing base (3712659) to head (baf0840).

Files Patch % Lines
internal/provider/kubernetes/controller.go 18.18% 38 Missing and 7 partials ⚠️
internal/gatewayapi/validate.go 80.21% 14 Missing and 4 partials ⚠️
internal/gatewayapi/status/backend.go 0.00% 13 Missing ⚠️
internal/provider/kubernetes/predicates.go 0.00% 11 Missing ⚠️
internal/provider/kubernetes/status_updater.go 0.00% 6 Missing ⚠️
internal/gatewayapi/envoyextensionpolicy.go 54.54% 4 Missing and 1 partial ⚠️
internal/ir/xds.go 58.33% 4 Missing and 1 partial ⚠️
internal/provider/kubernetes/helpers.go 0.00% 5 Missing ⚠️
internal/gatewayapi/backend.go 88.23% 2 Missing and 2 partials ⚠️
internal/gatewayapi/ext_service.go 80.00% 2 Missing and 2 partials ⚠️
... and 5 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3495      +/-   ##
==========================================
+ Coverage   68.12%   68.14%   +0.02%     
==========================================
  Files         165      167       +2     
  Lines       20122    20370     +248     
==========================================
+ Hits        13708    13882     +174     
- Misses       5416     5487      +71     
- Partials      998     1001       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@guydc guydc force-pushed the feat-backend branch 2 times, most recently from fe6d28d to 9fe20b9 Compare May 28, 2024 19:47
Signed-off-by: Guy Daich <[email protected]>
@guydc
Copy link
Contributor Author

guydc commented May 28, 2024

/retest

@guydc guydc marked this pull request as ready for review May 28, 2024 23:03
@guydc guydc requested a review from a team as a code owner May 28, 2024 23:03
@@ -1176,6 +1178,17 @@ func (t *Translator) processDestination(backendRefContext BackendRefContext,
Endpoints: endpoints,
AddressType: addrType,
}
// TODO: support mixed endpointslice address type for the same backendRef
Copy link
Contributor

Choose a reason for hiding this comment

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

can we move this out of switch to make it common amongst all backend types ?

Copy link
Contributor Author

@guydc guydc May 29, 2024

Choose a reason for hiding this comment

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

The condition is slightly different for Backend vs. the native K8s resources (depending on EndpointRouting toggle) and so is the message. I'll split the validation and status update.

case true:
return newCondition(string(egv1a1.BackendReasonInvalid), metav1.ConditionTrue,
string(egv1a1.BackendConditionAccepted),
"The Backend was accepted Envoy Gateway", time.Now(), be.Generation)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"The Backend was accepted Envoy Gateway", time.Now(), be.Generation)
"The Backend was accepted by Envoy Gateway", time.Now(), be.Generation)
Suggested change
"The Backend was accepted Envoy Gateway", time.Now(), be.Generation)
"The Backend was accepted Envoy Gateway", time.Now(), be.Generation)

case true:
return newCondition(string(egv1a1.BackendReasonInvalid), metav1.ConditionTrue,
string(egv1a1.BackendConditionAccepted),
"The Backend was accepted Envoy Gateway", time.Now(), be.Generation)
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need the "Envoy Gateway" suffix?

// Watch Backend CRUDs and process affected *Route objects.
if r.envoyGateway.ExtensionAPIs != nil && r.envoyGateway.ExtensionAPIs.EnableBackend {
backendPredicates := []predicate.TypedPredicate[*egv1a1.Backend]{
predicate.NewTypedPredicateFuncs[*egv1a1.Backend](func(be *egv1a1.Backend) bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

ObservedGeneration predicate ? we haven't added the ObjectMeta yet so it won't work
the issue I see here is constant reconciling -

  • reconcile backend
  • translate backend and update status
  • translate again (may be stopped by watchable layer which checks for equality)

Copy link
Contributor

Choose a reason for hiding this comment

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

return reflect.DeepEqual(c, y)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you elaborate on why TypedGenerationChangedPredicate won't work here?

Copy link
Contributor

Choose a reason for hiding this comment

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

nvm it will, took a look at the struct

metav1.ObjectMeta `json:"metadata,omitempty"`

and you've added ObjectMeta, so should work, lets add it

@guydc
Copy link
Contributor Author

guydc commented May 29, 2024

/retest

arkodg
arkodg previously approved these changes May 30, 2024
Copy link
Contributor

@arkodg arkodg left a comment

Choose a reason for hiding this comment

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

LGTM thanks for implementing this and making it easier to route to non k8s endpoints !

can we document the remaining items (disallowing localhost , other xRoute backend) as subtasks ?

@arkodg arkodg requested review from a team May 30, 2024 23:31
@guydc guydc mentioned this pull request May 31, 2024
17 tasks
@guydc
Copy link
Contributor Author

guydc commented May 31, 2024

/retest

@guydc guydc requested a review from arkodg May 31, 2024 14:12
arkodg
arkodg previously approved these changes May 31, 2024
Copy link
Contributor

@liorokman liorokman left a comment

Choose a reason for hiding this comment

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

Looking at this carefully again, I think maybe it would make sense to rename the IPv4 field in BackendEndpoint to IP since using IPv6 addresses would probably already just work.

}
} else { // IP or FQDN
err := validation.IsDNS1123Subdomain(d.Host)
_, pErr := netip.ParseAddr(d.Host)
Copy link
Contributor

Choose a reason for hiding this comment

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

Even though the field is called "ipv4", I think IPv6 addresses would still work here. netip.ParseAddr has no issue with IPv6 addresses.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is enforced with CEL at the moment:

// +kubebuilder:validation:Pattern=`^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$`

unsupportedRef = true
case bep.FQDN != nil && bep.FQDN.Hostname == "localhost":
unsupportedRef = true
case bep.IPv4 != nil && bep.IPv4.Address == "127.0.0.1":
Copy link
Contributor

Choose a reason for hiding this comment

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

Even though the field is called IPv4, there's nothing that really prevents IPv6 addresses from being specified here as far as I can tell.

Consider also rejecting the IPv6 localhost address ? ::1 ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, localhost on Linux systems is not just 127.0.0.1. It's 127.0.0.1/8, which means that this check is wrong.

The correct way to check this would be to check if the provided address is contained in 127.0.0.0/8, not to string-compare to 127.0.0.1.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yea, good point, we just discussed this in slack. I'll update the validation to comply with the K8s one for EP Slices that checks for the entire range.

switch {
case bep.Unix != nil:
unsupportedRef = true
case bep.FQDN != nil && bep.FQDN.Hostname == "localhost":
Copy link
Contributor

Choose a reason for hiding this comment

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

If the intent is to prevent localhost from being the target, in case of an FQDN the better approach would be to try to resolve the hostname and see that it doesn't resolve to the localhost.

Copy link
Contributor

@liorokman liorokman May 31, 2024

Choose a reason for hiding this comment

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

What would happen if somebody were to use an IP in the FQDN field of BackendEndpoint? I think it would just work, except that it would be possible to use 127.0.0.1 or ::1 here.

Copy link
Contributor Author

@guydc guydc May 31, 2024

Choose a reason for hiding this comment

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

If the intent is to prevent localhost from being the target, in case of an FQDN the better approach would be to try to resolve the hostname and see that it doesn't resolve to the localhost.

Users can change the DNS record this after the resource is accepted. Then, it becomes a race with the next translation until the config is rejected. So, it's still possible that there's a time window where localhost routing is possible.

We discussed this concern here: #3063 (comment). I'm not sure that there really is a good solution for that. The compromise is to enforce validations equivalent to what K8s allows in EP slices, and make users aware of the risk...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What would happen if somebody were to use an IP in the FQDN field of BackendEndpoint?

There's a CEL validation that will probably reject IPv6 Addresses (as ":" is nto supported). But, IPv4 addresses are actually accepted by this validation. I'll add additional programatic validations.

// +kubebuilder:validation:Pattern=`^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`

@guydc
Copy link
Contributor Author

guydc commented May 31, 2024

/retest

@guydc guydc added the hold do not merge label May 31, 2024
Copy link
Contributor

Choose a reason for hiding this comment

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

a non-blocking comment:

func isStatusEqual(objA, objB interface{}) bool {
and
func kindOf(obj interface{}) string {
can also need a update.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch, thanks, done!


for _, ap := range backend.Spec.AppProtocols {
if ap == v1alpha1.AppProtocolTypeH2C {
dstProtocol = ir.HTTP2
Copy link
Contributor

Choose a reason for hiding this comment

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

need a break here?

}

ds.Weight = &weight
return ds
}

func validateDestinationSettings(destinationSettings *ir.DestinationSetting, endpointRoutingDisabled bool, kind *gwapiv1.Kind) (bool, string) {
Copy link
Contributor

@shawnh2 shawnh2 Jun 2, 2024

Choose a reason for hiding this comment

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

instead of string, can we return an error here? then we can use error.Error() to retrieve the string of this error later.

@guydc
Copy link
Contributor Author

guydc commented Jun 3, 2024

IP:

  • API Changed from IPv4/IPv6 => IP for future use, as suggested
  • IPv6 still not supported and rejected in validation (CEL, Controller).
  • Validate that target is not in the entire loopback range, for both xRoute and non-xRoute

FQDN:

  • Fix CEL validation to not support wildcards
  • Add controller validation that FQDN is not an IP address and has at least two segments, for both xRoute and non-xRoute

Signed-off-by: Guy Daich <[email protected]>
@guydc guydc removed the hold do not merge label Jun 3, 2024
@guydc guydc requested review from arkodg, shawnh2 and liorokman June 3, 2024 23:57
@zhaohuabing
Copy link
Member

zhaohuabing commented Jun 4, 2024

This one is huge :-)

It would be easier to review and quicker to merge if it could be split into multiple PRs.

@zhaohuabing
Copy link
Member

This one is huge :-)

It would be easier to review and quicker to merge if it were split into multiple PRs.

I don’t mean this one, as it’s already under review, but in the future, it might be helpful to split large PRs into multiple smaller ones for easier and quicker reviews.


// computeBackendAcceptedCondition computes the Backend Accepted status condition.
func computeBackendAcceptedCondition(be *egv1a1.Backend, accepted bool, msg string) metav1.Condition {
switch accepted {
Copy link
Contributor

Choose a reason for hiding this comment

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

why not use the simple if..else for this boolean ?

@arkodg arkodg merged commit 09fd519 into envoyproxy:main Jun 4, 2024
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support a custom Backend API for FQDN, IP and Unix Domain Sockets
5 participants