Repository: dexidp/dex
Pull request #3401 (2024-03-06 04:59:22)

build(deps): bump google.golang.org/grpc from 1.62.0 to 1.62.1 in /examples
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.62.0 to 1.62.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/grpc/grpc-go/releases">google.golang.org/grpc's releases</a>.</em></p>
<blockquote>
<h2>Release 1.62.1</h2>
<h1>Bug Fixes</h1>
<ul>
<li>xds: fix a bug that results in <code>no matching virtual host found</code> RPC errors due to a difference between the target and LDS resource names (<a href="https://redirect.github.com/grpc/grpc-go/issues/6997">#6997</a>)</li>
<li>server: fixed stats handler data <code>InPayload.Length</code> for unary RPC calls (<a href="https://redirect.github.com/grpc/grpc-go/issues/6766">#6766</a>)
<ul>
<li>Special Thanks: <a href="https://github.com/hueypark"><code>@​hueypark</code></a></li>
</ul>
</li>
<li>grpc: the experimental <code>RecvBufferPool</code> <code>DialOption</code> and <code>ServerOption</code> are now active during unary RPCs with compression (<a href="https://redirect.github.com/grpc/grpc-go/issues/6766">#6766</a>)
<ul>
<li>Special Thanks: <a href="https://github.com/hueypark"><code>@​hueypark</code></a></li>
</ul>
</li>
<li>grpc: trim whitespaces in <code>accept-encoding</code> header before determining compressors
<ul>
<li>Special Thanks: <a href="https://github.com/sercand"><code>@​sercand</code></a></li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/grpc/grpc-go/commit/9952aa83979822b5915c3fcb2bb0f60afe55aa7d"><code>9952aa8</code></a> Change version to 1.62.1 (<a href="https://redirect.github.com/grpc/grpc-go/issues/7020">#7020</a>)</li>
<li><a href="https://github.com/grpc/grpc-go/commit/d7334c477d1ea670fa8d5fa12f06a2bfe4f41d4f"><code>d7334c4</code></a> fix enabling compression by trimming whitespaces in accept encoding header (#...</li>
<li><a href="https://github.com/grpc/grpc-go/commit/d076e14b4849f4262f6f50042a9370ec5ce0116d"><code>d076e14</code></a> rpc_util: Fix RecvBufferPool deactivation issues (<a href="https://redirect.github.com/grpc/grpc-go/issues/6766">#6766</a>)</li>
<li><a href="https://github.com/grpc/grpc-go/commit/9d981b0eb01a1ccd61f16593461277e95e83a34b"><code>9d981b0</code></a> cherry-pick <a href="https://redirect.github.com/grpc/grpc-go/issues/6997">#6997</a> to 1.62.x release branch (<a href="https://redirect.github.com/grpc/grpc-go/issues/6979">#6979</a>) (<a href="https://redirect.github.com/grpc/grpc-go/issues/7018">#7018</a>)</li>
<li><a href="https://github.com/grpc/grpc-go/commit/7c4b5533d07d5d7d01aa71145c32af27ac6e1a4d"><code>7c4b553</code></a> Switch version to 1.62.1-dev (<a href="https://redirect.github.com/grpc/grpc-go/issues/6995">#6995</a>)</li>
<li>See full diff in <a href="https://github.com/grpc/grpc-go/compare/v1.62.0...v1.62.1">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=google.golang.org/grpc&package-manager=go_modules&previous-version=1.62.0&new-version=1.62.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/cmd/dex/config.go b/cmd/dex/config.go
index 9c5c1039..33c0db47 100644
--- a/cmd/dex/config.go
+++ b/cmd/dex/config.go
@@ -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"`
diff --git a/cmd/dex/serve.go b/cmd/dex/serve.go
index 3494443e..9461a622 100644
--- a/cmd/dex/serve.go
+++ b/cmd/dex/serve.go
@@ -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,
diff --git a/connector/ldap/ldap.go b/connector/ldap/ldap.go
index c50d8309..f0aa7eff 100644
--- a/connector/ldap/ldap.go
+++ b/connector/ldap/ldap.go
@@ -460,6 +460,7 @@ func (c *ldapConnector) userEntry(conn *ldap.Conn, username string) (user ldap.E
 
 func (c *ldapConnector) Login(ctx context.Context, s connector.Scopes, username, password string) (ident connector.Identity, validPass bool, err error) {
 	// make this check to avoid unauthenticated bind to the LDAP server.
+
 	if password == "" {
 		return connector.Identity{}, false, nil
 	}
@@ -471,6 +472,9 @@ func (c *ldapConnector) Login(ctx context.Context, s connector.Scopes, username,
 		user          ldap.Entry
 	)
 
+	username = ldap.EscapeFilter(username)
+	password = ldap.EscapeFilter(password)
+
 	err = c.do(ctx, func(conn *ldap.Conn) error {
 		entry, found, err := c.userEntry(conn, username)
 		if err != nil {
diff --git a/examples/config-dev.yaml b/examples/config-dev.yaml
index 956aa84c..8f1018ff 100644
--- a/examples/config-dev.yaml
+++ b/examples/config-dev.yaml
@@ -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:
diff --git a/examples/go.mod b/examples/go.mod
index cea09bb1..7f61f4be 100644
--- a/examples/go.mod
+++ b/examples/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/dexidp/dex/api/v2 v2.1.0
 	github.com/spf13/cobra v1.8.0
 	golang.org/x/oauth2 v0.18.0
-	google.golang.org/grpc v1.62.0
+	google.golang.org/grpc v1.62.1
 )
 
 require (
diff --git a/examples/go.sum b/examples/go.sum
index dc59051d..3d6ab31c 100644
--- a/examples/go.sum
+++ b/examples/go.sum
@@ -80,8 +80,8 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs
 google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
-google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
-google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
+google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
+google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
diff --git a/server/server.go b/server/server.go
index bb9da17b..1eaf1915 100644
--- a/server/server.go
+++ b/server/server.go
@@ -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 {


Test patch:
diff --git a/cmd/dex/config_test.go b/cmd/dex/config_test.go
index 16803d6d..e3169650 100644
--- a/cmd/dex/config_test.go
+++ b/cmd/dex/config_test.go
@@ -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",
diff --git a/connector/ldap/ldap_test.go b/connector/ldap/ldap_test.go
index 24254dcc..f00f1ead 100644
--- a/connector/ldap/ldap_test.go
+++ b/connector/ldap/ldap_test.go
@@ -83,6 +83,18 @@ func TestQuery(t *testing.T) {
 			password:  "foo",
 			wantBadPW: true, // Want invalid password, not a query error.
 		},
+		{
+			name:      "invalid wildcard username",
+			username:  "a*", // wildcard query is not allowed
+			password:  "foo",
+			wantBadPW: true, // Want invalid password, not a query error.
+		},
+		{
+			name:      "invalid wildcard password",
+			username:  "john",
+			password:  "*",  // wildcard password is not allowed
+			wantBadPW: true, // Want invalid password, not a query error.
+		},
 	}
 
 	runTests(t, connectLDAP, c, tests)
diff --git a/server/server_test.go b/server/server_test.go
index f39d64d3..25d1909d 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -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"))
+}