Isolating some PICS custom changes (#57)

## Description

Isolating PICS custom changes

## Motivation and Context

It's hard to understand what are our custom changes and what is from
OAuth2-Proxy main repo

## How Has This Been Tested?

Created a local container image of the oauth-proxy from this PR and
integrated it with Reporting locally.
- run in the root of this repo
  - docker buildx build -t oauth-local .
- Updated FROM statement in pics/src/services/Oauth2Proxy/Dockerfile to 
  - FROM oauth-local

The following flows were checked:
- Login
- Audit logs
- Logout 


## Checklist:

- [x] Isolating some dunction in separated files
- [x] Creating a folder for Pics packages
This commit is contained in:
Anderson Valério 2024-12-09 09:15:36 -03:00 committed by GitHub
commit fb0723e5bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 120 additions and 91 deletions

View File

@ -25,11 +25,11 @@ import (
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/redirect" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/redirect"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/audit"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/basic" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/basic"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
proxyhttp "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/http" proxyhttp "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/http"
picsaudit "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/pics/audit"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
@ -114,7 +114,8 @@ type OAuthProxy struct {
appDirector redirect.AppDirector appDirector redirect.AppDirector
encodeState bool encodeState bool
AuditClient *audit.Client
picsAuditClient *picsaudit.Client
} }
// NewOAuthProxy creates a new instance of OAuthProxy from the options provided // NewOAuthProxy creates a new instance of OAuthProxy from the options provided
@ -213,7 +214,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
Validator: redirectValidator, Validator: redirectValidator,
}) })
auditClient, err := audit.NewAuditClient(&audit.ClientOpts{ picsAuditClient, err := picsaudit.NewAuditClient(&picsaudit.ClientOpts{
URL: opts.AuditURL, URL: opts.AuditURL,
Enabled: opts.EnableAudit, Enabled: opts.EnableAudit,
ProductName: opts.AuditProductName, ProductName: opts.AuditProductName,
@ -256,7 +257,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
redirectValidator: redirectValidator, redirectValidator: redirectValidator,
appDirector: appDirector, appDirector: appDirector,
encodeState: opts.EncodeState, encodeState: opts.EncodeState,
AuditClient: auditClient, picsAuditClient: picsAuditClient,
} }
p.buildServeMux(opts.ProxyPrefix) p.buildServeMux(opts.ProxyPrefix)
@ -434,7 +435,7 @@ func buildSessionChain(opts *options.Options, provider providers.Provider, sessi
if oidcProviderSettings.CookieRefreshURL == "" { if oidcProviderSettings.CookieRefreshURL == "" {
oidcProviderSettings.CookieRefreshURL = fmt.Sprintf("%s/session/refresh", oidcProviderSettings.IssuerURL) oidcProviderSettings.CookieRefreshURL = fmt.Sprintf("%s/session/refresh", oidcProviderSettings.IssuerURL)
} }
chain = chain.Append(middleware.NewCookieRefresh(&middleware.CookieRefreshOptions{CookieRefreshURL: oidcProviderSettings.CookieRefreshURL, CookieRefreshName: oidcProviderSettings.CookieRefreshName})) chain = chain.Append(middleware.PicsNewCookieRefresh(&middleware.CookieRefreshOptions{CookieRefreshURL: oidcProviderSettings.CookieRefreshURL, CookieRefreshName: oidcProviderSettings.CookieRefreshName}))
logger.Printf("Enabling OIDC cookie refresh functionality for the cookie '%s' using the url '%s' because OIDCEnableCookieRefresh is enabled", oidcProviderSettings.CookieRefreshURL, oidcProviderSettings.CookieRefreshName) logger.Printf("Enabling OIDC cookie refresh functionality for the cookie '%s' using the url '%s' because OIDCEnableCookieRefresh is enabled", oidcProviderSettings.CookieRefreshURL, oidcProviderSettings.CookieRefreshName)
} }
@ -925,7 +926,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
if !csrf.CheckOAuthState(nonce) { if !csrf.CheckOAuthState(nonce) {
errorMsg := "Invalid authentication via OAuth2: CSRF token mismatch, potential attack" errorMsg := "Invalid authentication via OAuth2: CSRF token mismatch, potential attack"
logger.PrintAuthf(session.Email, req, logger.AuthFailure, errorMsg) logger.PrintAuthf(session.Email, req, logger.AuthFailure, errorMsg)
p.AuditClient.CreateFailedLoginAuditEntry(session, appRedirect, req.Header.Get("edisp-org-id"), errorMsg) p.picsAuditClient.CreateFailedLoginAuditEntry(session, appRedirect, req.Header.Get("edisp-org-id"), errorMsg)
p.ErrorPage(rw, req, http.StatusForbidden, "CSRF token mismatch, potential attack", "Login Failed: Unable to find a valid CSRF token. Please try again.") p.ErrorPage(rw, req, http.StatusForbidden, "CSRF token mismatch, potential attack", "Login Failed: Unable to find a valid CSRF token. Please try again.")
return return
} }
@ -954,7 +955,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error()) p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
return return
} }
p.AuditClient.CreateSuccessfulLoginAuditEntry(session, appRedirect, req.Header.Get("edisp-org-id")) p.picsAuditClient.CreateSuccessfulLoginAuditEntry(session, appRedirect, req.Header.Get("edisp-org-id"))
http.Redirect(rw, req, appRedirect, http.StatusFound) http.Redirect(rw, req, appRedirect, http.StatusFound)
} else { } else {
logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: unauthorized") logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: unauthorized")

View File

@ -247,7 +247,7 @@ func (l *LegacyHeaders) getRequestHeaders() []Header {
} }
if l.PassAuthorization { if l.PassAuthorization {
requestHeaders = append(requestHeaders, getAuthorizationHeader()...) requestHeaders = append(requestHeaders, PicsGetAuthorizationHeader()...)
} }
for i := range requestHeaders { for i := range requestHeaders {
@ -272,11 +272,11 @@ func (l *LegacyHeaders) getResponseHeaders() []Header {
} }
if l.SetAuthorization { if l.SetAuthorization {
responseHeaders = append(responseHeaders, getAuthorizationHeader()...) responseHeaders = append(responseHeaders, PicsGetAuthorizationHeader()...)
} }
if l.SetIntrospectionValue { if l.SetIntrospectionValue {
responseHeaders = append(responseHeaders, getXAuthIntrospectionValueHeaders()) responseHeaders = append(responseHeaders, PicsGetXAuthIntrospectionValueHeaders())
} }
return responseHeaders return responseHeaders
} }
@ -369,32 +369,20 @@ func getPassAccessTokenHeader() Header {
} }
} }
func getAuthorizationHeader() []Header { // PICS: changed to PicsGetAuthorizationHeader, this one is not used anywhere
headers := []Header{ // func getAuthorizationHeader() Header {
{ // return Header{
Name: "Authorization", // Name: "Authorization",
Values: []HeaderValue{ // Values: []HeaderValue{
{ // {
ClaimSource: &ClaimSource{ // ClaimSource: &ClaimSource{
Claim: "id_token", // Claim: "id_token",
Prefix: "Bearer ", // Prefix: "Bearer ",
}, // },
}, // },
}, // },
}, // }
{ // }
Name: "x-auth-request-id-token",
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
Claim: "id_token",
},
},
},
},
}
return headers
}
func getPreferredUsernameHeader() Header { func getPreferredUsernameHeader() Header {
return Header{ return Header{
@ -469,19 +457,6 @@ func getXAuthRequestAccessTokenHeader() Header {
} }
} }
func getXAuthIntrospectionValueHeaders() Header {
return Header{
Name: "X-Auth-Introspect-Value",
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
Claim: "introspect-claims",
},
},
},
}
}
type LegacyServer struct { type LegacyServer struct {
MetricsAddress string `flag:"metrics-address" cfg:"metrics_address"` MetricsAddress string `flag:"metrics-address" cfg:"metrics_address"`
MetricsSecureAddress string `flag:"metrics-secure-address" cfg:"metrics_secure_address"` MetricsSecureAddress string `flag:"metrics-secure-address" cfg:"metrics_secure_address"`

View File

@ -0,0 +1,41 @@
package options
func PicsGetAuthorizationHeader() []Header {
headers := []Header{
{
Name: "Authorization",
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
Claim: "id_token",
Prefix: "Bearer ",
},
},
},
},
{
Name: "x-auth-request-id-token",
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
Claim: "id_token",
},
},
},
},
}
return headers
}
func PicsGetXAuthIntrospectionValueHeaders() Header {
return Header{
Name: "X-Auth-Introspect-Value",
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
Claim: "introspect-claims",
},
},
},
}
}

View File

@ -15,7 +15,7 @@ type CookieRefreshOptions struct {
CookieRefreshURL string CookieRefreshURL string
} }
func NewCookieRefresh(opts *CookieRefreshOptions) alice.Constructor { func PicsNewCookieRefresh(opts *CookieRefreshOptions) alice.Constructor {
cr := &cookieRefresh{ cr := &cookieRefresh{
HTTPClient: &http.Client{}, HTTPClient: &http.Client{},
CookieRefreshName: opts.CookieRefreshName, CookieRefreshName: opts.CookieRefreshName,

View File

@ -33,7 +33,7 @@ type Client struct {
func NewAuditClient(opts *ClientOpts) (*Client, error) { func NewAuditClient(opts *ClientOpts) (*Client, error) {
if opts.Enabled { if opts.Enabled {
log.Print("Audit entries will be created since OAUTH2_PROXY_ENABLE_AUDIT is true") log.Print("Audit entries will be created since OAUTH2_PROXY_ENABLE_AUDIT is true")
err := opts.Validate() err := opts.validate()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -154,7 +154,7 @@ func (c *Client) send(msg string) error {
return nil return nil
} }
func (c *ClientOpts) Validate() error { func (c *ClientOpts) validate() error {
err := errors.New("") err := errors.New("")
if strings.TrimSpace(c.URL) == "" { if strings.TrimSpace(c.URL) == "" {
err = errors.New("the OAUTH2_PROXY_AUDIT_URL must be set") err = errors.New("the OAUTH2_PROXY_AUDIT_URL must be set")

View File

@ -81,6 +81,7 @@ type ExtensionContent struct {
URL string `json:"url,omitempty"` URL string `json:"url,omitempty"`
ValueString string `json:"valueString,omitempty"` ValueString string `json:"valueString,omitempty"`
} }
type Extension struct { type Extension struct {
URL string `json:"url,omitempty"` URL string `json:"url,omitempty"`
Extension []*ExtensionContent `json:"extension,omitempty"` Extension []*ExtensionContent `json:"extension,omitempty"`

View File

@ -1,19 +1,15 @@
package providers package providers
import ( import (
"bytes"
"context" "context"
b64 "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@ -98,7 +94,8 @@ func (p *OIDCProvider) Redeem(ctx context.Context, redirectURL, code, codeVerifi
// EnrichSession is called after Redeem to allow providers to enrich session fields // EnrichSession is called after Redeem to allow providers to enrich session fields
// such as User, Email, Groups with provider specific API calls. // such as User, Email, Groups with provider specific API calls.
func (p *OIDCProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error { func (p *OIDCProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
err := p.enrichFromIntrospectURL(ctx, s)
err := p.PicsEnrichFromIntrospectURL(ctx, s)
if err != nil { if err != nil {
logger.Errorf("Warning: Introspect URL request failed: %v", err) logger.Errorf("Warning: Introspect URL request failed: %v", err)
} }
@ -130,40 +127,6 @@ func (p *OIDCProvider) ValidateSession(ctx context.Context, s *sessions.SessionS
return true return true
} }
// enrichFromIntrospectURL enriches a session's claims and permissions via the JSON response of
// an OIDC Introspection URL
func (p *OIDCProvider) enrichFromIntrospectURL(ctx context.Context, s *sessions.SessionState) error {
clientSecret, err := p.GetClientSecret()
if err != nil {
return err
}
params := url.Values{}
params.Add("token", s.AccessToken)
basicAuth := b64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", p.ClientID, clientSecret)))
if p.IntrospectURL == nil {
p.IntrospectURL = &url.URL{
Scheme: p.RedeemURL.Scheme,
Host: p.RedeemURL.Host,
Path: "/authorize/oauth2/v4/introspect",
}
}
logger.Printf("Requesting introspect from '%s'", p.IntrospectURL)
result := requests.New(p.IntrospectURL.String()).
WithContext(ctx).
WithMethod("POST").
WithBody(bytes.NewBufferString(params.Encode())).
SetHeader("Authorization", fmt.Sprintf("Basic %s", basicAuth)).
SetHeader("Content-Type", "application/x-www-form-urlencoded").
Do()
if result.StatusCode() != http.StatusOK {
return fmt.Errorf("error while requesting introspect claims, status code - %d", result.StatusCode())
}
s.IntrospectClaims = b64.StdEncoding.EncodeToString(result.Body())
return nil
}
// RefreshSession uses the RefreshToken to fetch new Access and ID Tokens // RefreshSession uses the RefreshToken to fetch new Access and ID Tokens
func (p *OIDCProvider) RefreshSession(ctx context.Context, s *sessions.SessionState) (bool, error) { func (p *OIDCProvider) RefreshSession(ctx context.Context, s *sessions.SessionState) (bool, error) {
if s == nil || s.RefreshToken == "" { if s == nil || s.RefreshToken == "" {

48
providers/pics_oidc.go Normal file
View File

@ -0,0 +1,48 @@
package providers
import (
"bytes"
"context"
b64 "encoding/base64"
"fmt"
"net/http"
"net/url"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
)
// enrichFromIntrospectURL enriches a session's claims and permissions via the JSON response of
// an OIDC Introspection URL
func (p *OIDCProvider) PicsEnrichFromIntrospectURL(ctx context.Context, s *sessions.SessionState) error {
clientSecret, err := p.GetClientSecret()
if err != nil {
return err
}
params := url.Values{}
params.Add("token", s.AccessToken)
basicAuth := b64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", p.ClientID, clientSecret)))
if p.IntrospectURL == nil {
p.IntrospectURL = &url.URL{
Scheme: p.RedeemURL.Scheme,
Host: p.RedeemURL.Host,
Path: "/authorize/oauth2/v4/introspect",
}
}
logger.Printf("Requesting introspect from '%s'", p.IntrospectURL)
result := requests.New(p.IntrospectURL.String()).
WithContext(ctx).
WithMethod("POST").
WithBody(bytes.NewBufferString(params.Encode())).
SetHeader("Authorization", fmt.Sprintf("Basic %s", basicAuth)).
SetHeader("Content-Type", "application/x-www-form-urlencoded").
Do()
if result.StatusCode() != http.StatusOK {
return fmt.Errorf("error while requesting introspect claims, status code - %d", result.StatusCode())
}
s.IntrospectClaims = b64.StdEncoding.EncodeToString(result.Body())
return nil
}