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

[oidc-discovery-provider] Fix keys url #5690

Open
wants to merge 44 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
bfbdeb3
[oidc-discovery-provider] Fix keys url
kfox1111 Dec 8, 2024
442a1c8
Update tests
kfox1111 Dec 8, 2024
6b33c7f
Merge branch 'main' into issuer-fix
kfox1111 Dec 15, 2024
29bdd98
Add advertised_url support
kfox1111 Dec 15, 2024
63d7981
Simplify change
kfox1111 Dec 15, 2024
f35e79b
Revert domain name check
kfox1111 Dec 15, 2024
6f5e7e9
Add prefix support
kfox1111 Dec 15, 2024
88e33bb
Cleanup
kfox1111 Dec 15, 2024
295ccc2
Fix typo
kfox1111 Dec 15, 2024
8800dba
Merge branch 'main' into issuer-fix
kfox1111 Dec 16, 2024
219ac45
Merge branch 'main' into issuer-fix
kfox1111 Dec 16, 2024
b88a3ae
Merge branch 'main' into issuer-fix
kfox1111 Dec 17, 2024
9bcf2dd
Merge branch 'main' into issuer-fix
kfox1111 Dec 19, 2024
d18d37d
Merge branch 'main' into issuer-fix
kfox1111 Jan 2, 2025
79865b8
Merge branch 'main' into issuer-fix
kfox1111 Jan 3, 2025
851dc3a
Merge branch 'main' into issuer-fix
kfox1111 Jan 3, 2025
dc97122
Merge branch 'main' into issuer-fix
kfox1111 Jan 13, 2025
d191da0
Update names after feedback
kfox1111 Jan 14, 2025
bc0b68e
Merge branch 'main' into issuer-fix
kfox1111 Jan 14, 2025
6d07e62
Merge branch 'main' into issuer-fix
kfox1111 Jan 15, 2025
8c7972b
Fix typo
kfox1111 Jan 15, 2025
e28186a
Incorperate feedback
kfox1111 Jan 20, 2025
462a7b2
Merge branch 'main' into issuer-fix
kfox1111 Jan 20, 2025
4675ec8
Merge branch 'main' into issuer-fix
kfox1111 Jan 20, 2025
ef0c4b5
Incorperate feedback
kfox1111 Jan 20, 2025
7da1b9f
Merge branch 'issuer-fix' of https://github.com/kfox1111/spire into i…
kfox1111 Jan 20, 2025
a448c12
Fix tests
kfox1111 Jan 21, 2025
96dee03
Fix test
kfox1111 Jan 21, 2025
1902ac9
Merge branch 'main' into issuer-fix
kfox1111 Jan 21, 2025
e6694f6
Incorperate feedback
kfox1111 Jan 21, 2025
38a2d49
Merge branch 'main' into issuer-fix
kfox1111 Jan 24, 2025
710d78b
Merge branch 'main' into issuer-fix
MarcosDY Jan 24, 2025
01709db
Incorperate feedback
kfox1111 Jan 24, 2025
3b80a00
Merge branch 'main' into issuer-fix
kfox1111 Jan 24, 2025
a438130
Incorperate feedback
kfox1111 Jan 29, 2025
bcf6e92
Merge branch 'issuer-fix' of https://github.com/kfox1111/spire into i…
kfox1111 Jan 29, 2025
fcb33ec
Merge branch 'main' into issuer-fix
kfox1111 Jan 29, 2025
35895d1
Add test for compat behavior. Fix lint.
kfox1111 Jan 29, 2025
dc7aa77
Merge branch 'issuer-fix' of https://github.com/kfox1111/spire into i…
kfox1111 Jan 29, 2025
e6814c5
Fix lint
kfox1111 Jan 29, 2025
9146d52
Merge branch 'main' into issuer-fix
kfox1111 Jan 30, 2025
7a5f5ac
Merge branch 'main' into issuer-fix
kfox1111 Jan 30, 2025
32465f0
Update docs
kfox1111 Jan 30, 2025
e6772f8
Merge branch 'issuer-fix' of https://github.com/kfox1111/spire into i…
kfox1111 Jan 30, 2025
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
5 changes: 5 additions & 0 deletions support/oidc-discovery-provider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ It provides the following endpoints:
| `GET` | `/ready` | Returns http.OK (200) as soon as requests can be served. (disabled by default) |
| `GET` | `/live` | Returns http.OK (200) as soon as a keyset is available, otherwise http.InternalServerError (500). (disabled by default) |

The endpoints can be moved to a different prefix by way of the `server_path_prefix` option. For example, setting server_path_prefix to `/instance/1` will make
the OIDC discovery document served at `/instance/1/.well-known/openid-configuration` and keys at `/instance/1/keys`

The provider by default relies on ACME to obtain TLS certificates that it uses to
serve the documents securely.

Expand Down Expand Up @@ -49,6 +52,8 @@ The configuration file is **required** by the provider. It contains
| `workload_api` | section | required\[2\] | Provides Workload API details. | |
| `health_checks` | section | optional | Enable and configure health check endpoints | |
| `jwt_issuer` | string | optional | Specifies the issuer for the OIDC provider configuration request | |
| `jwks_uri` | string | optional | Specifies the JWKS URI returned in the discovery document | |
| `server_path_prefix` | string | optional | If specified, all endpoints listened to will be prefixed by this value | `"/"` |

| experimental | Type | Required? | Description | Default |
|--------------------------|--------|--------------------|------------------------------------------------------|---------|
Expand Down
15 changes: 15 additions & 0 deletions support/oidc-discovery-provider/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ type Config struct {

// JWTIssuer specifies the issuer for the OIDC provider configuration request.
JWTIssuer string `hcl:"jwt_issuer"`

// JWKSURI specifies the absolute uri to the jwks keys document. Use this if you are fronting the
// discovery provider with a load balancer or reverse proxy
JWKSURI string `hcl:"jwks_uri"`

// ServerPathPrefix specifies the prefix to strip from the path of requests to route to the server.
// Example: if ServerPathPrefix is /foo then a request to http://127.0.0.1/foo/.well-known/openid-configuration and
// http://127.0.0.1/foo/keys will function with the server.
ServerPathPrefix string `hcl:"server_path_prefix"`
}

type ServingCertFileConfig struct {
Expand Down Expand Up @@ -308,6 +317,12 @@ func ParseConfig(hclConfig string) (_ *Config, err error) {
return nil, errors.New("the jwt_issuer url must contain a host")
}
}
if c.JWKSURI != "" {
jwksURI, err := url.Parse(c.JWKSURI)
if err != nil || jwksURI.Scheme == "" || jwksURI.Host == "" {
return nil, fmt.Errorf("the jwks_uri setting could not be parsed: %w", err)
}
}
return c, nil
}

Expand Down
89 changes: 63 additions & 26 deletions support/oidc-discovery-provider/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,40 @@ type Handler struct {
allowInsecureScheme bool
setKeyUse bool
log logrus.FieldLogger
jwtIssuer string
jwtIssuer *url.URL
jwksURI *url.URL
serverPathPrefix string

http.Handler
}

