Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v4 fixes for 5702 and 5714 #5726

Merged
merged 13 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ it will be removed; but as it won't be user-visible this isn't considered a brea
### Emissary-ingress and Ambassador Edge Stack

- Feature: This upgrades Emissary-ingress to be built on Envoy v1.28.0 which provides security,
performance and feature enhancements. You can read more about them here: <a
performance and feature enhancements. You can read more about them here: <a
href="https://www.envoyproxy.io/docs/envoy/v1.28.0/version_history/version_history">Envoy Proxy
1.28.0 Release Notes</a>

Expand All @@ -103,37 +103,42 @@ it will be removed; but as it won't be user-visible this isn't considered a brea
- Change: Upgraded Emissary-ingress to the latest release of Golang as part of our general
dependency upgrade process.

- Bugfix: Emissary-ingress was incorrectly caching Mappings with regex headers using the header name
instead of the Mapping name, which could reduce the cache's effectiveness. This has been fixed so
that the correct key is used. ([Incorrect Cache Key for Mapping])

[Incorrect Cache Key for Mapping]: https://github.com/emissary-ingress/emissary/issues/5714

## [3.9.0] November 13, 2023
[3.9.0]: https://github.com/emissary-ingress/emissary/compare/v3.8.0...v3.9.0

### Emissary-ingress and Ambassador Edge Stack

- Feature: This upgrades Emissary-ingress to be built on Envoy v1.27.2 which provides security,
performance and feature enhancements. You can read more about them here: <a
performance and feature enhancements. You can read more about them here: <a
href="https://www.envoyproxy.io/docs/envoy/v1.27.2/version_history/version_history">Envoy Proxy
1.27.2 Release Notes</a>

- Feature: By default, Emissary-ingress will return an `UNAVAILABLE` code when a request using gRPC
- Feature: By default, Emissary-ingress will return an `UNAVAILABLE` code when a request using gRPC
is rate limited. The `RateLimitService` resource now exposes a new
`grpc.use_resource_exhausted_code` field that when set to `true`, Emissary-ingress will return a
`RESOURCE_EXHAUSTED` gRPC code instead. Thanks to <a href="https://github.com/jeromefroe">Jerome
`grpc.use_resource_exhausted_code` field that when set to `true`, Emissary-ingress will return a
`RESOURCE_EXHAUSTED` gRPC code instead. Thanks to <a href="https://github.com/jeromefroe">Jerome
Froelich</a> for contributing this feature!

- Feature: Envoy runtime fields that were provided to mitigate the recent HTTP/2 rapid reset
vulnerability can now be configured via the Module resource so the configuration will persist
between restarts. This configuration is added to the Envoy bootstrap config, so restarting
Emissary is necessary after changing these fields for the configuration to take effect.
vulnerability can now be configured via the Module resource so the configuration will persist
between restarts. This configuration is added to the Envoy bootstrap config, so restarting
Emissary is necessary after changing these fields for the configuration to take effect.

- Change: APIExt would previously allow for TLS 1.0 connections. We have updated it to now only use
a minimum TLS version of 1.3 to resolve security concerns.
a minimum TLS version of 1.3 to resolve security concerns.

- Change: - Update default image to Emissary-ingress v3.9.0. <br/>

- Bugfix: The APIExt server provides CRD conversion between the stored version v2 and the version
watched for by Emissary-ingress v3alpha1. Since this component is required to operate
Emissary-ingress, we have introduced an init container that will ensure it is available before
starting. This will help address some of the intermittent issues seen during install and
upgrades.
watched for by Emissary-ingress v3alpha1. Since this component is required to operate
Emissary-ingress, we have introduced an init container that will ensure it is available before
starting. This will help address some of the intermittent issues seen during install and upgrades.

## [3.8.0] August 29, 2023
[3.8.0]: https://github.com/emissary-ingress/emissary/compare/v3.7.2...v3.8.0
Expand Down
177 changes: 177 additions & 0 deletions cmd/entrypoint/irtype.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package entrypoint

import (
"encoding/json"
"fmt"
)

type IRResource struct {
Active bool `json:"_active"`
CacheKey string `json:"_cache_key,omitempty"`
Errored bool `json:"_errored"`
ReferencedBy []string `json:"_referenced_by,omitempty"`
RKey string `json:"_rkey,omitempty"`
Location string `json:"location,omitempty"`
Kind string `json:"kind"`
Name string `json:"name"`
Namespace string `json:"namespace,omitempty"`
}

type IRClusterHealthCheck struct {
IRResource
}

type IRClusterTarget struct {
IP string `json:"ip"`
Port int `json:"port"`
TargetKind string `json:"target_kind"`
}

