From b17d5118d0ba4c2e77dff1a9da8ce412168c0acd Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Mon, 10 Feb 2025 17:56:00 +0545 Subject: [PATCH] feat: RBAC --- go.mod | 28 ++++- go.sum | 94 ++++++++++++++-- query/info.go | 39 +++++++ rbac/adapter/permission.go | 202 +++++++++++++++++++++++++++++++++ rbac/custom_functions.go | 142 +++++++++++++++++++++++ rbac/custom_functions_test.go | 137 ++++++++++++++++++++++ rbac/init.go | 206 ++++++++++++++++++++++++++++++++++ rbac/model.ini | 14 +++ rbac/objects.go | 201 +++++++++++++++++++++++++++++++++ rbac/policies.yaml | 52 +++++++++ rbac/policy/policy.go | 205 +++++++++++++++++++++++++++++++++ rbac/rbac_test.go | 166 +++++++++++++++++++++++++++ rbac/types/types.go | 68 +++++++++++ 13 files changed, 1539 insertions(+), 15 deletions(-) create mode 100644 query/info.go create mode 100644 rbac/adapter/permission.go create mode 100644 rbac/custom_functions.go create mode 100644 rbac/custom_functions_test.go create mode 100644 rbac/init.go create mode 100644 rbac/model.ini create mode 100644 rbac/objects.go create mode 100644 rbac/policies.yaml create mode 100644 rbac/policy/policy.go create mode 100644 rbac/rbac_test.go create mode 100644 rbac/types/types.go diff --git a/go.mod b/go.mod index b5911830..fa15a059 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/flanksource/duty go 1.23 require ( - ariga.io/atlas v0.14.2 + ariga.io/atlas v0.15.0 cloud.google.com/go/cloudsqlconn v1.5.1 cloud.google.com/go/storage v1.43.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 @@ -16,6 +16,9 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.27.36 github.com/aws/aws-sdk-go-v2/credentials v1.17.34 github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 + github.com/casbin/casbin/v2 v2.103.0 + github.com/casbin/gorm-adapter/v3 v3.32.0 + github.com/casbin/govaluate v1.3.0 github.com/eko/gocache/lib/v4 v4.1.6 github.com/eko/gocache/store/go_cache/v4 v4.2.2 github.com/exaring/otelpgx v0.6.2 @@ -94,6 +97,7 @@ require ( cloud.google.com/go/kms v1.19.0 // indirect cloud.google.com/go/longrunning v0.6.0 // indirect dario.cat/mergo v1.0.0 // indirect + filippo.io/edwards25519 v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect @@ -132,18 +136,21 @@ require ( github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.5.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect - github.com/fatih/color v1.16.0 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/flanksource/is-healthy v1.0.59 // indirect + github.com/flanksource/is-healthy v1.0.60 // indirect github.com/flanksource/kubectl-neat v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/geoffgarside/ber v1.1.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect + github.com/glebarez/go-sqlite v1.20.3 // indirect + github.com/glebarez/sqlite v1.7.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -153,11 +160,15 @@ require ( github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-yaml v1.12.0 // indirect + github.com/gofrs/uuid v4.2.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -208,6 +219,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/microsoft/go-mssqldb v1.6.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/spdystream v0.4.0 // indirect @@ -225,6 +237,7 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/robertkrimen/otto v0.3.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect @@ -233,7 +246,7 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.2.2 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect - github.com/tidwall/gjson v1.14.4 // indirect + github.com/tidwall/gjson v1.17.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.0.4 // indirect @@ -275,9 +288,16 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect + gorm.io/driver/mysql v1.5.7 // indirect + gorm.io/driver/sqlserver v1.5.3 // indirect + gorm.io/plugin/dbresolver v1.5.3 // indirect k8s.io/apiextensions-apiserver v0.31.1 // indirect k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 // indirect layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf // indirect + modernc.org/libc v1.22.2 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/sqlite v1.20.3 // indirect sigs.k8s.io/gateway-api v1.1.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect diff --git a/go.sum b/go.sum index 2df41a3e..8d34a8f6 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -ariga.io/atlas v0.14.2 h1:efxCuSGnDuhx7xm4JaqImR6xd+PqyizgGy5u/XUEI/g= -ariga.io/atlas v0.14.2/go.mod h1:isZrlzJ5cpoCoKFoY9knZug7Lq4pP1cm8g3XciLZ0Pw= +ariga.io/atlas v0.15.0 h1:9lwSVcO/D3WgaCzstSGqR1hEDtsGibu6JqUofEI/0sY= +ariga.io/atlas v0.15.0/go.mod h1:isZrlzJ5cpoCoKFoY9knZug7Lq4pP1cm8g3XciLZ0Pw= cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -58,16 +58,30 @@ dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 h1:m/sWOGCREuSBqg2htVQTBY8nOZpyajYztF0vUvSZTuM= github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0/go.mod h1:Pu5Zksi2KrU7LPbZbNINx6fuVrUp/ffvpxdDj+i8LeE= github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0 h1:yfJe15aSwEQ6Oo6J+gdfdulPNoZ3TEhmbhLIoxZcA+U= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0/go.mod h1:Q28U+75mpCaSCDowNEmhIo/rmgdkqmkmzI7N6TGR4UY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 h1:T028gtTPiYt/RMUfs8nVsAL7FDQrfLlrm/NnRG/zcC4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -160,6 +174,7 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q= github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -167,6 +182,12 @@ github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0 github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/casbin/casbin/v2 v2.103.0 h1:dHElatNXNrr8XcseUov0ZSiWjauwmZZE6YMV3eU1yic= +github.com/casbin/casbin/v2 v2.103.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco= +github.com/casbin/gorm-adapter/v3 v3.32.0 h1:Au+IOILBIE9clox5BJhI2nA3p9t7Ep1ePlupdGbGfus= +github.com/casbin/gorm-adapter/v3 v3.32.0/go.mod h1:Zre/H8p17mpv5U3EaWgPoxLILLdXO3gHW5aoQQpUDZI= +github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc= +github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -200,6 +221,10 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/eko/gocache/lib/v4 v4.1.6 h1:5WWIGISKhE7mfkyF+SJyWwqa4Dp2mkdX8QsZpnENqJI= github.com/eko/gocache/lib/v4 v4.1.6/go.mod h1:HFxC8IiG2WeRotg09xEnPD72sCheJiTSr4Li5Ameg7g= github.com/eko/gocache/store/go_cache/v4 v4.2.2 h1:tAI9nl6TLoJyKG1ujF0CS0n/IgTEMl+NivxtR5R3/hw= @@ -218,8 +243,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/exaring/otelpgx v0.6.2 h1:z1ayuDusPITNOhzvmx3nLpFax+tv7Hu7mdrjtgW3ZeA= github.com/exaring/otelpgx v0.6.2/go.mod h1:DuRveXIeRNz6VJrMTj2uCBFqiocMx4msCN1mIMmbZUI= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fergusstrange/embedded-postgres v1.29.0 h1:Uv8hdhoiaNMuH0w8UuGXDHr60VoAQPFdgx7Qf3bzXJM= @@ -230,8 +255,8 @@ github.com/flanksource/commons v1.36.1 h1:SDppXOYO6vk06d12SPOYVZJX+8gV72FmK1l9ar github.com/flanksource/commons v1.36.1/go.mod h1:nsYCn6JOhENXNLR5pz4zNco70DQV+bGObQ8NbOrUeuQ= github.com/flanksource/gomplate/v3 v3.24.55 h1:BW+KeMcggCkNawb4VTbY+Zn3s9eYIhwqb5/myiyJRb8= github.com/flanksource/gomplate/v3 v3.24.55/go.mod h1:hGEObNtnOQs8rNUX8sM8aJTAhnt4ehjyOw1MvDhl6AU= -github.com/flanksource/is-healthy v1.0.59 h1:/dObdgBEouYMX7eF2R4l20G8I+Equ0YGDrXOtRpar/s= -github.com/flanksource/is-healthy v1.0.59/go.mod h1:5MUvkRbq78aPVIpwGjpn+k89n5+1thBLIRdhfcozUcQ= +github.com/flanksource/is-healthy v1.0.60 h1:2/h+qc6N6zFUT8B+e4AaMGCOGtaE8kPs1Q/rndAG7mQ= +github.com/flanksource/is-healthy v1.0.60/go.mod h1:5MUvkRbq78aPVIpwGjpn+k89n5+1thBLIRdhfcozUcQ= github.com/flanksource/kubectl-neat v1.0.4 h1:t5/9CqgE84oEtB0KitgJ2+WIeLfD+RhXSxYrqb4X8yI= github.com/flanksource/kubectl-neat v1.0.4/go.mod h1:Un/Voyh3cmiZNKQrW/TkAl28nAA7vwnwDGVjRErKjOw= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -244,6 +269,10 @@ github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9 github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4= +github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0= +github.com/glebarez/sqlite v1.7.0 h1:A7Xj/KN2Lvie4Z4rrgQHY8MsbebX3NyWsL3n2i82MVI= +github.com/glebarez/sqlite v1.7.0/go.mod h1:PkeevrRlF/1BhQBCnzcMWzgrIk7IOop+qS2jUYLfHhk= github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -281,6 +310,7 @@ github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -292,11 +322,15 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= @@ -388,6 +422,7 @@ github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= @@ -398,6 +433,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q= @@ -412,6 +449,8 @@ github.com/hairyhenderson/toml v0.4.2-0.20210923231440-40456b8e66cf h1:I1sbT4ZbI github.com/hairyhenderson/toml v0.4.2-0.20210923231440-40456b8e66cf/go.mod h1:jDHmWDKZY6MIIYltYYfW4Rs7hQ50oS4qf/6spSiZAxY= github.com/hairyhenderson/yaml v0.0.0-20220618171115-2d35fca545ce h1:cVkYhlWAxwuS2/Yp6qPtcl0fGpcWxuZNonywHZ6/I+s= github.com/hairyhenderson/yaml v0.0.0-20220618171115-2d35fca545ce/go.mod h1:7TyiGlHI+IO+iJbqRZ82QbFtvgj/AIcFm5qc9DLn7Kc= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14= @@ -491,6 +530,12 @@ github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jeremywohl/flatten v0.0.0-20180923035001-588fe0d4c603 h1:gSech9iGLFCosfl/DC7BWnpSSh/tQClWnKS2I2vdPww= github.com/jeremywohl/flatten v0.0.0-20180923035001-588fe0d4c603/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -587,6 +632,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -612,6 +659,7 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -664,6 +712,9 @@ github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -734,8 +785,8 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= -github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= @@ -857,6 +908,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= @@ -929,6 +982,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -944,6 +998,7 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= @@ -1023,6 +1078,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1049,6 +1105,7 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= @@ -1066,6 +1123,7 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= @@ -1259,13 +1317,19 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= -gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/driver/sqlserver v1.5.3 h1:rjupPS4PVw+rjJkfvr8jn2lJ8BMhT4UW5FwuJY0P3Z0= +gorm.io/driver/sqlserver v1.5.3/go.mod h1:B+CZ0/7oFJ6tAlefsKoyxdgDCXJKSgwS2bMOQZT0I00= gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +gorm.io/plugin/dbresolver v1.5.3 h1:wFwINGZZmttuu9h7XpvbDHd8Lf9bb8GNzp/NpAMV2wU= +gorm.io/plugin/dbresolver v1.5.3/go.mod h1:TSrVhaUg2DZAWP3PrHlDlITEJmNOkL0tFTjvTEsQ4XE= gorm.io/plugin/prometheus v0.1.0 h1:kDQwAfCUsT9D6jDUpIp7pnc7bCJu/6voM8I/BmFjxUQ= gorm.io/plugin/prometheus v0.1.0/go.mod h1:5nrc/JrWCUNoDXCY4eOae/FK/J5WjQ0axXuFusCzdTc= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= @@ -1293,6 +1357,14 @@ k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 h1:MDF6h2H/h4tbzmtIKTuctcwZmY0tY k8s.io/utils v0.0.0-20240921022957-49e7df575cb6/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf h1:rRz0YsF7VXj9fXRF6yQgFI7DzST+hsI3TeFSGupntu0= layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf/go.mod h1:ivKkcY8Zxw5ba0jldhZCYYQfGdb2K6u9tbYK1AwMIBc= +modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0= +modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/sqlite v1.20.3 h1:SqGJMMxjj1PHusLxdYxeQSodg7Jxn9WWkaAQjKrntZs= +modernc.org/sqlite v1.20.3/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/query/info.go b/query/info.go new file mode 100644 index 00000000..833a95a9 --- /dev/null +++ b/query/info.go @@ -0,0 +1,39 @@ +package query + +import ( + "github.com/lib/pq" + "gorm.io/gorm" +) + +type Info struct { + Tables pq.StringArray `gorm:"type:[]text"` + Views pq.StringArray `gorm:"type:[]text"` + Functions pq.StringArray `gorm:"type:[]text"` +} + +func (info *Info) Get(db *gorm.DB) error { + sql := ` + SELECT tables, + views, + functions + FROM (SELECT array_agg(information_schema.views.table_name) AS views + FROM information_schema.views + WHERE information_schema.views.table_schema = any (current_schemas(false)) AND table_name not like 'pg_%' + ) + t, + (SELECT array_agg(information_schema.tables.table_name) AS tables + FROM information_schema."tables" + WHERE information_schema.tables.table_schema = any ( + current_schemas(false) ) + AND information_schema.tables.table_type = 'BASE TABLE') v, + (SELECT array_agg(proname) AS functions + FROM pg_proc p + INNER JOIN pg_namespace ns + ON ( p.pronamespace = ns.oid ) + WHERE ns.nspname = 'public' + AND probin IS NULL + AND probin IS NULL + AND proretset IS TRUE) f + ` + return db.Raw(sql).Scan(info).Error +} diff --git a/rbac/adapter/permission.go b/rbac/adapter/permission.go new file mode 100644 index 00000000..7293f5c0 --- /dev/null +++ b/rbac/adapter/permission.go @@ -0,0 +1,202 @@ +package adapter + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/casbin/casbin/v2/model" + "github.com/casbin/casbin/v2/persist" + gormadapter "github.com/casbin/gorm-adapter/v3" + "github.com/flanksource/commons/collections" + "github.com/flanksource/duty/models" + pkgPolicy "github.com/flanksource/duty/rbac/policy" + "github.com/flanksource/duty/rbac/types" + "github.com/samber/lo" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type PermissionAdapter struct { + *gormadapter.Adapter // gorm adapter for `casbin_rules` table + + db *gorm.DB +} + +var _ persist.BatchAdapter = &PermissionAdapter{} + +func NewPermissionAdapter(db *gorm.DB, main *gormadapter.Adapter) *PermissionAdapter { + return &PermissionAdapter{ + db: db, + Adapter: main, + } +} + +func (a *PermissionAdapter) LoadPolicy(model model.Model) error { + if err := a.Adapter.LoadPolicy(model); err != nil { + return err + } + + var permissions []models.Permission + if err := a.db.Where("deleted_at IS NULL").Find(&permissions).Error; err != nil { + return fmt.Errorf("failed to load permissions: %w", err) + } + + for _, permission := range permissions { + policies := PermissionToCasbinRule(permission) + for _, policy := range policies { + if err := persist.LoadPolicyArray(policy, model); err != nil { + return err + } + } + } + + var permissionGroups []models.PermissionGroup + if err := a.db.Where("deleted_at IS NULL").Find(&permissionGroups).Error; err != nil { + return fmt.Errorf("failed to load permissions: %w", err) + } + + for _, pg := range permissionGroups { + policies, err := a.permissionGroupToCasbinRule(pg) + if err != nil { + return err + } + + for _, policy := range policies { + if err := persist.LoadPolicyArray(policy, model); err != nil { + return err + } + } + } + + return nil +} + +func PermissionToCasbinRule(permission models.Permission) [][]string { + var policies [][]string + patterns := strings.Split(permission.Action, ",") + + for _, action := range pkgPolicy.AllActions { + if !collections.MatchItems(action, patterns...) { + continue + } + + policies = append(policies, createPolicy(permission, action)) + + if objectSelector := rbacToABACObjectSelector(permission, action); objectSelector != nil { + abacPermission := permission + abacPermission.Object = "" + abacPermission.ObjectSelector = objectSelector + policies = append(policies, createPolicy(abacPermission, action)) + } + } + + return policies +} + +// createPolicy generates a Casbin policy rule from a permission. +func createPolicy(permission models.Permission, action string) []string { + return []string{ + "p", + permission.Principal(), + permission.GetObject(), + action, + permission.Effect(), + permission.Condition(), + permission.ID.String(), + } +} + +// rbacToABACObjectSelector returns object selectors (v1.PermissionObject) in JSON +// for ABAC policies from a global permission. +func rbacToABACObjectSelector(permission models.Permission, action string) []byte { + switch permission.Object { + case pkgPolicy.ObjectPlaybooks: + if lo.Contains([]string{pkgPolicy.ActionPlaybookRun, pkgPolicy.ActionPlaybookApprove}, action) { + return []byte(`{"playbooks": [{"name":"*"}]}`) + } + + case pkgPolicy.ObjectCatalog: + if pkgPolicy.ActionRead == action { + return []byte(`{"configs": [{"name":"*"}]}`) + } + + case pkgPolicy.ObjectTopology: + if pkgPolicy.ActionRead == action { + return []byte(`{"components": [{"name":"*"}]}`) + } + } + + return nil +} + +func (a *PermissionAdapter) permissionGroupToCasbinRule(permission models.PermissionGroup) ([][]string, error) { + var subject types.PermissionGroupSubjects + if err := json.Unmarshal(permission.Selectors, &subject); err != nil { + return nil, err + } + + var allIDs []string + + if len(subject.Notifications) > 0 { + var clauses []clause.Expression + for _, selector := range subject.Notifications { + if selector.Empty() { + continue + } + + var conditions []clause.Expression + if selector.Namespace != "" { + conditions = append(conditions, clause.Eq{Column: "namespace", Value: selector.Namespace}) + } + if selector.Name != "" { + conditions = append(conditions, clause.Eq{Column: "name", Value: selector.Name}) + } + + clauses = append(clauses, clause.And(conditions...)) + } + + if len(clauses) > 0 { + var notifications []string + if err := a.db.Select("id").Model(&models.Notification{}).Clauses(clause.Or(clauses...)).Find(¬ifications).Error; err != nil { + return nil, err + } + + allIDs = append(allIDs, notifications...) + } + } + + if len(subject.People) > 0 { + var personIDs []string + if err := a.db.Select("id").Model(&models.Person{}).Where("email IN ? OR name IN ?", subject.People, subject.People).Find(&personIDs).Error; err != nil { + return nil, err + } + + allIDs = append(allIDs, personIDs...) + } + + if len(subject.Teams) > 0 { + var teamIDs []string + if err := a.db.Select("id").Model(&models.Team{}).Where("name = ?", subject.Teams).Find(&teamIDs).Error; err != nil { + return nil, err + } + + allIDs = append(allIDs, teamIDs...) + } + + var policies [][]string + for _, id := range allIDs { + policy := []string{ + "g", + id, + permission.Name, + "", + "", + "", + } + + policies = append(policies, policy) + } + + return policies, nil +} diff --git a/rbac/custom_functions.go b/rbac/custom_functions.go new file mode 100644 index 00000000..ba092818 --- /dev/null +++ b/rbac/custom_functions.go @@ -0,0 +1,142 @@ +package rbac + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + + "github.com/casbin/govaluate" + "github.com/flanksource/commons/collections" + "github.com/flanksource/duty/models" + "github.com/flanksource/duty/rbac/types" + "github.com/google/uuid" + "github.com/samber/lo" +) + +func matchPerm(attr *models.ABACAttribute, _agents any, tagsEncoded string) (bool, error) { + var rAgents []string + switch v := _agents.(type) { + case []any: + rAgents = lo.Map(v, func(item any, _ int) string { return item.(string) }) + case string: + if v != "" { + rAgents = append(rAgents, v) + } + } + + rTags := collections.SelectorToMap(tagsEncoded) + if attr.Config.ID != uuid.Nil { + var agentsMatch = true + if len(rAgents) > 0 { + agentsMatch = lo.Contains(rAgents, attr.Config.AgentID.String()) + } + + tagsmatch := mapContains(rTags, attr.Config.Tags) + return tagsmatch && agentsMatch, nil + } + + return false, nil +} + +type addableEnforcer interface { + AddFunction(name string, function govaluate.ExpressionFunction) +} + +func addCustomFunctions(enforcer addableEnforcer) { + enforcer.AddFunction("matchPerm", func(args ...any) (any, error) { + if len(args) != 3 { + return false, fmt.Errorf("matchPerm needs 3 arguments. got %d", len(args)) + } + + obj := args[0] + if _, ok := obj.(string); ok { + // an object is required to satisfy the agents & tags requirement. + // If a role is passed, we don't match this permission. + return false, nil + } + + attr, ok := obj.(*models.ABACAttribute) + if !ok { + return false, errors.New("[matchPerm] unknown input type: expected *models.ABACAttribute") + } + + agents := args[1] + tags := args[2] + + tagsEncoded, ok := tags.(string) + if !ok { + return false, errors.New("tags must be a string") + } + + return matchPerm(attr, agents, tagsEncoded) + }) + + enforcer.AddFunction("matchResourceSelector", func(args ...any) (any, error) { + if len(args) != 2 { + return false, fmt.Errorf("matchResourceSelector needs 2 arguments. got %d", len(args)) + } + + attributeSet := args[0] + + if _, ok := attributeSet.(string); ok { + return false, nil + } + + attr, ok := attributeSet.(*models.ABACAttribute) + if !ok { + return false, fmt.Errorf("[matchResourceSelector] unknown input type: %T. expected *models.ABACAttribute", attributeSet) + } + + selector, ok := args[1].(string) + if !ok { + return false, fmt.Errorf("[matchResourceSelector] selector must be a string") + } + + rs, err := base64.StdEncoding.DecodeString(selector) + if err != nil { + return false, err + } + + var objectSelector types.PermissionObject + if err := json.Unmarshal([]byte(rs), &objectSelector); err != nil { + return false, err + } + + var resourcesMatched int + + for _, rs := range objectSelector.Components { + if rs.Matches(attr.Component) { + resourcesMatched++ + break + } + } + + for _, rs := range objectSelector.Playbooks { + if rs.Matches(&attr.Playbook) { + resourcesMatched++ + break + } + } + + for _, rs := range objectSelector.Configs { + if rs.Matches(attr.Config) { + resourcesMatched++ + break + } + } + + return resourcesMatched == objectSelector.RequiredMatchCount(), nil + }) +} + +// mapContains returns true if `request` fully contains `want`. +func mapContains(want map[string]string, request map[string]string) bool { + for k, v := range want { + if request[k] != v { + return false + } + } + + return true +} diff --git a/rbac/custom_functions_test.go b/rbac/custom_functions_test.go new file mode 100644 index 00000000..3ab34fb8 --- /dev/null +++ b/rbac/custom_functions_test.go @@ -0,0 +1,137 @@ +package rbac + +import ( + "testing" + + "github.com/flanksource/duty/models" + "github.com/google/uuid" +) + +func Test_matchPerm(t *testing.T) { + type args struct { + obj models.ABACAttribute + _agents any + _tags string + } + + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "tag only match", + args: args{ + obj: models.ABACAttribute{ + Config: models.ConfigItem{ + ID: uuid.New(), + Tags: map[string]string{ + "namespace": "default", + }, + }, + }, + _agents: "", + _tags: "namespace=default", + }, + want: true, + }, + { + name: "multiple tags match", + args: args{ + obj: models.ABACAttribute{ + Config: models.ConfigItem{ + ID: uuid.New(), + Tags: map[string]string{ + "namespace": "default", + "cluster": "homelab", + }, + }, + }, + _agents: "", + _tags: "namespace=default,cluster=homelab", + }, + want: true, + }, + { + name: "multiple tags no match", + args: args{ + obj: models.ABACAttribute{ + Config: models.ConfigItem{ + ID: uuid.New(), + Tags: map[string]string{ + "namespace": "default", + }, + }, + }, + _agents: "", + _tags: "namespace=default,cluster=homelab", + }, + want: false, + }, + { + name: "tags & agents match", + args: args{ + obj: models.ABACAttribute{ + Config: models.ConfigItem{ + ID: uuid.New(), + Tags: map[string]string{ + "namespace": "default", + }, + AgentID: uuid.MustParse("66eda456-315f-455a-95d4-6ef059fc13a8"), + }, + }, + _agents: "66eda456-315f-455a-95d4-6ef059fc13a8", + _tags: "namespace=default", + }, + want: true, + }, + { + name: "tags match & agent no match", + args: args{ + obj: models.ABACAttribute{ + Config: models.ConfigItem{ + ID: uuid.New(), + Tags: map[string]string{ + "namespace": "default", + }, + AgentID: uuid.MustParse("66eda456-315f-455a-95d4-6ef059fc13a8"), + }, + }, + _agents: "", + _tags: "namespace=default,cluster=homelab", + }, + want: false, + }, + { + name: "tags no match & agent match", + args: args{ + obj: models.ABACAttribute{ + Config: models.ConfigItem{ + ID: uuid.New(), + Tags: map[string]string{ + "namespace": "default", + }, + AgentID: uuid.MustParse("66eda456-315f-455a-95d4-6ef059fc13a8"), + }, + }, + _agents: "66eda456-315f-455a-95d4-6ef059fc13a8", + _tags: "namespace=mc", + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := matchPerm(&tt.args.obj, tt.args._agents, tt.args._tags) + if (err != nil) != tt.wantErr { + t.Errorf("matchPerm() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("matchPerm() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/rbac/init.go b/rbac/init.go new file mode 100644 index 00000000..e8f0da21 --- /dev/null +++ b/rbac/init.go @@ -0,0 +1,206 @@ +package rbac + +import ( + _ "embed" + "fmt" + "strings" + "time" + + "github.com/casbin/casbin/v2" + "github.com/casbin/casbin/v2/model" + gormadapter "github.com/casbin/gorm-adapter/v3" + "github.com/flanksource/duty/context" + "github.com/flanksource/duty/query" + pkgAdapater "github.com/flanksource/duty/rbac/adapter" + "github.com/flanksource/duty/rbac/policy" + "gopkg.in/yaml.v3" +) + +var enforcer *casbin.SyncedCachedEnforcer + +//go:embed policies.yaml +var defaultPolicies string + +//go:embed model.ini +var defaultModel string + +func Init(ctx context.Context, adminUserID string) error { + model, err := model.NewModelFromString(defaultModel) + if err != nil { + return fmt.Errorf("error creating rbac model: %v", err) + } + + info := &query.Info{} + if err := info.Get(ctx.DB()); err != nil { + ctx.Warnf("Cannot get DB info: %v", err) + } + + for _, table := range append(info.Views, info.Tables...) { + if GetObjectByTable(table) == "" { + ctx.Warnf("Unmapped database table: %s", table) + } + } + for _, table := range info.Functions { + if GetObjectByTable("rpc/"+table) == "" { + ctx.Warnf("Unmapped database function: %s", table) + } + } + + db := ctx.DB() + + gormadapter.TurnOffAutoMigrate(db) + casbinRuleAdapter, err := gormadapter.NewAdapterByDB(db) + if err != nil { + return fmt.Errorf("error creating rbac adapter: %v", err) + } + + adapter := pkgAdapater.NewPermissionAdapter(db, casbinRuleAdapter) + enforcer, err = casbin.NewSyncedCachedEnforcer(model, adapter) + if err != nil { + return fmt.Errorf("error creating rbac enforcer: %v", err) + } + if err := enforcer.LoadPolicy(); err != nil { + ctx.Errorf("Failed to load existing policies: %v", err) + } + + enforcer.SetExpireTime(ctx.Properties().Duration("casbin.cache.expiry", 1*time.Minute)) + enforcer.EnableCache(ctx.Properties().On(true, "casbin.cache")) + if ctx.Properties().Int("casbin.log.level", 1) >= 2 { + enforcer.EnableLog(true) + } + + addCustomFunctions(enforcer) + + if adminUserID != "" { + if _, err := enforcer.AddRoleForUser(adminUserID, policy.RoleAdmin); err != nil { + return fmt.Errorf("error adding role for admin user: %v", err) + } + } + + var policies []policy.Policy + + if err := yaml.Unmarshal([]byte(defaultPolicies), &policies); err != nil { + return fmt.Errorf("unable to load default policies: %v", err) + } + + enforcer.EnableAutoSave(ctx.Properties().On(true, "casbin.auto.save")) + + // Adding policies in a loop is important + // If we use Enforcer.AddPolicies(), new policies do not get saved + for _, p := range policies { + for _, inherited := range p.Inherit { + if _, err := enforcer.AddGroupingPolicy(p.Principal, inherited); err != nil { + return fmt.Errorf("error adding group policy for %s -> %s: %v", p.Principal, inherited, err) + } + } + for _, acl := range p.GetPolicyDefintions() { + if _, err := enforcer.AddPolicy(acl); err != nil { + return fmt.Errorf("error adding rbac policy %s: %v", p, err) + } + } + } + + enforcer.StartAutoLoadPolicy(ctx.Properties().Duration("casbin.cache.reload.interval", 5*time.Minute)) + + return nil +} + +func Stop() { + if enforcer != nil { + enforcer.StopAutoLoadPolicy() + } +} + +func DeleteRole(role string) (bool, error) { + return enforcer.DeleteRole(role) +} + +func DeleteRoleForUser(user string, role string) error { + _, err := enforcer.DeleteRoleForUser(user, role) + return err +} + +func DeleteAllRolesForUser(user string) error { + _, err := enforcer.DeleteRolesForUser(user) + return err +} + +func AddRoleForUser(user string, role ...string) error { + _, err := enforcer.AddRolesForUser(user, role) + return err +} + +func RolesForUser(user string) ([]string, error) { + implicit, err := enforcer.GetImplicitRolesForUser(user) + if err != nil { + return nil, err + } + + roles, err := enforcer.GetRolesForUser(user) + if err != nil { + return nil, err + } + + return append(implicit, roles...), nil +} + +func PermsForUser(user string) ([]policy.Permission, error) { + implicit, err := enforcer.GetImplicitPermissionsForUser(user) + if err != nil { + return nil, err + } + perms, err := enforcer.GetPermissionsForUser(user) + if err != nil { + return nil, err + } + var s []policy.Permission + for _, perm := range append(perms, implicit...) { + s = append(s, policy.NewPermission(perm)) + } + return s, nil +} + +func Check(ctx context.Context, subject, object, action string) bool { + hasEveryone, err := enforcer.HasRoleForUser(subject, policy.RoleEveryone) + if err != nil { + ctx.Errorf("RBAC Enforce failed: %v", err) + return false + } + + if !hasEveryone { + if _, err := enforcer.AddRoleForUser(subject, policy.RoleEveryone); err != nil { + ctx.Debugf("error adding role %s to user %s", policy.RoleEveryone, subject) + } + } + + if ctx.Properties().On(false, "casbin.explain") { + allowed, rules, err := enforcer.EnforceEx(subject, object, action) + if err != nil { + ctx.Errorf("RBAC Enforce failed: %v", err) + } + ctx.Debugf("[%s] %s:%s -> %v (%s)", subject, object, action, allowed, strings.Join(rules, "\n\t")) + return allowed + } + + allowed, err := enforcer.Enforce(subject, object, action) + if err != nil { + ctx.Errorf("RBAC Enforce failed: %v", err) + return false + } + if ctx.IsTrace() { + ctx.Tracef("rbac: %s %s:%s = %v", subject, object, action, allowed) + } + + return allowed +} + +func ReloadPolicy() error { + if enforcer == nil { + return nil + } + return enforcer.LoadPolicy() +} + +func Enforcer() *casbin.SyncedCachedEnforcer { + return enforcer +} diff --git a/rbac/model.ini b/rbac/model.ini new file mode 100644 index 00000000..99c72180 --- /dev/null +++ b/rbac/model.ini @@ -0,0 +1,14 @@ +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act, eft, condition, id + +[role_definition] +g = _, _ + +[policy_effect] +e = some(where (p.eft == allow)) && !some(where (p.eft == deny)) + +[matchers] +m = g(r.sub, p.sub) && (p.obj == '*' || r.obj == p.obj) && (p.act == '*' || r.act == p.act) && (p.condition == '' || eval(p.condition)) diff --git a/rbac/objects.go b/rbac/objects.go new file mode 100644 index 00000000..5439fcc0 --- /dev/null +++ b/rbac/objects.go @@ -0,0 +1,201 @@ +package rbac + +import ( + "net/http" + + "github.com/flanksource/duty/rbac/policy" +) + +var dbResourceObjMap = map[string]string{ + "access_token": policy.ObjectAuthConfidential, + "access_tokens": policy.ObjectAuthConfidential, + "agents_summary": policy.ObjectMonitor, + "agents": policy.ObjectDatabasePublic, + "analysis_by_component": policy.ObjectCatalog, + "analysis_by_config": policy.ObjectCatalog, + "analysis_summary_by_component": policy.ObjectCatalog, + "analysis_types": policy.ObjectDatabasePublic, + "analyzer_types": policy.ObjectDatabasePublic, + "artifacts": policy.ObjectArtifact, + "canaries_with_status": policy.ObjectCanary, + "canaries": policy.ObjectCanary, + "casbin_rule": policy.ObjectAuth, + "catalog_changes": policy.ObjectCatalog, + "change_types": policy.ObjectDatabasePublic, + "changes_by_component": policy.ObjectCatalog, + "check_component_relationships": policy.ObjectCanary, + "check_config_relationships": policy.ObjectCanary, + "check_labels": policy.ObjectDatabasePublic, + "check_names": policy.ObjectDatabasePublic, + "check_status_summary_hour": policy.ObjectCanary, + "check_statuses_1d": policy.ObjectCanary, + "check_statuses_1h": policy.ObjectCanary, + "check_statuses_5m": policy.ObjectCanary, + "check_statuses": policy.ObjectCanary, + "check_summary_by_component": policy.ObjectCanary, + "check_summary_by_config": policy.ObjectCatalog, + "check_summary_for_config": policy.ObjectCatalog, + "check_summary": policy.ObjectCanary, + "checks_by_component": policy.ObjectCanary, + "checks_by_config": policy.ObjectCanary, + "checks_status_artifacts": policy.ObjectCanary, + "checks": policy.ObjectCanary, + "comment_responders": policy.ObjectIncident, + "comments": policy.ObjectIncident, + "component_labels": policy.ObjectDatabasePublic, + "component_names_all": policy.ObjectTopology, + "component_names": policy.ObjectDatabasePublic, + "component_relationships": policy.ObjectTopology, + "component_types": policy.ObjectDatabasePublic, + "components_with_logs": policy.ObjectTopology, + "components": policy.ObjectTopology, + "config_analysis_analyzers": policy.ObjectCatalog, + "config_analysis_by_severity": policy.ObjectCatalog, + "config_analysis_items": policy.ObjectCatalog, + "config_analysis": policy.ObjectCatalog, + "config_changes_by_types": policy.ObjectCatalog, + "config_changes_items": policy.ObjectCatalog, + "config_changes": policy.ObjectCatalog, + "config_class_summary": policy.ObjectCatalog, + "config_classes": policy.ObjectDatabasePublic, + "config_component_relationships": policy.ObjectCatalog, + "config_detail": policy.ObjectCatalog, + "config_items_aws": policy.ObjectCatalog, + "config_items": policy.ObjectCatalog, + "config_labels": policy.ObjectDatabasePublic, + "config_names": policy.ObjectDatabasePublic, + "config_relationships": policy.ObjectCatalog, + "config_scrapers_with_status": policy.ObjectMonitor, + "config_scrapers": policy.ObjectDatabaseSettings, + "config_statuses": policy.ObjectDatabasePublic, + "config_summary": policy.ObjectCatalog, + "config_tags": policy.ObjectDatabasePublic, + "config_types": policy.ObjectDatabasePublic, + "configs": policy.ObjectCatalog, + "connections_list": policy.ObjectDatabasePublic, + "connections": policy.ObjectConnection, + "connection_details": policy.ObjectConnectionDetail, + "courier_message_dispatches": policy.ObjectAuthConfidential, + "courier_messaged_dispatches": policy.ObjectAuthConfidential, + "courier_messages": policy.ObjectAuthConfidential, + "event_queue_summary": policy.ObjectMonitor, + "event_queue": policy.ObjectDatabaseSystem, + "evidences": policy.ObjectIncident, + "failed_events": policy.ObjectMonitor, + "hypotheses": policy.ObjectIncident, + "identities": policy.ObjectDatabasePublic, + "identity_credential_identifiers": policy.ObjectAuthConfidential, + "identity_credential_types": policy.ObjectAuthConfidential, + "identity_credentials": policy.ObjectAuthConfidential, + "identity_recovery_addresses": policy.ObjectAuthConfidential, + "identity_recovery_codes": policy.ObjectAuthConfidential, + "identity_recovery_tokens": policy.ObjectAuthConfidential, + "identity_verifiable_addresses": policy.ObjectAuthConfidential, + "identity_verification_codes": policy.ObjectAuthConfidential, + "identity_verification_tokens": policy.ObjectAuthConfidential, + "incident_histories": policy.ObjectIncident, + "incident_relationships": policy.ObjectIncident, + "incident_rules": policy.ObjectIncident, + "incident_summary_by_component": policy.ObjectIncident, + "incident_summary": policy.ObjectIncident, + "incidents_by_component": policy.ObjectIncident, + "incidents_by_config": policy.ObjectIncident, + "incidents": policy.ObjectIncident, + "integrations_with_status": policy.ObjectMonitor, + "integrations": policy.ObjectMonitor, + "job_histories": policy.ObjectMonitor, + "job_history_latest_status": policy.ObjectMonitor, + "job_history_names": policy.ObjectMonitor, + "job_history": policy.ObjectMonitor, + "logging_backends": policy.ObjectDatabaseSettings, + "migration_logs": policy.ObjectDatabaseSystem, + "networks": policy.ObjectAuthConfidential, + "notification_send_history": policy.ObjectMonitor, + "notification_send_history_summary": policy.ObjectMonitor, + "notifications_summary": policy.ObjectMonitor, + "notifications": policy.ObjectNotification, + "notification_silences": policy.ObjectNotification, + "people_roles": policy.ObjectDatabasePublic, + "people": policy.ObjectPeople, + "permissions": policy.ObjectDatabaseSystem, + "permission_groups": policy.ObjectDatabaseSystem, + "permissions_summary": policy.ObjectDatabaseSystem, + "permissions_group_summary": policy.ObjectDatabaseSystem, + "playbook_action_agent_data": policy.ObjectPlaybooks, + "playbook_approvals": policy.ObjectPlaybooks, + "playbook_names": policy.ObjectDatabasePublic, + "playbook_run_actions": policy.ObjectPlaybooks, + "playbook_runs": policy.ObjectPlaybooks, + "playbooks_for_agent": policy.ObjectAgentPush, + "playbooks": policy.ObjectPlaybooks, + "properties": policy.ObjectDatabaseSystem, + "push_queue_summary": policy.ObjectMonitor, + "responders": policy.ObjectIncident, + "rpc/lookup_component_config_id_related_components": policy.ObjectTopology, + "rpc/_related_config_ids_recursive": policy.ObjectCatalog, + "rpc/check_summary_for_component": policy.ObjectCanary, + "rpc/config_relationships_recursive": policy.ObjectCatalog, + "rpc/get_recursive_path": policy.ObjectCatalog, + "rpc/lookup_analysis_by_component": policy.ObjectTopology, + "rpc/lookup_changes_by_component": policy.ObjectTopology, + "rpc/lookup_component_by_property": policy.ObjectTopology, + "rpc/lookup_component_children": policy.ObjectTopology, + "rpc/lookup_component_incidents": policy.ObjectTopology, + "rpc/lookup_component_names": policy.ObjectTopology, + "rpc/lookup_component_relations": policy.ObjectTopology, + "rpc/lookup_components_by_check": policy.ObjectTopology, + "rpc/lookup_components_by_config": policy.ObjectTopology, + "rpc/lookup_config_children": policy.ObjectCatalog, + "rpc/lookup_config_relations": policy.ObjectCatalog, + "rpc/lookup_configs_by_component": policy.ObjectTopology, + "rpc/lookup_related_configs": policy.ObjectCatalog, + "rpc/related_changes_recursive": policy.ObjectCatalog, + "rpc/related_config_ids_recursive": policy.ObjectCatalog, + "rpc/related_config_ids": policy.ObjectCatalog, + "rpc/related_configs_recursive": policy.ObjectCatalog, + "rpc/related_configs": policy.ObjectCatalog, + "rpc/soft_delete_canary": policy.ObjectCanary, + "rpc/soft_delete_check": policy.ObjectCanary, + "rpc/uuid_to_ulid": policy.ObjectDatabasePublic, + "saved_query": policy.ObjectDatabasePublic, + "schema_migration": policy.ObjectAuthConfidential, + "scrape_plugins": policy.ObjectCatalog, + "selfservice_errors": policy.ObjectAuthConfidential, + "selfservice_login_flows": policy.ObjectAuthConfidential, + "selfservice_recovery_flows": policy.ObjectAuthConfidential, + "selfservice_registration_flows": policy.ObjectAuthConfidential, + "selfservice_settings_flows": policy.ObjectAuthConfidential, + "selfservice_verification_flows": policy.ObjectAuthConfidential, + "session_devices": policy.ObjectAuthConfidential, + "sessions": policy.ObjectAuthConfidential, + "severities": policy.ObjectDatabasePublic, + "team_components": policy.ObjectDatabasePublic, + "team_members": policy.ObjectDatabasePublic, + "teams_with_status": policy.ObjectDatabasePublic, + "teams": policy.ObjectDatabasePublic, + "topologies_with_status": policy.ObjectTopology, + "topologies": policy.ObjectTopology, + "topology": policy.ObjectTopology, +} + +func GetObjectByTable(resource string) string { + if v, exists := dbResourceObjMap[resource]; exists { + return v + } + return "" +} + +func GetActionFromHttpMethod(method string) string { + switch method { + case http.MethodGet: + return policy.ActionRead + case http.MethodPatch: + return policy.ActionUpdate + case http.MethodPost: + return policy.ActionCreate + case http.MethodDelete: + return policy.ActionDelete + } + + return "" +} diff --git a/rbac/policies.yaml b/rbac/policies.yaml new file mode 100644 index 00000000..2bad685c --- /dev/null +++ b/rbac/policies.yaml @@ -0,0 +1,52 @@ +- principal: everyone + acl: + - objects: database.kratos + actions: "!*" + # Activate after UI update + # - objects: connection + # actions: "!read" +- principal: admin + acl: + - objects: "*" + actions: "*" + inherit: + - everyone +- principal: viewer + acl: + - objects: database.public,canaries,catalog,playbooks,topology,people + actions: read +- principal: guest + inherit: + - everyone + - viewer # remove this. the UI should handle just the guest role. +- principal: commander + acl: + - objects: incident + actions: create,read,update,delete + inherit: + - viewer +- principal: responder + acl: + - objects: incident + actions: create,read,update,delete + inherit: + - viewer +- principal: editor + acl: + - objects: canaries,catalog,topology,playbooks,kubernetes-proxy,notification + actions: create,read,update,delete + - objects: connection + actions: "create,read,update,delete" + - objects: connection-detail + actions: read + inherit: + - viewer +- principal: agent + acl: + - objects: playbooks,database.public + actions: read + - objects: agent-push + actions: create,read,update + # For topology push + - objects: topology + actions: create,update diff --git a/rbac/policy/policy.go b/rbac/policy/policy.go new file mode 100644 index 00000000..5057e06b --- /dev/null +++ b/rbac/policy/policy.go @@ -0,0 +1,205 @@ +package policy + +import ( + "fmt" + "strings" +) + +func Read(objects ...string) ACL { + return ACL{ + Actions: ActionRead, + Objects: strings.Join(objects, ","), + } +} + +func Update(objects ...string) ACL { + return ACL{ + Actions: ActionUpdate, + Objects: strings.Join(objects, ","), + } +} + +func Approve(objects ...string) ACL { + return ACL{ + Actions: ActionPlaybookApprove, + Objects: strings.Join(objects, ","), + } +} + +func Create(objects ...string) ACL { + return ACL{ + Actions: ActionCreate, + Objects: strings.Join(objects, ","), + } +} + +func Delete(objects ...string) ACL { + return ACL{ + Actions: ActionDelete, + Objects: strings.Join(objects, ","), + } +} + +func CRUD(objects ...string) ACL { + return ACL{ + Actions: ActionCRUD, + Objects: strings.Join(objects, ","), + } +} + +func Run(objects ...string) ACL { + return ACL{ + Actions: ActionPlaybookRun, + Objects: strings.Join(objects, ","), + } +} + +func All(objects ...string) ACL { + return ACL{ + Actions: ActionAll, + Objects: strings.Join(objects, ","), + } +} + +type ACL struct { + Objects string `yaml:"objects" json:"objects"` + Actions string `yaml:"actions" json:"actions"` + Principal string `yaml:"principal,omitempty" json:"principal,omitempty"` +} + +func (acl ACL) GetPolicyDefinition() [][]string { + var definitions [][]string + for _, object := range strings.Split(acl.Objects, ",") { + for _, action := range strings.Split(acl.Actions, ",") { + if strings.HasPrefix(action, "!") { + definitions = append(definitions, []string{acl.Principal, object, action[1:], "deny", "true", "na"}) + } else { + definitions = append(definitions, []string{acl.Principal, object, action, "allow", "true", "na"}) + } + } + } + return definitions +} + +type Policy struct { + Principal string `yaml:"principal" json:"principal"` + ACLs []ACL `yaml:"acl,omitempty" json:"acl"` + Inherit []string `yaml:"inherit,omitempty" json:"inherit"` +} + +func (p Policy) GetPolicyDefintions() [][]string { + var definitions [][]string + for _, acl := range p.ACLs { + if acl.Principal == "" { + acl.Principal = p.Principal + } + definitions = append(definitions, acl.GetPolicyDefinition()...) + } + return definitions +} + +func (p Policy) String() string { + s := "" + for _, policy := range p.GetPolicyDefintions() { + if s != "" { + s += "\n" + } + s += strings.Join(policy, ", ") + } + return s +} + +type Permission struct { + ID string `json:"id,omitempty"` + Subject string `json:"subject,omitempty"` + Object string `json:"object,omitempty"` + Action string `json:"action,omitempty"` + Deny bool `json:"deny,omitempty"` + Condition string `json:"condition,omitempty"` +} + +func NewPermission(perm []string) Permission { + return Permission{ + Subject: perm[0], + Object: perm[1], + Action: perm[2], + Deny: perm[3] == "deny", + Condition: perm[4], + ID: perm[5], + } +} + +func NewPermissions(perms [][]string) []Permission { + var arr []Permission + + for _, p := range perms { + arr = append(arr, NewPermission(p)) + } + + return arr + +} + +func (p Permission) String() string { + return fmt.Sprintf("%s on %s (%s)", p.Subject, p.Object, p.Action) +} + +const ( + // Roles + RoleAdmin = "admin" + RoleEveryone = "everyone" + RoleEditor = "editor" + RoleViewer = "viewer" + RoleCommander = "commander" + RoleResponder = "responder" + RoleAgent = "agent" + RoleGuest = "guest" + + // Objects + ObjectKubernetesProxy = "kubernetes-proxy" + ObjectLogs = "logs" + ObjectAgent = "agent" + ObjectAgentPush = "agent-push" + ObjectArtifact = "artifact" + ObjectAuth = "auth" + ObjectCanary = "canaries" + ObjectCatalog = "catalog" + ObjectConnection = "connection" + ObjectConnectionDetail = "connection-detail" + ObjectDatabase = "database" + ObjectDatabaseIdentity = "database.identities" + ObjectAuthConfidential = "database.kratos" + ObjectDatabasePublic = "database.public" + ObjectDatabaseSettings = "database.config_scrapers" + ObjectDatabaseSystem = "database.system" + ObjectIncident = "incident" + ObjectMonitor = "database.monitor" + ObjectPlaybooks = "playbooks" + ObjectRBAC = "rbac" + ObjectTopology = "topology" + ObjectPeople = "people" + ObjectNotification = "notification" +) + +// Actions +const ( + ActionAll = "*" + ActionCRUD = "create,read,update,delete" + ActionCreate = "create" + ActionDelete = "delete" + ActionRead = "read" + ActionUpdate = "update" + + // Playbooks + ActionPlaybookRun = "playbook:run" + ActionPlaybookApprove = "playbook:approve" +) + +var AllActions = []string{ + ActionCreate, + ActionDelete, + ActionRead, + ActionUpdate, + ActionPlaybookApprove, + ActionPlaybookRun, +} diff --git a/rbac/rbac_test.go b/rbac/rbac_test.go new file mode 100644 index 00000000..af166994 --- /dev/null +++ b/rbac/rbac_test.go @@ -0,0 +1,166 @@ +package rbac + +import ( + "testing" + + "github.com/casbin/casbin/v2" + casbinModel "github.com/casbin/casbin/v2/model" + stringadapter "github.com/casbin/casbin/v2/persist/string-adapter" + "github.com/flanksource/duty/models" + "github.com/flanksource/duty/rbac/adapter" + "github.com/flanksource/duty/rbac/policy" + "github.com/google/uuid" + "github.com/samber/lo" +) + +func NewEnforcer(policy string) (*casbin.Enforcer, error) { + model, err := casbinModel.NewModelFromString(defaultModel) + if err != nil { + return nil, err + } + + sa := stringadapter.NewAdapter(policy) + e, err := casbin.NewEnforcer(model, sa) + addCustomFunctions(e) + return e, err +} + +func TestEnforcer(t *testing.T) { + policies := ` +p, admin, *, * , allow, true, na +g, johndoe, admin, , , , na +p, johndoe, *, playbook:run, allow, r.obj.Playbook.Name == 'scale-deployment' , na +p, johndoe, *, playbook:run, deny, r.obj.Playbook.Name == 'delete-deployment' , na +p, johndoe, *, playbook:run, allow, r.obj.Playbook.Name == 'restart-deployment' && r.obj.Config.Tags.namespace == 'default' , na +p, alice, *, playbook:run, deny, r.obj.Playbook.Name == 'restart-deployment' && r.obj.Config.Tags.namespace == 'default', na +` + + var userID = uuid.New() + + permissions := []models.Permission{ + { + ID: uuid.New(), + PersonID: lo.ToPtr(userID), + Object: policy.ObjectCatalog, + Action: policy.ActionRead, + Tags: map[string]string{ + "namespace": "default", + "cluster": "aws", + }, + Agents: []string{"123"}, + }, + { + ID: uuid.New(), + PersonID: lo.ToPtr(userID), + Object: "*", + Action: policy.ActionRead, + Tags: map[string]string{ + "namespace": "default", + }, + }, + } + + enforcer, err := NewEnforcer(policies) + if err != nil { + t.Fatal(err) + } + + for _, p := range permissions { + for _, policy := range adapter.PermissionToCasbinRule(p) { + if ok, err := enforcer.AddPolicy(policy[1:]); err != nil || !ok { + t.Fatal() + } + } + } + + testData := []struct { + description string + user string + obj any + act string + allowed bool + }{ + { + description: "simple | allow", + user: "johndoe", + obj: &models.ABACAttribute{Playbook: models.Playbook{Name: "scale-deployment"}}, + act: "playbook:run", + allowed: true, + }, + { + description: "simple | explicit deny", + user: "johndoe", + obj: &models.ABACAttribute{Playbook: models.Playbook{Name: "delete-deployment"}}, + act: "playbook:run", + allowed: false, + }, + { + description: "simple | default deny", + user: "johndoe", + obj: &models.ABACAttribute{Playbook: models.Playbook{Name: "delete-namespace"}}, + act: "playbook:run", + allowed: false, + }, + { + description: "multi | allow", + user: "johndoe", + obj: &models.ABACAttribute{ + Playbook: models.Playbook{ + Name: "restart-deployment", + }, + Config: models.ConfigItem{ + ID: uuid.New(), + Tags: map[string]string{"namespace": "default"}, + }, + }, + act: "playbook:run", + allowed: true, + }, + { + description: "multi | explicit deny", + user: "alice", + obj: &models.ABACAttribute{ + Playbook: models.Playbook{ + Name: "restart-deployment", + }, + Config: models.ConfigItem{ + ID: uuid.New(), + Tags: map[string]string{"namespace": "default"}, + }, + }, + act: "playbook:run", + allowed: false, + }, + { + description: "simple read test", + user: userID.String(), + obj: "catalog", + act: "read", + allowed: false, + }, + { + description: "abac catalog test", + user: userID.String(), + obj: &models.ABACAttribute{Config: models.ConfigItem{ID: uuid.New(), Tags: map[string]string{"namespace": "default"}}}, + act: "read", + allowed: true, + }, + } + + for _, td := range testData { + t.Run(td.description, func(t *testing.T) { + user := td.user + obj := td.obj + act := td.act + + allowed, err := enforcer.Enforce(user, obj, act) + if err != nil { + t.Fatal(err) + } + + if allowed != td.allowed { + t.Errorf("expected %t but got %t. user=%s, obj=%v, act=%s", td.allowed, allowed, user, obj, act) + } + }) + } +} diff --git a/rbac/types/types.go b/rbac/types/types.go new file mode 100644 index 00000000..d0ebea0c --- /dev/null +++ b/rbac/types/types.go @@ -0,0 +1,68 @@ +package types + +import ( + "github.com/flanksource/duty/rbac/policy" + "github.com/flanksource/duty/types" +) + +// +kubebuilder:object:generate=true +type PermissionGroupSubjects struct { + Notifications []PermissionGroupSelector `json:"notifications,omitempty"` + People []string `json:"people,omitempty"` + Teams []string `json:"teams,omitempty"` +} + +// +kubebuilder:object:generate=true +type PermissionGroupSelector struct { + Namespace string `json:"namespace,omitempty"` + Name string `json:"name,omitempty"` +} + +func (t PermissionGroupSelector) Empty() bool { + return t.Name == "" && t.Namespace == "" +} + +type PermissionObject struct { + Playbooks []types.ResourceSelector `json:"playbooks,omitempty"` + Configs []types.ResourceSelector `json:"configs,omitempty"` + Components []types.ResourceSelector `json:"components,omitempty"` +} + +// GlobalObject checks if the object selector semantically maps to a global object +// and returns the corresponding global object if applicable. +// For example: +// +// configs: +// - name: '*' +// +// is interpreted as the object: catalog. +func (t *PermissionObject) GlobalObject() (string, bool) { + if len(t.Playbooks) == 1 && len(t.Configs) == 0 && len(t.Components) == 0 && t.Playbooks[0].Wildcard() { + return policy.ObjectPlaybooks, true + } + + if len(t.Configs) == 1 && len(t.Playbooks) == 0 && len(t.Components) == 0 && t.Configs[0].Wildcard() { + return policy.ObjectCatalog, true + } + + if len(t.Components) == 1 && len(t.Playbooks) == 0 && len(t.Configs) == 0 && t.Components[0].Wildcard() { + return policy.ObjectTopology, true + } + + return "", false +} + +func (t PermissionObject) RequiredMatchCount() int { + var count int + if len(t.Playbooks) > 0 { + count++ + } + if len(t.Configs) > 0 { + count++ + } + if len(t.Components) > 0 { + count++ + } + + return count +}