Skip to content

Commit 723a927

Browse files
committed
Added initial support for SAML based authentication
1 parent 20be942 commit 723a927

File tree

10 files changed

+638
-29
lines changed

10 files changed

+638
-29
lines changed

go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ require (
1919
github.com/gorilla/sessions v1.4.0
2020
github.com/hashicorp/vault/api v1.15.0
2121
github.com/jackc/pgx/v5 v5.7.5
22+
github.com/jackc/pgxlisten v0.0.0-20241106001234-1d6f6656415c
2223
github.com/markbates/goth v1.80.0
2324
github.com/moby/buildkit v0.18.1
2425
github.com/pkg/profile v1.7.0
2526
github.com/rs/zerolog v1.33.0
27+
github.com/russellhaering/gosaml2 v0.10.0
28+
github.com/russellhaering/goxmldsig v1.5.0
2629
github.com/segmentio/ksuid v1.0.4
2730
github.com/urfave/cli/v2 v2.27.5
2831
go.starlark.net v0.0.0-20241125201518-c05ff208a98f
@@ -51,6 +54,7 @@ require (
5154
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect
5255
github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 // indirect
5356
github.com/aws/smithy-go v1.22.1 // indirect
57+
github.com/beevik/etree v1.5.0 // indirect
5458
github.com/caddyserver/zerossl v0.1.3 // indirect
5559
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
5660
github.com/cloudflare/circl v1.6.1 // indirect
@@ -81,13 +85,14 @@ require (
8185
github.com/huandu/xstrings v1.5.0 // indirect
8286
github.com/jackc/pgpassfile v1.0.0 // indirect
8387
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
84-
github.com/jackc/pgxlisten v0.0.0-20241106001234-1d6f6656415c // indirect
8588
github.com/jackc/puddle/v2 v2.2.2 // indirect
8689
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
90+
github.com/jonboulle/clockwork v0.5.0 // indirect
8791
github.com/kevinburke/ssh_config v1.2.0 // indirect
8892
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
8993
github.com/libdns/libdns v0.2.2 // indirect
9094
github.com/markbates/going v1.0.3 // indirect
95+
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
9196
github.com/mholt/acmez/v2 v2.0.3 // indirect
9297
github.com/miekg/dns v1.1.62 // indirect
9398
github.com/mitchellh/copystructure v1.2.0 // indirect

go.sum

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 h1:s4074ZO1Hk8qv65GqNXqDjmkf4HS
5151
github.com/aws/aws-sdk-go-v2/service/sts v1.33.2/go.mod h1:mVggCnIWoM09jP71Wh+ea7+5gAp53q+49wDFs1SW5z8=
5252
github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
5353
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
54+
github.com/beevik/etree v1.5.0 h1:iaQZFSDS+3kYZiGoc9uKeOkUY3nYMXOKLl6KIJxiJWs=
55+
github.com/beevik/etree v1.5.0/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs=
5456
github.com/benbjohnson/hashfs v0.2.2 h1:vFZtksphM5LcnMRFctj49jCUkCc7wp3NP6INyfjkse4=
5557
github.com/benbjohnson/hashfs v0.2.2/go.mod h1:7OMXaMVo1YkfiIPxKrl7OXkUTUgWjmsAKyR+E6xDIRM=
5658
github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q=
@@ -185,6 +187,8 @@ github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da h1:FjHUJJ7oBW4G/9
185187
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4=
186188
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
187189
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
190+
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
191+
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
188192
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
189193
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
190194
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
@@ -207,6 +211,8 @@ github.com/markbates/going v1.0.3 h1:mY45T5TvW+Xz5A6jY7lf4+NLg9D8+iuStIHyR7M8qsE
207211
github.com/markbates/going v1.0.3/go.mod h1:fQiT6v6yQar9UD6bd/D4Z5Afbk9J6BBVBtLiyY4gp2o=
208212
github.com/markbates/goth v1.80.0 h1:NnvatczZDzOs1hn9Ug+dVYf2Viwwkp/ZDX5K+GLjan8=
209213
github.com/markbates/goth v1.80.0/go.mod h1:4/GYHo+W6NWisrMPZnq0Yr2Q70UntNLn7KXEFhrIdAY=
214+
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
215+
github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
210216
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
211217
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
212218
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -252,6 +258,10 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
252258
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
253259
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
254260
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
261+
github.com/russellhaering/gosaml2 v0.10.0 h1:z7JTpKmC4JVG94tvSQz4lszUdKLt+uy5c6lEkhdEz3Y=
262+
github.com/russellhaering/gosaml2 v0.10.0/go.mod h1:XLwI/5aWV4E2X9p+qj6LgRwiYGv2nh4YS6pQBGlQ0Cc=
263+
github.com/russellhaering/goxmldsig v1.5.0 h1:AU2UkkYIUOTyZRbe08XMThaOCelArgvNfYapcmSjBNw=
264+
github.com/russellhaering/goxmldsig v1.5.0/go.mod h1:x98CjQNFJcWfMxeOrMnMKg70lvDP6tE0nTaeUnjXDmk=
255265
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
256266
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
257267
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
@@ -273,6 +283,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
273283
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
274284
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
275285
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
286+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
276287
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
277288
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
278289
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=

internal/server/app_apis.go

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ func (s *Server) CreateAppTx(ctx context.Context, currentTx types.Transaction, a
132132
appEntry.SourceUrl = sourceUrl
133133
appEntry.IsDev = appRequest.IsDev
134134
if appRequest.AppAuthn != "" {
135-
if !s.ssoAuth.ValidateAuthType(string(appRequest.AppAuthn)) {
136-
return nil, fmt.Errorf("invalid authentication type %s", appRequest.AppAuthn)
135+
if err := s.validateAppAuthnType(string(appRequest.AppAuthn)); err != nil {
136+
return nil, err
137137
}
138138
appEntry.Settings.AuthnType = appRequest.AppAuthn
139139
} else {
@@ -163,6 +163,18 @@ func (s *Server) CreateAppTx(ctx context.Context, currentTx types.Transaction, a
163163
return auditResult, nil
164164
}
165165

166+
func (s *Server) validateAppAuthnType(authStr string) error {
167+
if strings.HasPrefix(authStr, SAML_AUTH_PREFIX) || strings.HasPrefix(authStr, RBAC_AUTH_PREFIX+SAML_AUTH_PREFIX) {
168+
// saml auth, with or without rbac
169+
if !s.samlManager.ValidateSAMLProvider(authStr) {
170+
return fmt.Errorf("invalid saml auth type %s", authStr)
171+
}
172+
} else if !s.oAuthManager.ValidateAuthType(authStr) {
173+
return fmt.Errorf("invalid authentication type %s", authStr)
174+
}
175+
return nil
176+
}
177+
166178
func (s *Server) createApp(ctx context.Context, tx types.Transaction,
167179
appEntry *types.AppEntry, approve, dryRun bool, branch, commit, gitAuth string, applyInfo *types.CreateAppRequest, repoCache *RepoCache) (*types.AppCreateResponse, error) {
168180
if !system.IsGit(appEntry.SourceUrl) {
@@ -495,15 +507,28 @@ func (s *Server) authenticateAndServeApp(w http.ResponseWriter, r *http.Request,
495507
return
496508
}
497509
userId = strippedAuthStr
510+
} else if strings.HasPrefix(strippedAuthStr, SAML_AUTH_PREFIX) {
511+
// Use SAML auth
512+
if !s.samlManager.ValidateSAMLProvider(strippedAuthStr) {
513+
http.Error(w, "Unsupported saml provider: "+strippedAuthStr, http.StatusInternalServerError)
514+
return
515+
}
516+
userId, groups, err = s.samlManager.CheckSAMLAuth(w, r, strippedAuthStr)
517+
if err != nil {
518+
http.Error(w, err.Error(), http.StatusInternalServerError)
519+
}
520+
if userId == "" {
521+
return // Already redirected to auth provider
522+
}
498523
} else {
499524
// Use SSO auth
500-
if !s.ssoAuth.ValidateProviderName(strippedAuthStr) {
525+
if !s.oAuthManager.ValidateProviderName(strippedAuthStr) {
501526
http.Error(w, "Unsupported authentication provider: "+strippedAuthStr, http.StatusInternalServerError)
502527
return
503528
}
504529

505530
// Redirect to the auth provider if not logged in
506-
userId, groups, err = s.ssoAuth.CheckAuth(w, r, strippedAuthStr, true)
531+
userId, groups, err = s.oAuthManager.CheckAuth(w, r, strippedAuthStr, true)
507532
if err != nil {
508533
http.Error(w, err.Error(), http.StatusInternalServerError)
509534
}

internal/server/app_updates.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,8 +484,8 @@ func (s *Server) updateAppSettings(ctx context.Context, tx types.Transaction, ap
484484
}
485485

486486
if updateAppRequest.AuthnType != types.StringValueUndefined {
487-
if !s.ssoAuth.ValidateAuthType(string(updateAppRequest.AuthnType)) {
488-
return nil, fmt.Errorf("invalid authentication type %s", updateAppRequest.AuthnType)
487+
if err := s.validateAppAuthnType(string(updateAppRequest.AuthnType)); err != nil {
488+
return nil, err
489489
}
490490
linkedApp.Settings.AuthnType = types.AppAuthnType(updateAppRequest.AuthnType)
491491
}

internal/server/sso_auth.go renamed to internal/server/oauth.go

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/gorilla/sessions"
1515
"github.com/markbates/goth"
1616
"github.com/markbates/goth/gothic"
17+
"github.com/openrundev/openrun/internal/system"
1718
"github.com/openrundev/openrun/internal/types"
1819

1920
"github.com/markbates/goth/providers/amazon"
@@ -42,15 +43,16 @@ const (
4243
REDIRECT_URL = "redirect"
4344
)
4445

45-
type SSOAuth struct {
46+
// OAuthManager manages the OAuth providers and their configurations (also OIDC)
47+
type OAuthManager struct {
4648
*types.Logger
4749
config *types.ServerConfig
4850
cookieStore *sessions.CookieStore
4951
providerConfigs map[string]*types.AuthConfig
5052
}
5153

52-
func NewSSOAuth(logger *types.Logger, config *types.ServerConfig) *SSOAuth {
53-
return &SSOAuth{
54+
func NewOAuthManager(logger *types.Logger, config *types.ServerConfig) *OAuthManager {
55+
return &OAuthManager{
5456
Logger: logger,
5557
config: config,
5658
}
@@ -77,7 +79,7 @@ func generateRandomKey(length int) (string, error) {
7779
return string(key), nil
7880
}
7981

80-
func (s *SSOAuth) Setup() error {
82+
func (s *OAuthManager) Setup() error {
8183
var err error
8284
sessionKey := s.config.Security.SessionSecret
8385
if sessionKey == "" {
@@ -167,14 +169,14 @@ func (s *SSOAuth) Setup() error {
167169
}
168170

169171
if len(providers) != 0 && s.config.Security.CallbackUrl == "" {
170-
return fmt.Errorf("security.callback_url must be set for enabling SSO auth")
172+
return fmt.Errorf("security.callback_url must be set for enabling OAuth")
171173
}
172174

173175
goth.UseProviders(providers...) // Register the providers with goth
174176
return nil
175177
}
176178

177-
func (s *SSOAuth) RegisterRoutes(mux *chi.Mux) {
179+
func (s *OAuthManager) RegisterRoutes(mux *chi.Mux) {
178180
mux.Get(types.INTERNAL_URL_PREFIX+"/auth/{provider}/callback", func(w http.ResponseWriter, r *http.Request) {
179181
user, err := gothic.CompleteUserAuth(w, r)
180182
if err != nil {
@@ -308,7 +310,7 @@ func (s *SSOAuth) RegisterRoutes(mux *chi.Mux) {
308310
})
309311
}
310312

311-
func (s *SSOAuth) validateResponse(providerName string, user goth.User) error {
313+
func (s *OAuthManager) validateResponse(providerName string, user goth.User) error {
312314
providerConfig := s.providerConfigs[providerName]
313315
if providerConfig == nil {
314316
return fmt.Errorf("provider %s not configured", providerName)
@@ -326,11 +328,11 @@ func (s *SSOAuth) validateResponse(providerName string, user goth.User) error {
326328
return nil
327329
}
328330

329-
func (s *SSOAuth) ValidateProviderName(provider string) bool {
331+
func (s *OAuthManager) ValidateProviderName(provider string) bool {
330332
return s.providerConfigs[provider] != nil
331333
}
332334

333-
func (s *SSOAuth) ValidateAuthType(authType string) bool {
335+
func (s *OAuthManager) ValidateAuthType(authType string) bool {
334336
authType = strings.TrimPrefix(authType, RBAC_AUTH_PREFIX)
335337
switch authType {
336338
case string(types.AppAuthnDefault), string(types.AppAuthnSystem), string(types.AppAuthnNone):
@@ -344,7 +346,7 @@ func (s *SSOAuth) ValidateAuthType(authType string) bool {
344346
}
345347
}
346348

347-
func (s *SSOAuth) CheckAuth(w http.ResponseWriter, r *http.Request, appProvider string, updateRedirect bool) (string, []string, error) {
349+
func (s *OAuthManager) CheckAuth(w http.ResponseWriter, r *http.Request, appProvider string, updateRedirect bool) (string, []string, error) {
348350
cookieName := genCookieName(appProvider)
349351
session, err := s.cookieStore.Get(r, cookieName)
350352
if err != nil {
@@ -355,16 +357,16 @@ func (s *SSOAuth) CheckAuth(w http.ResponseWriter, r *http.Request, appProvider
355357
s.cookieStore.Save(r, w, session) //nolint:errcheck
356358
}
357359
if r.Header.Get("HX-Request") == "true" {
358-
w.Header().Set("HX-Redirect", r.RequestURI)
360+
w.Header().Set("HX-Redirect", system.GetRequestUrl(r))
359361
} else {
360-
http.Redirect(w, r, r.RequestURI, http.StatusTemporaryRedirect)
362+
http.Redirect(w, r, system.GetRequestUrl(r), http.StatusTemporaryRedirect)
361363
}
362364
return "", nil, err
363365
}
364366
if auth, ok := session.Values[AUTH_KEY].(bool); !ok || !auth {
365367
// Store the target URL before redirecting to login
366368
if updateRedirect {
367-
session.Values[REDIRECT_URL] = r.RequestURI
369+
session.Values[REDIRECT_URL] = system.GetRequestUrl(r)
368370
err = session.Save(r, w)
369371
if err != nil {
370372
s.Warn().Err(err).Msg("failed to save session")
@@ -373,25 +375,25 @@ func (s *SSOAuth) CheckAuth(w http.ResponseWriter, r *http.Request, appProvider
373375
}
374376
s.Warn().Err(err).Msg("no auth, redirecting to login")
375377
if r.Header.Get("HX-Request") == "true" {
376-
w.Header().Set("HX-Redirect", types.INTERNAL_URL_PREFIX+"/auth/"+appProvider)
378+
w.Header().Set("HX-Redirect", s.config.Security.CallbackUrl+types.INTERNAL_URL_PREFIX+"/auth/"+appProvider)
377379
} else {
378-
http.Redirect(w, r, types.INTERNAL_URL_PREFIX+"/auth/"+appProvider, http.StatusTemporaryRedirect)
380+
http.Redirect(w, r, s.config.Security.CallbackUrl+types.INTERNAL_URL_PREFIX+"/auth/"+appProvider, http.StatusTemporaryRedirect)
379381
}
380382
return "", nil, nil
381383
}
382384

383385
// Check if provider name matches the one in the session
384386
if providerName, ok := session.Values[PROVIDER_NAME_KEY].(string); !ok || providerName != appProvider {
385387
if updateRedirect {
386-
session.Values[REDIRECT_URL] = r.RequestURI
388+
session.Values[REDIRECT_URL] = system.GetRequestUrl(r)
387389
err = session.Save(r, w)
388390
if err != nil {
389391
s.Warn().Err(err).Msg("failed to save session")
390392
return "", nil, err
391393
}
392394
}
393395
s.Warn().Err(err).Msg("provider mismatch, redirecting to login")
394-
http.Redirect(w, r, types.INTERNAL_URL_PREFIX+"/auth/"+appProvider, http.StatusTemporaryRedirect)
396+
http.Redirect(w, r, s.config.Security.CallbackUrl+types.INTERNAL_URL_PREFIX+"/auth/"+appProvider, http.StatusTemporaryRedirect)
395397
return "", nil, nil
396398
}
397399

internal/server/router.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ func NewTCPHandler(logger *types.Logger, config *types.ServerConfig, server *Ser
144144
// Webhooks are always mounted, they are disabled at the app level by default
145145
router.Mount(types.WEBHOOK_URL_PREFIX, handler.serveWebhooks())
146146

147-
server.ssoAuth.RegisterRoutes(router) // register SSO routes
147+
server.oAuthManager.RegisterRoutes(router) // register OAuth routes
148+
server.samlManager.RegisterRoutes(router) // register SAML routes
148149

149150
router.HandleFunc("/*", handler.callApp)
150151
router.HandleFunc("/testperf", func(w http.ResponseWriter, r *http.Request) {

0 commit comments

Comments
 (0)