6
6
_ "embed"
7
7
"encoding/json"
8
8
"errors"
9
+ "fmt"
9
10
"strings"
10
11
"sync"
11
12
"time"
@@ -23,7 +24,9 @@ import (
23
24
"github.com/coder/coder/v2/coderd/rbac/regosql"
24
25
"github.com/coder/coder/v2/coderd/rbac/regosql/sqltypes"
25
26
"github.com/coder/coder/v2/coderd/tracing"
27
+ "github.com/coder/coder/v2/coderd/util/ptr"
26
28
"github.com/coder/coder/v2/coderd/util/slice"
29
+ "github.com/coder/coder/v2/coderd/util/syncmap"
27
30
)
28
31
29
32
type AuthCall struct {
@@ -362,11 +365,11 @@ func (a RegoAuthorizer) Authorize(ctx context.Context, subject Subject, action p
362
365
defer span .End ()
363
366
364
367
err := a .authorize (ctx , subject , action , object )
365
-
366
- span .SetAttributes (attribute .Bool ("authorized" , err == nil ))
368
+ authorized := err == nil
369
+ span .SetAttributes (attribute .Bool ("authorized" , authorized ))
367
370
368
371
dur := time .Since (start )
369
- if err != nil {
372
+ if ! authorized {
370
373
a .authorizeHist .WithLabelValues ("false" ).Observe (dur .Seconds ())
371
374
return err
372
375
}
@@ -741,3 +744,98 @@ func rbacTraceAttributes(actor Subject, action policy.Action, objectType string,
741
744
attribute .String ("object_type" , objectType ),
742
745
)... )
743
746
}
747
+
748
+ type authRecorder struct {
749
+ authz Authorizer
750
+ }
751
+
752
+ // Recorder returns an Authorizer that records any authorization checks made
753
+ // on the Context provided for the authorization check.
754
+ //
755
+ // Requires using the RecordAuthzChecks middleware.
756
+ func Recorder (authz Authorizer ) Authorizer {
757
+ return & authRecorder {authz : authz }
758
+ }
759
+
760
+ func (c * authRecorder ) Authorize (ctx context.Context , subject Subject , action policy.Action , object Object ) error {
761
+ err := c .authz .Authorize (ctx , subject , action , object )
762
+ authorized := err == nil
763
+ recordAuthzCheck (ctx , action , object , authorized )
764
+ return err
765
+ }
766
+
767
+ func (c * authRecorder ) Prepare (ctx context.Context , subject Subject , action policy.Action , objectType string ) (PreparedAuthorized , error ) {
768
+ return c .authz .Prepare (ctx , subject , action , objectType )
769
+ }
770
+
771
+ type authzCheckRecorderKey struct {}
772
+
773
+ func WithAuthzCheckRecorder (ctx context.Context ) context.Context {
774
+ return context .WithValue (ctx , authzCheckRecorderKey {}, ptr .Ref (AuthzCheckRecorder {
775
+ checks : syncmap.Map [string , bool ]{},
776
+ }))
777
+ }
778
+
779
+ type AuthzCheckRecorder struct {
780
+ // Checks is a map from preformatted authz check IDs to their authorization
781
+ // status (true => authorized, false => not authorized)
782
+ checks syncmap.Map [string , bool ]
783
+ }
784
+
785
+ func recordAuthzCheck (ctx context.Context , action policy.Action , object Object , authorized bool ) {
786
+ r , ok := ctx .Value (authzCheckRecorderKey {}).(* AuthzCheckRecorder )
787
+ if ! ok {
788
+ return
789
+ }
790
+
791
+ // We serialize the check using the following syntax
792
+ var b strings.Builder
793
+ if object .OrgID != "" {
794
+ _ , err := fmt .Fprintf (& b , "organization:%v::" , object .OrgID )
795
+ if err != nil {
796
+ return
797
+ }
798
+ }
799
+ if object .AnyOrgOwner {
800
+ _ , err := fmt .Fprint (& b , "organization:any::" )
801
+ if err != nil {
802
+ return
803
+ }
804
+ }
805
+ if object .Owner != "" {
806
+ _ , err := fmt .Fprintf (& b , "owner:%v::" , object .Owner )
807
+ if err != nil {
808
+ return
809
+ }
810
+ }
811
+ if object .ID != "" {
812
+ _ , err := fmt .Fprintf (& b , "id:%v::" , object .ID )
813
+ if err != nil {
814
+ return
815
+ }
816
+ }
817
+ _ , err := fmt .Fprintf (& b , "%v.%v" , object .RBACObject ().Type , action )
818
+ if err != nil {
819
+ return
820
+ }
821
+
822
+ r .checks .Store (b .String (), authorized )
823
+ }
824
+
825
+ func GetAuthzCheckRecorder (ctx context.Context ) (* AuthzCheckRecorder , bool ) {
826
+ checks , ok := ctx .Value (authzCheckRecorderKey {}).(* AuthzCheckRecorder )
827
+ if ! ok {
828
+ return nil , false
829
+ }
830
+
831
+ return checks , true
832
+ }
833
+
834
+ // String serializes all of the checks recorded, using the following syntax:
835
+ func (r * AuthzCheckRecorder ) String () string {
836
+ checks := make ([]string , 0 )
837
+ for check , result := range r .checks .Seq () {
838
+ checks = append (checks , fmt .Sprintf ("%v=%v" , check , result ))
839
+ }
840
+ return strings .Join (checks , "; " )
841
+ }
0 commit comments