diff options
| author | mo khan <mo@mokhan.ca> | 2025-05-15 14:42:08 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-05-15 14:42:08 -0600 |
| commit | c151c1a77d31c5e01885691b6df1ea7b0be0b0e5 (patch) | |
| tree | 254aed8be6abaffaeba71df5bcb35d41d52bb2b2 | |
| parent | 3d01a69471fc4f0ae9f2f4145620b6aea50f2216 (diff) | |
| parent | b6968005e1e1758e37edc7830c02e2217ee5fd90 (diff) | |
Merge branch 'envoy-cleanup' into 'main'
Delete code that is now handled by envoy
See merge request gitlab-org/software-supply-chain-security/authorization/sparkled!7
37 files changed, 84 insertions, 909 deletions
@@ -1,8 +1,5 @@ APP_ENV=development -BIND_ADDR=:8080 HMAC_SESSION_SECRET=session_secret -HOST=localhost OAUTH_CLIENT_ID=client_id OAUTH_CLIENT_SECRET=client_secret -OAUTH_REDIRECT_URL=http://localhost:8080/session/callback OIDC_ISSUER=https://gitlab.com diff --git a/.runway/env-production.yml b/.runway/env-production.yml index 9c1f873..fd18897 100644 --- a/.runway/env-production.yml +++ b/.runway/env-production.yml @@ -1,5 +1,3 @@ APP_ENV: "production" -HOST: "sparkle.runway.gitlab.net" OAUTH_CLIENT_ID: "75656280b7ca60223b060b57c4eb98a8a324878531efeccafc1d25709dbee5c9" -OAUTH_REDIRECT_URL: "https://sparkle.runway.gitlab.net/session/callback" OIDC_ISSUER: "https://gitlab.com" diff --git a/.runway/env-staging.yml b/.runway/env-staging.yml index 66df510..7a1192f 100644 --- a/.runway/env-staging.yml +++ b/.runway/env-staging.yml @@ -1,5 +1,3 @@ APP_ENV: "production" -HOST: "sparkle.staging.runway.gitlab.net" OAUTH_CLIENT_ID: "786e37c8d2207d200f735379ad52579c452948222f9affc7a45e74bd7074ad3c" -OAUTH_REDIRECT_URL: "https://sparkle.staging.runway.gitlab.net/session/callback" OIDC_ISSUER: "https://staging.gitlab.com" @@ -6,7 +6,6 @@ COPY . ./ RUN go build -o /bin/sparkled ./cmd/sparkled/main.go FROM envoyproxy/envoy:v1.34-latest -ENV BIND_ADDR=":8080" EXPOSE 8080 9901 10000 WORKDIR /opt/sparkle/ RUN mkdir -p bin etc public @@ -15,4 +14,4 @@ COPY --from=build /app/public public COPY etc/ etc COPY bin/*.sh bin/ RUN chmod +x bin/*.sh -CMD ["/opt/sparkle/bin/init.sh"] +CMD ["/opt/sparkle/bin/entrypoint.sh"] @@ -36,7 +36,7 @@ build-builder-image: @docker build --target build --tag $(IMAGE_TAG) . run: clean build - @godotenv -f .env.local,.env ./bin/init.sh + @godotenv -f .env.local,.env ./bin/entrypoint.sh run-image: clean build-image @docker run --rm --network host --env-file .env.local -p 10000:10000 -p 9901:9901 -p 8080:8080 -it $(IMAGE_TAG) @@ -67,7 +67,11 @@ The Authorization team is researching ways to evolve GitLab's authorization stac ```bash $ vim .env.local ``` + Follow these instructions to [create a user-owned application](https://docs.gitlab.com/integration/oauth_provider/#create-a-user-owned-application) and set the `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET`. 6. Start the service ```bash $ make run ``` +7. Open a browser and navigate to: http://localhost:10000 + +See the [DEVELOPMENT](./share/man/development.md) guide for additional instructions. @@ -4,16 +4,16 @@ import ( "net/http" "path/filepath" + "github.com/coreos/go-oidc/v3/oidc" "github.com/rs/zerolog" "github.com/xlgmokha/x/pkg/ioc" "github.com/xlgmokha/x/pkg/log" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/controllers/dashboard" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/controllers/health" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/controllers/sessions" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/controllers/sparkles" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/middleware" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" ) type Mountable interface { @@ -26,7 +26,6 @@ func New(rootDir string) http.Handler { mountable := []Mountable{ ioc.MustResolve[*dashboard.Controller](ioc.Default), ioc.MustResolve[*health.Controller](ioc.Default), - ioc.MustResolve[*sessions.Controller](ioc.Default), ioc.MustResolve[*sparkles.Controller](ioc.Default), } for _, m := range mountable { @@ -37,9 +36,11 @@ func New(rootDir string) http.Handler { mux.Handle("GET /", http.FileServer(dir)) logger := ioc.MustResolve[*zerolog.Logger](ioc.Default) - oidc := ioc.MustResolve[*oidc.OpenID](ioc.Default) users := ioc.MustResolve[domain.Repository[*domain.User]](ioc.Default) - - chain := middleware.IDToken(oidc, middleware.IDTokenFromSessionCookie)(middleware.User(users)(mux)) + chain := middleware.IDToken( + ioc.MustResolve[*oidc.Provider](ioc.Default), + ioc.MustResolve[*oidc.Config](ioc.Default), + middleware.FromCookie(cfg.IDTokenCookie), + )(middleware.User(users)(mux)) return log.HTTP(logger)(chain) } diff --git a/app/cfg/cfg.go b/app/cfg/cfg.go index 1dffa16..b423413 100644 --- a/app/cfg/cfg.go +++ b/app/cfg/cfg.go @@ -1,20 +1,17 @@ package cfg import ( + "github.com/coreos/go-oidc/v3/oidc" "github.com/xlgmokha/x/pkg/context" "github.com/xlgmokha/x/pkg/env" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" ) var CurrentUser context.Key[*domain.User] = context.Key[*domain.User]("current_user") var IDToken context.Key[*oidc.IDToken] = context.Key[*oidc.IDToken]("id_token") var OIDCIssuer string = env.Fetch("OIDC_ISSUER", "https://gitlab.com") var OAuthClientID string = env.Fetch("OAUTH_CLIENT_ID", "client_id") -var OAuthClientSecret string = env.Fetch("OAUTH_CLIENT_SECRET", "client_secret") -var OAuthRedirectURL string = env.Fetch("OAUTH_REDIRECT_URL", "") -var CSRFCookie string = "__csrf" var IDTokenCookie string = "id_token" var BearerTokenCookie string = "bearer_token" var RefreshTokenCookie string = "refresh_token" diff --git a/app/controllers/sessions/controller.go b/app/controllers/sessions/controller.go deleted file mode 100644 index 898244c..0000000 --- a/app/controllers/sessions/controller.go +++ /dev/null @@ -1,130 +0,0 @@ -package sessions - -import ( - "net/http" - - "github.com/xlgmokha/x/pkg/cookie" - "github.com/xlgmokha/x/pkg/env" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/middleware" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/web" -) - -type Controller struct { - svc *Service -} - -func New(cfg *oidc.OpenID, http *http.Client) *Controller { - return &Controller{ - svc: NewService(cfg, http), - } -} - -func (c *Controller) MountTo(mux *http.ServeMux) { - mux.HandleFunc("GET /session/new", c.New) - mux.HandleFunc("GET /session", c.Show) - mux.HandleFunc("GET /session/callback", c.Create) - mux.HandleFunc("POST /session/destroy", c.Destroy) -} - -func (c *Controller) New(w http.ResponseWriter, r *http.Request) { - if middleware.IsLoggedIn(r) { - http.Redirect(w, r, "/dashboard", http.StatusFound) - return - } - - url, nonce := c.svc.GenerateRedirectURL(r) - cookie.Write(w, web.NewCookie(cfg.CSRFCookie, nonce)) - c.redirectTo(w, r, url) -} - -func (c *Controller) Show(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie(cfg.IDTokenCookie) - if err != nil { - pls.LogError(r.Context(), err) - w.WriteHeader(http.StatusNotFound) - return - } - - token, err := c.svc.JWTBody(r.Context(), oidc.RawToken(cookie.Value)) - if err != nil { - pls.LogError(r.Context(), err) - w.WriteHeader(http.StatusNotFound) - return - } - - w.WriteHeader(http.StatusOK) - w.Write(token) -} - -/* -This is the callback endpoint from the OIDC Provider: - -It will exchange a grant for the following tokens: - -* access_token -* id_token -* refresh_token - -# These tokens are encoded as a Base64 JSON string and stored in a session cookie - -For Example: - -The following is an example of the base64 value stored in the session cookie: - -```base64 -eyJhY2Nlc3NfdG9rZW4iOiJkNzQ2ZTVmMGQ2NmYyNTgxM2ZjYzIyYzNjY2E0YmYxYjNmOTQwMjQ5NTkxYTg4YzZmMDBjMDQzZTMxYTVkZDRhIiwidG9rZW5fdHlwZSI6IkJlYXJlciIsInJlZnJlc2hfdG9rZW4iOiIyZGUxNTFhMGQ2ZGJhNzU5MDVmNGRmZTRlZThkNmQ5MGNjZmRkYzYxYjJkNGRlNzU2ODRiYzFjYmY5YjE0ODBlIiwiZXhwaXJ5IjoiMjAyNS0wNC0yNVQxMTozODozOS4zNDAwNDc4MjQtMDY6MDAiLCJpZF90b2tlbiI6ImV5SjBlWEFpT2lKS1YxUWlMQ0pyYVdRaU9pSjBaREJUYldSS1VUUnhVR2cxY1U1TGVrMHlOakJEV0hneVZXZ3RkMmhITFUxRWFtOVBTMWRtZERoRklpd2lZV3huSWpvaVVsTXlOVFlpZlEuZXlKcGMzTWlPaUpvZEhSd09pOHZaMlJyTG5SbGMzUTZNekF3TUNJc0luTjFZaUk2SWpFaUxDSmhkV1FpT2lKbE16RmxNV1JoTUdJNFpqWmlObVV6TldOaE56QmpOemt3WWpFell6QTBNRFpsTkRSaFkyRTJZakppWmpZM1pqVTFaR1UzTXpVMVlUazNPV0V5TWpSbUlpd2laWGh3SWpveE56UTFOVGsxTmpNNUxDSnBZWFFpT2pFM05EVTFPVFUxTVRrc0ltRjFkR2hmZEdsdFpTSTZNVGMwTlRVd016TXpPQ3dpYzNWaVgyeGxaMkZqZVNJNklqSTBOelJqWmpCaU1qSXhNVFk0T0dFMU56STVOMkZqWlRCbE1qWXdZVEUxT1RRME56VTBaREUyWWpGaVpEUXlZemxrTmpjM09XTTVNREF6TmpjNE1EY2lMQ0p1WVcxbElqb2lRV1J0YVc1cGMzUnlZWFJ2Y2lJc0ltNXBZMnR1WVcxbElqb2ljbTl2ZENJc0luQnlaV1psY25KbFpGOTFjMlZ5Ym1GdFpTSTZJbkp2YjNRaUxDSmxiV0ZwYkNJNkltRmtiV2x1UUdWNFlXMXdiR1V1WTI5dElpd2laVzFoYVd4ZmRtVnlhV1pwWldRaU9uUnlkV1VzSW5CeWIyWnBiR1VpT2lKb2RIUndPaTh2WjJSckxuUmxjM1E2TXpBd01DOXliMjkwSWl3aWNHbGpkSFZ5WlNJNkltaDBkSEJ6T2k4dmQzZDNMbWR5WVhaaGRHRnlMbU52YlM5aGRtRjBZWEl2TWpVNFpEaGtZemt4Tm1SaU9HTmxZVEpqWVdaaU5tTXpZMlF3WTJJd01qUTJaV1psTURZeE5ESXhaR0prT0RObFl6TmhNelV3TkRJNFkyRmlaR0UwWmo5elBUZ3dKbVE5YVdSbGJuUnBZMjl1SWl3aVozSnZkWEJ6WDJScGNtVmpkQ0k2V3lKbmFYUnNZV0l0YjNKbklpd2lkRzl2YkdKdmVDSXNJbTFoYzNOZmFXNXpaWEowWDJkeWIzVndYMTh3WHpFd01DSXNJbU4xYzNSdmJTMXliMnhsY3kxeWIyOTBMV2R5YjNWd0wyRmhJaXdpWTNWemRHOXRMWEp2YkdWekxYSnZiM1F0WjNKdmRYQXZZV0V2WVdGaElpd2laMjUxZDJkbGRDSXNJa052YlcxcGREUTFNU0lzSW1waGMyaHJaVzVoY3lJc0ltWnNhV2RvZEdweklpd2lkSGRwZEhSbGNpSXNJbWRwZEd4aFlpMWxlR0Z0Y0d4bGN5SXNJbWRwZEd4aFlpMWxlR0Z0Y0d4bGN5OXpaV04xY21sMGVTSXNJalF4TWpjd09DSXNJbWRwZEd4aFlpMWxlR0Z0Y0d4bGN5OWtaVzF2TFdkeWIzVndJaXdpWTNWemRHOXRMWEp2YkdWekxYSnZiM1F0WjNKdmRYQWlMQ0kwTXpRd05EUXRaM0p2ZFhBdE1TSXNJalF6TkRBME5DMW5jbTkxY0MweUlpd2laMmwwYkdGaUxXOXlaekVpTENKbmFYUnNZV0l0YjNKbkwzTmxZM1Z5WlNJc0ltZHBkR3hoWWkxdmNtY3ZjMlZqZFhKbEwyMWhibUZuWlhKeklpd2laMmwwYkdGaUxXOXlaeTl6WldOMWNtbDBlUzF3Y205a2RXTjBjeUlzSW1kcGRHeGhZaTF2Y21jdmMyVmpkWEpwZEhrdGNISnZaSFZqZEhNdllXNWhiSGw2WlhKeklsMTkuZ2Fwc01vcVJSOWZyVS1MQTVYaUtaMC1PYWVkNG1SSXNiOG5JbEJuVUswM1lTVTY4R2Y5WlhLV1F0VHMwbGpCekNxLWZhOXBWY0I5YU1TUnZ0bGJuZ0doNFU0aWpWUGU0am5vWC01VXJZaTJpTEYxdDJ5VGRFWEhDSWg2bXNBLXJEUTUwR2UxNUtaSmRXTE0tbFo0VGhNNENlbGpQMWF4NUJjeUV0UG1pcmZIaHppR3pKYmFEczRVMk5aaW1hcHo3Q1hSX3FaeHI0ajYyQW00dmVYXzhPaDFhT1I2bUtDMTlCZUlqeFozWlZ3Z0x3UUVsaFlLcEhUWTRSS2ZnUkh3TVlXVGZIZUF0VTM4UTV0VW9DSGU0RW1wcEIza0x0aW1Gemp2YWhnMGRjazBzc3FTWkh4X252cXJldjctSVdKa096OVRSVG04SU1xU3h4OUxxd1pCVFRRIn0= -``` - -When it is decoded it has the following form: - -```json - - { - "access_token": "d746e5f0d66f25813fcc22c3cca4bf1b3f940249591a88c6f00c043e31a5dd4a", - "token_type": "Bearer", - "refresh_token": "2de151a0d6dba75905f4dfe4ee8d6d90ccfddc61b2d4de75684bc1cbf9b1480e", - "expiry": "2025-04-25T11:38:39.340047824-06:00", - "id_token": "eyJ0eXAiOiJKV1QiLCJraWQiOiJ0ZDBTbWRKUTRxUGg1cU5Lek0yNjBDWHgyVWgtd2hHLU1Eam9PS1dmdDhFIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwOi8vZ2RrLnRlc3Q6MzAwMCIsInN1YiI6IjEiLCJhdWQiOiJlMzFlMWRhMGI4ZjZiNmUzNWNhNzBjNzkwYjEzYzA0MDZlNDRhY2E2YjJiZjY3ZjU1ZGU3MzU1YTk3OWEyMjRmIiwiZXhwIjoxNzQ1NTk1NjM5LCJpYXQiOjE3NDU1OTU1MTksImF1dGhfdGltZSI6MTc0NTUwMzMzOCwic3ViX2xlZ2FjeSI6IjI0NzRjZjBiMjIxMTY4OGE1NzI5N2FjZTBlMjYwYTE1OTQ0NzU0ZDE2YjFiZDQyYzlkNjc3OWM5MDAzNjc4MDciLCJuYW1lIjoiQWRtaW5pc3RyYXRvciIsIm5pY2tuYW1lIjoicm9vdCIsInByZWZlcnJlZF91c2VybmFtZSI6InJvb3QiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInByb2ZpbGUiOiJodHRwOi8vZ2RrLnRlc3Q6MzAwMC9yb290IiwicGljdHVyZSI6Imh0dHBzOi8vd3d3LmdyYXZhdGFyLmNvbS9hdmF0YXIvMjU4ZDhkYzkxNmRiOGNlYTJjYWZiNmMzY2QwY2IwMjQ2ZWZlMDYxNDIxZGJkODNlYzNhMzUwNDI4Y2FiZGE0Zj9zPTgwJmQ9aWRlbnRpY29uIiwiZ3JvdXBzX2RpcmVjdCI6WyJnaXRsYWItb3JnIiwidG9vbGJveCIsIm1hc3NfaW5zZXJ0X2dyb3VwX18wXzEwMCIsImN1c3RvbS1yb2xlcy1yb290LWdyb3VwL2FhIiwiY3VzdG9tLXJvbGVzLXJvb3QtZ3JvdXAvYWEvYWFhIiwiZ251d2dldCIsIkNvbW1pdDQ1MSIsImphc2hrZW5hcyIsImZsaWdodGpzIiwidHdpdHRlciIsImdpdGxhYi1leGFtcGxlcyIsImdpdGxhYi1leGFtcGxlcy9zZWN1cml0eSIsIjQxMjcwOCIsImdpdGxhYi1leGFtcGxlcy9kZW1vLWdyb3VwIiwiY3VzdG9tLXJvbGVzLXJvb3QtZ3JvdXAiLCI0MzQwNDQtZ3JvdXAtMSIsIjQzNDA0NC1ncm91cC0yIiwiZ2l0bGFiLW9yZzEiLCJnaXRsYWItb3JnL3NlY3VyZSIsImdpdGxhYi1vcmcvc2VjdXJlL21hbmFnZXJzIiwiZ2l0bGFiLW9yZy9zZWN1cml0eS1wcm9kdWN0cyIsImdpdGxhYi1vcmcvc2VjdXJpdHktcHJvZHVjdHMvYW5hbHl6ZXJzIl19.gapsMoqRR9frU-LA5XiKZ0-Oaed4mRIsb8nIlBnUK03YSU68Gf9ZXKWQtTs0ljBzCq-fa9pVcB9aMSRvtlbngGh4U4ijVPe4jnoX-5UrYi2iLF1t2yTdEXHCIh6msA-rDQ50Ge15KZJdWLM-lZ4ThM4CeljP1ax5BcyEtPmirfHhziGzJbaDs4U2NZimapz7CXR_qZxr4j62Am4veX_8Oh1aOR6mKC19BeIjxZ3ZVwgLwQElhYKpHTY4RKfgRHwMYWTfHeAtU38Q5tUoCHe4EmppB3kLtimFzjvahg0dck0ssqSZHx_nvqrev7-IWJkOz9TRTm8IMqSxx9LqwZBTTQ" - } - -``` -*/ -func (c *Controller) Create(w http.ResponseWriter, r *http.Request) { - if middleware.IsLoggedIn(r) { - http.Redirect(w, r, "/dashboard", http.StatusFound) - return - } - - tokens, err := c.svc.Exchange(r) - if err != nil { - pls.LogError(r.Context(), err) - w.WriteHeader(http.StatusBadRequest) - return - } - - web.ExpireCookie(w, cfg.CSRFCookie) - web.WriteCookie(w, web.NewCookie(cfg.IDTokenCookie, tokens.IDToken.String())) - web.WriteCookie(w, web.NewCookie(cfg.BearerTokenCookie, tokens.AccessToken)) - web.WriteCookie(w, web.NewCookie(cfg.RefreshTokenCookie, tokens.RefreshToken)) - - c.redirectTo(w, r, "/dashboard") -} - -func (c *Controller) Destroy(w http.ResponseWriter, r *http.Request) { - web.ExpireCookie(w, cfg.CSRFCookie) - web.ExpireCookie(w, cfg.IDTokenCookie) - web.ExpireCookie(w, cfg.BearerTokenCookie) - web.ExpireCookie(w, cfg.RefreshTokenCookie) - c.redirectTo(w, r, "/") -} - -func (c *Controller) redirectTo(w http.ResponseWriter, r *http.Request, location string) { - if env.Fetch("APP_ENV", "development") == "production" { - (&RedirectDTO{URL: location}).ServeHTTP(w, r) - } else { - http.Redirect(w, r, location, http.StatusFound) - } -} diff --git a/app/controllers/sessions/controller_test.go b/app/controllers/sessions/controller_test.go deleted file mode 100644 index 3e7f662..0000000 --- a/app/controllers/sessions/controller_test.go +++ /dev/null @@ -1,253 +0,0 @@ -package sessions - -import ( - "net/http" - "net/url" - "testing" - - "github.com/oauth2-proxy/mockoidc" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/xlgmokha/x/pkg/serde" - "github.com/xlgmokha/x/pkg/test" - "github.com/xlgmokha/x/pkg/x" - xcfg "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/web" -) - -func TestSessions(t *testing.T) { - srv := oidc.NewTestServer(t) - defer srv.Close() - - clientID := srv.MockOIDC.Config().ClientID - clientSecret := srv.MockOIDC.Config().ClientSecret - cfg := oidc.New( - srv.Provider, - clientID, - clientSecret, - "callback_url", - ) - controller := New(cfg, http.DefaultClient) - mux := http.NewServeMux() - controller.MountTo(mux) - - t.Run("GET /session/new", func(t *testing.T) { - t.Run("without an authenticated session", func(t *testing.T) { - r, w := test.RequestResponse("GET", "/session/new") - - mux.ServeHTTP(w, r) - - t.Run("redirect to the OIDC Provider", func(t *testing.T) { - require.Equal(t, http.StatusFound, w.Code) - require.NotEmpty(t, w.Header().Get("Location")) - redirectURL, err := url.Parse(w.Header().Get("Location")) - require.NoError(t, err) - assert.Equal(t, srv.AuthorizationEndpoint(), redirectURL.Scheme+"://"+redirectURL.Host+redirectURL.Path) - assert.NotEmpty(t, redirectURL.Query().Get("state")) - assert.Equal(t, srv.MockOIDC.Config().ClientID, redirectURL.Query().Get("client_id")) - assert.Equal(t, "openid profile email", redirectURL.Query().Get("scope")) - assert.Equal(t, cfg.Config.ClientID, redirectURL.Query().Get("audience")) - assert.Equal(t, cfg.Config.RedirectURL, redirectURL.Query().Get("redirect_uri")) - assert.Equal(t, "code", redirectURL.Query().Get("response_type")) - }) - - t.Run("generates a CSRF token", func(t *testing.T) { - cookieHeader := w.Header().Get("Set-Cookie") - require.NotEmpty(t, cookieHeader) - - cookie, err := http.ParseSetCookie(w.Header().Get("Set-Cookie")) - require.NoError(t, err) - require.NotZero(t, cookie) - - assert.Equal(t, xcfg.CSRFCookie, cookie.Name) - }) - }) - - t.Run("with an active authenicated session", func(t *testing.T) { - t.Run("redirects to the dashboard", func(t *testing.T) { - user := &domain.User{} - r, w := test.RequestResponse( - "GET", - "/session/new", - test.WithContextKeyValue(t.Context(), xcfg.CurrentUser, user), - ) - - mux.ServeHTTP(w, r) - - require.Equal(t, http.StatusFound, w.Code) - assert.Equal(t, "/dashboard", w.Header().Get("Location")) - }) - }) - }) - - t.Run("GET /session/callback", func(t *testing.T) { - t.Run("with an invalid csrf token", func(t *testing.T) { - user := mockoidc.DefaultUser() - code := srv.CreateAuthorizationCodeFor(user) - nonce := pls.GenerateRandomHex(32) - - r, w := test.RequestResponse( - "GET", - "/session/callback?code="+code+"&state=invalid", - test.WithCookie(web.NewCookie(xcfg.CSRFCookie, nonce)), - ) - - mux.ServeHTTP(w, r) - - require.Equal(t, http.StatusBadRequest, w.Code) - }) - - t.Run("with an invalid authorization code grant", func(t *testing.T) { - r, w := test.RequestResponse("GET", "/session/callback?code=invalid") - - mux.ServeHTTP(w, r) - - assert.Equal(t, http.StatusBadRequest, w.Code) - }) - - t.Run("when already logged in", func(t *testing.T) { - t.Run("redirects to the dashboard", func(t *testing.T) { - user := &domain.User{} - r, w := test.RequestResponse( - "GET", - "/session/callback?code=valid", - test.WithContextKeyValue(t.Context(), xcfg.CurrentUser, user), - ) - - mux.ServeHTTP(w, r) - - require.Equal(t, http.StatusFound, w.Code) - assert.Equal(t, "/dashboard", w.Header().Get("Location")) - }) - }) - - t.Run("with a valid authorization code grant", func(t *testing.T) { - user := mockoidc.DefaultUser() - code := srv.CreateAuthorizationCodeFor(user) - nonce := pls.GenerateRandomHex(32) - - r, w := test.RequestResponse( - "GET", - "/session/callback?code="+code+"&state="+nonce, - test.WithCookie(web.NewCookie(xcfg.CSRFCookie, nonce)), - ) - - mux.ServeHTTP(w, r) - - cookieValues := w.Header().Values("Set-Cookie") - cookies := x.Map(cookieValues, func(line string) *http.Cookie { - ck, err := http.ParseSetCookie(line) - require.NoError(t, err) - return ck - }) - - t.Run("stores the id token in a session cookie", func(t *testing.T) { - cookie := x.Find(cookies, func(item *http.Cookie) bool { - return item.Name == xcfg.IDTokenCookie - }) - - require.NotNil(t, cookie) - - idToken := srv.Verify(cookie.Value) - assert.Equal(t, user.Subject, idToken.Subject) - }) - - t.Run("stores the access token in a session cookie", func(t *testing.T) { - cookie := x.Find(cookies, func(item *http.Cookie) bool { - return item.Name == xcfg.BearerTokenCookie - }) - - require.NotNil(t, cookie) - - keypair, err := mockoidc.DefaultKeypair() - require.NoError(t, err) - - token, err := keypair.VerifyJWT(cookie.Value, nil) - require.NoError(t, err) - - sub, err := token.Claims.GetSubject() - require.NoError(t, err) - assert.Equal(t, user.Subject, sub) - }) - - t.Run("stores the refresh token in a session cookie", func(t *testing.T) { - cookie := x.Find(cookies, func(item *http.Cookie) bool { - return item.Name == xcfg.RefreshTokenCookie - }) - require.NotNil(t, cookie) - - keypair, err := mockoidc.DefaultKeypair() - require.NoError(t, err) - - token, err := keypair.VerifyJWT(cookie.Value, nil) - require.NoError(t, err) - - sub, err := token.Claims.GetSubject() - require.NoError(t, err) - assert.Equal(t, user.Subject, sub) - }) - - t.Run("redirects to the homepage", func(t *testing.T) { - require.Equal(t, http.StatusFound, w.Code) - assert.Equal(t, "/dashboard", w.Header().Get("Location")) - }) - - t.Run("applies the appropriate cookie settings", func(t *testing.T) { - x.Each(cookies, func(cookie *http.Cookie) { - t.Logf("%v: %v\n", cookie.Name, cookie.Value) - assert.Equal(t, "/", cookie.Path) - assert.NotEmpty(t, cookie.Name) - assert.True(t, cookie.HttpOnly) - assert.True(t, cookie.Secure) - }) - }) - }) - }) - - t.Run("GET /session", func(t *testing.T) { - t.Run("with an id_token cookie", func(t *testing.T) { - user := mockoidc.DefaultUser() - _, rawIDToken := srv.CreateTokensFor(user) - cookie := &http.Cookie{Name: xcfg.IDTokenCookie, Value: rawIDToken} - r, w := test.RequestResponse("GET", "/session", test.WithCookie(cookie)) - - mux.ServeHTTP(w, r) - - require.Equal(t, http.StatusOK, w.Code) - items, err := serde.FromJSON[map[string]interface{}](w.Body) - require.NoError(t, err) - assert.Equal(t, srv.Issuer(), items["iss"]) - }) - - t.Run("without an id_token cookie", func(t *testing.T) { - r, w := test.RequestResponse("GET", "/session") - - mux.ServeHTTP(w, r) - - require.Equal(t, http.StatusNotFound, w.Code) - }) - }) - - t.Run("POST /session/destroy", func(t *testing.T) { - t.Run("clears the session cookie", func(t *testing.T) { - cookie := web.NewCookie(xcfg.IDTokenCookie, "value") - r, w := test.RequestResponse("POST", "/session/destroy", test.WithCookie(cookie)) - - mux.ServeHTTP(w, r) - - require.Equal(t, http.StatusFound, w.Code) - assert.Equal(t, "/", w.Header().Get("Location")) - - expected := []string{ - "__csrf=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; HttpOnly; Secure", - "id_token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; HttpOnly; Secure", - "bearer_token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; HttpOnly; Secure", - "refresh_token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; HttpOnly; Secure", - } - assert.ElementsMatch(t, expected, w.Header().Values("Set-Cookie")) - }) - }) -} diff --git a/app/controllers/sessions/dto.go b/app/controllers/sessions/dto.go deleted file mode 100644 index a3ce6ba..0000000 --- a/app/controllers/sessions/dto.go +++ /dev/null @@ -1,19 +0,0 @@ -package sessions - -import ( - "net/http" - - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/views" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" -) - -type RedirectDTO struct { - URL string -} - -func (d *RedirectDTO) ServeHTTP(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Header().Add("Content-Type", "text/html") - - pls.LogError(r.Context(), views.Render(w, "sessions/redirect", d)) -} diff --git a/app/controllers/sessions/service.go b/app/controllers/sessions/service.go deleted file mode 100644 index c0466e4..0000000 --- a/app/controllers/sessions/service.go +++ /dev/null @@ -1,80 +0,0 @@ -package sessions - -import ( - "context" - "encoding/base64" - "errors" - "net/http" - "net/url" - "strings" - - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" - "golang.org/x/oauth2" -) - -type Service struct { - cfg *oidc.OpenID - http *http.Client -} - -func NewService(cfg *oidc.OpenID, http *http.Client) *Service { - return &Service{ - cfg: cfg, - http: http, - } -} - -func (svc *Service) GenerateRedirectURL(r *http.Request) (string, string) { - nonce := pls.GenerateRandomHex(32) - url := svc.cfg.Config.AuthCodeURL( - nonce, - oauth2.SetAuthURLParam("audience", svc.cfg.Config.ClientID), - oauth2.SetAuthURLParam("redirect_uri", svc.redirectURIFor(r)), - ) - return url, nonce -} - -func (svc *Service) Exchange(r *http.Request) (*oidc.Tokens, error) { - cookies := r.CookiesNamed(cfg.CSRFCookie) - if len(cookies) != 1 { - return nil, errors.New("Missing CSRF token") - } - - state := r.URL.Query().Get("state") - if state != cookies[0].Value { - return nil, errors.New("Invalid CSRF token") - } - - ctx := context.WithValue(r.Context(), oauth2.HTTPClient, svc.http) - - token, err := svc.cfg.Config.Exchange(ctx, r.URL.Query().Get("code")) - if err != nil { - return nil, err - } - - tokens := &oidc.Tokens{Token: token} - if rawIDToken, ok := token.Extra("id_token").(string); ok { - tokens.IDToken = oidc.RawToken(rawIDToken) - } - return tokens, nil -} - -func (svc *Service) JWTBody(ctx context.Context, raw oidc.RawToken) ([]byte, error) { - sections := strings.SplitN(raw.String(), ".", 3) - if len(sections) != 3 { - return nil, errors.New("Invalid JWT") - } - - return base64.RawURLEncoding.DecodeString(sections[1]) -} - -func (svc *Service) redirectURIFor(r *http.Request) string { - if len(svc.cfg.Config.RedirectURL) > 0 { - return svc.cfg.Config.RedirectURL - } - redirectURL, _ := url.Parse(r.URL.String()) - redirectURL.Path = "/session/callback" - return redirectURL.String() -} diff --git a/app/controllers/sessions/service_test.go b/app/controllers/sessions/service_test.go deleted file mode 100644 index 05baa2f..0000000 --- a/app/controllers/sessions/service_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package sessions - -import ( - "net/http" - "testing" - - "github.com/oauth2-proxy/mockoidc" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/xlgmokha/x/pkg/test" - xcfg "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/web" -) - -func TestService(t *testing.T) { - srv := oidc.NewTestServer(t) - defer srv.Close() - - clientID := srv.MockOIDC.Config().ClientID - clientSecret := srv.MockOIDC.Config().ClientSecret - cfg := oidc.New( - srv.Provider, - clientID, - clientSecret, - "/session/callback", - ) - svc := NewService(cfg, http.DefaultClient) - - t.Run("Exchange", func(t *testing.T) { - t.Run("when the csrf token is missing", func(t *testing.T) { - r := test.Request("GET", "/session/callback") - tokens, err := svc.Exchange(r) - - require.Error(t, err) - assert.Nil(t, tokens) - }) - - t.Run("when the csrf token is invalid", func(t *testing.T) { - user := mockoidc.DefaultUser() - code := srv.CreateAuthorizationCodeFor(user) - nonce := pls.GenerateRandomHex(32) - - r := test.Request( - "GET", - "/session/callback?code="+code+"&state=invalid", - test.WithCookie(web.NewCookie(xcfg.CSRFCookie, nonce)), - ) - tokens, err := svc.Exchange(r) - - require.Error(t, err) - assert.Nil(t, tokens) - }) - - t.Run("with an invalid authorization code grant", func(t *testing.T) { - nonce := pls.GenerateRandomHex(32) - - r := test.Request( - "GET", "/session/callback?code=invalid", - test.WithCookie(web.NewCookie(xcfg.CSRFCookie, nonce)), - ) - - tokens, err := svc.Exchange(r) - - require.Error(t, err) - assert.Nil(t, tokens) - }) - - t.Run("with a valid grant", func(t *testing.T) { - user := mockoidc.DefaultUser() - code := srv.CreateAuthorizationCodeFor(user) - nonce := pls.GenerateRandomHex(32) - - r := test.Request( - "GET", - "/session/callback?code="+code+"&state="+nonce, - test.WithCookie(web.NewCookie(xcfg.CSRFCookie, nonce)), - ) - - tokens, err := svc.Exchange(r) - - require.NoError(t, err) - assert.NotNil(t, tokens) - assert.NotEmpty(t, tokens.AccessToken) - assert.NotEmpty(t, tokens.Expiry) - assert.NotEmpty(t, tokens.TokenType) - assert.NotEmpty(t, tokens.RefreshToken) - assert.NotEmpty(t, tokens.IDToken) - }) - }) -} diff --git a/app/init.go b/app/init.go index 8de5461..7ea0dd2 100644 --- a/app/init.go +++ b/app/init.go @@ -5,18 +5,16 @@ import ( "net/http" "os" - xoidc "github.com/coreos/go-oidc/v3/oidc" + "github.com/coreos/go-oidc/v3/oidc" "github.com/rs/zerolog" "github.com/xlgmokha/x/pkg/ioc" "github.com/xlgmokha/x/pkg/log" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/controllers/dashboard" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/controllers/health" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/controllers/sessions" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/controllers/sparkles" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/db" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/web" "golang.org/x/oauth2" ) @@ -50,25 +48,16 @@ func init() { }, } }) - ioc.Register[*xoidc.Provider](ioc.Default, func() *xoidc.Provider { + ioc.Register[*oidc.Provider](ioc.Default, func() *oidc.Provider { ctx := context.WithValue(context.Background(), oauth2.HTTPClient, ioc.MustResolve[*http.Client](ioc.Default)) - return oidc.NewProvider(ctx, cfg.OIDCIssuer, func(err error) { + return web.NewOIDCProvider(ctx, cfg.OIDCIssuer, func(err error) { ioc.MustResolve[*zerolog.Logger](ioc.Default).Err(err).Send() }) }) - ioc.RegisterSingleton[*oidc.OpenID](ioc.Default, func() *oidc.OpenID { - return oidc.New( - ioc.MustResolve[*xoidc.Provider](ioc.Default), - cfg.OAuthClientID, - cfg.OAuthClientSecret, - cfg.OAuthRedirectURL, - ) - }) - ioc.Register[*sessions.Controller](ioc.Default, func() *sessions.Controller { - return sessions.New( - ioc.MustResolve[*oidc.OpenID](ioc.Default), - ioc.MustResolve[*http.Client](ioc.Default), - ) + ioc.Register[*oidc.Config](ioc.Default, func() *oidc.Config { + return &oidc.Config{ + ClientID: cfg.OAuthClientID, + } }) http.DefaultClient = ioc.MustResolve[*http.Client](ioc.Default) diff --git a/app/middleware/from_cookie.go b/app/middleware/from_cookie.go new file mode 100644 index 0000000..316d6e4 --- /dev/null +++ b/app/middleware/from_cookie.go @@ -0,0 +1,15 @@ +package middleware + +import "net/http" + +func FromCookie(name string) TokenParser { + return func(r *http.Request) RawToken { + cookies := r.CookiesNamed(name) + + if len(cookies) != 1 { + return "" + } + + return RawToken(cookies[0].Value) + } +} diff --git a/app/middleware/id_token.go b/app/middleware/id_token.go index dbaf691..8084af0 100644 --- a/app/middleware/id_token.go +++ b/app/middleware/id_token.go @@ -3,21 +3,22 @@ package middleware import ( "net/http" + "github.com/coreos/go-oidc/v3/oidc" "github.com/xlgmokha/x/pkg/log" "github.com/xlgmokha/x/pkg/x" xcfg "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/web" ) -func IDToken(cfg *oidc.OpenID, parsers ...TokenParser) func(http.Handler) http.Handler { +func IDToken(provider *oidc.Provider, config *oidc.Config, parsers ...TokenParser) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { for _, parser := range parsers { rawIDToken := parser(r) if x.IsPresent(rawIDToken) { - idToken, err := cfg.ValidateIDToken(r.Context(), rawIDToken) + verifier := provider.VerifierContext(r.Context(), config) + idToken, err := verifier.Verify(r.Context(), rawIDToken.String()) if err != nil { pls.LogError(r.Context(), err) diff --git a/app/middleware/id_token_test.go b/app/middleware/id_token_test.go index b363d2c..5487ada 100644 --- a/app/middleware/id_token_test.go +++ b/app/middleware/id_token_test.go @@ -4,23 +4,23 @@ import ( "net/http" "testing" + "github.com/coreos/go-oidc/v3/oidc" "github.com/oauth2-proxy/mockoidc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/xlgmokha/x/pkg/test" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" xcfg "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/web" ) func TestIDToken(t *testing.T) { - srv := oidc.NewTestServer(t) + srv := web.NewOIDCServer(t) defer srv.Close() - openID := oidc.New(srv.Provider, srv.MockOIDC.ClientID, srv.MockOIDC.ClientSecret, "https://example.com/oauth/callback") - middleware := IDToken(openID, IDTokenFromSessionCookie) + middleware := IDToken(srv.Provider, &oidc.Config{ClientID: srv.MockOIDC.ClientID}, FromCookie(cfg.IDTokenCookie)) - t.Run("when an active session cookie is provided", func(t *testing.T) { + t.Run("when an active id_token cookie is provided", func(t *testing.T) { t.Run("attaches the token to the request context", func(t *testing.T) { user := mockoidc.DefaultUser() @@ -45,7 +45,7 @@ func TestIDToken(t *testing.T) { }) }) - t.Run("when an invalid session cookie is provided", func(t *testing.T) { + t.Run("when an invalid id_token cookie is provided", func(t *testing.T) { t.Run("forwards the request", func(t *testing.T) { server := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { require.Nil(t, xcfg.IDToken.From(r.Context())) diff --git a/app/middleware/init.go b/app/middleware/init.go index f1a693d..874ca52 100644 --- a/app/middleware/init.go +++ b/app/middleware/init.go @@ -1,14 +1,23 @@ package middleware import ( + "github.com/coreos/go-oidc/v3/oidc" "github.com/xlgmokha/x/pkg/mapper" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" ) +type CustomClaims struct { + Name string `json:"name"` + Nickname string `json:"nickname"` + Email string `json:"email"` + ProfileURL string `json:"profile"` + Picture string `json:"picture"` + Groups []string `json:"groups_direct"` +} + func init() { mapper.Register(func(idToken *oidc.IDToken) *domain.User { - customClaims := &oidc.CustomClaims{} + customClaims := &CustomClaims{} if err := idToken.Claims(customClaims); err != nil { return &domain.User{ID: domain.ID(idToken.Subject)} } diff --git a/pkg/oidc/raw_token.go b/app/middleware/raw_token.go index 08bd1e5..f7aa264 100644 --- a/pkg/oidc/raw_token.go +++ b/app/middleware/raw_token.go @@ -1,4 +1,4 @@ -package oidc +package middleware type RawToken string diff --git a/app/middleware/token_parser.go b/app/middleware/token_parser.go index 22a7af9..48034f0 100644 --- a/app/middleware/token_parser.go +++ b/app/middleware/token_parser.go @@ -4,18 +4,6 @@ import ( "net/http" "github.com/xlgmokha/x/pkg/x" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" ) -type TokenParser x.Mapper[*http.Request, oidc.RawToken] - -func IDTokenFromSessionCookie(r *http.Request) oidc.RawToken { - cookies := r.CookiesNamed(cfg.IDTokenCookie) - - if len(cookies) != 1 { - return "" - } - - return oidc.RawToken(cookies[0].Value) -} +type TokenParser x.Mapper[*http.Request, RawToken] diff --git a/app/middleware/user.go b/app/middleware/user.go index c0181f9..9a88f8e 100644 --- a/app/middleware/user.go +++ b/app/middleware/user.go @@ -3,11 +3,11 @@ package middleware import ( "net/http" + "github.com/coreos/go-oidc/v3/oidc" "github.com/xlgmokha/x/pkg/mapper" "github.com/xlgmokha/x/pkg/x" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" ) diff --git a/app/middleware/user_test.go b/app/middleware/user_test.go index e6ba09d..aed3582 100644 --- a/app/middleware/user_test.go +++ b/app/middleware/user_test.go @@ -4,13 +4,13 @@ import ( "net/http" "testing" + "github.com/coreos/go-oidc/v3/oidc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/xlgmokha/x/pkg/test" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/db" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" ) diff --git a/app/views/dashboard/nav.html.tmpl b/app/views/dashboard/nav.html.tmpl index c43ea48..c9c4a84 100644 --- a/app/views/dashboard/nav.html.tmpl +++ b/app/views/dashboard/nav.html.tmpl @@ -13,7 +13,7 @@ <li><a href="/dashboard">Dashboard</a></li> <li><a href="{{ .CurrentUser.ProfileURL }}">Profile</a></li> <li> - <form action="/session/destroy" method="post"> + <form action="/signout" method="post"> <input type="submit" value="Logout"> </form> </li> @@ -21,7 +21,7 @@ </details> </li> {{ else }} - <li><a href="/session/new" class="secondary">Login</a></li> + <li><a href="/dashboard" class="secondary">Login</a></li> {{ end }} </ul> </nav> diff --git a/app/views/sessions/redirect.html.tmpl b/app/views/sessions/redirect.html.tmpl deleted file mode 100644 index e70e1d5..0000000 --- a/app/views/sessions/redirect.html.tmpl +++ /dev/null @@ -1,9 +0,0 @@ -<!doctype html> -<html lang="en"> - <head> - <meta http-equiv="refresh" content="0;URL='{{ .URL }}'"/> - </head> - <body> - <p>Moved to <a href="{{ .URL }}">location</a>.</p> - </body> -</html> diff --git a/bin/init.sh b/bin/entrypoint.sh index 28d6c5f..28d6c5f 100755 --- a/bin/init.sh +++ b/bin/entrypoint.sh diff --git a/bin/envoy.sh b/bin/envoy.sh index 8690bd1..67ea54c 100755 --- a/bin/envoy.sh +++ b/bin/envoy.sh @@ -17,18 +17,15 @@ if [ "$oidc_scheme" = "http" ]; then yaml=$(echo "$yaml" | sed -e "s/port_value: 443/port_value: $oidc_port/") fi -# I need an adult with access to vault to set this if [ -z "$OAUTH_CLIENT_SECRET" ]; then export OAUTH_CLIENT_SECRET="secret" fi - -# and this. if [ -z "$HMAC_SESSION_SECRET" ]; then export HMAC_SESSION_SECRET="$OAUTH_CLIENT_SECRET" fi if ! command -v envoy; then - echo "envoy could not be found" + echo "envoy could not be found: https://www.envoyproxy.io/docs/envoy/latest/start/install" exit 1 fi diff --git a/cmd/sparkled/main.go b/cmd/sparkled/main.go index 7bec1b7..161e8c1 100644 --- a/cmd/sparkled/main.go +++ b/cmd/sparkled/main.go @@ -11,7 +11,7 @@ import ( ) func main() { - bindAddr := env.Fetch("BIND_ADDR", ":http") + bindAddr := env.Fetch("BIND_ADDR", ":8080") log.Printf("Listening on %v\n", bindAddr) log.Fatal(http.ListenAndServe( diff --git a/pkg/oidc/custom_claims.go b/pkg/oidc/custom_claims.go deleted file mode 100644 index 0d89d89..0000000 --- a/pkg/oidc/custom_claims.go +++ /dev/null @@ -1,10 +0,0 @@ -package oidc - -type CustomClaims struct { - Name string `json:"name"` - Nickname string `json:"nickname"` - Email string `json:"email"` - ProfileURL string `json:"profile"` - Picture string `json:"picture"` - Groups []string `json:"groups_direct"` -} diff --git a/pkg/oidc/id_token.go b/pkg/oidc/id_token.go deleted file mode 100644 index ce3fb23..0000000 --- a/pkg/oidc/id_token.go +++ /dev/null @@ -1,53 +0,0 @@ -package oidc - -import "github.com/coreos/go-oidc/v3/oidc" - -/* -Example ID Token from GitLab OIDC Provider: - -```json - - { - "iss": "http://gdk.test:3000", - "sub": "1", - "aud": "e31e1da0b8f6b6e35ca70c790b13c0406e44aca6b2bf67f55de7355a979a224f", - "exp": 1745427493, - "iat": 1745427373, - "auth_time": 1745418001, - "sub_legacy": "2474cf0b2211688a57297ace0e260a15944754d16b1bd42c9d6779c900367807", - "name": "Administrator", - "nickname": "root", - "preferred_username": "root", - "email": "admin@example.com", - "email_verified": true, - "profile": "http://gdk.test:3000/root", - "picture": "https://www.gravatar.com/avatar/258d8dc916db8cea2cafb6c3cd0cb0246efe061421dbd83ec3a350428cabda4f?s=80&d=identicon", - "groups_direct": [ - "gitlab-org", - "toolbox", - "mass_insert_group__0_100", - "custom-roles-root-group/aa", - "custom-roles-root-group/aa/aaa", - "gnuwget", - "Commit451", - "jashkenas", - "flightjs", - "twitter", - "gitlab-examples", - "gitlab-examples/security", - "412708", - "gitlab-examples/demo-group", - "custom-roles-root-group", - "434044-group-1", - "434044-group-2", - "gitlab-org1", - "gitlab-org/secure", - "gitlab-org/secure/managers", - "gitlab-org/security-products", - "gitlab-org/security-products/analyzers" - ] - } - -``` -*/ -type IDToken = oidc.IDToken diff --git a/pkg/oidc/oidc.go b/pkg/oidc/oidc.go deleted file mode 100644 index 4704f63..0000000 --- a/pkg/oidc/oidc.go +++ /dev/null @@ -1,36 +0,0 @@ -package oidc - -import ( - "context" - - "github.com/coreos/go-oidc/v3/oidc" - "golang.org/x/oauth2" -) - -type OpenID struct { - Provider *oidc.Provider - Config *oauth2.Config - OIDCConfig *oidc.Config -} - -func New(provider *oidc.Provider, clientID, clientSecret, callbackURL string) *OpenID { - return &OpenID{ - Provider: provider, - Config: &oauth2.Config{ - ClientID: clientID, - ClientSecret: clientSecret, - RedirectURL: callbackURL, - Endpoint: provider.Endpoint(), - Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, - }, - OIDCConfig: &oidc.Config{ - ClientID: clientID, - }, - } -} - -func (o *OpenID) ValidateIDToken(ctx context.Context, rawIDToken RawToken) (*IDToken, error) { - verifier := o.Provider.VerifierContext(ctx, o.OIDCConfig) - idToken, err := verifier.Verify(ctx, rawIDToken.String()) - return idToken, err -} diff --git a/pkg/oidc/oidc_test.go b/pkg/oidc/oidc_test.go deleted file mode 100644 index a3dc7e4..0000000 --- a/pkg/oidc/oidc_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package oidc - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestOpenID(t *testing.T) { - srv := NewTestServer(t) - defer srv.Close() - - t.Run("GET /.well-known/openid-configuration", func(t *testing.T) { - openID := New( - srv.Provider, - srv.MockOIDC.ClientID, - srv.MockOIDC.ClientSecret, - "https://example.com/oauth/callback", - ) - - assert.Equal(t, srv.AuthorizationEndpoint(), openID.Provider.Endpoint().AuthURL) - assert.Equal(t, srv.TokenEndpoint(), openID.Provider.Endpoint().TokenURL) - }) -} diff --git a/pkg/oidc/tokens.go b/pkg/oidc/tokens.go deleted file mode 100644 index 70d3a3d..0000000 --- a/pkg/oidc/tokens.go +++ /dev/null @@ -1,37 +0,0 @@ -package oidc - -import ( - "bytes" - "encoding/base64" - "encoding/json" - - "github.com/xlgmokha/x/pkg/serde" - "golang.org/x/oauth2" -) - -type Tokens struct { - *oauth2.Token - IDToken RawToken `json:"id_token"` -} - -func (t *Tokens) ToBase64String() (string, error) { - data, err := json.Marshal(t) - if err != nil { - return "", err - } - return base64.URLEncoding.EncodeToString(data), nil -} - -func TokensFromBase64String(encoded string) (*Tokens, error) { - decoded, err := base64.URLEncoding.DecodeString(encoded) - if err != nil { - return nil, err - } - - tokens, err := serde.FromJSON[*Tokens](bytes.NewBuffer(decoded)) - if err != nil { - return nil, err - } - - return tokens, nil -} diff --git a/pkg/oidc/tokens_test.go b/pkg/oidc/tokens_test.go deleted file mode 100644 index 42c470d..0000000 --- a/pkg/oidc/tokens_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package oidc - -import ( - "bytes" - "encoding/json" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/xlgmokha/x/pkg/serde" - "golang.org/x/oauth2" -) - -func TestTokens(t *testing.T) { - t.Run("serializes to JSON", func(t *testing.T) { - tokens := &Tokens{ - Token: &oauth2.Token{ - AccessToken: "access_token", - TokenType: "Bearer", - RefreshToken: "refresh_token", - ExpiresIn: 60 * 60, - }, - IDToken: "eyJ0eXAiOiJKV1QiLCJraWQiOiJ0ZDBTbWRKUTRxUGg1cU5Lek0yNjBDWHgyVWgtd2hHLU1Eam9PS1dmdDhFIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwOi8vZ2RrLnRlc3Q6MzAwMCIsInN1YiI6IjEiLCJhdWQiOiJlMzFlMWRhMGI4ZjZiNmUzNWNhNzBjNzkwYjEzYzA0MDZlNDRhY2E2YjJiZjY3ZjU1ZGU3MzU1YTk3OWEyMjRmIiwiZXhwIjoxNzQ0NzM3NDI3LCJpYXQiOjE3NDQ3MzczMDcsImF1dGhfdGltZSI6MTc0NDczNDY0OSwic3ViX2xlZ2FjeSI6IjI0NzRjZjBiMjIxMTY4OGE1NzI5N2FjZTBlMjYwYTE1OTQ0NzU0ZDE2YjFiZDQyYzlkNjc3OWM5MDAzNjc4MDciLCJuYW1lIjoiQWRtaW5pc3RyYXRvciIsIm5pY2tuYW1lIjoicm9vdCIsInByZWZlcnJlZF91c2VybmFtZSI6InJvb3QiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInByb2ZpbGUiOiJodHRwOi8vZ2RrLnRlc3Q6MzAwMC9yb290IiwicGljdHVyZSI6Imh0dHBzOi8vd3d3LmdyYXZhdGFyLmNvbS9hdmF0YXIvMjU4ZDhkYzkxNmRiOGNlYTJjYWZiNmMzY2QwY2IwMjQ2ZWZlMDYxNDIxZGJkODNlYzNhMzUwNDI4Y2FiZGE0Zj9zPTgwJmQ9aWRlbnRpY29uIiwiZ3JvdXBzX2RpcmVjdCI6WyJ0b29sYm94IiwiZ2l0bGFiLW9yZyIsImdudXdnZXQiLCJDb21taXQ0NTEiLCJqYXNoa2VuYXMiLCJmbGlnaHRqcyIsInR3aXR0ZXIiLCJnaXRsYWItZXhhbXBsZXMiLCJnaXRsYWItZXhhbXBsZXMvc2VjdXJpdHkiLCI0MTI3MDgiLCJnaXRsYWItZXhhbXBsZXMvZGVtby1ncm91cCIsImN1c3RvbS1yb2xlcy1yb290LWdyb3VwIiwiNDM0MDQ0LWdyb3VwLTEiLCI0MzQwNDQtZ3JvdXAtMiIsImdpdGxhYi1vcmcxIiwiZ2l0bGFiLW9yZy9zZWN1cmUiLCJnaXRsYWItb3JnL3NlY3VyZS9tYW5hZ2VycyIsImdpdGxhYi1vcmcvc2VjdXJpdHktcHJvZHVjdHMiLCJnaXRsYWItb3JnL3NlY3VyaXR5LXByb2R1Y3RzL2FuYWx5emVycyIsImN1c3RvbS1yb2xlcy1yb290LWdyb3VwL2FhIiwiY3VzdG9tLXJvbGVzLXJvb3QtZ3JvdXAvYWEvYWFhIiwibWFzc19pbnNlcnRfZ3JvdXBfXzBfMTAwIl19.SZu_l7tQ2Kkeogq0z8cRaDWPfv52JTo-RkiExbnud_lrfrXXneS77BIzaGKX_bzq4SM_oO_Q63AzK66B1r6Gp7ACo4DjOUEIWETg7ZBKcDzEZnresB7kmI_MJ5rfIJTmnH75GOfc_pl5l8T896TbaShN6zSpaXXIVEfhyUrflSWb4hhA7Hbwy2b6laXiaDv0qpcn1udPVYMTsll8I5ni_2yzuEPSVRgrcQoQ46OwVDZIi9tlfdT2qNVjH6FxJ3mkBcxtIVjf3_JYAawFEscg2uvQYwFWj9T6LleMknAh3QFJJMrS6mPqlXJGPUE5pTQgsBInfEikfm9PXxezA-IY6g", - } - - b, err := json.Marshal(tokens) - require.NoError(t, err) - - result, err := serde.FromJSON[map[string]interface{}](bytes.NewBuffer(b)) - require.NoError(t, err) - - assert.Equal(t, "access_token", result["access_token"]) - assert.Equal(t, "Bearer", result["token_type"]) - assert.Equal(t, "refresh_token", result["refresh_token"]) - assert.Equal(t, float64(60*60), result["expires_in"]) - }) - - t.Run("ToBase64String", func(t *testing.T) { - t.Run("serializes to Base64", func(t *testing.T) { - tokens := &Tokens{ - Token: &oauth2.Token{ - AccessToken: "access_token", - TokenType: "Bearer", - RefreshToken: "refresh_token", - ExpiresIn: 60 * 60, - }, - IDToken: "eyJ0eXAiOiJKV1QiLCJraWQiOiJ0ZDBTbWRKUTRxUGg1cU5Lek0yNjBDWHgyVWgtd2hHLU1Eam9PS1dmdDhFIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwOi8vZ2RrLnRlc3Q6MzAwMCIsInN1YiI6IjEiLCJhdWQiOiJlMzFlMWRhMGI4ZjZiNmUzNWNhNzBjNzkwYjEzYzA0MDZlNDRhY2E2YjJiZjY3ZjU1ZGU3MzU1YTk3OWEyMjRmIiwiZXhwIjoxNzQ0NzM3NDI3LCJpYXQiOjE3NDQ3MzczMDcsImF1dGhfdGltZSI6MTc0NDczNDY0OSwic3ViX2xlZ2FjeSI6IjI0NzRjZjBiMjIxMTY4OGE1NzI5N2FjZTBlMjYwYTE1OTQ0NzU0ZDE2YjFiZDQyYzlkNjc3OWM5MDAzNjc4MDciLCJuYW1lIjoiQWRtaW5pc3RyYXRvciIsIm5pY2tuYW1lIjoicm9vdCIsInByZWZlcnJlZF91c2VybmFtZSI6InJvb3QiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInByb2ZpbGUiOiJodHRwOi8vZ2RrLnRlc3Q6MzAwMC9yb290IiwicGljdHVyZSI6Imh0dHBzOi8vd3d3LmdyYXZhdGFyLmNvbS9hdmF0YXIvMjU4ZDhkYzkxNmRiOGNlYTJjYWZiNmMzY2QwY2IwMjQ2ZWZlMDYxNDIxZGJkODNlYzNhMzUwNDI4Y2FiZGE0Zj9zPTgwJmQ9aWRlbnRpY29uIiwiZ3JvdXBzX2RpcmVjdCI6WyJ0b29sYm94IiwiZ2l0bGFiLW9yZyIsImdudXdnZXQiLCJDb21taXQ0NTEiLCJqYXNoa2VuYXMiLCJmbGlnaHRqcyIsInR3aXR0ZXIiLCJnaXRsYWItZXhhbXBsZXMiLCJnaXRsYWItZXhhbXBsZXMvc2VjdXJpdHkiLCI0MTI3MDgiLCJnaXRsYWItZXhhbXBsZXMvZGVtby1ncm91cCIsImN1c3RvbS1yb2xlcy1yb290LWdyb3VwIiwiNDM0MDQ0LWdyb3VwLTEiLCI0MzQwNDQtZ3JvdXAtMiIsImdpdGxhYi1vcmcxIiwiZ2l0bGFiLW9yZy9zZWN1cmUiLCJnaXRsYWItb3JnL3NlY3VyZS9tYW5hZ2VycyIsImdpdGxhYi1vcmcvc2VjdXJpdHktcHJvZHVjdHMiLCJnaXRsYWItb3JnL3NlY3VyaXR5LXByb2R1Y3RzL2FuYWx5emVycyIsImN1c3RvbS1yb2xlcy1yb290LWdyb3VwL2FhIiwiY3VzdG9tLXJvbGVzLXJvb3QtZ3JvdXAvYWEvYWFhIiwibWFzc19pbnNlcnRfZ3JvdXBfXzBfMTAwIl19.SZu_l7tQ2Kkeogq0z8cRaDWPfv52JTo-RkiExbnud_lrfrXXneS77BIzaGKX_bzq4SM_oO_Q63AzK66B1r6Gp7ACo4DjOUEIWETg7ZBKcDzEZnresB7kmI_MJ5rfIJTmnH75GOfc_pl5l8T896TbaShN6zSpaXXIVEfhyUrflSWb4hhA7Hbwy2b6laXiaDv0qpcn1udPVYMTsll8I5ni_2yzuEPSVRgrcQoQ46OwVDZIi9tlfdT2qNVjH6FxJ3mkBcxtIVjf3_JYAawFEscg2uvQYwFWj9T6LleMknAh3QFJJMrS6mPqlXJGPUE5pTQgsBInfEikfm9PXxezA-IY6g", - } - - result, err := tokens.ToBase64String() - require.NoError(t, err) - - assert.Equal(t, "eyJhY2Nlc3NfdG9rZW4iOiJhY2Nlc3NfdG9rZW4iLCJ0b2tlbl90eXBlIjoiQmVhcmVyIiwicmVmcmVzaF90b2tlbiI6InJlZnJlc2hfdG9rZW4iLCJleHBpcnkiOiIwMDAxLTAxLTAxVDAwOjAwOjAwWiIsImV4cGlyZXNfaW4iOjM2MDAsImlkX3Rva2VuIjoiZXlKMGVYQWlPaUpLVjFRaUxDSnJhV1FpT2lKMFpEQlRiV1JLVVRSeFVHZzFjVTVMZWsweU5qQkRXSGd5VldndGQyaEhMVTFFYW05UFMxZG1kRGhGSWl3aVlXeG5Jam9pVWxNeU5UWWlmUS5leUpwYzNNaU9pSm9kSFJ3T2k4dloyUnJMblJsYzNRNk16QXdNQ0lzSW5OMVlpSTZJakVpTENKaGRXUWlPaUpsTXpGbE1XUmhNR0k0WmpaaU5tVXpOV05oTnpCak56a3dZakV6WXpBME1EWmxORFJoWTJFMllqSmlaalkzWmpVMVpHVTNNelUxWVRrM09XRXlNalJtSWl3aVpYaHdJam94TnpRME56TTNOREkzTENKcFlYUWlPakUzTkRRM016Y3pNRGNzSW1GMWRHaGZkR2x0WlNJNk1UYzBORGN6TkRZME9Td2ljM1ZpWDJ4bFoyRmplU0k2SWpJME56UmpaakJpTWpJeE1UWTRPR0UxTnpJNU4yRmpaVEJsTWpZd1lURTFPVFEwTnpVMFpERTJZakZpWkRReVl6bGtOamMzT1dNNU1EQXpOamM0TURjaUxDSnVZVzFsSWpvaVFXUnRhVzVwYzNSeVlYUnZjaUlzSW01cFkydHVZVzFsSWpvaWNtOXZkQ0lzSW5CeVpXWmxjbkpsWkY5MWMyVnlibUZ0WlNJNkluSnZiM1FpTENKbGJXRnBiQ0k2SW1Ga2JXbHVRR1Y0WVcxd2JHVXVZMjl0SWl3aVpXMWhhV3hmZG1WeWFXWnBaV1FpT25SeWRXVXNJbkJ5YjJacGJHVWlPaUpvZEhSd09pOHZaMlJyTG5SbGMzUTZNekF3TUM5eWIyOTBJaXdpY0dsamRIVnlaU0k2SW1oMGRIQnpPaTh2ZDNkM0xtZHlZWFpoZEdGeUxtTnZiUzloZG1GMFlYSXZNalU0WkRoa1l6a3hObVJpT0dObFlUSmpZV1ppTm1NelkyUXdZMkl3TWpRMlpXWmxNRFl4TkRJeFpHSmtPRE5sWXpOaE16VXdOREk0WTJGaVpHRTBaajl6UFRnd0ptUTlhV1JsYm5ScFkyOXVJaXdpWjNKdmRYQnpYMlJwY21WamRDSTZXeUowYjI5c1ltOTRJaXdpWjJsMGJHRmlMVzl5WnlJc0ltZHVkWGRuWlhRaUxDSkRiMjF0YVhRME5URWlMQ0pxWVhOb2EyVnVZWE1pTENKbWJHbG5hSFJxY3lJc0luUjNhWFIwWlhJaUxDSm5hWFJzWVdJdFpYaGhiWEJzWlhNaUxDSm5hWFJzWVdJdFpYaGhiWEJzWlhNdmMyVmpkWEpwZEhraUxDSTBNVEkzTURnaUxDSm5hWFJzWVdJdFpYaGhiWEJzWlhNdlpHVnRieTFuY205MWNDSXNJbU4xYzNSdmJTMXliMnhsY3kxeWIyOTBMV2R5YjNWd0lpd2lORE0wTURRMExXZHliM1Z3TFRFaUxDSTBNelF3TkRRdFozSnZkWEF0TWlJc0ltZHBkR3hoWWkxdmNtY3hJaXdpWjJsMGJHRmlMVzl5Wnk5elpXTjFjbVVpTENKbmFYUnNZV0l0YjNKbkwzTmxZM1Z5WlM5dFlXNWhaMlZ5Y3lJc0ltZHBkR3hoWWkxdmNtY3ZjMlZqZFhKcGRIa3RjSEp2WkhWamRITWlMQ0puYVhSc1lXSXRiM0puTDNObFkzVnlhWFI1TFhCeWIyUjFZM1J6TDJGdVlXeDVlbVZ5Y3lJc0ltTjFjM1J2YlMxeWIyeGxjeTF5YjI5MExXZHliM1Z3TDJGaElpd2lZM1Z6ZEc5dExYSnZiR1Z6TFhKdmIzUXRaM0p2ZFhBdllXRXZZV0ZoSWl3aWJXRnpjMTlwYm5ObGNuUmZaM0p2ZFhCZlh6QmZNVEF3SWwxOS5TWnVfbDd0UTJLa2VvZ3EwejhjUmFEV1BmdjUySlRvLVJraUV4Ym51ZF9scmZyWFhuZVM3N0JJemFHS1hfYnpxNFNNX29PX1E2M0F6SzY2QjFyNkdwN0FDbzREak9VRUlXRVRnN1pCS2NEekVabnJlc0I3a21JX01KNXJmSUpUbW5INzVHT2ZjX3BsNWw4VDg5NlRiYVNoTjZ6U3BhWFhJVkVmaHlVcmZsU1diNGhoQTdIYnd5MmI2bGFYaWFEdjBxcGNuMXVkUFZZTVRzbGw4STVuaV8yeXp1RVBTVlJncmNRb1E0Nk93VkRaSWk5dGxmZFQycU5Wakg2RnhKM21rQmN4dElWamYzX0pZQWF3RkVzY2cydXZRWXdGV2o5VDZMbGVNa25BaDNRRkpKTXJTNm1QcWxYSkdQVUU1cFRRZ3NCSW5mRWlrZm05UFh4ZXpBLUlZNmcifQ==", result) - }) - }) - - t.Run("TokensFromBase64String", func(t *testing.T) { - t.Run("deserializes from Base64", func(t *testing.T) { - s := "eyJhY2Nlc3NfdG9rZW4iOiJhY2Nlc3NfdG9rZW4iLCJ0b2tlbl90eXBlIjoiQmVhcmVyIiwicmVmcmVzaF90b2tlbiI6InJlZnJlc2hfdG9rZW4iLCJleHBpcnkiOiIwMDAxLTAxLTAxVDAwOjAwOjAwWiIsImV4cGlyZXNfaW4iOjM2MDAsImlkX3Rva2VuIjoiZXlKMGVYQWlPaUpLVjFRaUxDSnJhV1FpT2lKMFpEQlRiV1JLVVRSeFVHZzFjVTVMZWsweU5qQkRXSGd5VldndGQyaEhMVTFFYW05UFMxZG1kRGhGSWl3aVlXeG5Jam9pVWxNeU5UWWlmUS5leUpwYzNNaU9pSm9kSFJ3T2k4dloyUnJMblJsYzNRNk16QXdNQ0lzSW5OMVlpSTZJakVpTENKaGRXUWlPaUpsTXpGbE1XUmhNR0k0WmpaaU5tVXpOV05oTnpCak56a3dZakV6WXpBME1EWmxORFJoWTJFMllqSmlaalkzWmpVMVpHVTNNelUxWVRrM09XRXlNalJtSWl3aVpYaHdJam94TnpRME56TTNOREkzTENKcFlYUWlPakUzTkRRM016Y3pNRGNzSW1GMWRHaGZkR2x0WlNJNk1UYzBORGN6TkRZME9Td2ljM1ZpWDJ4bFoyRmplU0k2SWpJME56UmpaakJpTWpJeE1UWTRPR0UxTnpJNU4yRmpaVEJsTWpZd1lURTFPVFEwTnpVMFpERTJZakZpWkRReVl6bGtOamMzT1dNNU1EQXpOamM0TURjaUxDSnVZVzFsSWpvaVFXUnRhVzVwYzNSeVlYUnZjaUlzSW01cFkydHVZVzFsSWpvaWNtOXZkQ0lzSW5CeVpXWmxjbkpsWkY5MWMyVnlibUZ0WlNJNkluSnZiM1FpTENKbGJXRnBiQ0k2SW1Ga2JXbHVRR1Y0WVcxd2JHVXVZMjl0SWl3aVpXMWhhV3hmZG1WeWFXWnBaV1FpT25SeWRXVXNJbkJ5YjJacGJHVWlPaUpvZEhSd09pOHZaMlJyTG5SbGMzUTZNekF3TUM5eWIyOTBJaXdpY0dsamRIVnlaU0k2SW1oMGRIQnpPaTh2ZDNkM0xtZHlZWFpoZEdGeUxtTnZiUzloZG1GMFlYSXZNalU0WkRoa1l6a3hObVJpT0dObFlUSmpZV1ppTm1NelkyUXdZMkl3TWpRMlpXWmxNRFl4TkRJeFpHSmtPRE5sWXpOaE16VXdOREk0WTJGaVpHRTBaajl6UFRnd0ptUTlhV1JsYm5ScFkyOXVJaXdpWjNKdmRYQnpYMlJwY21WamRDSTZXeUowYjI5c1ltOTRJaXdpWjJsMGJHRmlMVzl5WnlJc0ltZHVkWGRuWlhRaUxDSkRiMjF0YVhRME5URWlMQ0pxWVhOb2EyVnVZWE1pTENKbWJHbG5hSFJxY3lJc0luUjNhWFIwWlhJaUxDSm5hWFJzWVdJdFpYaGhiWEJzWlhNaUxDSm5hWFJzWVdJdFpYaGhiWEJzWlhNdmMyVmpkWEpwZEhraUxDSTBNVEkzTURnaUxDSm5hWFJzWVdJdFpYaGhiWEJzWlhNdlpHVnRieTFuY205MWNDSXNJbU4xYzNSdmJTMXliMnhsY3kxeWIyOTBMV2R5YjNWd0lpd2lORE0wTURRMExXZHliM1Z3TFRFaUxDSTBNelF3TkRRdFozSnZkWEF0TWlJc0ltZHBkR3hoWWkxdmNtY3hJaXdpWjJsMGJHRmlMVzl5Wnk5elpXTjFjbVVpTENKbmFYUnNZV0l0YjNKbkwzTmxZM1Z5WlM5dFlXNWhaMlZ5Y3lJc0ltZHBkR3hoWWkxdmNtY3ZjMlZqZFhKcGRIa3RjSEp2WkhWamRITWlMQ0puYVhSc1lXSXRiM0puTDNObFkzVnlhWFI1TFhCeWIyUjFZM1J6TDJGdVlXeDVlbVZ5Y3lJc0ltTjFjM1J2YlMxeWIyeGxjeTF5YjI5MExXZHliM1Z3TDJGaElpd2lZM1Z6ZEc5dExYSnZiR1Z6TFhKdmIzUXRaM0p2ZFhBdllXRXZZV0ZoSWl3aWJXRnpjMTlwYm5ObGNuUmZaM0p2ZFhCZlh6QmZNVEF3SWwxOS5TWnVfbDd0UTJLa2VvZ3EwejhjUmFEV1BmdjUySlRvLVJraUV4Ym51ZF9scmZyWFhuZVM3N0JJemFHS1hfYnpxNFNNX29PX1E2M0F6SzY2QjFyNkdwN0FDbzREak9VRUlXRVRnN1pCS2NEekVabnJlc0I3a21JX01KNXJmSUpUbW5INzVHT2ZjX3BsNWw4VDg5NlRiYVNoTjZ6U3BhWFhJVkVmaHlVcmZsU1diNGhoQTdIYnd5MmI2bGFYaWFEdjBxcGNuMXVkUFZZTVRzbGw4STVuaV8yeXp1RVBTVlJncmNRb1E0Nk93VkRaSWk5dGxmZFQycU5Wakg2RnhKM21rQmN4dElWamYzX0pZQWF3RkVzY2cydXZRWXdGV2o5VDZMbGVNa25BaDNRRkpKTXJTNm1QcWxYSkdQVUU1cFRRZ3NCSW5mRWlrZm05UFh4ZXpBLUlZNmcifQ==" - - result, err := TokensFromBase64String(s) - require.NoError(t, err) - - require.NotNil(t, result) - assert.Equal(t, "access_token", result.AccessToken) - assert.Equal(t, "Bearer", result.TokenType) - assert.Equal(t, "refresh_token", result.RefreshToken) - assert.Equal(t, int64(3600), result.ExpiresIn) - assert.Equal(t, RawToken("eyJ0eXAiOiJKV1QiLCJraWQiOiJ0ZDBTbWRKUTRxUGg1cU5Lek0yNjBDWHgyVWgtd2hHLU1Eam9PS1dmdDhFIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwOi8vZ2RrLnRlc3Q6MzAwMCIsInN1YiI6IjEiLCJhdWQiOiJlMzFlMWRhMGI4ZjZiNmUzNWNhNzBjNzkwYjEzYzA0MDZlNDRhY2E2YjJiZjY3ZjU1ZGU3MzU1YTk3OWEyMjRmIiwiZXhwIjoxNzQ0NzM3NDI3LCJpYXQiOjE3NDQ3MzczMDcsImF1dGhfdGltZSI6MTc0NDczNDY0OSwic3ViX2xlZ2FjeSI6IjI0NzRjZjBiMjIxMTY4OGE1NzI5N2FjZTBlMjYwYTE1OTQ0NzU0ZDE2YjFiZDQyYzlkNjc3OWM5MDAzNjc4MDciLCJuYW1lIjoiQWRtaW5pc3RyYXRvciIsIm5pY2tuYW1lIjoicm9vdCIsInByZWZlcnJlZF91c2VybmFtZSI6InJvb3QiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInByb2ZpbGUiOiJodHRwOi8vZ2RrLnRlc3Q6MzAwMC9yb290IiwicGljdHVyZSI6Imh0dHBzOi8vd3d3LmdyYXZhdGFyLmNvbS9hdmF0YXIvMjU4ZDhkYzkxNmRiOGNlYTJjYWZiNmMzY2QwY2IwMjQ2ZWZlMDYxNDIxZGJkODNlYzNhMzUwNDI4Y2FiZGE0Zj9zPTgwJmQ9aWRlbnRpY29uIiwiZ3JvdXBzX2RpcmVjdCI6WyJ0b29sYm94IiwiZ2l0bGFiLW9yZyIsImdudXdnZXQiLCJDb21taXQ0NTEiLCJqYXNoa2VuYXMiLCJmbGlnaHRqcyIsInR3aXR0ZXIiLCJnaXRsYWItZXhhbXBsZXMiLCJnaXRsYWItZXhhbXBsZXMvc2VjdXJpdHkiLCI0MTI3MDgiLCJnaXRsYWItZXhhbXBsZXMvZGVtby1ncm91cCIsImN1c3RvbS1yb2xlcy1yb290LWdyb3VwIiwiNDM0MDQ0LWdyb3VwLTEiLCI0MzQwNDQtZ3JvdXAtMiIsImdpdGxhYi1vcmcxIiwiZ2l0bGFiLW9yZy9zZWN1cmUiLCJnaXRsYWItb3JnL3NlY3VyZS9tYW5hZ2VycyIsImdpdGxhYi1vcmcvc2VjdXJpdHktcHJvZHVjdHMiLCJnaXRsYWItb3JnL3NlY3VyaXR5LXByb2R1Y3RzL2FuYWx5emVycyIsImN1c3RvbS1yb2xlcy1yb290LWdyb3VwL2FhIiwiY3VzdG9tLXJvbGVzLXJvb3QtZ3JvdXAvYWEvYWFhIiwibWFzc19pbnNlcnRfZ3JvdXBfXzBfMTAwIl19.SZu_l7tQ2Kkeogq0z8cRaDWPfv52JTo-RkiExbnud_lrfrXXneS77BIzaGKX_bzq4SM_oO_Q63AzK66B1r6Gp7ACo4DjOUEIWETg7ZBKcDzEZnresB7kmI_MJ5rfIJTmnH75GOfc_pl5l8T896TbaShN6zSpaXXIVEfhyUrflSWb4hhA7Hbwy2b6laXiaDv0qpcn1udPVYMTsll8I5ni_2yzuEPSVRgrcQoQ46OwVDZIi9tlfdT2qNVjH6FxJ3mkBcxtIVjf3_JYAawFEscg2uvQYwFWj9T6LleMknAh3QFJJMrS6mPqlXJGPUE5pTQgsBInfEikfm9PXxezA-IY6g"), result.IDToken) - }) - }) -} diff --git a/pkg/oidc/provider.go b/pkg/web/oidc.go index 31f7577..707a1b5 100644 --- a/pkg/oidc/provider.go +++ b/pkg/web/oidc.go @@ -1,4 +1,4 @@ -package oidc +package web import ( "context" @@ -6,7 +6,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" ) -func NewProvider(ctx context.Context, issuer string, report func(error)) *oidc.Provider { +func NewOIDCProvider(ctx context.Context, issuer string, report func(error)) *oidc.Provider { provider, err := oidc.NewProvider(ctx, issuer) if err == nil { return provider diff --git a/pkg/oidc/test_server.go b/pkg/web/oidc_server.go index 81b37ca..31ef572 100644 --- a/pkg/oidc/test_server.go +++ b/pkg/web/oidc_server.go @@ -1,4 +1,4 @@ -package oidc +package web import ( "net/http" @@ -12,14 +12,14 @@ import ( "golang.org/x/oauth2" ) -type TestServer struct { +type OIDCServer struct { *mockoidc.MockOIDC *oauth2.Config *oidc.Provider *testing.T } -func NewTestServer(t *testing.T) *TestServer { +func NewOIDCServer(t *testing.T) *OIDCServer { srv, err := mockoidc.Run() require.NoError(t, err) @@ -29,12 +29,10 @@ func NewTestServer(t *testing.T) *TestServer { next.ServeHTTP(w, r) }) }) + provider, err := oidc.NewProvider(t.Context(), srv.Issuer()) + require.NoError(t, err) - provider := NewProvider(t.Context(), srv.Issuer(), func(err error) { - require.NoError(t, err) - }) - - return &TestServer{ + return &OIDCServer{ srv, &oauth2.Config{ ClientID: srv.ClientID, @@ -48,7 +46,7 @@ func NewTestServer(t *testing.T) *TestServer { } } -func (srv *TestServer) CreateAuthorizationCodeFor(user mockoidc.User) string { +func (srv *OIDCServer) CreateAuthorizationCodeFor(user mockoidc.User) string { code := strconv.FormatInt(time.Now().Unix(), 10) srv.QueueUser(user) srv.QueueCode(code) @@ -58,21 +56,21 @@ func (srv *TestServer) CreateAuthorizationCodeFor(user mockoidc.User) string { return code } -func (srv *TestServer) CreateTokenFor(user mockoidc.User) *oauth2.Token { +func (srv *OIDCServer) CreateTokenFor(user mockoidc.User) *oauth2.Token { code := srv.CreateAuthorizationCodeFor(user) token, err := srv.Exchange(srv.Context(), code) require.NoError(srv, err) return token } -func (srv *TestServer) CreateTokensFor(user mockoidc.User) (*oauth2.Token, string) { +func (srv *OIDCServer) CreateTokensFor(user mockoidc.User) (*oauth2.Token, string) { token := srv.CreateTokenFor(user) rawIDToken, ok := token.Extra("id_token").(string) require.True(srv, ok) return token, rawIDToken } -func (srv *TestServer) Verify(rawIDToken string) *oidc.IDToken { +func (srv *OIDCServer) Verify(rawIDToken string) *oidc.IDToken { idToken, err := srv. Verifier(&oidc.Config{ClientID: srv.MockOIDC.Config().ClientID}). Verify(srv.Context(), rawIDToken) @@ -81,6 +79,6 @@ func (srv *TestServer) Verify(rawIDToken string) *oidc.IDToken { return idToken } -func (s *TestServer) Close() { +func (s *OIDCServer) Close() { s.Shutdown() } diff --git a/share/man/DEVELOPMENT.md b/share/man/DEVELOPMENT.md index 9d4eaf6..cc5fe5e 100644 --- a/share/man/DEVELOPMENT.md +++ b/share/man/DEVELOPMENT.md @@ -56,12 +56,13 @@ The following environment variables must be defined: | Variable Name | Description | | ---------------------- | ------------------------------------------------ | | `APP_ENV` | Sparkle environment (default: `development`) | -| `BIND_ADDR` | Address Sparkle listens on (default: `:8080`) | | `OAUTH_CLIENT_ID` | The client ID registered with GitLab IdP | | `OAUTH_CLIENT_SECRET` | The corresponding client secret | -| `OAUTH_REDIRECT_URL` | Redirect URI configured in your GitLab OAuth app | | `OIDC_ISSUER` | The issuer URL (e.g., `http://gdk.test:3000`) | +Follow these instructions to [create a user-owned application](https://docs.gitlab.com/integration/oauth_provider/#create-a-user-owned-application) +and set the `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET`. + You can refer to the `Dockerfile` in the root of this repository to determine the exact version of Envoy required. To install Envoy locally, follow the [official installation guide](https://www.envoyproxy.io/docs/envoy/latest/start/install). diff --git a/test/integration/container_test.go b/test/integration/container_test.go index b956250..73724fb 100644 --- a/test/integration/container_test.go +++ b/test/integration/container_test.go @@ -14,18 +14,16 @@ import ( "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/xlgmokha/x/pkg/env" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/web" ) -func environmentVariables(srv *oidc.TestServer) map[string]string { +func environmentVariables(srv *web.OIDCServer) map[string]string { return map[string]string{ "APP_ENV": "test", - "BIND_ADDR": ":8080", "DEBUG": env.Fetch("DEBUG", ""), "HMAC_SESSION_SECRET": "secret", "OAUTH_CLIENT_ID": srv.MockOIDC.ClientID, "OAUTH_CLIENT_SECRET": srv.MockOIDC.ClientSecret, - "OAUTH_REDIRECT_URL": "", "OIDC_ISSUER": srv.Issuer(), } } @@ -34,7 +32,7 @@ func TestContainer(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() - srv := oidc.NewTestServer(t) + srv := web.NewOIDCServer(t) defer srv.Close() container := NewContainer(t, ctx, environmentVariables(srv)) |
