Add support for permitted groups
This commit is contained in:
parent
2fd6f02cd8
commit
b546ef4a3f
4
main.go
4
main.go
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
emailDomains := StringArray{}
|
||||
upstreams := StringArray{}
|
||||
skipAuthRegex := StringArray{}
|
||||
googleGroups := StringArray{}
|
||||
permittedGroups := StringArray{}
|
||||
|
||||
config := flagSet.String("config", "", "path to config file")
|
||||
showVersion := flagSet.Bool("version", false, "print version string")
|
||||
|
|
@ -36,6 +36,7 @@ func main() {
|
|||
flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream")
|
||||
flagSet.Bool("pass-groups", false, "pass user group information in the X-Forwarded-Groups header to upstream (Azure only)")
|
||||
flagSet.String("filter-groups", "", "exclude groups that do not contain this value in its 'displayName' (Azure only)")
|
||||
flagSet.Var(&permittedGroups, "permit-groups", "restrict logins to members of this group (may be given multiple times; Azure and Google only).")
|
||||
flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header")
|
||||
flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header")
|
||||
flagSet.Bool("pass-host-header", true, "pass the request Host Header to upstream")
|
||||
|
|
@ -47,7 +48,6 @@ func main() {
|
|||
flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.")
|
||||
flagSet.String("github-org", "", "restrict logins to members of this organisation")
|
||||
flagSet.String("github-team", "", "restrict logins to members of this team")
|
||||
flagSet.Var(&googleGroups, "google-group", "restrict logins to members of this google group (may be given multiple times).")
|
||||
flagSet.String("google-admin-email", "", "the google admin to impersonate for api calls")
|
||||
flagSet.String("google-service-account-json", "", "the path to the service account json credentials")
|
||||
flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"")
|
||||
|
|
|
|||
|
|
@ -566,7 +566,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
// set cookie, or deny
|
||||
if p.Validator(session.Email) && p.provider.ValidateGroup(session.Email) {
|
||||
if p.Validator(session.Email) && p.provider.ValidateGroup(session) {
|
||||
log.Printf("%s authentication complete %s", remoteAddr, session)
|
||||
err := p.SaveSession(rw, req, session)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ func TestEncodedSlashes(t *testing.T) {
|
|||
|
||||
func TestRobotsTxt(t *testing.T) {
|
||||
opts := NewOptions()
|
||||
opts.Provider = "google"
|
||||
opts.ClientID = "bazquux"
|
||||
opts.ClientSecret = "foobar"
|
||||
opts.CookieSecret = "xyzzyplugh"
|
||||
|
|
@ -151,6 +152,7 @@ func TestBasicAuthPassword(t *testing.T) {
|
|||
opts.Upstreams = append(opts.Upstreams, provider_server.URL)
|
||||
// The CookieSecret must be 32 bytes in order to create the AES
|
||||
// cipher.
|
||||
opts.Provider = "google"
|
||||
opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
|
||||
opts.ClientID = "bazquux"
|
||||
opts.ClientSecret = "foobar"
|
||||
|
|
@ -245,6 +247,7 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
|
|||
t.opts.Upstreams = append(t.opts.Upstreams, t.provider_server.URL)
|
||||
// The CookieSecret must be 32 bytes in order to create the AES
|
||||
// cipher.
|
||||
t.opts.Provider = "google"
|
||||
t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
|
||||
t.opts.ClientID = "bazquux"
|
||||
t.opts.ClientSecret = "foobar"
|
||||
|
|
@ -370,6 +373,7 @@ func NewSignInPageTest() *SignInPageTest {
|
|||
var sip_test SignInPageTest
|
||||
|
||||
sip_test.opts = NewOptions()
|
||||
sip_test.opts.Provider = "google"
|
||||
sip_test.opts.CookieSecret = "foobar"
|
||||
sip_test.opts.ClientID = "bazquux"
|
||||
sip_test.opts.ClientSecret = "xyzzyplugh"
|
||||
|
|
@ -441,6 +445,7 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts) *ProcessCookieTest {
|
|||
var pc_test ProcessCookieTest
|
||||
|
||||
pc_test.opts = NewOptions()
|
||||
pc_test.opts.Provider = "google"
|
||||
pc_test.opts.ClientID = "bazquux"
|
||||
pc_test.opts.ClientSecret = "xyzzyplugh"
|
||||
pc_test.opts.CookieSecret = "0123456789abcdefabcd"
|
||||
|
|
@ -672,6 +677,7 @@ type SignatureTest struct {
|
|||
|
||||
func NewSignatureTest() *SignatureTest {
|
||||
opts := NewOptions()
|
||||
opts.Provider = "google"
|
||||
opts.CookieSecret = "cookie secret"
|
||||
opts.ClientID = "client ID"
|
||||
opts.ClientSecret = "client secret"
|
||||
|
|
|
|||
21
options.go
21
options.go
|
|
@ -32,7 +32,6 @@ type Options struct {
|
|||
EmailDomains []string `flag:"email-domain" cfg:"email_domains"`
|
||||
GitHubOrg string `flag:"github-org" cfg:"github_org"`
|
||||
GitHubTeam string `flag:"github-team" cfg:"github_team"`
|
||||
GoogleGroups []string `flag:"google-group" cfg:"google_group"`
|
||||
GoogleAdminEmail string `flag:"google-admin-email" cfg:"google_admin_email"`
|
||||
GoogleServiceAccountJSON string `flag:"google-service-account-json" cfg:"google_service_account_json"`
|
||||
HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file"`
|
||||
|
|
@ -53,6 +52,7 @@ type Options struct {
|
|||
PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
|
||||
PassGroups bool `flag:"pass-groups" cfg:"pass_groups"`
|
||||
FilterGroups string `flag:"filter-groups" cfg:"filter_groups"`
|
||||
PermitGroups []string `flag:"permit-groups" cfg:"permit_groups"`
|
||||
BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"`
|
||||
PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"`
|
||||
PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"`
|
||||
|
|
@ -197,9 +197,10 @@ func (o *Options) Validate() error {
|
|||
o.CookieExpire.String()))
|
||||
}
|
||||
|
||||
if len(o.GoogleGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" {
|
||||
if len(o.GoogleGroups) < 1 {
|
||||
msgs = append(msgs, "missing setting: google-group")
|
||||
if o.Provider == "google" {
|
||||
if len(o.PermitGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" {
|
||||
if len(o.PermitGroups) < 1 {
|
||||
msgs = append(msgs, "missing setting: permit-groups")
|
||||
}
|
||||
if o.GoogleAdminEmail == "" {
|
||||
msgs = append(msgs, "missing setting: google-admin-email")
|
||||
|
|
@ -208,6 +209,7 @@ func (o *Options) Validate() error {
|
|||
msgs = append(msgs, "missing setting: google-service-account-json")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msgs = parseSignatureKey(o, msgs)
|
||||
msgs = validateCookieName(o, msgs)
|
||||
|
|
@ -239,10 +241,17 @@ func parseProviderInfo(o *Options, msgs []string) []string {
|
|||
p.ValidateURL, msgs = parseURL(o.ValidateURL, "validate", msgs)
|
||||
p.ProtectedResource, msgs = parseURL(o.ProtectedResource, "resource", msgs)
|
||||
|
||||
o.provider = providers.New(o.Provider, p)
|
||||
provider, err := providers.New(o.Provider, p)
|
||||
o.provider = provider
|
||||
if err != nil {
|
||||
msgs = append(msgs, err.Error())
|
||||
}
|
||||
switch p := o.provider.(type) {
|
||||
case *providers.AzureProvider:
|
||||
p.Configure(o.AzureTenant)
|
||||
if len(o.PermitGroups) > 0 {
|
||||
p.SetGroupRestriction(o.PermitGroups)
|
||||
}
|
||||
case *providers.GitHubProvider:
|
||||
p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam)
|
||||
case *providers.GoogleProvider:
|
||||
|
|
@ -251,7 +260,7 @@ func parseProviderInfo(o *Options, msgs []string) []string {
|
|||
if err != nil {
|
||||
msgs = append(msgs, "invalid Google credentials file: "+o.GoogleServiceAccountJSON)
|
||||
} else {
|
||||
p.SetGroupRestriction(o.GoogleGroups, o.GoogleAdminEmail, file)
|
||||
p.SetGroupRestriction(o.PermitGroups, o.GoogleAdminEmail, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import (
|
|||
func testOptions() *Options {
|
||||
o := NewOptions()
|
||||
o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8080/")
|
||||
o.Provider = "google"
|
||||
o.CookieSecret = "foobar"
|
||||
o.ClientID = "bazquux"
|
||||
o.ClientSecret = "xyzzyplugh"
|
||||
|
|
@ -38,25 +39,34 @@ func TestNewOptions(t *testing.T) {
|
|||
"missing setting: upstream",
|
||||
"missing setting: cookie-secret",
|
||||
"missing setting: client-id",
|
||||
"missing setting: client-secret"})
|
||||
"missing setting: client-secret",
|
||||
"missing setting: provider"})
|
||||
assert.Equal(t, expected, err.Error())
|
||||
}
|
||||
|
||||
func TestGoogleGroupOptions(t *testing.T) {
|
||||
func TestGooglePermitGroupsOptions(t *testing.T) {
|
||||
o := testOptions()
|
||||
o.GoogleGroups = []string{"googlegroup"}
|
||||
o.GoogleAdminEmail = "admin@example.com"
|
||||
err := o.Validate()
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
expected := errorMsg([]string{
|
||||
"missing setting: google-admin-email",
|
||||
"missing setting: permit-groups",
|
||||
"missing setting: google-service-account-json"})
|
||||
assert.Equal(t, expected, err.Error())
|
||||
}
|
||||
|
||||
func TestGoogleGroupInvalidFile(t *testing.T) {
|
||||
func TestPermitGroupsOptions(t *testing.T) {
|
||||
o := testOptions()
|
||||
o.GoogleGroups = []string{"test_group"}
|
||||
o.Provider = "azure"
|
||||
o.PermitGroups = []string{"agoodgroup"}
|
||||
err := o.Validate()
|
||||
assert.Equal(t, nil, err)
|
||||
}
|
||||
|
||||
func TestPermitGroupsInvalidFile(t *testing.T) {
|
||||
o := testOptions()
|
||||
o.PermitGroups = []string{"test_group"}
|
||||
o.GoogleAdminEmail = "admin@example.com"
|
||||
o.GoogleServiceAccountJSON = "file_doesnt_exist.json"
|
||||
err := o.Validate()
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import (
|
|||
type AzureProvider struct {
|
||||
*ProviderData
|
||||
Tenant string
|
||||
PermittedGroups []string
|
||||
}
|
||||
|
||||
func NewAzureProvider(p *ProviderData) *AzureProvider {
|
||||
|
|
@ -205,3 +206,18 @@ func (p *AzureProvider) GetLoginURL(redirectURI, finalRedirect string) string {
|
|||
a.RawQuery = params.Encode()
|
||||
return a.String()
|
||||
}
|
||||
|
||||
func (p *AzureProvider) SetGroupRestriction(groups []string) {
|
||||
p.PermittedGroups = groups
|
||||
}
|
||||
|
||||
func (p *AzureProvider) ValidateGroup(s *SessionState) bool {
|
||||
if len(p.PermittedGroups) != 0 {
|
||||
for _, pGroup := range p.PermittedGroups {
|
||||
if strings.Contains(s.Groups, pGroup) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,8 +252,8 @@ func fetchGroupMembers(service *admin.Service, group string) ([]*admin.Member, e
|
|||
|
||||
// ValidateGroup validates that the provided email exists in the configured Google
|
||||
// group(s).
|
||||
func (p *GoogleProvider) ValidateGroup(email string) bool {
|
||||
return p.GroupValidator(email)
|
||||
func (p *GoogleProvider) ValidateGroup(s *SessionState) bool {
|
||||
return p.GroupValidator(s.Email)
|
||||
}
|
||||
|
||||
func (p *GoogleProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) {
|
||||
|
|
@ -267,7 +267,7 @@ func (p *GoogleProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) {
|
|||
}
|
||||
|
||||
// re-check that the user is in the proper google group(s)
|
||||
if !p.ValidateGroup(s.Email) {
|
||||
if !p.ValidateGroup(s) {
|
||||
return false, fmt.Errorf("%s is no longer in the group(s)", s.Email)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ func (p *ProviderData) GetGroups(s *SessionState, f string) (string, error) {
|
|||
|
||||
// ValidateGroup validates that the provided email exists in the configured provider
|
||||
// email group(s).
|
||||
func (p *ProviderData) ValidateGroup(email string) bool {
|
||||
func (p *ProviderData) ValidateGroup(s *SessionState) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package providers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/bitly/oauth2_proxy/cookie"
|
||||
)
|
||||
|
||||
|
|
@ -9,7 +10,7 @@ type Provider interface {
|
|||
GetEmailAddress(*SessionState) (string, error)
|
||||
GetGroups(*SessionState, string) (string, error)
|
||||
Redeem(string, string) (*SessionState, error)
|
||||
ValidateGroup(string) bool
|
||||
ValidateGroup(*SessionState) bool
|
||||
ValidateSessionState(*SessionState) bool
|
||||
GetLoginURL(redirectURI, finalRedirect string) string
|
||||
RefreshSessionIfNeeded(*SessionState) (bool, error)
|
||||
|
|
@ -17,21 +18,23 @@ type Provider interface {
|
|||
CookieForSession(*SessionState, *cookie.Cipher) (string, error)
|
||||
}
|
||||
|
||||
func New(provider string, p *ProviderData) Provider {
|
||||
func New(provider string, p *ProviderData) (Provider, error) {
|
||||
switch provider {
|
||||
case "myusa":
|
||||
return NewMyUsaProvider(p)
|
||||
return NewMyUsaProvider(p), nil
|
||||
case "linkedin":
|
||||
return NewLinkedInProvider(p)
|
||||
return NewLinkedInProvider(p), nil
|
||||
case "facebook":
|
||||
return NewFacebookProvider(p)
|
||||
return NewFacebookProvider(p), nil
|
||||
case "github":
|
||||
return NewGitHubProvider(p)
|
||||
return NewGitHubProvider(p), nil
|
||||
case "azure":
|
||||
return NewAzureProvider(p)
|
||||
return NewAzureProvider(p), nil
|
||||
case "gitlab":
|
||||
return NewGitLabProvider(p)
|
||||
return NewGitLabProvider(p), nil
|
||||
case "google":
|
||||
return NewGoogleProvider(p), nil
|
||||
default:
|
||||
return NewGoogleProvider(p)
|
||||
return nil, errors.New("missing setting: provider")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue