Skip to content

Commit

Permalink
Add headers control to dex web server (#3339)
Browse files Browse the repository at this point in the history
Customization of headers in the authentication server is crucial for enforcing stringent security measures by allowing the inclusion of specific headers required for authentication protocols and compliance standards. This customization ensures that authentication requests are processed securely, mitigating potential vulnerabilities and ensuring adherence to security policies.

Signed-off-by: m.nabokikh <maksim.nabokikh@flant.com>
  • Loading branch information
nabokihms committed Mar 11, 2024
1 parent 77333d6 commit 088339f
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 3 deletions.
48 changes: 48 additions & 0 deletions cmd/dex/config.go
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"

Expand Down Expand Up @@ -153,6 +154,7 @@ type OAuth2 struct {
type Web struct {
HTTP string `json:"http"`
HTTPS string `json:"https"`
Headers Headers `json:"headers"`
TLSCert string `json:"tlsCert"`
TLSKey string `json:"tlsKey"`
TLSMinVersion string `json:"tlsMinVersion"`
Expand All @@ -161,6 +163,52 @@ type Web struct {
AllowedHeaders []string `json:"allowedHeaders"`
}

type Headers struct {
// Set the Content-Security-Policy header to HTTP responses.
// Unset if blank.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
ContentSecurityPolicy string `json:"Content-Security-Policy"`
// Set the X-Frame-Options header to HTTP responses.
// Unset if blank. Accepted values are deny and sameorigin.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
XFrameOptions string `json:"X-Frame-Options"`
// Set the X-Content-Type-Options header to HTTP responses.
// Unset if blank. Accepted value is nosniff.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
XContentTypeOptions string `json:"X-Content-Type-Options"`
// Set the X-XSS-Protection header to all responses.
// Unset if blank.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
XXSSProtection string `json:"X-XSS-Protection"`
// Set the Strict-Transport-Security header to HTTP responses.
// Unset if blank.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
StrictTransportSecurity string `json:"Strict-Transport-Security"`
}

func (h *Headers) ToHTTPHeader() http.Header {
if h == nil {
return make(map[string][]string)
}
header := make(map[string][]string)
if h.ContentSecurityPolicy != "" {
header["Content-Security-Policy"] = []string{h.ContentSecurityPolicy}
}
if h.XFrameOptions != "" {
header["X-Frame-Options"] = []string{h.XFrameOptions}
}
if h.XContentTypeOptions != "" {
header["X-Content-Type-Options"] = []string{h.XContentTypeOptions}
}
if h.XXSSProtection != "" {
header["X-XSS-Protection"] = []string{h.XXSSProtection}
}
if h.StrictTransportSecurity != "" {
header["Strict-Transport-Security"] = []string{h.StrictTransportSecurity}
}
return header
}

// Telemetry is the config format for telemetry including the HTTP server config.
type Telemetry struct {
HTTP string `json:"http"`
Expand Down
5 changes: 5 additions & 0 deletions cmd/dex/config_test.go
Expand Up @@ -74,6 +74,8 @@ web:
https: 127.0.0.1:5556
tlsMinVersion: 1.3
tlsMaxVersion: 1.2
headers:
Strict-Transport-Security: "max-age=31536000; includeSubDomains"
frontend:
dir: ./web
Expand Down Expand Up @@ -149,6 +151,9 @@ logger:
HTTPS: "127.0.0.1:5556",
TLSMinVersion: "1.3",
TLSMaxVersion: "1.2",
Headers: Headers{
StrictTransportSecurity: "max-age=31536000; includeSubDomains",
},
},
Frontend: server.WebConfig{
Dir: "./web",
Expand Down
1 change: 1 addition & 0 deletions cmd/dex/serve.go
Expand Up @@ -278,6 +278,7 @@ func runServe(options serveOptions) error {
SkipApprovalScreen: c.OAuth2.SkipApprovalScreen,
AlwaysShowLoginScreen: c.OAuth2.AlwaysShowLoginScreen,
PasswordConnector: c.OAuth2.PasswordConnector,
Headers: c.Web.Headers.ToHTTPHeader(),
AllowedOrigins: c.Web.AllowedOrigins,
AllowedHeaders: c.Web.AllowedHeaders,
Issuer: c.Issuer,
Expand Down
7 changes: 7 additions & 0 deletions examples/config-dev.yaml
Expand Up @@ -52,6 +52,13 @@ web:
# https: 127.0.0.1:5554
# tlsCert: /etc/dex/tls.crt
# tlsKey: /etc/dex/tls.key
# headers:
# X-Frame-Options: "DENY"
# X-Content-Type-Options: "nosniff"
# X-XSS-Protection: "1; mode=block"
# Content-Security-Policy: "default-src 'self'"
# Strict-Transport-Security: "max-age=31536000; includeSubDomains"


# Configuration for dex appearance
# frontend:
Expand Down
18 changes: 15 additions & 3 deletions server/server.go
Expand Up @@ -72,6 +72,9 @@ type Config struct {
// flow. If no response types are supplied this value defaults to "code".
SupportedResponseTypes []string

// Headers is a map of headers to be added to the all responses.
Headers http.Header

// List of allowed origins for CORS requests on discovery, token and keys endpoint.
// If none are indicated, CORS requests are disabled. Passing in "*" will allow any
// domain.
Expand Down Expand Up @@ -345,9 +348,18 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
}
}

handlerWithHeaders := func(handlerName string, handler http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
for k, v := range c.Headers {
w.Header()[k] = v
}
instrumentHandlerCounter(handlerName, handler)(w, r)
}
}

r := mux.NewRouter().SkipClean(true).UseEncodedPath()
handle := func(p string, h http.Handler) {
r.Handle(path.Join(issuerURL.Path, p), instrumentHandlerCounter(p, h))
r.Handle(path.Join(issuerURL.Path, p), handlerWithHeaders(p, h))
}
handleFunc := func(p string, h http.HandlerFunc) {
handle(p, h)
Expand All @@ -365,7 +377,7 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
)
handler = cors(handler)
}
r.Handle(path.Join(issuerURL.Path, p), instrumentHandlerCounter(p, handler))
r.Handle(path.Join(issuerURL.Path, p), handlerWithHeaders(p, handler))
}
r.NotFoundHandler = http.NotFoundHandler()

Expand All @@ -388,7 +400,7 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
// TODO(nabokihms): "/device/token" endpoint is deprecated, consider using /token endpoint instead
handleFunc("/device/token", s.handleDeviceTokenDeprecated)
handleFunc(deviceCallbackURI, s.handleDeviceCallback)
r.HandleFunc(path.Join(issuerURL.Path, "/callback"), func(w http.ResponseWriter, r *http.Request) {
handleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
// Strip the X-Remote-* headers to prevent security issues on
// misconfigured authproxy connector setups.
for key := range r.Header {
Expand Down
22 changes: 22 additions & 0 deletions server/server_test.go
Expand Up @@ -1799,3 +1799,25 @@ func TestServerSupportedGrants(t *testing.T) {
})
}
}

func TestHeaders(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

httpServer, _ := newTestServer(ctx, t, func(c *Config) {
c.Headers = map[string][]string{
"Strict-Transport-Security": {"max-age=31536000; includeSubDomains"},
}
})
defer httpServer.Close()

p, err := oidc.NewProvider(ctx, httpServer.URL)
if err != nil {
t.Fatalf("failed to get provider: %v", err)
}

resp, err := http.Get(p.Endpoint().TokenURL)
require.NoError(t, err)

require.Equal(t, "max-age=31536000; includeSubDomains", resp.Header.Get("Strict-Transport-Security"))
}

0 comments on commit 088339f

Please sign in to comment.