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

feat: Add HTTP support for External Auth #4994

Merged
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
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 @@ -372,6 +372,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 @@ -435,19 +439,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 @@ -644,6 +649,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 @@ -849,19 +912,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 @@ -930,14 +994,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 @@ -23,6 +23,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 @@ -391,6 +392,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 @@ -504,10 +533,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,
}

xdsServerType := contour_api_v1alpha1.ContourServerType
Expand Down
46 changes: 44 additions & 2 deletions cmd/contour/servecontext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"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 +482,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 @@ -699,6 +701,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