Skip to content

Commit

Permalink
Basic Authentication - #399
Browse files Browse the repository at this point in the history
Make the configuration dynamic.

See <#399> for further information.
  • Loading branch information
Mohamed Bana authored and mbana committed Jun 15, 2022
1 parent 2447fbc commit 9d528ae
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 93 deletions.
11 changes: 6 additions & 5 deletions internal/controllers/config_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ func (c *KubeEnvoyConfigManager) UpdateConfiguration(ctx context.Context, fleetI
return err
}

httpConnectionManagerBuilder, err := config.NewHCMBuilder()
if err != nil {
return fmt.Errorf("failed to get HTTP connection manager: %w", err)
}

parser := spec.NewParser(nil)
for _, api := range apis {
l.Info("Processing API configuration", "fleet", fleetIDstr, "api", api.Name)
Expand All @@ -107,7 +112,7 @@ func (c *KubeEnvoyConfigManager) UpdateConfiguration(ctx context.Context, fleetI
return fmt.Errorf("failed to validate options: %w", err)
}

if err = UpdateConfigFromAPIOpts(envoyConfig, c.Validator, opts, apiSpec); err != nil {
if err = UpdateConfigFromAPIOpts(envoyConfig, c.Validator, opts, apiSpec, httpConnectionManagerBuilder); err != nil {
return fmt.Errorf("failed to generate config: %w", err)
}
l.Info("API route configuration processed", "fleet", fleetIDstr, "api", api.Name)
Expand Down Expand Up @@ -150,10 +155,6 @@ func (c *KubeEnvoyConfigManager) UpdateConfiguration(ctx context.Context, fleetI
}
}

httpConnectionManagerBuilder, err := config.NewHCMBuilder()
if err != nil {
return fmt.Errorf("failed to get HTTP connection manager: %w", err)
}
if fleet.Spec.AccessLog != nil {
var accessLogBuilder *config.AccessLogBuilder
var err error
Expand Down
33 changes: 29 additions & 4 deletions internal/controllers/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ Domain search order:
*/

// UpdateConfigFromAPIOpts updates Envoy configuration from OpenAPI spec and x-kusk options
func UpdateConfigFromAPIOpts(envoyConfiguration *config.EnvoyConfiguration, proxy *validation.Proxy, opts *options.Options, spec *openapi3.T) error {
func UpdateConfigFromAPIOpts(
envoyConfiguration *config.EnvoyConfiguration,
proxy *validation.Proxy,
opts *options.Options,
spec *openapi3.T,
httpConnectionManagerBuilder *config.HCMBuilder,
) error {
// Add new vhost if already not present.
for _, vhost := range opts.Hosts {
if envoyConfiguration.GetVirtualHost(string(vhost)) == nil {
Expand Down Expand Up @@ -297,16 +303,21 @@ func UpdateConfigFromAPIOpts(envoyConfiguration *config.EnvoyConfiguration, prox
routesToAddToVirtualHost = append(routesToAddToVirtualHost, rt)
case finalOpts.Auth != nil:
fmt.Println("-----")
fmt.Printf("finalOpts.Auth not nil, finalOpts.Auth=%+v\n", finalOpts.Auth)
// clusterName := "ext-authz-http-service:9002"
fmt.Printf("finalOpts.Auth not nil - finalOpts.Auth=%+v\n", finalOpts.Auth)

if finalOpts.Auth.PathPrefix != nil {

}

// clusterName := "ext-authz-http-service:9002"
upstreamServiceHost := finalOpts.Auth.AuthUpstream.Host.Hostname
upstreamServicePort := finalOpts.Auth.AuthUpstream.Host.Port

// upstreamServiceHost := "ext-authz-http-service"
// var upstreamServicePort uint32 = 9002
clusterName := generateClusterName(upstreamServiceHost, upstreamServicePort)
// clusterName := upstreamServiceHost

if !envoyConfiguration.ClusterExist(clusterName) {
fmt.Printf("cluster does not exist - add: %s %s:%d\n", clusterName, upstreamServiceHost, upstreamServicePort)
envoyConfiguration.AddCluster(
Expand All @@ -315,8 +326,22 @@ func UpdateConfigFromAPIOpts(envoyConfiguration *config.EnvoyConfiguration, prox
upstreamServicePort,
)
} else {
fmt.Printf("cluster does not exist - skip add: %s %s:%d\n", clusterName, upstreamServiceHost, upstreamServicePort)
fmt.Printf("cluster exists - skip add: %s %s:%d\n", clusterName, upstreamServiceHost, upstreamServicePort)
}

// httpExternalAuthorizationFilter, err := config.NewHTTPExternalAuthorizationFilter("ext-authz-http-service", 9002)
httpExternalAuthorizationFilter, err := config.NewHTTPExternalAuthorizationFilter(
upstreamServiceHost,
upstreamServicePort,
clusterName,
)
httpConnectionManagerBuilder.AppendFilterHTTPExternalAuthorizationFilterToStart(httpExternalAuthorizationFilter)
if err != nil {
fmt.Println("finalOpts.Auth not nil - could not add append - err=%w", err)
fmt.Println("-----")
return err
}

fmt.Println("-----")
// Default - proxy to the upstream
default:
Expand Down
93 changes: 93 additions & 0 deletions internal/envoy/config/filters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
MIT License
Copyright (c) 2022 Kubeshop
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package config

import (
"fmt"

envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
envoy_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_authz/v3"
envoy_type_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/durationpb"
)

// https://github.com/envoyproxy/envoy/tree/main/examples/ext_authz
// https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter#config-http-filters-ext-authz
func NewHTTPExternalAuthorizationFilter(upstreamHostname string, upstreamPort uint32, clusterName string) (*anypb.Any, error) {
// uri := fmt.Sprintf("http://%s:%d", "envoy-auth-basic-http-service.svc.cluster.local", 9002)
// uri := fmt.Sprintf("%s:%d", "envoy-auth-basic-http-service.svc.cluster.local", 9002)
uri := fmt.Sprintf("%s:%d", upstreamHostname, upstreamPort)

// cluster := generateClusterName(upstreamHostname, upstreamPort)
httpUpstreamType := &envoy_config_core_v3.HttpUri_Cluster{
Cluster: clusterName,
// Cluster: "ext-authz-http-service",
}
serverUri := &envoy_config_core_v3.HttpUri{
// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/http_uri.proto#envoy-v3-api-msg-config-core-v3-httpuri
Uri: uri,
HttpUpstreamType: httpUpstreamType,
Timeout: &durationpb.Duration{
Seconds: 60,
},
}
pathPrefix := ""
authorizationResponse := &envoy_auth_v3.AuthorizationResponse{
AllowedUpstreamHeaders: &envoy_type_matcher_v3.ListStringMatcher{
Patterns: []*envoy_type_matcher_v3.StringMatcher{
{
MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{
Exact: "x-current-user",
},
IgnoreCase: true,
},
},
},
}
httpService := &envoy_auth_v3.HttpService{
ServerUri: serverUri,
PathPrefix: pathPrefix,
AuthorizationResponse: authorizationResponse,
}
services := &envoy_auth_v3.ExtAuthz_HttpService{
HttpService: httpService,
}
authorization := &envoy_auth_v3.ExtAuthz{
Services: services,
TransportApiVersion: envoy_config_core_v3.ApiVersion_V3,
}
anyAuthorization, err := anypb.New(authorization)
if err != nil {
return nil, fmt.Errorf("cannot marshal authorization configuration: %w", err)
}

return anyAuthorization, nil
}

// // each cluster can be uniquely identified by dns name + port (i.e. canonical Host, which is hostname:port)
// // Copied from: `internal/controllers/parser.go`.
// func generateClusterName(upstreamHostname string, upstreamPort uint32) string {
// return fmt.Sprintf("%s-%d", upstreamHostname, upstreamPort)
// }
124 changes: 42 additions & 82 deletions internal/envoy/config/hcm.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,22 @@ import (
core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
simplecache "github.com/envoyproxy/go-control-plane/envoy/extensions/cache/simple_http_cache/v3"
cachev3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/cache/v3"
envoy_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_authz/v3"
ratelimit "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/local_ratelimit/v3"
hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
envoy_type_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
"github.com/envoyproxy/go-control-plane/pkg/resource/v3"
"github.com/envoyproxy/go-control-plane/pkg/wellknown"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/durationpb"
)

const (
RouteName string = "local_route"
)

type hcmBuilder struct {
httpConnectionManager *hcm.HttpConnectionManager
type HCMBuilder struct {
HTTPConnectionManager *hcm.HttpConnectionManager
}

func NewHCMBuilder() (*hcmBuilder, error) {
func NewHCMBuilder() (*HCMBuilder, error) {
rl := &ratelimit.LocalRateLimit{
StatPrefix: "http_local_rate_limiter",
}
Expand All @@ -74,13 +71,13 @@ func NewHCMBuilder() (*hcmBuilder, error) {
return nil, fmt.Errorf("cannot marshal cacheconfig configuration: %w", err)
}

anyAuthorization, err := makeHTTPExternalAuthorizationFilter()
if err != nil {
return nil, err
}
// httpExternalAuthorizationFilter, err := NewHTTPExternalAuthorizationFilter("ext-authz-http-service", 9002)
// if err != nil {
// return nil, err
// }

return &hcmBuilder{
httpConnectionManager: &hcm.HttpConnectionManager{
hcmBuilder := &HCMBuilder{
HTTPConnectionManager: &hcm.HttpConnectionManager{
CodecType: hcm.HttpConnectionManager_AUTO,
StatPrefix: "http",
RouteSpecifier: &hcm.HttpConnectionManager_Rds{
Expand All @@ -102,12 +99,6 @@ func NewHCMBuilder() (*hcmBuilder, error) {
TypedConfig: anyRateLimit,
},
},
{
Name: wellknown.HTTPExternalAuthorization,
ConfigType: &hcm.HttpFilter_TypedConfig{
TypedConfig: anyAuthorization,
},
},
{
Name: wellknown.CORS,
},
Expand All @@ -116,20 +107,46 @@ func NewHCMBuilder() (*hcmBuilder, error) {
},
},
},
}, nil
}

return hcmBuilder, nil
}

func (h *hcmBuilder) Validate() error {
return h.httpConnectionManager.Validate()
// AppendFilterHTTPExternalAuthorizationFilterToStart - `HTTPExternalAuthorization` needs to come before `CORS` and `Router`,
// append it to the start of the list.
func (h *HCMBuilder) AppendFilterHTTPExternalAuthorizationFilterToStart(anyAuthorization *anypb.Any) {
httpExternalAuthorizationFilter := &hcm.HttpFilter{
Name: wellknown.HTTPExternalAuthorization,
ConfigType: &hcm.HttpFilter_TypedConfig{
TypedConfig: anyAuthorization,
},
}

fmt.Println("----")
fmt.Printf("before h.HTTPConnectionManager.HttpFilters[0]=%+v\n", h.HTTPConnectionManager.HttpFilters[0])
fmt.Printf("before h.HTTPConnectionManager.HttpFilters=%+v\n", len(h.HTTPConnectionManager.HttpFilters))
h.HTTPConnectionManager.HttpFilters = append(
[]*hcm.HttpFilter{
httpExternalAuthorizationFilter,
},
h.HTTPConnectionManager.HttpFilters...,
)
fmt.Printf("after h.HTTPConnectionManager.HttpFilters[0]=%+v\n", h.HTTPConnectionManager.HttpFilters[0])
fmt.Printf("after h.HTTPConnectionManager.HttpFilters=%+v\n", len(h.HTTPConnectionManager.HttpFilters))
fmt.Println("----")
}

func (h *HCMBuilder) Validate() error {
return h.HTTPConnectionManager.Validate()
}

func (h *hcmBuilder) AddAccessLog(al *accesslog.AccessLog) *hcmBuilder {
h.httpConnectionManager.AccessLog = append(h.httpConnectionManager.AccessLog, al)
func (h *HCMBuilder) AddAccessLog(al *accesslog.AccessLog) *HCMBuilder {
h.HTTPConnectionManager.AccessLog = append(h.HTTPConnectionManager.AccessLog, al)
return h
}

func (h *hcmBuilder) GetHTTPConnectionManager() *hcm.HttpConnectionManager {
return h.httpConnectionManager
func (h *HCMBuilder) GetHTTPConnectionManager() *hcm.HttpConnectionManager {
return h.HTTPConnectionManager
}

func makeConfigSource() *core.ConfigSource {
Expand All @@ -149,60 +166,3 @@ func makeConfigSource() *core.ConfigSource {
}
return source
}

func makeHTTPExternalAuthorizationFilter() (*anypb.Any, error) {
// uri := fmt.Sprintf("http://%s:%d", "envoy-auth-basic-http-service.svc.cluster.local", 9002)
// uri := fmt.Sprintf("%s:%d", "envoy-auth-basic-http-service.svc.cluster.local", 9002)
uri := fmt.Sprintf("%s:%d", "ext-authz-http-service", 9002)

cluster := generateClusterName("ext-authz-http-service", 9002)
httpUpstreamType := &core.HttpUri_Cluster{
Cluster: cluster,
// Cluster: "ext-authz-http-service",
}
serverUri := &core.HttpUri{
// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/http_uri.proto#envoy-v3-api-msg-config-core-v3-httpuri
Uri: uri,
HttpUpstreamType: httpUpstreamType,
Timeout: &durationpb.Duration{
Seconds: 60,
},
}
pathPrefix := ""
authorizationResponse := &envoy_auth_v3.AuthorizationResponse{
AllowedUpstreamHeaders: &envoy_type_matcher_v3.ListStringMatcher{
Patterns: []*envoy_type_matcher_v3.StringMatcher{
{
MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{
Exact: "x-current-user",
},
IgnoreCase: true,
},
},
},
}
httpService := &envoy_auth_v3.HttpService{
ServerUri: serverUri,
PathPrefix: pathPrefix,
AuthorizationResponse: authorizationResponse,
}
services := &envoy_auth_v3.ExtAuthz_HttpService{
HttpService: httpService,
}
authorization := &envoy_auth_v3.ExtAuthz{
Services: services,
TransportApiVersion: core.ApiVersion_V3,
}
anyAuthorization, err := anypb.New(authorization)
if err != nil {
return nil, fmt.Errorf("cannot marshal authorization configuration: %w", err)
}

return anyAuthorization, nil
}

// each cluster can be uniquely identified by dns name + port (i.e. canonical Host, which is hostname:port)
// Copied from: `internal/controllers/parser.go`.
func generateClusterName(name string, port uint32) string {
return fmt.Sprintf("%s-%d", name, port)
}
2 changes: 1 addition & 1 deletion pkg/options/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type AuthOptions struct {
}

type AuthUpstream struct {
Host *AuthUpstreamHost `json:"host,omitempty" yaml:"host,omitempty"`
Host AuthUpstreamHost `json:"host,omitempty" yaml:"host,omitempty"`
}

type AuthUpstreamHost struct {
Expand Down
2 changes: 1 addition & 1 deletion pkg/options/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ auth:
Scheme: "basic",
PathPrefix: stringToPtr("/login"),
AuthUpstream: AuthUpstream{
Host: &AuthUpstreamHost{
Host: AuthUpstreamHost{
Hostname: "example.com",
Port: 80,
},
Expand Down

0 comments on commit 9d528ae

Please sign in to comment.