type IRCluster struct {
IRResource
BarHostname string `json:"_hostname"` // Why this _and_ hostname?
BarNamespace string `json:"_namespace"` // Why this _and_ namespace?
Copy link
Member

Choose a reason for hiding this comment

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

I don't like this but I get why it's here for now

Copy link
Member Author

Choose a reason for hiding this comment

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

You and me both. I'll figure out what up with this soon.

Port int `json:"_port"`
Resolver string `json:"_resolver"`
ConnectTimeoutMs int `json:"connect_timeout_ms"`
EnableEndpoints bool `json:"enable_endpoints"`
EnableIPv4 bool `json:"enable_ipv4"`
EnableIPv6 bool `json:"enable_ipv6"`
EnvoyName string `json:"envoy_name"`
HealthChecks IRClusterHealthCheck `json:"health_checks,omitempty"`
IgnoreCluster bool `json:"ignore_cluster"`
LBType string `json:"lb_type"`
RespectDNSTTL bool `json:"respect_dns_ttl"`
Service string `json:"service"`
StatsName string `json:"stats_name"`
Targets []IRClusterTarget `json:"targets"`
Type string `json:"type"`
URLs []string `json:"urls"`
}

type IRQueryParameter struct {
Name string `json:"name,omitempty"`
Value string `json:"value,omitempty"`
Regex bool `json:"regex,omitempty"`
}

type IRRegexRewrite struct {
Pattern string `json:"pattern,omitempty"`
Substitution string `json:"substitution,omitempty"`
}

// Route weights are really annoying: in Python they're a
// List[Union[str, int]], which is a pain to represent in Go.

type IRRouteWeightElement struct {
Int int
Str string
}

type IRRouteWeight []IRRouteWeightElement

func (rw IRRouteWeight) MarshalJSON() ([]byte, error) {
arr := make([]interface{}, len(rw))

for i, elem := range rw {
if elem.Str != "" {
arr[i] = elem.Str
} else {
arr[i] = elem.Int
}
}

return json.Marshal(arr)
}

func (rw *IRRouteWeight) UnmarshalJSON(data []byte) error {
var arr []interface{}

if err := json.Unmarshal(data, &arr); err != nil {
return err
}

*rw = make([]IRRouteWeightElement, len(arr))

for i, elem := range arr {
switch v := elem.(type) {
case string:
(*rw)[i] = IRRouteWeightElement{Str: v}
case float64:
(*rw)[i] = IRRouteWeightElement{Int: int(v)}
default:
return fmt.Errorf("unexpected type in IRRouteWeight: %T", elem)
}
}

return nil
}

type IRMapping struct {
IRResource
// CumulativeWeight is the _computed_ cumulative weight assigned to a
// Mapping within its groups; Weight is the value specified in the
// Mapping's spec.weight (it's a pointer because that value is optional).
//
// If you have mappings in a Group with weights 10, 40, and unset, the
// Weights will be pointer-to-10, pointer-to-40, and nil, and the
// CumulativeWeights will be 10, 50, and 100.
CumulativeWeight int `json:"_weight"`
Weight *int `json:"weight"`
Cluster IRCluster `json:"cluster"`
ClusterKey string `json:"cluster_key"`
DefaultClass string `json:"default_class"`
GroupID string `json:"group_id"`
Headers []IRHeader `json:"headers"`
Host string `json:"host"`
Precedence int `json:"precedence"`
Prefix string `json:"prefix"`
QueryParameters []IRQueryParameter `json:"query_parameters,omitempty"`
RegexRewrite IRRegexRewrite `json:"regex_rewrite,omitempty"`
Resolver string `json:"resolver"`
Rewrite string `json:"rewrite"`
RouteWeight IRRouteWeight `json:"route_weight"`
Service string `json:"service"`
TimeoutMS int `json:"timeout_ms"`
}

type IRRequestPolicy struct {
Action string `json:"action"`
}

type IRHost struct {
IRResource
Hostname string `json:"hostname"`
InsecureAction string `json:"insecure_action"`
RequestPolicy map[string]IRRequestPolicy `json:"requestPolicy"` // Yes, really.
SecureAction string `json:"secure_action"`
SNI string `json:"sni"`
}

type IRHeader struct {
Name string `json:"name"`
Regex bool `json:"regex"`
Value string `json:"value"`
}

type IRGroup struct {
IRResource
DefaultClass string `json:"default_class"`
GroupID string `json:"group_id"`
GroupWeight IRRouteWeight `json:"group_weight"`
Headers []IRHeader `json:"headers"`
Host string `json:"host"`
Mappings []IRMapping `json:"mappings"`
Precedence int `json:"precedence"`
Prefix string `json:"prefix"`
QueryParameters []IRQueryParameter `json:"query_parameters"`
RegexRewrite IRRegexRewrite `json:"regex_rewrite"`
Rewrite string `json:"rewrite"`
TimeoutMS int `json:"timeout_ms"`
}

