Skip to content
This repository has been archived by the owner on Jan 24, 2023. It is now read-only.

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions deploy/kubernetes/console/templates/deployment.yaml
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
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
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
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
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
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
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
}