add HSP IAM audit capabilities (#52)
<!--- Provide a general summary of your changes in the Title above --> ## Description Add HSDP IAM audit capabilities to oauth2 proxy ## Motivation and Context HSDP IAM does not provide important audit capabilities out of the box such as successful login and some key auth errors. ## How Has This Been Tested? Tested locally with: ```sh export OAUTH2_PROXY_ENABLE_AUDIT='true' export OAUTH2_PROXY_AUDIT_URL='url' export OAUTH2_PROXY_AUDIT_PRODUCT_NAME='Reporting' export OAUTH2_PROXY_AUDIT_PRODUCT_KEY='product key' export OAUTH2_PROXY_AUDIT_SHARED_KEY='shared key' export OAUTH2_PROXY_AUDIT_SECRET_KEY='secret' ``` ## Checklist: <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> <!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> - [ ] My change requires a change to the documentation or CHANGELOG. - [ ] I have updated the documentation/CHANGELOG accordingly. - [ ] I have created a feature (non-master) branch for my PR.
This commit is contained in:
commit
8f7537a6e9
9
go.mod
9
go.mod
|
|
@ -12,6 +12,7 @@ require (
|
|||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
|
||||
github.com/go-redis/redis/v9 v9.0.0-rc.1
|
||||
github.com/go-resty/resty/v2 v2.13.1
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
|
|
@ -29,9 +30,9 @@ require (
|
|||
github.com/spf13/viper v1.6.3
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5
|
||||
golang.org/x/crypto v0.17.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/exp v0.0.0-20221019170559-20944726eadf
|
||||
golang.org/x/net v0.19.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/oauth2 v0.13.0
|
||||
golang.org/x/sync v0.4.0
|
||||
google.golang.org/api v0.126.0
|
||||
|
|
@ -71,8 +72,8 @@ require (
|
|||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect
|
||||
google.golang.org/grpc v1.60.1 // indirect
|
||||
|
|
|
|||
35
go.sum
35
go.sum
|
|
@ -159,6 +159,8 @@ github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
|||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-redis/redis/v9 v9.0.0-rc.1 h1:/+bS+yeUnanqAbuD3QwlejzQZ+4eqgfUtFTG4b+QnXs=
|
||||
github.com/go-redis/redis/v9 v9.0.0-rc.1/go.mod h1:8et+z03j0l8N+DvsVnclzjf3Dl/pFHgRk+2Ct1qw66A=
|
||||
github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g=
|
||||
github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
|
|
@ -458,8 +460,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
|
@ -499,6 +502,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
|
@ -551,8 +555,11 @@ golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug
|
|||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
|
@ -589,6 +596,7 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
@ -668,10 +676,17 @@ golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
@ -682,11 +697,16 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
@ -744,6 +764,7 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
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/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/cookies"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
|
||||
|
|
@ -104,6 +105,8 @@ type OAuthProxy struct {
|
|||
serveMux *mux.Router
|
||||
redirectValidator redirect.Validator
|
||||
appDirector redirect.AppDirector
|
||||
|
||||
AuditClient *audit.Client
|
||||
}
|
||||
|
||||
// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
|
||||
|
|
@ -202,6 +205,17 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||
Validator: redirectValidator,
|
||||
})
|
||||
|
||||
auditClient, err := audit.NewAuditClient(&audit.ClientOpts{
|
||||
URL: opts.AuditURL,
|
||||
Enabled: opts.EnableAudit,
|
||||
ProductName: opts.AuditProductName,
|
||||
ProductKey: opts.AuditProductKey,
|
||||
SharedKey: opts.AuditSharedKey,
|
||||
SecretKey: opts.AuditSecretKey})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error setting up server (audit client): %v", err)
|
||||
}
|
||||
|
||||
p := &OAuthProxy{
|
||||
CookieOptions: &opts.Cookie,
|
||||
Validator: validator,
|
||||
|
|
@ -231,6 +245,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||
upstreamProxy: upstreamProxy,
|
||||
redirectValidator: redirectValidator,
|
||||
appDirector: appDirector,
|
||||
AuditClient: auditClient,
|
||||
}
|
||||
p.buildServeMux(opts.ProxyPrefix)
|
||||
|
||||
|
|
@ -851,7 +866,9 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
if !csrf.CheckOAuthState(nonce) {
|
||||
logger.PrintAuthf(session.Email, req, logger.AuthFailure, "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)
|
||||
p.AuditClient.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.")
|
||||
return
|
||||
}
|
||||
|
|
@ -880,6 +897,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
|||
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
p.AuditClient.CreateSuccessfulLoginAuditEntry(session, appRedirect, req.Header.Get("edisp-org-id"))
|
||||
http.Redirect(rw, req, appRedirect, http.StatusFound)
|
||||
} else {
|
||||
logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: unauthorized")
|
||||
|
|
|
|||
|
|
@ -72,6 +72,14 @@ type Options struct {
|
|||
oidcVerifier internaloidc.IDTokenVerifier
|
||||
jwtBearerVerifiers []internaloidc.IDTokenVerifier
|
||||
realClientIPParser ipapi.RealClientIPParser
|
||||
|
||||
// Philips opts
|
||||
EnableAudit bool `flag:"enable-audit" cfg:"enable_audit"`
|
||||
AuditURL string `flag:"audit-url" cfg:"audit_url"`
|
||||
AuditProductName string `flag:"audit-product-name" cfg:"audit_product_name"`
|
||||
AuditProductKey string `flag:"audit-product-key" cfg:"audit_product_key"`
|
||||
AuditSharedKey string `flag:"audit-shared-key" cfg:"audit_shared_key"`
|
||||
AuditSecretKey string `flag:"audit-secret-key" cfg:"audit_secret_key"`
|
||||
}
|
||||
|
||||
// Options for Getting internal values
|
||||
|
|
@ -149,6 +157,14 @@ func NewFlagSet() *pflag.FlagSet {
|
|||
flagSet.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)")
|
||||
flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints")
|
||||
|
||||
// Philips opts
|
||||
flagSet.Bool("enable-audit", false, "Persist audit entries in the audit server upon user authentication and relevant errors")
|
||||
flagSet.String("audit-url", "", "The url where the audit entries will be posted")
|
||||
flagSet.String("audit-product-name", "", "The name of the product to be used in the audit")
|
||||
flagSet.String("audit-product-key", "", "The key of the audit")
|
||||
flagSet.String("audit-shared-key", "", "The shared key of the audit")
|
||||
flagSet.String("audit-secret-key", "", "The secret key of the audit")
|
||||
|
||||
flagSet.AddFlagSet(cookieFlagSet())
|
||||
flagSet.AddFlagSet(loggingFlagSet())
|
||||
flagSet.AddFlagSet(templatesFlagSet())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,181 @@
|
|||
package audit
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
)
|
||||
|
||||
type ClientOpts struct {
|
||||
Enabled bool
|
||||
URL string
|
||||
ProductKey string
|
||||
ProductName string
|
||||
SharedKey string
|
||||
SecretKey string
|
||||
}
|
||||
|
||||
// Client interface for communicating with audit system
|
||||
type Client struct {
|
||||
enabled bool
|
||||
apiSignature APISignature
|
||||
opts *ClientOpts
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewAuditClient(opts *ClientOpts) (*Client, error) {
|
||||
if opts.Enabled {
|
||||
log.Print("Audit entries will be created since OAUTH2_PROXY_ENABLE_AUDIT is true")
|
||||
err := opts.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
log.Print("Audit entries will NOT be created since OAUTH2_PROXY_ENABLE_AUDIT is false")
|
||||
}
|
||||
apiSignature := NewAPISignature(opts.SecretKey, opts.SharedKey)
|
||||
client := resty.New()
|
||||
client.SetRetryCount(3).
|
||||
SetRetryWaitTime(5 * time.Second).
|
||||
SetRetryMaxWaitTime(20 * time.Second).
|
||||
SetContentLength(true).
|
||||
SetRetryAfter(func(client *resty.Client, resp *resty.Response) (time.Duration, error) {
|
||||
return 0, fmt.Errorf("%w: retry quota exceeded", ErrPersitAuditEvent)
|
||||
})
|
||||
return &Client{enabled: opts.Enabled, apiSignature: apiSignature, client: client, opts: opts}, nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateSuccessfulLoginAuditEntry(ss *sessions.SessionState, appURL string, tenantID string) {
|
||||
c.createAuditEntry(ss, appURL, tenantID, "0", "Success")
|
||||
}
|
||||
|
||||
func (c *Client) CreateFailedLoginAuditEntry(ss *sessions.SessionState, appURL string, tenantID string, errorDesc string) {
|
||||
c.createAuditEntry(ss, appURL, tenantID, "1", errorDesc)
|
||||
}
|
||||
|
||||
func (c *Client) createAuditEntry(ss *sessions.SessionState, appURL string, tenantID string, outcomeCode string, outcomeDesc string) {
|
||||
if !c.enabled {
|
||||
return
|
||||
}
|
||||
auditObject := RootEvent{
|
||||
ResourceType: "AuditEvent",
|
||||
Event: &Event{
|
||||
Type: &Coding{
|
||||
System: "http://hl7.org/fhir/ValueSet/audit-event-type", Version: "1", Code: "110114", Display: "User Authentication"},
|
||||
Action: "E",
|
||||
DateTime: time.Now().UTC().Format(time.RFC3339),
|
||||
Outcome: outcomeCode,
|
||||
OutcomeDesc: outcomeDesc},
|
||||
|
||||
Participant: []*Participant{
|
||||
{AltID: ss.User, UserID: UserID{Value: ss.Email}, Name: ss.PreferredUsername, Requestor: true}},
|
||||
Source: Source{
|
||||
Identifier: Identifier{
|
||||
Type: &Coding{
|
||||
System: "http://hl7.org/fhir/ValueSet/audit-source-type",
|
||||
Code: "4",
|
||||
Display: "Application Server",
|
||||
},
|
||||
Value: ss.Email,
|
||||
},
|
||||
Type: []*Coding{{System: "http://hl7.org/fhir/security-source-type", Code: "1", Display: "End-user display device, diagnostic device."}},
|
||||
Extension: []*Extension{
|
||||
{
|
||||
URL: appURL,
|
||||
Extension: []*ExtensionContent{
|
||||
{
|
||||
URL: "applicationName",
|
||||
ValueString: c.opts.ProductName,
|
||||
},
|
||||
{
|
||||
URL: "applicationVersion",
|
||||
ValueString: "1",
|
||||
},
|
||||
{
|
||||
URL: "serverName",
|
||||
ValueString: "oauth2proxy",
|
||||
},
|
||||
{
|
||||
URL: "componentName",
|
||||
ValueString: "oauth2proxy",
|
||||
},
|
||||
{
|
||||
URL: "productKey",
|
||||
ValueString: c.opts.ProductKey,
|
||||
},
|
||||
{
|
||||
URL: "tenant",
|
||||
ValueString: tenantID,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
auditMessage, err := json.Marshal(auditObject)
|
||||
if err != nil {
|
||||
logger.Errorf("%s: could not marshal the audit object: %v", ErrPersitAuditEvent.Error(), err)
|
||||
return
|
||||
}
|
||||
err = c.send(string(auditMessage))
|
||||
if err != nil {
|
||||
logger.Errorf("%s: could not send the audit message to the url '%s': %v", ErrPersitAuditEvent.Error(), c.opts.URL, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) send(msg string) error {
|
||||
signedDate := time.Now().UTC().Format(time.RFC3339)
|
||||
signature := c.apiSignature.GetSignature(signedDate)
|
||||
resp, err := c.client.R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("api-version", "2").
|
||||
SetHeader("HSDP-API-Signature", signature).
|
||||
SetHeader("SignedDate", signedDate).
|
||||
SetBody(msg).
|
||||
Post(c.opts.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode() != 201 {
|
||||
log.Println("Not able to send the audit message ", resp)
|
||||
return fmt.Errorf("not able to persist audit, audit server returned %v", resp.StatusCode())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClientOpts) Validate() error {
|
||||
err := errors.New("")
|
||||
if strings.TrimSpace(c.URL) == "" {
|
||||
err = errors.New("the OAUTH2_PROXY_AUDIT_URL must be set")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(c.ProductName) == "" {
|
||||
err = fmt.Errorf("%w: the OAUTH2_PROXY_AUDIT_PRODUCT_NAME must be set", err)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(c.ProductKey) == "" {
|
||||
err = fmt.Errorf("%w: the OAUTH2_PROXY_AUDIT_PRODUCT_KEY must be set", err)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(c.SharedKey) == "" {
|
||||
err = fmt.Errorf("%w: the OAUTH2_PROXY_AUDIT_SHARED_KEY must be set", err)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(c.SecretKey) == "" {
|
||||
err = fmt.Errorf("%w: the OAUTH2_PROXY_AUDIT_SECRET_KEY must be set", err)
|
||||
}
|
||||
|
||||
if err != nil && err.Error() != "" {
|
||||
return fmt.Errorf("the OAUTH2_PROXY_ENABLE_AUDIT is set to true however these are missing: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package audit
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrPersitAuditEvent = errors.New("could not persist the audit event")
|
||||
)
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
package audit
|
||||
|
||||
type Coding struct {
|
||||
System string `json:"system,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
Display string `json:"display,omitempty"`
|
||||
UserSelected string `json:"userSelected,omitempty"`
|
||||
}
|
||||
|
||||
type Identifier struct {
|
||||
Use string `json:"use,omitempty"`
|
||||
Type *Coding `json:"type,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
System string `json:"system,omitempty"`
|
||||
}
|
||||
|
||||
type Reference struct {
|
||||
Reference string `json:"reference,omitempty"`
|
||||
Display string `json:"display,omitempty"`
|
||||
}
|
||||
|
||||
type Detail struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type Object struct {
|
||||
Identifier *Identifier `json:"identifier,omitempty"`
|
||||
Reference *Reference `json:"reference,omitempty"`
|
||||
Type *Coding `json:"type,omitempty"`
|
||||
Role *Coding `json:"role,omitempty"`
|
||||
Lifecycle *Coding `json:"lifecycle,omitempty"`
|
||||
SecurityLabel []Coding `json:"securityLabel,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Query interface{} `json:"query,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Detail []Detail `json:"detail,omitempty"`
|
||||
}
|
||||
|
||||
type Type struct {
|
||||
Coding `json:"coding,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
}
|
||||
|
||||
type UserID struct {
|
||||
Use string `json:"use,omitempty"`
|
||||
Type *Type `json:"type,omitempty"`
|
||||
System string `json:"system,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type Network struct {
|
||||
Address string `json:"address,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
type Media struct {
|
||||
*Coding `json:"coding,omitempty"`
|
||||
}
|
||||
|
||||
type PurposeOfUse struct {
|
||||
*Coding `json:"coding,omitempty"`
|
||||
}
|
||||
|
||||
type Participant struct {
|
||||
Role []Type `json:"role,omitempty"`
|
||||
Reference *Reference `json:"reference,omitempty"`
|
||||
UserID UserID `json:"userId,omitempty"`
|
||||
AltID string `json:"altId,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Requestor bool `json:"requestor,omitempty"`
|
||||
Location *Reference `json:"location,omitempty"`
|
||||
Policy []string `json:"policy,omitempty"`
|
||||
Media *Media `json:"media,omitempty"`
|
||||
Network *Network `json:"network,omitempty"`
|
||||
PurposeOfUse []PurposeOfUse `json:"purposeOfUse,omitempty"`
|
||||
}
|
||||
|
||||
type ExtensionContent struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
ValueString string `json:"valueString,omitempty"`
|
||||
}
|
||||
type Extension struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Extension []*ExtensionContent `json:"extension,omitempty"`
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
Site string `json:"site,omitempty"`
|
||||
Identifier Identifier `json:"identifier,omitempty"`
|
||||
Type []*Coding `json:"type,omitempty"`
|
||||
Extension []*Extension `json:"extension,omitempty"`
|
||||
}
|
||||
|
||||
type PurposeOfEvent struct {
|
||||
Coding `json:"coding,omitempty"`
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
Type *Coding `json:"type,omitempty"`
|
||||
Subtype []*Coding `json:"subtype,omitempty"`
|
||||
Action string `json:"action,omitempty"`
|
||||
DateTime string `json:"dateTime,omitempty"`
|
||||
Outcome string `json:"outcome,omitempty"`
|
||||
OutcomeDesc string `json:"outcomeDesc,omitempty"`
|
||||
PurposeOfEvent []*PurposeOfEvent `json:"purposeOfEvent,omitempty"`
|
||||
}
|
||||
|
||||
type RootEvent struct {
|
||||
ResourceType string `json:"resourceType,omitempty"`
|
||||
Event *Event `json:"event,omitempty"`
|
||||
Participant []*Participant `json:"participant,omitempty"`
|
||||
Source Source `json:"source,omitempty"`
|
||||
Object []*Object `json:"object,omitempty"`
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package audit
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"hash"
|
||||
)
|
||||
|
||||
type APISignature interface {
|
||||
GetSignature(signedDate string) string
|
||||
}
|
||||
|
||||
type apiSignature struct {
|
||||
secretKey string
|
||||
sharedKey string
|
||||
hash hash.Hash
|
||||
}
|
||||
|
||||
func NewAPISignature(secretKey, sharedKey string) APISignature {
|
||||
secret := fmt.Sprintf("DHPWS%s", secretKey)
|
||||
return &apiSignature{secretKey: secretKey, sharedKey: sharedKey, hash: hmac.New(sha256.New, []byte(secret))}
|
||||
}
|
||||
|
||||
func (s *apiSignature) GetSignature(signedDate string) string {
|
||||
s.hash.Write([]byte(b64.StdEncoding.EncodeToString([]byte(signedDate))))
|
||||
b := s.hash.Sum(nil)
|
||||
sEnc := b64.StdEncoding.EncodeToString(b)
|
||||
s.hash.Reset()
|
||||
return fmt.Sprintf("HmacSHA256;Credential:%s;SignedHeaders:SignedDate;Signature:%s", s.sharedKey, sEnc)
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ func (cr *cookieRefresh) refreshCookie(next http.Handler) http.Handler {
|
|||
cookie, err := req.Cookie(cr.CookieRefreshName)
|
||||
if err != nil {
|
||||
logger.Errorf("SSO Cookie Refresher - Could find '%s' cookie in the request: %v", cr.CookieRefreshName, err)
|
||||
next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
resp := requests.New(cr.CookieRefreshURL).
|
||||
|
|
@ -53,6 +54,7 @@ func (cr *cookieRefresh) refreshCookie(next http.Handler) http.Handler {
|
|||
if resp.StatusCode() != http.StatusNoContent {
|
||||
bodyString := string(resp.Body())
|
||||
logger.Errorf("SSO Cookie Refresher - Could not refresh the '%s' cookie in the url '%s' due to status and content: %v - %v", cr.CookieRefreshName, cr.CookieRefreshURL, resp.StatusCode(), bodyString)
|
||||
next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue