diff --git a/changelog/13871.txt b/changelog/13871.txt new file mode 100644 index 000000000000..5878fba9d858 --- /dev/null +++ b/changelog/13871.txt @@ -0,0 +1,3 @@ +```release-note:bug +oidc: Support dynamic port for loopback redirect URI +``` diff --git a/vault/identity_store_oidc_provider.go b/vault/identity_store_oidc_provider.go index d3a7a260de4c..2800f446292e 100644 --- a/vault/identity_store_oidc_provider.go +++ b/vault/identity_store_oidc_provider.go @@ -1513,7 +1513,8 @@ func (i *IdentityStore) pathOIDCAuthorize(ctx context.Context, req *logical.Requ if redirectURI == "" { return authResponse("", state, ErrAuthInvalidRequest, "redirect_uri parameter is required") } - if !strutil.StrListContains(client.RedirectURIs, redirectURI) { + + if !strutil.StrListContains(client.RedirectURIs, redirectURI) && !isValidRedirectURI(redirectURI, client.RedirectURIs) { return authResponse("", state, ErrAuthInvalidRedirectURI, "redirect_uri is not allowed for the client") } diff --git a/vault/identity_store_oidc_provider_util.go b/vault/identity_store_oidc_provider_util.go new file mode 100644 index 000000000000..05df8f8a347f --- /dev/null +++ b/vault/identity_store_oidc_provider_util.go @@ -0,0 +1,49 @@ +package vault + +import ( + "net/url" + "regexp" + "strings" +) + +func isValidRedirectURI(uri string, validUris []string) bool { + requestedUri, err := url.Parse(uri) + if err != nil { + return false + } + + for _, validUri := range validUris { + if strings.ToLower(validUri) == strings.ToLower(uri) || isLoopbackURI(requestedUri, validUri) { + return true + } + } + + return false +} + +func isLoopbackURI(requestUri *url.URL, validUri string) bool { + registeredUri, err := url.Parse(validUri) + if err != nil { + return false + } + + // Verifies that the valid URL is HTTP and is the loopback address before + // proceeding, otherwise return false + if registeredUri.Scheme != "http" || !isLoopbackAddress(registeredUri.Host) { + return false + } + + // Returns true if the path after the IP/port is the same + // Request URL and valid URL have already been validated as loopback + if requestUri.Scheme == "http" && isLoopbackAddress(requestUri.Host) && registeredUri.Path == requestUri.Path { + return true + } + + return false +} + +// Returns true if the address hostname is the IPv4 or IPv6 loopback address and ignores port +func isLoopbackAddress(address string) bool { + match, _ := regexp.MatchString("^(127.0.0.1|\\[::1\\])(:?)(\\d*)$", address) + return match +}