type IR struct {
Clusters map[string]IRCluster `json:"clusters"`
Groups []IRGroup `json:"groups"`
Hosts []IRHost `json:"hosts"`
}
153 changes: 153 additions & 0 deletions cmd/entrypoint/irtype_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package entrypoint_test

import (
"encoding/json"
"testing"

"github.com/emissary-ingress/emissary/v3/cmd/entrypoint"
"github.com/stretchr/testify/assert"
)

func TestIRRouteWeight(t *testing.T) {
rw := entrypoint.IRRouteWeight{
{Int: 1},
{Str: "foo"},
{Int: 2},
}

// MarshalJSON
expectedJSON := `[1,"foo",2]`
actualJSON, err := json.Marshal(rw)
assert.Nil(t, err)
assert.Equal(t, expectedJSON, string(actualJSON))

var check entrypoint.IRRouteWeight
err = json.Unmarshal(actualJSON, &check)
assert.Nil(t, err)
assert.Equal(t, rw, check)

// UnmarshalJSON
jsonData := []byte(`[1,"bar",3]`)
expectedRW := entrypoint.IRRouteWeight{
{Int: 1},
{Str: "bar"},
{Int: 3},
}
var actualRW entrypoint.IRRouteWeight
err = json.Unmarshal(jsonData, &actualRW)
assert.Nil(t, err)
assert.Equal(t, expectedRW, actualRW)
}

func TestIRCluster(t *testing.T) {
clusterJSON := `{
"_active": true,
"_cache_key": "Cluster-cluster_127_0_0_1_8877_default",
"_errored": false,
"_hostname": "127.0.0.1",
"_namespace": "default",
"_port": 8877,
"_referenced_by": [
"--internal--"
],
"_resolver": "kubernetes-service",
"_rkey": "cluster_127_0_0_1_8877_default",
"connect_timeout_ms": 3000,
"enable_endpoints": false,
"enable_ipv4": true,
"enable_ipv6": false,
"envoy_name": "cluster_127_0_0_1_8877_default",
"health_checks": {
"_active": true,
"_errored": false,
"_rkey": "ir.health_checks",
"kind": "IRHealthChecks",
"location": "--internal--",
"name": "health_checks",
"namespace": "default"
},
"ignore_cluster": false,
"kind": "IRCluster",
"lb_type": "round_robin",
"location": "--internal--",
"name": "cluster_127_0_0_1_8877_default",
"namespace": "default",
"respect_dns_ttl": false,
"service": "127.0.0.1:8877",
"stats_name": "127_0_0_1_8877",
"targets": [
{
"ip": "127.0.0.1",
"port": 8877,
"target_kind": "IPaddr"
}
],
"type": "strict_dns",
"urls": [
"tcp://127.0.0.1:8877"
]
}`

expectedCluster := entrypoint.IRCluster{
IRResource: entrypoint.IRResource{
Active: true,
CacheKey: "Cluster-cluster_127_0_0_1_8877_default",
Errored: false,
ReferencedBy: []string{"--internal--"},
RKey: "cluster_127_0_0_1_8877_default",
Location: "--internal--",
Kind: "IRCluster",
Name: "cluster_127_0_0_1_8877_default",
Namespace: "default",
},
BarHostname: "127.0.0.1",
BarNamespace: "default",
Port: 8877,
Resolver: "kubernetes-service",
ConnectTimeoutMs: 3000,
EnableEndpoints: false,
EnableIPv4: true,
EnableIPv6: false,
EnvoyName: "cluster_127_0_0_1_8877_default",
HealthChecks: entrypoint.IRClusterHealthCheck{
IRResource: entrypoint.IRResource{
Active: true,
Errored: false,
RKey: "ir.health_checks",
Kind: "IRHealthChecks",
Location: "--internal--",
Name: "health_checks",
Namespace: "default",
},
},
IgnoreCluster: false,
LBType: "round_robin",
RespectDNSTTL: false,
Service: "127.0.0.1:8877",
StatsName: "127_0_0_1_8877",
Targets: []entrypoint.IRClusterTarget{
{
IP: "127.0.0.1",
Port: 8877,
TargetKind: "IPaddr",
},
},
Type: "strict_dns",
URLs: []string{
"tcp://127.0.0.1:8877",
},
}

var unmarshaledCluster entrypoint.IRCluster
err := json.Unmarshal([]byte(clusterJSON), &unmarshaledCluster)
assert.Nil(t, err)
assert.Equal(t, expectedCluster, unmarshaledCluster)

remarshaledJSON, err := json.Marshal(unmarshaledCluster)
assert.Nil(t, err)

var unmarshaledCluster2 entrypoint.IRCluster
err = json.Unmarshal(remarshaledJSON, &unmarshaledCluster2)
assert.Nil(t, err)
assert.Equal(t, expectedCluster, unmarshaledCluster2)
}
Loading
Loading