Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gate SSO redirect on optional state whitelist #3933

Merged
merged 3 commits into from
Oct 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions deploy/kubernetes/console/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ spec:
value: {{default "false" .Values.console.ssoLogin | quote}}
- name: SSO_OPTIONS
value: {{default "" .Values.console.ssoOptions | quote}}
- name: SSO_WHITELIST
value: {{ default "" .Values.console.ssoWhiteList | quote }}
{{- if .Values.console.templatesConfigMapName }}
- name: TEMPLATE_DIR
value: /etc/templates
Expand Down
10 changes: 9 additions & 1 deletion docs/sso.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,16 @@ The `authorized_grant_types` value should contain `authorization_code`. If not u
uaac client update cf --authorized_grant_types authorization_code
```

## Adding a Stratos SSO State Whitelist

When SSO has been configured Stratos's log in request will contain a URL that tells SSO where to return to. When using a browser this is automatically populated. To avoid situations where this can be hijacked or called separately an SSO `state` whitelist can be provided via the environment variable `SSO_WHITELIST`. This is a comma separated list. For example...

```
SSO_WHITELIST=https://your.domain
```

```
SSO_WHITELIST=https://your.domain,https://your.other.domain
```


When set, any requests to log in with a different `state` will be denied.
30 changes: 28 additions & 2 deletions src/jetstream/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,16 @@ func (p *portalProxy) initSSOlogin(c echo.Context) error {
if len(state) == 0 {
err := interfaces.NewHTTPShadowError(
http.StatusUnauthorized,
"SSO Login: State parameter missing",
"SSO Login: State parameter missing")
"SSO Login: Redirect state parameter missing",
"SSO Login: Redirect state parameter missing")
return err
}

if !safeSSORedirectState(state, p.Config.SSOWhiteList) {
err := interfaces.NewHTTPShadowError(
http.StatusUnauthorized,
"SSO Login: Disallowed redirect state",
"SSO Login: Disallowed redirect state")
return err
}

Expand All @@ -107,6 +115,24 @@ func (p *portalProxy) initSSOlogin(c echo.Context) error {
return nil
}

func safeSSORedirectState(state string, whiteListStr string) bool {
if len(whiteListStr) == 0 {
return true;
}

whiteList := strings.Split(whiteListStr, ",")
if len(whiteList) == 0 {
return true;
}

for _, n := range whiteList {
if CompareURL(state, n) {
return true
}
}
return false
}

func getSSORedirectURI(base string, state string, endpointGUID string) string {
baseURL, _ := url.Parse(base)
baseURL.Path = ""
Expand Down
3 changes: 3 additions & 0 deletions src/jetstream/default.config.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ ENCRYPTION_KEY=B374A26A71490437AA024E4FADD5B497FDFF1A8EA6FF12F6FB65AF2720B59CCF
SQLITE_KEEP_DB=true
UI_PATH=../../dist

SSO_LOGIN=false
SSO_WHITELIST=

# Enable feature in tech preview
ENABLE_TECH_PREVIEW=false

Expand Down
1 change: 1 addition & 0 deletions src/jetstream/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ func showSSOConfig(portalProxy *portalProxy) {
log.Infof("SSO Configuration:")
log.Infof("... SSO Enabled : %t", portalProxy.Config.SSOLogin)
log.Infof("... SSO Options : %s", portalProxy.Config.SSOOptions)
log.Infof("... SSO Redirect Whitelist : %s", portalProxy.Config.SSOWhiteList)
}

func getEncryptionKey(pc interfaces.PortalConfig) ([]byte, error) {
Expand Down
1 change: 1 addition & 0 deletions src/jetstream/repository/interfaces/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ type PortalConfig struct {
AutoRegisterCFName string `configName:"AUTO_REG_CF_NAME"`
SSOLogin bool `configName:"SSO_LOGIN"`
SSOOptions string `configName:"SSO_OPTIONS"`
SSOWhiteList string `configName:"SSO_WHITELIST"`
AuthEndpointType string `configName:"AUTH_ENDPOINT_TYPE"`
CookieDomain string `configName:"COOKIE_DOMAIN"`
LogLevel string `configName:"LOG_LEVEL"`
Expand Down
35 changes: 35 additions & 0 deletions src/jetstream/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"strings"
"unicode"
"net/url"
)

// ArrayContainsString checks the string array to see if it contains the specifed value
Expand All @@ -24,3 +25,37 @@ func RemoveSpaces(str string) string {
return r
}, str)
}

// CompareURL compares two URLs, taking into account default HTTP/HTTPS ports and ignoring query string
func CompareURL(a, b string) bool {

ua, err := url.Parse(a)
if err != nil {
return false
}

ub, err := url.Parse(b)
if err != nil {
return false
}

aPort := getPort(ua)
bPort := getPort(ub)
return ua.Scheme == ub.Scheme && ua.Hostname() == ub.Hostname() && aPort == bPort && ua.Path == ub.Path
}

func getPort(u *url.URL) string {
port := u.Port()
if len(port) == 0 {
switch u.Scheme {
case "http":
port = "80"
case "https":
port = "443"
default:
port = ""
}
}

return port
}