Revoke access token on logout (#66)
Adding functionality to revoke access token on logout. This will use the newly added OAUTH2_PROXY_BACKEND_REVOKE_ACCESS_TOKEN_URL environment variable. AB#1624642 ## Motivation and Context Adding the functionality to revoke an access token on logout prevents an attacker from continuing to use a stolen access token until the expiration of the TTL. ## How Has This Been Tested? Running it locally integrated with Pics.
This commit is contained in:
commit
b5da4ecc31
|
|
@ -35,12 +35,12 @@ jobs:
|
|||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.4
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.61.0
|
||||
version: v1.64.8
|
||||
|
||||
Tests:
|
||||
name: Tests - Executing unit tests
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
run:
|
||||
deadline: 120s
|
||||
linters:
|
||||
enable:
|
||||
- govet
|
||||
|
|
|
|||
|
|
@ -446,6 +446,7 @@ Provider holds all configuration for a single provider
|
|||
| `code_challenge_method` | _string_ | The code challenge method |
|
||||
| `backendLogoutURL` | _string_ | URL to call to perform backend logout, `{id_token}` would be replaced by the actual `id_token` if available in the session |
|
||||
| `backendLogoutAllSessionsURL` | _string_ | URL to call to perform backend logout, `{user_id}` would be replaced by the actual `user_id` if available in the session IntrospectClaims |
|
||||
| `backendRevokeAccessTokenURL` | _string_ | URL to call to perform backend revoke token |
|
||||
|
||||
### ProviderType
|
||||
#### (`string` alias)
|
||||
|
|
|
|||
|
|
@ -798,7 +798,6 @@ func (p *OAuthProxy) backendLogout(rw http.ResponseWriter, req *http.Request, si
|
|||
}
|
||||
|
||||
providerData := p.provider.Data()
|
||||
var resp *http.Response
|
||||
if signOutAllSessions {
|
||||
if providerData.BackendLogoutAllSessionsURL == "" {
|
||||
return
|
||||
|
|
@ -815,6 +814,19 @@ func (p *OAuthProxy) backendLogout(rw http.ResponseWriter, req *http.Request, si
|
|||
}
|
||||
p.picsAuditClient.CreateSuccessfulLogoutAuditEntry(session, req.RequestURI, req.Header.Get("edisp-org-id"))
|
||||
} else {
|
||||
if providerData.BackendRevokeAccessTokenURL != "" {
|
||||
resp, err := PicsRevokeAcessToken(providerData.BackendRevokeAccessTokenURL, session.AccessToken, providerData.ClientID, providerData.ClientSecret)
|
||||
if err != nil {
|
||||
logger.Errorf("error while calling backend revoke access token: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode() != 200 {
|
||||
logger.Errorf("error while calling backend revoke acess token url, returned error code %v", resp.StatusCode())
|
||||
}
|
||||
p.picsAuditClient.CreateSuccessfulRevokeAccessTokenAuditEntry(session, req.RequestURI, req.Header.Get("edisp-org-id"))
|
||||
}
|
||||
|
||||
if providerData.BackendLogoutURL == "" {
|
||||
return
|
||||
}
|
||||
|
|
@ -822,7 +834,7 @@ func (p *OAuthProxy) backendLogout(rw http.ResponseWriter, req *http.Request, si
|
|||
backendLogoutURL := strings.ReplaceAll(providerData.BackendLogoutURL, "{id_token}", session.IDToken)
|
||||
// security exception because URL is dynamic ({id_token} replacement) but
|
||||
// base is not end-user provided but comes from configuration somewhat secure
|
||||
resp, err = http.Get(backendLogoutURL) // #nosec G107
|
||||
resp, err := http.Get(backendLogoutURL) // #nosec G107
|
||||
if err != nil {
|
||||
logger.Errorf("error while calling backend logout: %v", err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -29,12 +29,32 @@ func PicsSignOutAllSessions(backendLogoutAllSessionsURL string, introspectClaims
|
|||
Do()
|
||||
|
||||
if resp.Error() != nil {
|
||||
return nil, fmt.Errorf("error logging out from IAM: %v", err)
|
||||
return nil, fmt.Errorf("error logging out from IAM: %v", resp.Error())
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func PicsRevokeAcessToken(backendRevokeURL string, accessToken string, clientID string, clientSecret string) (resp requests.Result, err error) {
|
||||
authHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientID+":"+clientSecret))
|
||||
body := "token=" + accessToken
|
||||
|
||||
resp = requests.New(backendRevokeURL).
|
||||
WithMethod("POST").
|
||||
SetHeader("Authorization", authHeader).
|
||||
SetHeader("api-version", "2").
|
||||
SetHeader("Content-Type", "application/x-www-form-urlencoded").
|
||||
SetHeader("Accept", "application/json").
|
||||
WithBody(strings.NewReader(body)).
|
||||
Do()
|
||||
|
||||
if resp.Error() != nil {
|
||||
return nil, fmt.Errorf("error revoking access token: %v", resp.Error())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func getUserID(introspectClaims string) (string, error) {
|
||||
decodedClaims, err := base64.StdEncoding.DecodeString(introspectClaims)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -546,6 +546,7 @@ type LegacyProvider struct {
|
|||
BackendLogoutURL string `flag:"backend-logout-url" cfg:"backend_logout_url"`
|
||||
|
||||
BackendLogoutAllSessionsURL string `flag:"backend-logout-all-sessions-url" cfg:"backend_logout_all_sessions_url"`
|
||||
BackendRevokeAccessTokenURL string `flag:"backend-revoke-access-token-url" cfg:"backend_revoke_access_token_url"`
|
||||
|
||||
AcrValues string `flag:"acr-values" cfg:"acr_values"`
|
||||
JWTKey string `flag:"jwt-key" cfg:"jwt_key"`
|
||||
|
|
@ -616,6 +617,7 @@ func legacyProviderFlagSet() *pflag.FlagSet {
|
|||
flagSet.StringSlice("allowed-role", []string{}, "(keycloak-oidc) restrict logins to members of these roles (may be given multiple times)")
|
||||
flagSet.String("backend-logout-url", "", "url to perform a backend logout, {id_token} can be used as placeholder for the id_token")
|
||||
flagSet.String("backend-logout-all-sessions-url", "", "url to perform a backend logout, {user_id} can be used as placeholder for the user_id")
|
||||
flagSet.String("backend-revoke-access-token-url", "", "url to perform a backend revoke access token")
|
||||
|
||||
return flagSet
|
||||
}
|
||||
|
|
@ -698,6 +700,7 @@ func (l *LegacyProvider) convert() (Providers, error) {
|
|||
BackendLogoutURL: l.BackendLogoutURL,
|
||||
|
||||
BackendLogoutAllSessionsURL: l.BackendLogoutAllSessionsURL,
|
||||
BackendRevokeAccessTokenURL: l.BackendRevokeAccessTokenURL,
|
||||
}
|
||||
|
||||
// This part is out of the switch section for all providers that support OIDC
|
||||
|
|
|
|||
|
|
@ -91,6 +91,9 @@ type Provider struct {
|
|||
|
||||
// URL to call to perform backend logout, `{user_id}` would be replaced by the actual `user_id` if available in the session IntrospectClaims
|
||||
BackendLogoutAllSessionsURL string `json:"backendLogoutAllSessionsURL"`
|
||||
|
||||
// URL to call to perform backend revoke token
|
||||
BackendRevokeAccessTokenURL string `json:"backendRevokeAccessTokenURL"`
|
||||
}
|
||||
|
||||
// ProviderType is used to enumerate the different provider type options
|
||||
|
|
|
|||
|
|
@ -70,6 +70,12 @@ func (c *Client) CreateSuccessfulLogoutAuditEntry(ss *sessions.SessionState, app
|
|||
c.createAuditEntry(ss, appURL, tenantID, "0", "Success", &coding)
|
||||
}
|
||||
|
||||
func (c *Client) CreateSuccessfulRevokeAccessTokenAuditEntry(ss *sessions.SessionState, appURL string, tenantID string) {
|
||||
coding := Coding{
|
||||
System: "http://hl7.org/fhir/ValueSet/audit-event-type", Version: "1", Code: "110123", Display: "User revoked access token"}
|
||||
c.createAuditEntry(ss, appURL, tenantID, "0", "Success", &coding)
|
||||
}
|
||||
|
||||
func (c *Client) createAuditEntry(ss *sessions.SessionState, appURL string, tenantID string, outcomeCode string, outcomeDesc string, coding *Coding) {
|
||||
if !c.enabled {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|||
func randSeq(n int) string {
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
max := big.NewInt(int64(len(letters)))
|
||||
bigN, err := rand.Int(rand.Reader, max)
|
||||
maxInt := big.NewInt(int64(len(letters)))
|
||||
bigN, err := rand.Int(rand.Reader, maxInt)
|
||||
if err != nil {
|
||||
// This should never happen
|
||||
panic(err)
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ type ProviderData struct {
|
|||
BackendLogoutURL string
|
||||
|
||||
BackendLogoutAllSessionsURL string
|
||||
BackendRevokeAccessTokenURL string
|
||||
}
|
||||
|
||||
// Data returns the ProviderData
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ func newProviderDataFromConfig(providerConfig options.Provider) (*ProviderData,
|
|||
|
||||
p.BackendLogoutURL = providerConfig.BackendLogoutURL
|
||||
p.BackendLogoutAllSessionsURL = providerConfig.BackendLogoutAllSessionsURL
|
||||
p.BackendRevokeAccessTokenURL = providerConfig.BackendRevokeAccessTokenURL
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue