Skip to content

Commit

Permalink
Configurable JWT Issuer for the OIDC Discovery Provider (#5657)
Browse files Browse the repository at this point in the history
* Adding support for configurable jwt issuer + test cases

Signed-off-by: Luthra, Ayush <[email protected]>

* Moving verifyhost check + adding more test cases

Signed-off-by: Luthra, Ayush <[email protected]>

* Adding test case of jwt issuer with just a host

Signed-off-by: Luthra, Ayush <[email protected]>

* Updating readme docs + fixing spacing

Signed-off-by: Luthra, Ayush <[email protected]>

* Fixing spacing in readme table

Signed-off-by: Luthra, Ayush <[email protected]>

* fixing windows test cases + minor refactor

Signed-off-by: Luthra, Ayush <[email protected]>

---------

Signed-off-by: Luthra, Ayush <[email protected]>
  • Loading branch information
aluthra-37 authored Dec 3, 2024
1 parent 791c8e3 commit 40fb0df
Show file tree
Hide file tree
Showing 7 changed files with 422 additions and 20 deletions.
1 change: 1 addition & 0 deletions support/oidc-discovery-provider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ The configuration file is **required** by the provider. It contains
| `server_api` | section | required\[2\] | Provides SPIRE Server API details. | |
| `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 | |

| experimental | Type | Required? | Description | Default |
|--------------------------|--------|--------------------|------------------------------------------------------|---------|
Expand Down
11 changes: 10 additions & 1 deletion support/oidc-discovery-provider/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"net"
"net/url"
"os"
"time"

Expand Down Expand Up @@ -75,6 +76,9 @@ type Config struct {

// Experimental options that are subject to change or removal.
Experimental experimentalConfig `hcl:"experimental"`

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

type ServingCertFileConfig struct {
Expand Down Expand Up @@ -287,7 +291,12 @@ func ParseConfig(hclConfig string) (_ *Config, err error) {
default:
return nil, errs.New("the server_api and workload_api sections are mutually exclusive")
}

if c.JWTIssuer != "" {
jwtIssuer, err := url.Parse(c.JWTIssuer)
if err != nil || jwtIssuer.Scheme == "" || jwtIssuer.Host == "" {
return nil, errs.New("the jwt_issuer url could not be parsed")
}
}
return c, nil
}

Expand Down
112 changes: 112 additions & 0 deletions support/oidc-discovery-provider/config_posix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -640,5 +640,117 @@ func parseConfigCasesOS() []parseConfigCase {
HealthChecks: nil,
},
},
{
name: "with JWT issuer",
in: `
domains = ["domain.test"]
jwt_issuer = "https://domain.test/some/issuer/path/issuer1/"
serving_cert_file {
cert_file_path = "test"
key_file_path = "test"
}
server_api {
address = "unix:///some/socket/path"
}
`,
out: &Config{
LogLevel: defaultLogLevel,
Domains: []string{"domain.test"},
JWTIssuer: "https://domain.test/some/issuer/path/issuer1/",
ServingCertFile: &ServingCertFileConfig{
CertFilePath: "test",
KeyFilePath: "test",
FileSyncInterval: time.Minute,
Addr: &net.TCPAddr{
IP: nil,
Port: 443,
},
RawAddr: ":443",
},
ServerAPI: &ServerAPIConfig{
Address: "unix:///some/socket/path",
PollInterval: defaultPollInterval,
},
HealthChecks: nil,
},
},
{
name: "JWT issuer with missing scheme",
in: `
domains = ["domain.test"]
jwt_issuer = "domain.test/some/issuer/path/issuer1/"
serving_cert_file {
cert_file_path = "test"
key_file_path = "test"
}
server_api {
address = "unix:///some/socket/path"
}
`,
err: "the jwt_issuer url could not be parsed",
},
{
name: "JWT issuer with missing host",
in: `
domains = ["domain.test"]
jwt_issuer = "https:///path"
serving_cert_file {
cert_file_path = "test"
key_file_path = "test"
}
server_api {
address = "unix:///some/socket/path"
}
`,
err: "the jwt_issuer url could not be parsed",
},
{
name: "JWT issuer is invalid",
in: `
domains = ["domain.test"]
jwt_issuer = "http://domain.test:someportnumber/some/path"
serving_cert_file {
cert_file_path = "test"
key_file_path = "test"
}
server_api {
address = "unix:///some/socket/path"
}
`,
err: "the jwt_issuer url could not be parsed",
},
{
name: "JWT issuer is empty",
in: `
domains = ["domain.test"]
jwt_issuer = ""
serving_cert_file {
cert_file_path = "test"
key_file_path = "test"
}
server_api {
address = "unix:///some/socket/path"
}
`,
out: &Config{
LogLevel: defaultLogLevel,
Domains: []string{"domain.test"},
ServingCertFile: &ServingCertFileConfig{
CertFilePath: "test",
KeyFilePath: "test",
FileSyncInterval: time.Minute,
Addr: &net.TCPAddr{
IP: nil,
Port: 443,
},
RawAddr: ":443",
},
ServerAPI: &ServerAPIConfig{
Address: "unix:///some/socket/path",
PollInterval: defaultPollInterval,
},
HealthChecks: nil,
},
},
}
}
133 changes: 133 additions & 0 deletions support/oidc-discovery-provider/config_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -577,5 +577,138 @@ func parseConfigCasesOS() []parseConfigCase {
`,
err: "trust_domain must be configured in the workload_api configuration section",
},
{
name: "with JWT issuer",
in: `
domains = ["domain.test"]
jwt_issuer = "https://domain.test/some/issuer/path/issuer1/"
serving_cert_file {
cert_file_path = "test"
key_file_path = "test"
}
server_api {
address = "unix:///some/socket/path"
experimental {
named_pipe_name = "\\name\\for\\server\\api"
}
}
`,
out: &Config{
LogLevel: defaultLogLevel,
Domains: []string{"domain.test"},
JWTIssuer: "https://domain.test/some/issuer/path/issuer1/",
ServingCertFile: &ServingCertFileConfig{
CertFilePath: "test",
KeyFilePath: "test",
FileSyncInterval: time.Minute,
Addr: &net.TCPAddr{
IP: nil,
Port: 443,
},
RawAddr: ":443",
},
ServerAPI: &ServerAPIConfig{
Address: "unix:///some/socket/path",
PollInterval: defaultPollInterval,
Experimental: experimentalServerAPIConfig{
NamedPipeName: "\\name\\for\\server\\api",
},
},
HealthChecks: nil,
},
},
{
name: "JWT issuer with missing scheme",
in: `
domains = ["domain.test"]
jwt_issuer = "domain.test/some/issuer/path/issuer1/"
serving_cert_file {
cert_file_path = "test"
key_file_path = "test"
}
server_api {
address = "unix:///some/socket/path"
experimental {
named_pipe_name = "\\name\\for\\server\\api"
}
}
`,
err: "the jwt_issuer url could not be parsed",
},
{
name: "JWT issuer with missing host",
in: `
domains = ["domain.test"]
jwt_issuer = "https:///path"
serving_cert_file {
cert_file_path = "test"
key_file_path = "test"
}
server_api {
address = "unix:///some/socket/path"
experimental {
named_pipe_name = "\\name\\for\\server\\api"
}
}
`,
err: "the jwt_issuer url could not be parsed",
},
{
name: "JWT issuer is invalid",
in: `
domains = ["domain.test"]
jwt_issuer = "http://domain.test:someportnumber/some/path"
serving_cert_file {
cert_file_path = "test"
key_file_path = "test"
}
server_api {
address = "unix:///some/socket/path"
experimental {
named_pipe_name = "\\name\\for\\server\\api"
}
}
`,
err: "the jwt_issuer url could not be parsed",
},
{
name: "JWT issuer is empty",
in: `
domains = ["domain.test"]
jwt_issuer = ""
serving_cert_file {
cert_file_path = "test"
key_file_path = "test"
}
server_api {
address = "unix:///some/socket/path"
experimental {
named_pipe_name = "\\name\\for\\server\\api"
}
}
`,
out: &Config{
LogLevel: defaultLogLevel,
Domains: []string{"domain.test"},
ServingCertFile: &ServingCertFileConfig{
CertFilePath: "test",
KeyFilePath: "test",
FileSyncInterval: time.Minute,
Addr: &net.TCPAddr{
IP: nil,
Port: 443,
},
RawAddr: ":443",
},
ServerAPI: &ServerAPIConfig{
Address: "unix:///some/socket/path",
PollInterval: defaultPollInterval,
Experimental: experimentalServerAPIConfig{
NamedPipeName: "\\name\\for\\server\\api",
},
},
HealthChecks: nil,
},
},
}
}
36 changes: 23 additions & 13 deletions support/oidc-discovery-provider/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,19 @@ type Handler struct {
allowInsecureScheme bool
setKeyUse bool
log logrus.FieldLogger
jwtIssuer string

http.Handler
}

func NewHandler(log logrus.FieldLogger, domainPolicy DomainPolicy, source JWKSSource, allowInsecureScheme bool, setKeyUse bool) *Handler {
func NewHandler(log logrus.FieldLogger, domainPolicy DomainPolicy, source JWKSSource, allowInsecureScheme bool, setKeyUse bool, jwtIssuer string) *Handler {
h := &Handler{
domainPolicy: domainPolicy,
source: source,
allowInsecureScheme: allowInsecureScheme,
setKeyUse: setKeyUse,
log: log,
jwtIssuer: jwtIssuer,
}

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

if err := h.verifyHost(r.Host); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
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 := url.URL{
Scheme: urlScheme,
Host: r.Host,
Host: host,
Path: path,
}

jwksURI := url.URL{
Scheme: urlScheme,
Host: r.Host,
Path: "/keys",
}
jwksURI := issuerURL.JoinPath("keys")

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

0 comments on commit 40fb0df

Please sign in to comment.