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

Fix Listener Filter bugs and cover more Envoy Listener Filter Types #1442

Merged
merged 11 commits into from
Aug 30, 2022
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
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