Skip to content
Snippets Groups Projects
Unverified Commit 088339fc authored by Maksim Nabokikh's avatar Maksim Nabokikh Committed by GitHub
Browse files

Add headers control to dex web server (#3339)


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: default avatarm.nabokikh <maksim.nabokikh@flant.com>
parent 77333d61
Branches
Tags
No related merge requests found
......@@ -4,6 +4,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
......@@ -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"`
......@@ -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"`
......
......@@ -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
......@@ -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",
......
......@@ -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,
......
......@@ -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:
......
......@@ -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.
......@@ -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)
......@@ -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()
......@@ -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 {
......
......@@ -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% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment