Skip to content

Commit

Permalink
feat: Add HTTP support for External Auth (projectcontour#4994)
Browse files Browse the repository at this point in the history
Support globally configuring an external auth
server which is enabled by default for all vhosts,
both HTTP and HTTPS.

Closes projectcontour#4954.

Signed-off-by: claytonig <claytonivorgonsalves@gmail.com>
Signed-off-by: yy <yang.yang@daocloud.io>
  • Loading branch information
clayton-gonsalves authored and yangyy93 committed Mar 27, 2023
1 parent 73c4945 commit f9d7ca5
Show file tree
Hide file tree
Showing 32 changed files with 3,165 additions and 165 deletions.
2 changes: 1 addition & 1 deletion apis/projectcontour/v1/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
// AuthorizationConfigured returns whether authorization is
// configured on this virtual host.
func (v *VirtualHost) AuthorizationConfigured() bool {
return v.TLS != nil && v.Authorization != nil
return v.Authorization != nil
}

// DisableAuthorization returns true if this virtual host disables
Expand Down
4 changes: 2 additions & 2 deletions apis/projectcontour/v1/httpproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ type ExtensionServiceReference struct {
type AuthorizationServer struct {
// ExtensionServiceRef specifies the extension resource that will authorize client requests.
//
// +required
ExtensionServiceRef ExtensionServiceReference `json:"extensionRef"`
// +optional
ExtensionServiceRef ExtensionServiceReference `json:"extensionRef,omitempty"`

// AuthPolicy sets a default authorization policy for client requests.
// This policy will be used unless overridden by individual routes.
Expand Down
5 changes: 5 additions & 0 deletions apis/projectcontour/v1alpha1/contourconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ type ContourConfigurationSpec struct {
// +optional
EnableExternalNameService *bool `json:"enableExternalNameService,omitempty"`

// GlobalExternalAuthorization allows envoys external authorization filter
// to be enabled for all virtual hosts.
// +optional
GlobalExternalAuthorization *contour_api_v1.AuthorizationServer `json:"globalExtAuth,omitempty"`

// RateLimitService optionally holds properties of the Rate Limit Service
// to be used for global rate limiting.
// +optional
Expand Down
5 changes: 5 additions & 0 deletions apis/projectcontour/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions changelogs/unreleased/4994-clayton-gonsalves-minor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Add support for Global External Authorization for HTTPProxy.

Contour now supports external authorization for all hosts by setting the config as part of the `contourConfig` like so:

```yaml
globalExtAuth:
extensionService: projectcontour-auth/htpasswd
failOpen: false
authPolicy:
context:
header1: value1
header2: value2
responseTimeout: 1s
```
Individual hosts can also override or opt out of this global configuration.
You can read more about this feature in detail in the [guide](https://projectcontour.io/docs/v1.25.0/guides/external-authorization/#global-external-authorization).
133 changes: 99 additions & 34 deletions cmd/contour/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,10 @@ func (s *Server) doServe() error {
return err
}

if listenerConfig.GlobalExternalAuthConfig, err = s.setupGlobalExternalAuthentication(contourConfiguration); err != nil {
return err
}

contourMetrics := metrics.NewMetrics(s.registry)

// Endpoints updates are handled directly by the EndpointsTranslator
Expand Down Expand Up @@ -441,19 +445,20 @@ func (s *Server) doServe() error {
}

builder := s.getDAGBuilder(dagBuilderConfig{
ingressClassNames: ingressClassNames,
rootNamespaces: contourConfiguration.HTTPProxy.RootNamespaces,
gatewayControllerName: gatewayControllerName,
gatewayRef: gatewayRef,
disablePermitInsecure: *contourConfiguration.HTTPProxy.DisablePermitInsecure,
enableExternalNameService: *contourConfiguration.EnableExternalNameService,
dnsLookupFamily: contourConfiguration.Envoy.Cluster.DNSLookupFamily,
headersPolicy: contourConfiguration.Policy,
clientCert: clientCert,
fallbackCert: fallbackCert,
connectTimeout: timeouts.ConnectTimeout,
client: s.mgr.GetClient(),
metrics: contourMetrics,
ingressClassNames: ingressClassNames,
rootNamespaces: contourConfiguration.HTTPProxy.RootNamespaces,
gatewayControllerName: gatewayControllerName,
gatewayRef: gatewayRef,
disablePermitInsecure: *contourConfiguration.HTTPProxy.DisablePermitInsecure,
enableExternalNameService: *contourConfiguration.EnableExternalNameService,
dnsLookupFamily: contourConfiguration.Envoy.Cluster.DNSLookupFamily,
headersPolicy: contourConfiguration.Policy,
clientCert: clientCert,
fallbackCert: fallbackCert,
connectTimeout: timeouts.ConnectTimeout,
client: s.mgr.GetClient(),
metrics: contourMetrics,
globalExternalAuthorizationService: contourConfiguration.GlobalExternalAuthorization,
})

// Build the core Kubernetes event handler.
Expand Down Expand Up @@ -719,6 +724,64 @@ func (s *Server) setupRateLimitService(contourConfiguration contour_api_v1alpha1
}, nil
}

func (s *Server) setupGlobalExternalAuthentication(contourConfiguration contour_api_v1alpha1.ContourConfigurationSpec) (*xdscache_v3.GlobalExternalAuthConfig, error) {
if contourConfiguration.GlobalExternalAuthorization == nil {
return nil, nil
}

// ensure the specified ExtensionService exists
extensionSvc := &contour_api_v1alpha1.ExtensionService{}

key := client.ObjectKey{
Namespace: contourConfiguration.GlobalExternalAuthorization.ExtensionServiceRef.Namespace,
Name: contourConfiguration.GlobalExternalAuthorization.ExtensionServiceRef.Name,
}

// Using GetAPIReader() here because the manager's caches won't be started yet,
// so reads from the manager's client (which uses the caches for reads) will fail.
if err := s.mgr.GetAPIReader().Get(context.Background(), key, extensionSvc); err != nil {
return nil, fmt.Errorf("error getting global external authorization extension service %s: %v", key, err)
}

// get the response timeout from the ExtensionService
var responseTimeout timeout.Setting
var err error

if tp := extensionSvc.Spec.TimeoutPolicy; tp != nil {
responseTimeout, err = timeout.Parse(tp.Response)
if err != nil {
return nil, fmt.Errorf("error parsing global http ext auth extension service %s response timeout: %v", key, err)
}
}

var sni string
if extensionSvc.Spec.UpstreamValidation != nil {
sni = extensionSvc.Spec.UpstreamValidation.SubjectName
}

var context map[string]string
if contourConfiguration.GlobalExternalAuthorization.AuthPolicy.Context != nil {
context = contourConfiguration.GlobalExternalAuthorization.AuthPolicy.Context
}

globalExternalAuthConfig := &xdscache_v3.GlobalExternalAuthConfig{
ExtensionService: key,
SNI: sni,
Timeout: responseTimeout,
FailOpen: contourConfiguration.GlobalExternalAuthorization.FailOpen,
Context: context,
}

if contourConfiguration.GlobalExternalAuthorization.WithRequestBody != nil {
globalExternalAuthConfig.WithRequestBody = &dag.AuthorizationServerBufferSettings{
PackAsBytes: contourConfiguration.GlobalExternalAuthorization.WithRequestBody.PackAsBytes,
AllowPartialMessage: contourConfiguration.GlobalExternalAuthorization.WithRequestBody.AllowPartialMessage,
MaxRequestBytes: contourConfiguration.GlobalExternalAuthorization.WithRequestBody.MaxRequestBytes,
}
}
return globalExternalAuthConfig, nil
}

func (s *Server) setupDebugService(debugConfig contour_api_v1alpha1.DebugConfig, builder *dag.Builder) error {
debugsvc := &debug.Service{
Service: httpsvc.Service{
Expand Down Expand Up @@ -924,19 +987,20 @@ func (s *Server) setupGatewayAPI(contourConfiguration contour_api_v1alpha1.Conto
}

type dagBuilderConfig struct {
ingressClassNames []string
rootNamespaces []string
gatewayControllerName string
gatewayRef *types.NamespacedName
disablePermitInsecure bool
enableExternalNameService bool
dnsLookupFamily contour_api_v1alpha1.ClusterDNSFamilyType
headersPolicy *contour_api_v1alpha1.PolicyConfig
clientCert *types.NamespacedName
fallbackCert *types.NamespacedName
connectTimeout time.Duration
client client.Client
metrics *metrics.Metrics
ingressClassNames []string
rootNamespaces []string
gatewayControllerName string
gatewayRef *types.NamespacedName
disablePermitInsecure bool
enableExternalNameService bool
dnsLookupFamily contour_api_v1alpha1.ClusterDNSFamilyType
headersPolicy *contour_api_v1alpha1.PolicyConfig
clientCert *types.NamespacedName
fallbackCert *types.NamespacedName
connectTimeout time.Duration
client client.Client
metrics *metrics.Metrics
globalExternalAuthorizationService *contour_api_v1.AuthorizationServer
}

func (s *Server) getDAGBuilder(dbc dagBuilderConfig) *dag.Builder {
Expand Down Expand Up @@ -1005,14 +1069,15 @@ func (s *Server) getDAGBuilder(dbc dagBuilderConfig) *dag.Builder {
ConnectTimeout: dbc.connectTimeout,
},
&dag.HTTPProxyProcessor{
EnableExternalNameService: dbc.enableExternalNameService,
DisablePermitInsecure: dbc.disablePermitInsecure,
FallbackCertificate: dbc.fallbackCert,
DNSLookupFamily: dbc.dnsLookupFamily,
ClientCertificate: dbc.clientCert,
RequestHeadersPolicy: &requestHeadersPolicy,
ResponseHeadersPolicy: &responseHeadersPolicy,
ConnectTimeout: dbc.connectTimeout,
EnableExternalNameService: dbc.enableExternalNameService,
DisablePermitInsecure: dbc.disablePermitInsecure,
FallbackCertificate: dbc.fallbackCert,
DNSLookupFamily: dbc.dnsLookupFamily,
ClientCertificate: dbc.clientCert,
RequestHeadersPolicy: &requestHeadersPolicy,
ResponseHeadersPolicy: &responseHeadersPolicy,
ConnectTimeout: dbc.connectTimeout,
GlobalExternalAuthorization: dbc.globalExternalAuthorizationService,
},
}

Expand Down
38 changes: 34 additions & 4 deletions cmd/contour/servecontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"strings"
"time"

contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1"
contour_api_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1"
envoy_v3 "github.com/projectcontour/contour/internal/envoy/v3"
"github.com/projectcontour/contour/internal/k8s"
Expand Down Expand Up @@ -417,6 +418,34 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_api_v1alpha
serverHeaderTransformation = contour_api_v1alpha1.PassThroughServerHeader
}

var globalExtAuth *contour_api_v1.AuthorizationServer
if ctx.Config.GlobalExternalAuthorization.ExtensionService != "" {
nsedName := k8s.NamespacedNameFrom(ctx.Config.GlobalExternalAuthorization.ExtensionService)
globalExtAuth = &contour_api_v1.AuthorizationServer{
ExtensionServiceRef: contour_api_v1.ExtensionServiceReference{
Name: nsedName.Name,
Namespace: nsedName.Namespace,
},
ResponseTimeout: ctx.Config.GlobalExternalAuthorization.ResponseTimeout,
FailOpen: ctx.Config.GlobalExternalAuthorization.FailOpen,
}

if ctx.Config.GlobalExternalAuthorization.AuthPolicy != nil {
globalExtAuth.AuthPolicy = &contour_api_v1.AuthorizationPolicy{
Disabled: ctx.Config.GlobalExternalAuthorization.AuthPolicy.Disabled,
Context: ctx.Config.GlobalExternalAuthorization.AuthPolicy.Context,
}
}

if ctx.Config.GlobalExternalAuthorization.WithRequestBody != nil {
globalExtAuth.WithRequestBody = &contour_api_v1.AuthorizationServerBufferSettings{
MaxRequestBytes: ctx.Config.GlobalExternalAuthorization.WithRequestBody.MaxRequestBytes,
AllowPartialMessage: ctx.Config.GlobalExternalAuthorization.WithRequestBody.AllowPartialMessage,
PackAsBytes: ctx.Config.GlobalExternalAuthorization.WithRequestBody.PackAsBytes,
}
}
}

policy := &contour_api_v1alpha1.PolicyConfig{
RequestHeadersPolicy: &contour_api_v1alpha1.HeadersPolicy{
Set: ctx.Config.Policy.RequestHeadersPolicy.Set,
Expand Down Expand Up @@ -530,10 +559,11 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_api_v1alpha
RootNamespaces: ctx.proxyRootNamespaces(),
FallbackCertificate: fallbackCertificate,
},
EnableExternalNameService: &ctx.Config.EnableExternalNameService,
RateLimitService: rateLimitService,
Policy: policy,
Metrics: &contourMetrics,
EnableExternalNameService: &ctx.Config.EnableExternalNameService,
GlobalExternalAuthorization: globalExtAuth,
RateLimitService: rateLimitService,
Policy: policy,
Metrics: &contourMetrics,
Tracing: tracingConfig,
}

Expand Down
49 changes: 47 additions & 2 deletions cmd/contour/servecontext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import (
"testing"
"time"

"github.com/projectcontour/contour/pkg/config"
"github.com/tsaarni/certyaml"

contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1"
contour_api_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1"
"github.com/projectcontour/contour/internal/contourconfig"
envoy_v3 "github.com/projectcontour/contour/internal/envoy/v3"
Expand Down Expand Up @@ -481,8 +485,9 @@ func TestConvertServeContext(t *testing.T) {
DisablePermitInsecure: ref.To(false),
FallbackCertificate: nil,
},
EnableExternalNameService: ref.To(false),
RateLimitService: nil,
EnableExternalNameService: ref.To(false),
RateLimitService: nil,
GlobalExternalAuthorization: nil,
Policy: &contour_api_v1alpha1.PolicyConfig{
RequestHeadersPolicy: &contour_api_v1alpha1.HeadersPolicy{},
ResponseHeadersPolicy: &contour_api_v1alpha1.HeadersPolicy{},
Expand Down Expand Up @@ -743,6 +748,46 @@ func TestConvertServeContext(t *testing.T) {
return cfg
},
},
"global external authorization": {
getServeContext: func(ctx *serveContext) *serveContext {
ctx.Config.GlobalExternalAuthorization = config.GlobalExternalAuthorization{
ExtensionService: "extauthns/extauthtext",
FailOpen: true,
AuthPolicy: &config.GlobalAuthorizationPolicy{
Context: map[string]string{
"foo": "bar",
},
},
WithRequestBody: &config.GlobalAuthorizationServerBufferSettings{
MaxRequestBytes: 512,
PackAsBytes: true,
AllowPartialMessage: true,
},
}
return ctx
},
getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
cfg.GlobalExternalAuthorization = &contour_api_v1.AuthorizationServer{
ExtensionServiceRef: contour_api_v1.ExtensionServiceReference{
Name: "extauthtext",
Namespace: "extauthns",
},
FailOpen: true,
AuthPolicy: &contour_api_v1.AuthorizationPolicy{
Context: map[string]string{
"foo": "bar",
},
Disabled: false,
},
WithRequestBody: &contour_api_v1.AuthorizationServerBufferSettings{
MaxRequestBytes: 512,
PackAsBytes: true,
AllowPartialMessage: true,
},
}
return cfg
},
},
}

for name, tc := range cases {
Expand Down
Loading

0 comments on commit f9d7ca5

Please sign in to comment.