mirror of
https://github.com/netbirdio/dex.git
synced 2026-05-22 18:43:53 -07:00
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: m.nabokikh <maksim.nabokikh@flant.com>
This commit is contained in:
@@ -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:
|
||||
|
||||
+15
-3
@@ -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"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user