consul/agent/envoyextensions/builtin/ext-authz/ext_authz.go

149 lines
4.5 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
[COMPLIANCE] License changes (#18443) * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2023-08-11 13:12:13 +00:00
// 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
// 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
}
// 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 !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
}
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 err := a.Config.validate(); err != nil {
resultErr = multierror.Append(resultErr, err)
}
return resultErr
}