Skip to content

Commit

Permalink
Merge branch 'master' into azure-ad-provider
Browse files Browse the repository at this point in the history
  • Loading branch information
sporkmonger committed Nov 16, 2018
2 parents 6bf6230 + e239103 commit 52f78d9
Show file tree
Hide file tree
Showing 28 changed files with 329 additions and 653 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
- setup_remote_docker
- run:
name: get lint
command: go get github.com/golang/lint/golint
command: go get golang.org/x/lint/golint
- run:
name: install dependencies
command: |
Expand Down
2 changes: 1 addition & 1 deletion docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This quickstart guide will walk you through the process of creating a set of
Google OAuth credentials and using Docker Compose to run an example deployment
of **sso** protecting two upstream services.

To learn how to get started using SSO with Kubernetes, you can check out this [blog post](https://medium.com/@while1eq1/single-sign-on-for-internal-apps-in-kubernetes-using-google-oauth-sso-2386a34bc433) and [example](/quickstart/kubernetes), added and written by [Bill Broach](https://twitter.com/while1eq1), one our community contributors!
To learn how to get started using SSO with Kubernetes, you can check out this [blog post](https://medium.com/@while1eq1/single-sign-on-for-internal-apps-in-kubernetes-using-google-oauth-sso-2386a34bc433) and [example](/quickstart/kubernetes), added and written by [Bill Broach](https://twitter.com/while1eq1), one of our community contributors!


## Prerequisites
Expand Down
8 changes: 4 additions & 4 deletions docs/sso_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

### Proxy Config
All services using `sso_proxy` are configured in a `upstream_config.yml` file.
All config values can be templated from environmental variables with the prefix `BUZZFEED_SSO_CONFIG_`.
For example, the following config would have the following environment variables configed:, `BUZZFEED_SSO_CONFIG_CLUSTER`, `BUZZFEED_SSO_CONFIG_ROOT_DOMAIN`.
All config values can be templated from environmental variables with the prefix `SSO_CONFIG_`.
For example, the following config would have the following environment variables configed:, `SSO_CONFIG_CLUSTER`, `SSO_CONFIG_ROOT_DOMAIN`.


```json
Expand Down Expand Up @@ -85,11 +85,11 @@ and proxy the request to that upstream.

### Request Signing
SSO Proxy can sign requests using an HMAC shared-secret signing key specified per upstream. This must be of the form `algorithm:secret_value`, where `sha256` is preferred for the algorithm.
To enable request signing, SSO Proxy looks for environment variables with the format `BUZZFEED_SSO_CONFIG_{{SERVICE}}_SIGNING_KEY` with the previous mentioned key.
To enable request signing, SSO Proxy looks for environment variables with the format `SSO_CONFIG_{{SERVICE}}_SIGNING_KEY` with the previous mentioned key.

For example:
```bash
export BUZZFEED_SSO_CONFIG_FOOBAR_SIGNING_KEY="sha256:shared-secret-value"
export SSO_CONFIG_FOOBAR_SIGNING_KEY="sha256:shared-secret-value"
```
would be the signing key for the `foobar` upstream service, use the sha256 with the `shared-secret-value` as it's signing key value.

Expand Down
3 changes: 3 additions & 0 deletions internal/auth/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ func (o *Options) Validate() error {
if len(o.EmailDomains) == 0 {
msgs = append(msgs, "missing setting for email validation: email-domain required.\n use email-domain=* to authorize all email addresses")
}
if len(o.ProxyRootDomains) == 0 {
msgs = append(msgs, "missing setting: proxy-root-domain")
}
if o.ProxyClientID == "" {
msgs = append(msgs, "missing setting: proxy-client-id")
}
Expand Down
2 changes: 2 additions & 0 deletions internal/auth/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func testOptions() *Options {
o.EmailDomains = []string{"*"}
o.ProxyClientID = "abcdef"
o.ProxyClientSecret = "testtest"
o.ProxyRootDomains = []string{"*"}
o.StatsdHost = "statsdhost"
o.StatsdPort = 12344
o.Host = "/"
Expand All @@ -43,6 +44,7 @@ func TestNewOptions(t *testing.T) {
"missing setting: cookie-secret",
"missing setting: client-id",
"missing setting: client-secret",
"missing setting: proxy-root-domain",
"missing setting: proxy-client-id",
"missing setting: proxy-client-secret",
"missing setting: required-host-header",
Expand Down
2 changes: 1 addition & 1 deletion internal/auth/providers/singleflight_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"strings"
"time"

"github.com/buzzfeed/sso/internal/auth/singleflight"
"github.com/buzzfeed/sso/internal/pkg/sessions"
"github.com/buzzfeed/sso/internal/pkg/singleflight"

"github.com/datadog/datadog-go/statsd"
)
Expand Down
2 changes: 1 addition & 1 deletion internal/auth/static_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (fs noDirectoryFS) Open(name string) (http.File, error) {
return f, nil
}

//go:generate $GOPATH/bin/statik -src=./static
//go:generate $GOPATH/bin/statik -f -src=./static

func loadFSHandler() (http.Handler, error) {
statikFS, err := fs.New()
Expand Down
File renamed without changes.
11 changes: 6 additions & 5 deletions internal/proxy/oauthproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,20 +427,21 @@ func (p *OAuthProxy) GetRedirectURL(host string) *url.URL {
return &u
}

func (p *OAuthProxy) redeemCode(host, code string) (s *providers.SessionState, err error) {
func (p *OAuthProxy) redeemCode(host, code string) (*providers.SessionState, error) {
if code == "" {
return nil, errors.New("missing code")
}
redirectURL := p.GetRedirectURL(host)
s, err = p.provider.Redeem(redirectURL.String(), code)
s, err := p.provider.Redeem(redirectURL.String(), code)
if err != nil {
return
return s, err
}

if s.Email == "" {
s.Email, err = p.provider.GetEmailAddress(s)
return s, errors.New("invalid email address")
}
return

return s, nil
}

// MakeSessionCookie constructs a session cookie given the request, an expiration time and the current time.
Expand Down
83 changes: 15 additions & 68 deletions internal/proxy/oauthproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,51 +438,12 @@ func TestFavicon(t *testing.T) {
testutil.Equal(t, http.StatusNotFound, rw.Code)
}

type TestProvider struct {
*providers.ProviderData
EmailAddress string
ValidToken bool
}

func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
return &TestProvider{
ProviderData: &providers.ProviderData{
ProviderName: "Test Provider",
SignInURL: &url.URL{
Scheme: "http",
Host: providerURL.Host,
Path: "/oauth/authorize",
},
RedeemURL: &url.URL{
Scheme: "http",
Host: providerURL.Host,
Path: "/oauth/token",
},
ProfileURL: &url.URL{
Scheme: "http",
Host: providerURL.Host,
Path: "/api/v1/profile",
},
Scope: "profile.email",
},
EmailAddress: emailAddress,
}
}

func (tp *TestProvider) GetEmailAddress(session *providers.SessionState) (string, error) {
return tp.EmailAddress, nil
}

func (tp *TestProvider) ValidateSessionState(session *providers.SessionState, g []string) bool {
return tp.ValidToken
}

type ProcessCookieTest struct {
opts *Options
proxy *OAuthProxy
rw *httptest.ResponseRecorder
req *http.Request
provider TestProvider
provider providers.TestProvider
responseCode int
validateUser bool
}
Expand All @@ -508,8 +469,8 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts) *ProcessCookieTest {
return nil
})

pcTest.proxy.provider = &TestProvider{
ValidToken: opts.providerValidateCookieResponse,
pcTest.proxy.provider = &providers.TestProvider{
ValidateSessionFunc: func(*providers.SessionState, []string) bool { return opts.providerValidateCookieResponse },
}

pcTest.rw = httptest.NewRecorder()
Expand Down Expand Up @@ -695,7 +656,7 @@ func TestAuthSkippedForPreflightRequests(t *testing.T) {
opts.Validate()

upstreamURL, _ := url.Parse(upstream.URL)
opts.provider = NewTestProvider(upstreamURL, "")
opts.provider = providers.NewTestProvider(upstreamURL, "")

proxy, _ := NewOAuthProxy(opts)
rw := httptest.NewRecorder()
Expand Down Expand Up @@ -749,7 +710,7 @@ func TestAuthSkipRequests(t *testing.T) {
opts.Validate()

upstreamURL, _ := url.Parse(upstream.URL)
opts.provider = NewTestProvider(upstreamURL, "")
opts.provider = providers.NewTestProvider(upstreamURL, "")

proxy, _ := NewOAuthProxy(opts)

Expand Down Expand Up @@ -829,7 +790,7 @@ func TestMultiAuthSkipRequests(t *testing.T) {
opts.Validate()

upstreamFooURL, _ := url.Parse(upstreamFoo.URL)
opts.provider = NewTestProvider(upstreamFooURL, "")
opts.provider = providers.NewTestProvider(upstreamFooURL, "")

proxy, _ := NewOAuthProxy(opts)

Expand Down Expand Up @@ -921,7 +882,7 @@ func NewSignatureTest(key string) *SignatureTest {
}
provider := httptest.NewServer(http.HandlerFunc(providerHandler))
providerURL, _ := url.Parse(provider.URL)
opts.provider = NewTestProvider(providerURL, "email1@example.com")
opts.provider = providers.NewTestProvider(providerURL, "email1@example.com")
opts.upstreamConfigs = generateSignatureTestUpstreamConfigs(key, upstream.URL)
opts.Validate()

Expand Down Expand Up @@ -1041,7 +1002,7 @@ func TestHeadersSentToUpstreams(t *testing.T) {
opts.upstreamConfigs = generateTestUpstreamConfigs(upstream.URL)
opts.Validate()
providerURL, _ := url.Parse("http://sso-auth.example.com/")
opts.provider = NewTestProvider(providerURL, "")
opts.provider = providers.NewTestProvider(providerURL, "")

state := testSession()
state.Email = "foo@example.com"
Expand Down Expand Up @@ -1098,20 +1059,6 @@ func TestHeadersSentToUpstreams(t *testing.T) {

}

type testAuthenticateProvider struct {
*providers.ProviderData
refreshSessionFunc func(*providers.SessionState, []string) (bool, error)
validateSessionFunc func(*providers.SessionState, []string) bool
}

func (tap *testAuthenticateProvider) RefreshSession(s *providers.SessionState, g []string) (bool, error) {
return tap.refreshSessionFunc(s, g)
}

func (tap *testAuthenticateProvider) ValidateSessionState(s *providers.SessionState, g []string) bool {
return tap.validateSessionFunc(s, g)
}

func TestAuthenticate(t *testing.T) {
// Constants to represent possible cookie behaviors.
const (
Expand Down Expand Up @@ -1261,9 +1208,9 @@ func TestAuthenticate(t *testing.T) {
opts.upstreamConfigs = generateTestUpstreamConfigs("foo-internal.sso.dev")
opts.Validate()
proxy, _ := NewOAuthProxy(opts, testValidatorFunc(true), testCookieCipher(tc.Cipher))
proxy.provider = &testAuthenticateProvider{
refreshSessionFunc: tc.RefreshSessionFunc,
validateSessionFunc: tc.ValidateSessionFunc,
proxy.provider = &providers.TestProvider{
RefreshSessionFunc: tc.RefreshSessionFunc,
ValidateSessionFunc: tc.ValidateSessionFunc,
}

value, err := providers.MarshalSession(tc.Session, proxy.CookieCipher)
Expand Down Expand Up @@ -1518,7 +1465,7 @@ func TestPing(t *testing.T) {
opts.Validate()

providerURL, _ := url.Parse("http://sso-auth.example.com/")
opts.provider = NewTestProvider(providerURL, "")
opts.provider = providers.NewTestProvider(providerURL, "")

proxy, _ := NewOAuthProxy(opts)
state := testSession()
Expand Down Expand Up @@ -1597,7 +1544,7 @@ func TestSecurityHeaders(t *testing.T) {
opts.Validate()

providerURL, _ := url.Parse("http://sso-auth.example.com/")
opts.provider = NewTestProvider(providerURL, "")
opts.provider = providers.NewTestProvider(providerURL, "")

proxy, _ := NewOAuthProxy(opts, testValidatorFunc(true))

Expand Down Expand Up @@ -1741,7 +1688,7 @@ func TestHeaderOverrides(t *testing.T) {
opts.Validate()

providerURL, _ := url.Parse("http://sso-auth.example.com/")
opts.provider = NewTestProvider(providerURL, "")
opts.provider = providers.NewTestProvider(providerURL, "")

proxy, _ := NewOAuthProxy(opts, testValidatorFunc(true))

Expand Down Expand Up @@ -1785,7 +1732,7 @@ func TestHTTPSRedirect(t *testing.T) {
defer upstream.Close()

providerURL, _ := url.Parse("http://sso-auth.example.com/")
provider := NewTestProvider(providerURL, "")
provider := providers.NewTestProvider(providerURL, "")
state := testSession()

testCases := []struct {
Expand Down
22 changes: 18 additions & 4 deletions internal/proxy/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
// Options are configuration options that can be set by Environment Variables
// Port - int - port to listen on for HTTP clients
// ProviderURLString - the URL for the provider in this environment: "https://sso-auth.example.com"
// ProxyProviderURLString - the internal URL for the provider in this environment: "https://sso-auth-int.example.com"
// UpstreamConfigsFile - the path to upstream configs file
// Cluster - the cluster in which this is running, used for upstream configs
// Scheme - the default scheme, used for upstream configs
Expand Down Expand Up @@ -46,10 +47,11 @@ import (
type Options struct {
Port int `envconfig:"PORT" default:"4180"`

ProviderURLString string `envconfig:"PROVIDER_URL"`
UpstreamConfigsFile string `envconfig:"UPSTREAM_CONFIGS"`
Cluster string `envconfig:"CLUSTER"`
Scheme string `envconfig:"SCHEME" default:"https"`
ProviderURLString string `envconfig:"PROVIDER_URL"`
ProxyProviderURLString string `envconfig:"PROXY_PROVIDER_URL"`
UpstreamConfigsFile string `envconfig:"UPSTREAM_CONFIGS"`
Cluster string `envconfig:"CLUSTER"`
Scheme string `envconfig:"SCHEME" default:"https"`

SkipAuthPreflight bool `envconfig:"SKIP_AUTH_PREFLIGHT"`

Expand Down Expand Up @@ -125,6 +127,9 @@ func (o *Options) Validate() error {
if o.ProviderURLString == "" {
msgs = append(msgs, "missing setting: provider-url")
}
if o.ProxyProviderURLString == "" {
o.ProxyProviderURLString = o.ProviderURLString
}
if o.UpstreamConfigsFile == "" {
msgs = append(msgs, "missing setting: upstream-configs")
}
Expand Down Expand Up @@ -215,10 +220,19 @@ func parseProviderInfo(o *Options) error {
return errors.New("provider-url must include scheme and host")
}

proxyProviderURL, err := url.Parse(o.ProxyProviderURLString)
if err != nil {
return err
}
if proxyProviderURL.Scheme == "" || proxyProviderURL.Host == "" {
return errors.New("proxy provider url must include scheme and host")
}

providerData := &providers.ProviderData{
ClientID: o.ClientID,
ClientSecret: o.ClientSecret,
ProviderURL: providerURL,
ProxyProviderURL: proxyProviderURL,
Scope: o.Scope,
SessionLifetimeTTL: o.SessionLifetimeTTL,
SessionValidTTL: o.SessionValidTTL,
Expand Down

0 comments on commit 52f78d9

Please sign in to comment.