mirror of https://github.com/hashicorp/consul
164 lines
5.1 KiB
Go
164 lines
5.1 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package extauthz
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
|
"github.com/mitchellh/mapstructure"
|
|
|
|
"github.com/hashicorp/consul/api"
|
|
ext_cmn "github.com/hashicorp/consul/envoyextensions/extensioncommon"
|
|
"github.com/hashicorp/go-multierror"
|
|
)
|
|
|
|
type extAuthz struct {
|
|
ext_cmn.BasicExtensionAdapter
|
|
|
|
// ProxyType identifies the type of Envoy proxy that this extension applies to.
|
|
// The extension will only be configured for proxies that match this type and
|
|
// will be ignored for all other proxy types.
|
|
ProxyType api.ServiceKind
|
|
// InsertOptions controls how the extension inserts the filter.
|
|
InsertOptions ext_cmn.InsertOptions
|
|
// ListenerType controls which listener the extension applies to. It supports "inbound" or "outbound" listeners.
|
|
ListenerType string
|
|
// Config holds the extension configuration.
|
|
Config extAuthzConfig
|
|
}
|
|
|
|
var _ ext_cmn.BasicExtension = (*extAuthz)(nil)
|
|
|
|
func Constructor(ext api.EnvoyExtension) (ext_cmn.EnvoyExtender, error) {
|
|
auth, err := newExtAuthz(ext)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ext_cmn.BasicEnvoyExtender{
|
|
Extension: auth,
|
|
}, nil
|
|
}
|
|
|
|
// CanApply indicates if the ext-authz extension can be applied to the given extension runtime configuration.
|
|
func (a *extAuthz) CanApply(config *ext_cmn.RuntimeConfig) bool {
|
|
return config.Kind == api.ServiceKindConnectProxy
|
|
}
|
|
|
|
// PatchClusters modifies the cluster resources for the ext-authz extension.
|
|
//
|
|
// If the extension is configured to target an ext-authz service running on the local host network
|
|
// this func will insert a cluster for calling that service. It does nothing if the extension is
|
|
// configured to target an upstream service because the existing cluster for the upstream will be
|
|
// used directly by the filter.
|
|
func (a *extAuthz) PatchClusters(cfg *ext_cmn.RuntimeConfig, c ext_cmn.ClusterMap) (ext_cmn.ClusterMap, error) {
|
|
cluster, err := a.Config.toEnvoyCluster(cfg)
|
|
if err != nil {
|
|
return c, err
|
|
}
|
|
if cluster != nil {
|
|
c[cluster.Name] = cluster
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func (a *extAuthz) matchesListenerDirection(isInboundListener bool) bool {
|
|
return (!isInboundListener && a.ListenerType == "outbound") || (isInboundListener && a.ListenerType == "inbound")
|
|
}
|
|
|
|
// PatchFilters inserts an ext-authz filter into the list of network filters or the filter chain of the HTTP connection manager.
|
|
func (a *extAuthz) PatchFilters(cfg *ext_cmn.RuntimeConfig, filters []*envoy_listener_v3.Filter, isInboundListener bool) ([]*envoy_listener_v3.Filter, error) {
|
|
// The ext_authz extension only patches filters for inbound listeners.
|
|
if !a.matchesListenerDirection(isInboundListener) {
|
|
return filters, nil
|
|
}
|
|
|
|
a.configureInsertOptions(cfg.Protocol)
|
|
|
|
switch cfg.Protocol {
|
|
case "grpc", "http2", "http":
|
|
extAuthzFilter, err := a.Config.toEnvoyHttpFilter(cfg)
|
|
if err != nil {
|
|
return filters, err
|
|
}
|
|
return ext_cmn.InsertHTTPFilter(filters, extAuthzFilter, a.InsertOptions)
|
|
case "tcp":
|
|
fallthrough
|
|
default:
|
|
extAuthzFilter, err := a.Config.toEnvoyNetworkFilter(cfg)
|
|
if err != nil {
|
|
return filters, err
|
|
}
|
|
return ext_cmn.InsertNetworkFilter(filters, extAuthzFilter, a.InsertOptions)
|
|
}
|
|
}
|
|
|
|
func newExtAuthz(ext api.EnvoyExtension) (*extAuthz, error) {
|
|
auth := &extAuthz{}
|
|
if ext.Name != api.BuiltinExtAuthzExtension {
|
|
return auth, fmt.Errorf("expected extension name %q but got %q", api.BuiltinExtAuthzExtension, ext.Name)
|
|
}
|
|
if err := auth.fromArguments(ext.Arguments); err != nil {
|
|
return auth, err
|
|
}
|
|
// The filter's failure mode is always configured based on whether or not the extension is required.
|
|
auth.Config.failureModeAllow = !ext.Required
|
|
return auth, nil
|
|
}
|
|
|
|
func (a *extAuthz) fromArguments(args map[string]any) error {
|
|
if err := mapstructure.Decode(args, a); err != nil {
|
|
return err
|
|
}
|
|
a.normalize()
|
|
return a.validate()
|
|
}
|
|
|
|
func (a *extAuthz) configureInsertOptions(protocol string) {
|
|
// If the insert options have been expressly configured, then use them.
|
|
if a.InsertOptions.Location != "" {
|
|
return
|
|
}
|
|
|
|
// Configure the default, insert the filter immediately before the terminal filter.
|
|
a.InsertOptions.Location = ext_cmn.InsertBeforeFirstMatch
|
|
switch protocol {
|
|
case "grpc", "http2", "http":
|
|
a.InsertOptions.FilterName = "envoy.filters.http.router"
|
|
default:
|
|
a.InsertOptions.FilterName = "envoy.filters.network.tcp_proxy"
|
|
}
|
|
}
|
|
|
|
func (a *extAuthz) normalize() {
|
|
if a.ProxyType == "" {
|
|
a.ProxyType = api.ServiceKindConnectProxy
|
|
}
|
|
|
|
if a.ListenerType == "" {
|
|
a.ListenerType = "inbound"
|
|
}
|
|
|
|
a.Config.normalize()
|
|
}
|
|
|
|
func (a *extAuthz) validate() error {
|
|
var resultErr error
|
|
if a.ProxyType != api.ServiceKindConnectProxy {
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf("unsupported ProxyType %q, only %q is supported",
|
|
a.ProxyType,
|
|
api.ServiceKindConnectProxy))
|
|
}
|
|
|
|
if a.ListenerType != "inbound" && a.ListenerType != "outbound" {
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf(`unexpected ListenerType %q, supported values are "inbound" or "outbound"`, a.ListenerType))
|
|
}
|
|
|
|
if err := a.Config.validate(); err != nil {
|
|
resultErr = multierror.Append(resultErr, err)
|
|
}
|
|
|
|
return resultErr
|
|
}
|