diff options
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | cmd/cashierd/main.go | 2 | ||||
-rw-r--r-- | example-server.conf | 1 | ||||
-rw-r--r-- | server/auth/github/github.go | 4 | ||||
-rw-r--r-- | server/auth/github/github_test.go | 10 | ||||
-rw-r--r-- | server/auth/gitlab/gitlab.go | 5 | ||||
-rw-r--r-- | server/auth/gitlab/gitlab_test.go | 13 | ||||
-rw-r--r-- | server/auth/google/google.go | 4 | ||||
-rw-r--r-- | server/auth/google/google_test.go | 9 | ||||
-rw-r--r-- | server/auth/provider.go | 18 | ||||
-rw-r--r-- | server/auth/provider_test.go | 30 | ||||
-rw-r--r-- | server/auth/testprovider/testprovider.go | 3 | ||||
-rw-r--r-- | server/config/config.go | 1 | ||||
-rw-r--r-- | server/config/config_test.go | 1 | ||||
-rw-r--r-- | server/config/testdata/test.config | 1 |
15 files changed, 76 insertions, 29 deletions
@@ -152,7 +152,6 @@ Obviously you should setup a role user for running in prodution. - `provider` : string. Name of the oauth provider. Valid providers are currently "google", "github" and "gitlab". - `oauth_client_id` : string. Oauth Client ID. This can be a secret stored in a [vault](https://www.vaultproject.io/) using the form `/vault/path/key` e.g. `/vault/secret/cashier/oauth_client_id`. - `oauth_client_secret` : string. Oauth secret. This can be a secret stored in a [vault](https://www.vaultproject.io/) using the form `/vault/path/key` e.g. `/vault/secret/cashier/oauth_client_secret`. -- `oauth_callback_url` : string. URL that the Oauth provider will redirect to after user authorisation. The path is hardcoded to `"/auth/callback"` in the source. - `provider_opts` : object. Additional options for the provider. - `users_whitelist` : array of strings. Optional list of whitelisted usernames. If missing, all users of your current domain/organization are allowed to authenticate against cashierd. For Google auth a user is an email address. For GitHub auth a user is a GitHub username. @@ -210,7 +209,7 @@ The server is configured using a HCL configuration file - [example](example-serv For the server you need the following: - A new ssh private key. Generate one in the usual way using `ssh-keygen -f ssh_ca` - this is your CA signing key. At this time Cashier supports RSA, ECDSA and Ed25519 keys. *Important* This key should be kept safe - *ANY* ssh key signed with this key will be able to access your machines. -- OAuth (Google or GitHub) credentials. You may also need to set the callback URL when creating these. +- OAuth (Google, GitHub or Gitlab) credentials. Cashier's callback handler is `/auth/callback` so set the callback URL appropriately when creating the credentials. ## Using cashier Once the server is up and running you'll need to configure your client. diff --git a/cmd/cashierd/main.go b/cmd/cashierd/main.go index 7277773..b054606 100644 --- a/cmd/cashierd/main.go +++ b/cmd/cashierd/main.go @@ -182,7 +182,7 @@ func signHandler(a *appContext, w http.ResponseWriter, r *http.Request) (int, er func loginHandler(a *appContext, w http.ResponseWriter, r *http.Request) (int, error) { state := newState() a.setAuthStateCookie(w, r, state) - a.authsession = a.authprovider.StartSession(state) + a.authsession = a.authprovider.StartSession(state, r) http.Redirect(w, r, a.authsession.AuthURL, http.StatusFound) return http.StatusFound, nil } diff --git a/example-server.conf b/example-server.conf index e0b3ea5..e4484d1 100644 --- a/example-server.conf +++ b/example-server.conf @@ -23,7 +23,6 @@ auth { provider = "google" # Oauth provider to use oauth_client_id = "nnnnnnnnnnnnnnnn.apps.googleusercontent.com" # Oauth client ID oauth_client_secret = "yyyyyyyyyyyyyyyyyyyyyy" # Oauth client secret - oauth_callback_url = "https://sshca.example.com/auth/callback" # Oauth callback url provider_opts { domain = "example.com" # Oauth-provider specific options } diff --git a/server/auth/github/github.go b/server/auth/github/github.go index 46cf76a..c985eed 100644 --- a/server/auth/github/github.go +++ b/server/auth/github/github.go @@ -40,7 +40,6 @@ func New(c *config.Auth) (*Config, error) { config: &oauth2.Config{ ClientID: c.OauthClientID, ClientSecret: c.OauthClientSecret, - RedirectURL: c.OauthCallbackURL, Endpoint: github.Endpoint, Scopes: []string{ string(githubapi.ScopeUser), @@ -91,7 +90,8 @@ func (c *Config) Revoke(token *oauth2.Token) error { } // StartSession retrieves an authentication endpoint from Github. -func (c *Config) StartSession(state string) *auth.Session { +func (c *Config) StartSession(state string, r *http.Request) *auth.Session { + c.config.RedirectURL = auth.Oauth2RedirectURL(r) return &auth.Session{ AuthURL: c.config.AuthCodeURL(state), } diff --git a/server/auth/github/github_test.go b/server/auth/github/github_test.go index 8c51f4f..d9d5f00 100644 --- a/server/auth/github/github_test.go +++ b/server/auth/github/github_test.go @@ -2,6 +2,7 @@ package github import ( "fmt" + "net/http" "testing" "github.com/nsheridan/cashier/server/config" @@ -22,13 +23,11 @@ func TestNew(t *testing.T) { p, _ := New(&config.Auth{ OauthClientID: oauthClientID, OauthClientSecret: oauthClientSecret, - OauthCallbackURL: oauthCallbackURL, ProviderOpts: map[string]string{"organization": organization}, UsersWhitelist: users, }) a.Equal(p.config.ClientID, oauthClientID) a.Equal(p.config.ClientSecret, oauthClientSecret) - a.Equal(p.config.RedirectURL, oauthCallbackURL) a.Equal(p.organization, organization) a.Equal(p.whitelist, map[string]bool{"user": true}) } @@ -37,7 +36,6 @@ func TestWhitelist(t *testing.T) { c := &config.Auth{ OauthClientID: oauthClientID, OauthClientSecret: oauthClientSecret, - OauthCallbackURL: oauthCallbackURL, ProviderOpts: map[string]string{"organization": ""}, UsersWhitelist: []string{}, } @@ -61,7 +59,10 @@ func TestStartSession(t *testing.T) { a := assert.New(t) p, _ := newGithub() - s := p.StartSession("test_state") + r := &http.Request{ + Host: oauthCallbackURL, + } + s := p.StartSession("test_state", r) a.Contains(s.AuthURL, "github.com/login/oauth/authorize") a.Contains(s.AuthURL, "state=test_state") a.Contains(s.AuthURL, fmt.Sprintf("client_id=%s", oauthClientID)) @@ -71,7 +72,6 @@ func newGithub() (*Config, error) { c := &config.Auth{ OauthClientID: oauthClientID, OauthClientSecret: oauthClientSecret, - OauthCallbackURL: oauthCallbackURL, ProviderOpts: map[string]string{"organization": organization}, } return New(c) diff --git a/server/auth/gitlab/gitlab.go b/server/auth/gitlab/gitlab.go index f76b2e8..27edafa 100644 --- a/server/auth/gitlab/gitlab.go +++ b/server/auth/gitlab/gitlab.go @@ -2,6 +2,7 @@ package gitlab import ( "errors" + "net/http" "strconv" "github.com/nsheridan/cashier/server/auth" @@ -51,7 +52,6 @@ func New(c *config.Auth) (*Config, error) { config: &oauth2.Config{ ClientID: c.OauthClientID, ClientSecret: c.OauthClientSecret, - RedirectURL: c.OauthCallbackURL, Endpoint: oauth2.Endpoint{ AuthURL: siteURL + "oauth/authorize", TokenURL: siteURL + "oauth/token", @@ -110,7 +110,8 @@ func (c *Config) Revoke(token *oauth2.Token) error { } // StartSession retrieves an authentication endpoint from Gitlab. -func (c *Config) StartSession(state string) *auth.Session { +func (c *Config) StartSession(state string, r *http.Request) *auth.Session { + c.config.RedirectURL = auth.Oauth2RedirectURL(r) return &auth.Session{ AuthURL: c.config.AuthCodeURL(state), } diff --git a/server/auth/gitlab/gitlab_test.go b/server/auth/gitlab/gitlab_test.go index 39c2701..676cda2 100644 --- a/server/auth/gitlab/gitlab_test.go +++ b/server/auth/gitlab/gitlab_test.go @@ -2,6 +2,7 @@ package gitlab import ( "fmt" + "net/http" "testing" "github.com/nsheridan/cashier/server/auth" @@ -25,7 +26,6 @@ func TestNew(t *testing.T) { g := p.(*Config) a.Equal(g.config.ClientID, oauthClientID) a.Equal(g.config.ClientSecret, oauthClientSecret) - a.Equal(g.config.RedirectURL, oauthCallbackURL) } func TestNewBrokenSiteURL(t *testing.T) { @@ -55,7 +55,10 @@ func TestGoodAllUsers(t *testing.T) { a := assert.New(t) p, _ := newGitlab() - s := p.StartSession("test_state") + r := &http.Request{ + Host: oauthCallbackURL, + } + s := p.StartSession("test_state", r) a.Contains(s.AuthURL, "exampleorg/oauth/authorize") a.Contains(s.AuthURL, "state=test_state") a.Contains(s.AuthURL, fmt.Sprintf("client_id=%s", oauthClientID)) @@ -77,7 +80,10 @@ func TestStartSession(t *testing.T) { a := assert.New(t) p, _ := newGitlab() - s := p.StartSession("test_state") + r := &http.Request{ + Host: oauthCallbackURL, + } + s := p.StartSession("test_state", r) a.Contains(s.AuthURL, "exampleorg/oauth/authorize") a.Contains(s.AuthURL, "state=test_state") a.Contains(s.AuthURL, fmt.Sprintf("client_id=%s", oauthClientID)) @@ -87,7 +93,6 @@ func newGitlab() (auth.Provider, error) { c := &config.Auth{ OauthClientID: oauthClientID, OauthClientSecret: oauthClientSecret, - OauthCallbackURL: oauthCallbackURL, ProviderOpts: map[string]string{ "group": group, "siteurl": siteurl, diff --git a/server/auth/google/google.go b/server/auth/google/google.go index 8c6f53b..305b6f4 100644 --- a/server/auth/google/google.go +++ b/server/auth/google/google.go @@ -43,7 +43,6 @@ func New(c *config.Auth) (*Config, error) { config: &oauth2.Config{ ClientID: c.OauthClientID, ClientSecret: c.OauthClientSecret, - RedirectURL: c.OauthCallbackURL, Endpoint: google.Endpoint, Scopes: []string{googleapi.UserinfoEmailScope, googleapi.UserinfoProfileScope}, }, @@ -101,7 +100,8 @@ func (c *Config) Revoke(token *oauth2.Token) error { } // StartSession retrieves an authentication endpoint from Google. -func (c *Config) StartSession(state string) *auth.Session { +func (c *Config) StartSession(state string, r *http.Request) *auth.Session { + c.config.RedirectURL = auth.Oauth2RedirectURL(r) return &auth.Session{ AuthURL: c.config.AuthCodeURL(state, oauth2.SetAuthURLParam("hd", c.domain)), } diff --git a/server/auth/google/google_test.go b/server/auth/google/google_test.go index b3d2633..4d6191b 100644 --- a/server/auth/google/google_test.go +++ b/server/auth/google/google_test.go @@ -2,6 +2,7 @@ package google import ( "fmt" + "net/http" "testing" "github.com/nsheridan/cashier/server/config" @@ -22,7 +23,6 @@ func TestNew(t *testing.T) { a.NoError(err) a.Equal(p.config.ClientID, oauthClientID) a.Equal(p.config.ClientSecret, oauthClientSecret) - a.Equal(p.config.RedirectURL, oauthCallbackURL) a.Equal(p.domain, domain) a.Equal(p.whitelist, map[string]bool{"user": true}) } @@ -31,7 +31,6 @@ func TestWhitelist(t *testing.T) { c := &config.Auth{ OauthClientID: oauthClientID, OauthClientSecret: oauthClientSecret, - OauthCallbackURL: oauthCallbackURL, ProviderOpts: map[string]string{"domain": ""}, UsersWhitelist: []string{}, } @@ -56,7 +55,10 @@ func TestStartSession(t *testing.T) { p, err := newGoogle() a.NoError(err) - s := p.StartSession("test_state") + r := &http.Request{ + Host: oauthCallbackURL, + } + s := p.StartSession("test_state", r) a.Contains(s.AuthURL, "accounts.google.com/o/oauth2/auth") a.Contains(s.AuthURL, "state=test_state") a.Contains(s.AuthURL, fmt.Sprintf("hd=%s", domain)) @@ -67,7 +69,6 @@ func newGoogle() (*Config, error) { c := &config.Auth{ OauthClientID: oauthClientID, OauthClientSecret: oauthClientSecret, - OauthCallbackURL: oauthCallbackURL, ProviderOpts: map[string]string{"domain": domain}, UsersWhitelist: users, } diff --git a/server/auth/provider.go b/server/auth/provider.go index 06dc1c9..d4a8e58 100644 --- a/server/auth/provider.go +++ b/server/auth/provider.go @@ -1,11 +1,16 @@ package auth -import "golang.org/x/oauth2" +import ( + "fmt" + "net/http" + + "golang.org/x/oauth2" +) // Provider is an abstraction of different auth methods. type Provider interface { Name() string - StartSession(string) *Session + StartSession(string, *http.Request) *Session Exchange(string) (*oauth2.Token, error) Username(*oauth2.Token) string Valid(*oauth2.Token) bool @@ -28,3 +33,12 @@ func (s *Session) Authorize(provider Provider, code string) error { s.Token = t return nil } + +// Oauth2RedirectURL returns an OAuth redirect_uri for this request. +func Oauth2RedirectURL(r *http.Request) string { + protocol := "http" + if r.TLS != nil { + protocol = "https" + } + return fmt.Sprintf("%s://%s/auth/callback", protocol, r.Host) +} diff --git a/server/auth/provider_test.go b/server/auth/provider_test.go new file mode 100644 index 0000000..e35dcea --- /dev/null +++ b/server/auth/provider_test.go @@ -0,0 +1,30 @@ +package auth + +import ( + "crypto/tls" + "net/http" + "testing" +) + +func TestHTTP(t *testing.T) { + want := "http://example.com/auth/callback" + r := &http.Request{ + Host: "example.com", + } + ret := Oauth2RedirectURL(r) + if want != ret { + t.Errorf("Wanted %s, got %s", want, ret) + } +} + +func TestHTTPS(t *testing.T) { + want := "https://example.com/auth/callback" + r := &http.Request{ + Host: "example.com", + TLS: &tls.ConnectionState{}, + } + ret := Oauth2RedirectURL(r) + if want != ret { + t.Errorf("Wanted %s, got %s", want, ret) + } +} diff --git a/server/auth/testprovider/testprovider.go b/server/auth/testprovider/testprovider.go index e30b04a..0bc2397 100644 --- a/server/auth/testprovider/testprovider.go +++ b/server/auth/testprovider/testprovider.go @@ -1,6 +1,7 @@ package testprovider import ( + "net/http" "time" "github.com/nsheridan/cashier/server/auth" @@ -38,7 +39,7 @@ func (c *Config) Revoke(token *oauth2.Token) error { } // StartSession retrieves an authentication endpoint. -func (c *Config) StartSession(state string) *auth.Session { +func (c *Config) StartSession(state string, r *http.Request) *auth.Session { return &auth.Session{ AuthURL: "https://www.example.com/auth", } diff --git a/server/config/config.go b/server/config/config.go index 422a135..794ba8a 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -43,7 +43,6 @@ type Server struct { type Auth struct { OauthClientID string `hcl:"oauth_client_id"` OauthClientSecret string `hcl:"oauth_client_secret"` - OauthCallbackURL string `hcl:"oauth_callback_url"` Provider string `hcl:"provider"` ProviderOpts map[string]string `hcl:"provider_opts"` UsersWhitelist []string `hcl:"users_whitelist"` diff --git a/server/config/config_test.go b/server/config/config_test.go index 5536a4e..e247917 100644 --- a/server/config/config_test.go +++ b/server/config/config_test.go @@ -23,7 +23,6 @@ var ( Auth: &Auth{ OauthClientID: "client_id", OauthClientSecret: "secret", - OauthCallbackURL: "https://sshca.example.com/auth/callback", Provider: "google", ProviderOpts: map[string]string{"domain": "example.com"}, UsersWhitelist: []string{"a_user"}, diff --git a/server/config/testdata/test.config b/server/config/testdata/test.config index 96899e7..6584add 100644 --- a/server/config/testdata/test.config +++ b/server/config/testdata/test.config @@ -19,7 +19,6 @@ auth { provider = "google" oauth_client_id = "client_id" oauth_client_secret = "secret" - oauth_callback_url = "https://sshca.example.com/auth/callback" provider_opts { domain = "example.com" } |