From 2694c82d97005ca39f29f540e26249c18a21f6d6 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 18 Jun 2025 17:11:42 -0600 Subject: refactor: switch to a pure rust implementation --- .gitignore | 1 + .gitlab-ci.yml | 4 +- .runway/runway.yml | 2 +- .tool-versions | 5 - Cargo.lock | 813 ++++++++++++++++++++++++++++ Cargo.toml | 9 + Dockerfile | 13 +- Makefile | 28 +- README.md | 12 +- app/app.go | 31 -- app/app_test.go | 52 -- app/init.go | 15 - app/server.go | 24 - app/server_test.go | 63 --- app/services/ability.go | 27 - app/services/check.go | 72 --- app/services/check_test.go | 95 ---- cmd/authzd/main.go | 16 - go.mod | 48 -- go.sum | 102 ---- magefile.go | 55 -- mise.toml | 5 + pkg/.keep | 0 pkg/gid/gid.go | 36 -- pkg/gid/gid_test.go | 38 -- pkg/policies/allowed.go | 29 - pkg/policies/allowed_test.go | 146 ------ pkg/policies/entities.json | 286 ---------- pkg/policies/gtwy.cedar | 12 - pkg/policies/init.go | 60 --- pkg/policies/organization.cedar | 5 - pkg/rpc/ability.pb.go | 194 ------- pkg/rpc/ability.twirp.go | 1104 --------------------------------------- protos/ability.proto | 19 - src/main.rs | 44 ++ tmp/cache/.keep | 0 tmp/pids/.keep | 0 37 files changed, 899 insertions(+), 2566 deletions(-) delete mode 100644 .tool-versions create mode 100644 Cargo.lock create mode 100644 Cargo.toml delete mode 100644 app/app.go delete mode 100644 app/app_test.go delete mode 100644 app/init.go delete mode 100644 app/server.go delete mode 100644 app/server_test.go delete mode 100644 app/services/ability.go delete mode 100644 app/services/check.go delete mode 100644 app/services/check_test.go delete mode 100644 cmd/authzd/main.go delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 magefile.go create mode 100644 mise.toml delete mode 100644 pkg/.keep delete mode 100644 pkg/gid/gid.go delete mode 100644 pkg/gid/gid_test.go delete mode 100644 pkg/policies/allowed.go delete mode 100644 pkg/policies/allowed_test.go delete mode 100644 pkg/policies/entities.json delete mode 100644 pkg/policies/gtwy.cedar delete mode 100644 pkg/policies/init.go delete mode 100644 pkg/policies/organization.cedar delete mode 100644 pkg/rpc/ability.pb.go delete mode 100644 pkg/rpc/ability.twirp.go delete mode 100644 protos/ability.proto create mode 100644 src/main.rs delete mode 100644 tmp/cache/.keep delete mode 100644 tmp/pids/.keep diff --git a/.gitignore b/.gitignore index 1afff66a..6bfb8923 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ tmp *.pdf +/target diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8d6d4ef3..25648846 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,7 +32,7 @@ build image: - docker push $CONTAINER_IMAGE_COMMIT unit: - image: golang:alpine + image: rust:latest stage: test script: - - go test ./... + - cargo test diff --git a/.runway/runway.yml b/.runway/runway.yml index 43be4694..1eac7654 100644 --- a/.runway/runway.yml +++ b/.runway/runway.yml @@ -7,7 +7,7 @@ metadata: owner_email_handle: mkhan product_category: authorization spec: - container_port: 80 + container_port: 50051 deployment: strategy: "expedited" regions: diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index eb4df2a3..00000000 --- a/.tool-versions +++ /dev/null @@ -1,5 +0,0 @@ -cargo latest -golang 1.24.0 -make 4.4.1 -protoc 3.19.6 -rust stable diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..9f9300df --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,813 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "authzd" +version = "0.1.0" +dependencies = [ + "envoy-types", + "tokio", + "tonic", +] + +[[package]] +name = "axum" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "envoy-types" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065b6b0018b25902cab074d44c0e2098205329b6b5a309a33cc688bc0ac9573d" +dependencies = [ + "futures-core", + "prost", + "tonic", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.59.0", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "syn" +version = "2.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "tokio" +version = "1.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tonic" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" +dependencies = [ + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..5d71a4ae --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "authzd" +version = "0.1.0" +edition = "2024" + +[dependencies] +envoy-types = "0.6.0" +tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } +tonic = "*" diff --git a/Dockerfile b/Dockerfile index d4e2b99d..3bb0e723 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,13 @@ # syntax=docker/dockerfile:1 -FROM golang:1.24.0 AS build -ENV CGO_ENABLED=0 +FROM rust:latest AS builder WORKDIR /app +RUN rustup target add x86_64-unknown-linux-musl COPY . ./ -RUN go build -o authzd ./cmd/authzd/main.go && mv ./authzd /bin/authzd - +RUN cargo build --release --target x86_64-unknown-linux-musl +RUN ls -alh /app FROM scratch -ENV BIND_ADDR=":http" -EXPOSE 80 +EXPOSE 50051 WORKDIR /var/www/ -COPY --from=build /bin/authzd /bin/authzd +COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/authzd /bin/authzd CMD ["/bin/authzd"] diff --git a/Makefile b/Makefile index 028279a6..ab0140fe 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,17 @@ -default: install-tools - @mage -l - -install-tools: - @cargo install --keep-going cedar-policy-cli - @go install tool - @command -v cedar - @command -v mage - @command -v protoc-gen-go - @command -v protoc-gen-go-grpc - @command -v protoc-gen-twirp_ruby +PROJECT_NAME := $(shell basename $(shell pwd)) +GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD | sed 's/\//_/g') +IMAGE_TAG := $(PROJECT_NAME):$(GIT_BRANCH) + +.PHONY: clean run-image + +.PHONY: clean +clean: + @cargo clean + +.PHONY: build-image +build-image: + @docker build --no-cache --tag $(IMAGE_TAG) . + +.PHONY: run-image +run-image: build-image + @docker run --rm -p 50051:50051 -it $(IMAGE_TAG) diff --git a/README.md b/README.md index 6b414a9f..9f455f3d 100644 --- a/README.md +++ b/README.md @@ -52,28 +52,18 @@ It integrates with an identity provider (IdP) and uses message queues to stay in ```sh $ mise install - $ make install-tools ``` 1. Start servers: ```sh - $ mage servers + $ cargo run ``` -## Questions - -See the [FAQ][9] - ## References -* [go tool][5] * [gRPC][6] * [protocol buffers][7] -* [twirp][8] -[1]: https://github.com/twitchtv/twirp -[5]: https://tip.golang.org/doc/modules/managing-dependencies#tools [6]: https://grpc.io/docs/ [7]: https://protobuf.dev/programming-guides/proto3/ -[8]: https://github.com/arthurnn/twirp-ruby/wiki/Code-Generation diff --git a/app/app.go b/app/app.go deleted file mode 100644 index f79b67b1..00000000 --- a/app/app.go +++ /dev/null @@ -1,31 +0,0 @@ -package app - -import ( - "net/http" - - "github.com/rs/zerolog" - "github.com/xlgmokha/x/pkg/ioc" - "github.com/xlgmokha/x/pkg/log" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/app/services" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/rpc" -) - -func New() http.Handler { - mux := http.NewServeMux() - for _, handler := range handlers() { - mux.Handle(handler.PathPrefix(), handler) - } - - mux.Handle("/health", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - })) - - logger := ioc.MustResolve[*zerolog.Logger](ioc.Default) - return log.HTTP(logger)(mux) -} - -func handlers() []rpc.TwirpServer { - return []rpc.TwirpServer{ - rpc.NewAbilityServer(services.NewAbilityService()), - } -} diff --git a/app/app_test.go b/app/app_test.go deleted file mode 100644 index f0068e87..00000000 --- a/app/app_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package app - -import ( - http "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/rpc" -) - -func TestApp(t *testing.T) { - handler := New() - srv := httptest.NewServer(handler) - defer srv.Close() - - t.Run("Ability.Allowed", func(t *testing.T) { - client := rpc.NewAbilityProtobufClient(srv.URL, &http.Client{}) - - t.Run("forbids", func(t *testing.T) { - reply, err := client.Allowed(t.Context(), &rpc.AllowRequest{ - Subject: "", - Permission: "", - Resource: "", - }) - require.NoError(t, err) - assert.False(t, reply.Result) - }) - - t.Run("allows gid://User/1 read gid://Organization/2", func(t *testing.T) { - reply, err := client.Allowed(t.Context(), &rpc.AllowRequest{ - Subject: "gid://example/User/1", - Permission: "read", - Resource: "gid://example/Organization/2", - }) - require.NoError(t, err) - assert.True(t, reply.Result) - }) - }) - - t.Run("GET /health", func(t *testing.T) { - t.Run("returns OK", func(t *testing.T) { - r := httptest.NewRequest("GET", "/health", nil) - w := httptest.NewRecorder() - - handler.ServeHTTP(w, r) - - assert.Equal(t, http.StatusOK, w.Code) - }) - }) -} diff --git a/app/init.go b/app/init.go deleted file mode 100644 index 3c4e757e..00000000 --- a/app/init.go +++ /dev/null @@ -1,15 +0,0 @@ -package app - -import ( - "os" - - "github.com/rs/zerolog" - "github.com/xlgmokha/x/pkg/ioc" - "github.com/xlgmokha/x/pkg/log" -) - -func init() { - ioc.RegisterSingleton[*zerolog.Logger](ioc.Default, func() *zerolog.Logger { - return log.New(os.Stdout, log.Fields{"app": "authzd"}) - }) -} diff --git a/app/server.go b/app/server.go deleted file mode 100644 index 3ce0aadc..00000000 --- a/app/server.go +++ /dev/null @@ -1,24 +0,0 @@ -package app - -import ( - "context" - - auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/app/services" - "google.golang.org/grpc" - "google.golang.org/grpc/reflection" -) - -type Server struct { - *grpc.Server -} - -func NewServer(ctx context.Context, options ...grpc.ServerOption) *Server { - server := grpc.NewServer(options...) - auth.RegisterAuthorizationServer(server, services.NewCheckService()) - reflection.Register(server) - - return &Server{ - Server: server, - } -} diff --git a/app/server_test.go b/app/server_test.go deleted file mode 100644 index ff34487a..00000000 --- a/app/server_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package app - -import ( - "context" - "net" - "testing" - - auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/test/bufconn" -) - -type HTTPRequest = auth.AttributeContext_HttpRequest - -func TestServer(t *testing.T) { - socket := bufconn.Listen(1024 * 1024) - srv := NewServer(t.Context()) - - defer srv.GracefulStop() - go func() { - require.NoError(t, srv.Serve(socket)) - }() - - connection, err := grpc.DialContext( - t.Context(), - "bufnet", - grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { - return socket.Dial() - }), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - require.NoError(t, err) - defer connection.Close() - - client := auth.NewAuthorizationClient(connection) - - t.Run("CheckRequest", func(t *testing.T) { - tt := []struct { - http *HTTPRequest - status codes.Code - }{ - {status: codes.OK, http: &HTTPRequest{Method: "GET", Path: "/"}}, - } - - for _, example := range tt { - t.Run(example.http.Path, func(t *testing.T) { - response, err := client.Check(t.Context(), &auth.CheckRequest{ - Attributes: &auth.AttributeContext{ - Request: &auth.AttributeContext_Request{ - Http: example.http, - }, - }, - }) - require.NoError(t, err) - assert.Equal(t, int32(example.status), response.Status.Code) - }) - } - }) -} diff --git a/app/services/ability.go b/app/services/ability.go deleted file mode 100644 index f0379513..00000000 --- a/app/services/ability.go +++ /dev/null @@ -1,27 +0,0 @@ -package services - -import ( - context "context" - - "github.com/cedar-policy/cedar-go" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/gid" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/policies" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/rpc" -) - -type AbilityService struct { -} - -func NewAbilityService() *AbilityService { - return &AbilityService{} -} - -func (h *AbilityService) Allowed(ctx context.Context, req *rpc.AllowRequest) (*rpc.AllowReply, error) { - ok := policies.Allowed(ctx, cedar.Request{ - Principal: gid.NewEntityUID(req.Subject), - Action: cedar.NewEntityUID("Permission", cedar.String(req.Permission)), - Resource: gid.NewEntityUID(req.Resource), - Context: cedar.NewRecord(cedar.RecordMap{}), - }) - return &rpc.AllowReply{Result: ok}, nil -} diff --git a/app/services/check.go b/app/services/check.go deleted file mode 100644 index 23deecb9..00000000 --- a/app/services/check.go +++ /dev/null @@ -1,72 +0,0 @@ -package services - -import ( - "context" - - "github.com/cedar-policy/cedar-go" - core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" - types "github.com/envoyproxy/go-control-plane/envoy/type/v3" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/gid" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/policies" - status "google.golang.org/genproto/googleapis/rpc/status" - "google.golang.org/grpc/codes" -) - -type CheckService struct { - auth.UnimplementedAuthorizationServer -} - -func NewCheckService() *CheckService { - return &CheckService{} -} - -func (svc *CheckService) Check(ctx context.Context, request *auth.CheckRequest) (*auth.CheckResponse, error) { - if svc.isAllowed(ctx, request) { - return svc.OK(ctx), nil - } - return svc.Denied(ctx), nil -} - -func (svc *CheckService) isAllowed(ctx context.Context, r *auth.CheckRequest) bool { - return policies.Allowed(ctx, cedar.Request{ - Principal: gid.NewEntityUID("gid://gitlab/User/*"), - Action: cedar.NewEntityUID("HttpMethod", cedar.String(r.Attributes.Request.Http.Method)), - Resource: cedar.NewEntityUID("HttpPath", cedar.String(r.Attributes.Request.Http.Path)), - Context: cedar.NewRecord(cedar.RecordMap{ - "host": cedar.String(r.Attributes.Request.Http.Host), - }), - }) - -} - -func (svc *CheckService) OK(ctx context.Context) *auth.CheckResponse { - return &auth.CheckResponse{ - Status: &status.Status{ - Code: int32(codes.OK), - }, - HttpResponse: &auth.CheckResponse_OkResponse{ - OkResponse: &auth.OkHttpResponse{ - Headers: []*core.HeaderValueOption{}, - HeadersToRemove: []string{}, - ResponseHeadersToAdd: []*core.HeaderValueOption{}, - }, - }, - } -} - -func (svc *CheckService) Denied(ctx context.Context) *auth.CheckResponse { - return &auth.CheckResponse{ - Status: &status.Status{ - Code: int32(codes.PermissionDenied), - }, - HttpResponse: &auth.CheckResponse_DeniedResponse{ - DeniedResponse: &auth.DeniedHttpResponse{ - Status: &types.HttpStatus{ - Code: types.StatusCode_Unauthorized, - }, - Headers: []*core.HeaderValueOption{}, - }, - }, - } -} diff --git a/app/services/check_test.go b/app/services/check_test.go deleted file mode 100644 index 4eb396bb..00000000 --- a/app/services/check_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package services - -import ( - "strings" - "testing" - - core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/timestamppb" -) - -func TestCheckService(t *testing.T) { - svc := NewCheckService() - - t.Run("allows access", func(t *testing.T) { - idToken := "header.payload.signature" - accessToken := "f88f60df11e458b594c80b299aee05f8e5805c65c3e779cc6fbc606c4ac36227" - refreshToken := "0847d325d6e4f021c4baaae0ddb425dbd8795807a4751cd2131bec8e8a9aee24" - - cookies := []string{ - "bearer_token=" + accessToken + ";", - "id_token=" + idToken + ";", - "refresh_token=" + refreshToken, - } - - response, err := svc.Check(t.Context(), &auth.CheckRequest{ - Attributes: &auth.AttributeContext{ - Source: &auth.AttributeContext_Peer{ - Address: &core.Address{ - Address: &core.Address_SocketAddress{ - SocketAddress: &core.SocketAddress{ - Address: "127.0.0.1", - PortSpecifier: &core.SocketAddress_PortValue{ - PortValue: 52358, - }, - }, - }, - }, - }, - Destination: &auth.AttributeContext_Peer{ - Address: &core.Address{ - Address: &core.Address_SocketAddress{ - SocketAddress: &core.SocketAddress{ - Address: "127.0.0.1", - PortSpecifier: &core.SocketAddress_PortValue{ - PortValue: 10000, - }, - }, - }, - }, - }, - Request: &auth.AttributeContext_Request{ - Time: ×tamppb.Timestamp{Seconds: 1747937928, Nanos: 476481000}, - Http: &auth.AttributeContext_HttpRequest{ - Id: "1248474133684962828", - Method: "GET", - Headers: map[string]string{ - ":authority": "localhost:10000", - ":method": "GET", - ":path": "/health", - ":scheme": "http", - "accept": "*/*", - "accept-encoding": "gzip, deflate, br, zstd", - "accept-language": "en-US,en;q=0.9", - "cache-control": "max-age=0", - "content-length": "64", - "content-type": "application/json", - "cookie": strings.Join(cookies, "; "), - "origin": "http://localhost:10000", - "referer": "http://localhost:10000/dashboard", - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": "Linux", - "sec-fetch-dest": "empty", - "sec-fetch-mode": "cors", - "sec-fetch-site": "same-origin", - "x-forwarded-proto": "http", - "x-request-id": "7e064610-9e19-4a38-8354-0de0b5fbd7c6", - }, - Path: "/health", - Host: "localhost:10000", - Scheme: "http", - Protocol: "HTTP/1.1", - }, - }, - MetadataContext: &core.Metadata{}, - RouteMetadataContext: &core.Metadata{}, - }, - }) - - require.NoError(t, err) - assert.NotNil(t, response.GetOkResponse()) - }) -} diff --git a/cmd/authzd/main.go b/cmd/authzd/main.go deleted file mode 100644 index 05b0b148..00000000 --- a/cmd/authzd/main.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - - "github.com/xlgmokha/x/pkg/env" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/app" -) - -func main() { - bindAddr := env.Fetch("BIND_ADDR", "localhost:8080") - fmt.Printf("Listening on %v\n", bindAddr) - log.Fatal(http.ListenAndServe(bindAddr, app.New())) -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 0da45df9..00000000 --- a/go.mod +++ /dev/null @@ -1,48 +0,0 @@ -module gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git - -go 1.24 - -require ( - github.com/cedar-policy/cedar-go v1.2.1 - github.com/envoyproxy/go-control-plane/envoy v1.32.4 - github.com/magefile/mage v1.15.0 - github.com/rs/zerolog v1.34.0 - github.com/stretchr/testify v1.10.0 - github.com/twitchtv/twirp v8.1.3+incompatible - github.com/xlgmokha/x v0.0.0-20250430185455-b691eda27477 - google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 - google.golang.org/grpc v1.71.0 - google.golang.org/protobuf v1.36.6 -) - -require ( - github.com/arthurnn/twirp-ruby v1.13.0 // indirect - github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/golobby/container/v3 v3.3.2 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect - golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect - golang.org/x/net v0.37.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/text v0.24.0 // indirect - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) - -tool ( - github.com/arthurnn/twirp-ruby/protoc-gen-twirp_ruby - github.com/magefile/mage - github.com/twitchtv/twirp/protoc-gen-twirp - google.golang.org/grpc/cmd/protoc-gen-go-grpc - google.golang.org/protobuf/cmd/protoc-gen-go -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 0a713aca..00000000 --- a/go.sum +++ /dev/null @@ -1,102 +0,0 @@ -github.com/arthurnn/twirp-ruby v1.13.0 h1:j0T7I5oxe2niKFdfjiiCmkiydwYeegrbwVMs+Gajm6M= -github.com/arthurnn/twirp-ruby v1.13.0/go.mod h1:1fVOQuSLzwXoPi9/ejlDYG3roilJIPAZN2sw+A3o48o= -github.com/cedar-policy/cedar-go v1.2.1 h1:f4Ie1j6OT2TqtmnbVcR7OyJWfyPEUvYrFYaurQZhTAo= -github.com/cedar-policy/cedar-go v1.2.1/go.mod h1:Ahhlu3DxDzvGTR88v/+j/EeiMEyvta11hlkdDgj4AZU= -github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q= -github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= -github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golobby/container/v3 v3.3.2 h1:7u+RgNnsdVlhGoS8gY4EXAG601vpMMzLZlYqSp77Quw= -github.com/golobby/container/v3 v3.3.2/go.mod h1:RDdKpnKpV1Of11PFBe7Dxc2C1k2KaLE4FD47FflAmj0= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonapi v1.0.0 h1:qIGgO5Smu3yJmSs+QlvhQnrscdZfFhiV6S8ryJAglqU= -github.com/google/jsonapi v1.0.0/go.mod h1:YYHiRPJT8ARXGER8In9VuLv4qvLfDmA9ULQqptbLE4s= -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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= -github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= -github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU= -github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= -github.com/xlgmokha/x v0.0.0-20250430185455-b691eda27477 h1:oeKrn9BSDSic5MTXKhQesxhIhB7byxl86IHtCD+yw4k= -github.com/xlgmokha/x v0.0.0-20250430185455-b691eda27477/go.mod h1:axGPKzoJCNTmPJxYqN5l+Z9gGbPe0yolkT61a5p3QiI= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= -golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/magefile.go b/magefile.go deleted file mode 100644 index 49cd4c87..00000000 --- a/magefile.go +++ /dev/null @@ -1,55 +0,0 @@ -//go:build mage -// +build mage - -package main - -import ( - "context" - "path/filepath" - - "github.com/magefile/mage/mg" - "github.com/magefile/mage/sh" - "github.com/xlgmokha/x/pkg/x" -) - -// Default target to run when none is specified -// If not set, running mage will list available targets -var Default = Servers - -// Run the Authzd Service -func Authzd() error { - env := map[string]string{ - "BIND_ADDR": ":8080", - } - return sh.RunWithV(env, "go", "run", "./cmd/authzd/main.go") -} - -// Generate gRPC from protocal buffers -func Protos() error { - for _, file := range x.Must(filepath.Glob("./protos/*.proto")) { - if err := sh.RunV( - "protoc", - "--proto_path=./protos", - "--go_out=.", - "--twirp_out=.", - file, - ); err != nil { - return err - } - } - - return nil -} - -// Run All the servers -func Servers(ctx context.Context) { - mg.CtxDeps(ctx, Authzd) -} - -// Run the end to end tests -func Test(ctx context.Context) error { - mg.CtxDeps(ctx, func() error { - return sh.RunV("go", "clean", "-testcache") - }) - return sh.RunV("go", "test", "-shuffle=on", "-v", "./...") -} diff --git a/mise.toml b/mise.toml new file mode 100644 index 00000000..87a29b17 --- /dev/null +++ b/mise.toml @@ -0,0 +1,5 @@ +[tools] +cargo = "latest" +make = "latest" +rust = "stable" +yamlfmt = "latest" diff --git a/pkg/.keep b/pkg/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/pkg/gid/gid.go b/pkg/gid/gid.go deleted file mode 100644 index 7d1ea978..00000000 --- a/pkg/gid/gid.go +++ /dev/null @@ -1,36 +0,0 @@ -package gid - -import ( - "net/url" - "strings" - - "github.com/cedar-policy/cedar-go" -) - -func NewEntityUID(globalID string) cedar.EntityUID { - if !strings.HasPrefix(globalID, "gid://") { - return DefaultEntityUID(globalID) - } - - url, err := url.Parse(globalID) - if err != nil { - return DefaultEntityUID(globalID) - } - items := strings.SplitN(url.Path, "/", 3) - if len(items) != 3 { - return DefaultEntityUID(globalID) - } - - return cedar.NewEntityUID( - cedar.EntityType(items[1]), - cedar.String(items[2]), - ) -} - -func DefaultEntityUID(id string) cedar.EntityUID { - return cedar.NewEntityUID("User", cedar.String(id)) -} - -func ZeroEntityUID() cedar.EntityUID { - return cedar.NewEntityUID("", "") -} diff --git a/pkg/gid/gid_test.go b/pkg/gid/gid_test.go deleted file mode 100644 index e1f6285b..00000000 --- a/pkg/gid/gid_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package gid - -import ( - "testing" - - "github.com/cedar-policy/cedar-go" - "github.com/stretchr/testify/assert" -) - -func TestNewEntityUID(t *testing.T) { - t.Run("returns an Entity UID with an integer id", func(t *testing.T) { - result := NewEntityUID("gid://example/User/1") - - assert.Equal(t, cedar.EntityType("User"), result.Type) - assert.Equal(t, cedar.String("1"), result.ID) - }) - - t.Run("returns an Entity UID with a UUID", func(t *testing.T) { - result := NewEntityUID("gid://example/User/4707ce42-1017-11f0-acdf-7ec11f4b308c") - - assert.Equal(t, cedar.EntityType("User"), result.Type) - assert.Equal(t, cedar.String("4707ce42-1017-11f0-acdf-7ec11f4b308c"), result.ID) - }) - - t.Run("returns an Entity UID with a namespace", func(t *testing.T) { - result := NewEntityUID("gid://example/Authn::User/1") - - assert.Equal(t, cedar.EntityType("Authn::User"), result.Type) - assert.Equal(t, cedar.String("1"), result.ID) - }) - - t.Run("returns a default when a global id is not provided", func(t *testing.T) { - result := NewEntityUID("alice") - - assert.Equal(t, cedar.EntityType("User"), result.Type) - assert.Equal(t, cedar.String("alice"), result.ID) - }) -} diff --git a/pkg/policies/allowed.go b/pkg/policies/allowed.go deleted file mode 100644 index 733c08b8..00000000 --- a/pkg/policies/allowed.go +++ /dev/null @@ -1,29 +0,0 @@ -package policies - -import ( - "context" - - "github.com/cedar-policy/cedar-go" - "github.com/cedar-policy/cedar-go/types" - "github.com/xlgmokha/x/pkg/log" -) - -func Allowed(ctx context.Context, request cedar.Request) bool { - ok, diagnostic := All.IsAuthorized(Entities, request) - - log.WithFields(ctx, log.Fields{ - "ok": ok, - "principal": request.Principal, - "action": request.Action, - "context": request.Context, - "resource": request.Resource, - }) - - if len(diagnostic.Errors) > 0 { - log.WithFields(ctx, log.Fields{"errors": diagnostic.Errors}) - } - if len(diagnostic.Reasons) > 0 { - log.WithFields(ctx, log.Fields{"reasons": diagnostic.Reasons}) - } - return ok == types.Allow -} diff --git a/pkg/policies/allowed_test.go b/pkg/policies/allowed_test.go deleted file mode 100644 index 367bd99f..00000000 --- a/pkg/policies/allowed_test.go +++ /dev/null @@ -1,146 +0,0 @@ -package policies - -import ( - "fmt" - "testing" - - "github.com/cedar-policy/cedar-go" - "github.com/stretchr/testify/assert" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/gid" -) - -func build(f func(*cedar.Request)) *cedar.Request { - request := &cedar.Request{ - Principal: gid.NewEntityUID("gid://example/User/1"), - Action: cedar.NewEntityUID("HttpMethod", "GET"), - Resource: cedar.NewEntityUID("HttpPath", "/"), - Context: cedar.NewRecord(cedar.RecordMap{ - "host": cedar.String("example.com"), - }), - } - f(request) - return request -} - -func TestAllowed(t *testing.T) { - allowed := []*cedar.Request{ - build(func(r *cedar.Request) {}), - build(func(r *cedar.Request) { - r.Principal = gid.NewEntityUID("gid://example/User/1") - r.Action = cedar.NewEntityUID("HttpMethod", "POST") - }), - build(func(r *cedar.Request) { - r.Principal = gid.NewEntityUID("gid://example/User/1") - r.Action = cedar.NewEntityUID("HttpMethod", "PUT") - }), - build(func(r *cedar.Request) { - r.Principal = gid.NewEntityUID("gid://example/User/1") - r.Action = cedar.NewEntityUID("HttpMethod", "PATCH") - }), - build(func(r *cedar.Request) { - r.Principal = gid.NewEntityUID("gid://example/User/1") - r.Action = cedar.NewEntityUID("HttpMethod", "DELETE") - }), - build(func(r *cedar.Request) { - r.Principal = gid.NewEntityUID("gid://example/User/1") - r.Action = cedar.NewEntityUID("HttpMethod", "HEAD") - }), - build(func(r *cedar.Request) { - r.Principal = gid.NewEntityUID("gid://example/User/1") - r.Resource = cedar.NewEntityUID("HttpPath", "/organizations.json") - r.Context = cedar.NewRecord(cedar.RecordMap{ - "host": cedar.String("api.example.com"), - }) - }), - build(func(r *cedar.Request) { - r.Principal = gid.NewEntityUID("gid://example/User/1") - r.Resource = cedar.NewEntityUID("HttpPath", "/groups.json") - r.Context = cedar.NewRecord(cedar.RecordMap{ - "host": cedar.String("api.example.com"), - }) - }), - build(func(r *cedar.Request) { - r.Principal = gid.NewEntityUID("gid://example/User/1") - r.Resource = cedar.NewEntityUID("HttpPath", "/.well-known/openid-configuration") - r.Context = cedar.NewRecord(cedar.RecordMap{ - "host": cedar.String("idp.example.com"), - }) - }), - build(func(r *cedar.Request) { - r.Principal = gid.NewEntityUID("gid://example/User/1") - r.Resource = cedar.NewEntityUID("HttpPath", "/.well-known/oauth-authorization-server") - r.Context = cedar.NewRecord(cedar.RecordMap{ - "host": cedar.String("idp.example.com"), - }) - }), - build(func(r *cedar.Request) { - r.Principal = gid.NewEntityUID("gid://example/User/*") - r.Resource = cedar.NewEntityUID("HttpPath", "/.well-known/openid-configuration") - r.Context = cedar.NewRecord(cedar.RecordMap{ - "host": cedar.String("idp.example.com"), - }) - }), - build(func(r *cedar.Request) { - r.Principal = gid.NewEntityUID("gid://example/User/*") - r.Resource = cedar.NewEntityUID("HttpPath", "/.well-known/oauth-authorization-server") - r.Context = cedar.NewRecord(cedar.RecordMap{ - "host": cedar.String("idp.example.com"), - }) - }), - build(func(r *cedar.Request) { - r.Principal = gid.NewEntityUID("gid://example/User/1") - r.Action = cedar.NewEntityUID("HttpMethod", "POST") - r.Resource = cedar.NewEntityUID("HttpPath", "/twirp/authz.rpc.Ability/Allowed") - r.Context = cedar.NewRecord(cedar.RecordMap{ - "host": cedar.String("idp.example.com"), - }) - }), - build(func(r *cedar.Request) { - r.Principal = gid.NewEntityUID("gid://example/User/1") - r.Action = cedar.NewEntityUID("HttpMethod", "GET") - r.Resource = cedar.NewEntityUID("HttpPath", "/index.html") - r.Context = cedar.NewRecord(cedar.RecordMap{ - "host": cedar.String("ui.example.com"), - }) - }), - } - - for _, tt := range allowed { - t.Run(fmt.Sprintf("allows: %v/%v %v %v%v", tt.Principal.Type, tt.Principal.ID, tt.Action.ID, tt.Context.Map()["host"], tt.Resource.ID), func(t *testing.T) { - assert.True(t, Allowed(t.Context(), *tt)) - }) - } - - denied := []*cedar.Request{ - build(func(r *cedar.Request) { - r.Principal = gid.ZeroEntityUID() - r.Action = cedar.NewEntityUID("HttpMethod", cedar.String("POST")) - }), - build(func(r *cedar.Request) { - r.Principal = gid.ZeroEntityUID() - r.Action = cedar.NewEntityUID("HttpMethod", cedar.String("PUT")) - }), - build(func(r *cedar.Request) { - r.Principal = gid.ZeroEntityUID() - r.Action = cedar.NewEntityUID("HttpMethod", cedar.String("PATCH")) - }), - build(func(r *cedar.Request) { - r.Principal = gid.ZeroEntityUID() - r.Action = cedar.NewEntityUID("HttpMethod", cedar.String("DELETE")) - }), - build(func(r *cedar.Request) { - r.Principal = gid.ZeroEntityUID() - r.Action = cedar.NewEntityUID("HttpMethod", cedar.String("HEAD")) - }), - build(func(r *cedar.Request) { - r.Principal = gid.ZeroEntityUID() - r.Action = cedar.NewEntityUID("HttpMethod", cedar.String("TRACE")) - }), - } - - for _, tt := range denied { - t.Run(fmt.Sprintf("denies: %v/%v %v %v%v", tt.Principal.Type, tt.Principal.ID, tt.Action.ID, tt.Context.Map()["host"], tt.Resource.ID), func(t *testing.T) { - assert.False(t, Allowed(t.Context(), *tt)) - }) - } -} diff --git a/pkg/policies/entities.json b/pkg/policies/entities.json deleted file mode 100644 index 8d50e674..00000000 --- a/pkg/policies/entities.json +++ /dev/null @@ -1,286 +0,0 @@ -[ - { - "uid": { - "type": "User", - "id": "1" - }, - "attrs": {}, - "parents": [] - }, - { - "uid": { - "type": "Organization", - "id": "1" - }, - "attrs": { - "name": "default" - }, - "parents": [] - }, - { - "uid": { - "type": "Organization", - "id": "2" - }, - "attrs": { - "name": "gitlab" - }, - "parents": [] - }, - { - "uid": { - "type": "Group", - "id": "1" - }, - "attrs": { - "name": "A" - }, - "parents": [ - { - "type": "Organization", - "id": "1" - } - ] - }, - { - "uid": { - "type": "Group", - "id": "2" - }, - "attrs": { - "name": "B" - }, - "parents": [ - { - "type": "Organization", - "id": "1" - } - ] - }, - { - "uid": { - "type": "Group", - "id": "3" - }, - "attrs": { - "name": "gitlab-org" - }, - "parents": [ - { - "type": "Organization", - "id": "2" - } - ] - }, - { - "uid": { - "type": "Group", - "id": "4" - }, - "attrs": { - "name": "gitlab-com" - }, - "parents": [ - { - "type": "Organization", - "id": "2" - } - ] - }, - { - "uid": { - "type": "Group", - "id": "5" - }, - "attrs": { - "name": "gl-security" - }, - "parents": [ - { - "type": "Organization", - "id": "2" - }, - { - "type": "Group", - "id": "4" - } - ] - }, - { - "uid": { - "type": "Group", - "id": "6" - }, - "attrs": { - "name": "test-projects" - }, - "parents": [ - { - "type": "Organization", - "id": "2" - }, - { - "type": "Group", - "id": "5" - } - ] - }, - { - "uid": { - "type": "Group", - "id": "7" - }, - "attrs": { - "name": "support" - }, - "parents": [ - { - "type": "Organization", - "id": "2" - }, - { - "type": "Group", - "id": "4" - } - ] - }, - { - "uid": { - "type": "Group", - "id": "8" - }, - "attrs": { - "name": "toolbox" - }, - "parents": [ - { - "type": "Organization", - "id": "2" - }, - { - "type": "Group", - "id": "7" - } - ] - }, - { - "uid": { - "type": "Project", - "id": "1" - }, - "attrs": { - "name": "A1" - }, - "parents": [ - { - "type": "Group", - "id": "1" - } - ] - }, - { - "uid": { - "type": "Project", - "id": "2" - }, - "attrs": { - "name": "B1" - }, - "parents": [ - { - "type": "Group", - "id": "2" - } - ] - }, - { - "uid": { - "type": "Project", - "id": "3" - }, - "attrs": { - "name": "gitlab" - }, - "parents": [ - { - "type": "Group", - "id": "3" - } - ] - }, - { - "uid": { - "type": "Project", - "id": "4" - }, - "attrs": { - "name": "eicar-test-project" - }, - "parents": [ - { - "type": "Group", - "id": "6" - } - ] - }, - { - "uid": { - "type": "Project", - "id": "5" - }, - "attrs": { - "name": "disclosures" - }, - "parents": [ - { - "type": "Group", - "id": "5" - } - ] - }, - { - "uid": { - "type": "Project", - "id": "6" - }, - "attrs": { - "name": "changelog-parser" - }, - "parents": [ - { - "type": "Group", - "id": "8" - } - ] - }, - { - "uid": { - "type": "Project", - "id": "7" - }, - "attrs": { - "name": "handbook" - }, - "parents": [ - { - "type": "Group", - "id": "4" - } - ] - }, - { - "uid": { - "type": "Project", - "id": "8" - }, - "attrs": { - "name": "www-gitlab-com" - }, - "parents": [ - { - "type": "Group", - "id": "4" - } - ] - } -] diff --git a/pkg/policies/gtwy.cedar b/pkg/policies/gtwy.cedar deleted file mode 100644 index a236e08b..00000000 --- a/pkg/policies/gtwy.cedar +++ /dev/null @@ -1,12 +0,0 @@ -permit( - principal is User, - action in [ - HttpMethod::"DELETE", - HttpMethod::"GET", - HttpMethod::"HEAD", - HttpMethod::"PATCH", - HttpMethod::"POST", - HttpMethod::"PUT" - ], - resource -); diff --git a/pkg/policies/init.go b/pkg/policies/init.go deleted file mode 100644 index bc270763..00000000 --- a/pkg/policies/init.go +++ /dev/null @@ -1,60 +0,0 @@ -package policies - -import ( - "context" - "embed" - _ "embed" - "io/fs" - "strings" - - "github.com/cedar-policy/cedar-go" - "github.com/xlgmokha/x/pkg/log" -) - -//go:embed *.cedar *.json -var files embed.FS - -var All *cedar.PolicySet = cedar.NewPolicySet() -var Entities cedar.EntityMap = cedar.EntityMap{} - -func init() { - err := fs.WalkDir(files, ".", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - - if d.IsDir() { - return nil - } - - if strings.HasSuffix(path, ".cedar") { - content, err := fs.ReadFile(files, path) - if err != nil { - return err - } - - policy := cedar.Policy{} - if err := policy.UnmarshalCedar(content); err != nil { - return err - } - policy.SetFilename(path) - - All.Add(cedar.PolicyID(path), &policy) - } else if strings.HasSuffix(path, ".json") { - content, err := fs.ReadFile(files, path) - if err != nil { - return err - } - - if err := Entities.UnmarshalJSON(content); err != nil { - return err - } - } - - return nil - }) - - if err != nil { - log.WithFields(context.Background(), log.Fields{"error": err}) - } -} diff --git a/pkg/policies/organization.cedar b/pkg/policies/organization.cedar deleted file mode 100644 index a853f4e4..00000000 --- a/pkg/policies/organization.cedar +++ /dev/null @@ -1,5 +0,0 @@ -permit ( - principal == User::"1", - action == Permission::"read", - resource == Organization::"2" -); diff --git a/pkg/rpc/ability.pb.go b/pkg/rpc/ability.pb.go deleted file mode 100644 index 939719fc..00000000 --- a/pkg/rpc/ability.pb.go +++ /dev/null @@ -1,194 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.6 -// protoc v3.19.6 -// source: ability.proto - -package rpc - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" - unsafe "unsafe" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type AllowRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Subject string `protobuf:"bytes,1,opt,name=subject,proto3" json:"subject,omitempty"` - Permission string `protobuf:"bytes,2,opt,name=permission,proto3" json:"permission,omitempty"` - Resource string `protobuf:"bytes,3,opt,name=resource,proto3" json:"resource,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AllowRequest) Reset() { - *x = AllowRequest{} - mi := &file_ability_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AllowRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AllowRequest) ProtoMessage() {} - -func (x *AllowRequest) ProtoReflect() protoreflect.Message { - mi := &file_ability_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AllowRequest.ProtoReflect.Descriptor instead. -func (*AllowRequest) Descriptor() ([]byte, []int) { - return file_ability_proto_rawDescGZIP(), []int{0} -} - -func (x *AllowRequest) GetSubject() string { - if x != nil { - return x.Subject - } - return "" -} - -func (x *AllowRequest) GetPermission() string { - if x != nil { - return x.Permission - } - return "" -} - -func (x *AllowRequest) GetResource() string { - if x != nil { - return x.Resource - } - return "" -} - -type AllowReply struct { - state protoimpl.MessageState `protogen:"open.v1"` - Result bool `protobuf:"varint,1,opt,name=result,proto3" json:"result,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AllowReply) Reset() { - *x = AllowReply{} - mi := &file_ability_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AllowReply) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AllowReply) ProtoMessage() {} - -func (x *AllowReply) ProtoReflect() protoreflect.Message { - mi := &file_ability_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AllowReply.ProtoReflect.Descriptor instead. -func (*AllowReply) Descriptor() ([]byte, []int) { - return file_ability_proto_rawDescGZIP(), []int{1} -} - -func (x *AllowReply) GetResult() bool { - if x != nil { - return x.Result - } - return false -} - -var File_ability_proto protoreflect.FileDescriptor - -const file_ability_proto_rawDesc = "" + - "\n" + - "\rability.proto\x12\tauthz.rpc\"d\n" + - "\fAllowRequest\x12\x18\n" + - "\asubject\x18\x01 \x01(\tR\asubject\x12\x1e\n" + - "\n" + - "permission\x18\x02 \x01(\tR\n" + - "permission\x12\x1a\n" + - "\bresource\x18\x03 \x01(\tR\bresource\"$\n" + - "\n" + - "AllowReply\x12\x16\n" + - "\x06result\x18\x01 \x01(\bR\x06result2F\n" + - "\aAbility\x12;\n" + - "\aAllowed\x12\x17.authz.rpc.AllowRequest\x1a\x15.authz.rpc.AllowReply\"\x00B\tZ\apkg/rpcb\x06proto3" - -var ( - file_ability_proto_rawDescOnce sync.Once - file_ability_proto_rawDescData []byte -) - -func file_ability_proto_rawDescGZIP() []byte { - file_ability_proto_rawDescOnce.Do(func() { - file_ability_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_ability_proto_rawDesc), len(file_ability_proto_rawDesc))) - }) - return file_ability_proto_rawDescData -} - -var file_ability_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_ability_proto_goTypes = []any{ - (*AllowRequest)(nil), // 0: authz.rpc.AllowRequest - (*AllowReply)(nil), // 1: authz.rpc.AllowReply -} -var file_ability_proto_depIdxs = []int32{ - 0, // 0: authz.rpc.Ability.Allowed:input_type -> authz.rpc.AllowRequest - 1, // 1: authz.rpc.Ability.Allowed:output_type -> authz.rpc.AllowReply - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_ability_proto_init() } -func file_ability_proto_init() { - if File_ability_proto != nil { - return - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_ability_proto_rawDesc), len(file_ability_proto_rawDesc)), - NumEnums: 0, - NumMessages: 2, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_ability_proto_goTypes, - DependencyIndexes: file_ability_proto_depIdxs, - MessageInfos: file_ability_proto_msgTypes, - }.Build() - File_ability_proto = out.File - file_ability_proto_goTypes = nil - file_ability_proto_depIdxs = nil -} diff --git a/pkg/rpc/ability.twirp.go b/pkg/rpc/ability.twirp.go deleted file mode 100644 index f5a33296..00000000 --- a/pkg/rpc/ability.twirp.go +++ /dev/null @@ -1,1104 +0,0 @@ -// Code generated by protoc-gen-twirp v8.1.3, DO NOT EDIT. -// source: ability.proto - -package rpc - -import context "context" -import fmt "fmt" -import http "net/http" -import io "io" -import json "encoding/json" -import strconv "strconv" -import strings "strings" - -import protojson "google.golang.org/protobuf/encoding/protojson" -import proto "google.golang.org/protobuf/proto" -import twirp "github.com/twitchtv/twirp" -import ctxsetters "github.com/twitchtv/twirp/ctxsetters" - -import bytes "bytes" -import errors "errors" -import path "path" -import url "net/url" - -// Version compatibility assertion. -// If the constant is not defined in the package, that likely means -// the package needs to be updated to work with this generated code. -// See https://twitchtv.github.io/twirp/docs/version_matrix.html -const _ = twirp.TwirpPackageMinVersion_8_1_0 - -// ================= -// Ability Interface -// ================= - -type Ability interface { - Allowed(context.Context, *AllowRequest) (*AllowReply, error) -} - -// ======================= -// Ability Protobuf Client -// ======================= - -type abilityProtobufClient struct { - client HTTPClient - urls [1]string - interceptor twirp.Interceptor - opts twirp.ClientOptions -} - -// NewAbilityProtobufClient creates a Protobuf client that implements the Ability interface. -// It communicates using Protobuf and can be configured with a custom HTTPClient. -func NewAbilityProtobufClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) Ability { - if c, ok := client.(*http.Client); ok { - client = withoutRedirects(c) - } - - clientOpts := twirp.ClientOptions{} - for _, o := range opts { - o(&clientOpts) - } - - // Using ReadOpt allows backwards and forwards compatibility with new options in the future - literalURLs := false - _ = clientOpts.ReadOpt("literalURLs", &literalURLs) - var pathPrefix string - if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { - pathPrefix = "/twirp" // default prefix - } - - // Build method URLs: []/./ - serviceURL := sanitizeBaseURL(baseURL) - serviceURL += baseServicePath(pathPrefix, "authz.rpc", "Ability") - urls := [1]string{ - serviceURL + "Allowed", - } - - return &abilityProtobufClient{ - client: client, - urls: urls, - interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), - opts: clientOpts, - } -} - -func (c *abilityProtobufClient) Allowed(ctx context.Context, in *AllowRequest) (*AllowReply, error) { - ctx = ctxsetters.WithPackageName(ctx, "authz.rpc") - ctx = ctxsetters.WithServiceName(ctx, "Ability") - ctx = ctxsetters.WithMethodName(ctx, "Allowed") - caller := c.callAllowed - if c.interceptor != nil { - caller = func(ctx context.Context, req *AllowRequest) (*AllowReply, error) { - resp, err := c.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AllowRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AllowRequest) when calling interceptor") - } - return c.callAllowed(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AllowReply) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AllowReply) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - return caller(ctx, in) -} - -func (c *abilityProtobufClient) callAllowed(ctx context.Context, in *AllowRequest) (*AllowReply, error) { - out := new(AllowReply) - ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -// =================== -// Ability JSON Client -// =================== - -type abilityJSONClient struct { - client HTTPClient - urls [1]string - interceptor twirp.Interceptor - opts twirp.ClientOptions -} - -// NewAbilityJSONClient creates a JSON client that implements the Ability interface. -// It communicates using JSON and can be configured with a custom HTTPClient. -func NewAbilityJSONClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) Ability { - if c, ok := client.(*http.Client); ok { - client = withoutRedirects(c) - } - - clientOpts := twirp.ClientOptions{} - for _, o := range opts { - o(&clientOpts) - } - - // Using ReadOpt allows backwards and forwards compatibility with new options in the future - literalURLs := false - _ = clientOpts.ReadOpt("literalURLs", &literalURLs) - var pathPrefix string - if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { - pathPrefix = "/twirp" // default prefix - } - - // Build method URLs: []/./ - serviceURL := sanitizeBaseURL(baseURL) - serviceURL += baseServicePath(pathPrefix, "authz.rpc", "Ability") - urls := [1]string{ - serviceURL + "Allowed", - } - - return &abilityJSONClient{ - client: client, - urls: urls, - interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), - opts: clientOpts, - } -} - -func (c *abilityJSONClient) Allowed(ctx context.Context, in *AllowRequest) (*AllowReply, error) { - ctx = ctxsetters.WithPackageName(ctx, "authz.rpc") - ctx = ctxsetters.WithServiceName(ctx, "Ability") - ctx = ctxsetters.WithMethodName(ctx, "Allowed") - caller := c.callAllowed - if c.interceptor != nil { - caller = func(ctx context.Context, req *AllowRequest) (*AllowReply, error) { - resp, err := c.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AllowRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AllowRequest) when calling interceptor") - } - return c.callAllowed(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AllowReply) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AllowReply) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - return caller(ctx, in) -} - -func (c *abilityJSONClient) callAllowed(ctx context.Context, in *AllowRequest) (*AllowReply, error) { - out := new(AllowReply) - ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -// ====================== -// Ability Server Handler -// ====================== - -type abilityServer struct { - Ability - interceptor twirp.Interceptor - hooks *twirp.ServerHooks - pathPrefix string // prefix for routing - jsonSkipDefaults bool // do not include unpopulated fields (default values) in the response - jsonCamelCase bool // JSON fields are serialized as lowerCamelCase rather than keeping the original proto names -} - -// NewAbilityServer builds a TwirpServer that can be used as an http.Handler to handle -// HTTP requests that are routed to the right method in the provided svc implementation. -// The opts are twirp.ServerOption modifiers, for example twirp.WithServerHooks(hooks). -func NewAbilityServer(svc Ability, opts ...interface{}) TwirpServer { - serverOpts := newServerOpts(opts) - - // Using ReadOpt allows backwards and forwards compatibility with new options in the future - jsonSkipDefaults := false - _ = serverOpts.ReadOpt("jsonSkipDefaults", &jsonSkipDefaults) - jsonCamelCase := false - _ = serverOpts.ReadOpt("jsonCamelCase", &jsonCamelCase) - var pathPrefix string - if ok := serverOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { - pathPrefix = "/twirp" // default prefix - } - - return &abilityServer{ - Ability: svc, - hooks: serverOpts.Hooks, - interceptor: twirp.ChainInterceptors(serverOpts.Interceptors...), - pathPrefix: pathPrefix, - jsonSkipDefaults: jsonSkipDefaults, - jsonCamelCase: jsonCamelCase, - } -} - -// writeError writes an HTTP response with a valid Twirp error format, and triggers hooks. -// If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) -func (s *abilityServer) writeError(ctx context.Context, resp http.ResponseWriter, err error) { - writeError(ctx, resp, err, s.hooks) -} - -// handleRequestBodyError is used to handle error when the twirp server cannot read request -func (s *abilityServer) handleRequestBodyError(ctx context.Context, resp http.ResponseWriter, msg string, err error) { - if context.Canceled == ctx.Err() { - s.writeError(ctx, resp, twirp.NewError(twirp.Canceled, "failed to read request: context canceled")) - return - } - if context.DeadlineExceeded == ctx.Err() { - s.writeError(ctx, resp, twirp.NewError(twirp.DeadlineExceeded, "failed to read request: deadline exceeded")) - return - } - s.writeError(ctx, resp, twirp.WrapError(malformedRequestError(msg), err)) -} - -// AbilityPathPrefix is a convenience constant that may identify URL paths. -// Should be used with caution, it only matches routes generated by Twirp Go clients, -// with the default "/twirp" prefix and default CamelCase service and method names. -// More info: https://twitchtv.github.io/twirp/docs/routing.html -const AbilityPathPrefix = "/twirp/authz.rpc.Ability/" - -func (s *abilityServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { - ctx := req.Context() - ctx = ctxsetters.WithPackageName(ctx, "authz.rpc") - ctx = ctxsetters.WithServiceName(ctx, "Ability") - ctx = ctxsetters.WithResponseWriter(ctx, resp) - - var err error - ctx, err = callRequestReceived(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - if req.Method != "POST" { - msg := fmt.Sprintf("unsupported method %q (only POST is allowed)", req.Method) - s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) - return - } - - // Verify path format: []/./ - prefix, pkgService, method := parseTwirpPath(req.URL.Path) - if pkgService != "authz.rpc.Ability" { - msg := fmt.Sprintf("no handler for path %q", req.URL.Path) - s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) - return - } - if prefix != s.pathPrefix { - msg := fmt.Sprintf("invalid path prefix %q, expected %q, on path %q", prefix, s.pathPrefix, req.URL.Path) - s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) - return - } - - switch method { - case "Allowed": - s.serveAllowed(ctx, resp, req) - return - default: - msg := fmt.Sprintf("no handler for path %q", req.URL.Path) - s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) - return - } -} - -func (s *abilityServer) serveAllowed(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - header := req.Header.Get("Content-Type") - i := strings.Index(header, ";") - if i == -1 { - i = len(header) - } - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveAllowedJSON(ctx, resp, req) - case "application/protobuf": - s.serveAllowedProtobuf(ctx, resp, req) - default: - msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) - twerr := badRouteError(msg, req.Method, req.URL.Path) - s.writeError(ctx, resp, twerr) - } -} - -func (s *abilityServer) serveAllowedJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "Allowed") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - d := json.NewDecoder(req.Body) - rawReqBody := json.RawMessage{} - if err := d.Decode(&rawReqBody); err != nil { - s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) - return - } - reqContent := new(AllowRequest) - unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} - if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { - s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) - return - } - - handler := s.Ability.Allowed - if s.interceptor != nil { - handler = func(ctx context.Context, req *AllowRequest) (*AllowReply, error) { - resp, err := s.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AllowRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AllowRequest) when calling interceptor") - } - return s.Ability.Allowed(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AllowReply) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AllowReply) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - - // Call service method - var respContent *AllowReply - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = handler(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *AllowReply and nil error while calling Allowed. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} - respBytes, err := marshaler.Marshal(respContent) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) - return - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - resp.Header().Set("Content-Type", "application/json") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - ctx = callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *abilityServer) serveAllowedProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "Allowed") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - buf, err := io.ReadAll(req.Body) - if err != nil { - s.handleRequestBodyError(ctx, resp, "failed to read request body", err) - return - } - reqContent := new(AllowRequest) - if err = proto.Unmarshal(buf, reqContent); err != nil { - s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) - return - } - - handler := s.Ability.Allowed - if s.interceptor != nil { - handler = func(ctx context.Context, req *AllowRequest) (*AllowReply, error) { - resp, err := s.interceptor( - func(ctx context.Context, req interface{}) (interface{}, error) { - typedReq, ok := req.(*AllowRequest) - if !ok { - return nil, twirp.InternalError("failed type assertion req.(*AllowRequest) when calling interceptor") - } - return s.Ability.Allowed(ctx, typedReq) - }, - )(ctx, req) - if resp != nil { - typedResp, ok := resp.(*AllowReply) - if !ok { - return nil, twirp.InternalError("failed type assertion resp.(*AllowReply) when calling interceptor") - } - return typedResp, err - } - return nil, err - } - } - - // Call service method - var respContent *AllowReply - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = handler(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *AllowReply and nil error while calling Allowed. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - respBytes, err := proto.Marshal(respContent) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) - return - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - resp.Header().Set("Content-Type", "application/protobuf") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - ctx = callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *abilityServer) ServiceDescriptor() ([]byte, int) { - return twirpFileDescriptor0, 0 -} - -func (s *abilityServer) ProtocGenTwirpVersion() string { - return "v8.1.3" -} - -// PathPrefix returns the base service path, in the form: "//./" -// that is everything in a Twirp route except for the . This can be used for routing, -// for example to identify the requests that are targeted to this service in a mux. -func (s *abilityServer) PathPrefix() string { - return baseServicePath(s.pathPrefix, "authz.rpc", "Ability") -} - -// ===== -// Utils -// ===== - -// HTTPClient is the interface used by generated clients to send HTTP requests. -// It is fulfilled by *(net/http).Client, which is sufficient for most users. -// Users can provide their own implementation for special retry policies. -// -// HTTPClient implementations should not follow redirects. Redirects are -// automatically disabled if *(net/http).Client is passed to client -// constructors. See the withoutRedirects function in this file for more -// details. -type HTTPClient interface { - Do(req *http.Request) (*http.Response, error) -} - -// TwirpServer is the interface generated server structs will support: they're -// HTTP handlers with additional methods for accessing metadata about the -// service. Those accessors are a low-level API for building reflection tools. -// Most people can think of TwirpServers as just http.Handlers. -type TwirpServer interface { - http.Handler - - // ServiceDescriptor returns gzipped bytes describing the .proto file that - // this service was generated from. Once unzipped, the bytes can be - // unmarshalled as a - // google.golang.org/protobuf/types/descriptorpb.FileDescriptorProto. - // - // The returned integer is the index of this particular service within that - // FileDescriptorProto's 'Service' slice of ServiceDescriptorProtos. This is a - // low-level field, expected to be used for reflection. - ServiceDescriptor() ([]byte, int) - - // ProtocGenTwirpVersion is the semantic version string of the version of - // twirp used to generate this file. - ProtocGenTwirpVersion() string - - // PathPrefix returns the HTTP URL path prefix for all methods handled by this - // service. This can be used with an HTTP mux to route Twirp requests. - // The path prefix is in the form: "//./" - // that is, everything in a Twirp route except for the at the end. - PathPrefix() string -} - -func newServerOpts(opts []interface{}) *twirp.ServerOptions { - serverOpts := &twirp.ServerOptions{} - for _, opt := range opts { - switch o := opt.(type) { - case twirp.ServerOption: - o(serverOpts) - case *twirp.ServerHooks: // backwards compatibility, allow to specify hooks as an argument - twirp.WithServerHooks(o)(serverOpts) - case nil: // backwards compatibility, allow nil value for the argument - continue - default: - panic(fmt.Sprintf("Invalid option type %T, please use a twirp.ServerOption", o)) - } - } - return serverOpts -} - -// WriteError writes an HTTP response with a valid Twirp error format (code, msg, meta). -// Useful outside of the Twirp server (e.g. http middleware), but does not trigger hooks. -// If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) -func WriteError(resp http.ResponseWriter, err error) { - writeError(context.Background(), resp, err, nil) -} - -// writeError writes Twirp errors in the response and triggers hooks. -func writeError(ctx context.Context, resp http.ResponseWriter, err error, hooks *twirp.ServerHooks) { - // Convert to a twirp.Error. Non-twirp errors are converted to internal errors. - var twerr twirp.Error - if !errors.As(err, &twerr) { - twerr = twirp.InternalErrorWith(err) - } - - statusCode := twirp.ServerHTTPStatusFromErrorCode(twerr.Code()) - ctx = ctxsetters.WithStatusCode(ctx, statusCode) - ctx = callError(ctx, hooks, twerr) - - respBody := marshalErrorToJSON(twerr) - - resp.Header().Set("Content-Type", "application/json") // Error responses are always JSON - resp.Header().Set("Content-Length", strconv.Itoa(len(respBody))) - resp.WriteHeader(statusCode) // set HTTP status code and send response - - _, writeErr := resp.Write(respBody) - if writeErr != nil { - // We have three options here. We could log the error, call the Error - // hook, or just silently ignore the error. - // - // Logging is unacceptable because we don't have a user-controlled - // logger; writing out to stderr without permission is too rude. - // - // Calling the Error hook would confuse users: it would mean the Error - // hook got called twice for one request, which is likely to lead to - // duplicated log messages and metrics, no matter how well we document - // the behavior. - // - // Silently ignoring the error is our least-bad option. It's highly - // likely that the connection is broken and the original 'err' says - // so anyway. - _ = writeErr - } - - callResponseSent(ctx, hooks) -} - -// sanitizeBaseURL parses the the baseURL, and adds the "http" scheme if needed. -// If the URL is unparsable, the baseURL is returned unchanged. -func sanitizeBaseURL(baseURL string) string { - u, err := url.Parse(baseURL) - if err != nil { - return baseURL // invalid URL will fail later when making requests - } - if u.Scheme == "" { - u.Scheme = "http" - } - return u.String() -} - -// baseServicePath composes the path prefix for the service (without ). -// e.g.: baseServicePath("/twirp", "my.pkg", "MyService") -// -// returns => "/twirp/my.pkg.MyService/" -// -// e.g.: baseServicePath("", "", "MyService") -// -// returns => "/MyService/" -func baseServicePath(prefix, pkg, service string) string { - fullServiceName := service - if pkg != "" { - fullServiceName = pkg + "." + service - } - return path.Join("/", prefix, fullServiceName) + "/" -} - -// parseTwirpPath extracts path components form a valid Twirp route. -// Expected format: "[]/./" -// e.g.: prefix, pkgService, method := parseTwirpPath("/twirp/pkg.Svc/MakeHat") -func parseTwirpPath(path string) (string, string, string) { - parts := strings.Split(path, "/") - if len(parts) < 2 { - return "", "", "" - } - method := parts[len(parts)-1] - pkgService := parts[len(parts)-2] - prefix := strings.Join(parts[0:len(parts)-2], "/") - return prefix, pkgService, method -} - -// getCustomHTTPReqHeaders retrieves a copy of any headers that are set in -// a context through the twirp.WithHTTPRequestHeaders function. -// If there are no headers set, or if they have the wrong type, nil is returned. -func getCustomHTTPReqHeaders(ctx context.Context) http.Header { - header, ok := twirp.HTTPRequestHeaders(ctx) - if !ok || header == nil { - return nil - } - copied := make(http.Header) - for k, vv := range header { - if vv == nil { - copied[k] = nil - continue - } - copied[k] = make([]string, len(vv)) - copy(copied[k], vv) - } - return copied -} - -// newRequest makes an http.Request from a client, adding common headers. -func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType string) (*http.Request, error) { - req, err := http.NewRequest("POST", url, reqBody) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if customHeader := getCustomHTTPReqHeaders(ctx); customHeader != nil { - req.Header = customHeader - } - req.Header.Set("Accept", contentType) - req.Header.Set("Content-Type", contentType) - req.Header.Set("Twirp-Version", "v8.1.3") - return req, nil -} - -// JSON serialization for errors -type twerrJSON struct { - Code string `json:"code"` - Msg string `json:"msg"` - Meta map[string]string `json:"meta,omitempty"` -} - -// marshalErrorToJSON returns JSON from a twirp.Error, that can be used as HTTP error response body. -// If serialization fails, it will use a descriptive Internal error instead. -func marshalErrorToJSON(twerr twirp.Error) []byte { - // make sure that msg is not too large - msg := twerr.Msg() - if len(msg) > 1e6 { - msg = msg[:1e6] - } - - tj := twerrJSON{ - Code: string(twerr.Code()), - Msg: msg, - Meta: twerr.MetaMap(), - } - - buf, err := json.Marshal(&tj) - if err != nil { - buf = []byte("{\"type\": \"" + twirp.Internal + "\", \"msg\": \"There was an error but it could not be serialized into JSON\"}") // fallback - } - - return buf -} - -// errorFromResponse builds a twirp.Error from a non-200 HTTP response. -// If the response has a valid serialized Twirp error, then it's returned. -// If not, the response status code is used to generate a similar twirp -// error. See twirpErrorFromIntermediary for more info on intermediary errors. -func errorFromResponse(resp *http.Response) twirp.Error { - statusCode := resp.StatusCode - statusText := http.StatusText(statusCode) - - if isHTTPRedirect(statusCode) { - // Unexpected redirect: it must be an error from an intermediary. - // Twirp clients don't follow redirects automatically, Twirp only handles - // POST requests, redirects should only happen on GET and HEAD requests. - location := resp.Header.Get("Location") - msg := fmt.Sprintf("unexpected HTTP status code %d %q received, Location=%q", statusCode, statusText, location) - return twirpErrorFromIntermediary(statusCode, msg, location) - } - - respBodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - return wrapInternal(err, "failed to read server error response body") - } - - var tj twerrJSON - dec := json.NewDecoder(bytes.NewReader(respBodyBytes)) - dec.DisallowUnknownFields() - if err := dec.Decode(&tj); err != nil || tj.Code == "" { - // Invalid JSON response; it must be an error from an intermediary. - msg := fmt.Sprintf("Error from intermediary with HTTP status code %d %q", statusCode, statusText) - return twirpErrorFromIntermediary(statusCode, msg, string(respBodyBytes)) - } - - errorCode := twirp.ErrorCode(tj.Code) - if !twirp.IsValidErrorCode(errorCode) { - msg := "invalid type returned from server error response: " + tj.Code - return twirp.InternalError(msg).WithMeta("body", string(respBodyBytes)) - } - - twerr := twirp.NewError(errorCode, tj.Msg) - for k, v := range tj.Meta { - twerr = twerr.WithMeta(k, v) - } - return twerr -} - -// twirpErrorFromIntermediary maps HTTP errors from non-twirp sources to twirp errors. -// The mapping is similar to gRPC: https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md. -// Returned twirp Errors have some additional metadata for inspection. -func twirpErrorFromIntermediary(status int, msg string, bodyOrLocation string) twirp.Error { - var code twirp.ErrorCode - if isHTTPRedirect(status) { // 3xx - code = twirp.Internal - } else { - switch status { - case 400: // Bad Request - code = twirp.Internal - case 401: // Unauthorized - code = twirp.Unauthenticated - case 403: // Forbidden - code = twirp.PermissionDenied - case 404: // Not Found - code = twirp.BadRoute - case 429: // Too Many Requests - code = twirp.ResourceExhausted - case 502, 503, 504: // Bad Gateway, Service Unavailable, Gateway Timeout - code = twirp.Unavailable - default: // All other codes - code = twirp.Unknown - } - } - - twerr := twirp.NewError(code, msg) - twerr = twerr.WithMeta("http_error_from_intermediary", "true") // to easily know if this error was from intermediary - twerr = twerr.WithMeta("status_code", strconv.Itoa(status)) - if isHTTPRedirect(status) { - twerr = twerr.WithMeta("location", bodyOrLocation) - } else { - twerr = twerr.WithMeta("body", bodyOrLocation) - } - return twerr -} - -func isHTTPRedirect(status int) bool { - return status >= 300 && status <= 399 -} - -// wrapInternal wraps an error with a prefix as an Internal error. -// The original error cause is accessible by github.com/pkg/errors.Cause. -func wrapInternal(err error, prefix string) twirp.Error { - return twirp.InternalErrorWith(&wrappedError{prefix: prefix, cause: err}) -} - -type wrappedError struct { - prefix string - cause error -} - -func (e *wrappedError) Error() string { return e.prefix + ": " + e.cause.Error() } -func (e *wrappedError) Unwrap() error { return e.cause } // for go1.13 + errors.Is/As -func (e *wrappedError) Cause() error { return e.cause } // for github.com/pkg/errors - -// ensurePanicResponses makes sure that rpc methods causing a panic still result in a Twirp Internal -// error response (status 500), and error hooks are properly called with the panic wrapped as an error. -// The panic is re-raised so it can be handled normally with middleware. -func ensurePanicResponses(ctx context.Context, resp http.ResponseWriter, hooks *twirp.ServerHooks) { - if r := recover(); r != nil { - // Wrap the panic as an error so it can be passed to error hooks. - // The original error is accessible from error hooks, but not visible in the response. - err := errFromPanic(r) - twerr := &internalWithCause{msg: "Internal service panic", cause: err} - // Actually write the error - writeError(ctx, resp, twerr, hooks) - // If possible, flush the error to the wire. - f, ok := resp.(http.Flusher) - if ok { - f.Flush() - } - - panic(r) - } -} - -// errFromPanic returns the typed error if the recovered panic is an error, otherwise formats as error. -func errFromPanic(p interface{}) error { - if err, ok := p.(error); ok { - return err - } - return fmt.Errorf("panic: %v", p) -} - -// internalWithCause is a Twirp Internal error wrapping an original error cause, -// but the original error message is not exposed on Msg(). The original error -// can be checked with go1.13+ errors.Is/As, and also by (github.com/pkg/errors).Unwrap -type internalWithCause struct { - msg string - cause error -} - -func (e *internalWithCause) Unwrap() error { return e.cause } // for go1.13 + errors.Is/As -func (e *internalWithCause) Cause() error { return e.cause } // for github.com/pkg/errors -func (e *internalWithCause) Error() string { return e.msg + ": " + e.cause.Error() } -func (e *internalWithCause) Code() twirp.ErrorCode { return twirp.Internal } -func (e *internalWithCause) Msg() string { return e.msg } -func (e *internalWithCause) Meta(key string) string { return "" } -func (e *internalWithCause) MetaMap() map[string]string { return nil } -func (e *internalWithCause) WithMeta(key string, val string) twirp.Error { return e } - -// malformedRequestError is used when the twirp server cannot unmarshal a request -func malformedRequestError(msg string) twirp.Error { - return twirp.NewError(twirp.Malformed, msg) -} - -// badRouteError is used when the twirp server cannot route a request -func badRouteError(msg string, method, url string) twirp.Error { - err := twirp.NewError(twirp.BadRoute, msg) - err = err.WithMeta("twirp_invalid_route", method+" "+url) - return err -} - -// withoutRedirects makes sure that the POST request can not be redirected. -// The standard library will, by default, redirect requests (including POSTs) if it gets a 302 or -// 303 response, and also 301s in go1.8. It redirects by making a second request, changing the -// method to GET and removing the body. This produces very confusing error messages, so instead we -// set a redirect policy that always errors. This stops Go from executing the redirect. -// -// We have to be a little careful in case the user-provided http.Client has its own CheckRedirect -// policy - if so, we'll run through that policy first. -// -// Because this requires modifying the http.Client, we make a new copy of the client and return it. -func withoutRedirects(in *http.Client) *http.Client { - copy := *in - copy.CheckRedirect = func(req *http.Request, via []*http.Request) error { - if in.CheckRedirect != nil { - // Run the input's redirect if it exists, in case it has side effects, but ignore any error it - // returns, since we want to use ErrUseLastResponse. - err := in.CheckRedirect(req, via) - _ = err // Silly, but this makes sure generated code passes errcheck -blank, which some people use. - } - return http.ErrUseLastResponse - } - return © -} - -// doProtobufRequest makes a Protobuf request to the remote Twirp service. -func doProtobufRequest(ctx context.Context, client HTTPClient, hooks *twirp.ClientHooks, url string, in, out proto.Message) (_ context.Context, err error) { - reqBodyBytes, err := proto.Marshal(in) - if err != nil { - return ctx, wrapInternal(err, "failed to marshal proto request") - } - reqBody := bytes.NewBuffer(reqBodyBytes) - if err = ctx.Err(); err != nil { - return ctx, wrapInternal(err, "aborted because context was done") - } - - req, err := newRequest(ctx, url, reqBody, "application/protobuf") - if err != nil { - return ctx, wrapInternal(err, "could not build request") - } - ctx, err = callClientRequestPrepared(ctx, hooks, req) - if err != nil { - return ctx, err - } - - req = req.WithContext(ctx) - resp, err := client.Do(req) - if err != nil { - return ctx, wrapInternal(err, "failed to do request") - } - defer func() { _ = resp.Body.Close() }() - - if err = ctx.Err(); err != nil { - return ctx, wrapInternal(err, "aborted because context was done") - } - - if resp.StatusCode != 200 { - return ctx, errorFromResponse(resp) - } - - respBodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - return ctx, wrapInternal(err, "failed to read response body") - } - if err = ctx.Err(); err != nil { - return ctx, wrapInternal(err, "aborted because context was done") - } - - if err = proto.Unmarshal(respBodyBytes, out); err != nil { - return ctx, wrapInternal(err, "failed to unmarshal proto response") - } - return ctx, nil -} - -// doJSONRequest makes a JSON request to the remote Twirp service. -func doJSONRequest(ctx context.Context, client HTTPClient, hooks *twirp.ClientHooks, url string, in, out proto.Message) (_ context.Context, err error) { - marshaler := &protojson.MarshalOptions{UseProtoNames: true} - reqBytes, err := marshaler.Marshal(in) - if err != nil { - return ctx, wrapInternal(err, "failed to marshal json request") - } - if err = ctx.Err(); err != nil { - return ctx, wrapInternal(err, "aborted because context was done") - } - - req, err := newRequest(ctx, url, bytes.NewReader(reqBytes), "application/json") - if err != nil { - return ctx, wrapInternal(err, "could not build request") - } - ctx, err = callClientRequestPrepared(ctx, hooks, req) - if err != nil { - return ctx, err - } - - req = req.WithContext(ctx) - resp, err := client.Do(req) - if err != nil { - return ctx, wrapInternal(err, "failed to do request") - } - - defer func() { - cerr := resp.Body.Close() - if err == nil && cerr != nil { - err = wrapInternal(cerr, "failed to close response body") - } - }() - - if err = ctx.Err(); err != nil { - return ctx, wrapInternal(err, "aborted because context was done") - } - - if resp.StatusCode != 200 { - return ctx, errorFromResponse(resp) - } - - d := json.NewDecoder(resp.Body) - rawRespBody := json.RawMessage{} - if err := d.Decode(&rawRespBody); err != nil { - return ctx, wrapInternal(err, "failed to unmarshal json response") - } - unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} - if err = unmarshaler.Unmarshal(rawRespBody, out); err != nil { - return ctx, wrapInternal(err, "failed to unmarshal json response") - } - if err = ctx.Err(); err != nil { - return ctx, wrapInternal(err, "aborted because context was done") - } - return ctx, nil -} - -// Call twirp.ServerHooks.RequestReceived if the hook is available -func callRequestReceived(ctx context.Context, h *twirp.ServerHooks) (context.Context, error) { - if h == nil || h.RequestReceived == nil { - return ctx, nil - } - return h.RequestReceived(ctx) -} - -// Call twirp.ServerHooks.RequestRouted if the hook is available -func callRequestRouted(ctx context.Context, h *twirp.ServerHooks) (context.Context, error) { - if h == nil || h.RequestRouted == nil { - return ctx, nil - } - return h.RequestRouted(ctx) -} - -// Call twirp.ServerHooks.ResponsePrepared if the hook is available -func callResponsePrepared(ctx context.Context, h *twirp.ServerHooks) context.Context { - if h == nil || h.ResponsePrepared == nil { - return ctx - } - return h.ResponsePrepared(ctx) -} - -// Call twirp.ServerHooks.ResponseSent if the hook is available -func callResponseSent(ctx context.Context, h *twirp.ServerHooks) { - if h == nil || h.ResponseSent == nil { - return - } - h.ResponseSent(ctx) -} - -// Call twirp.ServerHooks.Error if the hook is available -func callError(ctx context.Context, h *twirp.ServerHooks, err twirp.Error) context.Context { - if h == nil || h.Error == nil { - return ctx - } - return h.Error(ctx, err) -} - -func callClientResponseReceived(ctx context.Context, h *twirp.ClientHooks) { - if h == nil || h.ResponseReceived == nil { - return - } - h.ResponseReceived(ctx) -} - -func callClientRequestPrepared(ctx context.Context, h *twirp.ClientHooks, req *http.Request) (context.Context, error) { - if h == nil || h.RequestPrepared == nil { - return ctx, nil - } - return h.RequestPrepared(ctx, req) -} - -func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error) { - if h == nil || h.Error == nil { - return - } - h.Error(ctx, err) -} - -var twirpFileDescriptor0 = []byte{ - // 196 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4d, 0x4c, 0xca, 0xcc, - 0xc9, 0x2c, 0xa9, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x4c, 0x2c, 0x2d, 0xc9, 0xa8, - 0xd2, 0x2b, 0x2a, 0x48, 0x56, 0x4a, 0xe1, 0xe2, 0x71, 0xcc, 0xc9, 0xc9, 0x2f, 0x0f, 0x4a, 0x2d, - 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x92, 0xe0, 0x62, 0x2f, 0x2e, 0x4d, 0xca, 0x4a, 0x4d, 0x2e, 0x91, - 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0x71, 0x85, 0xe4, 0xb8, 0xb8, 0x0a, 0x52, 0x8b, 0x72, - 0x33, 0x8b, 0x8b, 0x33, 0xf3, 0xf3, 0x24, 0x98, 0xc0, 0x92, 0x48, 0x22, 0x42, 0x52, 0x5c, 0x1c, - 0x45, 0xa9, 0xc5, 0xf9, 0xa5, 0x45, 0xc9, 0xa9, 0x12, 0xcc, 0x60, 0x59, 0x38, 0x5f, 0x49, 0x85, - 0x8b, 0x0b, 0x6a, 0x4b, 0x41, 0x4e, 0xa5, 0x90, 0x18, 0x17, 0x5b, 0x51, 0x6a, 0x71, 0x69, 0x0e, - 0xc4, 0x0a, 0x8e, 0x20, 0x28, 0xcf, 0xc8, 0x8d, 0x8b, 0xdd, 0x11, 0xe2, 0x4e, 0x21, 0x6b, 0x2e, - 0x76, 0xb0, 0x86, 0xd4, 0x14, 0x21, 0x71, 0x3d, 0xb8, 0x6b, 0xf5, 0x90, 0x9d, 0x2a, 0x25, 0x8a, - 0x29, 0x51, 0x90, 0x53, 0xa9, 0xc4, 0xe0, 0xc4, 0x19, 0xc5, 0x5e, 0x90, 0x9d, 0xae, 0x5f, 0x54, - 0x90, 0x9c, 0xc4, 0x06, 0xf6, 0xb0, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x72, 0x35, 0x46, 0x7c, - 0x01, 0x01, 0x00, 0x00, -} diff --git a/protos/ability.proto b/protos/ability.proto deleted file mode 100644 index b58f5027..00000000 --- a/protos/ability.proto +++ /dev/null @@ -1,19 +0,0 @@ -syntax = "proto3"; - -package authz.rpc; -option go_package = "pkg/rpc"; - - -service Ability { - rpc Allowed (AllowRequest) returns (AllowReply) {} -} - -message AllowRequest { - string subject = 1; - string permission = 2; - string resource = 3; -} - -message AllowReply { - bool result = 1; -} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..f84dc08e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,44 @@ +use envoy_types::ext_authz::v3::pb::{ + Authorization, AuthorizationServer, CheckRequest, CheckResponse, +}; +use envoy_types::ext_authz::v3::{CheckRequestExt, CheckResponseExt}; +use tonic::{Request, Response, Status, transport::Server}; + +#[derive(Debug, Default)] +struct PolicyServer; + +#[tonic::async_trait] +impl Authorization for PolicyServer { + async fn check( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let client_headers = request + .get_client_headers() + .ok_or_else(|| Status::invalid_argument("client headers not populated by envoy"))?; + + let mut request_status = Status::unauthenticated("not authorized"); + + if let Some(authorization) = client_headers.get("authorization") { + if authorization == "Bearer valid-token" { + request_status = Status::ok("request is valid"); + } + } + + Ok(Response::new(CheckResponse::with_status(request_status))) + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "[::1]:50051".parse()?; + + Server::builder() + .add_service(AuthorizationServer::new(PolicyServer::default())) + .serve(addr) + .await?; + + Ok(()) +} diff --git a/tmp/cache/.keep b/tmp/cache/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/tmp/pids/.keep b/tmp/pids/.keep deleted file mode 100644 index e69de29b..00000000 -- cgit v1.2.3 From b7cabd7fb1adce8a8f05ac397feea00712d04575 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 18 Jun 2025 17:15:35 -0600 Subject: chore: remove unnecessary ls from Dockerfile --- Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3bb0e723..2a367c7a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,6 @@ WORKDIR /app RUN rustup target add x86_64-unknown-linux-musl COPY . ./ RUN cargo build --release --target x86_64-unknown-linux-musl -RUN ls -alh /app FROM scratch EXPOSE 50051 -- cgit v1.2.3 From fabafd3c434532935fa74fe8a75c09b9bbfb51e9 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 18 Jun 2025 17:26:00 -0600 Subject: chore: configure runway to use grpc --- .runway/runway.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.runway/runway.yml b/.runway/runway.yml index 1eac7654..78f3d526 100644 --- a/.runway/runway.yml +++ b/.runway/runway.yml @@ -13,12 +13,9 @@ spec: regions: - us-east1 startup_probe: - path: "/health" - timeout_seconds: 5 - period_seconds: 5 - failure_threshold: 24 # 2 minutes + grpc_service: "grpc.health.v1.Health/Check" liveness_probe: - path: "/health" + grpc_service: "grpc.health.v1.Health/Check" scalability: min_instances: 1 max_instances: 1 @@ -29,4 +26,5 @@ spec: memory: 512Mi load_balancing: external_load_balancer: - backend_protocol: HTTPS + enabled: true + backend_protocol: HTTP2 -- cgit v1.2.3 From 1e9a769e5e4af4684967473f0844f66c3958432c Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 18 Jun 2025 17:50:51 -0600 Subject: feat: register the health check service and the reflection service --- Cargo.lock | 37 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ Makefile | 4 ++++ mise.toml | 1 + src/main.rs | 12 +++++++++++- 5 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9f9300df..cfb0a9d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,6 +47,8 @@ dependencies = [ "envoy-types", "tokio", "tonic", + "tonic-health", + "tonic-reflection", ] [[package]] @@ -481,6 +483,15 @@ dependencies = [ "syn", ] +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + [[package]] name = "quote" version = "1.0.40" @@ -597,6 +608,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] @@ -641,6 +653,31 @@ dependencies = [ "tracing", ] +[[package]] +name = "tonic-health" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb87334d340313fefa513b6e60794d44a86d5f039b523229c99c323e4e19ca4b" +dependencies = [ + "prost", + "tokio", + "tokio-stream", + "tonic", +] + +[[package]] +name = "tonic-reflection" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9687bd5bfeafebdded2356950f278bba8226f0b32109537c4253406e09aafe1" +dependencies = [ + "prost", + "prost-types", + "tokio", + "tokio-stream", + "tonic", +] + [[package]] name = "tower" version = "0.5.2" diff --git a/Cargo.toml b/Cargo.toml index 5d71a4ae..fc79f61b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,5 @@ edition = "2024" envoy-types = "0.6.0" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } tonic = "*" +tonic-health = "0.13.1" +tonic-reflection = "0.13.1" diff --git a/Makefile b/Makefile index ab0140fe..62138427 100644 --- a/Makefile +++ b/Makefile @@ -15,3 +15,7 @@ build-image: .PHONY: run-image run-image: build-image @docker run --rm -p 50051:50051 -it $(IMAGE_TAG) + +.PHONY: health-check +health-check: + @grpcurl -plaintext localhost:50051 grpc.health.v1.Health/Check diff --git a/mise.toml b/mise.toml index 87a29b17..c466bc2b 100644 --- a/mise.toml +++ b/mise.toml @@ -1,5 +1,6 @@ [tools] cargo = "latest" +grpcurl = "latest" make = "latest" rust = "stable" yamlfmt = "latest" diff --git a/src/main.rs b/src/main.rs index f84dc08e..5af58aa0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,9 +34,19 @@ impl Authorization for PolicyServer { #[tokio::main] async fn main() -> Result<(), Box> { let addr = "[::1]:50051".parse()?; - + let (health_reporter, health_service) = tonic_health::server::health_reporter(); + health_reporter + .set_serving::>() + .await; + + let reflection_service = tonic_reflection::server::Builder::configure() + .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET) + .build_v1() + .unwrap(); Server::builder() + .add_service(health_service) .add_service(AuthorizationServer::new(PolicyServer::default())) + .add_service(reflection_service) .serve(addr) .await?; -- cgit v1.2.3 From 594abe311e4dfbbcc477474561756bc5e5f0e539 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 18 Jun 2025 17:53:00 -0600 Subject: chore: add make target to list rpc services --- Makefile | 4 ++++ src/main.rs | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 62138427..14254543 100644 --- a/Makefile +++ b/Makefile @@ -19,3 +19,7 @@ run-image: build-image .PHONY: health-check health-check: @grpcurl -plaintext localhost:50051 grpc.health.v1.Health/Check + +.PHONY: list-services +list-services: + @grpcurl -plaintext localhost:50051 list diff --git a/src/main.rs b/src/main.rs index 5af58aa0..05d57719 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,7 @@ impl Authorization for PolicyServer { #[tokio::main] async fn main() -> Result<(), Box> { let addr = "[::1]:50051".parse()?; + let authorization_service = AuthorizationServer::new(PolicyServer::default()); let (health_reporter, health_service) = tonic_health::server::health_reporter(); health_reporter .set_serving::>() @@ -44,8 +45,8 @@ async fn main() -> Result<(), Box> { .build_v1() .unwrap(); Server::builder() + .add_service(authorization_service) .add_service(health_service) - .add_service(AuthorizationServer::new(PolicyServer::default())) .add_service(reflection_service) .serve(addr) .await?; -- cgit v1.2.3 From f08ef97f4894fde8feff08bb91bed1e1c1105b83 Mon Sep 17 00:00:00 2001 From: mo khan Date: Thu, 19 Jun 2025 11:40:27 -0600 Subject: test: add tests --- Cargo.lock | 302 +++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 12 ++ src/authorization.rs | 104 ++++++++++++++++ src/lib.rs | 3 + src/main.rs | 35 +----- tests/common/mod.rs | 35 ++++++ tests/integration_tests.rs | 50 ++++++++ 7 files changed, 501 insertions(+), 40 deletions(-) create mode 100644 src/authorization.rs create mode 100644 src/lib.rs create mode 100644 tests/common/mod.rs create mode 100644 tests/integration_tests.rs diff --git a/Cargo.lock b/Cargo.lock index cfb0a9d7..9d1c3bdc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,12 +17,43 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-trait" version = "0.1.88" @@ -46,7 +77,9 @@ version = "0.1.0" dependencies = [ "envoy-types", "tokio", + "tokio-test", "tonic", + "tonic-build", "tonic-health", "tonic-reflection", ] @@ -117,6 +150,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + [[package]] name = "bytes" version = "1.10.1" @@ -142,7 +181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "065b6b0018b25902cab074d44c0e2098205329b6b5a309a33cc688bc0ac9573d" dependencies = [ "futures-core", - "prost", + "prost 0.13.5", "tonic", ] @@ -152,6 +191,28 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "fnv" version = "1.0.7" @@ -197,6 +258,18 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + [[package]] name = "gimli" version = "0.31.1" @@ -228,6 +301,12 @@ version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "http" version = "1.3.1" @@ -339,6 +418,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -360,6 +448,18 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + [[package]] name = "matchit" version = "0.8.4" @@ -394,10 +494,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "object" version = "0.36.7" @@ -419,6 +525,16 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -451,6 +567,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "prettyplease" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -460,6 +586,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive 0.12.6", +] + [[package]] name = "prost" version = "0.13.5" @@ -467,7 +603,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.13.5", +] + +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes", + "heck", + "itertools 0.12.1", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost 0.12.6", + "prost-types 0.12.6", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -477,19 +647,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools", + "itertools 0.14.0", "proc-macro2", "quote", "syn", ] +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost 0.12.6", +] + [[package]] name = "prost-types" version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" dependencies = [ - "prost", + "prost 0.13.5", ] [[package]] @@ -501,12 +680,60 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "rustc-demangle" version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + [[package]] name = "rustversion" version = "1.0.21" @@ -572,6 +799,19 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "tokio" version = "1.45.1" @@ -611,6 +851,19 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tokio-test" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +dependencies = [ + "async-stream", + "bytes", + "futures-core", + "tokio", + "tokio-stream", +] + [[package]] name = "tokio-util" version = "0.7.15" @@ -643,7 +896,7 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "prost", + "prost 0.13.5", "socket2", "tokio", "tokio-stream", @@ -653,13 +906,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "tonic-build" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn", +] + [[package]] name = "tonic-health" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb87334d340313fefa513b6e60794d44a86d5f039b523229c99c323e4e19ca4b" dependencies = [ - "prost", + "prost 0.13.5", "tokio", "tokio-stream", "tonic", @@ -671,8 +937,8 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9687bd5bfeafebdded2356950f278bba8226f0b32109537c4253406e09aafe1" dependencies = [ - "prost", - "prost-types", + "prost 0.13.5", + "prost-types 0.13.5", "tokio", "tokio-stream", "tonic", @@ -767,6 +1033,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -848,3 +1123,12 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] diff --git a/Cargo.toml b/Cargo.toml index fc79f61b..a13b9464 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,9 +3,21 @@ name = "authzd" version = "0.1.0" edition = "2024" +[[bin]] +name = "authzd" +path = "src/main.rs" + +[lib] +name = "authzd" +path = "src/lib.rs" + [dependencies] envoy-types = "0.6.0" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } tonic = "*" tonic-health = "0.13.1" tonic-reflection = "0.13.1" + +[dev-dependencies] +tokio-test = "0.4" +tonic-build = "0.10" diff --git a/src/authorization.rs b/src/authorization.rs new file mode 100644 index 00000000..22cd695c --- /dev/null +++ b/src/authorization.rs @@ -0,0 +1,104 @@ +use envoy_types::ext_authz::v3::CheckResponseExt; +use envoy_types::ext_authz::v3::pb::{CheckRequest, CheckResponse}; +use tonic::{Request, Response, Status}; + +#[derive(Debug, Default)] +pub struct PolicyServer; + +#[tonic::async_trait] +impl envoy_types::ext_authz::v3::pb::Authorization for PolicyServer { + async fn check( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let client_headers = request + .attributes + .as_ref() + .and_then(|attr| attr.request.as_ref()) + .and_then(|req| req.http.as_ref()) + .map(|http| &http.headers) + .ok_or_else(|| Status::invalid_argument("client headers not populated by envoy"))?; + + let mut request_status = Status::unauthenticated("not authorized"); + + if let Some(authorization) = client_headers.get("authorization") { + if authorization == "Bearer valid-token" { + request_status = Status::ok("request is valid"); + } + } + + Ok(Response::new(CheckResponse::with_status(request_status))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use envoy_types::ext_authz::v3::pb::{Authorization, CheckRequest}; + use std::collections::HashMap; + use tonic::Request; + + fn create_test_request_with_headers(headers: HashMap) -> Request { + use envoy_types::pb::envoy::service::auth::v3::{AttributeContext, attribute_context}; + + let http_request = attribute_context::HttpRequest { + headers, + ..Default::default() + }; + + let request_context = attribute_context::Request { + http: Some(http_request), + ..Default::default() + }; + + let attributes = AttributeContext { + request: Some(request_context), + ..Default::default() + }; + + let check_request = CheckRequest { + attributes: Some(attributes), + ..Default::default() + }; + + Request::new(check_request) + } + + fn create_headers_with_auth(auth_value: &str) -> HashMap { + let mut headers = HashMap::new(); + headers.insert("authorization".to_string(), auth_value.to_string()); + headers + } + + #[tokio::test] + async fn test_check_allows_valid_bearer_token() { + let token = String::from("valid-token"); + let server = PolicyServer::default(); + let headers = create_headers_with_auth(&format!("Bearer {}", token)); + let request = create_test_request_with_headers(headers); + + let response = server.check(request).await; + + assert!(response.is_ok()); + let check_response = response.unwrap().into_inner(); + assert!(check_response.status.is_some()); + let status = check_response.status.unwrap(); + assert_eq!(status.code, tonic::Code::Ok.into()); + } + + #[tokio::test] + async fn test_check_denies_invalid_bearer_token() { + let server = PolicyServer::default(); + let request = create_test_request_with_headers(HashMap::new()); + + let response = server.check(request).await; + + assert!(response.is_ok()); + let check_response = response.unwrap().into_inner(); + assert!(check_response.status.is_some()); + let status = check_response.status.unwrap(); + assert_eq!(status.code, tonic::Code::Unauthenticated.into()); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..cb28e34b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +pub mod authorization; + +pub use authorization::PolicyServer; diff --git a/src/main.rs b/src/main.rs index 05d57719..25e2c88f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,35 +1,8 @@ -use envoy_types::ext_authz::v3::pb::{ - Authorization, AuthorizationServer, CheckRequest, CheckResponse, -}; -use envoy_types::ext_authz::v3::{CheckRequestExt, CheckResponseExt}; -use tonic::{Request, Response, Status, transport::Server}; +use envoy_types::ext_authz::v3::pb::AuthorizationServer; +use tonic::transport::Server; -#[derive(Debug, Default)] -struct PolicyServer; - -#[tonic::async_trait] -impl Authorization for PolicyServer { - async fn check( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - - let client_headers = request - .get_client_headers() - .ok_or_else(|| Status::invalid_argument("client headers not populated by envoy"))?; - - let mut request_status = Status::unauthenticated("not authorized"); - - if let Some(authorization) = client_headers.get("authorization") { - if authorization == "Bearer valid-token" { - request_status = Status::ok("request is valid"); - } - } - - Ok(Response::new(CheckResponse::with_status(request_status))) - } -} +pub mod authorization; +use authorization::PolicyServer; #[tokio::main] async fn main() -> Result<(), Box> { diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 00000000..e2137146 --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,35 @@ +use envoy_types::ext_authz::v3::pb::CheckRequest; +use std::collections::HashMap; +use tonic::Request; + +pub fn create_test_request_with_headers(headers: HashMap) -> Request { + use envoy_types::pb::envoy::service::auth::v3::{AttributeContext, attribute_context}; + + let http_request = attribute_context::HttpRequest { + headers, + ..Default::default() + }; + + let request_context = attribute_context::Request { + http: Some(http_request), + ..Default::default() + }; + + let attributes = AttributeContext { + request: Some(request_context), + ..Default::default() + }; + + let check_request = CheckRequest { + attributes: Some(attributes), + ..Default::default() + }; + + Request::new(check_request) +} + +pub fn create_headers_with_auth(auth_value: &str) -> HashMap { + let mut headers = HashMap::new(); + headers.insert("authorization".to_string(), auth_value.to_string()); + headers +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs new file mode 100644 index 00000000..28cdf959 --- /dev/null +++ b/tests/integration_tests.rs @@ -0,0 +1,50 @@ +use authzd::PolicyServer; +use envoy_types::ext_authz::v3::pb::Authorization; + +mod common; + +#[tokio::test] +async fn test_success_response() { + let server = PolicyServer::default(); + + let headers = common::create_headers_with_auth("Bearer valid-token"); + let request = common::create_test_request_with_headers(headers); + + let response = server.check(request).await; + assert!(response.is_ok()); + + let check_response = response.unwrap().into_inner(); + assert!(check_response.status.is_some()); + + let status = check_response.status.unwrap(); + assert_eq!(status.code, tonic::Code::Ok.into()); +} + +#[tokio::test] +async fn test_multiple() { + let server = PolicyServer::default(); + + let test_cases = vec![ + ("Bearer valid-token", true), + ("Bearer invalid-token", false), + ("Basic valid-token", false), + ("", false), + ]; + + for (auth_value, should_succeed) in test_cases { + let headers = common::create_headers_with_auth(auth_value); + let request = common::create_test_request_with_headers(headers); + + let response = server.check(request).await; + assert!(response.is_ok()); + + let check_response = response.unwrap().into_inner(); + let status = check_response.status.unwrap(); + + if should_succeed { + assert_eq!(status.code, tonic::Code::Ok.into()); + } else { + assert_eq!(status.code, tonic::Code::Unauthenticated.into()); + } + } +} -- cgit v1.2.3 From 5fc7a6f0d477b7dcb9489f7b6841b01d7964fdbe Mon Sep 17 00:00:00 2001 From: mo khan Date: Thu, 19 Jun 2025 17:18:07 -0600 Subject: refactor: rename PolicyServer to CheckService --- .gitignore | 4 +--- src/authorization.rs | 8 ++++---- src/lib.rs | 2 +- src/main.rs | 9 +++------ tests/integration_tests.rs | 6 +++--- 5 files changed, 12 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 6bfb8923..eb5a316c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ -tmp -*.pdf -/target +target diff --git a/src/authorization.rs b/src/authorization.rs index 22cd695c..279f62cc 100644 --- a/src/authorization.rs +++ b/src/authorization.rs @@ -3,10 +3,10 @@ use envoy_types::ext_authz::v3::pb::{CheckRequest, CheckResponse}; use tonic::{Request, Response, Status}; #[derive(Debug, Default)] -pub struct PolicyServer; +pub struct CheckService; #[tonic::async_trait] -impl envoy_types::ext_authz::v3::pb::Authorization for PolicyServer { +impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { async fn check( &self, request: Request, @@ -75,7 +75,7 @@ mod tests { #[tokio::test] async fn test_check_allows_valid_bearer_token() { let token = String::from("valid-token"); - let server = PolicyServer::default(); + let server = CheckService::default(); let headers = create_headers_with_auth(&format!("Bearer {}", token)); let request = create_test_request_with_headers(headers); @@ -90,7 +90,7 @@ mod tests { #[tokio::test] async fn test_check_denies_invalid_bearer_token() { - let server = PolicyServer::default(); + let server = CheckService::default(); let request = create_test_request_with_headers(HashMap::new()); let response = server.check(request).await; diff --git a/src/lib.rs b/src/lib.rs index cb28e34b..b3791974 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,3 @@ pub mod authorization; -pub use authorization::PolicyServer; +pub use authorization::CheckService; diff --git a/src/main.rs b/src/main.rs index 25e2c88f..917cace5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,16 +2,13 @@ use envoy_types::ext_authz::v3::pb::AuthorizationServer; use tonic::transport::Server; pub mod authorization; -use authorization::PolicyServer; +use authorization::CheckService; #[tokio::main] async fn main() -> Result<(), Box> { let addr = "[::1]:50051".parse()?; - let authorization_service = AuthorizationServer::new(PolicyServer::default()); - let (health_reporter, health_service) = tonic_health::server::health_reporter(); - health_reporter - .set_serving::>() - .await; + let authorization_service = AuthorizationServer::new(CheckService::default()); + let (_health_reporter, health_service) = tonic_health::server::health_reporter(); let reflection_service = tonic_reflection::server::Builder::configure() .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 28cdf959..a3603c11 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,11 +1,11 @@ -use authzd::PolicyServer; +use authzd::CheckService; use envoy_types::ext_authz::v3::pb::Authorization; mod common; #[tokio::test] async fn test_success_response() { - let server = PolicyServer::default(); + let server = CheckService::default(); let headers = common::create_headers_with_auth("Bearer valid-token"); let request = common::create_test_request_with_headers(headers); @@ -22,7 +22,7 @@ async fn test_success_response() { #[tokio::test] async fn test_multiple() { - let server = PolicyServer::default(); + let server = CheckService::default(); let test_cases = vec![ ("Bearer valid-token", true), -- cgit v1.2.3 From 3291163206f16d2d624b98e11125abf76da36c47 Mon Sep 17 00:00:00 2001 From: mo khan Date: Thu, 19 Jun 2025 17:33:31 -0600 Subject: refactor: extract a Authorizer Trait with a stub for a Cedar authorizer --- src/authorization.rs | 52 ++++++++++++++++++++++++++++++++++++++-------------- src/main.rs | 15 ++++++++------- 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/authorization.rs b/src/authorization.rs index 279f62cc..d86e8938 100644 --- a/src/authorization.rs +++ b/src/authorization.rs @@ -2,34 +2,58 @@ use envoy_types::ext_authz::v3::CheckResponseExt; use envoy_types::ext_authz::v3::pb::{CheckRequest, CheckResponse}; use tonic::{Request, Response, Status}; -#[derive(Debug, Default)] -pub struct CheckService; +trait Authorizer { + fn authorize(&self, request: CheckRequest) -> bool; +} -#[tonic::async_trait] -impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { - async fn check( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); +struct CedarAuthorizer {} +impl CedarAuthorizer { + fn new() -> CedarAuthorizer { + return CedarAuthorizer {}; + } +} + +impl Authorizer for CedarAuthorizer { + fn authorize(&self, request: CheckRequest) -> bool { let client_headers = request .attributes .as_ref() .and_then(|attr| attr.request.as_ref()) .and_then(|req| req.http.as_ref()) .map(|http| &http.headers) - .ok_or_else(|| Status::invalid_argument("client headers not populated by envoy"))?; - - let mut request_status = Status::unauthenticated("not authorized"); + .ok_or_else(|| Status::invalid_argument("client headers not populated by envoy")) + .unwrap(); if let Some(authorization) = client_headers.get("authorization") { if authorization == "Bearer valid-token" { - request_status = Status::ok("request is valid"); + return true; } } - Ok(Response::new(CheckResponse::with_status(request_status))) + return false; + } +} + +#[derive(Debug, Default)] +pub struct CheckService; + +#[tonic::async_trait] +impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { + async fn check( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let authorizer = CedarAuthorizer::new(); + if authorizer.authorize(request) { + return Ok(Response::new(CheckResponse::with_status(Status::ok("OK")))); + } + + return Ok(Response::new(CheckResponse::with_status( + Status::unauthenticated("Unauthorized"), + ))); } } diff --git a/src/main.rs b/src/main.rs index 917cace5..57f98b9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,17 +7,18 @@ use authorization::CheckService; #[tokio::main] async fn main() -> Result<(), Box> { let addr = "[::1]:50051".parse()?; - let authorization_service = AuthorizationServer::new(CheckService::default()); + let (_health_reporter, health_service) = tonic_health::server::health_reporter(); - let reflection_service = tonic_reflection::server::Builder::configure() - .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET) - .build_v1() - .unwrap(); Server::builder() - .add_service(authorization_service) + .add_service(AuthorizationServer::new(CheckService::default())) .add_service(health_service) - .add_service(reflection_service) + .add_service( + tonic_reflection::server::Builder::configure() + .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET) + .build_v1() + .unwrap(), + ) .serve(addr) .await?; -- cgit v1.2.3 From 1041f07d7ef3faedf759c31cd8b2c9df264799ff Mon Sep 17 00:00:00 2001 From: mo khan Date: Thu, 19 Jun 2025 17:38:57 -0600 Subject: refactor: rename client_headers to headers --- src/authorization.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/authorization.rs b/src/authorization.rs index d86e8938..35062d84 100644 --- a/src/authorization.rs +++ b/src/authorization.rs @@ -16,16 +16,15 @@ impl CedarAuthorizer { impl Authorizer for CedarAuthorizer { fn authorize(&self, request: CheckRequest) -> bool { - let client_headers = request + let headers = request .attributes .as_ref() .and_then(|attr| attr.request.as_ref()) .and_then(|req| req.http.as_ref()) .map(|http| &http.headers) - .ok_or_else(|| Status::invalid_argument("client headers not populated by envoy")) .unwrap(); - if let Some(authorization) = client_headers.get("authorization") { + if let Some(authorization) = headers.get("authorization") { if authorization == "Bearer valid-token" { return true; } -- cgit v1.2.3 From 2e26e151c273cbcc063eba2d08d28dc2ba5a33ec Mon Sep 17 00:00:00 2001 From: mo khan Date: Thu, 19 Jun 2025 17:45:39 -0600 Subject: refactor: split types into separate files --- src/authorization.rs | 127 ---------------------------------- src/authorization/authorizer.rs | 5 ++ src/authorization/cedar_authorizer.rs | 103 +++++++++++++++++++++++++++ src/authorization/check_service.rs | 97 ++++++++++++++++++++++++++ src/authorization/mod.rs | 7 ++ src/lib.rs | 2 +- 6 files changed, 213 insertions(+), 128 deletions(-) delete mode 100644 src/authorization.rs create mode 100644 src/authorization/authorizer.rs create mode 100644 src/authorization/cedar_authorizer.rs create mode 100644 src/authorization/check_service.rs create mode 100644 src/authorization/mod.rs diff --git a/src/authorization.rs b/src/authorization.rs deleted file mode 100644 index 35062d84..00000000 --- a/src/authorization.rs +++ /dev/null @@ -1,127 +0,0 @@ -use envoy_types::ext_authz::v3::CheckResponseExt; -use envoy_types::ext_authz::v3::pb::{CheckRequest, CheckResponse}; -use tonic::{Request, Response, Status}; - -trait Authorizer { - fn authorize(&self, request: CheckRequest) -> bool; -} - -struct CedarAuthorizer {} - -impl CedarAuthorizer { - fn new() -> CedarAuthorizer { - return CedarAuthorizer {}; - } -} - -impl Authorizer for CedarAuthorizer { - fn authorize(&self, request: CheckRequest) -> bool { - let headers = request - .attributes - .as_ref() - .and_then(|attr| attr.request.as_ref()) - .and_then(|req| req.http.as_ref()) - .map(|http| &http.headers) - .unwrap(); - - if let Some(authorization) = headers.get("authorization") { - if authorization == "Bearer valid-token" { - return true; - } - } - - return false; - } -} - -#[derive(Debug, Default)] -pub struct CheckService; - -#[tonic::async_trait] -impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { - async fn check( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - - let authorizer = CedarAuthorizer::new(); - if authorizer.authorize(request) { - return Ok(Response::new(CheckResponse::with_status(Status::ok("OK")))); - } - - return Ok(Response::new(CheckResponse::with_status( - Status::unauthenticated("Unauthorized"), - ))); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use envoy_types::ext_authz::v3::pb::{Authorization, CheckRequest}; - use std::collections::HashMap; - use tonic::Request; - - fn create_test_request_with_headers(headers: HashMap) -> Request { - use envoy_types::pb::envoy::service::auth::v3::{AttributeContext, attribute_context}; - - let http_request = attribute_context::HttpRequest { - headers, - ..Default::default() - }; - - let request_context = attribute_context::Request { - http: Some(http_request), - ..Default::default() - }; - - let attributes = AttributeContext { - request: Some(request_context), - ..Default::default() - }; - - let check_request = CheckRequest { - attributes: Some(attributes), - ..Default::default() - }; - - Request::new(check_request) - } - - fn create_headers_with_auth(auth_value: &str) -> HashMap { - let mut headers = HashMap::new(); - headers.insert("authorization".to_string(), auth_value.to_string()); - headers - } - - #[tokio::test] - async fn test_check_allows_valid_bearer_token() { - let token = String::from("valid-token"); - let server = CheckService::default(); - let headers = create_headers_with_auth(&format!("Bearer {}", token)); - let request = create_test_request_with_headers(headers); - - let response = server.check(request).await; - - assert!(response.is_ok()); - let check_response = response.unwrap().into_inner(); - assert!(check_response.status.is_some()); - let status = check_response.status.unwrap(); - assert_eq!(status.code, tonic::Code::Ok.into()); - } - - #[tokio::test] - async fn test_check_denies_invalid_bearer_token() { - let server = CheckService::default(); - let request = create_test_request_with_headers(HashMap::new()); - - let response = server.check(request).await; - - assert!(response.is_ok()); - let check_response = response.unwrap().into_inner(); - assert!(check_response.status.is_some()); - let status = check_response.status.unwrap(); - assert_eq!(status.code, tonic::Code::Unauthenticated.into()); - } -} diff --git a/src/authorization/authorizer.rs b/src/authorization/authorizer.rs new file mode 100644 index 00000000..0f700ba7 --- /dev/null +++ b/src/authorization/authorizer.rs @@ -0,0 +1,5 @@ +use envoy_types::ext_authz::v3::pb::CheckRequest; + +pub trait Authorizer { + fn authorize(&self, request: CheckRequest) -> bool; +} diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs new file mode 100644 index 00000000..2efbda28 --- /dev/null +++ b/src/authorization/cedar_authorizer.rs @@ -0,0 +1,103 @@ +use super::authorizer::Authorizer; +use envoy_types::ext_authz::v3::pb::CheckRequest; + +pub struct CedarAuthorizer {} + +impl CedarAuthorizer { + pub fn new() -> CedarAuthorizer { + CedarAuthorizer {} + } +} + +impl Default for CedarAuthorizer { + fn default() -> Self { + Self::new() + } +} + +impl Authorizer for CedarAuthorizer { + fn authorize(&self, request: CheckRequest) -> bool { + let headers = request + .attributes + .as_ref() + .and_then(|attr| attr.request.as_ref()) + .and_then(|req| req.http.as_ref()) + .map(|http| &http.headers) + .unwrap(); + + if let Some(authorization) = headers.get("authorization") { + if authorization == "Bearer valid-token" { + return true; + } + } + + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + use envoy_types::pb::envoy::service::auth::v3::{AttributeContext, attribute_context}; + use std::collections::HashMap; + + fn create_test_request_with_headers(headers: HashMap) -> CheckRequest { + let http_request = attribute_context::HttpRequest { + headers, + ..Default::default() + }; + + let request_context = attribute_context::Request { + http: Some(http_request), + ..Default::default() + }; + + let attributes = AttributeContext { + request: Some(request_context), + ..Default::default() + }; + + CheckRequest { + attributes: Some(attributes), + ..Default::default() + } + } + + #[test] + fn test_cedar_authorizer_allows_valid_token() { + let authorizer = CedarAuthorizer::new(); + let mut headers = HashMap::new(); + headers.insert( + "authorization".to_string(), + "Bearer valid-token".to_string(), + ); + let request = create_test_request_with_headers(headers); + + let result = authorizer.authorize(request); + assert!(result); + } + + #[test] + fn test_cedar_authorizer_denies_invalid_token() { + let authorizer = CedarAuthorizer::new(); + let mut headers = HashMap::new(); + headers.insert( + "authorization".to_string(), + "Bearer invalid-token".to_string(), + ); + let request = create_test_request_with_headers(headers); + + let result = authorizer.authorize(request); + assert!(!result); + } + + #[test] + fn test_cedar_authorizer_denies_missing_header() { + let authorizer = CedarAuthorizer::new(); + let headers = HashMap::new(); + let request = create_test_request_with_headers(headers); + + let result = authorizer.authorize(request); + assert!(!result); + } +} diff --git a/src/authorization/check_service.rs b/src/authorization/check_service.rs new file mode 100644 index 00000000..7ca39fcd --- /dev/null +++ b/src/authorization/check_service.rs @@ -0,0 +1,97 @@ +use envoy_types::ext_authz::v3::CheckResponseExt; +use envoy_types::ext_authz::v3::pb::{CheckRequest, CheckResponse}; +use tonic::{Request, Response, Status}; + +use super::authorizer::Authorizer; +use super::cedar_authorizer::CedarAuthorizer; + +#[derive(Debug, Default)] +pub struct CheckService; + +#[tonic::async_trait] +impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { + async fn check( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let authorizer = CedarAuthorizer::new(); + if authorizer.authorize(request) { + Ok(Response::new(CheckResponse::with_status(Status::ok("OK")))) + } else { + Ok(Response::new(CheckResponse::with_status( + Status::unauthenticated("Unauthorized"), + ))) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use envoy_types::ext_authz::v3::pb::{Authorization, CheckRequest}; + use envoy_types::pb::envoy::service::auth::v3::{AttributeContext, attribute_context}; + use std::collections::HashMap; + use tonic::Request; + + fn create_test_request_with_headers(headers: HashMap) -> Request { + let http_request = attribute_context::HttpRequest { + headers, + ..Default::default() + }; + + let request_context = attribute_context::Request { + http: Some(http_request), + ..Default::default() + }; + + let attributes = AttributeContext { + request: Some(request_context), + ..Default::default() + }; + + let check_request = CheckRequest { + attributes: Some(attributes), + ..Default::default() + }; + + Request::new(check_request) + } + + fn create_headers_with_auth(auth_value: &str) -> HashMap { + let mut headers = HashMap::new(); + headers.insert("authorization".to_string(), auth_value.to_string()); + headers + } + + #[tokio::test] + async fn test_check_allows_valid_bearer_token() { + let token = String::from("valid-token"); + let server = CheckService::default(); + let headers = create_headers_with_auth(&format!("Bearer {}", token)); + let request = create_test_request_with_headers(headers); + + let response = server.check(request).await; + + assert!(response.is_ok()); + let check_response = response.unwrap().into_inner(); + assert!(check_response.status.is_some()); + let status = check_response.status.unwrap(); + assert_eq!(status.code, tonic::Code::Ok.into()); + } + + #[tokio::test] + async fn test_check_denies_invalid_bearer_token() { + let server = CheckService::default(); + let request = create_test_request_with_headers(HashMap::new()); + + let response = server.check(request).await; + + assert!(response.is_ok()); + let check_response = response.unwrap().into_inner(); + assert!(check_response.status.is_some()); + let status = check_response.status.unwrap(); + assert_eq!(status.code, tonic::Code::Unauthenticated.into()); + } +} diff --git a/src/authorization/mod.rs b/src/authorization/mod.rs new file mode 100644 index 00000000..7d3856a5 --- /dev/null +++ b/src/authorization/mod.rs @@ -0,0 +1,7 @@ +pub mod authorizer; +pub mod cedar_authorizer; +pub mod check_service; + +pub use authorizer::Authorizer; +pub use cedar_authorizer::CedarAuthorizer; +pub use check_service::CheckService; diff --git a/src/lib.rs b/src/lib.rs index b3791974..c24db79b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,3 @@ pub mod authorization; -pub use authorization::CheckService; +pub use authorization::{Authorizer, CedarAuthorizer, CheckService}; -- cgit v1.2.3 From e3fec7bf38d6070c9fb547ab08670e556ec974ab Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 20 Jun 2025 10:37:56 -0600 Subject: refactor: experiment with rust generics --- src/authorization/cedar_authorizer.rs | 38 +++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index 2efbda28..ff4c3c5b 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -41,11 +41,22 @@ mod tests { use envoy_types::pb::envoy::service::auth::v3::{AttributeContext, attribute_context}; use std::collections::HashMap; + fn build() -> T { + T::default() + } + + fn build_with(initializer: impl std::ops::FnOnce(T) -> T) -> T { + let item = build::(); + initializer(item) + } + fn create_test_request_with_headers(headers: HashMap) -> CheckRequest { - let http_request = attribute_context::HttpRequest { - headers, - ..Default::default() - }; + let http_request = build_with::( + |mut item: attribute_context::HttpRequest| { + item.headers = headers; + item + }, + ); let request_context = attribute_context::Request { http: Some(http_request), @@ -100,4 +111,23 @@ mod tests { let result = authorizer.authorize(request); assert!(!result); } + + // test css passthrough + // test javascript passthrough + // test ico passthrough + // test png,jpg,bmp passthrough + // test html passthrough + // #[test] + // fn authorize_test_css_passthrough() { + // let authorizer = CedarAuthorizer::new(); + + // let request = CheckRequest { + // attributes: Some(AttributeContext { + // ..Default::default() + // }), + // }; + // let result = authorizer.authorize(request); + + // assert!(result) + // } } -- cgit v1.2.3 From e88297eb165bc680bfaa7adc3f5feaf3691bf51c Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 20 Jun 2025 10:41:35 -0600 Subject: refactor: collapse object initializers into a single one --- src/authorization/cedar_authorizer.rs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index ff4c3c5b..cefdef2b 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -51,22 +51,20 @@ mod tests { } fn create_test_request_with_headers(headers: HashMap) -> CheckRequest { - let http_request = build_with::( - |mut item: attribute_context::HttpRequest| { - item.headers = headers; - item - }, - ); - - let request_context = attribute_context::Request { - http: Some(http_request), - ..Default::default() - }; - - let attributes = AttributeContext { - request: Some(request_context), - ..Default::default() - }; + let attributes = build_with::(|mut item: AttributeContext| { + item.request = Some(build_with::( + |mut item: attribute_context::Request| { + item.http = Some(build_with::( + |mut item: attribute_context::HttpRequest| { + item.headers = headers; + item + }, + )); + item + }, + )); + item + }); CheckRequest { attributes: Some(attributes), -- cgit v1.2.3 From 57c7bd667bc9fc809d1e1ee49c0696a13d3bfd8d Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 20 Jun 2025 10:43:27 -0600 Subject: refactor: collapse initializers in a functional chain --- src/authorization/cedar_authorizer.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index cefdef2b..cc1fc93b 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -51,12 +51,17 @@ mod tests { } fn create_test_request_with_headers(headers: HashMap) -> CheckRequest { - let attributes = build_with::(|mut item: AttributeContext| { - item.request = Some(build_with::( - |mut item: attribute_context::Request| { - item.http = Some(build_with::( - |mut item: attribute_context::HttpRequest| { - item.headers = headers; + build_with::(|mut item: CheckRequest| { + item.attributes = Some(build_with::( + |mut item: AttributeContext| { + item.request = Some(build_with::( + |mut item: attribute_context::Request| { + item.http = Some(build_with::( + |mut item: attribute_context::HttpRequest| { + item.headers = headers; + item + }, + )); item }, )); @@ -64,12 +69,7 @@ mod tests { }, )); item - }); - - CheckRequest { - attributes: Some(attributes), - ..Default::default() - } + }) } #[test] -- cgit v1.2.3 From e88052fe49c42350d392624101af4be1b10680cb Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 20 Jun 2025 10:51:56 -0600 Subject: refactor: improve generic builder methods --- src/authorization/cedar_authorizer.rs | 37 +++++++++++++++-------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index cc1fc93b..547a1318 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -45,30 +45,25 @@ mod tests { T::default() } - fn build_with(initializer: impl std::ops::FnOnce(T) -> T) -> T { - let item = build::(); - initializer(item) + fn build_with(initializer: F) -> T + where + T: Default, + F: std::ops::FnOnce(&mut T), + { + let mut item = build::(); + initializer(&mut item); + item } fn create_test_request_with_headers(headers: HashMap) -> CheckRequest { - build_with::(|mut item: CheckRequest| { - item.attributes = Some(build_with::( - |mut item: AttributeContext| { - item.request = Some(build_with::( - |mut item: attribute_context::Request| { - item.http = Some(build_with::( - |mut item: attribute_context::HttpRequest| { - item.headers = headers; - item - }, - )); - item - }, - )); - item - }, - )); - item + build_with(|item: &mut CheckRequest| { + item.attributes = Some(build_with(|item: &mut AttributeContext| { + item.request = Some(build_with(|item: &mut attribute_context::Request| { + item.http = Some(build_with(|item: &mut attribute_context::HttpRequest| { + item.headers = headers; + })); + })); + })); }) } -- cgit v1.2.3 From 468f1b652080c94d5d8668cf8f6e650af00a782d Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 20 Jun 2025 11:09:15 -0600 Subject: refactor: extract an x module --- src/authorization/cedar_authorizer.rs | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index 547a1318..16a3f405 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -35,17 +35,12 @@ impl Authorizer for CedarAuthorizer { } } -#[cfg(test)] -mod tests { - use super::*; - use envoy_types::pb::envoy::service::auth::v3::{AttributeContext, attribute_context}; - use std::collections::HashMap; - - fn build() -> T { +mod x { + pub fn build() -> T { T::default() } - fn build_with(initializer: F) -> T + pub fn build_with(initializer: F) -> T where T: Default, F: std::ops::FnOnce(&mut T), @@ -54,14 +49,23 @@ mod tests { initializer(&mut item); item } +} + +#[cfg(test)] +mod tests { + use super::*; + use envoy_types::pb::envoy::service::auth::v3::{AttributeContext, attribute_context}; + use std::collections::HashMap; fn create_test_request_with_headers(headers: HashMap) -> CheckRequest { - build_with(|item: &mut CheckRequest| { - item.attributes = Some(build_with(|item: &mut AttributeContext| { - item.request = Some(build_with(|item: &mut attribute_context::Request| { - item.http = Some(build_with(|item: &mut attribute_context::HttpRequest| { - item.headers = headers; - })); + x::build_with(|item: &mut CheckRequest| { + item.attributes = Some(x::build_with(|item: &mut AttributeContext| { + item.request = Some(x::build_with(|item: &mut attribute_context::Request| { + item.http = Some(x::build_with( + |item: &mut attribute_context::HttpRequest| { + item.headers = headers; + }, + )); })); })); }) -- cgit v1.2.3 From 63ce8f874564369ceabfbb023c7516c9bcfd3838 Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 20 Jun 2025 11:45:59 -0600 Subject: refactor: use please builders --- Cargo.lock | 6 ++++++ Cargo.toml | 1 + src/authorization/cedar_authorizer.rs | 36 +++++++++++------------------------ 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d1c3bdc..9ceb88cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,7 @@ name = "authzd" version = "0.1.0" dependencies = [ "envoy-types", + "please", "tokio", "tokio-test", "tonic", @@ -567,6 +568,11 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "please" +version = "0.1.0" +source = "git+https://github.com/xlgmokha/please.git#bff13caf9ee2f806dd32c5d77f0e4ac9eb28c7f5" + [[package]] name = "prettyplease" version = "0.2.34" diff --git a/Cargo.toml b/Cargo.toml index a13b9464..5eeecac3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ path = "src/lib.rs" [dependencies] envoy-types = "0.6.0" +please = { git = "https://github.com/xlgmokha/please.git", version = "0.1.0" } tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } tonic = "*" tonic-health = "0.13.1" diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index 16a3f405..c1f5455d 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -35,22 +35,6 @@ impl Authorizer for CedarAuthorizer { } } -mod x { - pub fn build() -> T { - T::default() - } - - pub fn build_with(initializer: F) -> T - where - T: Default, - F: std::ops::FnOnce(&mut T), - { - let mut item = build::(); - initializer(&mut item); - item - } -} - #[cfg(test)] mod tests { use super::*; @@ -58,15 +42,17 @@ mod tests { use std::collections::HashMap; fn create_test_request_with_headers(headers: HashMap) -> CheckRequest { - x::build_with(|item: &mut CheckRequest| { - item.attributes = Some(x::build_with(|item: &mut AttributeContext| { - item.request = Some(x::build_with(|item: &mut attribute_context::Request| { - item.http = Some(x::build_with( - |item: &mut attribute_context::HttpRequest| { - item.headers = headers; - }, - )); - })); + please::build_with(|item: &mut CheckRequest| { + item.attributes = Some(please::build_with(|item: &mut AttributeContext| { + item.request = Some(please::build_with( + |item: &mut attribute_context::Request| { + item.http = Some(please::build_with( + |item: &mut attribute_context::HttpRequest| { + item.headers = headers; + }, + )); + }, + )); })); }) } -- cgit v1.2.3 From b7c3a1c50d616cf68155e76a9f143b98bc29fb35 Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 20 Jun 2025 14:29:40 -0600 Subject: test: extract helper function to create a check request --- src/authorization/cedar_authorizer.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index c1f5455d..49659d35 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -41,15 +41,15 @@ mod tests { use envoy_types::pb::envoy::service::auth::v3::{AttributeContext, attribute_context}; use std::collections::HashMap; - fn create_test_request_with_headers(headers: HashMap) -> CheckRequest { + fn create_request( + f: impl std::ops::FnOnce(&mut attribute_context::HttpRequest), + ) -> CheckRequest { please::build_with(|item: &mut CheckRequest| { item.attributes = Some(please::build_with(|item: &mut AttributeContext| { item.request = Some(please::build_with( |item: &mut attribute_context::Request| { item.http = Some(please::build_with( - |item: &mut attribute_context::HttpRequest| { - item.headers = headers; - }, + |item: &mut attribute_context::HttpRequest| f(item), )); }, )); @@ -57,6 +57,12 @@ mod tests { }) } + fn create_test_request_with_headers(headers: HashMap) -> CheckRequest { + return create_request(|item: &mut attribute_context::HttpRequest| { + item.headers = headers; + }); + } + #[test] fn test_cedar_authorizer_allows_valid_token() { let authorizer = CedarAuthorizer::new(); -- cgit v1.2.3 From 081dafdebcb7369b918f483dd135219980160304 Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 20 Jun 2025 14:54:21 -0600 Subject: test: delegate to create_request helper --- src/authorization/cedar_authorizer.rs | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index 49659d35..44bc9e06 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -38,31 +38,20 @@ impl Authorizer for CedarAuthorizer { #[cfg(test)] mod tests { use super::*; - use envoy_types::pb::envoy::service::auth::v3::{AttributeContext, attribute_context}; + use envoy_types::pb::envoy::service::auth::v3::AttributeContext; + use envoy_types::pb::envoy::service::auth::v3::attribute_context::{HttpRequest, Request}; use std::collections::HashMap; - fn create_request( - f: impl std::ops::FnOnce(&mut attribute_context::HttpRequest), - ) -> CheckRequest { + fn create_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest { please::build_with(|item: &mut CheckRequest| { item.attributes = Some(please::build_with(|item: &mut AttributeContext| { - item.request = Some(please::build_with( - |item: &mut attribute_context::Request| { - item.http = Some(please::build_with( - |item: &mut attribute_context::HttpRequest| f(item), - )); - }, - )); + item.request = Some(please::build_with(|item: &mut Request| { + item.http = Some(please::build_with(|item: &mut HttpRequest| f(item))); + })); })); }) } - fn create_test_request_with_headers(headers: HashMap) -> CheckRequest { - return create_request(|item: &mut attribute_context::HttpRequest| { - item.headers = headers; - }); - } - #[test] fn test_cedar_authorizer_allows_valid_token() { let authorizer = CedarAuthorizer::new(); @@ -71,7 +60,9 @@ mod tests { "authorization".to_string(), "Bearer valid-token".to_string(), ); - let request = create_test_request_with_headers(headers); + let request = create_request(|item: &mut HttpRequest| { + item.headers = headers; + }); let result = authorizer.authorize(request); assert!(result); @@ -85,7 +76,9 @@ mod tests { "authorization".to_string(), "Bearer invalid-token".to_string(), ); - let request = create_test_request_with_headers(headers); + let request = create_request(|item: &mut HttpRequest| { + item.headers = headers; + }); let result = authorizer.authorize(request); assert!(!result); @@ -95,7 +88,9 @@ mod tests { fn test_cedar_authorizer_denies_missing_header() { let authorizer = CedarAuthorizer::new(); let headers = HashMap::new(); - let request = create_test_request_with_headers(headers); + let request = create_request(|item: &mut HttpRequest| { + item.headers = headers; + }); let result = authorizer.authorize(request); assert!(!result); -- cgit v1.2.3 From a0537b163037a92652ec92c1f47945e0572bb76e Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 20 Jun 2025 14:58:50 -0600 Subject: refactor: copy create_request to test common mod --- tests/common/mod.rs | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index e2137146..9b2370cb 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,31 +1,24 @@ use envoy_types::ext_authz::v3::pb::CheckRequest; +use envoy_types::pb::envoy::service::auth::v3::AttributeContext; +use envoy_types::pb::envoy::service::auth::v3::attribute_context::{HttpRequest, Request}; use std::collections::HashMap; -use tonic::Request; -pub fn create_test_request_with_headers(headers: HashMap) -> Request { - use envoy_types::pb::envoy::service::auth::v3::{AttributeContext, attribute_context}; - - let http_request = attribute_context::HttpRequest { - headers, - ..Default::default() - }; - - let request_context = attribute_context::Request { - http: Some(http_request), - ..Default::default() - }; - - let attributes = AttributeContext { - request: Some(request_context), - ..Default::default() - }; - - let check_request = CheckRequest { - attributes: Some(attributes), - ..Default::default() - }; +pub fn create_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest { + please::build_with(|item: &mut CheckRequest| { + item.attributes = Some(please::build_with(|item: &mut AttributeContext| { + item.request = Some(please::build_with(|item: &mut Request| { + item.http = Some(please::build_with(|item: &mut HttpRequest| f(item))); + })); + })); + }) +} - Request::new(check_request) +pub fn create_test_request_with_headers( + headers: HashMap, +) -> tonic::Request { + tonic::Request::new(create_request(|item: &mut HttpRequest| { + item.headers = headers; + })) } pub fn create_headers_with_auth(auth_value: &str) -> HashMap { -- cgit v1.2.3 From 85490a4cfa7f3836d3d2f1e7cbfe48b668aa484b Mon Sep 17 00:00:00 2001 From: mo khan Date: Tue, 24 Jun 2025 14:36:58 -0600 Subject: feat: connect check service to a minimal cedar policy --- Cargo.lock | 1234 ++++++++++++++++++++++++++++++++- Cargo.toml | 9 +- mise.toml | 6 - policies/auth_policy.cedar | 23 + src/authorization/authorizer.rs | 2 +- src/authorization/cedar_authorizer.rs | 123 +++- src/authorization/check_service.rs | 29 +- src/main.rs | 8 +- tests/integration_tests.rs | 15 +- 9 files changed, 1381 insertions(+), 68 deletions(-) delete mode 100644 mise.toml create mode 100644 policies/auth_policy.cedar diff --git a/Cargo.lock b/Cargo.lock index 9ceb88cf..45af23e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,12 +26,42 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -75,6 +105,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" name = "authzd" version = "0.1.0" dependencies = [ + "cedar-policy", "envoy-types", "please", "tokio", @@ -85,6 +116,12 @@ dependencies = [ "tonic-reflection", ] +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "axum" version = "0.8.4" @@ -142,7 +179,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -151,30 +188,319 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +dependencies = [ + "cfg_aliases", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "cc" +version = "1.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +dependencies = [ + "shlex", +] + +[[package]] +name = "cedar-policy" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d37df3afde553492d8413d02a733e86a786c29f5b36c6d8d72bc6e5c0c7f63" +dependencies = [ + "cedar-policy-core", + "cedar-policy-formatter", + "cedar-policy-validator", + "itertools 0.14.0", + "lalrpop-util", + "lazy_static", + "miette", + "nonempty", + "ref-cast", + "semver", + "serde", + "serde_json", + "serde_with", + "smol_str", + "thiserror", +] + +[[package]] +name = "cedar-policy-core" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd896075d493e8283e960c94cf85d85773dc3359cf948a5f5ea88a6f249e8665" +dependencies = [ + "chrono", + "educe", + "either", + "itertools 0.14.0", + "lalrpop", + "lalrpop-util", + "lazy_static", + "miette", + "nonempty", + "ref-cast", + "regex", + "rustc_lexer", + "serde", + "serde_json", + "serde_with", + "smol_str", + "stacker", + "thiserror", +] + +[[package]] +name = "cedar-policy-formatter" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95db50a024d5ed3efe4b9b513124b564313716ada017860c17995ec227ab46b1" +dependencies = [ + "cedar-policy-core", + "itertools 0.14.0", + "lazy_static", + "logos", + "miette", + "pretty", + "regex", + "smol_str", +] + +[[package]] +name = "cedar-policy-validator" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f40762e669e4ec5a4b3a2b3d3aa2567d768aa74b735760f13197312c1150f69" +dependencies = [ + "cedar-policy-core", + "educe", + "itertools 0.14.0", + "lalrpop", + "lalrpop-util", + "lazy_static", + "miette", + "nonempty", + "ref-cast", + "serde", + "serde_json", + "serde_with", + "smol_str", + "stacker", + "thiserror", + "unicode-security", +] + [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "envoy-types" version = "0.6.0" @@ -194,12 +520,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -214,6 +540,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "fnv" version = "1.0.7" @@ -259,6 +591,16 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.3.3" @@ -289,13 +631,19 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.9.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.4" @@ -308,6 +656,21 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "http" version = "1.3.1" @@ -409,6 +772,47 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.9.0" @@ -416,7 +820,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.4", + "serde", ] [[package]] @@ -443,6 +848,63 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools 0.14.0", + "lalrpop-util", + "petgraph 0.7.1", + "pico-args", + "regex", + "regex-syntax", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "regex-automata", + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.174" @@ -455,6 +917,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.27" @@ -462,17 +934,74 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] -name = "matchit" -version = "0.8.4" +name = "logos" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +checksum = "ab6f536c1af4c7cc81edf73da1f8029896e7e1e16a219ef09b184e76a296f3db" +dependencies = [ + "logos-derive", +] [[package]] -name = "memchr" -version = "2.7.5" +name = "logos-codegen" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - +checksum = "189bbfd0b61330abea797e5e9276408f2edbe4f822d7ad08685d67419aafb34e" +dependencies = [ + "beef", + "fnv", + "lazy_static", + "proc-macro2", + "quote", + "regex-syntax", + "rustc_version", + "syn", +] + +[[package]] +name = "logos-derive" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebfe8e1a19049ddbfccbd14ac834b215e11b85b90bab0c2dba7c7b92fb5d5cba" +dependencies = [ + "logos-codegen", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "miette-derive", + "serde", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "mime" version = "0.3.17" @@ -505,6 +1034,36 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nonempty" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "303e8749c804ccd6ca3b428de7fe0d86cb86bc7606bc15291f100fd487960bb8" +dependencies = [ + "serde", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.7" @@ -520,6 +1079,29 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -532,10 +1114,35 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ - "fixedbitset", - "indexmap", + "fixedbitset 0.4.2", + "indexmap 2.9.0", ] +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset 0.5.7", + "indexmap 2.9.0", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.10" @@ -573,11 +1180,34 @@ name = "please" version = "0.1.0" source = "git+https://github.com/xlgmokha/please.git#bff13caf9ee2f806dd32c5d77f0e4ac9eb28c7f5" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "pretty" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac98773b7109bc75f475ab5a134c9b64b87e59d776d31098d8f346922396a477" +dependencies = [ + "arrayvec", + "typed-arena", + "unicode-width", +] + [[package]] name = "prettyplease" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" dependencies = [ "proc-macro2", "syn", @@ -624,7 +1254,7 @@ dependencies = [ "log", "multimap", "once_cell", - "petgraph", + "petgraph 0.6.5", "prettyplease", "prost 0.12.6", "prost-types 0.12.6", @@ -677,6 +1307,15 @@ dependencies = [ "prost 0.13.5", ] +[[package]] +name = "psm" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" +dependencies = [ + "cc", +] + [[package]] name = "quote" version = "1.0.40" @@ -692,6 +1331,35 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "regex" version = "1.11.1" @@ -727,6 +1395,24 @@ version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +[[package]] +name = "rustc_lexer" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86aae0c77166108c01305ee1a36a1e77289d7dc6ca0a3cd91ff4992de2d16a5" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.0.7" @@ -746,6 +1432,45 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" version = "1.0.219" @@ -766,6 +1491,72 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "indexmap 2.9.0", + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.9.0", + "schemars", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81679d9ed988d5e9a5e6531dc3f2c28efbd639cbd1dfb628df08edea6004da77" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.10" @@ -778,6 +1569,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "smol_str" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9676b89cd56310a87b93dec47b11af744f34d5fc9f367b829474eec0a891350d" +dependencies = [ + "borsh", + "serde", +] + [[package]] name = "socket2" version = "0.5.10" @@ -788,11 +1589,42 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "stacker" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" -version = "2.0.103" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -818,6 +1650,82 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "term" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a984c8d058c627faaf5e8e2ed493fa3c51771889196de1016cf9c1c6e90d750" +dependencies = [ + "home", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.45.1" @@ -958,7 +1866,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "indexmap", + "indexmap 2.9.0", "pin-project-lite", "slab", "sync_wrapper", @@ -1018,12 +1926,77 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-script" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" + +[[package]] +name = "unicode-security" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e4ddba1535dd35ed8b61c52166b7155d7f4e4b8847cec6f48e71dc66d8b5e50" +dependencies = [ + "unicode-normalization", + "unicode-script", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -1048,13 +2021,139 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1063,7 +2162,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", ] [[package]] @@ -1072,14 +2180,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -1088,48 +2212,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/Cargo.toml b/Cargo.toml index 5eeecac3..415f3806 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,13 +12,14 @@ name = "authzd" path = "src/lib.rs" [dependencies] +cedar-policy = "4.4.1" envoy-types = "0.6.0" please = { git = "https://github.com/xlgmokha/please.git", version = "0.1.0" } -tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } -tonic = "*" +tokio = { version = "1.0.0", features = ["macros", "rt-multi-thread"] } +tonic = "0.13.1" tonic-health = "0.13.1" tonic-reflection = "0.13.1" [dev-dependencies] -tokio-test = "0.4" -tonic-build = "0.10" +tokio-test = "0.4.0" +tonic-build = "0.10.0" diff --git a/mise.toml b/mise.toml deleted file mode 100644 index c466bc2b..00000000 --- a/mise.toml +++ /dev/null @@ -1,6 +0,0 @@ -[tools] -cargo = "latest" -grpcurl = "latest" -make = "latest" -rust = "stable" -yamlfmt = "latest" diff --git a/policies/auth_policy.cedar b/policies/auth_policy.cedar new file mode 100644 index 00000000..27e2bd1b --- /dev/null +++ b/policies/auth_policy.cedar @@ -0,0 +1,23 @@ +// Authorization policies for the authzd service + +// Allow requests with valid Bearer tokens +permit(principal, action == Action::"check", resource) +when { + context has bearer_token && + context.bearer_token == "valid-token" +}; + +// Allow static assets to pass through without authentication +permit(principal, action == Action::"check", resource) +when { + context has path && + (context.path like "*.css" || + context.path like "*.js" || + context.path like "*.ico" || + context.path like "*.png" || + context.path like "*.jpg" || + context.path like "*.jpeg" || + context.path like "*.gif" || + context.path like "*.bmp" || + context.path like "*.html") +}; \ No newline at end of file diff --git a/src/authorization/authorizer.rs b/src/authorization/authorizer.rs index 0f700ba7..14a7df27 100644 --- a/src/authorization/authorizer.rs +++ b/src/authorization/authorizer.rs @@ -1,5 +1,5 @@ use envoy_types::ext_authz::v3::pb::CheckRequest; -pub trait Authorizer { +pub trait Authorizer: std::fmt::Debug { fn authorize(&self, request: CheckRequest) -> bool; } diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index 44bc9e06..577b75ba 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -1,11 +1,28 @@ use super::authorizer::Authorizer; +use cedar_policy::{ + Authorizer as CedarAuth, Context, Entities, EntityId, EntityTypeName, EntityUid, PolicySet, + Request as CedarRequest, RestrictedExpression, +}; use envoy_types::ext_authz::v3::pb::CheckRequest; +use std::collections::HashMap; +use std::str::FromStr; -pub struct CedarAuthorizer {} +#[derive(Debug)] +pub struct CedarAuthorizer { + policies: PolicySet, + authorizer: CedarAuth, +} impl CedarAuthorizer { pub fn new() -> CedarAuthorizer { - CedarAuthorizer {} + let policy_src = include_str!("../../policies/auth_policy.cedar"); + let policies = policy_src.parse().expect("Failed to parse Cedar policies"); + let authorizer = CedarAuth::new(); + + CedarAuthorizer { + policies, + authorizer, + } } } @@ -17,21 +34,83 @@ impl Default for CedarAuthorizer { impl Authorizer for CedarAuthorizer { fn authorize(&self, request: CheckRequest) -> bool { - let headers = request + let headers = match request .attributes .as_ref() .and_then(|attr| attr.request.as_ref()) .and_then(|req| req.http.as_ref()) .map(|http| &http.headers) - .unwrap(); - - if let Some(authorization) = headers.get("authorization") { - if authorization == "Bearer valid-token" { - return true; + { + Some(headers) => headers, + None => return false, + }; + + // Extract authorization token + let bearer_token = headers + .get("authorization") + .and_then(|auth| auth.strip_prefix("Bearer ")) + .unwrap_or(""); + + // Extract request path for static asset checking + let path = headers + .get(":path") + .or_else(|| headers.get("path")) + .map_or("", |v| v.as_str()); + + // Create Cedar entities and request + match self.create_cedar_request(bearer_token, path) { + Ok(cedar_request) => { + let entities = Entities::empty(); + let response = + self.authorizer + .is_authorized(&cedar_request, &self.policies, &entities); + matches!(response.decision(), cedar_policy::Decision::Allow) } + Err(_) => false, } + } +} - false +impl CedarAuthorizer { + fn create_cedar_request( + &self, + bearer_token: &str, + path: &str, + ) -> Result> { + // Create principal entity + let principal_id = EntityId::from_str("client")?; + let principal_type = EntityTypeName::from_str("User")?; + let principal = EntityUid::from_type_name_and_id(principal_type, principal_id); + + // Create action entity + let action_id = EntityId::from_str("check")?; + let action_type = EntityTypeName::from_str("Action")?; + let action = EntityUid::from_type_name_and_id(action_type, action_id); + + // Create resource entity + let resource_id = EntityId::from_str("resource")?; + let resource_type = EntityTypeName::from_str("Resource")?; + let resource = EntityUid::from_type_name_and_id(resource_type, resource_id); + + // Create context with bearer token and path + let mut context_map = HashMap::new(); + if !bearer_token.is_empty() { + context_map.insert( + "bearer_token".to_string(), + RestrictedExpression::from_str(&format!("\"{}\"", bearer_token))?, + ); + } + if !path.is_empty() { + context_map.insert( + "path".to_string(), + RestrictedExpression::from_str(&format!("\"{}\"", path))?, + ); + } + + let context = Context::from_pairs(context_map.into_iter().collect::>())?; + + CedarRequest::new(principal, action, resource, context, None) + .map_err(|e| Box::new(e) as Box) } } @@ -96,6 +175,32 @@ mod tests { assert!(!result); } + #[test] + fn test_cedar_authorizer_allows_static_assets() { + let authorizer = CedarAuthorizer::new(); + let mut headers = HashMap::new(); + headers.insert(":path".to_string(), "/static/style.css".to_string()); + let request = create_request(|item: &mut HttpRequest| { + item.headers = headers; + }); + + let result = authorizer.authorize(request); + assert!(result); + } + + #[test] + fn test_cedar_authorizer_allows_js_assets() { + let authorizer = CedarAuthorizer::new(); + let mut headers = HashMap::new(); + headers.insert(":path".to_string(), "/app.js".to_string()); + let request = create_request(|item: &mut HttpRequest| { + item.headers = headers; + }); + + let result = authorizer.authorize(request); + assert!(result); + } + // test css passthrough // test javascript passthrough // test ico passthrough diff --git a/src/authorization/check_service.rs b/src/authorization/check_service.rs index 7ca39fcd..a4d0ec7b 100644 --- a/src/authorization/check_service.rs +++ b/src/authorization/check_service.rs @@ -1,12 +1,20 @@ use envoy_types::ext_authz::v3::CheckResponseExt; use envoy_types::ext_authz::v3::pb::{CheckRequest, CheckResponse}; +use std::sync::Arc; use tonic::{Request, Response, Status}; use super::authorizer::Authorizer; -use super::cedar_authorizer::CedarAuthorizer; -#[derive(Debug, Default)] -pub struct CheckService; +#[derive(Debug)] +pub struct CheckService { + authorizer: Arc, +} + +impl CheckService { + pub fn new(authorizer: Arc) -> Self { + Self { authorizer } + } +} #[tonic::async_trait] impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { @@ -16,8 +24,7 @@ impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { ) -> Result, Status> { let request = request.into_inner(); - let authorizer = CedarAuthorizer::new(); - if authorizer.authorize(request) { + if self.authorizer.authorize(request) { Ok(Response::new(CheckResponse::with_status(Status::ok("OK")))) } else { Ok(Response::new(CheckResponse::with_status( @@ -30,9 +37,11 @@ impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { #[cfg(test)] mod tests { use super::*; + use super::super::cedar_authorizer::CedarAuthorizer; use envoy_types::ext_authz::v3::pb::{Authorization, CheckRequest}; use envoy_types::pb::envoy::service::auth::v3::{AttributeContext, attribute_context}; use std::collections::HashMap; + use std::sync::Arc; use tonic::Request; fn create_test_request_with_headers(headers: HashMap) -> Request { @@ -68,7 +77,8 @@ mod tests { #[tokio::test] async fn test_check_allows_valid_bearer_token() { let token = String::from("valid-token"); - let server = CheckService::default(); + let authorizer = Arc::new(CedarAuthorizer::new()); + let server = CheckService::new(authorizer); let headers = create_headers_with_auth(&format!("Bearer {}", token)); let request = create_test_request_with_headers(headers); @@ -78,12 +88,13 @@ mod tests { let check_response = response.unwrap().into_inner(); assert!(check_response.status.is_some()); let status = check_response.status.unwrap(); - assert_eq!(status.code, tonic::Code::Ok.into()); + assert_eq!(status.code, tonic::Code::Ok as i32); } #[tokio::test] async fn test_check_denies_invalid_bearer_token() { - let server = CheckService::default(); + let authorizer = Arc::new(CedarAuthorizer::new()); + let server = CheckService::new(authorizer); let request = create_test_request_with_headers(HashMap::new()); let response = server.check(request).await; @@ -92,6 +103,6 @@ mod tests { let check_response = response.unwrap().into_inner(); assert!(check_response.status.is_some()); let status = check_response.status.unwrap(); - assert_eq!(status.code, tonic::Code::Unauthenticated.into()); + assert_eq!(status.code, tonic::Code::Unauthenticated as i32); } } diff --git a/src/main.rs b/src/main.rs index 57f98b9a..8eb7b5ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ use envoy_types::ext_authz::v3::pb::AuthorizationServer; +use std::sync::Arc; use tonic::transport::Server; pub mod authorization; -use authorization::CheckService; +use authorization::{CedarAuthorizer, CheckService}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -10,8 +11,11 @@ async fn main() -> Result<(), Box> { let (_health_reporter, health_service) = tonic_health::server::health_reporter(); + let authorizer = Arc::new(CedarAuthorizer::new()); + let check_service = CheckService::new(authorizer); + Server::builder() - .add_service(AuthorizationServer::new(CheckService::default())) + .add_service(AuthorizationServer::new(check_service)) .add_service(health_service) .add_service( tonic_reflection::server::Builder::configure() diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index a3603c11..9bbeaea5 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,11 +1,13 @@ -use authzd::CheckService; +use authzd::{CedarAuthorizer, CheckService}; use envoy_types::ext_authz::v3::pb::Authorization; +use std::sync::Arc; mod common; #[tokio::test] async fn test_success_response() { - let server = CheckService::default(); + let authorizer = Arc::new(CedarAuthorizer::new()); + let server = CheckService::new(authorizer); let headers = common::create_headers_with_auth("Bearer valid-token"); let request = common::create_test_request_with_headers(headers); @@ -17,12 +19,13 @@ async fn test_success_response() { assert!(check_response.status.is_some()); let status = check_response.status.unwrap(); - assert_eq!(status.code, tonic::Code::Ok.into()); + assert_eq!(status.code, tonic::Code::Ok as i32); } #[tokio::test] async fn test_multiple() { - let server = CheckService::default(); + let authorizer = Arc::new(CedarAuthorizer::new()); + let server = CheckService::new(authorizer); let test_cases = vec![ ("Bearer valid-token", true), @@ -42,9 +45,9 @@ async fn test_multiple() { let status = check_response.status.unwrap(); if should_succeed { - assert_eq!(status.code, tonic::Code::Ok.into()); + assert_eq!(status.code, tonic::Code::Ok as i32); } else { - assert_eq!(status.code, tonic::Code::Unauthenticated.into()); + assert_eq!(status.code, tonic::Code::Unauthenticated as i32); } } } -- cgit v1.2.3 From d4e3bb8f30c4b9edeb392881bd7b4a6baf79d415 Mon Sep 17 00:00:00 2001 From: mo khan Date: Tue, 24 Jun 2025 16:41:03 -0600 Subject: refactor: copy duplicate test helper method --- src/authorization/check_service.rs | 59 ++++++++++++++++---------------------- src/lib.rs | 1 - src/main.rs | 13 ++++----- 3 files changed, 30 insertions(+), 43 deletions(-) diff --git a/src/authorization/check_service.rs b/src/authorization/check_service.rs index a4d0ec7b..c0a05e21 100644 --- a/src/authorization/check_service.rs +++ b/src/authorization/check_service.rs @@ -36,51 +36,38 @@ impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { #[cfg(test)] mod tests { - use super::*; use super::super::cedar_authorizer::CedarAuthorizer; + use super::*; use envoy_types::ext_authz::v3::pb::{Authorization, CheckRequest}; - use envoy_types::pb::envoy::service::auth::v3::{AttributeContext, attribute_context}; + use envoy_types::pb::envoy::service::auth::v3::AttributeContext; + use envoy_types::pb::envoy::service::auth::v3::attribute_context::{HttpRequest, Request}; use std::collections::HashMap; use std::sync::Arc; - use tonic::Request; - - fn create_test_request_with_headers(headers: HashMap) -> Request { - let http_request = attribute_context::HttpRequest { - headers, - ..Default::default() - }; - - let request_context = attribute_context::Request { - http: Some(http_request), - ..Default::default() - }; - - let attributes = AttributeContext { - request: Some(request_context), - ..Default::default() - }; - - let check_request = CheckRequest { - attributes: Some(attributes), - ..Default::default() - }; - Request::new(check_request) + pub fn create_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest { + please::build_with(|item: &mut CheckRequest| { + item.attributes = Some(please::build_with(|item: &mut AttributeContext| { + item.request = Some(please::build_with(|item: &mut Request| { + item.http = Some(please::build_with(|item: &mut HttpRequest| f(item))); + })); + })); + }) } - fn create_headers_with_auth(auth_value: &str) -> HashMap { - let mut headers = HashMap::new(); - headers.insert("authorization".to_string(), auth_value.to_string()); - headers + pub fn create_token() -> String { + return String::from("valid-token"); } #[tokio::test] async fn test_check_allows_valid_bearer_token() { - let token = String::from("valid-token"); - let authorizer = Arc::new(CedarAuthorizer::new()); - let server = CheckService::new(authorizer); - let headers = create_headers_with_auth(&format!("Bearer {}", token)); - let request = create_test_request_with_headers(headers); + let token = create_token(); + let server = CheckService::new(Arc::new(CedarAuthorizer::new())); + + let mut headers = HashMap::new(); + headers.insert("authorization".to_string(), format!("Bearer {}", token)); + let request = tonic::Request::new(create_request(|item: &mut HttpRequest| { + item.headers = headers; + })); let response = server.check(request).await; @@ -95,7 +82,9 @@ mod tests { async fn test_check_denies_invalid_bearer_token() { let authorizer = Arc::new(CedarAuthorizer::new()); let server = CheckService::new(authorizer); - let request = create_test_request_with_headers(HashMap::new()); + let request = tonic::Request::new(create_request(|item: &mut HttpRequest| { + item.headers = HashMap::new(); + })); let response = server.check(request).await; diff --git a/src/lib.rs b/src/lib.rs index c24db79b..210699b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,2 @@ pub mod authorization; - pub use authorization::{Authorizer, CedarAuthorizer, CheckService}; diff --git a/src/main.rs b/src/main.rs index 8eb7b5ef..8fa32b33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,17 @@ -use envoy_types::ext_authz::v3::pb::AuthorizationServer; -use std::sync::Arc; -use tonic::transport::Server; - pub mod authorization; -use authorization::{CedarAuthorizer, CheckService}; #[tokio::main] async fn main() -> Result<(), Box> { + use envoy_types::ext_authz::v3::pb::AuthorizationServer; + use std::sync::Arc; + use tonic::transport::Server; + let addr = "[::1]:50051".parse()?; let (_health_reporter, health_service) = tonic_health::server::health_reporter(); - let authorizer = Arc::new(CedarAuthorizer::new()); - let check_service = CheckService::new(authorizer); + let authorizer = Arc::new(authorization::CedarAuthorizer::new()); + let check_service = authorization::CheckService::new(authorizer); Server::builder() .add_service(AuthorizationServer::new(check_service)) -- cgit v1.2.3 From 654b31287617c56f8e148faabc63916e316f56c6 Mon Sep 17 00:00:00 2001 From: mo khan Date: Tue, 24 Jun 2025 16:42:01 -0600 Subject: test: remove commented out test --- policies/auth_policy.cedar | 2 +- src/authorization/cedar_authorizer.rs | 21 +-------------------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/policies/auth_policy.cedar b/policies/auth_policy.cedar index 27e2bd1b..c7eb6ce5 100644 --- a/policies/auth_policy.cedar +++ b/policies/auth_policy.cedar @@ -20,4 +20,4 @@ when { context.path like "*.gif" || context.path like "*.bmp" || context.path like "*.html") -}; \ No newline at end of file +}; diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index 577b75ba..19886222 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -179,7 +179,7 @@ mod tests { fn test_cedar_authorizer_allows_static_assets() { let authorizer = CedarAuthorizer::new(); let mut headers = HashMap::new(); - headers.insert(":path".to_string(), "/static/style.css".to_string()); + headers.insert(":path".to_string(), "/public/style.css".to_string()); let request = create_request(|item: &mut HttpRequest| { item.headers = headers; }); @@ -200,23 +200,4 @@ mod tests { let result = authorizer.authorize(request); assert!(result); } - - // test css passthrough - // test javascript passthrough - // test ico passthrough - // test png,jpg,bmp passthrough - // test html passthrough - // #[test] - // fn authorize_test_css_passthrough() { - // let authorizer = CedarAuthorizer::new(); - - // let request = CheckRequest { - // attributes: Some(AttributeContext { - // ..Default::default() - // }), - // }; - // let result = authorizer.authorize(request); - - // assert!(result) - // } } -- cgit v1.2.3 From d099e77eaa8e51eef14cd775234bfd4b12098a4c Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 25 Jun 2025 14:20:33 -0600 Subject: test: move cedar_authorizer tests to integration test suite --- src/authorization/cedar_authorizer.rs | 88 ---------------------------- tests/authorization/cedar_authorizer_test.rs | 78 ++++++++++++++++++++++++ tests/authorization/mod.rs | 1 + tests/integration_tests.rs | 1 + 4 files changed, 80 insertions(+), 88 deletions(-) create mode 100644 tests/authorization/cedar_authorizer_test.rs create mode 100644 tests/authorization/mod.rs diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index 19886222..fb85012e 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -113,91 +113,3 @@ impl CedarAuthorizer { .map_err(|e| Box::new(e) as Box) } } - -#[cfg(test)] -mod tests { - use super::*; - use envoy_types::pb::envoy::service::auth::v3::AttributeContext; - use envoy_types::pb::envoy::service::auth::v3::attribute_context::{HttpRequest, Request}; - use std::collections::HashMap; - - fn create_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest { - please::build_with(|item: &mut CheckRequest| { - item.attributes = Some(please::build_with(|item: &mut AttributeContext| { - item.request = Some(please::build_with(|item: &mut Request| { - item.http = Some(please::build_with(|item: &mut HttpRequest| f(item))); - })); - })); - }) - } - - #[test] - fn test_cedar_authorizer_allows_valid_token() { - let authorizer = CedarAuthorizer::new(); - let mut headers = HashMap::new(); - headers.insert( - "authorization".to_string(), - "Bearer valid-token".to_string(), - ); - let request = create_request(|item: &mut HttpRequest| { - item.headers = headers; - }); - - let result = authorizer.authorize(request); - assert!(result); - } - - #[test] - fn test_cedar_authorizer_denies_invalid_token() { - let authorizer = CedarAuthorizer::new(); - let mut headers = HashMap::new(); - headers.insert( - "authorization".to_string(), - "Bearer invalid-token".to_string(), - ); - let request = create_request(|item: &mut HttpRequest| { - item.headers = headers; - }); - - let result = authorizer.authorize(request); - assert!(!result); - } - - #[test] - fn test_cedar_authorizer_denies_missing_header() { - let authorizer = CedarAuthorizer::new(); - let headers = HashMap::new(); - let request = create_request(|item: &mut HttpRequest| { - item.headers = headers; - }); - - let result = authorizer.authorize(request); - assert!(!result); - } - - #[test] - fn test_cedar_authorizer_allows_static_assets() { - let authorizer = CedarAuthorizer::new(); - let mut headers = HashMap::new(); - headers.insert(":path".to_string(), "/public/style.css".to_string()); - let request = create_request(|item: &mut HttpRequest| { - item.headers = headers; - }); - - let result = authorizer.authorize(request); - assert!(result); - } - - #[test] - fn test_cedar_authorizer_allows_js_assets() { - let authorizer = CedarAuthorizer::new(); - let mut headers = HashMap::new(); - headers.insert(":path".to_string(), "/app.js".to_string()); - let request = create_request(|item: &mut HttpRequest| { - item.headers = headers; - }); - - let result = authorizer.authorize(request); - assert!(result); - } -} diff --git a/tests/authorization/cedar_authorizer_test.rs b/tests/authorization/cedar_authorizer_test.rs new file mode 100644 index 00000000..2ed3dd68 --- /dev/null +++ b/tests/authorization/cedar_authorizer_test.rs @@ -0,0 +1,78 @@ +#[cfg(test)] +mod tests { + use crate::common::create_request; + use authzd::Authorizer; + use authzd::CedarAuthorizer; + use envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest; + use std::collections::HashMap; + + #[test] + fn test_cedar_authorizer_allows_valid_token() { + let authorizer = CedarAuthorizer::new(); + let mut headers = HashMap::new(); + headers.insert( + "authorization".to_string(), + "Bearer valid-token".to_string(), + ); + let request = create_request(|item: &mut HttpRequest| { + item.headers = headers; + }); + + let result = authorizer.authorize(request); + assert!(result); + } + + #[test] + fn test_cedar_authorizer_denies_invalid_token() { + let authorizer = CedarAuthorizer::new(); + let mut headers = HashMap::new(); + headers.insert( + "authorization".to_string(), + "Bearer invalid-token".to_string(), + ); + let request = create_request(|item: &mut HttpRequest| { + item.headers = headers; + }); + + let result = authorizer.authorize(request); + assert!(!result); + } + + #[test] + fn test_cedar_authorizer_denies_missing_header() { + let authorizer = CedarAuthorizer::new(); + let headers = HashMap::new(); + let request = create_request(|item: &mut HttpRequest| { + item.headers = headers; + }); + + let result = authorizer.authorize(request); + assert!(!result); + } + + #[test] + fn test_cedar_authorizer_allows_static_assets() { + let authorizer = CedarAuthorizer::new(); + let mut headers = HashMap::new(); + headers.insert(":path".to_string(), "/public/style.css".to_string()); + let request = create_request(|item: &mut HttpRequest| { + item.headers = headers; + }); + + let result = authorizer.authorize(request); + assert!(result); + } + + #[test] + fn test_cedar_authorizer_allows_js_assets() { + let authorizer = CedarAuthorizer::new(); + let mut headers = HashMap::new(); + headers.insert(":path".to_string(), "/app.js".to_string()); + let request = create_request(|item: &mut HttpRequest| { + item.headers = headers; + }); + + let result = authorizer.authorize(request); + assert!(result); + } +} diff --git a/tests/authorization/mod.rs b/tests/authorization/mod.rs new file mode 100644 index 00000000..a8aab73a --- /dev/null +++ b/tests/authorization/mod.rs @@ -0,0 +1 @@ +mod cedar_authorizer_test; diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 9bbeaea5..2269c7f7 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -2,6 +2,7 @@ use authzd::{CedarAuthorizer, CheckService}; use envoy_types::ext_authz::v3::pb::Authorization; use std::sync::Arc; +mod authorization; mod common; #[tokio::test] -- cgit v1.2.3 From 86c9564a82f56b7c5ee60f4bff9fb07ca3e4a6eb Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 25 Jun 2025 14:24:19 -0600 Subject: test: move unit tests to integration test suite to share code --- src/authorization/check_service.rs | 62 ------------------------------- tests/authorization/check_service_test.rs | 48 ++++++++++++++++++++++++ tests/authorization/mod.rs | 1 + tests/common/mod.rs | 4 ++ 4 files changed, 53 insertions(+), 62 deletions(-) create mode 100644 tests/authorization/check_service_test.rs diff --git a/src/authorization/check_service.rs b/src/authorization/check_service.rs index c0a05e21..6c6bd9c6 100644 --- a/src/authorization/check_service.rs +++ b/src/authorization/check_service.rs @@ -33,65 +33,3 @@ impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { } } } - -#[cfg(test)] -mod tests { - use super::super::cedar_authorizer::CedarAuthorizer; - use super::*; - use envoy_types::ext_authz::v3::pb::{Authorization, CheckRequest}; - use envoy_types::pb::envoy::service::auth::v3::AttributeContext; - use envoy_types::pb::envoy::service::auth::v3::attribute_context::{HttpRequest, Request}; - use std::collections::HashMap; - use std::sync::Arc; - - pub fn create_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest { - please::build_with(|item: &mut CheckRequest| { - item.attributes = Some(please::build_with(|item: &mut AttributeContext| { - item.request = Some(please::build_with(|item: &mut Request| { - item.http = Some(please::build_with(|item: &mut HttpRequest| f(item))); - })); - })); - }) - } - - pub fn create_token() -> String { - return String::from("valid-token"); - } - - #[tokio::test] - async fn test_check_allows_valid_bearer_token() { - let token = create_token(); - let server = CheckService::new(Arc::new(CedarAuthorizer::new())); - - let mut headers = HashMap::new(); - headers.insert("authorization".to_string(), format!("Bearer {}", token)); - let request = tonic::Request::new(create_request(|item: &mut HttpRequest| { - item.headers = headers; - })); - - let response = server.check(request).await; - - assert!(response.is_ok()); - let check_response = response.unwrap().into_inner(); - assert!(check_response.status.is_some()); - let status = check_response.status.unwrap(); - assert_eq!(status.code, tonic::Code::Ok as i32); - } - - #[tokio::test] - async fn test_check_denies_invalid_bearer_token() { - let authorizer = Arc::new(CedarAuthorizer::new()); - let server = CheckService::new(authorizer); - let request = tonic::Request::new(create_request(|item: &mut HttpRequest| { - item.headers = HashMap::new(); - })); - - let response = server.check(request).await; - - assert!(response.is_ok()); - let check_response = response.unwrap().into_inner(); - assert!(check_response.status.is_some()); - let status = check_response.status.unwrap(); - assert_eq!(status.code, tonic::Code::Unauthenticated as i32); - } -} diff --git a/tests/authorization/check_service_test.rs b/tests/authorization/check_service_test.rs new file mode 100644 index 00000000..23655ffb --- /dev/null +++ b/tests/authorization/check_service_test.rs @@ -0,0 +1,48 @@ +#[cfg(test)] +mod tests { + use crate::common::create_request; + use crate::common::create_token; + use authzd::CedarAuthorizer; + use authzd::CheckService; + use envoy_types::ext_authz::v3::pb::Authorization; + use envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest; + use std::collections::HashMap; + use std::sync::Arc; + + #[tokio::test] + async fn test_check_allows_valid_bearer_token() { + let token = create_token(); + let server = CheckService::new(Arc::new(CedarAuthorizer::new())); + + let mut headers = HashMap::new(); + headers.insert("authorization".to_string(), format!("Bearer {}", token)); + let request = tonic::Request::new(create_request(|item: &mut HttpRequest| { + item.headers = headers; + })); + + let response = server.check(request).await; + + assert!(response.is_ok()); + let check_response = response.unwrap().into_inner(); + assert!(check_response.status.is_some()); + let status = check_response.status.unwrap(); + assert_eq!(status.code, tonic::Code::Ok as i32); + } + + #[tokio::test] + async fn test_check_denies_invalid_bearer_token() { + let authorizer = Arc::new(CedarAuthorizer::new()); + let server = CheckService::new(authorizer); + let request = tonic::Request::new(create_request(|item: &mut HttpRequest| { + item.headers = HashMap::new(); + })); + + let response = server.check(request).await; + + assert!(response.is_ok()); + let check_response = response.unwrap().into_inner(); + assert!(check_response.status.is_some()); + let status = check_response.status.unwrap(); + assert_eq!(status.code, tonic::Code::Unauthenticated as i32); + } +} diff --git a/tests/authorization/mod.rs b/tests/authorization/mod.rs index a8aab73a..a4ece924 100644 --- a/tests/authorization/mod.rs +++ b/tests/authorization/mod.rs @@ -1 +1,2 @@ mod cedar_authorizer_test; +mod check_service_test; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 9b2370cb..4e879b6f 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -26,3 +26,7 @@ pub fn create_headers_with_auth(auth_value: &str) -> HashMap { headers.insert("authorization".to_string(), auth_value.to_string()); headers } + +pub fn create_token() -> String { + return String::from("valid-token"); +} -- cgit v1.2.3 From ab2a06d2a1e63cdedfd06717114f128e1e9dc1a8 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 25 Jun 2025 14:39:13 -0600 Subject: test: start to extract builders --- tests/authorization/cedar_authorizer_test.rs | 10 +++++----- tests/common/factories.rs | 0 tests/common/mod.rs | 20 +++++++++++++++++--- 3 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 tests/common/factories.rs diff --git a/tests/authorization/cedar_authorizer_test.rs b/tests/authorization/cedar_authorizer_test.rs index 2ed3dd68..4531da67 100644 --- a/tests/authorization/cedar_authorizer_test.rs +++ b/tests/authorization/cedar_authorizer_test.rs @@ -9,12 +9,12 @@ mod tests { #[test] fn test_cedar_authorizer_allows_valid_token() { let authorizer = CedarAuthorizer::new(); - let mut headers = HashMap::new(); - headers.insert( - "authorization".to_string(), - "Bearer valid-token".to_string(), - ); let request = create_request(|item: &mut HttpRequest| { + let mut headers = HashMap::new(); + headers.insert( + "authorization".to_string(), + "Bearer valid-token".to_string(), + ); item.headers = headers; }); diff --git a/tests/common/factories.rs b/tests/common/factories.rs new file mode 100644 index 00000000..e69de29b diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 4e879b6f..aab3a412 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,10 +1,24 @@ +mod factories; + use envoy_types::ext_authz::v3::pb::CheckRequest; use envoy_types::pb::envoy::service::auth::v3::AttributeContext; use envoy_types::pb::envoy::service::auth::v3::attribute_context::{HttpRequest, Request}; use std::collections::HashMap; +pub fn build() -> T { + return please::build(); +} + +pub fn build_with(initializer: F) -> T +where + T: Default, + F: std::ops::FnOnce(&mut T), +{ + return please::build_with(initializer); +} + pub fn create_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest { - please::build_with(|item: &mut CheckRequest| { + build_with(|item: &mut CheckRequest| { item.attributes = Some(please::build_with(|item: &mut AttributeContext| { item.request = Some(please::build_with(|item: &mut Request| { item.http = Some(please::build_with(|item: &mut HttpRequest| f(item))); @@ -22,8 +36,8 @@ pub fn create_test_request_with_headers( } pub fn create_headers_with_auth(auth_value: &str) -> HashMap { - let mut headers = HashMap::new(); - headers.insert("authorization".to_string(), auth_value.to_string()); + let mut headers = build::>(); + headers.insert(String::from("authorization"), auth_value.to_string()); headers } -- cgit v1.2.3 From 25412b5ad670d5b8809d399aae4ab63bd5e1de40 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 25 Jun 2025 14:58:12 -0600 Subject: test: improve readability of some of the test code --- tests/authorization/cedar_authorizer_test.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/authorization/cedar_authorizer_test.rs b/tests/authorization/cedar_authorizer_test.rs index 4531da67..1012d917 100644 --- a/tests/authorization/cedar_authorizer_test.rs +++ b/tests/authorization/cedar_authorizer_test.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use crate::common::create_request; + use crate::common::*; use authzd::Authorizer; use authzd::CedarAuthorizer; use envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest; @@ -10,16 +10,15 @@ mod tests { fn test_cedar_authorizer_allows_valid_token() { let authorizer = CedarAuthorizer::new(); let request = create_request(|item: &mut HttpRequest| { - let mut headers = HashMap::new(); - headers.insert( - "authorization".to_string(), - "Bearer valid-token".to_string(), - ); - item.headers = headers; + item.headers = build_with(|item: &mut HashMap| { + item.insert( + String::from("authorization"), + String::from("Bearer valid-token"), + ); + }); }); - let result = authorizer.authorize(request); - assert!(result); + assert!(authorizer.authorize(request)); } #[test] -- cgit v1.2.3 From ff81c53e472857e08eb1333f66f3d96487813732 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 25 Jun 2025 15:08:55 -0600 Subject: test: extract factory_bot module --- tests/authorization/cedar_authorizer_test.rs | 1 + tests/authorization/check_service_test.rs | 2 +- tests/common/factories.rs | 0 tests/common/factory_bot.rs | 13 +++++++++++++ tests/common/mod.rs | 16 +++------------- 5 files changed, 18 insertions(+), 14 deletions(-) delete mode 100644 tests/common/factories.rs create mode 100644 tests/common/factory_bot.rs diff --git a/tests/authorization/cedar_authorizer_test.rs b/tests/authorization/cedar_authorizer_test.rs index 1012d917..c74a2042 100644 --- a/tests/authorization/cedar_authorizer_test.rs +++ b/tests/authorization/cedar_authorizer_test.rs @@ -1,5 +1,6 @@ #[cfg(test)] mod tests { + use crate::common::factory_bot::create_request; use crate::common::*; use authzd::Authorizer; use authzd::CedarAuthorizer; diff --git a/tests/authorization/check_service_test.rs b/tests/authorization/check_service_test.rs index 23655ffb..dadb6d1a 100644 --- a/tests/authorization/check_service_test.rs +++ b/tests/authorization/check_service_test.rs @@ -1,7 +1,7 @@ #[cfg(test)] mod tests { - use crate::common::create_request; use crate::common::create_token; + use crate::common::factory_bot::create_request; use authzd::CedarAuthorizer; use authzd::CheckService; use envoy_types::ext_authz::v3::pb::Authorization; diff --git a/tests/common/factories.rs b/tests/common/factories.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/common/factory_bot.rs b/tests/common/factory_bot.rs new file mode 100644 index 00000000..17c4c3d5 --- /dev/null +++ b/tests/common/factory_bot.rs @@ -0,0 +1,13 @@ +use envoy_types::ext_authz::v3::pb::CheckRequest; +use envoy_types::pb::envoy::service::auth::v3::AttributeContext; +use envoy_types::pb::envoy::service::auth::v3::attribute_context::{HttpRequest, Request}; + +pub fn create_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest { + crate::common::build_with(|item: &mut CheckRequest| { + item.attributes = Some(please::build_with(|item: &mut AttributeContext| { + item.request = Some(please::build_with(|item: &mut Request| { + item.http = Some(please::build_with(|item: &mut HttpRequest| f(item))); + })); + })); + }) +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index aab3a412..9b089f16 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,8 +1,8 @@ -mod factories; +pub mod factory_bot; use envoy_types::ext_authz::v3::pb::CheckRequest; -use envoy_types::pb::envoy::service::auth::v3::AttributeContext; -use envoy_types::pb::envoy::service::auth::v3::attribute_context::{HttpRequest, Request}; +use envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest; +use factory_bot::*; use std::collections::HashMap; pub fn build() -> T { @@ -17,16 +17,6 @@ where return please::build_with(initializer); } -pub fn create_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest { - build_with(|item: &mut CheckRequest| { - item.attributes = Some(please::build_with(|item: &mut AttributeContext| { - item.request = Some(please::build_with(|item: &mut Request| { - item.http = Some(please::build_with(|item: &mut HttpRequest| f(item))); - })); - })); - }) -} - pub fn create_test_request_with_headers( headers: HashMap, ) -> tonic::Request { -- cgit v1.2.3 From 0482895d6b34a3a3622979389ae62a40f3f64cf6 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 25 Jun 2025 15:13:03 -0600 Subject: test: move builder functions to factory_bot module --- tests/authorization/cedar_authorizer_test.rs | 3 +-- tests/authorization/check_service_test.rs | 3 +-- tests/common/factory_bot.rs | 31 ++++++++++++++++++++++++++ tests/common/mod.rs | 33 ---------------------------- tests/integration_tests.rs | 8 +++---- 5 files changed, 37 insertions(+), 41 deletions(-) diff --git a/tests/authorization/cedar_authorizer_test.rs b/tests/authorization/cedar_authorizer_test.rs index c74a2042..b13f48ad 100644 --- a/tests/authorization/cedar_authorizer_test.rs +++ b/tests/authorization/cedar_authorizer_test.rs @@ -1,7 +1,6 @@ #[cfg(test)] mod tests { - use crate::common::factory_bot::create_request; - use crate::common::*; + use crate::common::factory_bot::*; use authzd::Authorizer; use authzd::CedarAuthorizer; use envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest; diff --git a/tests/authorization/check_service_test.rs b/tests/authorization/check_service_test.rs index dadb6d1a..0582417e 100644 --- a/tests/authorization/check_service_test.rs +++ b/tests/authorization/check_service_test.rs @@ -1,7 +1,6 @@ #[cfg(test)] mod tests { - use crate::common::create_token; - use crate::common::factory_bot::create_request; + use crate::common::factory_bot::*; use authzd::CedarAuthorizer; use authzd::CheckService; use envoy_types::ext_authz::v3::pb::Authorization; diff --git a/tests/common/factory_bot.rs b/tests/common/factory_bot.rs index 17c4c3d5..bd2cb163 100644 --- a/tests/common/factory_bot.rs +++ b/tests/common/factory_bot.rs @@ -1,6 +1,19 @@ use envoy_types::ext_authz::v3::pb::CheckRequest; use envoy_types::pb::envoy::service::auth::v3::AttributeContext; use envoy_types::pb::envoy::service::auth::v3::attribute_context::{HttpRequest, Request}; +use std::collections::HashMap; + +pub fn build() -> T { + return please::build(); +} + +pub fn build_with(initializer: F) -> T +where + T: Default, + F: std::ops::FnOnce(&mut T), +{ + return please::build_with(initializer); +} pub fn create_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest { crate::common::build_with(|item: &mut CheckRequest| { @@ -11,3 +24,21 @@ pub fn create_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckReques })); }) } + +pub fn create_test_request_with_headers( + headers: HashMap, +) -> tonic::Request { + tonic::Request::new(create_request(|item: &mut HttpRequest| { + item.headers = headers; + })) +} + +pub fn create_headers_with_auth(auth_value: &str) -> HashMap { + let mut headers = build::>(); + headers.insert(String::from("authorization"), auth_value.to_string()); + headers +} + +pub fn create_token() -> String { + return String::from("valid-token"); +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 9b089f16..4db87a2c 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,36 +1,3 @@ pub mod factory_bot; -use envoy_types::ext_authz::v3::pb::CheckRequest; -use envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest; use factory_bot::*; -use std::collections::HashMap; - -pub fn build() -> T { - return please::build(); -} - -pub fn build_with(initializer: F) -> T -where - T: Default, - F: std::ops::FnOnce(&mut T), -{ - return please::build_with(initializer); -} - -pub fn create_test_request_with_headers( - headers: HashMap, -) -> tonic::Request { - tonic::Request::new(create_request(|item: &mut HttpRequest| { - item.headers = headers; - })) -} - -pub fn create_headers_with_auth(auth_value: &str) -> HashMap { - let mut headers = build::>(); - headers.insert(String::from("authorization"), auth_value.to_string()); - headers -} - -pub fn create_token() -> String { - return String::from("valid-token"); -} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 2269c7f7..fc31a037 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -10,8 +10,8 @@ async fn test_success_response() { let authorizer = Arc::new(CedarAuthorizer::new()); let server = CheckService::new(authorizer); - let headers = common::create_headers_with_auth("Bearer valid-token"); - let request = common::create_test_request_with_headers(headers); + let headers = common::factory_bot::create_headers_with_auth("Bearer valid-token"); + let request = common::factory_bot::create_test_request_with_headers(headers); let response = server.check(request).await; assert!(response.is_ok()); @@ -36,8 +36,8 @@ async fn test_multiple() { ]; for (auth_value, should_succeed) in test_cases { - let headers = common::create_headers_with_auth(auth_value); - let request = common::create_test_request_with_headers(headers); + let headers = common::factory_bot::create_headers_with_auth(auth_value); + let request = common::factory_bot::create_test_request_with_headers(headers); let response = server.check(request).await; assert!(response.is_ok()); -- cgit v1.2.3 From 13205895041cc545a41be9552fe28d5dc1c7318a Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 25 Jun 2025 15:29:33 -0600 Subject: test: extract a build_headers function --- tests/common/factory_bot.rs | 15 ++++++++++++--- tests/integration_tests.rs | 9 ++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/tests/common/factory_bot.rs b/tests/common/factory_bot.rs index bd2cb163..a16a1213 100644 --- a/tests/common/factory_bot.rs +++ b/tests/common/factory_bot.rs @@ -3,6 +3,7 @@ use envoy_types::pb::envoy::service::auth::v3::AttributeContext; use envoy_types::pb::envoy::service::auth::v3::attribute_context::{HttpRequest, Request}; use std::collections::HashMap; +#[allow(dead_code)] pub fn build() -> T { return please::build(); } @@ -33,10 +34,18 @@ pub fn create_test_request_with_headers( })) } +pub fn build_headers(headers: Vec<(String, String)>) -> HashMap { + return build_with(|item: &mut HashMap| { + for (key, value) in headers { + item.insert(key, value); + } + }); +} + pub fn create_headers_with_auth(auth_value: &str) -> HashMap { - let mut headers = build::>(); - headers.insert(String::from("authorization"), auth_value.to_string()); - headers + return build_with(|item: &mut HashMap| { + item.insert(String::from("authorization"), String::from(auth_value)); + }); } pub fn create_token() -> String { diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index fc31a037..41c3bf29 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -9,9 +9,12 @@ mod common; async fn test_success_response() { let authorizer = Arc::new(CedarAuthorizer::new()); let server = CheckService::new(authorizer); - - let headers = common::factory_bot::create_headers_with_auth("Bearer valid-token"); - let request = common::factory_bot::create_test_request_with_headers(headers); + let request = common::factory_bot::create_test_request_with_headers( + common::factory_bot::build_headers(vec![( + "authorization".to_string(), + "Bearer valid-token".to_string(), + )]), + ); let response = server.check(request).await; assert!(response.is_ok()); -- cgit v1.2.3 From 790b5d5e51c64e56bba876978ebb9970b0ce0f03 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 25 Jun 2025 15:42:20 -0600 Subject: test: use build_header function --- tests/common/factory_bot.rs | 6 ------ tests/integration_tests.rs | 5 ++++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/common/factory_bot.rs b/tests/common/factory_bot.rs index a16a1213..f25633f4 100644 --- a/tests/common/factory_bot.rs +++ b/tests/common/factory_bot.rs @@ -42,12 +42,6 @@ pub fn build_headers(headers: Vec<(String, String)>) -> HashMap }); } -pub fn create_headers_with_auth(auth_value: &str) -> HashMap { - return build_with(|item: &mut HashMap| { - item.insert(String::from("authorization"), String::from(auth_value)); - }); -} - pub fn create_token() -> String { return String::from("valid-token"); } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 41c3bf29..c25d1920 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -39,7 +39,10 @@ async fn test_multiple() { ]; for (auth_value, should_succeed) in test_cases { - let headers = common::factory_bot::create_headers_with_auth(auth_value); + let headers = common::factory_bot::build_headers(vec![( + "authorization".to_string(), + auth_value.to_string(), + )]); let request = common::factory_bot::create_test_request_with_headers(headers); let response = server.check(request).await; -- cgit v1.2.3 From 44c9f96488b97ad84f9c8f34d1a43c3c7287a263 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 25 Jun 2025 15:49:13 -0600 Subject: feat: inline create_test_request function --- tests/common/factory_bot.rs | 8 -------- tests/integration_tests.rs | 21 ++++++++++++--------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/tests/common/factory_bot.rs b/tests/common/factory_bot.rs index f25633f4..2389c858 100644 --- a/tests/common/factory_bot.rs +++ b/tests/common/factory_bot.rs @@ -26,14 +26,6 @@ pub fn create_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckReques }) } -pub fn create_test_request_with_headers( - headers: HashMap, -) -> tonic::Request { - tonic::Request::new(create_request(|item: &mut HttpRequest| { - item.headers = headers; - })) -} - pub fn build_headers(headers: Vec<(String, String)>) -> HashMap { return build_with(|item: &mut HashMap| { for (key, value) in headers { diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index c25d1920..56321acb 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,5 +1,7 @@ use authzd::{CedarAuthorizer, CheckService}; +use common::*; use envoy_types::ext_authz::v3::pb::Authorization; +use envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest; use std::sync::Arc; mod authorization; @@ -9,12 +11,12 @@ mod common; async fn test_success_response() { let authorizer = Arc::new(CedarAuthorizer::new()); let server = CheckService::new(authorizer); - let request = common::factory_bot::create_test_request_with_headers( - common::factory_bot::build_headers(vec![( + let request = tonic::Request::new(factory_bot::create_request(|item: &mut HttpRequest| { + item.headers = factory_bot::build_headers(vec![( "authorization".to_string(), "Bearer valid-token".to_string(), - )]), - ); + )]) + })); let response = server.check(request).await; assert!(response.is_ok()); @@ -39,11 +41,12 @@ async fn test_multiple() { ]; for (auth_value, should_succeed) in test_cases { - let headers = common::factory_bot::build_headers(vec![( - "authorization".to_string(), - auth_value.to_string(), - )]); - let request = common::factory_bot::create_test_request_with_headers(headers); + let request = tonic::Request::new(factory_bot::create_request(|item: &mut HttpRequest| { + item.headers = factory_bot::build_headers(vec![( + "authorization".to_string(), + auth_value.to_string(), + )]); + })); let response = server.check(request).await; assert!(response.is_ok()); -- cgit v1.2.3 From a13fad7e3d8ca33faeef10dcff9f3f24211bb4d2 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 25 Jun 2025 15:51:48 -0600 Subject: chore: print startup message when server starts --- src/main.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8fa32b33..884460fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ async fn main() -> Result<(), Box> { let authorizer = Arc::new(authorization::CedarAuthorizer::new()); let check_service = authorization::CheckService::new(authorizer); - Server::builder() + let server = Server::builder() .add_service(AuthorizationServer::new(check_service)) .add_service(health_service) .add_service( @@ -21,9 +21,10 @@ async fn main() -> Result<(), Box> { .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET) .build_v1() .unwrap(), - ) - .serve(addr) - .await?; + ); + + println!("Listening on... {addr}"); + server.serve(addr).await?; Ok(()) } -- cgit v1.2.3 From 7c433fcb79c09fa7f63ee0261205b738c7160feb Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 25 Jun 2025 16:06:06 -0600 Subject: chore: add make targets --- Makefile | 45 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 14254543..c550ba62 100644 --- a/Makefile +++ b/Makefile @@ -2,24 +2,57 @@ PROJECT_NAME := $(shell basename $(shell pwd)) GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD | sed 's/\//_/g') IMAGE_TAG := $(PROJECT_NAME):$(GIT_BRANCH) -.PHONY: clean run-image +.PHONY: build check test run clean fmt lint doc +.PHONY: build-image run-image health-check list-services test-grpc + +# Cargo targets +build: + @cargo build + +check: + @cargo check + +test: + @cargo test + +run: + @cargo run -.PHONY: clean clean: @cargo clean -.PHONY: build-image +fmt: + @cargo fmt + +lint: + @cargo clippy -- -D warnings + +doc: + @cargo doc --open + +# Docker targets build-image: @docker build --no-cache --tag $(IMAGE_TAG) . -.PHONY: run-image run-image: build-image @docker run --rm -p 50051:50051 -it $(IMAGE_TAG) -.PHONY: health-check +# gRPC testing targets health-check: @grpcurl -plaintext localhost:50051 grpc.health.v1.Health/Check -.PHONY: list-services list-services: @grpcurl -plaintext localhost:50051 list + +test-grpc: + @echo "Testing authorization service with valid token..." + @grpcurl -plaintext \ + -d '{"attributes":{"request":{"http":{"headers":{"authorization":"Bearer valid-token"}}}}}' \ + localhost:50051 \ + envoy.service.auth.v3.Authorization/Check + @echo "" + @echo "Testing authorization service without token..." + @grpcurl -plaintext \ + -d '{"attributes":{"request":{"http":{"headers":{}}}}}' \ + localhost:50051 \ + envoy.service.auth.v3.Authorization/Check -- cgit v1.2.3 From 59f90b902cfd523e0fafec43d345a66c60e28b49 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 25 Jun 2025 16:10:06 -0600 Subject: chore: install musl lib c in docker builder image --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 2a367c7a..0c0bf256 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ # syntax=docker/dockerfile:1 FROM rust:latest AS builder WORKDIR /app +RUN apt-get update && apt-get install -y musl-tools musl-dev RUN rustup target add x86_64-unknown-linux-musl COPY . ./ RUN cargo build --release --target x86_64-unknown-linux-musl -- cgit v1.2.3 From 145ec6d9d497985d53e0b6a40858b4ac2cb12f8a Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 25 Jun 2025 16:15:57 -0600 Subject: chore: use mise to install tools --- mise.toml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 mise.toml diff --git a/mise.toml b/mise.toml new file mode 100644 index 00000000..7917f815 --- /dev/null +++ b/mise.toml @@ -0,0 +1,4 @@ +[tools] +grpcurl = "latest" +rust = "latest" +rust-analyzer = "latest" -- cgit v1.2.3 From 5738d27bae33122544f389922b855cff5d375334 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 25 Jun 2025 16:55:35 -0600 Subject: chore: build optimized docker image --- .dockerignore | 10 ++++++++++ Dockerfile | 13 +++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..e8da7cb4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +target/ +.git/ +.gitignore +*.md +tests/ +.env* +Dockerfile* +.dockerignore +tags +mise.toml diff --git a/Dockerfile b/Dockerfile index 0c0bf256..0faffb8e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,14 @@ # syntax=docker/dockerfile:1 -FROM rust:latest AS builder +FROM rust:alpine AS builder +RUN apk add --no-cache musl-dev WORKDIR /app -RUN apt-get update && apt-get install -y musl-tools musl-dev -RUN rustup target add x86_64-unknown-linux-musl COPY . ./ RUN cargo build --release --target x86_64-unknown-linux-musl +RUN strip /app/target/x86_64-unknown-linux-musl/release/authzd -FROM scratch +FROM gcr.io/distroless/static-debian12:nonroot EXPOSE 50051 -WORKDIR /var/www/ +WORKDIR /var/www COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/authzd /bin/authzd -CMD ["/bin/authzd"] +COPY --from=builder /app/policies /etc/authzd/policies +ENTRYPOINT ["/bin/authzd"] -- cgit v1.2.3 From c006ac2a676c87f8058e52701648eb24d94b480f Mon Sep 17 00:00:00 2001 From: mo khan Date: Thu, 26 Jun 2025 12:52:54 -0600 Subject: chore: remove test-grpc until we can have the proper protobuf files --- Makefile | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Makefile b/Makefile index c550ba62..decb9a05 100644 --- a/Makefile +++ b/Makefile @@ -43,16 +43,3 @@ health-check: list-services: @grpcurl -plaintext localhost:50051 list - -test-grpc: - @echo "Testing authorization service with valid token..." - @grpcurl -plaintext \ - -d '{"attributes":{"request":{"http":{"headers":{"authorization":"Bearer valid-token"}}}}}' \ - localhost:50051 \ - envoy.service.auth.v3.Authorization/Check - @echo "" - @echo "Testing authorization service without token..." - @grpcurl -plaintext \ - -d '{"attributes":{"request":{"http":{"headers":{}}}}}' \ - localhost:50051 \ - envoy.service.auth.v3.Authorization/Check -- cgit v1.2.3 From 031d4ba92e69966ae91bbebd9ec797e6c115c2f0 Mon Sep 17 00:00:00 2001 From: mo khan Date: Thu, 26 Jun 2025 14:04:57 -0600 Subject: feat: add some debug logging --- src/authorization/check_service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/authorization/check_service.rs b/src/authorization/check_service.rs index 6c6bd9c6..a65d8d95 100644 --- a/src/authorization/check_service.rs +++ b/src/authorization/check_service.rs @@ -24,7 +24,7 @@ impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { ) -> Result, Status> { let request = request.into_inner(); - if self.authorizer.authorize(request) { + if dbg!(self.authorizer.authorize(dbg!(request))) { Ok(Response::new(CheckResponse::with_status(Status::ok("OK")))) } else { Ok(Response::new(CheckResponse::with_status( -- cgit v1.2.3 From 73688038a35d0ceee3d45bd15825a38bb26e74c0 Mon Sep 17 00:00:00 2001 From: mo khan Date: Thu, 26 Jun 2025 14:42:02 -0600 Subject: chore: update the BIND_ADDR variable and read it --- .runway/env-production.yml | 2 +- .runway/env-staging.yml | 2 +- src/main.rs | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.runway/env-production.yml b/.runway/env-production.yml index 6eaecdb8..9d86e025 100644 --- a/.runway/env-production.yml +++ b/.runway/env-production.yml @@ -1,2 +1,2 @@ APP_ENV: "production" -BIND_ADDR: ":http" +BIND_ADDR: "[::1]:50051" diff --git a/.runway/env-staging.yml b/.runway/env-staging.yml index 6eaecdb8..9d86e025 100644 --- a/.runway/env-staging.yml +++ b/.runway/env-staging.yml @@ -1,2 +1,2 @@ APP_ENV: "production" -BIND_ADDR: ":http" +BIND_ADDR: "[::1]:50051" diff --git a/src/main.rs b/src/main.rs index 884460fd..0a1cd5a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,9 @@ async fn main() -> Result<(), Box> { use std::sync::Arc; use tonic::transport::Server; - let addr = "[::1]:50051".parse()?; + let addr = std::env::var("BIND_ADDR") + .unwrap_or_else(|_| "[::1]:50051".to_string()) + .parse()?; let (_health_reporter, health_service) = tonic_health::server::health_reporter(); -- cgit v1.2.3 From 91a989b761f97a70e76031988cf570ad5d209f47 Mon Sep 17 00:00:00 2001 From: mo khan Date: Thu, 26 Jun 2025 15:00:07 -0600 Subject: chore: add minimal basic logging --- Cargo.lock | 104 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 ++ src/authorization/check_service.rs | 2 + src/main.rs | 4 +- 4 files changed, 112 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 45af23e3..a1c069e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,6 +107,7 @@ version = "0.1.0" dependencies = [ "cedar-policy", "envoy-types", + "log", "please", "tokio", "tokio-test", @@ -114,6 +115,8 @@ dependencies = [ "tonic-build", "tonic-health", "tonic-reflection", + "tracing", + "tracing-subscriber", ] [[package]] @@ -1049,6 +1052,16 @@ dependencies = [ "serde", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1079,6 +1092,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.4" @@ -1545,6 +1564,15 @@ dependencies = [ "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1680,6 +1708,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.41" @@ -1918,6 +1955,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "nu-ansi-term", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", + "tracing-serde", ] [[package]] @@ -1981,6 +2057,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version_check" version = "0.9.5" @@ -2079,6 +2161,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.9" @@ -2088,6 +2186,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.61.2" diff --git a/Cargo.toml b/Cargo.toml index 415f3806..2ac42209 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,11 +14,14 @@ path = "src/lib.rs" [dependencies] cedar-policy = "4.4.1" envoy-types = "0.6.0" +log = "0.4.27" please = { git = "https://github.com/xlgmokha/please.git", version = "0.1.0" } tokio = { version = "1.0.0", features = ["macros", "rt-multi-thread"] } tonic = "0.13.1" tonic-health = "0.13.1" tonic-reflection = "0.13.1" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["json"] } [dev-dependencies] tokio-test = "0.4.0" diff --git a/src/authorization/check_service.rs b/src/authorization/check_service.rs index a65d8d95..d2dd1e17 100644 --- a/src/authorization/check_service.rs +++ b/src/authorization/check_service.rs @@ -25,8 +25,10 @@ impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { let request = request.into_inner(); if dbg!(self.authorizer.authorize(dbg!(request))) { + log::info!("OK"); Ok(Response::new(CheckResponse::with_status(Status::ok("OK")))) } else { + log::info!("Unoauthorized"); Ok(Response::new(CheckResponse::with_status( Status::unauthenticated("Unauthorized"), ))) diff --git a/src/main.rs b/src/main.rs index 0a1cd5a5..d847a2ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,8 @@ async fn main() -> Result<(), Box> { use std::sync::Arc; use tonic::transport::Server; + tracing_subscriber::fmt().json().init(); + let addr = std::env::var("BIND_ADDR") .unwrap_or_else(|_| "[::1]:50051".to_string()) .parse()?; @@ -25,7 +27,7 @@ async fn main() -> Result<(), Box> { .unwrap(), ); - println!("Listening on... {addr}"); + log::info!("Listening on... {addr}"); server.serve(addr).await?; Ok(()) -- cgit v1.2.3 From 187c02e9bf32f152cbca9fd5790f4a6070dbb37d Mon Sep 17 00:00:00 2001 From: mo khan Date: Thu, 26 Jun 2025 15:30:41 -0600 Subject: refactor: try to move policy files to /etc/authzd/ --- Dockerfile | 2 +- etc/authzd/policy0.cedar | 19 +++++++++++++++++++ policies/auth_policy.cedar | 23 ----------------------- src/authorization/cedar_authorizer.rs | 11 +++-------- src/main.rs | 2 +- tests/authorization/cedar_authorizer_test.rs | 10 +++++----- tests/authorization/check_service_test.rs | 4 ++-- tests/integration_tests.rs | 4 ++-- 8 files changed, 33 insertions(+), 42 deletions(-) create mode 100644 etc/authzd/policy0.cedar delete mode 100644 policies/auth_policy.cedar diff --git a/Dockerfile b/Dockerfile index 0faffb8e..744b4f3d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,5 +10,5 @@ FROM gcr.io/distroless/static-debian12:nonroot EXPOSE 50051 WORKDIR /var/www COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/authzd /bin/authzd -COPY --from=builder /app/policies /etc/authzd/policies +COPY --from=builder /app/etc/authzd /etc/authzd ENTRYPOINT ["/bin/authzd"] diff --git a/etc/authzd/policy0.cedar b/etc/authzd/policy0.cedar new file mode 100644 index 00000000..e01182c5 --- /dev/null +++ b/etc/authzd/policy0.cedar @@ -0,0 +1,19 @@ +permit(principal, action == Action::"check", resource) +when { + context has bearer_token && + context.bearer_token == "valid-token" +}; + +permit(principal, action == Action::"check", resource) +when { + context has path && + (context.path like "*.css" || + context.path like "*.js" || + context.path like "*.ico" || + context.path like "*.png" || + context.path like "*.jpg" || + context.path like "*.jpeg" || + context.path like "*.gif" || + context.path like "*.bmp" || + context.path like "*.html") +}; diff --git a/policies/auth_policy.cedar b/policies/auth_policy.cedar deleted file mode 100644 index c7eb6ce5..00000000 --- a/policies/auth_policy.cedar +++ /dev/null @@ -1,23 +0,0 @@ -// Authorization policies for the authzd service - -// Allow requests with valid Bearer tokens -permit(principal, action == Action::"check", resource) -when { - context has bearer_token && - context.bearer_token == "valid-token" -}; - -// Allow static assets to pass through without authentication -permit(principal, action == Action::"check", resource) -when { - context has path && - (context.path like "*.css" || - context.path like "*.js" || - context.path like "*.ico" || - context.path like "*.png" || - context.path like "*.jpg" || - context.path like "*.jpeg" || - context.path like "*.gif" || - context.path like "*.bmp" || - context.path like "*.html") -}; diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index fb85012e..568bafbc 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -14,21 +14,16 @@ pub struct CedarAuthorizer { } impl CedarAuthorizer { - pub fn new() -> CedarAuthorizer { - let policy_src = include_str!("../../policies/auth_policy.cedar"); - let policies = policy_src.parse().expect("Failed to parse Cedar policies"); - let authorizer = CedarAuth::new(); - + pub fn new(policies: cedar_policy::PolicySet) -> CedarAuthorizer { CedarAuthorizer { policies, - authorizer, + authorizer: CedarAuth::new(), } } } - impl Default for CedarAuthorizer { fn default() -> Self { - Self::new() + Self::new(PolicySet::default()) } } diff --git a/src/main.rs b/src/main.rs index d847a2ee..1a3ff00c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ async fn main() -> Result<(), Box> { let (_health_reporter, health_service) = tonic_health::server::health_reporter(); - let authorizer = Arc::new(authorization::CedarAuthorizer::new()); + let authorizer = Arc::new(authorization::CedarAuthorizer::default()); let check_service = authorization::CheckService::new(authorizer); let server = Server::builder() diff --git a/tests/authorization/cedar_authorizer_test.rs b/tests/authorization/cedar_authorizer_test.rs index b13f48ad..6e1591eb 100644 --- a/tests/authorization/cedar_authorizer_test.rs +++ b/tests/authorization/cedar_authorizer_test.rs @@ -8,7 +8,7 @@ mod tests { #[test] fn test_cedar_authorizer_allows_valid_token() { - let authorizer = CedarAuthorizer::new(); + let authorizer = CedarAuthorizer::default(); let request = create_request(|item: &mut HttpRequest| { item.headers = build_with(|item: &mut HashMap| { item.insert( @@ -23,7 +23,7 @@ mod tests { #[test] fn test_cedar_authorizer_denies_invalid_token() { - let authorizer = CedarAuthorizer::new(); + let authorizer = CedarAuthorizer::default(); let mut headers = HashMap::new(); headers.insert( "authorization".to_string(), @@ -39,7 +39,7 @@ mod tests { #[test] fn test_cedar_authorizer_denies_missing_header() { - let authorizer = CedarAuthorizer::new(); + let authorizer = CedarAuthorizer::default(); let headers = HashMap::new(); let request = create_request(|item: &mut HttpRequest| { item.headers = headers; @@ -51,7 +51,7 @@ mod tests { #[test] fn test_cedar_authorizer_allows_static_assets() { - let authorizer = CedarAuthorizer::new(); + let authorizer = CedarAuthorizer::default(); let mut headers = HashMap::new(); headers.insert(":path".to_string(), "/public/style.css".to_string()); let request = create_request(|item: &mut HttpRequest| { @@ -64,7 +64,7 @@ mod tests { #[test] fn test_cedar_authorizer_allows_js_assets() { - let authorizer = CedarAuthorizer::new(); + let authorizer = CedarAuthorizer::default(); let mut headers = HashMap::new(); headers.insert(":path".to_string(), "/app.js".to_string()); let request = create_request(|item: &mut HttpRequest| { diff --git a/tests/authorization/check_service_test.rs b/tests/authorization/check_service_test.rs index 0582417e..a739b16a 100644 --- a/tests/authorization/check_service_test.rs +++ b/tests/authorization/check_service_test.rs @@ -11,7 +11,7 @@ mod tests { #[tokio::test] async fn test_check_allows_valid_bearer_token() { let token = create_token(); - let server = CheckService::new(Arc::new(CedarAuthorizer::new())); + let server = CheckService::new(Arc::new(CedarAuthorizer::default())); let mut headers = HashMap::new(); headers.insert("authorization".to_string(), format!("Bearer {}", token)); @@ -30,7 +30,7 @@ mod tests { #[tokio::test] async fn test_check_denies_invalid_bearer_token() { - let authorizer = Arc::new(CedarAuthorizer::new()); + let authorizer = Arc::new(CedarAuthorizer::default()); let server = CheckService::new(authorizer); let request = tonic::Request::new(create_request(|item: &mut HttpRequest| { item.headers = HashMap::new(); diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 56321acb..a265c2be 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -9,7 +9,7 @@ mod common; #[tokio::test] async fn test_success_response() { - let authorizer = Arc::new(CedarAuthorizer::new()); + let authorizer = Arc::new(CedarAuthorizer::default()); let server = CheckService::new(authorizer); let request = tonic::Request::new(factory_bot::create_request(|item: &mut HttpRequest| { item.headers = factory_bot::build_headers(vec![( @@ -30,7 +30,7 @@ async fn test_success_response() { #[tokio::test] async fn test_multiple() { - let authorizer = Arc::new(CedarAuthorizer::new()); + let authorizer = Arc::new(CedarAuthorizer::default()); let server = CheckService::new(authorizer); let test_cases = vec![ -- cgit v1.2.3 From 9f4bf84825c5a725b0ea36d4474d4fa2cec916fd Mon Sep 17 00:00:00 2001 From: mo khan Date: Thu, 26 Jun 2025 15:46:00 -0600 Subject: test: tidy up the tests --- src/authorization/check_service.rs | 2 +- tests/authorization/cedar_authorizer_test.rs | 33 ++++++++++++---------------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/authorization/check_service.rs b/src/authorization/check_service.rs index d2dd1e17..b6809339 100644 --- a/src/authorization/check_service.rs +++ b/src/authorization/check_service.rs @@ -24,7 +24,7 @@ impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { ) -> Result, Status> { let request = request.into_inner(); - if dbg!(self.authorizer.authorize(dbg!(request))) { + if self.authorizer.authorize(request) { log::info!("OK"); Ok(Response::new(CheckResponse::with_status(Status::ok("OK")))) } else { diff --git a/tests/authorization/cedar_authorizer_test.rs b/tests/authorization/cedar_authorizer_test.rs index 6e1591eb..d6742995 100644 --- a/tests/authorization/cedar_authorizer_test.rs +++ b/tests/authorization/cedar_authorizer_test.rs @@ -24,42 +24,38 @@ mod tests { #[test] fn test_cedar_authorizer_denies_invalid_token() { let authorizer = CedarAuthorizer::default(); - let mut headers = HashMap::new(); - headers.insert( - "authorization".to_string(), - "Bearer invalid-token".to_string(), - ); let request = create_request(|item: &mut HttpRequest| { - item.headers = headers; + item.headers = build_with(|item: &mut HashMap| { + item.insert( + String::from("authorization"), + String::from("Bearer invalid-token"), + ); + }); }); - let result = authorizer.authorize(request); - assert!(!result); + assert!(!authorizer.authorize(request)); } #[test] fn test_cedar_authorizer_denies_missing_header() { let authorizer = CedarAuthorizer::default(); - let headers = HashMap::new(); let request = create_request(|item: &mut HttpRequest| { - item.headers = headers; + item.headers = HashMap::new(); }); - let result = authorizer.authorize(request); - assert!(!result); + assert!(!authorizer.authorize(request)); } #[test] fn test_cedar_authorizer_allows_static_assets() { let authorizer = CedarAuthorizer::default(); - let mut headers = HashMap::new(); - headers.insert(":path".to_string(), "/public/style.css".to_string()); let request = create_request(|item: &mut HttpRequest| { - item.headers = headers; + item.headers = build_with(|item: &mut HashMap| { + item.insert(String::from(":path"), String::from("/public/style.css")); + }); }); - let result = authorizer.authorize(request); - assert!(result); + assert!(authorizer.authorize(request)); } #[test] @@ -71,7 +67,6 @@ mod tests { item.headers = headers; }); - let result = authorizer.authorize(request); - assert!(result); + assert!(authorizer.authorize(request)); } } -- cgit v1.2.3 From afd9729146a7e90bd97bf36f9d2081e29de9da35 Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 10:40:44 -0600 Subject: feat: scan directory for all policy files --- src/authorization/cedar_authorizer.rs | 34 +++++++++++++++++++++++++++- tests/authorization/cedar_authorizer_test.rs | 21 +++++++++-------- tests/authorization/check_service_test.rs | 11 ++++++--- tests/integration_tests.rs | 12 ++++++---- 4 files changed, 60 insertions(+), 18 deletions(-) diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index 568bafbc..4ec3b34d 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -5,6 +5,8 @@ use cedar_policy::{ }; use envoy_types::ext_authz::v3::pb::CheckRequest; use std::collections::HashMap; +use std::fs; +use std::path::Path; use std::str::FromStr; #[derive(Debug)] @@ -20,10 +22,40 @@ impl CedarAuthorizer { authorizer: CedarAuth::new(), } } + + pub fn new_from(dir_path: &str) -> CedarAuthorizer { + Self::new(Self::load_from(dir_path).unwrap_or_else(|_| PolicySet::default())) + } + + fn load_from(dir_path: &str) -> Result> { + let path = Path::new(dir_path); + if !path.exists() || !path.is_dir() { + return Ok(PolicySet::default()); + } + + let mut policies = PolicySet::new(); + + for entry in fs::read_dir(path)? { + let file_path = entry?.path(); + + if let Some(extension) = file_path.extension() { + if extension == "cedar" { + let content = fs::read_to_string(&file_path)?; + let file_policies = PolicySet::from_str(&content)?; + + for policy in file_policies.policies() { + policies.add(policy.clone())?; + } + } + } + } + + Ok(policies) + } } impl Default for CedarAuthorizer { fn default() -> Self { - Self::new(PolicySet::default()) + Self::new_from("/etc/authzd") } } diff --git a/tests/authorization/cedar_authorizer_test.rs b/tests/authorization/cedar_authorizer_test.rs index d6742995..3073417d 100644 --- a/tests/authorization/cedar_authorizer_test.rs +++ b/tests/authorization/cedar_authorizer_test.rs @@ -6,9 +6,14 @@ mod tests { use envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest; use std::collections::HashMap; + fn authorizer() -> CedarAuthorizer { + CedarAuthorizer::new_from( + "/home/mokhax/src/gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd/etc/authzd", + ) + } + #[test] fn test_cedar_authorizer_allows_valid_token() { - let authorizer = CedarAuthorizer::default(); let request = create_request(|item: &mut HttpRequest| { item.headers = build_with(|item: &mut HashMap| { item.insert( @@ -18,12 +23,11 @@ mod tests { }); }); - assert!(authorizer.authorize(request)); + assert!(authorizer().authorize(request)); } #[test] fn test_cedar_authorizer_denies_invalid_token() { - let authorizer = CedarAuthorizer::default(); let request = create_request(|item: &mut HttpRequest| { item.headers = build_with(|item: &mut HashMap| { item.insert( @@ -33,40 +37,37 @@ mod tests { }); }); - assert!(!authorizer.authorize(request)); + assert!(!authorizer().authorize(request)); } #[test] fn test_cedar_authorizer_denies_missing_header() { - let authorizer = CedarAuthorizer::default(); let request = create_request(|item: &mut HttpRequest| { item.headers = HashMap::new(); }); - assert!(!authorizer.authorize(request)); + assert!(!authorizer().authorize(request)); } #[test] fn test_cedar_authorizer_allows_static_assets() { - let authorizer = CedarAuthorizer::default(); let request = create_request(|item: &mut HttpRequest| { item.headers = build_with(|item: &mut HashMap| { item.insert(String::from(":path"), String::from("/public/style.css")); }); }); - assert!(authorizer.authorize(request)); + assert!(authorizer().authorize(request)); } #[test] fn test_cedar_authorizer_allows_js_assets() { - let authorizer = CedarAuthorizer::default(); let mut headers = HashMap::new(); headers.insert(":path".to_string(), "/app.js".to_string()); let request = create_request(|item: &mut HttpRequest| { item.headers = headers; }); - assert!(authorizer.authorize(request)); + assert!(authorizer().authorize(request)); } } diff --git a/tests/authorization/check_service_test.rs b/tests/authorization/check_service_test.rs index a739b16a..c101850c 100644 --- a/tests/authorization/check_service_test.rs +++ b/tests/authorization/check_service_test.rs @@ -8,10 +8,16 @@ mod tests { use std::collections::HashMap; use std::sync::Arc; + fn authorizer() -> Arc { + Arc::new(CedarAuthorizer::new_from( + "/home/mokhax/src/gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd/etc/authzd", + )) + } + #[tokio::test] async fn test_check_allows_valid_bearer_token() { let token = create_token(); - let server = CheckService::new(Arc::new(CedarAuthorizer::default())); + let server = CheckService::new(authorizer()); let mut headers = HashMap::new(); headers.insert("authorization".to_string(), format!("Bearer {}", token)); @@ -30,8 +36,7 @@ mod tests { #[tokio::test] async fn test_check_denies_invalid_bearer_token() { - let authorizer = Arc::new(CedarAuthorizer::default()); - let server = CheckService::new(authorizer); + let server = CheckService::new(authorizer()); let request = tonic::Request::new(create_request(|item: &mut HttpRequest| { item.headers = HashMap::new(); })); diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index a265c2be..8bf433d1 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -7,10 +7,15 @@ use std::sync::Arc; mod authorization; mod common; +fn authorizer() -> Arc { + Arc::new(CedarAuthorizer::new_from( + "/home/mokhax/src/gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd/etc/authzd", + )) +} + #[tokio::test] async fn test_success_response() { - let authorizer = Arc::new(CedarAuthorizer::default()); - let server = CheckService::new(authorizer); + let server = CheckService::new(authorizer()); let request = tonic::Request::new(factory_bot::create_request(|item: &mut HttpRequest| { item.headers = factory_bot::build_headers(vec![( "authorization".to_string(), @@ -30,8 +35,7 @@ async fn test_success_response() { #[tokio::test] async fn test_multiple() { - let authorizer = Arc::new(CedarAuthorizer::default()); - let server = CheckService::new(authorizer); + let server = CheckService::new(authorizer()); let test_cases = vec![ ("Bearer valid-token", true), -- cgit v1.2.3 From f59c24589af439e0e22f43a2e42595cf88973ccf Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 12:45:13 -0600 Subject: test: extract factory_bot factory for cedar authorizer --- tests/authorization/cedar_authorizer_test.rs | 17 +++++------------ tests/authorization/check_service_test.rs | 5 +---- tests/common/factory_bot.rs | 6 ++++++ tests/integration_tests.rs | 4 +--- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/tests/authorization/cedar_authorizer_test.rs b/tests/authorization/cedar_authorizer_test.rs index 3073417d..656e0060 100644 --- a/tests/authorization/cedar_authorizer_test.rs +++ b/tests/authorization/cedar_authorizer_test.rs @@ -2,16 +2,9 @@ mod tests { use crate::common::factory_bot::*; use authzd::Authorizer; - use authzd::CedarAuthorizer; use envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest; use std::collections::HashMap; - fn authorizer() -> CedarAuthorizer { - CedarAuthorizer::new_from( - "/home/mokhax/src/gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd/etc/authzd", - ) - } - #[test] fn test_cedar_authorizer_allows_valid_token() { let request = create_request(|item: &mut HttpRequest| { @@ -23,7 +16,7 @@ mod tests { }); }); - assert!(authorizer().authorize(request)); + assert!(build_cedar_authorizer().authorize(request)); } #[test] @@ -37,7 +30,7 @@ mod tests { }); }); - assert!(!authorizer().authorize(request)); + assert!(!build_cedar_authorizer().authorize(request)); } #[test] @@ -46,7 +39,7 @@ mod tests { item.headers = HashMap::new(); }); - assert!(!authorizer().authorize(request)); + assert!(!build_cedar_authorizer().authorize(request)); } #[test] @@ -57,7 +50,7 @@ mod tests { }); }); - assert!(authorizer().authorize(request)); + assert!(build_cedar_authorizer().authorize(request)); } #[test] @@ -68,6 +61,6 @@ mod tests { item.headers = headers; }); - assert!(authorizer().authorize(request)); + assert!(build_cedar_authorizer().authorize(request)); } } diff --git a/tests/authorization/check_service_test.rs b/tests/authorization/check_service_test.rs index c101850c..cddbb6b0 100644 --- a/tests/authorization/check_service_test.rs +++ b/tests/authorization/check_service_test.rs @@ -1,7 +1,6 @@ #[cfg(test)] mod tests { use crate::common::factory_bot::*; - use authzd::CedarAuthorizer; use authzd::CheckService; use envoy_types::ext_authz::v3::pb::Authorization; use envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest; @@ -9,9 +8,7 @@ mod tests { use std::sync::Arc; fn authorizer() -> Arc { - Arc::new(CedarAuthorizer::new_from( - "/home/mokhax/src/gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd/etc/authzd", - )) + Arc::new(build_cedar_authorizer()) } #[tokio::test] diff --git a/tests/common/factory_bot.rs b/tests/common/factory_bot.rs index 2389c858..3c3810a7 100644 --- a/tests/common/factory_bot.rs +++ b/tests/common/factory_bot.rs @@ -37,3 +37,9 @@ pub fn build_headers(headers: Vec<(String, String)>) -> HashMap pub fn create_token() -> String { return String::from("valid-token"); } + +pub fn build_cedar_authorizer() -> authzd::CedarAuthorizer { + authzd::CedarAuthorizer::new_from( + "/home/mokhax/src/gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd/etc/authzd", + ) +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 8bf433d1..63aaaadc 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -8,9 +8,7 @@ mod authorization; mod common; fn authorizer() -> Arc { - Arc::new(CedarAuthorizer::new_from( - "/home/mokhax/src/gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd/etc/authzd", - )) + Arc::new(factory_bot::build_cedar_authorizer()) } #[tokio::test] -- cgit v1.2.3 From c87d86d6e32d58070756883e4e0381d66b9fa1ab Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 13:06:08 -0600 Subject: test: replace hardcoded path with relative path --- src/authorization/cedar_authorizer.rs | 10 ++++------ tests/common/factory_bot.rs | 6 +++--- tests/integration_tests.rs | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index 4ec3b34d..c2c594fe 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -6,7 +6,6 @@ use cedar_policy::{ use envoy_types::ext_authz::v3::pb::CheckRequest; use std::collections::HashMap; use std::fs; -use std::path::Path; use std::str::FromStr; #[derive(Debug)] @@ -23,12 +22,11 @@ impl CedarAuthorizer { } } - pub fn new_from(dir_path: &str) -> CedarAuthorizer { - Self::new(Self::load_from(dir_path).unwrap_or_else(|_| PolicySet::default())) + pub fn new_from(path: &std::path::Path) -> CedarAuthorizer { + Self::new(Self::load_from(path).unwrap_or_else(|_| PolicySet::default())) } - fn load_from(dir_path: &str) -> Result> { - let path = Path::new(dir_path); + fn load_from(path: &std::path::Path) -> Result> { if !path.exists() || !path.is_dir() { return Ok(PolicySet::default()); } @@ -55,7 +53,7 @@ impl CedarAuthorizer { } impl Default for CedarAuthorizer { fn default() -> Self { - Self::new_from("/etc/authzd") + Self::new_from(fs::canonicalize("/etc/authzd").unwrap().as_path()) } } diff --git a/tests/common/factory_bot.rs b/tests/common/factory_bot.rs index 3c3810a7..6e45a01a 100644 --- a/tests/common/factory_bot.rs +++ b/tests/common/factory_bot.rs @@ -39,7 +39,7 @@ pub fn create_token() -> String { } pub fn build_cedar_authorizer() -> authzd::CedarAuthorizer { - authzd::CedarAuthorizer::new_from( - "/home/mokhax/src/gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd/etc/authzd", - ) + let realpath = std::fs::canonicalize("./etc/authzd").unwrap(); + let path = realpath.as_path(); + authzd::CedarAuthorizer::new_from(path) } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 63aaaadc..6e181f26 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,4 +1,4 @@ -use authzd::{CedarAuthorizer, CheckService}; +use authzd::CheckService; use common::*; use envoy_types::ext_authz::v3::pb::Authorization; use envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest; -- cgit v1.2.3 From 4230cb44a1212e62c54b3d4f1f7304e5fd02ef81 Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 13:08:45 -0600 Subject: test: rename create_request -> build_request --- tests/authorization/cedar_authorizer_test.rs | 10 +++++----- tests/authorization/check_service_test.rs | 6 +++--- tests/common/factory_bot.rs | 6 +----- tests/integration_tests.rs | 4 ++-- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/tests/authorization/cedar_authorizer_test.rs b/tests/authorization/cedar_authorizer_test.rs index 656e0060..e7e6eb2c 100644 --- a/tests/authorization/cedar_authorizer_test.rs +++ b/tests/authorization/cedar_authorizer_test.rs @@ -7,7 +7,7 @@ mod tests { #[test] fn test_cedar_authorizer_allows_valid_token() { - let request = create_request(|item: &mut HttpRequest| { + let request = build_request(|item: &mut HttpRequest| { item.headers = build_with(|item: &mut HashMap| { item.insert( String::from("authorization"), @@ -21,7 +21,7 @@ mod tests { #[test] fn test_cedar_authorizer_denies_invalid_token() { - let request = create_request(|item: &mut HttpRequest| { + let request = build_request(|item: &mut HttpRequest| { item.headers = build_with(|item: &mut HashMap| { item.insert( String::from("authorization"), @@ -35,7 +35,7 @@ mod tests { #[test] fn test_cedar_authorizer_denies_missing_header() { - let request = create_request(|item: &mut HttpRequest| { + let request = build_request(|item: &mut HttpRequest| { item.headers = HashMap::new(); }); @@ -44,7 +44,7 @@ mod tests { #[test] fn test_cedar_authorizer_allows_static_assets() { - let request = create_request(|item: &mut HttpRequest| { + let request = build_request(|item: &mut HttpRequest| { item.headers = build_with(|item: &mut HashMap| { item.insert(String::from(":path"), String::from("/public/style.css")); }); @@ -57,7 +57,7 @@ mod tests { fn test_cedar_authorizer_allows_js_assets() { let mut headers = HashMap::new(); headers.insert(":path".to_string(), "/app.js".to_string()); - let request = create_request(|item: &mut HttpRequest| { + let request = build_request(|item: &mut HttpRequest| { item.headers = headers; }); diff --git a/tests/authorization/check_service_test.rs b/tests/authorization/check_service_test.rs index cddbb6b0..65b2d120 100644 --- a/tests/authorization/check_service_test.rs +++ b/tests/authorization/check_service_test.rs @@ -13,12 +13,12 @@ mod tests { #[tokio::test] async fn test_check_allows_valid_bearer_token() { - let token = create_token(); + let token = String::from("valid-token"); let server = CheckService::new(authorizer()); let mut headers = HashMap::new(); headers.insert("authorization".to_string(), format!("Bearer {}", token)); - let request = tonic::Request::new(create_request(|item: &mut HttpRequest| { + let request = tonic::Request::new(build_request(|item: &mut HttpRequest| { item.headers = headers; })); @@ -34,7 +34,7 @@ mod tests { #[tokio::test] async fn test_check_denies_invalid_bearer_token() { let server = CheckService::new(authorizer()); - let request = tonic::Request::new(create_request(|item: &mut HttpRequest| { + let request = tonic::Request::new(build_request(|item: &mut HttpRequest| { item.headers = HashMap::new(); })); diff --git a/tests/common/factory_bot.rs b/tests/common/factory_bot.rs index 6e45a01a..ea3a7da1 100644 --- a/tests/common/factory_bot.rs +++ b/tests/common/factory_bot.rs @@ -16,7 +16,7 @@ where return please::build_with(initializer); } -pub fn create_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest { +pub fn build_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest { crate::common::build_with(|item: &mut CheckRequest| { item.attributes = Some(please::build_with(|item: &mut AttributeContext| { item.request = Some(please::build_with(|item: &mut Request| { @@ -34,10 +34,6 @@ pub fn build_headers(headers: Vec<(String, String)>) -> HashMap }); } -pub fn create_token() -> String { - return String::from("valid-token"); -} - pub fn build_cedar_authorizer() -> authzd::CedarAuthorizer { let realpath = std::fs::canonicalize("./etc/authzd").unwrap(); let path = realpath.as_path(); diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 6e181f26..f7093600 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -14,7 +14,7 @@ fn authorizer() -> Arc { #[tokio::test] async fn test_success_response() { let server = CheckService::new(authorizer()); - let request = tonic::Request::new(factory_bot::create_request(|item: &mut HttpRequest| { + let request = tonic::Request::new(factory_bot::build_request(|item: &mut HttpRequest| { item.headers = factory_bot::build_headers(vec![( "authorization".to_string(), "Bearer valid-token".to_string(), @@ -43,7 +43,7 @@ async fn test_multiple() { ]; for (auth_value, should_succeed) in test_cases { - let request = tonic::Request::new(factory_bot::create_request(|item: &mut HttpRequest| { + let request = tonic::Request::new(factory_bot::build_request(|item: &mut HttpRequest| { item.headers = factory_bot::build_headers(vec![( "authorization".to_string(), auth_value.to_string(), -- cgit v1.2.3 From e970d1e29aa9a4e1a4ac6419079928b803536825 Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 13:15:47 -0600 Subject: test: refactor duplicate tests and start to build test harness for rpc server --- tests/authorization/check_service_test.rs | 54 +++++++++++++++++++------ tests/integration_tests.rs | 65 ------------------------------- tests/main_test.rs | 2 + 3 files changed, 45 insertions(+), 76 deletions(-) delete mode 100644 tests/integration_tests.rs create mode 100644 tests/main_test.rs diff --git a/tests/authorization/check_service_test.rs b/tests/authorization/check_service_test.rs index 65b2d120..4a8f1426 100644 --- a/tests/authorization/check_service_test.rs +++ b/tests/authorization/check_service_test.rs @@ -11,39 +11,71 @@ mod tests { Arc::new(build_cedar_authorizer()) } + fn subject() -> CheckService { + CheckService::new(authorizer()) + } + #[tokio::test] async fn test_check_allows_valid_bearer_token() { - let token = String::from("valid-token"); - let server = CheckService::new(authorizer()); - - let mut headers = HashMap::new(); - headers.insert("authorization".to_string(), format!("Bearer {}", token)); let request = tonic::Request::new(build_request(|item: &mut HttpRequest| { - item.headers = headers; + item.headers = build_headers(vec![( + "authorization".to_string(), + format!("Bearer {}", String::from("valid-token")), + )]) })); - let response = server.check(request).await; - + let response = subject().check(request).await; assert!(response.is_ok()); + let check_response = response.unwrap().into_inner(); assert!(check_response.status.is_some()); + let status = check_response.status.unwrap(); assert_eq!(status.code, tonic::Code::Ok as i32); } #[tokio::test] async fn test_check_denies_invalid_bearer_token() { - let server = CheckService::new(authorizer()); let request = tonic::Request::new(build_request(|item: &mut HttpRequest| { item.headers = HashMap::new(); })); - let response = server.check(request).await; - + let response = subject().check(request).await; assert!(response.is_ok()); + let check_response = response.unwrap().into_inner(); assert!(check_response.status.is_some()); + let status = check_response.status.unwrap(); assert_eq!(status.code, tonic::Code::Unauthenticated as i32); } + + #[tokio::test] + async fn test_table() { + let test_cases = vec![ + ("Bearer valid-token", true), + ("Bearer invalid-token", false), + ("Basic valid-token", false), + ("", false), + ]; + + for (auth_value, should_succeed) in test_cases { + let request = tonic::Request::new(build_request(|item: &mut HttpRequest| { + item.headers = + build_headers(vec![("authorization".to_string(), auth_value.to_string())]); + })); + + let response = subject().check(request).await; + assert!(response.is_ok()); + + let check_response = response.unwrap().into_inner(); + let status = check_response.status.unwrap(); + + if should_succeed { + assert_eq!(status.code, tonic::Code::Ok as i32); + } else { + assert_eq!(status.code, tonic::Code::Unauthenticated as i32); + } + } + } } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs deleted file mode 100644 index f7093600..00000000 --- a/tests/integration_tests.rs +++ /dev/null @@ -1,65 +0,0 @@ -use authzd::CheckService; -use common::*; -use envoy_types::ext_authz::v3::pb::Authorization; -use envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest; -use std::sync::Arc; - -mod authorization; -mod common; - -fn authorizer() -> Arc { - Arc::new(factory_bot::build_cedar_authorizer()) -} - -#[tokio::test] -async fn test_success_response() { - let server = CheckService::new(authorizer()); - let request = tonic::Request::new(factory_bot::build_request(|item: &mut HttpRequest| { - item.headers = factory_bot::build_headers(vec![( - "authorization".to_string(), - "Bearer valid-token".to_string(), - )]) - })); - - let response = server.check(request).await; - assert!(response.is_ok()); - - let check_response = response.unwrap().into_inner(); - assert!(check_response.status.is_some()); - - let status = check_response.status.unwrap(); - assert_eq!(status.code, tonic::Code::Ok as i32); -} - -#[tokio::test] -async fn test_multiple() { - let server = CheckService::new(authorizer()); - - let test_cases = vec![ - ("Bearer valid-token", true), - ("Bearer invalid-token", false), - ("Basic valid-token", false), - ("", false), - ]; - - for (auth_value, should_succeed) in test_cases { - let request = tonic::Request::new(factory_bot::build_request(|item: &mut HttpRequest| { - item.headers = factory_bot::build_headers(vec![( - "authorization".to_string(), - auth_value.to_string(), - )]); - })); - - let response = server.check(request).await; - assert!(response.is_ok()); - - let check_response = response.unwrap().into_inner(); - let status = check_response.status.unwrap(); - - if should_succeed { - assert_eq!(status.code, tonic::Code::Ok as i32); - } else { - assert_eq!(status.code, tonic::Code::Unauthenticated as i32); - } - } -} diff --git a/tests/main_test.rs b/tests/main_test.rs new file mode 100644 index 00000000..5c6016f6 --- /dev/null +++ b/tests/main_test.rs @@ -0,0 +1,2 @@ +#[cfg(test)] +mod tests {} -- cgit v1.2.3 From 84420606035fd62bbdcacb6231b9181f197d068f Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 13:24:40 -0600 Subject: refactor: extract create_server function to test it directly --- src/authorization/cedar_authorizer.rs | 2 +- src/lib.rs | 20 ++++++++++++++++++++ src/main.rs | 21 ++------------------- tests/main_test.rs | 8 +++++++- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index c2c594fe..658de7a6 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -53,7 +53,7 @@ impl CedarAuthorizer { } impl Default for CedarAuthorizer { fn default() -> Self { - Self::new_from(fs::canonicalize("/etc/authzd").unwrap().as_path()) + Self::new_from(std::path::Path::new("/etc/authzd")) } } diff --git a/src/lib.rs b/src/lib.rs index 210699b5..a82c2ace 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,22 @@ pub mod authorization; pub use authorization::{Authorizer, CedarAuthorizer, CheckService}; + +use envoy_types::ext_authz::v3::pb::AuthorizationServer; +use std::sync::Arc; +use tonic::transport::Server; + +pub fn create_server() -> Result> { + let (_health_reporter, health_service) = tonic_health::server::health_reporter(); + let authorizer = Arc::new(authorization::CedarAuthorizer::default()); + let check_service = authorization::CheckService::new(authorizer); + let server = Server::builder() + .add_service(AuthorizationServer::new(check_service)) + .add_service(health_service) + .add_service( + tonic_reflection::server::Builder::configure() + .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET) + .build_v1() + .unwrap(), + ); + Ok(server) +} diff --git a/src/main.rs b/src/main.rs index 1a3ff00c..13d313d7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,31 +1,14 @@ -pub mod authorization; +use authzd::create_server; #[tokio::main] async fn main() -> Result<(), Box> { - use envoy_types::ext_authz::v3::pb::AuthorizationServer; - use std::sync::Arc; - use tonic::transport::Server; - tracing_subscriber::fmt().json().init(); let addr = std::env::var("BIND_ADDR") .unwrap_or_else(|_| "[::1]:50051".to_string()) .parse()?; - let (_health_reporter, health_service) = tonic_health::server::health_reporter(); - - let authorizer = Arc::new(authorization::CedarAuthorizer::default()); - let check_service = authorization::CheckService::new(authorizer); - - let server = Server::builder() - .add_service(AuthorizationServer::new(check_service)) - .add_service(health_service) - .add_service( - tonic_reflection::server::Builder::configure() - .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET) - .build_v1() - .unwrap(), - ); + let server = create_server()?; log::info!("Listening on... {addr}"); server.serve(addr).await?; diff --git a/tests/main_test.rs b/tests/main_test.rs index 5c6016f6..69c6eda1 100644 --- a/tests/main_test.rs +++ b/tests/main_test.rs @@ -1,2 +1,8 @@ #[cfg(test)] -mod tests {} +mod tests { + #[test] + fn test_create_server() { + let result = authzd::create_server(); + assert!(result.is_ok()); + } +} -- cgit v1.2.3 From c0bf614ccc7945abc06d5a583497582717c3c41f Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 13:28:00 -0600 Subject: style: reformat the cedar policy --- etc/authzd/policy0.cedar | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/etc/authzd/policy0.cedar b/etc/authzd/policy0.cedar index e01182c5..034e81b5 100644 --- a/etc/authzd/policy0.cedar +++ b/etc/authzd/policy0.cedar @@ -1,19 +1,20 @@ permit(principal, action == Action::"check", resource) when { - context has bearer_token && + context has bearer_token && context.bearer_token == "valid-token" }; permit(principal, action == Action::"check", resource) when { - context has path && - (context.path like "*.css" || - context.path like "*.js" || - context.path like "*.ico" || - context.path like "*.png" || - context.path like "*.jpg" || - context.path like "*.jpeg" || - context.path like "*.gif" || - context.path like "*.bmp" || - context.path like "*.html") + context has path && ( + context.path like "*.css" || + context.path like "*.js" || + context.path like "*.ico" || + context.path like "*.png" || + context.path like "*.jpg" || + context.path like "*.jpeg" || + context.path like "*.gif" || + context.path like "*.bmp" || + context.path like "*.html" + ) }; -- cgit v1.2.3 From 58bf47abcd34e7d6973de4013207473e8b7f0b3e Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 13:46:11 -0600 Subject: docs: Add links to README --- README.md | 14 ++++++++++---- mise.toml | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9f455f3d..5d023217 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,14 @@ It integrates with an identity provider (IdP) and uses message queues to stay in ## References -* [gRPC][6] -* [protocol buffers][7] +* [gRPC][3] +* [protocol buffers][4] +* [tokio][2] +* [tonic][1] +* [Envoy External Authorization][5] -[6]: https://grpc.io/docs/ -[7]: https://protobuf.dev/programming-guides/proto3/ +[1]: https://github.com/hyperium/tonic +[2]: https://github.com/tokio-rs/tokio +[3]: https://grpc.io/docs/ +[4]: https://protobuf.dev/programming-guides/proto3/ +[5]: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter diff --git a/mise.toml b/mise.toml index 7917f815..276d2e72 100644 --- a/mise.toml +++ b/mise.toml @@ -1,4 +1,5 @@ [tools] grpcurl = "latest" +make = "latest" rust = "latest" rust-analyzer = "latest" -- cgit v1.2.3 From 2f895218df8115d90ba03253024f6974a1c4f21b Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 14:50:34 -0600 Subject: test: add tests for grpc server --- Cargo.lock | 1 + Cargo.toml | 1 + tests/authorization/check_service_test.rs | 47 +++++++++++++++++++++--- tests/authorization/mod.rs | 1 + tests/authorization/server_test.rs | 7 ++++ tests/common/factory_bot.rs | 2 +- tests/common/mod.rs | 2 - tests/grpc_server_test.rs | 61 +++++++++++++++++++++++++++++++ tests/integration_test.rs | 2 + tests/main_test.rs | 8 ---- 10 files changed, 116 insertions(+), 16 deletions(-) create mode 100644 tests/authorization/server_test.rs create mode 100644 tests/grpc_server_test.rs create mode 100644 tests/integration_test.rs delete mode 100644 tests/main_test.rs diff --git a/Cargo.lock b/Cargo.lock index a1c069e6..4c657df8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,6 +110,7 @@ dependencies = [ "log", "please", "tokio", + "tokio-stream", "tokio-test", "tonic", "tonic-build", diff --git a/Cargo.toml b/Cargo.toml index 2ac42209..ebacce3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,5 +24,6 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["json"] } [dev-dependencies] +tokio-stream = "0.1" tokio-test = "0.4.0" tonic-build = "0.10.0" diff --git a/tests/authorization/check_service_test.rs b/tests/authorization/check_service_test.rs index 4a8f1426..3bdf30c2 100644 --- a/tests/authorization/check_service_test.rs +++ b/tests/authorization/check_service_test.rs @@ -7,12 +7,8 @@ mod tests { use std::collections::HashMap; use std::sync::Arc; - fn authorizer() -> Arc { - Arc::new(build_cedar_authorizer()) - } - fn subject() -> CheckService { - CheckService::new(authorizer()) + CheckService::new(Arc::new(build_cedar_authorizer())) } #[tokio::test] @@ -50,6 +46,47 @@ mod tests { assert_eq!(status.code, tonic::Code::Unauthenticated as i32); } + #[tokio::test] + async fn test_static_assets() { + let static_paths = vec![ + "app.js", + "favicon.ico", + "image.jpg", + "index.html", + "logo.png", + "style.css", + ]; + + for path in static_paths { + let request = tonic::Request::new(build_request(|http| { + http.headers = build_headers(vec![(":path".to_string(), path.to_string())]); + })); + + let response = subject().check(request).await; + assert!(response.is_ok()); + + let check_response = response.unwrap().into_inner(); + assert!(check_response.status.is_some()); + + let status = check_response.status.unwrap(); + assert_eq!(status.code, tonic::Code::Ok as i32); + } + } + + #[tokio::test] + async fn test_no_headers() { + let request = tonic::Request::new(build_request(|_http| {})); + + let response = subject().check(request).await; + assert!(response.is_ok()); + + let check_response = response.unwrap().into_inner(); + assert!(check_response.status.is_some()); + + let status = check_response.status.unwrap(); + assert_eq!(status.code, tonic::Code::Unauthenticated as i32); + } + #[tokio::test] async fn test_table() { let test_cases = vec![ diff --git a/tests/authorization/mod.rs b/tests/authorization/mod.rs index a4ece924..675247d4 100644 --- a/tests/authorization/mod.rs +++ b/tests/authorization/mod.rs @@ -1,2 +1,3 @@ mod cedar_authorizer_test; mod check_service_test; +mod server_test; diff --git a/tests/authorization/server_test.rs b/tests/authorization/server_test.rs new file mode 100644 index 00000000..6001e978 --- /dev/null +++ b/tests/authorization/server_test.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn test_create_server() { + assert!(authzd::create_server().is_ok()); + } +} diff --git a/tests/common/factory_bot.rs b/tests/common/factory_bot.rs index ea3a7da1..c64b53a2 100644 --- a/tests/common/factory_bot.rs +++ b/tests/common/factory_bot.rs @@ -17,7 +17,7 @@ where } pub fn build_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest { - crate::common::build_with(|item: &mut CheckRequest| { + build_with(|item: &mut CheckRequest| { item.attributes = Some(please::build_with(|item: &mut AttributeContext| { item.request = Some(please::build_with(|item: &mut Request| { item.http = Some(please::build_with(|item: &mut HttpRequest| f(item))); diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 4db87a2c..5e2a6d78 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,3 +1 @@ pub mod factory_bot; - -use factory_bot::*; diff --git a/tests/grpc_server_test.rs b/tests/grpc_server_test.rs new file mode 100644 index 00000000..ec471799 --- /dev/null +++ b/tests/grpc_server_test.rs @@ -0,0 +1,61 @@ +#[cfg(test)] +mod tests { + use authzd::create_server; + use std::net::SocketAddr; + use tokio::net::TcpListener; + use tonic::transport::Channel; + + async fn available_port() -> SocketAddr { + let listener = TcpListener::bind("127.0.0.1:0") + .await + .expect("Failed to bind to random port"); + let addr = listener.local_addr().expect("Failed to get local address"); + drop(listener); + addr + } + + async fn start_server() -> (SocketAddr, tokio::task::JoinHandle<()>) { + let addr = available_port().await; + let server = create_server().expect("Failed to create server"); + + let handle = tokio::spawn(async move { + server.serve(addr).await.expect("Failed to start server"); + }); + + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + (addr, handle) + } + + async fn build_channel(addr: SocketAddr) -> Channel { + Channel::from_shared(format!("http://{}", addr)) + .expect("Failed to create channel") + .connect() + .await + .expect("Failed to connect to server") + } + + async fn build_client( + addr: SocketAddr, + ) -> tonic_health::pb::health_client::HealthClient { + tonic_health::pb::health_client::HealthClient::new(build_channel(addr).await) + } + + #[tokio::test] + async fn test_health_check_service() { + let (addr, server) = start_server().await; + let mut client = build_client(addr).await; + let request = tonic::Request::new(tonic_health::pb::HealthCheckRequest { + service: String::new(), + }); + + let response = client.check(request).await; + assert!(response.is_ok()); + assert_eq!( + response.unwrap().into_inner().status(), + tonic_health::pb::health_check_response::ServingStatus::Serving + ); + + server.abort(); + } +} diff --git a/tests/integration_test.rs b/tests/integration_test.rs new file mode 100644 index 00000000..c1edf36e --- /dev/null +++ b/tests/integration_test.rs @@ -0,0 +1,2 @@ +mod authorization; +mod common; diff --git a/tests/main_test.rs b/tests/main_test.rs deleted file mode 100644 index 69c6eda1..00000000 --- a/tests/main_test.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[cfg(test)] -mod tests { - #[test] - fn test_create_server() { - let result = authzd::create_server(); - assert!(result.is_ok()); - } -} -- cgit v1.2.3 From f2e1cc278bedb63ba0b0b8d9a82e28f0e10fe048 Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 14:52:38 -0600 Subject: test: merge server tests --- tests/authorization/server_test.rs | 59 ++++++++++++++++++++++++++++++++++-- tests/grpc_server_test.rs | 61 -------------------------------------- 2 files changed, 56 insertions(+), 64 deletions(-) delete mode 100644 tests/grpc_server_test.rs diff --git a/tests/authorization/server_test.rs b/tests/authorization/server_test.rs index 6001e978..5e99fdac 100644 --- a/tests/authorization/server_test.rs +++ b/tests/authorization/server_test.rs @@ -1,7 +1,60 @@ #[cfg(test)] mod tests { - #[test] - fn test_create_server() { - assert!(authzd::create_server().is_ok()); + use std::net::SocketAddr; + use tokio::net::TcpListener; + use tonic::transport::Channel; + + async fn available_port() -> SocketAddr { + let listener = TcpListener::bind("127.0.0.1:0") + .await + .expect("Failed to bind to random port"); + let addr = listener.local_addr().expect("Failed to get local address"); + drop(listener); + addr + } + + async fn start_server() -> (SocketAddr, tokio::task::JoinHandle<()>) { + let addr = available_port().await; + let server = authzd::create_server().expect("Failed to create server"); + + let handle = tokio::spawn(async move { + server.serve(addr).await.expect("Failed to start server"); + }); + + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + (addr, handle) + } + + async fn build_channel(addr: SocketAddr) -> Channel { + Channel::from_shared(format!("http://{}", addr)) + .expect("Failed to create channel") + .connect() + .await + .expect("Failed to connect to server") + } + + async fn build_client( + addr: SocketAddr, + ) -> tonic_health::pb::health_client::HealthClient { + tonic_health::pb::health_client::HealthClient::new(build_channel(addr).await) + } + + #[tokio::test] + async fn test_health_check_service() { + let (addr, server) = start_server().await; + let mut client = build_client(addr).await; + let request = tonic::Request::new(tonic_health::pb::HealthCheckRequest { + service: String::new(), + }); + + let response = client.check(request).await; + assert!(response.is_ok()); + assert_eq!( + response.unwrap().into_inner().status(), + tonic_health::pb::health_check_response::ServingStatus::Serving + ); + + server.abort(); } } diff --git a/tests/grpc_server_test.rs b/tests/grpc_server_test.rs deleted file mode 100644 index ec471799..00000000 --- a/tests/grpc_server_test.rs +++ /dev/null @@ -1,61 +0,0 @@ -#[cfg(test)] -mod tests { - use authzd::create_server; - use std::net::SocketAddr; - use tokio::net::TcpListener; - use tonic::transport::Channel; - - async fn available_port() -> SocketAddr { - let listener = TcpListener::bind("127.0.0.1:0") - .await - .expect("Failed to bind to random port"); - let addr = listener.local_addr().expect("Failed to get local address"); - drop(listener); - addr - } - - async fn start_server() -> (SocketAddr, tokio::task::JoinHandle<()>) { - let addr = available_port().await; - let server = create_server().expect("Failed to create server"); - - let handle = tokio::spawn(async move { - server.serve(addr).await.expect("Failed to start server"); - }); - - tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; - - (addr, handle) - } - - async fn build_channel(addr: SocketAddr) -> Channel { - Channel::from_shared(format!("http://{}", addr)) - .expect("Failed to create channel") - .connect() - .await - .expect("Failed to connect to server") - } - - async fn build_client( - addr: SocketAddr, - ) -> tonic_health::pb::health_client::HealthClient { - tonic_health::pb::health_client::HealthClient::new(build_channel(addr).await) - } - - #[tokio::test] - async fn test_health_check_service() { - let (addr, server) = start_server().await; - let mut client = build_client(addr).await; - let request = tonic::Request::new(tonic_health::pb::HealthCheckRequest { - service: String::new(), - }); - - let response = client.check(request).await; - assert!(response.is_ok()); - assert_eq!( - response.unwrap().into_inner().status(), - tonic_health::pb::health_check_response::ServingStatus::Serving - ); - - server.abort(); - } -} -- cgit v1.2.3 From 2309a49fddd189e5dc9da1e7bf979864f02701e0 Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 14:53:48 -0600 Subject: test: rename common module to support --- tests/authorization/cedar_authorizer_test.rs | 2 +- tests/authorization/check_service_test.rs | 2 +- tests/common/factory_bot.rs | 41 ---------------------------- tests/common/mod.rs | 1 - tests/integration_test.rs | 2 +- tests/support/factory_bot.rs | 41 ++++++++++++++++++++++++++++ tests/support/mod.rs | 1 + 7 files changed, 45 insertions(+), 45 deletions(-) delete mode 100644 tests/common/factory_bot.rs delete mode 100644 tests/common/mod.rs create mode 100644 tests/support/factory_bot.rs create mode 100644 tests/support/mod.rs diff --git a/tests/authorization/cedar_authorizer_test.rs b/tests/authorization/cedar_authorizer_test.rs index e7e6eb2c..76bf06df 100644 --- a/tests/authorization/cedar_authorizer_test.rs +++ b/tests/authorization/cedar_authorizer_test.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use crate::common::factory_bot::*; + use crate::support::factory_bot::*; use authzd::Authorizer; use envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest; use std::collections::HashMap; diff --git a/tests/authorization/check_service_test.rs b/tests/authorization/check_service_test.rs index 3bdf30c2..3a225974 100644 --- a/tests/authorization/check_service_test.rs +++ b/tests/authorization/check_service_test.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use crate::common::factory_bot::*; + use crate::support::factory_bot::*; use authzd::CheckService; use envoy_types::ext_authz::v3::pb::Authorization; use envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest; diff --git a/tests/common/factory_bot.rs b/tests/common/factory_bot.rs deleted file mode 100644 index c64b53a2..00000000 --- a/tests/common/factory_bot.rs +++ /dev/null @@ -1,41 +0,0 @@ -use envoy_types::ext_authz::v3::pb::CheckRequest; -use envoy_types::pb::envoy::service::auth::v3::AttributeContext; -use envoy_types::pb::envoy::service::auth::v3::attribute_context::{HttpRequest, Request}; -use std::collections::HashMap; - -#[allow(dead_code)] -pub fn build() -> T { - return please::build(); -} - -pub fn build_with(initializer: F) -> T -where - T: Default, - F: std::ops::FnOnce(&mut T), -{ - return please::build_with(initializer); -} - -pub fn build_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest { - build_with(|item: &mut CheckRequest| { - item.attributes = Some(please::build_with(|item: &mut AttributeContext| { - item.request = Some(please::build_with(|item: &mut Request| { - item.http = Some(please::build_with(|item: &mut HttpRequest| f(item))); - })); - })); - }) -} - -pub fn build_headers(headers: Vec<(String, String)>) -> HashMap { - return build_with(|item: &mut HashMap| { - for (key, value) in headers { - item.insert(key, value); - } - }); -} - -pub fn build_cedar_authorizer() -> authzd::CedarAuthorizer { - let realpath = std::fs::canonicalize("./etc/authzd").unwrap(); - let path = realpath.as_path(); - authzd::CedarAuthorizer::new_from(path) -} diff --git a/tests/common/mod.rs b/tests/common/mod.rs deleted file mode 100644 index 5e2a6d78..00000000 --- a/tests/common/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod factory_bot; diff --git a/tests/integration_test.rs b/tests/integration_test.rs index c1edf36e..c17d8e65 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,2 +1,2 @@ mod authorization; -mod common; +mod support; diff --git a/tests/support/factory_bot.rs b/tests/support/factory_bot.rs new file mode 100644 index 00000000..c64b53a2 --- /dev/null +++ b/tests/support/factory_bot.rs @@ -0,0 +1,41 @@ +use envoy_types::ext_authz::v3::pb::CheckRequest; +use envoy_types::pb::envoy::service::auth::v3::AttributeContext; +use envoy_types::pb::envoy::service::auth::v3::attribute_context::{HttpRequest, Request}; +use std::collections::HashMap; + +#[allow(dead_code)] +pub fn build() -> T { + return please::build(); +} + +pub fn build_with(initializer: F) -> T +where + T: Default, + F: std::ops::FnOnce(&mut T), +{ + return please::build_with(initializer); +} + +pub fn build_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest { + build_with(|item: &mut CheckRequest| { + item.attributes = Some(please::build_with(|item: &mut AttributeContext| { + item.request = Some(please::build_with(|item: &mut Request| { + item.http = Some(please::build_with(|item: &mut HttpRequest| f(item))); + })); + })); + }) +} + +pub fn build_headers(headers: Vec<(String, String)>) -> HashMap { + return build_with(|item: &mut HashMap| { + for (key, value) in headers { + item.insert(key, value); + } + }); +} + +pub fn build_cedar_authorizer() -> authzd::CedarAuthorizer { + let realpath = std::fs::canonicalize("./etc/authzd").unwrap(); + let path = realpath.as_path(); + authzd::CedarAuthorizer::new_from(path) +} diff --git a/tests/support/mod.rs b/tests/support/mod.rs new file mode 100644 index 00000000..5e2a6d78 --- /dev/null +++ b/tests/support/mod.rs @@ -0,0 +1 @@ +pub mod factory_bot; -- cgit v1.2.3 From 22bd71354eafd9e7ef4d4579f9bf5e6181d44604 Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 14:58:14 -0600 Subject: test: convert build_client to a generic function --- tests/authorization/server_test.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/authorization/server_test.rs b/tests/authorization/server_test.rs index 5e99fdac..40cc270f 100644 --- a/tests/authorization/server_test.rs +++ b/tests/authorization/server_test.rs @@ -34,21 +34,23 @@ mod tests { .expect("Failed to connect to server") } - async fn build_client( - addr: SocketAddr, - ) -> tonic_health::pb::health_client::HealthClient { - tonic_health::pb::health_client::HealthClient::new(build_channel(addr).await) + async fn build_client(addr: SocketAddr, f: F) -> T + where + F: FnOnce(Channel) -> T, + { + f(build_channel(addr).await) } #[tokio::test] async fn test_health_check_service() { let (addr, server) = start_server().await; - let mut client = build_client(addr).await; + let mut client = build_client(addr, tonic_health::pb::health_client::HealthClient::new).await; + let request = tonic::Request::new(tonic_health::pb::HealthCheckRequest { service: String::new(), }); - let response = client.check(request).await; + assert!(response.is_ok()); assert_eq!( response.unwrap().into_inner().status(), -- cgit v1.2.3 From 7bda8947c80fc507b722f321977522bd50377c17 Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 15:02:17 -0600 Subject: test: move helpers to factory_bot module --- tests/authorization/server_test.rs | 20 +++----------------- tests/support/factory_bot.rs | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/tests/authorization/server_test.rs b/tests/authorization/server_test.rs index 40cc270f..55645dd4 100644 --- a/tests/authorization/server_test.rs +++ b/tests/authorization/server_test.rs @@ -1,8 +1,8 @@ #[cfg(test)] mod tests { + use crate::support::factory_bot::*; use std::net::SocketAddr; use tokio::net::TcpListener; - use tonic::transport::Channel; async fn available_port() -> SocketAddr { let listener = TcpListener::bind("127.0.0.1:0") @@ -26,25 +26,11 @@ mod tests { (addr, handle) } - async fn build_channel(addr: SocketAddr) -> Channel { - Channel::from_shared(format!("http://{}", addr)) - .expect("Failed to create channel") - .connect() - .await - .expect("Failed to connect to server") - } - - async fn build_client(addr: SocketAddr, f: F) -> T - where - F: FnOnce(Channel) -> T, - { - f(build_channel(addr).await) - } - #[tokio::test] async fn test_health_check_service() { let (addr, server) = start_server().await; - let mut client = build_client(addr, tonic_health::pb::health_client::HealthClient::new).await; + let mut client = + build_rpc_client(addr, tonic_health::pb::health_client::HealthClient::new).await; let request = tonic::Request::new(tonic_health::pb::HealthCheckRequest { service: String::new(), diff --git a/tests/support/factory_bot.rs b/tests/support/factory_bot.rs index c64b53a2..15c6f1f3 100644 --- a/tests/support/factory_bot.rs +++ b/tests/support/factory_bot.rs @@ -2,6 +2,8 @@ use envoy_types::ext_authz::v3::pb::CheckRequest; use envoy_types::pb::envoy::service::auth::v3::AttributeContext; use envoy_types::pb::envoy::service::auth::v3::attribute_context::{HttpRequest, Request}; use std::collections::HashMap; +use std::net::SocketAddr; +use tonic::transport::Channel; #[allow(dead_code)] pub fn build() -> T { @@ -39,3 +41,18 @@ pub fn build_cedar_authorizer() -> authzd::CedarAuthorizer { let path = realpath.as_path(); authzd::CedarAuthorizer::new_from(path) } + +pub async fn build_channel(addr: SocketAddr) -> Channel { + Channel::from_shared(format!("http://{}", addr)) + .expect("Failed to create channel") + .connect() + .await + .expect("Failed to connect to server") +} + +pub async fn build_rpc_client(addr: SocketAddr, f: F) -> T +where + F: FnOnce(Channel) -> T, +{ + f(build_channel(addr).await) +} -- cgit v1.2.3 From cce3e0f170dfacb6b626a8777255c3183c5c5eb3 Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 16:45:17 -0600 Subject: refactor: extract authorization::Server type --- src/authorization/cedar_authorizer.rs | 1 + src/authorization/mod.rs | 2 ++ src/authorization/server.rs | 41 +++++++++++++++++++++++++++++++++++ src/lib.rs | 22 +------------------ src/main.rs | 5 +---- tests/authorization/server_test.rs | 2 +- 6 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 src/authorization/server.rs diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index 658de7a6..a877cf87 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -51,6 +51,7 @@ impl CedarAuthorizer { Ok(policies) } } + impl Default for CedarAuthorizer { fn default() -> Self { Self::new_from(std::path::Path::new("/etc/authzd")) diff --git a/src/authorization/mod.rs b/src/authorization/mod.rs index 7d3856a5..d664815b 100644 --- a/src/authorization/mod.rs +++ b/src/authorization/mod.rs @@ -1,7 +1,9 @@ pub mod authorizer; pub mod cedar_authorizer; pub mod check_service; +pub mod server; pub use authorizer::Authorizer; pub use cedar_authorizer::CedarAuthorizer; pub use check_service::CheckService; +pub use server::Server; diff --git a/src/authorization/server.rs b/src/authorization/server.rs new file mode 100644 index 00000000..f11d0465 --- /dev/null +++ b/src/authorization/server.rs @@ -0,0 +1,41 @@ +use super::cedar_authorizer::CedarAuthorizer; +use super::check_service::CheckService; +use envoy_types::ext_authz::v3::pb::AuthorizationServer; +use std::sync::Arc; + +pub fn create_router() -> Result> { + let (_health_reporter, health_service) = tonic_health::server::health_reporter(); + let authorizer = Arc::new(CedarAuthorizer::default()); + let check_service = CheckService::new(authorizer); + let server = tonic::transport::Server::builder() + .add_service(AuthorizationServer::new(check_service)) + .add_service(health_service) + .add_service( + tonic_reflection::server::Builder::configure() + .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET) + .build_v1() + .unwrap(), + ); + Ok(server) +} + +pub struct Server { + router: tonic::transport::server::Router, +} + +impl Server { + pub fn new() -> Result> { + let router = create_router()?; + Ok(Server { router: router }) + } + + pub async fn serve(self, addr: std::net::SocketAddr) -> Result<(), tonic::transport::Error> { + self.router.serve(addr).await + } +} + +impl Default for Server { + fn default() -> Self { + Self::new().unwrap() + } +} diff --git a/src/lib.rs b/src/lib.rs index a82c2ace..3bd8fbd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,22 +1,2 @@ pub mod authorization; -pub use authorization::{Authorizer, CedarAuthorizer, CheckService}; - -use envoy_types::ext_authz::v3::pb::AuthorizationServer; -use std::sync::Arc; -use tonic::transport::Server; - -pub fn create_server() -> Result> { - let (_health_reporter, health_service) = tonic_health::server::health_reporter(); - let authorizer = Arc::new(authorization::CedarAuthorizer::default()); - let check_service = authorization::CheckService::new(authorizer); - let server = Server::builder() - .add_service(AuthorizationServer::new(check_service)) - .add_service(health_service) - .add_service( - tonic_reflection::server::Builder::configure() - .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET) - .build_v1() - .unwrap(), - ); - Ok(server) -} +pub use authorization::{Authorizer, CedarAuthorizer, CheckService, Server}; diff --git a/src/main.rs b/src/main.rs index 13d313d7..8638e14b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,3 @@ -use authzd::create_server; - #[tokio::main] async fn main() -> Result<(), Box> { tracing_subscriber::fmt().json().init(); @@ -8,9 +6,8 @@ async fn main() -> Result<(), Box> { .unwrap_or_else(|_| "[::1]:50051".to_string()) .parse()?; - let server = create_server()?; - log::info!("Listening on... {addr}"); + let server = authzd::authorization::Server::new()?; server.serve(addr).await?; Ok(()) diff --git a/tests/authorization/server_test.rs b/tests/authorization/server_test.rs index 55645dd4..fe8c8a73 100644 --- a/tests/authorization/server_test.rs +++ b/tests/authorization/server_test.rs @@ -15,7 +15,7 @@ mod tests { async fn start_server() -> (SocketAddr, tokio::task::JoinHandle<()>) { let addr = available_port().await; - let server = authzd::create_server().expect("Failed to create server"); + let server = authzd::authorization::Server::default(); let handle = tokio::spawn(async move { server.serve(addr).await.expect("Failed to start server"); -- cgit v1.2.3 From 9d950395315cef169fcb5e99d7109ea34af5b542 Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 17:26:37 -0600 Subject: refactor: attempt to create constructor that allows passing in services --- src/authorization/server.rs | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/authorization/server.rs b/src/authorization/server.rs index f11d0465..7c39b51c 100644 --- a/src/authorization/server.rs +++ b/src/authorization/server.rs @@ -3,30 +3,34 @@ use super::check_service::CheckService; use envoy_types::ext_authz::v3::pb::AuthorizationServer; use std::sync::Arc; -pub fn create_router() -> Result> { - let (_health_reporter, health_service) = tonic_health::server::health_reporter(); - let authorizer = Arc::new(CedarAuthorizer::default()); - let check_service = CheckService::new(authorizer); - let server = tonic::transport::Server::builder() - .add_service(AuthorizationServer::new(check_service)) - .add_service(health_service) - .add_service( - tonic_reflection::server::Builder::configure() - .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET) - .build_v1() - .unwrap(), - ); - Ok(server) -} - pub struct Server { router: tonic::transport::server::Router, } impl Server { pub fn new() -> Result> { - let router = create_router()?; - Ok(Server { router: router }) + Ok(Self::new_with(|mut builder| { + let (_health_reporter, health_service) = tonic_health::server::health_reporter(); + let authorizer = Arc::new(CedarAuthorizer::default()); + let check_service = CheckService::new(authorizer); + builder + .add_service(AuthorizationServer::new(check_service)) + .add_service(health_service) + .add_service( + tonic_reflection::server::Builder::configure() + .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET) + .build_v1() + .unwrap(), + ) + })) + } + + pub fn new_with(f: F) -> Server + where + F: FnOnce(tonic::transport::Server) -> tonic::transport::server::Router, + { + let router = f(tonic::transport::Server::builder()); + Server { router } } pub async fn serve(self, addr: std::net::SocketAddr) -> Result<(), tonic::transport::Error> { -- cgit v1.2.3 From f86aa3653c5b88586aa51e218865e62b030c045b Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 18:01:59 -0600 Subject: refactor: remove the reflection service --- Makefile | 3 --- src/authorization/server.rs | 6 ------ 2 files changed, 9 deletions(-) diff --git a/Makefile b/Makefile index decb9a05..a134ae64 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,3 @@ run-image: build-image # gRPC testing targets health-check: @grpcurl -plaintext localhost:50051 grpc.health.v1.Health/Check - -list-services: - @grpcurl -plaintext localhost:50051 list diff --git a/src/authorization/server.rs b/src/authorization/server.rs index 7c39b51c..2605bd54 100644 --- a/src/authorization/server.rs +++ b/src/authorization/server.rs @@ -16,12 +16,6 @@ impl Server { builder .add_service(AuthorizationServer::new(check_service)) .add_service(health_service) - .add_service( - tonic_reflection::server::Builder::configure() - .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET) - .build_v1() - .unwrap(), - ) })) } -- cgit v1.2.3 From eb3914a616cda9e2a777a47be7ad4387319df94e Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 18:05:38 -0600 Subject: refactor: fix typo in log message --- src/authorization/check_service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/authorization/check_service.rs b/src/authorization/check_service.rs index b6809339..e66a0e7b 100644 --- a/src/authorization/check_service.rs +++ b/src/authorization/check_service.rs @@ -28,7 +28,7 @@ impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { log::info!("OK"); Ok(Response::new(CheckResponse::with_status(Status::ok("OK")))) } else { - log::info!("Unoauthorized"); + log::info!("Unauthorized"); Ok(Response::new(CheckResponse::with_status( Status::unauthenticated("Unauthorized"), ))) -- cgit v1.2.3 From 12550869b5f9c50a6ce6c9ab54eee456b32057da Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 27 Jun 2025 18:08:42 -0600 Subject: chore: add a logging interceptor to log request --- src/authorization/server.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/authorization/server.rs b/src/authorization/server.rs index 2605bd54..de7df580 100644 --- a/src/authorization/server.rs +++ b/src/authorization/server.rs @@ -9,12 +9,15 @@ pub struct Server { impl Server { pub fn new() -> Result> { + let (_health_reporter, health_service) = tonic_health::server::health_reporter(); + let authorization_service = AuthorizationServer::with_interceptor( + CheckService::new(Arc::new(CedarAuthorizer::default())), + logging_interceptor, + ); + Ok(Self::new_with(|mut builder| { - let (_health_reporter, health_service) = tonic_health::server::health_reporter(); - let authorizer = Arc::new(CedarAuthorizer::default()); - let check_service = CheckService::new(authorizer); builder - .add_service(AuthorizationServer::new(check_service)) + .add_service(authorization_service) .add_service(health_service) })) } @@ -37,3 +40,8 @@ impl Default for Server { Self::new().unwrap() } } + +fn logging_interceptor(request: tonic::Request) -> Result, tonic::Status> { + log::info!("gRPC request received"); + Ok(request) +} -- cgit v1.2.3 From dbb213d385314e1f135d57e174ae8e41ff4b5329 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 2 Jul 2025 11:05:04 -0600 Subject: chore: remove logging interceptor --- src/authorization/check_service.rs | 6 +----- src/authorization/server.rs | 11 ++--------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/authorization/check_service.rs b/src/authorization/check_service.rs index e66a0e7b..f8c7577f 100644 --- a/src/authorization/check_service.rs +++ b/src/authorization/check_service.rs @@ -22,13 +22,9 @@ impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { &self, request: Request, ) -> Result, Status> { - let request = request.into_inner(); - - if self.authorizer.authorize(request) { - log::info!("OK"); + if self.authorizer.authorize(request.into_inner()) { Ok(Response::new(CheckResponse::with_status(Status::ok("OK")))) } else { - log::info!("Unauthorized"); Ok(Response::new(CheckResponse::with_status( Status::unauthenticated("Unauthorized"), ))) diff --git a/src/authorization/server.rs b/src/authorization/server.rs index de7df580..57712848 100644 --- a/src/authorization/server.rs +++ b/src/authorization/server.rs @@ -10,10 +10,8 @@ pub struct Server { impl Server { pub fn new() -> Result> { let (_health_reporter, health_service) = tonic_health::server::health_reporter(); - let authorization_service = AuthorizationServer::with_interceptor( - CheckService::new(Arc::new(CedarAuthorizer::default())), - logging_interceptor, - ); + let authorization_service = + AuthorizationServer::new(CheckService::new(Arc::new(CedarAuthorizer::default()))); Ok(Self::new_with(|mut builder| { builder @@ -40,8 +38,3 @@ impl Default for Server { Self::new().unwrap() } } - -fn logging_interceptor(request: tonic::Request) -> Result, tonic::Status> { - log::info!("gRPC request received"); - Ok(request) -} -- cgit v1.2.3 From 105b4b2c4af3716e128af84142f2ff0a3442855d Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 2 Jul 2025 11:11:45 -0600 Subject: chore: add requst timeout of 30 seconds --- src/authorization/server.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/authorization/server.rs b/src/authorization/server.rs index 57712848..6ae9361f 100644 --- a/src/authorization/server.rs +++ b/src/authorization/server.rs @@ -24,7 +24,9 @@ impl Server { where F: FnOnce(tonic::transport::Server) -> tonic::transport::server::Router, { - let router = f(tonic::transport::Server::builder()); + let builder = + tonic::transport::Server::builder().timeout(std::time::Duration::from_secs(30)); + let router = f(builder); Server { router } } -- cgit v1.2.3 From 6e25870046586fea54ba74ae1e673d2ad22eaeae Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 2 Jul 2025 12:09:45 -0600 Subject: chore: add minimal logging --- src/authorization/check_service.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/authorization/check_service.rs b/src/authorization/check_service.rs index f8c7577f..57f7b5d5 100644 --- a/src/authorization/check_service.rs +++ b/src/authorization/check_service.rs @@ -23,8 +23,10 @@ impl envoy_types::ext_authz::v3::pb::Authorization for CheckService { request: Request, ) -> Result, Status> { if self.authorizer.authorize(request.into_inner()) { + log::info!("OK"); Ok(Response::new(CheckResponse::with_status(Status::ok("OK")))) } else { + log::info!("Unauthorized"); Ok(Response::new(CheckResponse::with_status( Status::unauthenticated("Unauthorized"), ))) -- cgit v1.2.3 From bc673b0de36342ef4fca8d0ae4f8bd029b4054b8 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 2 Jul 2025 12:16:29 -0600 Subject: chore: request method, path and headers in tracing --- src/authorization/server.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/authorization/server.rs b/src/authorization/server.rs index 6ae9361f..da686350 100644 --- a/src/authorization/server.rs +++ b/src/authorization/server.rs @@ -24,8 +24,16 @@ impl Server { where F: FnOnce(tonic::transport::Server) -> tonic::transport::server::Router, { - let builder = - tonic::transport::Server::builder().timeout(std::time::Duration::from_secs(30)); + let builder = tonic::transport::Server::builder() + .trace_fn(|req| { + tracing::info_span!( + "grpc_request", + method = %req.method(), + path = %req.uri().path(), + headers = ?req.headers(), + ) + }) + .timeout(std::time::Duration::from_secs(30)); let router = f(builder); Server { router } } -- cgit v1.2.3 From 0b610d061e45811130d8cf3919037fdc9513e340 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 2 Jul 2025 12:17:31 -0600 Subject: chore: rename log message --- src/authorization/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/authorization/server.rs b/src/authorization/server.rs index da686350..2ad270df 100644 --- a/src/authorization/server.rs +++ b/src/authorization/server.rs @@ -27,7 +27,7 @@ impl Server { let builder = tonic::transport::Server::builder() .trace_fn(|req| { tracing::info_span!( - "grpc_request", + "request", method = %req.method(), path = %req.uri().path(), headers = ?req.headers(), -- cgit v1.2.3