Add support for permitted groups

This commit is contained in:
Brandon Matthews 2017-01-25 15:41:44 -08:00
parent 2fd6f02cd8
commit b546ef4a3f
9 changed files with 79 additions and 35 deletions

View File

@ -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\"")

View File

@ -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 {

View File

@ -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"

View File

@ -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,15 +197,17 @@ 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.GoogleAdminEmail == "" {
msgs = append(msgs, "missing setting: google-admin-email")
}
if o.GoogleServiceAccountJSON == "" {
msgs = append(msgs, "missing setting: google-service-account-json")
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")
}
if o.GoogleServiceAccountJSON == "" {
msgs = append(msgs, "missing setting: google-service-account-json")
}
}
}
@ -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)
}
}
}

View 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()

View File

@ -13,7 +13,8 @@ import (
type AzureProvider struct {
*ProviderData
Tenant string
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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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")
}
}