From 9117f1fc72fe0b6b69a358333e0f93aa166330e5 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 10 Jan 2024 10:49:23 -0600 Subject: [PATCH 1/6] chore: allow running fake idp with coderd dev --- coderd/coderdtest/oidctest/idp.go | 34 +++++++++++-- coderd/coderdtest/oidctest/testidp/main.go | 58 ++++++++++++++++++++++ codersdk/deployment.go | 9 ++-- 3 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 coderd/coderdtest/oidctest/testidp/main.go diff --git a/coderd/coderdtest/oidctest/idp.go b/coderd/coderdtest/oidctest/idp.go index bb758d60f5d0a..a4b48c9953ff7 100644 --- a/coderd/coderdtest/oidctest/idp.go +++ b/coderd/coderdtest/oidctest/idp.go @@ -78,9 +78,12 @@ type FakeIDP struct { // "Authorized Redirect URLs". This can be used to emulate that. hookValidRedirectURL func(redirectURL string) error hookUserInfo func(email string) (jwt.MapClaims, error) - hookMutateToken func(token map[string]interface{}) - fakeCoderd func(req *http.Request) (*http.Response, error) - hookOnRefresh func(email string) error + // defaultIDClaims is if a new client connects and we didn't preset + // some claims. + defaultIDClaims jwt.MapClaims + hookMutateToken func(token map[string]interface{}) + fakeCoderd func(req *http.Request) (*http.Response, error) + hookOnRefresh func(email string) error // Custom authentication for the client. This is useful if you want // to test something like PKI auth vs a client_secret. hookAuthenticateClient func(t testing.TB, req *http.Request) (url.Values, error) @@ -162,6 +165,12 @@ func WithStaticUserInfo(info jwt.MapClaims) func(*FakeIDP) { } } +func WithDefaultIDClaims(claims jwt.MapClaims) func(*FakeIDP) { + return func(f *FakeIDP) { + f.defaultIDClaims = claims + } +} + func WithDynamicUserInfo(userInfoFunc func(email string) (jwt.MapClaims, error)) func(*FakeIDP) { return func(f *FakeIDP) { f.hookUserInfo = userInfoFunc @@ -679,7 +688,7 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler { // Always invalidate the code after it is used. f.codeToStateMap.Delete(code) - idTokenClaims, ok := f.stateToIDTokenClaims.Load(stateStr) + idTokenClaims, ok := f.getClaims(f.stateToIDTokenClaims, stateStr) if !ok { t.Errorf("missing id token claims") http.Error(rw, "missing id token claims", http.StatusBadRequest) @@ -699,7 +708,7 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler { return } - idTokenClaims, ok := f.refreshIDTokenClaims.Load(refreshToken) + idTokenClaims, ok := f.getClaims(f.refreshIDTokenClaims, refreshToken) if !ok { t.Errorf("missing id token claims in refresh") http.Error(rw, "missing id token claims in refresh", http.StatusBadRequest) @@ -971,6 +980,10 @@ func (f *FakeIDP) ExternalAuthConfig(t testing.TB, id string, custom *ExternalAu return cfg } +func (f *FakeIDP) AppCredentials() (string, string) { + return f.clientID, f.clientSecret +} + // OIDCConfig returns the OIDC config to use for Coderd. func (f *FakeIDP) OIDCConfig(t testing.TB, scopes []string, opts ...func(cfg *coderd.OIDCConfig)) *coderd.OIDCConfig { t.Helper() @@ -1023,6 +1036,17 @@ func (f *FakeIDP) OIDCConfig(t testing.TB, scopes []string, opts ...func(cfg *co return cfg } +func (f *FakeIDP) getClaims(m *syncmap.Map[string, jwt.MapClaims], key string) (jwt.MapClaims, bool) { + v, ok := m.Load(key) + if !ok { + if f.defaultIDClaims != nil { + return f.defaultIDClaims, true + } + return nil, false + } + return v, true +} + func httpErrorCode(defaultCode int, err error) int { var stautsErr statusHookError status := defaultCode diff --git a/coderd/coderdtest/oidctest/testidp/main.go b/coderd/coderdtest/oidctest/testidp/main.go new file mode 100644 index 0000000000000..3de28ebe1ff9b --- /dev/null +++ b/coderd/coderdtest/oidctest/testidp/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "flag" + "log" + "os" + "os/signal" + "testing" + + "github.com/golang-jwt/jwt/v4" + + "github.com/coder/coder/v2/coderd/coderdtest/oidctest" +) + +func main() { + testing.Init() + _ = flag.Set("test.timeout", "0") + + flag.Parse() + + // This is just a way to run tests outside go test + testing.Main(func(pat, str string) (bool, error) { + return true, nil + }, []testing.InternalTest{ + { + Name: "Run Fake IDP", + F: RunIDP(), + }, + }, nil, nil) +} + +// RunIDP needs the testing.T because our oidctest package requires the +// testing.T. +func RunIDP() func(t *testing.T) { + return func(t *testing.T) { + idp := oidctest.NewFakeIDP(t, + oidctest.WithServing(), + oidctest.WithStaticUserInfo(jwt.MapClaims{}), + oidctest.WithDefaultIDClaims(jwt.MapClaims{}), + ) + id, sec := idp.AppCredentials() + prov := idp.WellknownConfig() + + log.Println("IDP Issuer URL", idp.IssuerURL()) + log.Println("Oauth Flags") + log.Printf(`--external-auth-providers='[{"type":"fake","client_id":"%s","client_secret":"%s","auth_url":"%s","token_url":"%s","validate_url":"%s","scopes":["openid","email","profile"]}]'`, + id, sec, prov.AuthURL, prov.TokenURL, prov.UserInfoURL, + ) + + log.Println("Press Ctrl+C to exit") + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + select { + case <-c: + log.Println("Closing") + } + } +} diff --git a/codersdk/deployment.go b/codersdk/deployment.go index c3e2db238e4ef..e0dcc4fe4ded2 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -1790,11 +1790,10 @@ Write out the current server config as YAML to stdout.`, // Env handling is done in cli.ReadGitAuthFromEnvironment Name: "External Auth Providers", Description: "External Authentication providers.", - // We need extra scrutiny to ensure this works, is documented, and - // tested before enabling. - YAML: "externalAuthProviders", - Value: &c.ExternalAuthConfigs, - Hidden: true, + YAML: "externalAuthProviders", + Flag: "external-auth-providers", + Value: &c.ExternalAuthConfigs, + Hidden: true, }, { Name: "Custom wgtunnel Host", From 27d1787640bff3f2a86b33e80b9d11a9a4bc2efa Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 10 Jan 2024 11:05:49 -0600 Subject: [PATCH 2/6] wording --- coderd/coderdtest/oidctest/testidp/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/coderdtest/oidctest/testidp/main.go b/coderd/coderdtest/oidctest/testidp/main.go index 3de28ebe1ff9b..205660b3d2377 100644 --- a/coderd/coderdtest/oidctest/testidp/main.go +++ b/coderd/coderdtest/oidctest/testidp/main.go @@ -42,7 +42,7 @@ func RunIDP() func(t *testing.T) { prov := idp.WellknownConfig() log.Println("IDP Issuer URL", idp.IssuerURL()) - log.Println("Oauth Flags") + log.Println("Coderd Flags") log.Printf(`--external-auth-providers='[{"type":"fake","client_id":"%s","client_secret":"%s","auth_url":"%s","token_url":"%s","validate_url":"%s","scopes":["openid","email","profile"]}]'`, id, sec, prov.AuthURL, prov.TokenURL, prov.UserInfoURL, ) From 7cd28aeae0a737f704b2a564a45ce7a00970fbbb Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 10 Jan 2024 11:27:50 -0600 Subject: [PATCH 3/6] Linter --- {coderd/coderdtest/oidctest => cmd}/testidp/main.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) rename {coderd/coderdtest/oidctest => cmd}/testidp/main.go (95%) diff --git a/coderd/coderdtest/oidctest/testidp/main.go b/cmd/testidp/main.go similarity index 95% rename from coderd/coderdtest/oidctest/testidp/main.go rename to cmd/testidp/main.go index 205660b3d2377..af85908cf5621 100644 --- a/coderd/coderdtest/oidctest/testidp/main.go +++ b/cmd/testidp/main.go @@ -50,9 +50,10 @@ func RunIDP() func(t *testing.T) { log.Println("Press Ctrl+C to exit") c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) - select { - case <-c: - log.Println("Closing") - } + + // Block until ctl+c + <-c + log.Println("Closing") + } } From 1a19b1cbcbd2e6361e536925c735ec9843b6810c Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 10 Jan 2024 11:33:41 -0600 Subject: [PATCH 4/6] Linter --- cmd/testidp/main.go | 1 - coderd/coderdtest/oidctest/idp.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/testidp/main.go b/cmd/testidp/main.go index af85908cf5621..fd96d0b84a87e 100644 --- a/cmd/testidp/main.go +++ b/cmd/testidp/main.go @@ -54,6 +54,5 @@ func RunIDP() func(t *testing.T) { // Block until ctl+c <-c log.Println("Closing") - } } diff --git a/coderd/coderdtest/oidctest/idp.go b/coderd/coderdtest/oidctest/idp.go index a4b48c9953ff7..6b6936e3465e7 100644 --- a/coderd/coderdtest/oidctest/idp.go +++ b/coderd/coderdtest/oidctest/idp.go @@ -980,7 +980,7 @@ func (f *FakeIDP) ExternalAuthConfig(t testing.TB, id string, custom *ExternalAu return cfg } -func (f *FakeIDP) AppCredentials() (string, string) { +func (f *FakeIDP) AppCredentials() (clientID string, clientSecret string) { return f.clientID, f.clientSecret } From f23d4e7636dc485b5f98f72807eb32832841f956 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 10 Jan 2024 14:00:58 -0600 Subject: [PATCH 5/6] add short usage doc --- cmd/testidp/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 cmd/testidp/README.md diff --git a/cmd/testidp/README.md b/cmd/testidp/README.md new file mode 100644 index 0000000000000..880064c803f3b --- /dev/null +++ b/cmd/testidp/README.md @@ -0,0 +1,15 @@ +# How to use + +Start the idp service: +```bash +$ go run main.go +2024-01-10 16:48:01.415 [info] stdlib: 2024/01/10 10:48:01 IDP Issuer URL http://127.0.0.1:44517 +2024-01-10 16:48:01.415 [info] stdlib: 2024/01/10 10:48:01 Oauth Flags +2024-01-10 16:48:01.415 [info] stdlib: 2024/01/10 10:48:01 --external-auth-providers='[{"type":"fake","client_id":"f2df566b-a1c9-407a-8b75-480db45c6476","client_secret":"55aca4e3-7b94-44b6-9f45-ecb5e81c560d","auth_url":"http://127.0.0.1:44517/oauth2/authorize","token_url":"http://127.0.0.1:44517/oauth2/token","validate_url":"http://127.0.0.1:44517/oauth2/userinfo","scopes":["openid","email","profile"]}]' +2024-01-10 16:48:01.415 [info] stdlib: 2024/01/10 10:48:01 Press Ctrl+C to exit +``` + +Then use the flag into your coderd instance: +```bash +develop.sh -- --external-auth-providers='[{"type":"fake","client_id":"f2df566b-a1c9-407a-8b75-480db45c6476","client_secret":"55aca4e3-7b94-44b6-9f45-ecb5e81c560d","auth_url":"http://127.0.0.1:44517/oauth2/authorize","token_url":"http://127.0.0.1:44517/oauth2/token","validate_url":"http://127.0.0.1:44517/oauth2/userinfo","scopes":["openid","email","profile"]}]' +``` From eab1a1a278c385d77a936a070d0057f6a76621e4 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 11 Jan 2024 12:00:24 -0600 Subject: [PATCH 6/6] fmt --- cmd/testidp/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/testidp/README.md b/cmd/testidp/README.md index 880064c803f3b..2dac79af8602b 100644 --- a/cmd/testidp/README.md +++ b/cmd/testidp/README.md @@ -1,6 +1,7 @@ # How to use Start the idp service: + ```bash $ go run main.go 2024-01-10 16:48:01.415 [info] stdlib: 2024/01/10 10:48:01 IDP Issuer URL http://127.0.0.1:44517 @@ -10,6 +11,7 @@ $ go run main.go ``` Then use the flag into your coderd instance: + ```bash develop.sh -- --external-auth-providers='[{"type":"fake","client_id":"f2df566b-a1c9-407a-8b75-480db45c6476","client_secret":"55aca4e3-7b94-44b6-9f45-ecb5e81c560d","auth_url":"http://127.0.0.1:44517/oauth2/authorize","token_url":"http://127.0.0.1:44517/oauth2/token","validate_url":"http://127.0.0.1:44517/oauth2/userinfo","scopes":["openid","email","profile"]}]' ```