Repository: fluxcd/flagger
Pull request #1164 (2022-04-05 18:12:10)

Contour: Update the httproxy API and enable RetryOn
PR to add support for the retryOn field for contour's httpproxy

This will allow for a little bit more control on how the ingress is created


Patch:
diff --git a/charts/grafana/dashboards/istio.json b/charts/grafana/dashboards/istio.json
index 6a97f849..c4a9579a 100644
--- a/charts/grafana/dashboards/istio.json
+++ b/charts/grafana/dashboards/istio.json
@@ -403,7 +403,7 @@
       "steppedLine": false,
       "targets": [
         {
-          "expr": "histogram_quantile(0.50, sum(irate(istio_request_duration_milliseconds_bucket{reporter=\"destination\",destination_workload=~\"$primary\", destination_workload_namespace=~\"$namespace\"}[1m])) by (le)) / 1000",
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_duration_milliseconds_bucket{reporter=\"destination\",destination_workload=~\"$primary\", destination_workload_namespace=~\"$namespace\"}[1m])) by (le))",
           "format": "time_series",
           "interval": "",
           "intervalFactor": 1,
@@ -411,7 +411,7 @@
           "refId": "A"
         },
         {
-          "expr": "histogram_quantile(0.90, sum(irate(istio_request_duration_milliseconds_bucket{reporter=\"destination\",destination_workload=~\"$primary\", destination_workload_namespace=~\"$namespace\"}[1m])) by (le)) / 1000",
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_duration_milliseconds_bucket{reporter=\"destination\",destination_workload=~\"$primary\", destination_workload_namespace=~\"$namespace\"}[1m])) by (le))",
           "format": "time_series",
           "hide": false,
           "intervalFactor": 1,
@@ -419,7 +419,7 @@
           "refId": "B"
         },
         {
-          "expr": "histogram_quantile(0.99, sum(irate(istio_request_duration_milliseconds_bucket{reporter=\"destination\",destination_workload=~\"$primary\", destination_workload_namespace=~\"$namespace\"}[1m])) by (le)) / 1000",
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_duration_milliseconds_bucket{reporter=\"destination\",destination_workload=~\"$primary\", destination_workload_namespace=~\"$namespace\"}[1m])) by (le))",
           "format": "time_series",
           "hide": false,
           "intervalFactor": 1,
@@ -509,7 +509,7 @@
       "steppedLine": false,
       "targets": [
         {
-          "expr": "histogram_quantile(0.50, sum(irate(istio_request_duration_milliseconds_bucket{reporter=\"destination\",destination_workload=~\"$canary\", destination_workload_namespace=~\"$namespace\"}[1m])) by (le)) / 1000",
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_duration_milliseconds_bucket{reporter=\"destination\",destination_workload=~\"$canary\", destination_workload_namespace=~\"$namespace\"}[1m])) by (le))",
           "format": "time_series",
           "interval": "",
           "intervalFactor": 1,
@@ -517,7 +517,7 @@
           "refId": "A"
         },
         {
-          "expr": "histogram_quantile(0.90, sum(irate(istio_request_duration_milliseconds_bucket{reporter=\"destination\",destination_workload=~\"$canary\", destination_workload_namespace=~\"$namespace\"}[1m])) by (le)) / 1000",
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_duration_milliseconds_bucket{reporter=\"destination\",destination_workload=~\"$canary\", destination_workload_namespace=~\"$namespace\"}[1m])) by (le))",
           "format": "time_series",
           "hide": false,
           "intervalFactor": 1,
@@ -525,7 +525,7 @@
           "refId": "B"
         },
         {
-          "expr": "histogram_quantile(0.99, sum(irate(istio_request_duration_milliseconds_bucket{reporter=\"destination\",destination_workload=~\"$canary\", destination_workload_namespace=~\"$namespace\"}[1m])) by (le)) / 1000",
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_duration_milliseconds_bucket{reporter=\"destination\",destination_workload=~\"$canary\", destination_workload_namespace=~\"$namespace\"}[1m])) by (le))",
           "format": "time_series",
           "hide": false,
           "intervalFactor": 1,
diff --git a/docs/gitbook/usage/monitoring.md b/docs/gitbook/usage/monitoring.md
index 67179480..fa860e44 100644
--- a/docs/gitbook/usage/monitoring.md
+++ b/docs/gitbook/usage/monitoring.md
@@ -117,8 +117,4 @@ flagger_canary_duration_seconds_bucket{name="podinfo",namespace="test",le="10"}
 flagger_canary_duration_seconds_bucket{name="podinfo",namespace="test",le="+Inf"} 6
 flagger_canary_duration_seconds_sum{name="podinfo",namespace="test"} 17.3561329
 flagger_canary_duration_seconds_count{name="podinfo",namespace="test"} 6
-
-# Last canary metric analysis result per different metrics
-flagger_canary_metric_analysis{metric="podinfo-http-successful-rate",name="podinfo",namespace="test"} 1
-flagger_canary_metric_analysis{metric="podinfo-custom-metric",name="podinfo",namespace="test"} 0.918223108974359
 ```
diff --git a/pkg/apis/projectcontour/v1/detailedconditions.go b/pkg/apis/projectcontour/v1/detailedconditions.go
new file mode 100644
index 00000000..5f5a27ad
--- /dev/null
+++ b/pkg/apis/projectcontour/v1/detailedconditions.go
@@ -0,0 +1,190 @@
+// Copyright Project Contour Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +k8s:deepcopy-gen=package
+
+// Package v1 is the v1 version of the API.
+// +groupName=projectcontour.io
+package v1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// ConditionStatus is a type alias for the k8s.io/apimachinery/pkg/apis/meta/v1
+// ConditionStatus type to maintain API compatibility.
+// +k8s:deepcopy-gen=false
+type ConditionStatus = metav1.ConditionStatus
+
+// These are valid condition statuses. "ConditionTrue" means a resource is in the condition.
+// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes
+// can't decide if a resource is in the condition or not. In the future, we could add other
+// intermediate conditions, e.g. ConditionDegraded. These are retained here for API compatibility.
+const (
+	ConditionTrue    ConditionStatus = metav1.ConditionTrue
+	ConditionFalse   ConditionStatus = metav1.ConditionFalse
+	ConditionUnknown ConditionStatus = metav1.ConditionUnknown
+)
+
+// Condition is a type alias for the k8s.io/apimachinery/pkg/apis/meta/v1
+// Condition type to maintain API compatibility.
+// +k8s:deepcopy-gen=false
+type Condition = metav1.Condition
+
+// SubCondition is a Condition-like type intended for use as a subcondition inside a DetailedCondition.
+//
+// It contains a subset of the Condition fields.
+//
+// It is intended for warnings and errors, so `type` names should use abnormal-true polarity,
+// that is, they should be of the form "ErrorPresent: true".
+//
+// The expected lifecycle for these errors is that they should only be present when the error or warning is,
+// and should be removed when they are not relevant.
+type SubCondition struct {
+	// Type of condition in `CamelCase` or in `foo.example.com/CamelCase`.
+	//
+	// This must be in abnormal-true polarity, that is, `ErrorFound` or `controller.io/ErrorFound`.
+	//
+	// The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
+	// +required
+	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$`
+	// +kubebuilder:validation:MaxLength=316
+	Type string `json:"type" protobuf:"bytes,1,opt,name=type"`
+	// Status of the condition, one of True, False, Unknown.
+	// +required
+	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:Enum=True;False;Unknown
+	Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status"`
+	// Reason contains a programmatic identifier indicating the reason for the condition's last transition.
+	// Producers of specific condition types may define expected values and meanings for this field,
+	// and whether the values are considered a guaranteed API.
+	//
+	// The value should be a CamelCase string.
+	//
+	// This field may not be empty.
+	// +required
+	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:MaxLength=1024
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$`
+	Reason string `json:"reason" protobuf:"bytes,3,opt,name=reason"`
+	// Message is a human readable message indicating details about the transition.
+	//
+	// This may be an empty string.
+	//
+	// +required
+	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:MaxLength=32768
+	Message string `json:"message" protobuf:"bytes,4,opt,name=message"`
+}
+
+// DetailedCondition is an extension of the normal Kubernetes conditions, with two extra
+// fields to hold sub-conditions, which provide more detailed reasons for the state (True or False)
+// of the condition.
+//
+// `errors` holds information about sub-conditions which are fatal to that condition and render its state False.
+//
+// `warnings` holds information about sub-conditions which are not fatal to that condition and do not force the state to be False.
+//
+// Remember that Conditions have a type, a status, and a reason.
+//
+// The type is the type of the condition, the most important one in this CRD set is `Valid`.
+// `Valid` is a positive-polarity condition: when it is `status: true` there are no problems.
+//
+// In more detail, `status: true` means that the object is has been ingested into Contour with no errors.
+// `warnings` may still be present, and will be indicated in the Reason field. There must be zero entries in the `errors`
+// slice in this case.
+//
+// `Valid`, `status: false` means that the object has had one or more fatal errors during processing into Contour.
+//  The details of the errors will be present under the `errors` field. There must be at least one error in the `errors`
+// slice if `status` is `false`.
+//
+// For DetailedConditions of types other than `Valid`, the Condition must be in the negative polarity.
+// When they have `status` `true`, there is an error. There must be at least one entry in the `errors` Subcondition slice.
+// When they have `status` `false`, there are no serious errors, and there must be zero entries in the `errors` slice.
+// In either case, there may be entries in the `warnings` slice.
+//
+// Regardless of the polarity, the `reason` and `message` fields must be updated with either the detail of the reason
+// (if there is one and only one entry in total across both the `errors` and `warnings` slices), or
+// `MultipleReasons` if there is more than one entry.
+type DetailedCondition struct {
+	Condition `json:",inline"`
+	// Errors contains a slice of relevant error subconditions for this object.
+	//
+	// Subconditions are expected to appear when relevant (when there is a error), and disappear when not relevant.
+	// An empty slice here indicates no errors.
+	// +optional
+	Errors []SubCondition `json:"errors,omitempty"`
+	// Warnings contains a slice of relevant warning subconditions for this object.
+	//
+	// Subconditions are expected to appear when relevant (when there is a warning), and disappear when not relevant.
+	// An empty slice here indicates no warnings.
+	// +optional
+	Warnings []SubCondition `json:"warnings,omitempty"`
+}
+
+const (
+	// ValidConditionType describes an valid condition.
+	ValidConditionType = "Valid"
+
+	// ConditionTypeAuthError describes an error condition related to Auth.
+	ConditionTypeAuthError = "AuthError"
+
+	// ConditionTypeCORSError describes an error condition related to CORS.
+	ConditionTypeCORSError = "CORSError"
+
+	// ConditionTypeIncludeError describes an error condition with
+	// inclusion of another HTTPProxy resource.
+	ConditionTypeIncludeError = "IncludeError"
+
+	// ConditionTypeOrphanedError describes an error condition
+	// with an HTTPProxy resource which is not part of a delegation chain.
+	ConditionTypeOrphanedError = "Orphaned"
+
+	// ConditionTypePrefixReplaceError describes an error condition with
+	// an HTTPProxy path prefix replacement issue.
+	ConditionTypePrefixReplaceError = "PrefixReplaceError"
+
+	// ConditionTypeRootNamespaceError describes an error condition
+	// with an HTTPProxy resource created in non-root namespace.
+	ConditionTypeRootNamespaceError = "RootNamespaceError"
+
+	// ConditionTypeRouteError describes an error condition that
+	// relates to Routes within an HTTPProxy.
+	ConditionTypeRouteError = "RouteError"
+
+	// ConditionTypeServiceError describes an error condition that
+	// relates to a Service error within an HTTPProxy.
+	ConditionTypeServiceError = "ServiceError"
+
+	// ConditionTypeSpecError describes an error condition that
+	// relates to the Spec of an HTTPProxy resource.
+	ConditionTypeSpecError = "SpecError"
+
+	// ConditionTypeTCPProxyIncludeError describes an error condition
+	// with inclusion of another HTTPProxy TCP Proxy resource.
+	ConditionTypeTCPProxyIncludeError = "TCPProxyIncludeError"
+
+	// ConditionTypeTCPProxyError describes an error condition relating
+	// to a TCP Proxy HTTPProxy resource.
+	ConditionTypeTCPProxyError = "TCPProxyError"
+
+	// ConditionTypeTLSError describes an error condition relating
+	// to TLS configuration.
+	ConditionTypeTLSError = "TLSError"
+
+	// ConditionTypeVirtualHostError describes an error condition relating
+	// to the VirtualHost configuration section of an HTTPProxy resource.
+	ConditionTypeVirtualHostError = "VirtualHostError"
+)
diff --git a/pkg/apis/projectcontour/v1/httpproxy.go b/pkg/apis/projectcontour/v1/httpproxy.go
index 59ad2e68..4eefd53a 100644
--- a/pkg/apis/projectcontour/v1/httpproxy.go
+++ b/pkg/apis/projectcontour/v1/httpproxy.go
@@ -1,4 +1,4 @@
-// Copyright © 2019 VMware
+// Copyright Project Contour Authors
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -14,13 +14,14 @@
 package v1
 
 import (
+	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 // HTTPProxySpec defines the spec of the CRD.
 type HTTPProxySpec struct {
 	// Virtualhost appears at most once. If it is present, the object is considered
-	// to be a "root".
+	// to be a "root" HTTPProxy.
 	// +optional
 	VirtualHost *VirtualHost `json:"virtualhost,omitempty"`
 	// Routes are the ingress routes. If TCPProxy is present, Routes is ignored.
@@ -29,9 +30,16 @@ type HTTPProxySpec struct {
 	// TCPProxy holds TCP proxy information.
 	// +optional
 	TCPProxy *TCPProxy `json:"tcpproxy,omitempty"`
-	// Includes allow for specific routing configuration to be appended to another HTTPProxy in another namespace.
+	// Includes allow for specific routing configuration to be included from another HTTPProxy,
+	// possibly in another namespace.
 	// +optional
 	Includes []Include `json:"includes,omitempty"`
+	// IngressClassName optionally specifies the ingress class to use for this
+	// HTTPProxy. This replaces the deprecated `kubernetes.io/ingress.class`
+	// annotation. For backwards compatibility, when that annotation is set, it
+	// is given precedence over this field.
+	// +optional
+	IngressClassName string `json:"ingressClassName,omitempty"`
 }
 
 // Include describes a set of policies that can be applied to an HTTPProxy in a namespace.
@@ -41,92 +49,285 @@ type Include struct {
 	// Namespace of the HTTPProxy to include. Defaults to the current namespace if not supplied.
 	// +optional
 	Namespace string `json:"namespace,omitempty"`
-	// Conditions are a set of routing properties that is applied to an HTTPProxy in a namespace.
+	// Conditions are a set of rules that are applied to included HTTPProxies.
+	// In effect, they are added onto the Conditions of included HTTPProxy Route
+	// structs.
+	// When applied, they are merged using AND, with one exception:
+	// There can be only one Prefix MatchCondition per Conditions slice.
+	// More than one Prefix, or contradictory Conditions, will make the
+	// include invalid.
 	// +optional
-	Conditions []Condition `json:"conditions,omitempty"`
+	Conditions []MatchCondition `json:"conditions,omitempty"`
 }
 
-// Condition are policies that are applied on top of HTTPProxies.
+// MatchCondition are a general holder for matching rules for HTTPProxies.
 // One of Prefix or Header must be provided.
-type Condition struct {
+type MatchCondition struct {
 	// Prefix defines a prefix match for a request.
 	// +optional
 	Prefix string `json:"prefix,omitempty"`
 
 	// Header specifies the header condition to match.
 	// +optional
-	Header *HeaderCondition `json:"header,omitempty"`
+	Header *HeaderMatchCondition `json:"header,omitempty"`
 }
 
-// HeaderCondition specifies the header condition to match.
-// Name is required. Only one of Present or Contains must
-// be provided.
-type HeaderCondition struct {
-
-	// Name is the name of the header to match on. Name is required.
+// HeaderMatchCondition specifies how to conditionally match against HTTP
+// headers. The Name field is required, but only one of the remaining
+// fields should be be provided.
+type HeaderMatchCondition struct {
+	// Name is the name of the header to match against. Name is required.
 	// Header names are case insensitive.
 	Name string `json:"name"`
 
-	// Present is true if the Header is present in the request.
+	// Present specifies that condition is true when the named header
+	// is present, regardless of its value. Note that setting Present
+	// to false does not make the condition true if the named header
+	// is absent.
 	// +optional
 	Present bool `json:"present,omitempty"`
 
-	// Contains is true if the Header containing this string is present
-	// in the request.
+	// NotPresent specifies that condition is true when the named header
+	// is not present. Note that setting NotPresent to false does not
+	// make the condition true if the named header is present.
+	// +optional
+	NotPresent bool `json:"notpresent,omitempty"`
+
+	// Contains specifies a substring that must be present in
+	// the header value.
 	// +optional
 	Contains string `json:"contains,omitempty"`
 
-	// NotContains is true if the Header containing this string is not present
-	// in the request.
+	// NotContains specifies a substring that must not be present
+	// in the header value.
 	// +optional
 	NotContains string `json:"notcontains,omitempty"`
 
-	// Exact is true if the Header containing this string matches exactly
-	// in the request.
+	// Exact specifies a string that the header value must be equal to.
 	// +optional
 	Exact string `json:"exact,omitempty"`
 
-	// NotExact is true if the Header containing this string doesn't match exactly
-	// in the request.
+	// NoExact specifies a string that the header value must not be
+	// equal to. The condition is true if the header has any other value.
 	// +optional
 	NotExact string `json:"notexact,omitempty"`
 }
 
+// ExtensionServiceReference names an ExtensionService resource.
+type ExtensionServiceReference struct {
+	// API version of the referent.
+	// If this field is not specified, the default "projectcontour.io/v1alpha1" will be used
+	//
+	// +optional
+	// +kubebuilder:validation:MinLength=1
+	APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,5,opt,name=apiVersion"`
+
+	// Namespace of the referent.
+	// If this field is not specifies, the namespace of the resource that targets the referent will be used.
+	//
+	// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
+	//
+	// +optional
+	// +kubebuilder:validation:MinLength=1
+	Namespace string `json:"namespace,omitempty" protobuf:"bytes,2,opt,name=namespace"`
+
+	// Name of the referent.
+	//
+	// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+	//
+	// +required
+	// +kubebuilder:validation:MinLength=1
+	Name string `json:"name,omitempty" protobuf:"bytes,3,opt,name=name"`
+}
+
+// AuthorizationServer configures an external server to authenticate
+// client requests. The external server must implement the v3 Envoy
+// external authorization GRPC protocol (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto).
+type AuthorizationServer struct {
+	// ExtensionServiceRef specifies the extension resource that will authorize client requests.
+	//
+	// +required
+	ExtensionServiceRef ExtensionServiceReference `json:"extensionRef"`
+
+	// AuthPolicy sets a default authorization policy for client requests.
+	// This policy will be used unless overridden by individual routes.
+	//
+	// +optional
+	AuthPolicy *AuthorizationPolicy `json:"authPolicy,omitempty"`
+
+	// ResponseTimeout configures maximum time to wait for a check response from the authorization server.
+	// Timeout durations are expressed in the Go [Duration format](https://godoc.org/time#ParseDuration).
+	// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
+	// The string "infinity" is also a valid input and specifies no timeout.
+	//
+	// +optional
+	// +kubebuilder:validation:Pattern=`^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$`
+	ResponseTimeout string `json:"responseTimeout,omitempty"`
+
+	// If FailOpen is true, the client request is forwarded to the upstream service
+	// even if the authorization server fails to respond. This field should not be
+	// set in most cases. It is intended for use only while migrating applications
+	// from internal authorization to Contour external authorization.
+	//
+	// +optional
+	FailOpen bool `json:"failOpen,omitempty"`
+
+	// WithRequestBody specifies configuration for sending the client request's body to authorization server.
+	// +optional
+	WithRequestBody *AuthorizationServerBufferSettings `json:"withRequestBody,omitempty"`
+}
+
+// AuthorizationServerBufferSettings enables ExtAuthz filter to buffer client request data and send it as part of authorization request
+type AuthorizationServerBufferSettings struct {
+	// MaxRequestBytes sets the maximum size of message body ExtAuthz filter will hold in-memory.
+	// +optional
+	// +kubebuilder:validation:Minimum=1
+	// +kubebuilder:default=1024
+	MaxRequestBytes uint32 `json:"maxRequestBytes,omitempty"`
+
+	// If AllowPartialMessage is true, then Envoy will buffer the body until MaxRequestBytes are reached.
+	// +optional
+	AllowPartialMessage bool `json:"allowPartialMessage,omitempty"`
+
+	// If PackAsBytes is true, the body sent to Authorization Server is in raw bytes.
+	// +optional
+	PackAsBytes bool `json:"packAsBytes,omitempty"`
+}
+
+// AuthorizationPolicy modifies how client requests are authenticated.
+type AuthorizationPolicy struct {
+	// When true, this field disables client request authentication
+	// for the scope of the policy.
+	//
+	// +optional
+	Disabled bool `json:"disabled,omitempty"`
+
+	// Context is a set of key/value pairs that are sent to the
+	// authentication server in the check request. If a context
+	// is provided at an enclosing scope, the entries are merged
+	// such that the inner scope overrides matching keys from the
+	// outer scope.
+	//
+	// +optional
+	Context map[string]string `json:"context,omitempty"`
+}
+
 // VirtualHost appears at most once. If it is present, the object is considered
 // to be a "root".
 type VirtualHost struct {
 	// The fully qualified domain name of the root of the ingress tree
-	// all leaves of the DAG rooted at this object relate to the fqdn
+	// all leaves of the DAG rooted at this object relate to the fqdn.
+	//
+	// +kubebuilder:validation:Pattern="^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"
 	Fqdn string `json:"fqdn"`
-	// If present describes tls properties. The CNI names that will be matched on
-	// are described in fqdn, the tls.secretName secret must contain a
-	// matching certificate
+
+	// If present the fields describes TLS properties of the virtual
+	// host. The SNI names that will be matched on are described in fqdn,
+	// the tls.secretName secret must contain a certificate that itself
+	// contains a name that matches the FQDN.
+	//
 	// +optional
 	TLS *TLS `json:"tls,omitempty"`
+
+	// This field configures an extension service to perform
+	// authorization for this virtual host. Authorization can
+	// only be configured on virtual hosts that have TLS enabled.
+	// If the TLS configuration requires client certificate
+	// validation, the client certificate is always included in the
+	// authentication check request.
+	//
+	// +optional
+	Authorization *AuthorizationServer `json:"authorization,omitempty"`
+	// Specifies the cross-origin policy to apply to the VirtualHost.
+	// +optional
+	CORSPolicy *CORSPolicy `json:"corsPolicy,omitempty"`
+	// The policy for rate limiting on the virtual host.
+	// +optional
+	RateLimitPolicy *RateLimitPolicy `json:"rateLimitPolicy,omitempty"`
 }
 
-// TLS describes tls properties. The CNI names that will be matched on
-// are described in fqdn, the tls.secretName secret must contain a
-// matching certificate unless tls.passthrough is set to true.
+// TLS describes tls properties. The SNI names that will be matched on
+// are described in the HTTPProxy's Spec.VirtualHost.Fqdn field.
 type TLS struct {
-	// required, the name of a secret in the current namespace
+	// SecretName is the name of a TLS secret in the current namespace.
+	// Either SecretName or Passthrough must be specified, but not both.
+	// If specified, the named secret must contain a matching certificate
+	// for the virtual host's FQDN.
 	SecretName string `json:"secretName,omitempty"`
-	// Minimum TLS version this vhost should negotiate
+	// MinimumProtocolVersion is the minimum TLS version this vhost should
+	// negotiate. Valid options are `1.2` (default) and `1.3`. Any other value
+	// defaults to TLS 1.2.
 	// +optional
 	MinimumProtocolVersion string `json:"minimumProtocolVersion,omitempty"`
-	// If Passthrough is set to true, the SecretName will be ignored
-	// and the encrypted handshake will be passed through to the
-	// backing cluster.
+	// Passthrough defines whether the encrypted TLS handshake will be
+	// passed through to the backing cluster. Either Passthrough or
+	// SecretName must be specified, but not both.
 	// +optional
 	Passthrough bool `json:"passthrough,omitempty"`
+	// ClientValidation defines how to verify the client certificate
+	// when an external client establishes a TLS connection to Envoy.
+	//
+	// This setting:
+	//
+	// 1. Enables TLS client certificate validation.
+	// 2. Specifies how the client certificate will be validated (i.e.
+	//    validation required or skipped).
+	//
+	// Note: Setting client certificate validation to be skipped should
+	// be only used in conjunction with an external authorization server that
+	// performs client validation as Contour will ensure client certificates
+	// are passed along.
+	//
+	// +optional
+	ClientValidation *DownstreamValidation `json:"clientValidation,omitempty"`
+
+	// EnableFallbackCertificate defines if the vhost should allow a default certificate to
+	// be applied which handles all requests which don't match the SNI defined in this vhost.
+	EnableFallbackCertificate bool `json:"enableFallbackCertificate,omitempty"`
+}
+
+// CORSHeaderValue specifies the value of the string headers returned by a cross-domain request.
+// +kubebuilder:validation:Pattern="^[a-zA-Z0-9!#$%&'*+.^_`|~-]+$"
+type CORSHeaderValue string
+
+// CORSPolicy allows setting the CORS policy
+type CORSPolicy struct {
+	// Specifies whether the resource allows credentials.
+	//  +optional
+	AllowCredentials bool `json:"allowCredentials,omitempty"`
+	// AllowOrigin specifies the origins that will be allowed to do CORS requests. "*" means
+	// allow any origin.
+	// +kubebuilder:validation:Required
+	AllowOrigin []string `json:"allowOrigin"`
+	// AllowMethods specifies the content for the *access-control-allow-methods* header.
+	// +kubebuilder:validation:Required
+	AllowMethods []CORSHeaderValue `json:"allowMethods"`
+	// AllowHeaders specifies the content for the *access-control-allow-headers* header.
+	//  +optional
+	AllowHeaders []CORSHeaderValue `json:"allowHeaders,omitempty"`
+	// ExposeHeaders Specifies the content for the *access-control-expose-headers* header.
+	//  +optional
+	ExposeHeaders []CORSHeaderValue `json:"exposeHeaders,omitempty"`
+	// MaxAge indicates for how long the results of a preflight request can be cached.
+	// MaxAge durations are expressed in the Go [Duration format](https://godoc.org/time#ParseDuration).
+	// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
+	// Only positive values are allowed while 0 disables the cache requiring a preflight OPTIONS
+	// check for all cross-origin requests.
+	//  +optional
+	MaxAge string `json:"maxAge,omitempty"`
 }
 
 // Route contains the set of routes for a virtual host.
 type Route struct {
-	// Conditions are a set of routing properties that is applied to an HTTPProxy in a namespace.
+	// Conditions are a set of rules that are applied to a Route.
+	// When applied, they are merged using AND, with one exception:
+	// There can be only one Prefix MatchCondition per Conditions slice.
+	// More than one Prefix, or contradictory Conditions, will make the
+	// route invalid.
 	// +optional
-	Conditions []Condition `json:"conditions,omitempty"`
+	Conditions []MatchCondition `json:"conditions,omitempty"`
 	// Services are the services to proxy traffic.
+	// +optional
 	Services []Service `json:"services,omitempty"`
 	// Enables websocket support for the route.
 	// +optional
@@ -135,6 +336,11 @@ type Route struct {
 	// not permitted when a `virtualhost.tls` block is present.
 	// +optional
 	PermitInsecure bool `json:"permitInsecure,omitempty"`
+	// AuthPolicy updates the authorization policy that was set
+	// on the root HTTPProxy object for client requests that
+	// match this route.
+	// +optional
+	AuthPolicy *AuthorizationPolicy `json:"authPolicy,omitempty"`
 	// The timeout policy for this route.
 	// +optional
 	TimeoutPolicy *TimeoutPolicy `json:"timeoutPolicy,omitempty"`
@@ -152,32 +358,300 @@ type Route struct {
 	//
 	// +optional
 	PathRewritePolicy *PathRewritePolicy `json:"pathRewritePolicy,omitempty"`
-	// The policy for managing request headers during proxying
+	// The policy for managing request headers during proxying.
 	// +optional
 	RequestHeadersPolicy *HeadersPolicy `json:"requestHeadersPolicy,omitempty"`
-	// The policy for managing response headers during proxying
+	// The policy for managing response headers during proxying.
+	// Rewriting the 'Host' header is not supported.
 	// +optional
 	ResponseHeadersPolicy *HeadersPolicy `json:"responseHeadersPolicy,omitempty"`
+	// The policies for rewriting Set-Cookie header attributes. Note that
+	// rewritten cookie names must be unique in this list. Order rewrite
+	// policies are specified in does not matter.
+	// +optional
+	CookieRewritePolicies []CookieRewritePolicy `json:"cookieRewritePolicies,omitempty"`
+	// The policy for rate limiting on the route.
+	// +optional
+	RateLimitPolicy *RateLimitPolicy `json:"rateLimitPolicy,omitempty"`
+
+	// RequestRedirectPolicy defines an HTTP redirection.
+	// +optional
+	RequestRedirectPolicy *HTTPRequestRedirectPolicy `json:"requestRedirectPolicy,omitempty"`
+}
+
+// HTTPRequestRedirectPolicy defines configuration for redirecting a request.
+type HTTPRequestRedirectPolicy struct {
+	// Scheme is the scheme to be used in the value of the `Location`
+	// header in the response.
+	// When empty, the scheme of the request is used.
+	// +optional
+	// +kubebuilder:validation:Enum=http;https
+	Scheme *string `json:"scheme,omitempty"`
+
+	// Hostname is the precise hostname to be used in the value of the `Location`
+	// header in the response.
+	// When empty, the hostname of the request is used.
+	// No wildcards are allowed.
+	// +optional
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:MaxLength=253
+	// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
+	Hostname *string `json:"hostname,omitempty"`
+
+	// Port is the port to be used in the value of the `Location`
+	// header in the response.
+	// When empty, port (if specified) of the request is used.
+	// +optional
+	// +kubebuilder:validation:Minimum=1
+	// +kubebuilder:validation:Maximum=65535
+	Port *int32 `json:"port,omitempty"`
+
+	// StatusCode is the HTTP status code to be used in response.
+	// +optional
+	// +kubebuilder:default=302
+	// +kubebuilder:validation:Enum=301;302
+	StatusCode *int `json:"statusCode,omitempty"`
+
+	// Path allows for redirection to a different path from the
+	// original on the request. The path must start with a
+	// leading slash.
+	//
+	// Note: Only one of Path or Prefix can be defined.
+	//
+	// +optional
+	// +kubebuilder:validation:Pattern=`^\/.*$`
+	Path *string `json:"path,omitempty"`
+
+	// Prefix defines the value to swap the matched prefix or path with.
+	// The prefix must start with a leading slash.
+	//
+	// Note: Only one of Path or Prefix can be defined.
+	//
+	// +optional
+	// +kubebuilder:validation:Pattern=`^\/.*$`
+	Prefix *string `json:"prefix,omitempty"`
+}
+
+type CookieRewritePolicy struct {
+	// Name is the name of the cookie for which attributes will be rewritten.
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:MaxLength=4096
+	// +kubebuilder:validation:Pattern=`^[^()<>@,;:\\"\/[\]?={} \t\x7f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]+$`
+	Name string `json:"name"`
+
+	// PathRewrite enables rewriting the Set-Cookie Path element.
+	// If not set, Path will not be rewritten.
+	// +optional
+	PathRewrite *CookiePathRewrite `json:"pathRewrite,omitempty"`
+
+	// DomainRewrite enables rewriting the Set-Cookie Domain element.
+	// If not set, Domain will not be rewritten.
+	// +optional
+	DomainRewrite *CookieDomainRewrite `json:"domainRewrite,omitempty"`
+
+	// Secure enables rewriting the Set-Cookie Secure element.
+	// If not set, Secure attribute will not be rewritten.
+	// +optional
+	Secure *bool `json:"secure,omitempty"`
+
+	// SameSite enables rewriting the Set-Cookie SameSite element.
+	// If not set, SameSite attribute will not be rewritten.
+	// +optional
+	// +kubebuilder:validation:Enum=Strict;Lax;None
+	SameSite *string `json:"sameSite,omitempty"`
 }
 
-func (r *Route) GetPrefixReplacements() []ReplacePrefix {
-	if r.PathRewritePolicy != nil {
-		return r.PathRewritePolicy.ReplacePrefix
-	}
-	return nil
+type CookiePathRewrite struct {
+	// Value is the value to rewrite the Path attribute to.
+	// For now this is required.
+	// +required
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:MaxLength=4096
+	// +kubebuilder:validation:Pattern=`^[^;\x7f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]+$`
+	Value string `json:"value"`
+}
+
+type CookieDomainRewrite struct {
+	// Value is the value to rewrite the Domain attribute to.
+	// For now this is required.
+	// +required
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:MaxLength=4096
+	// +kubebuilder:validation:Pattern="^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"
+	Value string `json:"value"`
+}
+
+// RateLimitPolicy defines rate limiting parameters.
+type RateLimitPolicy struct {
+	// Local defines local rate limiting parameters, i.e. parameters
+	// for rate limiting that occurs within each Envoy pod as requests
+	// are handled.
+	// +optional
+	Local *LocalRateLimitPolicy `json:"local,omitempty"`
+
+	// Global defines global rate limiting parameters, i.e. parameters
+	// defining descriptors that are sent to an external rate limit
+	// service (RLS) for a rate limit decision on each request.
+	// +optional
+	Global *GlobalRateLimitPolicy `json:"global,omitempty"`
 }
 
+// LocalRateLimitPolicy defines local rate limiting parameters.
+type LocalRateLimitPolicy struct {
+	// Requests defines how many requests per unit of time should
+	// be allowed before rate limiting occurs.
+	// +required
+	// +kubebuilder:validation:Minimum=1
+	Requests uint32 `json:"requests"`
+
+	// Unit defines the period of time within which requests
+	// over the limit will be rate limited. Valid values are
+	// "second", "minute" and "hour".
+	// +kubebuilder:validation:Enum=second;minute;hour
+	// +required
+	Unit string `json:"unit"`
+
+	// Burst defines the number of requests above the requests per
+	// unit that should be allowed within a short period of time.
+	// +optional
+	Burst uint32 `json:"burst,omitempty"`
+
+	// ResponseStatusCode is the HTTP status code to use for responses
+	// to rate-limited requests. Codes must be in the 400-599 range
+	// (inclusive). If not specified, the Envoy default of 429 (Too
+	// Many Requests) is used.
+	// +optional
+	// +kubebuilder:validation:Minimum=400
+	// +kubebuilder:validation:Maximum=599
+	ResponseStatusCode uint32 `json:"responseStatusCode,omitempty"`
+
+	// ResponseHeadersToAdd is an optional list of response headers to
+	// set when a request is rate-limited.
+	// +optional
+	ResponseHeadersToAdd []HeaderValue `json:"responseHeadersToAdd,omitempty"`
+}
+
+// GlobalRateLimitPolicy defines global rate limiting parameters.
+type GlobalRateLimitPolicy struct {
+	// Descriptors defines the list of descriptors that will
+	// be generated and sent to the rate limit service. Each
+	// descriptor contains 1+ key-value pair entries.
+	// +required
+	// +kubebuilder:validation:MinItems=1
+	Descriptors []RateLimitDescriptor `json:"descriptors,omitempty"`
+}
+
+// RateLimitDescriptor defines a list of key-value pair generators.
+type RateLimitDescriptor struct {
+	// Entries is the list of key-value pair generators.
+	// +required
+	// +kubebuilder:validation:MinItems=1
+	Entries []RateLimitDescriptorEntry `json:"entries,omitempty"`
+}
+
+// RateLimitDescriptorEntry is a key-value pair generator. Exactly
+// one field on this struct must be non-nil.
+type RateLimitDescriptorEntry struct {
+	// GenericKey defines a descriptor entry with a static key and value.
+	// +optional
+	GenericKey *GenericKeyDescriptor `json:"genericKey,omitempty"`
+
+	// RequestHeader defines a descriptor entry that's populated only if
+	// a given header is present on the request. The descriptor key is static,
+	// and the descriptor value is equal to the value of the header.
+	// +optional
+	RequestHeader *RequestHeaderDescriptor `json:"requestHeader,omitempty"`
+
+	// RequestHeaderValueMatch defines a descriptor entry that's populated
+	// if the request's headers match a set of 1+ match criteria. The
+	// descriptor key is "header_match", and the descriptor value is static.
+	// +optional
+	RequestHeaderValueMatch *RequestHeaderValueMatchDescriptor `json:"requestHeaderValueMatch,omitempty"`
+
+	// RemoteAddress defines a descriptor entry with a key of "remote_address"
+	// and a value equal to the client's IP address (from x-forwarded-for).
+	// +optional
+	RemoteAddress *RemoteAddressDescriptor `json:"remoteAddress,omitempty"`
+}
+
+// GenericKeyDescriptor defines a descriptor entry with a static key and
+// value.
+type GenericKeyDescriptor struct {
+	// Key defines the key of the descriptor entry. If not set, the
+	// key is set to "generic_key".
+	// +optional
+	Key string `json:"key,omitempty"`
+
+	// Value defines the value of the descriptor entry.
+	// +required
+	// +kubebuilder:validation:MinLength=1
+	Value string `json:"value,omitempty"`
+}
+
+// RequestHeaderDescriptor defines a descriptor entry that's populated only
+// if a given header is present on the request. The value of the descriptor
+// entry is equal to the value of the header (if present).
+type RequestHeaderDescriptor struct {
+	// HeaderName defines the name of the header to look for on the request.
+	// +required
+	// +kubebuilder:validation:MinLength=1
+	HeaderName string `json:"headerName,omitempty"`
+
+	// DescriptorKey defines the key to use on the descriptor entry.
+	// +required
+	// +kubebuilder:validation:MinLength=1
+	DescriptorKey string `json:"descriptorKey,omitempty"`
+}
+
+// RequestHeaderValueMatchDescriptor defines a descriptor entry that's populated
+// if the request's headers match a set of 1+ match criteria. The descriptor key
+// is "header_match", and the descriptor value is statically defined.
+type RequestHeaderValueMatchDescriptor struct {
+	// Headers is a list of 1+ match criteria to apply against the request
+	// to determine whether to populate the descriptor entry or not.
+	// +kubebuilder:validation:MinItems=1
+	Headers []HeaderMatchCondition `json:"headers,omitempty"`
+
+	// ExpectMatch defines whether the request must positively match the match
+	// criteria in order to generate a descriptor entry (i.e. true), or not
+	// match the match criteria in order to generate a descriptor entry (i.e. false).
+	// The default is true.
+	// +kubebuilder:default=true
+	ExpectMatch bool `json:"expectMatch,omitempty"`
+
+	// Value defines the value of the descriptor entry.
+	// +required
+	// +kubebuilder:validation:MinLength=1
+	Value string `json:"value,omitempty"`
+}
+
+// RemoteAddressDescriptor defines a descriptor entry with a key of
+// "remote_address" and a value equal to the client's IP address
+// (from x-forwarded-for).
+type RemoteAddressDescriptor struct{}
+
 // TCPProxy contains the set of services to proxy TCP connections.
 type TCPProxy struct {
-	// The load balancing policy for the backend services.
+	// The load balancing policy for the backend services. Note that the
+	// `Cookie` and `RequestHash` load balancing strategies cannot be used
+	// here.
 	// +optional
 	LoadBalancerPolicy *LoadBalancerPolicy `json:"loadBalancerPolicy,omitempty"`
 	// Services are the services to proxy traffic
-	Services []Service `json:"services,omitempty"`
-
+	// +optional
+	Services []Service `json:"services"`
 	// Include specifies that this tcpproxy should be delegated to another HTTPProxy.
 	// +optional
-	Include *TCPProxyInclude `json:"includes,omitempty"`
+	Include *TCPProxyInclude `json:"include,omitempty"`
+	// IncludesDeprecated allow for specific routing configuration to be appended to another HTTPProxy in another namespace.
+	//
+	// Exists due to a mistake when developing HTTPProxy and the field was marked plural
+	// when it should have been singular. This field should stay to not break backwards compatibility to v1 users.
+	// +optional
+	IncludesDeprecated *TCPProxyInclude `json:"includes,omitempty"`
+	// The health check policy for this tcp proxy
+	// +optional
+	HealthCheckPolicy *TCPHealthCheckPolicy `json:"healthCheckPolicy,omitempty"`
 }
 
 // TCPProxyInclude describes a target HTTPProxy document which contains the TCPProxy details.
@@ -195,25 +669,38 @@ type Service struct {
 	// Names defined here will be used to look up corresponding endpoints which contain the ips to route.
 	Name string `json:"name"`
 	// Port (defined as Integer) to proxy traffic to since a service can have multiple defined.
+	//
+	// +required
+	// +kubebuilder:validation:Minimum=1
+	// +kubebuilder:validation:Maximum=65536
+	// +kubebuilder:validation:ExclusiveMinimum=false
+	// +kubebuilder:validation:ExclusiveMaximum=true
 	Port int `json:"port"`
 	// Protocol may be used to specify (or override) the protocol used to reach this Service.
-	// Values may be tls, h2, h2c.  It ommitted protocol-selection falls back on Service annotations.
+	// Values may be tls, h2, h2c. If omitted, protocol-selection falls back on Service annotations.
+	// +kubebuilder:validation:Enum=h2;h2c;tls
 	// +optional
 	Protocol *string `json:"protocol,omitempty"`
 	// Weight defines percentage of traffic to balance traffic
 	// +optional
-	Weight uint32 `json:"weight,omitempty"`
+	// +kubebuilder:validation:Minimum=0
+	Weight int64 `json:"weight,omitempty"`
 	// UpstreamValidation defines how to verify the backend service's certificate
 	// +optional
 	UpstreamValidation *UpstreamValidation `json:"validation,omitempty"`
 	// If Mirror is true the Service will receive a read only mirror of the traffic for this route.
 	Mirror bool `json:"mirror,omitempty"`
-	// The policy for managing request headers during proxying
+	// The policy for managing request headers during proxying.
+	// Rewriting the 'Host' header is not supported.
 	// +optional
 	RequestHeadersPolicy *HeadersPolicy `json:"requestHeadersPolicy,omitempty"`
-	// The policy for managing response headers during proxying
+	// The policy for managing response headers during proxying.
+	// Rewriting the 'Host' header is not supported.
 	// +optional
 	ResponseHeadersPolicy *HeadersPolicy `json:"responseHeadersPolicy,omitempty"`
+	// The policies for rewriting Set-Cookie header attributes.
+	// +optional
+	CookieRewritePolicies []CookieRewritePolicy `json:"cookieRewritePolicies,omitempty"`
 }
 
 // HTTPHealthCheckPolicy defines health checks on the upstream service.
@@ -224,6 +711,24 @@ type HTTPHealthCheckPolicy struct {
 	// If left empty (default value), the name "contour-envoy-healthcheck"
 	// will be used.
 	Host string `json:"host,omitempty"`
+	// The interval (seconds) between health checks
+	// +optional
+	IntervalSeconds int64 `json:"intervalSeconds"`
+	// The time to wait (seconds) for a health check response
+	// +optional
+	TimeoutSeconds int64 `json:"timeoutSeconds"`
+	// The number of unhealthy health checks required before a host is marked unhealthy
+	// +optional
+	// +kubebuilder:validation:Minimum=0
+	UnhealthyThresholdCount int64 `json:"unhealthyThresholdCount"`
+	// The number of healthy health checks required before a host is marked healthy
+	// +optional
+	// +kubebuilder:validation:Minimum=0
+	HealthyThresholdCount int64 `json:"healthyThresholdCount"`
+}
+
+// TCPHealthCheckPolicy defines health checks on the upstream service.
+type TCPHealthCheckPolicy struct {
 	// The interval (seconds) between health checks
 	// +optional
 	IntervalSeconds int64 `json:"intervalSeconds"`
@@ -238,39 +743,89 @@ type HTTPHealthCheckPolicy struct {
 	HealthyThresholdCount uint32 `json:"healthyThresholdCount"`
 }
 
-// TimeoutPolicy defines the attributes associated with timeout.
+// TimeoutPolicy configures timeouts that are used for handling network requests.
+//
+// TimeoutPolicy durations are expressed in the Go [Duration format](https://godoc.org/time#ParseDuration).
+// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
+// The string "infinity" is also a valid input and specifies no timeout.
+// A value of "0s" will be treated as if the field were not set, i.e. by using Envoy's default behavior.
+//
+// Example input values: "300ms", "5s", "1m".
 type TimeoutPolicy struct {
-	// TimeoutPolicy durations are expressed as per the format specified in the ParseDuration documentation: https://godoc.org/time#ParseDuration
-	// Example input values: "300ms", "5s", "1m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
-	// The string 'infinity' is also a valid input and specifies no timeout.
-
 	// Timeout for receiving a response from the server after processing a request from client.
-	// If not supplied the timeout duration is undefined.
+	// If not supplied, Envoy's default value of 15s applies.
 	// +optional
+	// +kubebuilder:validation:Pattern=`^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$`
 	Response string `json:"response,omitempty"`
 
-	// Timeout after which if there are no active requests for this route, the connection between
-	// Envoy and the backend will be closed. If not specified, there is no per-route idle timeout.
+	// Timeout for how long the proxy should wait while there is no activity during single request/response (for HTTP/1.1) or stream (for HTTP/2).
+	// Timeout will not trigger while HTTP/1.1 connection is idle between two consecutive requests.
+	// If not specified, there is no per-route idle timeout, though a connection manager-wide
+	// stream_idle_timeout default of 5m still applies.
 	// +optional
+	// +kubebuilder:validation:Pattern=`^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$`
 	Idle string `json:"idle,omitempty"`
+
+	// Timeout for how long connection from the proxy to the upstream service is kept when there are no active requests.
+	// If not supplied, Envoy's default value of 1h applies.
+	// +optional
+	// +kubebuilder:validation:Pattern=`^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$`
+	IdleConnection string `json:"idleConnection,omitempty"`
 }
 
+// RetryOn is a string type alias with validation to ensure that the value is valid.
+// +kubebuilder:validation:Enum="5xx";gateway-error;reset;connect-failure;retriable-4xx;refused-stream;retriable-status-codes;retriable-headers;cancelled;deadline-exceeded;internal;resource-exhausted;unavailable
+type RetryOn string
+
 // RetryPolicy defines the attributes associated with retrying policy.
 type RetryPolicy struct {
 	// NumRetries is maximum allowed number of retries.
-	// If not supplied, the number of retries is one.
+	// If set to -1, then retries are disabled.
+	// If set to 0 or not supplied, the value is set
+	// to the Envoy default of 1.
 	// +optional
-	NumRetries uint32 `json:"count"`
+	// +kubebuilder:default=1
+	// +kubebuilder:validation:Minimum=-1
+	NumRetries int64 `json:"count"`
 	// PerTryTimeout specifies the timeout per retry attempt.
 	// Ignored if NumRetries is not supplied.
+	// +optional
+	// +kubebuilder:validation:Pattern=`^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$`
 	PerTryTimeout string `json:"perTryTimeout,omitempty"`
+	// RetryOn specifies the conditions on which to retry a request.
+	//
+	// Supported [HTTP conditions](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#x-envoy-retry-on):
+	//
+	// - `5xx`
+	// - `gateway-error`
+	// - `reset`
+	// - `connect-failure`
+	// - `retriable-4xx`
+	// - `refused-stream`
+	// - `retriable-status-codes`
+	// - `retriable-headers`
+	//
+	// Supported [gRPC conditions](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#x-envoy-retry-grpc-on):
+	//
+	// - `cancelled`
+	// - `deadline-exceeded`
+	// - `internal`
+	// - `resource-exhausted`
+	// - `unavailable`
+	// +optional
+	RetryOn []RetryOn `json:"retryOn,omitempty"`
+	// RetriableStatusCodes specifies the HTTP status codes that should be retried.
+	//
+	// This field is only respected when you include `retriable-status-codes` in the `RetryOn` field.
+	// +optional
+	RetriableStatusCodes []uint32 `json:"retriableStatusCodes,omitempty"`
 }
 
 // ReplacePrefix describes a path prefix replacement.
 type ReplacePrefix struct {
 	// Prefix specifies the URL path prefix to be replaced.
 	//
-	// If Prefix is specified, it must exactly match the Condition
+	// If Prefix is specified, it must exactly match the MatchCondition
 	// prefix that is rendered by the chain of including HTTPProxies
 	// and only that path prefix will be replaced by Replacement.
 	// This allows HTTPProxies that are included through multiple
@@ -304,17 +859,67 @@ type PathRewritePolicy struct {
 	ReplacePrefix []ReplacePrefix `json:"replacePrefix,omitempty"`
 }
 
+// HeaderHashOptions contains options to configure a HTTP request header hash
+// policy, used in request attribute hash based load balancing.
+type HeaderHashOptions struct {
+	// HeaderName is the name of the HTTP request header that will be used to
+	// calculate the hash key. If the header specified is not present on a
+	// request, no hash will be produced.
+	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:MinLength=1
+	HeaderName string `json:"headerName,omitempty"`
+}
+
+// RequestHashPolicy contains configuration for an individual hash policy
+// on a request attribute.
+type RequestHashPolicy struct {
+	// Terminal is a flag that allows for short-circuiting computing of a hash
+	// for a given request. If set to true, and the request attribute specified
+	// in the attribute hash options is present, no further hash policies will
+	// be used to calculate a hash for the request.
+	Terminal bool `json:"terminal,omitempty"`
+
+	// HeaderHashOptions should be set when request header hash based load
+	// balancing is desired. It must be the only hash option field set,
+	// otherwise this request hash policy object will be ignored.
+	// +optional
+	HeaderHashOptions *HeaderHashOptions `json:"headerHashOptions,omitempty"`
+
+	// HashSourceIP should be set to true when request source IP hash based
+	// load balancing is desired. It must be the only hash option field set,
+	// otherwise this request hash policy object will be ignored.
+	// +optional
+	HashSourceIP bool `json:"hashSourceIP,omitempty"`
+}
+
 // LoadBalancerPolicy defines the load balancing policy.
 type LoadBalancerPolicy struct {
+	// Strategy specifies the policy used to balance requests
+	// across the pool of backend pods. Valid policy names are
+	// `Random`, `RoundRobin`, `WeightedLeastRequest`, `Cookie`,
+	// and `RequestHash`. If an unknown strategy name is specified
+	// or no policy is supplied, the default `RoundRobin` policy
+	// is used.
 	Strategy string `json:"strategy,omitempty"`
+
+	// RequestHashPolicies contains a list of hash policies to apply when the
+	// `RequestHash` load balancing strategy is chosen. If an element of the
+	// supplied list of hash policies is invalid, it will be ignored. If the
+	// list of hash policies is empty after validation, the load balancing
+	// strategy will fall back the the default `RoundRobin`.
+	RequestHashPolicies []RequestHashPolicy `json:"requestHashPolicies,omitempty"`
 }
 
-// HeadersPolicy defines how headers are managed during forwarding
+// HeadersPolicy defines how headers are managed during forwarding.
+// The `Host` header is treated specially and if set in a HTTP response
+// will be used as the SNI server name when forwarding over TLS. It is an
+// error to attempt to set the `Host` header in a HTTP response.
 type HeadersPolicy struct {
-	// Set specifies a list of HTTP header values that will be set in the HTTP header
+	// Set specifies a list of HTTP header values that will be set in the HTTP header.
+	// If the header does not exist it will be added, otherwise it will be overwritten with the new value.
 	// +optional
 	Set []HeaderValue `json:"set,omitempty"`
-	// Remove specifies a list of HTTP header names to remove
+	// Remove specifies a list of HTTP header names to remove.
 	// +optional
 	Remove []string `json:"remove,omitempty"`
 }
@@ -333,37 +938,83 @@ type HeaderValue struct {
 
 // UpstreamValidation defines how to verify the backend service's certificate
 type UpstreamValidation struct {
-	// Name of the Kubernetes secret be used to validate the certificate presented by the backend
+	// Name or namespaced name of the Kubernetes secret used to validate the certificate presented by the backend
 	CACertificate string `json:"caSecret"`
 	// Key which is expected to be present in the 'subjectAltName' of the presented certificate
 	SubjectName string `json:"subjectName"`
 }
 
-// Status reports the current state of the HTTPProxy.
-type Status struct {
+// DownstreamValidation defines how to verify the client certificate.
+type DownstreamValidation struct {
+	// Name of a Kubernetes secret that contains a CA certificate bundle.
+	// The client certificate must validate against the certificates in the bundle.
+	// If specified and SkipClientCertValidation is true, client certificates will
+	// be required on requests.
+	// +optional
+	// +kubebuilder:validation:MinLength=1
+	CACertificate string `json:"caSecret,omitempty"`
+
+	// SkipClientCertValidation disables downstream client certificate
+	// validation. Defaults to false. This field is intended to be used in
+	// conjunction with external authorization in order to enable the external
+	// authorization server to validate client certificates. When this field
+	// is set to true, client certificates are requested but not verified by
+	// Envoy. If CACertificate is specified, client certificates are required on
+	// requests, but not verified. If external authorization is in use, they are
+	// presented to the external authorization server.
+	// +optional
+	SkipClientCertValidation bool `json:"skipClientCertValidation"`
+}
+
+// HTTPProxyStatus reports the current state of the HTTPProxy.
+type HTTPProxyStatus struct {
 	// +optional
 	CurrentStatus string `json:"currentStatus,omitempty"`
 	// +optional
 	Description string `json:"description,omitempty"`
+	// +optional
+	// LoadBalancer contains the current status of the load balancer.
+	LoadBalancer corev1.LoadBalancerStatus `json:"loadBalancer,omitempty"`
+	// +optional
+	// Conditions contains information about the current status of the HTTPProxy,
+	// in an upstream-friendly container.
+	//
+	// Contour will update a single condition, `Valid`, that is in normal-true polarity.
+	// That is, when `currentStatus` is `valid`, the `Valid` condition will be `status: true`,
+	// and vice versa.
+	//
+	// Contour will leave untouched any other Conditions set in this block,
+	// in case some other controller wants to add a Condition.
+	//
+	// If you are another controller owner and wish to add a condition, you *should*
+	// namespace your condition with a label, like `controller.domain.com/ConditionName`.
+	// +patchMergeKey=type
+	// +patchStrategy=merge
+	// +listType=map
+	// +listMapKey=type
+	Conditions []DetailedCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
 }
 
 // +genclient
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
-// HTTPProxy is an Ingress CRD specification
+// HTTPProxy is an Ingress CRD specification.
 // +k8s:openapi-gen=true
 // +kubebuilder:printcolumn:name="FQDN",type="string",JSONPath=".spec.virtualhost.fqdn",description="Fully qualified domain name"
 // +kubebuilder:printcolumn:name="TLS Secret",type="string",JSONPath=".spec.virtualhost.tls.secretName",description="Secret with TLS credentials"
 // +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.currentStatus",description="The current status of the HTTPProxy"
 // +kubebuilder:printcolumn:name="Status Description",type="string",JSONPath=".status.description",description="Description of the current status"
 // +kubebuilder:resource:scope=Namespaced,path=httpproxies,shortName=proxy;proxies,singular=httpproxy
+// +kubebuilder:subresource:status
 type HTTPProxy struct {
 	metav1.TypeMeta   `json:",inline"`
 	metav1.ObjectMeta `json:"metadata"`
 
 	Spec HTTPProxySpec `json:"spec"`
+	// Status is a container for computed information about the HTTPProxy.
 	// +optional
-	Status Status `json:"status,omitempty"`
+	// +kubebuilder:default={currentStatus: "NotReconciled", description:"Waiting for controller"}
+	Status HTTPProxyStatus `json:"status,omitempty"`
 }
 
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
diff --git a/pkg/apis/projectcontour/v1/zz_generated.deepcopy.go b/pkg/apis/projectcontour/v1/zz_generated.deepcopy.go
index be2d9f8d..1d076e9c 100644
--- a/pkg/apis/projectcontour/v1/zz_generated.deepcopy.go
+++ b/pkg/apis/projectcontour/v1/zz_generated.deepcopy.go
@@ -26,22 +26,269 @@ import (
 )
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *Condition) DeepCopyInto(out *Condition) {
+func (in *AuthorizationPolicy) DeepCopyInto(out *AuthorizationPolicy) {
 	*out = *in
-	if in.Header != nil {
-		in, out := &in.Header, &out.Header
-		*out = new(HeaderCondition)
+	if in.Context != nil {
+		in, out := &in.Context, &out.Context
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationPolicy.
+func (in *AuthorizationPolicy) DeepCopy() *AuthorizationPolicy {
+	if in == nil {
+		return nil
+	}
+	out := new(AuthorizationPolicy)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AuthorizationServer) DeepCopyInto(out *AuthorizationServer) {
+	*out = *in
+	out.ExtensionServiceRef = in.ExtensionServiceRef
+	if in.AuthPolicy != nil {
+		in, out := &in.AuthPolicy, &out.AuthPolicy
+		*out = new(AuthorizationPolicy)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.WithRequestBody != nil {
+		in, out := &in.WithRequestBody, &out.WithRequestBody
+		*out = new(AuthorizationServerBufferSettings)
+		**out = **in
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationServer.
+func (in *AuthorizationServer) DeepCopy() *AuthorizationServer {
+	if in == nil {
+		return nil
+	}
+	out := new(AuthorizationServer)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AuthorizationServerBufferSettings) DeepCopyInto(out *AuthorizationServerBufferSettings) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationServerBufferSettings.
+func (in *AuthorizationServerBufferSettings) DeepCopy() *AuthorizationServerBufferSettings {
+	if in == nil {
+		return nil
+	}
+	out := new(AuthorizationServerBufferSettings)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CORSPolicy) DeepCopyInto(out *CORSPolicy) {
+	*out = *in
+	if in.AllowOrigin != nil {
+		in, out := &in.AllowOrigin, &out.AllowOrigin
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.AllowMethods != nil {
+		in, out := &in.AllowMethods, &out.AllowMethods
+		*out = make([]CORSHeaderValue, len(*in))
+		copy(*out, *in)
+	}
+	if in.AllowHeaders != nil {
+		in, out := &in.AllowHeaders, &out.AllowHeaders
+		*out = make([]CORSHeaderValue, len(*in))
+		copy(*out, *in)
+	}
+	if in.ExposeHeaders != nil {
+		in, out := &in.ExposeHeaders, &out.ExposeHeaders
+		*out = make([]CORSHeaderValue, len(*in))
+		copy(*out, *in)
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CORSPolicy.
+func (in *CORSPolicy) DeepCopy() *CORSPolicy {
+	if in == nil {
+		return nil
+	}
+	out := new(CORSPolicy)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CookieDomainRewrite) DeepCopyInto(out *CookieDomainRewrite) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CookieDomainRewrite.
+func (in *CookieDomainRewrite) DeepCopy() *CookieDomainRewrite {
+	if in == nil {
+		return nil
+	}
+	out := new(CookieDomainRewrite)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CookiePathRewrite) DeepCopyInto(out *CookiePathRewrite) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CookiePathRewrite.
+func (in *CookiePathRewrite) DeepCopy() *CookiePathRewrite {
+	if in == nil {
+		return nil
+	}
+	out := new(CookiePathRewrite)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CookieRewritePolicy) DeepCopyInto(out *CookieRewritePolicy) {
+	*out = *in
+	if in.PathRewrite != nil {
+		in, out := &in.PathRewrite, &out.PathRewrite
+		*out = new(CookiePathRewrite)
+		**out = **in
+	}
+	if in.DomainRewrite != nil {
+		in, out := &in.DomainRewrite, &out.DomainRewrite
+		*out = new(CookieDomainRewrite)
+		**out = **in
+	}
+	if in.Secure != nil {
+		in, out := &in.Secure, &out.Secure
+		*out = new(bool)
+		**out = **in
+	}
+	if in.SameSite != nil {
+		in, out := &in.SameSite, &out.SameSite
+		*out = new(string)
 		**out = **in
 	}
 	return
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition.
-func (in *Condition) DeepCopy() *Condition {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CookieRewritePolicy.
+func (in *CookieRewritePolicy) DeepCopy() *CookieRewritePolicy {
 	if in == nil {
 		return nil
 	}
-	out := new(Condition)
+	out := new(CookieRewritePolicy)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *DetailedCondition) DeepCopyInto(out *DetailedCondition) {
+	*out = *in
+	in.Condition.DeepCopyInto(&out.Condition)
+	if in.Errors != nil {
+		in, out := &in.Errors, &out.Errors
+		*out = make([]SubCondition, len(*in))
+		copy(*out, *in)
+	}
+	if in.Warnings != nil {
+		in, out := &in.Warnings, &out.Warnings
+		*out = make([]SubCondition, len(*in))
+		copy(*out, *in)
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DetailedCondition.
+func (in *DetailedCondition) DeepCopy() *DetailedCondition {
+	if in == nil {
+		return nil
+	}
+	out := new(DetailedCondition)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *DownstreamValidation) DeepCopyInto(out *DownstreamValidation) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DownstreamValidation.
+func (in *DownstreamValidation) DeepCopy() *DownstreamValidation {
+	if in == nil {
+		return nil
+	}
+	out := new(DownstreamValidation)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ExtensionServiceReference) DeepCopyInto(out *ExtensionServiceReference) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionServiceReference.
+func (in *ExtensionServiceReference) DeepCopy() *ExtensionServiceReference {
+	if in == nil {
+		return nil
+	}
+	out := new(ExtensionServiceReference)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GenericKeyDescriptor) DeepCopyInto(out *GenericKeyDescriptor) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericKeyDescriptor.
+func (in *GenericKeyDescriptor) DeepCopy() *GenericKeyDescriptor {
+	if in == nil {
+		return nil
+	}
+	out := new(GenericKeyDescriptor)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GlobalRateLimitPolicy) DeepCopyInto(out *GlobalRateLimitPolicy) {
+	*out = *in
+	if in.Descriptors != nil {
+		in, out := &in.Descriptors, &out.Descriptors
+		*out = make([]RateLimitDescriptor, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalRateLimitPolicy.
+func (in *GlobalRateLimitPolicy) DeepCopy() *GlobalRateLimitPolicy {
+	if in == nil {
+		return nil
+	}
+	out := new(GlobalRateLimitPolicy)
 	in.DeepCopyInto(out)
 	return out
 }
@@ -68,7 +315,7 @@ func (in *HTTPProxy) DeepCopyInto(out *HTTPProxy) {
 	out.TypeMeta = in.TypeMeta
 	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
 	in.Spec.DeepCopyInto(&out.Spec)
-	out.Status = in.Status
+	in.Status.DeepCopyInto(&out.Status)
 	return
 }
 
@@ -164,17 +411,103 @@ func (in *HTTPProxySpec) DeepCopy() *HTTPProxySpec {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *HeaderCondition) DeepCopyInto(out *HeaderCondition) {
+func (in *HTTPProxyStatus) DeepCopyInto(out *HTTPProxyStatus) {
+	*out = *in
+	in.LoadBalancer.DeepCopyInto(&out.LoadBalancer)
+	if in.Conditions != nil {
+		in, out := &in.Conditions, &out.Conditions
+		*out = make([]DetailedCondition, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPProxyStatus.
+func (in *HTTPProxyStatus) DeepCopy() *HTTPProxyStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(HTTPProxyStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HTTPRequestRedirectPolicy) DeepCopyInto(out *HTTPRequestRedirectPolicy) {
+	*out = *in
+	if in.Scheme != nil {
+		in, out := &in.Scheme, &out.Scheme
+		*out = new(string)
+		**out = **in
+	}
+	if in.Hostname != nil {
+		in, out := &in.Hostname, &out.Hostname
+		*out = new(string)
+		**out = **in
+	}
+	if in.Port != nil {
+		in, out := &in.Port, &out.Port
+		*out = new(int32)
+		**out = **in
+	}
+	if in.StatusCode != nil {
+		in, out := &in.StatusCode, &out.StatusCode
+		*out = new(int)
+		**out = **in
+	}
+	if in.Path != nil {
+		in, out := &in.Path, &out.Path
+		*out = new(string)
+		**out = **in
+	}
+	if in.Prefix != nil {
+		in, out := &in.Prefix, &out.Prefix
+		*out = new(string)
+		**out = **in
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRequestRedirectPolicy.
+func (in *HTTPRequestRedirectPolicy) DeepCopy() *HTTPRequestRedirectPolicy {
+	if in == nil {
+		return nil
+	}
+	out := new(HTTPRequestRedirectPolicy)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HeaderHashOptions) DeepCopyInto(out *HeaderHashOptions) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeaderHashOptions.
+func (in *HeaderHashOptions) DeepCopy() *HeaderHashOptions {
+	if in == nil {
+		return nil
+	}
+	out := new(HeaderHashOptions)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HeaderMatchCondition) DeepCopyInto(out *HeaderMatchCondition) {
 	*out = *in
 	return
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeaderCondition.
-func (in *HeaderCondition) DeepCopy() *HeaderCondition {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeaderMatchCondition.
+func (in *HeaderMatchCondition) DeepCopy() *HeaderMatchCondition {
 	if in == nil {
 		return nil
 	}
-	out := new(HeaderCondition)
+	out := new(HeaderMatchCondition)
 	in.DeepCopyInto(out)
 	return out
 }
@@ -226,7 +559,7 @@ func (in *Include) DeepCopyInto(out *Include) {
 	*out = *in
 	if in.Conditions != nil {
 		in, out := &in.Conditions, &out.Conditions
-		*out = make([]Condition, len(*in))
+		*out = make([]MatchCondition, len(*in))
 		for i := range *in {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
@@ -247,6 +580,13 @@ func (in *Include) DeepCopy() *Include {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *LoadBalancerPolicy) DeepCopyInto(out *LoadBalancerPolicy) {
 	*out = *in
+	if in.RequestHashPolicies != nil {
+		in, out := &in.RequestHashPolicies, &out.RequestHashPolicies
+		*out = make([]RequestHashPolicy, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
 	return
 }
 
@@ -260,6 +600,48 @@ func (in *LoadBalancerPolicy) DeepCopy() *LoadBalancerPolicy {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LocalRateLimitPolicy) DeepCopyInto(out *LocalRateLimitPolicy) {
+	*out = *in
+	if in.ResponseHeadersToAdd != nil {
+		in, out := &in.ResponseHeadersToAdd, &out.ResponseHeadersToAdd
+		*out = make([]HeaderValue, len(*in))
+		copy(*out, *in)
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalRateLimitPolicy.
+func (in *LocalRateLimitPolicy) DeepCopy() *LocalRateLimitPolicy {
+	if in == nil {
+		return nil
+	}
+	out := new(LocalRateLimitPolicy)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *MatchCondition) DeepCopyInto(out *MatchCondition) {
+	*out = *in
+	if in.Header != nil {
+		in, out := &in.Header, &out.Header
+		*out = new(HeaderMatchCondition)
+		**out = **in
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchCondition.
+func (in *MatchCondition) DeepCopy() *MatchCondition {
+	if in == nil {
+		return nil
+	}
+	out := new(MatchCondition)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *PathRewritePolicy) DeepCopyInto(out *PathRewritePolicy) {
 	*out = *in
@@ -281,6 +663,107 @@ func (in *PathRewritePolicy) DeepCopy() *PathRewritePolicy {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RateLimitDescriptor) DeepCopyInto(out *RateLimitDescriptor) {
+	*out = *in
+	if in.Entries != nil {
+		in, out := &in.Entries, &out.Entries
+		*out = make([]RateLimitDescriptorEntry, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitDescriptor.
+func (in *RateLimitDescriptor) DeepCopy() *RateLimitDescriptor {
+	if in == nil {
+		return nil
+	}
+	out := new(RateLimitDescriptor)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RateLimitDescriptorEntry) DeepCopyInto(out *RateLimitDescriptorEntry) {
+	*out = *in
+	if in.GenericKey != nil {
+		in, out := &in.GenericKey, &out.GenericKey
+		*out = new(GenericKeyDescriptor)
+		**out = **in
+	}
+	if in.RequestHeader != nil {
+		in, out := &in.RequestHeader, &out.RequestHeader
+		*out = new(RequestHeaderDescriptor)
+		**out = **in
+	}
+	if in.RequestHeaderValueMatch != nil {
+		in, out := &in.RequestHeaderValueMatch, &out.RequestHeaderValueMatch
+		*out = new(RequestHeaderValueMatchDescriptor)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.RemoteAddress != nil {
+		in, out := &in.RemoteAddress, &out.RemoteAddress
+		*out = new(RemoteAddressDescriptor)
+		**out = **in
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitDescriptorEntry.
+func (in *RateLimitDescriptorEntry) DeepCopy() *RateLimitDescriptorEntry {
+	if in == nil {
+		return nil
+	}
+	out := new(RateLimitDescriptorEntry)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RateLimitPolicy) DeepCopyInto(out *RateLimitPolicy) {
+	*out = *in
+	if in.Local != nil {
+		in, out := &in.Local, &out.Local
+		*out = new(LocalRateLimitPolicy)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.Global != nil {
+		in, out := &in.Global, &out.Global
+		*out = new(GlobalRateLimitPolicy)
+		(*in).DeepCopyInto(*out)
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitPolicy.
+func (in *RateLimitPolicy) DeepCopy() *RateLimitPolicy {
+	if in == nil {
+		return nil
+	}
+	out := new(RateLimitPolicy)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RemoteAddressDescriptor) DeepCopyInto(out *RemoteAddressDescriptor) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemoteAddressDescriptor.
+func (in *RemoteAddressDescriptor) DeepCopy() *RemoteAddressDescriptor {
+	if in == nil {
+		return nil
+	}
+	out := new(RemoteAddressDescriptor)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ReplacePrefix) DeepCopyInto(out *ReplacePrefix) {
 	*out = *in
@@ -297,9 +780,77 @@ func (in *ReplacePrefix) DeepCopy() *ReplacePrefix {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RequestHashPolicy) DeepCopyInto(out *RequestHashPolicy) {
+	*out = *in
+	if in.HeaderHashOptions != nil {
+		in, out := &in.HeaderHashOptions, &out.HeaderHashOptions
+		*out = new(HeaderHashOptions)
+		**out = **in
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestHashPolicy.
+func (in *RequestHashPolicy) DeepCopy() *RequestHashPolicy {
+	if in == nil {
+		return nil
+	}
+	out := new(RequestHashPolicy)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RequestHeaderDescriptor) DeepCopyInto(out *RequestHeaderDescriptor) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestHeaderDescriptor.
+func (in *RequestHeaderDescriptor) DeepCopy() *RequestHeaderDescriptor {
+	if in == nil {
+		return nil
+	}
+	out := new(RequestHeaderDescriptor)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RequestHeaderValueMatchDescriptor) DeepCopyInto(out *RequestHeaderValueMatchDescriptor) {
+	*out = *in
+	if in.Headers != nil {
+		in, out := &in.Headers, &out.Headers
+		*out = make([]HeaderMatchCondition, len(*in))
+		copy(*out, *in)
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestHeaderValueMatchDescriptor.
+func (in *RequestHeaderValueMatchDescriptor) DeepCopy() *RequestHeaderValueMatchDescriptor {
+	if in == nil {
+		return nil
+	}
+	out := new(RequestHeaderValueMatchDescriptor)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *RetryPolicy) DeepCopyInto(out *RetryPolicy) {
 	*out = *in
+	if in.RetryOn != nil {
+		in, out := &in.RetryOn, &out.RetryOn
+		*out = make([]RetryOn, len(*in))
+		copy(*out, *in)
+	}
+	if in.RetriableStatusCodes != nil {
+		in, out := &in.RetriableStatusCodes, &out.RetriableStatusCodes
+		*out = make([]uint32, len(*in))
+		copy(*out, *in)
+	}
 	return
 }
 
@@ -318,7 +869,7 @@ func (in *Route) DeepCopyInto(out *Route) {
 	*out = *in
 	if in.Conditions != nil {
 		in, out := &in.Conditions, &out.Conditions
-		*out = make([]Condition, len(*in))
+		*out = make([]MatchCondition, len(*in))
 		for i := range *in {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
@@ -330,6 +881,11 @@ func (in *Route) DeepCopyInto(out *Route) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
+	if in.AuthPolicy != nil {
+		in, out := &in.AuthPolicy, &out.AuthPolicy
+		*out = new(AuthorizationPolicy)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.TimeoutPolicy != nil {
 		in, out := &in.TimeoutPolicy, &out.TimeoutPolicy
 		*out = new(TimeoutPolicy)
@@ -338,7 +894,7 @@ func (in *Route) DeepCopyInto(out *Route) {
 	if in.RetryPolicy != nil {
 		in, out := &in.RetryPolicy, &out.RetryPolicy
 		*out = new(RetryPolicy)
-		**out = **in
+		(*in).DeepCopyInto(*out)
 	}
 	if in.HealthCheckPolicy != nil {
 		in, out := &in.HealthCheckPolicy, &out.HealthCheckPolicy
@@ -348,7 +904,7 @@ func (in *Route) DeepCopyInto(out *Route) {
 	if in.LoadBalancerPolicy != nil {
 		in, out := &in.LoadBalancerPolicy, &out.LoadBalancerPolicy
 		*out = new(LoadBalancerPolicy)
-		**out = **in
+		(*in).DeepCopyInto(*out)
 	}
 	if in.PathRewritePolicy != nil {
 		in, out := &in.PathRewritePolicy, &out.PathRewritePolicy
@@ -365,6 +921,23 @@ func (in *Route) DeepCopyInto(out *Route) {
 		*out = new(HeadersPolicy)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.CookieRewritePolicies != nil {
+		in, out := &in.CookieRewritePolicies, &out.CookieRewritePolicies
+		*out = make([]CookieRewritePolicy, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	if in.RateLimitPolicy != nil {
+		in, out := &in.RateLimitPolicy, &out.RateLimitPolicy
+		*out = new(RateLimitPolicy)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.RequestRedirectPolicy != nil {
+		in, out := &in.RequestRedirectPolicy, &out.RequestRedirectPolicy
+		*out = new(HTTPRequestRedirectPolicy)
+		(*in).DeepCopyInto(*out)
+	}
 	return
 }
 
@@ -401,6 +974,13 @@ func (in *Service) DeepCopyInto(out *Service) {
 		*out = new(HeadersPolicy)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.CookieRewritePolicies != nil {
+		in, out := &in.CookieRewritePolicies, &out.CookieRewritePolicies
+		*out = make([]CookieRewritePolicy, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
 	return
 }
 
@@ -415,17 +995,33 @@ func (in *Service) DeepCopy() *Service {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *Status) DeepCopyInto(out *Status) {
+func (in *SubCondition) DeepCopyInto(out *SubCondition) {
 	*out = *in
 	return
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status.
-func (in *Status) DeepCopy() *Status {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubCondition.
+func (in *SubCondition) DeepCopy() *SubCondition {
 	if in == nil {
 		return nil
 	}
-	out := new(Status)
+	out := new(SubCondition)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TCPHealthCheckPolicy) DeepCopyInto(out *TCPHealthCheckPolicy) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPHealthCheckPolicy.
+func (in *TCPHealthCheckPolicy) DeepCopy() *TCPHealthCheckPolicy {
+	if in == nil {
+		return nil
+	}
+	out := new(TCPHealthCheckPolicy)
 	in.DeepCopyInto(out)
 	return out
 }
@@ -436,7 +1032,7 @@ func (in *TCPProxy) DeepCopyInto(out *TCPProxy) {
 	if in.LoadBalancerPolicy != nil {
 		in, out := &in.LoadBalancerPolicy, &out.LoadBalancerPolicy
 		*out = new(LoadBalancerPolicy)
-		**out = **in
+		(*in).DeepCopyInto(*out)
 	}
 	if in.Services != nil {
 		in, out := &in.Services, &out.Services
@@ -450,6 +1046,16 @@ func (in *TCPProxy) DeepCopyInto(out *TCPProxy) {
 		*out = new(TCPProxyInclude)
 		**out = **in
 	}
+	if in.IncludesDeprecated != nil {
+		in, out := &in.IncludesDeprecated, &out.IncludesDeprecated
+		*out = new(TCPProxyInclude)
+		**out = **in
+	}
+	if in.HealthCheckPolicy != nil {
+		in, out := &in.HealthCheckPolicy, &out.HealthCheckPolicy
+		*out = new(TCPHealthCheckPolicy)
+		**out = **in
+	}
 	return
 }
 
@@ -482,6 +1088,11 @@ func (in *TCPProxyInclude) DeepCopy() *TCPProxyInclude {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *TLS) DeepCopyInto(out *TLS) {
 	*out = *in
+	if in.ClientValidation != nil {
+		in, out := &in.ClientValidation, &out.ClientValidation
+		*out = new(DownstreamValidation)
+		**out = **in
+	}
 	return
 }
 
@@ -533,7 +1144,22 @@ func (in *VirtualHost) DeepCopyInto(out *VirtualHost) {
 	if in.TLS != nil {
 		in, out := &in.TLS, &out.TLS
 		*out = new(TLS)
-		**out = **in
+		(*in).DeepCopyInto(*out)
+	}
+	if in.Authorization != nil {
+		in, out := &in.Authorization, &out.Authorization
+		*out = new(AuthorizationServer)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.CORSPolicy != nil {
+		in, out := &in.CORSPolicy, &out.CORSPolicy
+		*out = new(CORSPolicy)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.RateLimitPolicy != nil {
+		in, out := &in.RateLimitPolicy, &out.RateLimitPolicy
+		*out = new(RateLimitPolicy)
+		(*in).DeepCopyInto(*out)
 	}
 	return
 }
diff --git a/pkg/controller/scheduler_metrics.go b/pkg/controller/scheduler_metrics.go
index f15945e2..7524da8b 100644
--- a/pkg/controller/scheduler_metrics.go
+++ b/pkg/controller/scheduler_metrics.go
@@ -146,7 +146,7 @@ func (c *Controller) runBuiltinMetricChecks(canary *flaggerv1.Canary) bool {
 				}
 				return false
 			}
-			c.recorder.SetAnalysis(canary, metric.Name, val)
+
 			if metric.ThresholdRange != nil {
 				tr := *metric.ThresholdRange
 				if tr.Min != nil && val < *tr.Min {
@@ -177,7 +177,6 @@ func (c *Controller) runBuiltinMetricChecks(canary *flaggerv1.Canary) bool {
 				}
 				return false
 			}
-			c.recorder.SetAnalysis(canary, metric.Name, val.Seconds())
 			if metric.ThresholdRange != nil {
 				tr := *metric.ThresholdRange
 				if tr.Min != nil && val < time.Duration(*tr.Min)*time.Millisecond {
@@ -210,7 +209,6 @@ func (c *Controller) runBuiltinMetricChecks(canary *flaggerv1.Canary) bool {
 				}
 				return false
 			}
-			c.recorder.SetAnalysis(canary, metric.Name, val)
 			if metric.ThresholdRange != nil {
 				tr := *metric.ThresholdRange
 				if tr.Min != nil && val < *tr.Min {
@@ -285,8 +283,6 @@ func (c *Controller) runMetricChecks(canary *flaggerv1.Canary) bool {
 				return false
 			}
 
-			c.recorder.SetAnalysis(canary, metric.Name, val)
-
 			if metric.ThresholdRange != nil {
 				tr := *metric.ThresholdRange
 				if tr.Min != nil && val < *tr.Min {
diff --git a/pkg/metrics/recorder.go b/pkg/metrics/recorder.go
index d787f913..fea9e330 100644
--- a/pkg/metrics/recorder.go
+++ b/pkg/metrics/recorder.go
@@ -31,7 +31,6 @@ type Recorder struct {
 	total    *prometheus.GaugeVec
 	status   *prometheus.GaugeVec
 	weight   *prometheus.GaugeVec
-	analysis *prometheus.GaugeVec
 }
 
 // NewRecorder creates a new recorder and registers the Prometheus metrics
@@ -68,19 +67,12 @@ func NewRecorder(controller string, register bool) Recorder {
 		Help:      "The virtual service destination weight current value",
 	}, []string{"workload", "namespace"})
 
-	analysis := prometheus.NewGaugeVec(prometheus.GaugeOpts{
-		Subsystem: controller,
-		Name:      "canary_metric_analysis",
-		Help:      "Last canary analysis result per metric",
-	}, []string{"name", "namespace", "metric"})
-
 	if register {
 		prometheus.MustRegister(info)
 		prometheus.MustRegister(duration)
 		prometheus.MustRegister(total)
 		prometheus.MustRegister(status)
 		prometheus.MustRegister(weight)
-		prometheus.MustRegister(analysis)
 	}
 
 	return Recorder{
@@ -89,7 +81,6 @@ func NewRecorder(controller string, register bool) Recorder {
 		total:    total,
 		status:   status,
 		weight:   weight,
-		analysis: analysis,
 	}
 }
 
@@ -108,10 +99,6 @@ func (cr *Recorder) SetTotal(namespace string, total int) {
 	cr.total.WithLabelValues(namespace).Set(float64(total))
 }
 
-func (cr *Recorder) SetAnalysis(cd *flaggerv1.Canary, metricTemplateName string, val float64) {
-	cr.analysis.WithLabelValues(cd.Spec.TargetRef.Name, cd.Namespace, metricTemplateName).Set(val)
-}
-
 // SetStatus sets the last known canary analysis status
 func (cr *Recorder) SetStatus(cd *flaggerv1.Canary, phase flaggerv1.CanaryPhase) {
 	var status int
diff --git a/pkg/router/contour.go b/pkg/router/contour.go
index ef34ce08..9ca42a87 100644
--- a/pkg/router/contour.go
+++ b/pkg/router/contour.go
@@ -19,6 +19,7 @@ package router
 import (
 	"context"
 	"fmt"
+	"strings"
 
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
@@ -51,7 +52,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
 	newSpec := contourv1.HTTPProxySpec{
 		Routes: []contourv1.Route{
 			{
-				Conditions: []contourv1.Condition{
+				Conditions: []contourv1.MatchCondition{
 					{
 						Prefix: cr.makePrefix(canary),
 					},
@@ -62,7 +63,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
 					{
 						Name:   primaryName,
 						Port:   int(canary.Spec.Service.Port),
-						Weight: uint32(100),
+						Weight: int64(100),
 						RequestHeadersPolicy: &contourv1.HeadersPolicy{
 							Set: []contourv1.HeaderValue{
 								cr.makeLinkerdHeaderValue(canary, primaryName),
@@ -72,7 +73,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
 					{
 						Name:   canaryName,
 						Port:   int(canary.Spec.Service.Port),
-						Weight: uint32(0),
+						Weight: int64(0),
 						RequestHeadersPolicy: &contourv1.HeadersPolicy{
 							Set: []contourv1.HeaderValue{
 								cr.makeLinkerdHeaderValue(canary, canaryName),
@@ -95,7 +96,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
 						{
 							Name:   primaryName,
 							Port:   int(canary.Spec.Service.Port),
-							Weight: uint32(100),
+							Weight: int64(100),
 							RequestHeadersPolicy: &contourv1.HeadersPolicy{
 								Set: []contourv1.HeaderValue{
 									cr.makeLinkerdHeaderValue(canary, primaryName),
@@ -105,7 +106,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
 						{
 							Name:   canaryName,
 							Port:   int(canary.Spec.Service.Port),
-							Weight: uint32(0),
+							Weight: int64(0),
 							RequestHeadersPolicy: &contourv1.HeadersPolicy{
 								Set: []contourv1.HeaderValue{
 									cr.makeLinkerdHeaderValue(canary, canaryName),
@@ -115,7 +116,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
 					},
 				},
 				{
-					Conditions: []contourv1.Condition{
+					Conditions: []contourv1.MatchCondition{
 						{
 							Prefix: cr.makePrefix(canary),
 						},
@@ -126,7 +127,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
 						{
 							Name:   primaryName,
 							Port:   int(canary.Spec.Service.Port),
-							Weight: uint32(100),
+							Weight: int64(100),
 							RequestHeadersPolicy: &contourv1.HeadersPolicy{
 								Set: []contourv1.HeaderValue{
 									cr.makeLinkerdHeaderValue(canary, primaryName),
@@ -136,7 +137,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
 						{
 							Name:   canaryName,
 							Port:   int(canary.Spec.Service.Port),
-							Weight: uint32(0),
+							Weight: int64(0),
 							RequestHeadersPolicy: &contourv1.HeadersPolicy{
 								Set: []contourv1.HeaderValue{
 									cr.makeLinkerdHeaderValue(canary, canaryName),
@@ -177,7 +178,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
 				},
 			},
 			Spec: newSpec,
-			Status: contourv1.Status{
+			Status: contourv1.HTTPProxyStatus{
 				CurrentStatus: "valid",
 				Description:   "valid HTTPProxy",
 			},
@@ -274,7 +275,7 @@ func (cr *ContourRouter) SetRoutes(
 	proxy.Spec = contourv1.HTTPProxySpec{
 		Routes: []contourv1.Route{
 			{
-				Conditions: []contourv1.Condition{
+				Conditions: []contourv1.MatchCondition{
 					{
 						Prefix: cr.makePrefix(canary),
 					},
@@ -285,7 +286,7 @@ func (cr *ContourRouter) SetRoutes(
 					{
 						Name:   primaryName,
 						Port:   int(canary.Spec.Service.Port),
-						Weight: uint32(primaryWeight),
+						Weight: int64(primaryWeight),
 						RequestHeadersPolicy: &contourv1.HeadersPolicy{
 							Set: []contourv1.HeaderValue{
 								cr.makeLinkerdHeaderValue(canary, primaryName),
@@ -295,7 +296,7 @@ func (cr *ContourRouter) SetRoutes(
 					{
 						Name:   canaryName,
 						Port:   int(canary.Spec.Service.Port),
-						Weight: uint32(canaryWeight),
+						Weight: int64(canaryWeight),
 						RequestHeadersPolicy: &contourv1.HeadersPolicy{
 							Set: []contourv1.HeaderValue{
 								cr.makeLinkerdHeaderValue(canary, canaryName),
@@ -317,7 +318,7 @@ func (cr *ContourRouter) SetRoutes(
 						{
 							Name:   primaryName,
 							Port:   int(canary.Spec.Service.Port),
-							Weight: uint32(primaryWeight),
+							Weight: int64(primaryWeight),
 							RequestHeadersPolicy: &contourv1.HeadersPolicy{
 								Set: []contourv1.HeaderValue{
 									cr.makeLinkerdHeaderValue(canary, primaryName),
@@ -327,7 +328,7 @@ func (cr *ContourRouter) SetRoutes(
 						{
 							Name:   canaryName,
 							Port:   int(canary.Spec.Service.Port),
-							Weight: uint32(canaryWeight),
+							Weight: int64(canaryWeight),
 							RequestHeadersPolicy: &contourv1.HeadersPolicy{
 								Set: []contourv1.HeaderValue{
 									cr.makeLinkerdHeaderValue(canary, canaryName),
@@ -337,7 +338,7 @@ func (cr *ContourRouter) SetRoutes(
 					},
 				},
 				{
-					Conditions: []contourv1.Condition{
+					Conditions: []contourv1.MatchCondition{
 						{
 							Prefix: cr.makePrefix(canary),
 						},
@@ -348,7 +349,7 @@ func (cr *ContourRouter) SetRoutes(
 						{
 							Name:   primaryName,
 							Port:   int(canary.Spec.Service.Port),
-							Weight: uint32(100),
+							Weight: int64(100),
 							RequestHeadersPolicy: &contourv1.HeadersPolicy{
 								Set: []contourv1.HeaderValue{
 									cr.makeLinkerdHeaderValue(canary, primaryName),
@@ -358,7 +359,7 @@ func (cr *ContourRouter) SetRoutes(
 						{
 							Name:   canaryName,
 							Port:   int(canary.Spec.Service.Port),
-							Weight: uint32(0),
+							Weight: int64(0),
 							RequestHeadersPolicy: &contourv1.HeadersPolicy{
 								Set: []contourv1.HeaderValue{
 									cr.makeLinkerdHeaderValue(canary, canaryName),
@@ -390,36 +391,36 @@ func (cr *ContourRouter) makePrefix(canary *flaggerv1.Canary) string {
 	return prefix
 }
 
-func (cr *ContourRouter) makeConditions(canary *flaggerv1.Canary) []contourv1.Condition {
-	list := []contourv1.Condition{}
+func (cr *ContourRouter) makeConditions(canary *flaggerv1.Canary) []contourv1.MatchCondition {
+	list := []contourv1.MatchCondition{}
 
 	if len(canary.GetAnalysis().Match) > 0 {
 		for _, match := range canary.GetAnalysis().Match {
 			for s, stringMatch := range match.Headers {
-				h := &contourv1.HeaderCondition{
+				h := &contourv1.HeaderMatchCondition{
 					Name:  s,
 					Exact: stringMatch.Exact,
 				}
 				if stringMatch.Suffix != "" {
-					h = &contourv1.HeaderCondition{
+					h = &contourv1.HeaderMatchCondition{
 						Name:     s,
 						Contains: stringMatch.Suffix,
 					}
 				}
 				if stringMatch.Prefix != "" {
-					h = &contourv1.HeaderCondition{
+					h = &contourv1.HeaderMatchCondition{
 						Name:     s,
 						Contains: stringMatch.Prefix,
 					}
 				}
-				list = append(list, contourv1.Condition{
+				list = append(list, contourv1.MatchCondition{
 					Prefix: cr.makePrefix(canary),
 					Header: h,
 				})
 			}
 		}
 	} else {
-		list = []contourv1.Condition{
+		list = []contourv1.MatchCondition{
 			{
 				Prefix: cr.makePrefix(canary),
 			},
@@ -442,13 +443,24 @@ func (cr *ContourRouter) makeTimeoutPolicy(canary *flaggerv1.Canary) *contourv1.
 func (cr *ContourRouter) makeRetryPolicy(canary *flaggerv1.Canary) *contourv1.RetryPolicy {
 	if canary.Spec.Service.Retries != nil {
 		return &contourv1.RetryPolicy{
-			NumRetries:    uint32(canary.Spec.Service.Retries.Attempts),
+			NumRetries:    int64(canary.Spec.Service.Retries.Attempts),
 			PerTryTimeout: canary.Spec.Service.Retries.PerTryTimeout,
+			RetryOn:       makeRetryOn(canary.Spec.Service.Retries.RetryOn),
 		}
 	}
 	return nil
 }
 
+func makeRetryOn(retryOnString string) []contourv1.RetryOn {
+	retryOnSplit := strings.Split(retryOnString, ",")
+
+	retryOn := make([]contourv1.RetryOn, len(retryOnSplit))
+	for i, v := range retryOnSplit {
+		retryOn[i] = contourv1.RetryOn(v)
+	}
+	return retryOn
+}
+
 func (cr *ContourRouter) makeLinkerdHeaderValue(canary *flaggerv1.Canary, serviceName string) contourv1.HeaderValue {
 	return contourv1.HeaderValue{
 		Name:  "l5d-dst-override",


Test patch:
diff --git a/pkg/router/contour_test.go b/pkg/router/contour_test.go
index c54f9c52..71221141 100644
--- a/pkg/router/contour_test.go
+++ b/pkg/router/contour_test.go
@@ -20,6 +20,8 @@ import (
 	"context"
 	"testing"
 
+	contourv1 "github.com/fluxcd/flagger/pkg/apis/projectcontour/v1"
+
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
@@ -46,8 +48,8 @@ func TestContourRouter_Reconcile(t *testing.T) {
 
 	services := proxy.Spec.Routes[0].Services
 	require.Len(t, services, 2)
-	assert.Equal(t, uint32(100), services[0].Weight)
-	assert.Equal(t, uint32(0), services[1].Weight)
+	assert.Equal(t, int64(100), services[0].Weight)
+	assert.Equal(t, int64(0), services[1].Weight)
 	assert.Equal(t, "contour", proxy.Annotations["projectcontour.io/ingress.class"])
 
 	// test update
@@ -69,7 +71,8 @@ func TestContourRouter_Reconcile(t *testing.T) {
 	assert.Equal(t, 8080, proxy.Spec.Routes[0].Services[0].Port)
 	assert.Equal(t, "1m", proxy.Spec.Routes[0].TimeoutPolicy.Response)
 	assert.Equal(t, "/podinfo", proxy.Spec.Routes[0].Conditions[0].Prefix)
-	assert.Equal(t, uint32(10), proxy.Spec.Routes[0].RetryPolicy.NumRetries)
+	assert.Equal(t, int64(10), proxy.Spec.Routes[0].RetryPolicy.NumRetries)
+	assert.Equal(t, []contourv1.RetryOn{"connect-failure", "gateway-error"}, proxy.Spec.Routes[0].RetryPolicy.RetryOn)
 
 	// test headers update
 	cd, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
@@ -111,7 +114,7 @@ func TestContourRouter_Routes(t *testing.T) {
 	require.NoError(t, err)
 
 	primary := proxy.Spec.Routes[0].Services[0]
-	assert.Equal(t, uint32(50), primary.Weight)
+	assert.Equal(t, int64(50), primary.Weight)
 
 	cd, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
 	require.NoError(t, err)
@@ -135,10 +138,10 @@ func TestContourRouter_Routes(t *testing.T) {
 	require.NoError(t, err)
 
 	primary = proxy.Spec.Routes[0].Services[0]
-	assert.Equal(t, uint32(100), primary.Weight)
+	assert.Equal(t, int64(100), primary.Weight)
 
 	primary = proxy.Spec.Routes[1].Services[0]
-	assert.Equal(t, uint32(100), primary.Weight)
+	assert.Equal(t, int64(100), primary.Weight)
 
 	// test set routers for A/B
 	err = router.SetRoutes(canary, 0, 100, false)
@@ -148,8 +151,8 @@ func TestContourRouter_Routes(t *testing.T) {
 	require.NoError(t, err)
 
 	primary = proxy.Spec.Routes[0].Services[0]
-	assert.Equal(t, uint32(0), primary.Weight)
+	assert.Equal(t, int64(0), primary.Weight)
 
 	primary = proxy.Spec.Routes[1].Services[0]
-	assert.Equal(t, uint32(100), primary.Weight)
+	assert.Equal(t, int64(100), primary.Weight)
 }
diff --git a/pkg/router/router_test.go b/pkg/router/router_test.go
index 917282bf..e077e008 100644
--- a/pkg/router/router_test.go
+++ b/pkg/router/router_test.go
@@ -132,6 +132,7 @@ func newTestCanary() *flaggerv1.Canary {
 				Retries: &istiov1alpha3.HTTPRetry{
 					Attempts:      10,
 					PerTryTimeout: "30s",
+					RetryOn:       "connect-failure,gateway-error",
 				},
 				Gateways: []string{
 					"public-gateway.istio",