diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml
index 94210e5..52a6c1e 100644
--- a/.github/workflows/docker.yaml
+++ b/.github/workflows/docker.yaml
@@ -45,13 +45,20 @@ jobs:
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: |
+ type=raw,value=latest,enable={{is_default_branch}}
+ type=ref,event=tag
+ type=ref,event=pr
+ type=semver,pattern={{raw}}
+ type=raw,value={{sha}}
+ type=ref,event=branch
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v5
with:
context: .
- push: ${{ github.event_name != 'pull_request'}}
+ push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
diff --git a/Dockerfile b/Dockerfile
index 29a54bc..a6d63ac 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,24 +2,23 @@ FROM golang:alpine AS plugin-build
COPY plugins /tmp/plugins
WORKDIR /tmp/plugins
RUN mkdir /plugins
-RUN for d in */ ; do echo "Bulding plugin in $d"; cd $d; go mod download -x -json; go build -o /plugins/ -x . ; echo "Built plugin in $d"; cd ../; done
+RUN for d in */ ; do echo "Bulding plugin in $d"; cd $d; go mod download -x -json; go build -x -v -o /plugins/ -x . ; echo "Built plugin in $d"; cd ../; done
RUN ls /plugins
FROM kong:alpine
USER root
-COPY --from=plugin-build /plugins /usr/local/kong/
-USER kong
+COPY --from=plugin-build /plugins /usr/local/bin/
+RUN chown kong:kong /usr/local/bin/oidc
ENV KONG_PLUGINSERVER_NAMES=oidc
-ENV KONG_PLUGINSERVER_OIDC_QUERY_CMD="/usr/local/kong/oidc -dump"
-ENV KONG_PROXY_LISTEN="0.0.0.0:8000 http2 reuseport backlog=16384"
-ENV KONG_ADMIN_LISTEN="0.0.0.0:8001 http2 reuseport backlog=16384"
-ENV KONG_PLUGINS=oidc,bundled
+ENV KONG_PLUGINSERVER_OIDC_START_CMD="/usr/local/bin/oidc"
+ENV KONG_PLUGINSERVER_OIDC_SOCKET="/usr/local/kong/oidc.socket"
+ENV KONG_PLUGINSERVER_OIDC_QUERY_CMD="/usr/local/bin/oidc -dump"
+ENV KONG_PROXY_LISTEN="0.0.0.0:8000 reuseport backlog=16384"
+ENV KONG_ADMIN_LISTEN="0.0.0.0:8001 reuseport backlog=16384"
+ENV KONG_GUI_LISTEN="0.0.0.0:8002"
+ENV KONG_PLUGINS=bundled,oidc
ENV KONG_PROXY_ACCESS_LOG=/dev/stdout
ENV KONG_ADMIN_ACCESS_LOG=/dev/stdout
ENV KONG_PROXY_ERROR_LOG=/dev/stderr
ENV KONG_ADMIN_ERROR_LOG=/dev/stderr
-ENTRYPOINT ["/docker-entrypoint.sh"]
-EXPOSE 8000 8001
-STOPSIGNAL SIGQUIT
-HEALTHCHECK --interval=10s --timeout=10s --retries=10 CMD kong health
-CMD ["kong", "docker-start"]
\ No newline at end of file
+LABEL org.opencontainers.image.source="https://github.com/wisdom-oss/api-gateway"
\ No newline at end of file
diff --git a/README.md b/README.md
index 8c79a09..f6c0514 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,28 @@
-
API Gateway
-api-gateway
-
-🛡️ A Kong API Gateway extended with functionality for the WISdoM
-platform
-
+
![](https://raw.githubusercontent.com/wisdom-oss/brand/main/svg/standalone_color.svg)
+
API Gateway
+
api-gateway
+
🛡️ Extended Kong API Gateway for the WISdoM Platform
+
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/wisdom-oss/api-gateway/docker.yaml?style=for-the-badge&label=Docker%20Build)
+
+
+
-# About
-The [WISdoM project](https://github.com/wisdom-oss) utilizes the Kong API
-Gateway to route requests to their services. Since some needed functionality of
-the gateway is locked behind paid plugins, this repository contains some custom
-plugins which implement the following features:
+> [!NOTE]
+> This API Gateway is based on the Open Source Edition of the [Kong API Gateway](https://konghq.com/products/kong-gateway).
+> Therefore, features available in the free (closed-source) and enterprise (also
+> closed-source) edition are not available with this image.
-- [x] OpenID Connect Authentication/Authorization with JWT Validation
+The API Gateway is the central part of the WISdoM Plaform and manages the access
+to the microservices implemented for the platform.
+Furhtermore, it is extended by a plugin using the
+[`go-pdk`](https://github.com/Kong/go-pdk) to validate JWTs which are used in a
+standard deployment to authenticate and secure requests.
+> — [Read more](plugins/oidc/README.md)
-The plugins are implemented in Golang using IPC for communicating with the
-API Gateway.
-
-## How to use
-Since the API gateway in included in every deployment of the WISdoM platform,
-there are no extra steps you need to take.
\ No newline at end of file
+The API Gateway is autommatically configured using the
+[gateway-service-watcher](https://github.com/wisdom-oss/gateway-service-watcher)
+which acts as a watchdog to check the deployed containers on the host for their
+association to the WISdoM platform.
+> — [Read more](https://github.com/wisdom-oss/gateway-service-watcher)
\ No newline at end of file
diff --git a/plugins/oidc/.gitignore b/plugins/oidc/.gitignore
new file mode 100644
index 0000000..d7c3743
--- /dev/null
+++ b/plugins/oidc/.gitignore
@@ -0,0 +1,2 @@
+.idea
+*.exe
\ No newline at end of file
diff --git a/plugins/oidc/README.md b/plugins/oidc/README.md
index 5265c14..f6c8633 100644
--- a/plugins/oidc/README.md
+++ b/plugins/oidc/README.md
@@ -20,5 +20,7 @@ These responses can optionally be cached using a redis database to further
minimize the number of network requests executed for each intercepted request.
## Configuration
-The configuration is done automatically during the startup of the api gateway
-using the environment variables passed to the api gateway
\ No newline at end of file
+The configuration is done by the
+[watchdog](https://github.com/wisdom-oss/watchdog) on a service-by-service
+basis.
+Please refer to the documentation of the watchdog for further information.
\ No newline at end of file
diff --git a/plugins/oidc/access.go b/plugins/oidc/access.go
index 48d5017..549550f 100644
--- a/plugins/oidc/access.go
+++ b/plugins/oidc/access.go
@@ -4,124 +4,251 @@ import (
"context"
"encoding/json"
"errors"
+ "fmt"
"net/http"
+ "reflect"
"regexp"
"strconv"
"strings"
"github.com/Kong/go-pdk"
+ "github.com/lestrrat-go/jwx/v2/jwk"
"github.com/lestrrat-go/jwx/v2/jwt"
)
-var headerRegex = regexp.MustCompile(`^(\w+) (\S+)$`)
+// bearerTokenPattern is a regular expression pattern used to validate and parse
+// bearer tokens.
+// It matches the following format: "Bearer ". The token is a non-empty
+// string of characters that do not contain whitespace.
+// The pattern is case-insensitive.
+// Example usage:
+//
+// if matched, _ := regexp.MatchString(bearerTokenPattern, token); matched {
+// // Token is valid
+// } else {
+// // Token is invalid
+// }
+const bearerTokenPattern = `(?i)^(Bearer) (\S+)$`
-// Access is executed every time the kong gateway receives a request. Since the
+// bearerTokenRegEx is used to store the regular expression after building it
+// once
+var bearerTokenRegEx *regexp.Regexp
+
+var errEmptyEndpoint = errors.New("empty discovery endpoint")
+var errDiscoveryRequestFailure = errors.New("discovery request failed")
+var errDiscoveryUnmarshalFailure = errors.New("unable to unmarshal openid connect configuration")
+var errDiscoveryInvalidType = errors.New("discovery response contained invalid type")
+
+// fieldTypeErrorTemplate is used to keep the same repeating string as a
+// constant before creating error messages with it
+const fieldTypeErrorTemplate = `field '%s' is expected to be 'string' got: %s`
+
+// discoverEndpoints retrieves the userinfo and jwks endpoints from the given
+// discovery endpoint.
+// The function sends a GET request to the discovery endpoint, which should
+// respond with the openid connect configuration.
+// This configuration is a JSON object that the function decodes into a map,
+// extracting the userinfo_endpoint and jwks_uri fields as the userinfo and jwks
+// endpoints respectively.
+// In case of error, it will also join the error with suitable contextual error
+// message and return.
+// If the discovery endpoint is empty or leads to a failed request, the function
+// returns an error.
+// If the retrieval or decoding of the openid connect configuration fails, the
+// function returns an error.
+//
+// Parameters:
+//
+// discoveryEndpoint (string): address of the discovery endpoint.
+//
+// Returns:
+//
+// userinfoEndpoint (string): the discovered userinfo endpoint.
+// jwksEndpoint (string): the discovered jwks endpoint.
+// err (error): the error, if any occurred during the process.
+func discoverEndpoints(discoveryEndpoint string) (userinfoEndpoint string, jwksEndpoint string, err error) {
+ // cleanup the discovery endpoint address and remove possible whitespaces
+ discoveryEndpoint = strings.TrimSpace(discoveryEndpoint)
+ if discoveryEndpoint == "" {
+ return "", "", errEmptyEndpoint
+ }
+
+ // now request the openid connect configuration endpoint
+ res, err := http.Get(discoveryEndpoint)
+ if err != nil {
+ return "", "", errors.Join(errDiscoveryRequestFailure, err)
+ }
+
+ // now read the openid connect configuration
+ var openIdConnectConfiguration map[string]interface{}
+ err = json.NewDecoder(res.Body).Decode(&openIdConnectConfiguration)
+ if err != nil {
+ return "", "", errors.Join(err, errDiscoveryUnmarshalFailure)
+ }
+ var isValidString bool
+ userinfoEndpoint, isValidString = openIdConnectConfiguration["userinfo_endpoint"].(string)
+ if !isValidString {
+ actualType := reflect.TypeOf(openIdConnectConfiguration["userinfo_endpoint"])
+ return "", "", errors.Join(errDiscoveryInvalidType, fmt.Errorf(fieldTypeErrorTemplate, "userinfo_endpoint", actualType))
+ }
+
+ jwksEndpoint, isValidString = openIdConnectConfiguration["jwks_uri"].(string)
+ if !isValidString {
+ actualType := reflect.TypeOf(openIdConnectConfiguration["jwks_uri"])
+ return "", "", errors.Join(errDiscoveryInvalidType, fmt.Errorf(fieldTypeErrorTemplate, "jwks_uri", actualType))
+ }
+
+ return userinfoEndpoint, jwksEndpoint, nil
+}
+
+// extractBearerToken is a function that takes an authorization header value as
+// input and extracts the bearer token from it.
+// It uses a regular expression to match the header value against a predefined
+// pattern.
+// If a match is found and there are two capture groups in the match result, the
+// function returns the second capture group, which represents the bearer token.
+// If no match is found or there are not exactly two capture groups, an empty
+// string is returned.
+//
+// Example usage:
+//
+// token := extractBearerToken("Bearer abcxyz123")
+// // token = "abcxyz123"
+//
+// Parameters:
+// - authorizationHeaderValue: The value of the authorization header.
+//
+// Returns:
+//
+// The extracted bearer token, or an empty string if no valid bearer token is found.
+func extractBearerToken(authorizationHeaderValue string) string {
+ // check if the bearer token regex has already been compiled
+ if bearerTokenRegEx == nil {
+ bearerTokenRegEx = regexp.MustCompile(bearerTokenPattern)
+ }
+ matches := bearerTokenRegEx.FindStringSubmatch(authorizationHeaderValue)
+ if len(matches) == 3 {
+ return matches[2]
+ }
+ return ""
+}
+
+// Access is executed every time the kong gateway receives a request. Since ther
// plugin may be restarted at any moment
func (c *Configuration) Access(kong *pdk.PDK) {
- // access the request and get the authorization header
- request := kong.Request
+ // create a shortcut for the logger
logger := kong.Log
- logger.Info("authenticating new request")
- logger.Debug("extracting authorization header and its values")
- authorizationHeader, _ := request.GetHeader("Authorization")
- // now trim away any blank values at the start or end of the string
- authorizationHeader = strings.TrimSpace(authorizationHeader)
- // now get the authorization method and token from the header
- matches := headerRegex.FindStringSubmatch(authorizationHeader)
- if len(matches) != 3 {
- // since the matches array needs to contain three entries, the value is
- // invalid and therefore the user is not authorized
+
+ userinfoEndpoint, jwksEndpoint, err := discoverEndpoints(c.DiscoveryUri)
+ if err != nil {
+ logger.Crit("unable to extract required endpoints from openid connect discovery", err)
response := GatewayError{
- ErrorCode: "gateway.MALFORMED_AUTHORIZATION_HEADER",
- ErrorTitle: "Authorization Header Malformed",
- ErrorDescription: "The 'Authorization' header is not correctly formatted. please check your request",
- HttpStatusCode: 400,
- HttpStatusText: "Bad Request",
+ HttpStatusCode: 500,
+ HttpStatusText: "InternalServerError",
}
- headers := map[string][]string{
- "WWW-Authenticate": {`Bearer scope="openid profile email", error="invalid_request", error_description="Authorization header malformed'"`},
+ switch {
+ case errors.Is(err, errEmptyEndpoint):
+ response = GatewayError{
+ ErrorCode: "gateway.OPENID_CONNECT_INVALID_DISCOVERY_ENDPOINT",
+ ErrorTitle: "Invalid Discovery Endpoint",
+ ErrorDescription: "The configured OpenID Connect discovery endpoint is invalid: " + err.Error(),
+ }
+ case errors.Is(err, errDiscoveryRequestFailure):
+ response = GatewayError{
+ ErrorCode: "gateway.OPENID_CONNECT_DISCOVERY_REQUEST_FAILURE",
+ ErrorTitle: "Discovery Request Failure",
+ ErrorDescription: "The discovery request needed for access token validation failed: " + err.Error(),
+ }
+ case errors.Is(err, errDiscoveryUnmarshalFailure):
+ response = GatewayError{
+ ErrorCode: "gateway.OPENID_CONNECT_DISCOVERY_RESPONSE_PARSE_ERROR",
+ ErrorTitle: "Discovery Response Parsing Error",
+ ErrorDescription: "The discovery response sent by the configured endpoint could not be parsed: " + err.Error(),
+ }
}
- response.SendError(kong, headers)
- return
+ response.SendError(kong, nil)
}
- // now extract the authorization method and the token
- authorizationMethod := matches[1]
- token := matches[2]
- logger.Debug("extracted authorization method and token")
- logger.Debug("validating authorization method")
- // now check if the authorization method is Bearer
- if authorizationMethod != "Bearer" {
+
+ // use the extracted JWKS endpoint to download the JSON Web Key Set to be
+ // able to verify the access token locally
+ jwks, err := jwk.Fetch(context.Background(), jwksEndpoint)
+ if err != nil {
+ logger.Crit("unable to fetch the JWKS", err.Error())
response := GatewayError{
- ErrorCode: "gateway.INVALID_AUTH_SCHEME",
- ErrorTitle: "Authorization Header Malformed",
- ErrorDescription: "The 'Authorization' does not use the 'Bearer' auth scheme",
+ ErrorCode: "gateway.OPENID_CONNECT_JWKS_DOWNLOAD_ERROR",
+ ErrorTitle: "JWKS Download Error",
+ ErrorDescription: "Unable to download the JWKS needed for validating the access token: " + err.Error(),
+ HttpStatusCode: 500,
+ HttpStatusText: "Internal Server Error",
+ }
+ response.SendError(kong, nil)
+ }
+
+ authorizationHeader, err := kong.Request.GetHeader("Authorization")
+ if err != nil {
+ logger.Crit("unable to extract headers from request", err.Error())
+ response := GatewayError{
+ ErrorCode: "gateway.HEADER_EXTRACTION_FAILED",
+ ErrorTitle: "Header Extraction Failure",
+ ErrorDescription: "Unable to extract required request headers from the incoming request",
+ HttpStatusCode: 500,
+ HttpStatusText: "Internal Server Error",
+ }
+ response.SendError(kong, nil)
+ }
+
+ bearerToken := extractBearerToken(authorizationHeader)
+ if strings.TrimSpace(bearerToken) == "" {
+ logger.Warn("empty bearer token extracted from request")
+ response := GatewayError{
+ ErrorCode: "gateway.EMPTY_BEARER_TOKEN",
+ ErrorTitle: "Empty Bearer Token",
+ ErrorDescription: "The Bearer Token extracted from the request is empty.",
HttpStatusCode: 400,
HttpStatusText: "Bad Request",
}
- headers := map[string][]string{
+ response.SendError(kong, map[string][]string{
"WWW-Authenticate": {`Bearer scope="openid profile email", error="invalid_request", error_description="Authorization header malformed'"`},
- }
- response.SendError(kong, headers)
- return
+ })
}
- logger.Debug("validated authorization method")
- // now take the extracted token and parse it using the jwks and validate it
- logger.Debug("parsing and validating jwt from headers")
- jwks, err := JWKSCache.Get(context.Background(), JWKSUrl)
- if err != nil {
- logger.Err("error while getting jwks", err)
- response := GatewayError{}
- response.WrapError(err, kong)
- return
- }
- accessToken, err := jwt.ParseString(token, jwt.WithKeySet(jwks), jwt.WithAudience(OIDC_ClientID), jwt.WithValidate(true))
+ // now parse the bearer token and automatically validate it
+ accessToken, err := jwt.ParseString(bearerToken, jwt.WithKeySet(jwks), jwt.WithAudience(c.ClientID), jwt.WithValidate(true))
if err != nil {
// prepare an error response
- response := GatewayError{}
+ response := GatewayError{
+ HttpStatusCode: http.StatusUnauthorized,
+ HttpStatusText: http.StatusText(http.StatusUnauthorized),
+ }
switch {
case errors.Is(err, jwt.ErrTokenExpired()):
response.ErrorCode = "gateway.TOKEN_EXPIRED"
response.ErrorTitle = "Access Token Expired"
response.ErrorDescription = "The access token in the 'Authorization' header has expired"
- response.HttpStatusCode = http.StatusUnauthorized
- response.HttpStatusText = http.StatusText(response.HttpStatusCode)
- break
case errors.Is(err, jwt.ErrInvalidIssuedAt()):
response.ErrorCode = "gateway.TOKEN_ISSUED_IN_FUTURE"
response.ErrorTitle = "Access Token Issued In Future"
response.ErrorDescription = "The access token in the 'Authorization' header has been issued in the future"
- response.HttpStatusCode = http.StatusUnauthorized
- response.HttpStatusText = http.StatusText(response.HttpStatusCode)
- break
case errors.Is(err, jwt.ErrTokenNotYetValid()):
response.ErrorCode = "gateway.TOKEN_USED_TOO_EARLY"
response.ErrorTitle = "Access Token Used Too Early"
response.ErrorDescription = "The access token in the 'Authorization' header is not valid yet. please try again later"
- response.HttpStatusCode = http.StatusUnauthorized
- response.HttpStatusText = http.StatusText(response.HttpStatusCode)
- break
case errors.Is(err, jwt.ErrInvalidAudience()):
response.ErrorCode = "gateway.TOKEN_INVALID_AUDIENCE"
response.ErrorTitle = "Access Token Invalid Audience"
response.ErrorDescription = "The access token in the 'Authorization' header has not been issued for this platform"
- response.HttpStatusCode = http.StatusUnauthorized
- response.HttpStatusText = http.StatusText(response.HttpStatusCode)
- break
default:
- logger.Err("unknown error occurred while checking jwt", err)
+ logger.Crit("unknown error occurred while checking jwt", err.Error())
response.WrapError(err, kong)
return
}
response.SendError(kong, nil)
}
- logger.Debug("validated jwt")
// since the access token now has been validated to be active, request the
// userinfo endpoint using the access token as Authorization
- logger.Debug("requesting userinfo")
- userinfoRequest, err := http.NewRequest("GET", UserinfoEndpoint, nil)
+ userinfoRequest, err := http.NewRequest("GET", userinfoEndpoint, nil)
if err != nil {
- logger.Err("unable to create new request for userinfo", err)
+ logger.Crit("unable to create new request for userinfo", err)
response := GatewayError{}
response.WrapError(err, kong)
return
@@ -130,17 +257,16 @@ func (c *Configuration) Access(kong *pdk.PDK) {
httpClient := http.Client{}
userinfoResponse, err := httpClient.Do(userinfoRequest)
if err != nil {
- logger.Err("error while getting userinfo", err)
+ logger.Crit("error while getting userinfo", err)
response := GatewayError{}
response.WrapError(err, kong)
return
}
// now parse the userinfo response
- logger.Debug("parsing userinfo response")
var userinfo map[string]interface{}
err = json.NewDecoder(userinfoResponse.Body).Decode(&userinfo)
if err != nil {
- logger.Err("error while parsing userinfo", err)
+ logger.Crit("error while parsing userinfo", err)
response := GatewayError{}
response.WrapError(err, kong)
return
@@ -150,7 +276,7 @@ func (c *Configuration) Access(kong *pdk.PDK) {
subject, isSet := userinfo["sub"].(string)
if !isSet {
err := errors.New("userinfo missing 'subject'")
- logger.Err("invalid userinfo response", err)
+ logger.Crit("invalid userinfo response", err)
response := GatewayError{}
response.WrapError(err, kong)
return
@@ -159,7 +285,7 @@ func (c *Configuration) Access(kong *pdk.PDK) {
// now check if the userinfo really is for the access token
if accessToken.Subject() != subject {
err := errors.New("userinfo subject mismatch")
- logger.Err("invalid userinfo response", err)
+ logger.Crit("invalid userinfo response", err)
response := GatewayError{}
response.WrapError(err, kong)
return
@@ -169,7 +295,7 @@ func (c *Configuration) Access(kong *pdk.PDK) {
username, isSet := userinfo["preferred_username"].(string)
if !isSet {
err := errors.New("userinfo missing 'preferred_username'")
- logger.Err("invalid userinfo response", err)
+ logger.Crit("invalid userinfo response", err)
response := GatewayError{}
response.WrapError(err, kong)
return
@@ -203,24 +329,83 @@ func (c *Configuration) Access(kong *pdk.PDK) {
// now check if the userinfo response contains the staff marker
staffString, isSet := userinfo["staff"].(string)
if !isSet {
- kong.ServiceRequest.SetHeader("X-Is-Staff", "false")
- kong.ServiceRequest.SetHeader("X-Superuser", "false")
+ err := kong.ServiceRequest.SetHeader("X-Is-Staff", "false")
+ if err != nil {
+ logger.Err("unable to set staff header", err)
+ response := GatewayError{}
+ response.WrapError(err, kong)
+ return
+ }
+ err = kong.ServiceRequest.SetHeader("X-Superuser", "false")
+ if err != nil {
+ logger.Err("unable to set superuser header", err)
+ response := GatewayError{}
+ response.WrapError(err, kong)
+ return
+ }
} else {
// try to parse the staff string into a boolean
isStaff, err := strconv.ParseBool(staffString)
if err != nil || !isStaff {
- kong.ServiceRequest.SetHeader("X-Is-Staff", "false")
- kong.ServiceRequest.SetHeader("X-Superuser", "false")
-
+ err := kong.ServiceRequest.SetHeader("X-Is-Staff", "false")
+ if err != nil {
+ logger.Err("unable to set staff header", err)
+ response := GatewayError{}
+ response.WrapError(err, kong)
+ return
+ }
+ err = kong.ServiceRequest.SetHeader("X-Superuser", "false")
+ if err != nil {
+ logger.Err("unable to set superuser header", err)
+ response := GatewayError{}
+ response.WrapError(err, kong)
+ return
+ }
} else {
- kong.ServiceRequest.SetHeader("X-Is-Staff", "true")
- kong.ServiceRequest.SetHeader("X-Superuser", "true")
+ err := kong.ServiceRequest.SetHeader("X-Is-Staff", "true")
+ if err != nil {
+ logger.Err("unable to set staff header", err)
+ response := GatewayError{}
+ response.WrapError(err, kong)
+ return
+ }
+ err = kong.ServiceRequest.SetHeader("X-Superuser", "true")
+ if err != nil {
+ logger.Err("unable to set superuser header", err)
+ response := GatewayError{}
+ response.WrapError(err, kong)
+ return
+ }
}
}
// now add the headers to the downstream request, which set the groups
- kong.ServiceRequest.SetHeader("X-WISdoM-User", username)
- kong.ServiceRequest.SetHeader("X-Authenticated-User", username)
- kong.ServiceRequest.SetHeader("X-WISdoM-Groups", groupString)
- kong.ServiceRequest.SetHeader("X-Authenticated-Groups", groupString)
+ err = kong.ServiceRequest.SetHeader("X-WISdoM-User", username)
+ if err != nil {
+ logger.Err("unable to set username header", err)
+ response := GatewayError{}
+ response.WrapError(err, kong)
+ return
+ }
+ err = kong.ServiceRequest.SetHeader("X-Authenticated-User", username)
+ if err != nil {
+ logger.Err("unable to set authenticated user header", err)
+ response := GatewayError{}
+ response.WrapError(err, kong)
+ return
+ }
+ err = kong.ServiceRequest.SetHeader("X-WISdoM-Groups", groupString)
+ if err != nil {
+ logger.Err("unable to set groups header", err)
+ response := GatewayError{}
+ response.WrapError(err, kong)
+ return
+ }
+ err = kong.ServiceRequest.SetHeader("X-Authenticated-Groups", groupString)
+ if err != nil {
+ logger.Err("unable to set authenticated groups header", err)
+ response := GatewayError{}
+ response.WrapError(err, kong)
+ return
+ }
}
diff --git a/plugins/oidc/config.go b/plugins/oidc/config.go
index 05dec0f..b85d7d0 100644
--- a/plugins/oidc/config.go
+++ b/plugins/oidc/config.go
@@ -1,4 +1,6 @@
package main
type Configuration struct {
+ DiscoveryUri string `json:"discoveryUri"`
+ ClientID string `json:"clientID"`
}
diff --git a/plugins/oidc/globals.go b/plugins/oidc/globals.go
deleted file mode 100644
index 8a910ea..0000000
--- a/plugins/oidc/globals.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package main
-
-import "github.com/lestrrat-go/jwx/v2/jwk"
-
-var JWKSCache *jwk.Cache
-var JWKSUrl string
-var OIDC_ClientID string
-var UserinfoEndpoint string
diff --git a/plugins/oidc/init.go b/plugins/oidc/init.go
deleted file mode 100644
index 73e703b..0000000
--- a/plugins/oidc/init.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package main
-
-import (
- "context"
- "encoding/json"
- "log"
- "net/http"
- "os"
-
- "github.com/lestrrat-go/jwx/v2/jwk"
-)
-
-func init() {
- // get the environment variable pointing to the openid connect configuration
- // on the authorization server
- oidcConnectUrl, isSet := os.LookupEnv("OIDC_AUTHORITY")
- if !isSet {
- log.Fatal("OIDC_AUTHORITY environment variable is not set")
- }
-
- // get the configuration of the openid connect server
- res, err := http.Get(oidcConnectUrl)
- if err != nil {
- log.Fatal("unable to load openid connect configuration")
- }
- var oidcConfiguration map[string]interface{}
- err = json.NewDecoder(res.Body).Decode(&oidcConfiguration)
- if err != nil {
- log.Fatal("unable to parse open id connect configuration")
- }
-
- // now retrieve the jwks url and the introspection url
- UserinfoEndpoint = oidcConfiguration["userinfo_endpoint"].(string)
- JWKSUrl = oidcConfiguration["jwks_uri"].(string)
- // now download the jwks used to sign the access tokens
- JWKSCache = jwk.NewCache(context.Background())
- err = JWKSCache.Register(JWKSUrl)
- if err != nil {
- log.Fatal("unable to register JWKS URL in JWKSCache")
- }
-
- // now get the Client ID
- OIDC_ClientID, isSet = os.LookupEnv("OIDC_CLIENT_ID")
- if !isSet {
- log.Fatal("OIDC_CLIENT_ID environment variable is not set")
- }
-}