func NewHandler(log logrus.FieldLogger, domainPolicy DomainPolicy, source JWKSSource, allowInsecureScheme bool, setKeyUse bool, jwtIssuer string) *Handler {
func NewHandler(log logrus.FieldLogger, domainPolicy DomainPolicy, source JWKSSource, allowInsecureScheme bool, setKeyUse bool, jwtIssuer *url.URL, jwksURI *url.URL, serverPathPrefix string) *Handler {
if serverPathPrefix == "" {
serverPathPrefix = "/"
}
h := &Handler{
domainPolicy: domainPolicy,
source: source,
allowInsecureScheme: allowInsecureScheme,
setKeyUse: setKeyUse,
log: log,
jwtIssuer: jwtIssuer,
jwksURI: jwksURI,
serverPathPrefix: serverPathPrefix,
}

mux := http.NewServeMux()
mux.Handle("/.well-known/openid-configuration", handlers.ProxyHeaders(http.HandlerFunc(h.serveWellKnown)))
mux.Handle("/keys", http.HandlerFunc(h.serveKeys))
wkPath, err := url.JoinPath(serverPathPrefix, "/.well-known/openid-configuration")
if err != nil {
return nil
}
jwksPath, err := url.JoinPath(serverPathPrefix, "/keys")
if err != nil {
return nil
}

mux.Handle(wkPath, handlers.ProxyHeaders(http.HandlerFunc(h.serveWellKnown)))
mux.Handle(jwksPath, http.HandlerFunc(h.serveKeys))

h.Handler = mux
return h
Expand All @@ -53,34 +69,55 @@ func (h *Handler) serveWellKnown(w http.ResponseWriter, r *http.Request) {
return
}

var host string
var path string
var urlScheme string
if h.jwtIssuer != "" {
jwtIssuerURL, _ := url.Parse(h.jwtIssuer)
host = jwtIssuerURL.Host
path = jwtIssuerURL.Path
urlScheme = jwtIssuerURL.Scheme
} else {
host = r.Host
urlScheme = "https"
if h.allowInsecureScheme && r.TLS == nil && r.URL.Scheme != "https" {
urlScheme = "http"
}
urlScheme := "https"
if h.allowInsecureScheme && r.TLS == nil && r.URL.Scheme != "https" {
urlScheme = "http"
}

if err := h.verifyHost(host); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
issuerURL := h.jwtIssuer
if h.jwtIssuer == nil {
issuerURL = &url.URL{
Scheme: urlScheme,
Host: r.Host,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we include serverPathPrefix in the Issuer here?
Per the specs:

OpenID Providers supporting Discovery MUST make a JSON document available at the path formed by concatenating the string /.well-known/openid-configuration to the Issuer.

So technically, if the well-known document includes a path component, the issuer should too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depends on if there are any proxies doing rewrites in front...

But, yeah. I think your probably right, as the default, if the serverPathPrefix is set, the default may benefit from having it set too. The user can always override issuer if they are using a proxy in front.

}
if h.serverPathPrefix != "/" {
issuerURL.Path = h.serverPathPrefix
}
}

issuerURL := url.URL{
Scheme: urlScheme,
Host: host,
Path: path,
var jwksURI *url.URL
switch {
case h.jwksURI != nil:
jwksURI = h.jwksURI
case h.jwtIssuer != nil:
// If jwksIsser is set but not jwksURI, fall back to 1.11.1 behavior until we can remove jwksIssuer leaking into jwksURI in 1.13.0
keysPath, err := url.JoinPath(h.jwtIssuer.Path, "keys")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
jwksURI = &url.URL{
Scheme: h.jwtIssuer.Scheme,
Host: h.jwtIssuer.Host,
Path: keysPath,
}
default:
keysPath, err := url.JoinPath(h.serverPathPrefix, "keys")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
jwksURI = &url.URL{
Scheme: urlScheme,
Host: r.Host,
Path: keysPath,
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to simplify this a bit. I'm thinking something like:

var issuerURL url.URL
if h.jwtIssuer != "" {
  ...
  issuerURL = ...
} else {
   ...
  issuerURL = ...
}

var jwksURL url.URL
if h.jwksURL != "" {
  ...
  jwksURL = ...
} else {
   ...
  jwksURL = ...
}

Minimizes dependencies between the parts of the two URLs and makes it a bit easier to read, at least in my opinion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow this one. Talking about moving the var definition closer to the block of code setting it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, basically that + keeping the extra vars (mainly host and port) outside of the ifs. I think it makes the code a bit easier to follow since the reader doesn't have to jump through various if conditions to come up with the final value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.


jwksURI := issuerURL.JoinPath("keys")
if err := h.verifyHost(r.Host); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

doc := struct {
Issuer string `json:"issuer"`
Expand Down
Loading
Loading