Skip to content

Commit

Permalink
Fix Listener Filter bugs and cover more Envoy Listener Filter Types (#…
Browse files Browse the repository at this point in the history
…1442)

* Extend timeout for port-forward
* Resolve merge conflict on config fixture
* Resolve merge conflicts on how listeners are displayed
* Fix linting issue
* Finish Ratelimit config
* Unescape >
* Add Changelog
* Fix unit tests
* Fix acceptance tests
* Remove the named return parameter
Co-authored-by: Ashwin Venkatesh <ashwin@hashicorp.com>
  • Loading branch information
Thomas Eckert committed Aug 30, 2022
1 parent f08238f commit 05c1266
Show file tree
Hide file tree
Showing 8 changed files with 516 additions and 51 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,14 @@ IMPROVEMENTS:
* Display clusters by their short names rather than FQDNs for the `proxy read` command. [[GH-1412](https://github.com/hashicorp/consul-k8s/pull/1412)]
* Display a message when `proxy list` returns no results. [[GH-1412](https://github.com/hashicorp/consul-k8s/pull/1412)]
* Display a warning when a user passes a field and table filter combination to `proxy read` where the given field is not present in any of the output tables. [[GH-1412](https://github.com/hashicorp/consul-k8s/pull/1412)]
* Extend the timeout for `consul-k8s proxy read` to establish a connection from 5s to 10s.
* Expand the set of Envoy Listener Filters that may be parsed and output to the Listeners table.

BUG FIXES:
* Helm
* API Gateway: Configure ACL auth for controller correctly when deployed in secondary datacenter with federation enabled [[GH-1462](https://github.com/hashicorp/consul-k8s/pull/1462)]
* CLI
* Fix issue where SNI filters for Terminating Gateways showed up as blank lines.

## 0.47.1 (August 12, 2022)

Expand Down
2 changes: 1 addition & 1 deletion acceptance/tests/connect/connect_inject_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func TestConnectInject(t *testing.T) {
// Static Client must have Static Server as a cluster and endpoint.
if strings.Contains(podName, "static-client") {
require.Regexp(r, "static-server.*static-server\\.default\\.dc1\\.internal.*EDS", output)
require.Regexp(r, ipv4RegEx+".*static-server.default.dc1.internal", output)
require.Regexp(r, ipv4RegEx+".*static-server", output)
}

}
Expand Down
7 changes: 5 additions & 2 deletions cli/cmd/proxy/read/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,12 +413,15 @@ func (c *ReadCommand) outputJSON(configs map[string]*EnvoyConfig) error {
cfgs[name] = cfg
}

out, err := json.MarshalIndent(cfgs, "", "\t")
escaped, err := json.MarshalIndent(cfgs, "", "\t")
if err != nil {
return err
}

c.UI.Output(string(out))
// Unescape `>` the cheap way.
out := strings.ReplaceAll(string(escaped), "\\u003e", ">")

c.UI.Output(out)

return nil
}
Expand Down
8 changes: 4 additions & 4 deletions cli/cmd/proxy/read/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ func TestReadCommandOutput(t *testing.T) {

"-listeners": {"==> Listeners \\(2\\)",
"Name.*Address:Port.*Direction.*Filter Chain Match.*Filters.*Last Updated",
"public_listener.*192\\.168\\.69\\.179:20000.*INBOUND.*Any.*\\* to local_app/",
"outbound_listener.*127.0.0.1:15001.*OUTBOUND.*10\\.100\\.134\\.173/32, 240\\.0\\.0\\.3/32.*to client.default.dc1.internal.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00.consul",
"10\\.100\\.31\\.2/32, 240\\.0\\.0\\.5/32.*to frontend\\.default\\.dc1\\.internal\\.bc3815c2-1a0f-f3ff-a2e9-20d791f08d00\\.consul",
"Any.*to original-destination"},
"public_listener.*192\\.168\\.69\\.179:20000.*INBOUND.*Any.*\\* -> local_app/",
"outbound_listener.*127.0.0.1:15001.*OUTBOUND.*10\\.100\\.134\\.173/32, 240\\.0\\.0\\.3/32.*TCP: -> client",
"10\\.100\\.31\\.2/32, 240\\.0\\.0\\.5/32.*TCP: -> frontend",
"Any.*TCP: -> original-destination"},

"-routes": {"==> Routes \\(1\\)",
"Name.*Destination Cluster.*Last Updated",
Expand Down
155 changes: 133 additions & 22 deletions cli/cmd/proxy/read/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ func parseListeners(rawCfg map[string]interface{}) ([]Listener, error) {

filterChain = append(filterChain, FilterChain{
FilterChainMatch: strings.Join(filterChainMatch, ", "),
Filters: formatFilters(chain),
Filters: formatFilters(chain.Filters),
})
}

Expand Down Expand Up @@ -396,45 +396,156 @@ func parseSecrets(rawCfg map[string]interface{}) ([]Secret, error) {
return secrets, nil
}

func formatFilters(filterChain filterChain) (filters []string) {
func formatFilters(filters []filter) []string {
formatted := []string{}

// Filters can have many custom configurations, each must be handled differently.
formatters := map[string]func(typedConfig) string{
// [List of known extensions](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/listener/v3/listener_components.proto).
formatters := map[string]func(filter) string{
"type.googleapis.com/envoy.extensions.filters.network.connection_limit.v3.ConnectionLimit": formatFilterConnectionLimit,
"type.googleapis.com/envoy.extensions.filters.network.direct_response.v3.Config": formatFilterDirectResponse,
"type.googleapis.com/envoy.extensions.filters.network.echo.v3.Echo": formatFilterEcho,
"type.googleapis.com/envoy.extensions.filters.network.ext_authz.v3.ExtAuthz": formatFilterExtAuthz,
"type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager": formatFilterHTTPConnectionManager,
"type.googleapis.com/envoy.extensions.filters.network.local_ratelimit.v3.LocalRateLimit": formatFilterLocalRatelimit,
"type.googleapis.com/envoy.extensions.filters.network.ratelimit.v3.RateLimit": formatFilterRatelimit,
"type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC": formatFilterRBAC,
"type.googleapis.com/envoy.extensions.filters.network.sni_cluster.v3.SniCluster": formatFilterSniCluster,
"type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy": formatFilterTCPProxy,
"type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager": formatFilterHTTPConnectionManager,
}

for _, chainFilter := range filterChain.Filters {
if formatter, ok := formatters[chainFilter.TypedConfig.Type]; ok {
filters = append(filters, formatter(chainFilter.TypedConfig))
for _, filter := range filters {
if formatter, ok := formatters[filter.TypedConfig.Type]; ok {
formatted = append(formatted, formatter(filter))
} else {
formatted = append(formatted, fmt.Sprintf("Unknown filter: %s", filter.TypedConfig.Type))
}
}
return
return formatted
}

func formatFilterTCPProxy(config typedConfig) (filter string) {
return "to " + config.Cluster
func formatFilterConnectionLimit(config filter) string {
return fmt.Sprintf("Connection limit: %d max connections with %s delay", config.TypedConfig.MaxConnections, config.TypedConfig.Delay)
}

func formatFilterRBAC(cfg typedConfig) (filter string) {
action := cfg.Rules.Action
for _, principal := range cfg.Rules.Policies.ConsulIntentions.Principals {
regex := principal.Authenticated.PrincipalName.SafeRegex.Regex
filter += fmt.Sprintf("%s %s", action, regex)
func formatFilterDirectResponse(config filter) string {
out := []string{"Direct response: ->"}
if file := config.TypedConfig.Response.Filename; file != "" {
out = append(out, fmt.Sprintf("file:%s", file))
}
if inlineBytes := config.TypedConfig.Response.InlineBytes; len(inlineBytes) != 0 {
if len(inlineBytes) > 24 {
out = append(out, fmt.Sprintf("bytes:%s...", string(inlineBytes)[:24]))
} else {
out = append(out, fmt.Sprintf("bytes:%s", string(inlineBytes)))
}
}
if inlineString := config.TypedConfig.Response.InlineString; inlineString != "" {
if len(inlineString) > 24 {
out = append(out, fmt.Sprintf("string:%s...", inlineString[:24]))
} else {
out = append(out, fmt.Sprintf("string:%s", inlineString))
}
}
if envVar := config.TypedConfig.Response.EnvironmentVariable; envVar != "" {
out = append(out, fmt.Sprintf("env:%s", envVar))
}

return strings.Join(out, " ")
}

func formatFilterEcho(config filter) string {
return "Echo: upstream will respond with the data it receives."
}

func formatFilterExtAuthz(config filter) string {
var upstream string
if config.TypedConfig.GrpcService.EnvoyGrpc.ClusterName != "" {
upstream = config.TypedConfig.GrpcService.EnvoyGrpc.ClusterName
} else if config.TypedConfig.GrpcService.GoogleGrpc.TargetUri != "" {
upstream = config.TypedConfig.GrpcService.GoogleGrpc.TargetUri
} else {
upstream = "No upstream configured."
}
return

return fmt.Sprintf("External authorization: %s", upstream)
}

func formatFilterHTTPConnectionManager(cfg typedConfig) (filter string) {
for _, host := range cfg.RouteConfig.VirtualHosts {
filter += strings.Join(host.Domains, ", ")
filter += " to "
func formatFilterHTTPConnectionManager(config filter) string {
out := "HTTP: "
for _, host := range config.TypedConfig.RouteConfig.VirtualHosts {
out += strings.Join(host.Domains, ", ")
out += " -> "

routes := ""
for _, route := range host.Routes {
routes += fmt.Sprintf("%s%s", route.Route.Cluster, route.Match.Prefix)
}
filter += routes
out += routes
}
return out
}

func formatFilterLocalRatelimit(config filter) string {
return fmt.Sprintf("Local rate limit: tokens: max %d per-fill %d, interval: %s",
config.TypedConfig.TokenBucket.MaxTokens,
config.TypedConfig.TokenBucket.TokensPerFill,
config.TypedConfig.TokenBucket.FillInterval)
}

func formatFilterRatelimit(config filter) string {
out := "Rate limit: "

if config.TypedConfig.Domain != "" {
out += config.TypedConfig.Domain + " "
}
return

// Rate limit using descriptors.
if len(config.TypedConfig.Descriptors) != 0 {
for _, descriptor := range config.TypedConfig.Descriptors {
for _, entry := range descriptor.Entries {
out += fmt.Sprintf("%s:%s ", entry.Key, entry.Value)
}
out += fmt.Sprintf("%d req per %s", descriptor.Limit.RequestsPerUnit, strings.ToLower(descriptor.Limit.Unit))
}
}

// Rate limit using an external Envoy gRPC service.
if config.TypedConfig.RateLimitService.GrpcService.EnvoyGrpc.ClusterName != "" {
out += fmt.Sprintf("using %s ", config.TypedConfig.RateLimitService.GrpcService.EnvoyGrpc.ClusterName)
}

// Rate limit using an external Google gRPC service.
if config.TypedConfig.RateLimitService.GrpcService.GoogleGrpc.TargetUri != "" {
out += fmt.Sprintf("using %s ", config.TypedConfig.RateLimitService.GrpcService.GoogleGrpc.TargetUri)
}

// Notify the user that failure to reach the rate limiting service will deny the caller.
if config.TypedConfig.FailureModeDeny {
out += "will deny if unreachable"
}

return strings.Trim(out, " ")
}

func formatFilterRBAC(config filter) string {
out := "RBAC: "
action := config.TypedConfig.Rules.Action
for _, principal := range config.TypedConfig.Rules.Policies.ConsulIntentions.Principals {
regex := principal.Authenticated.PrincipalName.SafeRegex.Regex
out += fmt.Sprintf("%s %s", action, regex)
}
return out
}

func formatFilterSniCluster(config filter) string {
return "SNI: Upstream cluster name set by SNI field in TLS connection."
}

func formatFilterTCPProxy(config filter) string {
if config.TypedConfig.Cluster == "" {
return "TCP: No upstream cluster configured."
}

return "TCP: -> " + strings.Split(config.TypedConfig.Cluster, ".")[0]
}
Loading

0 comments on commit 05c1266

Please sign in to comment.