summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-06-18 17:11:42 -0600
committermo khan <mo@mokhan.ca>2025-06-18 17:11:42 -0600
commit2694c82d97005ca39f29f540e26249c18a21f6d6 (patch)
tree259be3c918a047e26fb357b406d915315aa0ead5
parentc2b8edab01b23fde6cc196a3349ad6aa19a93299 (diff)
refactor: switch to a pure rust implementation
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml4
-rw-r--r--.runway/runway.yml2
-rw-r--r--.tool-versions5
-rw-r--r--Cargo.lock813
-rw-r--r--Cargo.toml9
-rw-r--r--Dockerfile13
-rw-r--r--Makefile28
-rw-r--r--README.md12
-rw-r--r--app/app.go31
-rw-r--r--app/app_test.go52
-rw-r--r--app/init.go15
-rw-r--r--app/server.go24
-rw-r--r--app/server_test.go63
-rw-r--r--app/services/ability.go27
-rw-r--r--app/services/check.go72
-rw-r--r--app/services/check_test.go95
-rw-r--r--cmd/authzd/main.go16
-rw-r--r--go.mod48
-rw-r--r--go.sum102
-rw-r--r--magefile.go55
-rw-r--r--mise.toml5
-rw-r--r--pkg/.keep0
-rw-r--r--pkg/gid/gid.go36
-rw-r--r--pkg/gid/gid_test.go38
-rw-r--r--pkg/policies/allowed.go29
-rw-r--r--pkg/policies/allowed_test.go146
-rw-r--r--pkg/policies/entities.json286
-rw-r--r--pkg/policies/gtwy.cedar12
-rw-r--r--pkg/policies/init.go60
-rw-r--r--pkg/policies/organization.cedar5
-rw-r--r--pkg/rpc/ability.pb.go194
-rw-r--r--pkg/rpc/ability.twirp.go1104
-rw-r--r--protos/ability.proto19
-rw-r--r--src/main.rs44
-rw-r--r--tmp/cache/.keep0
-rw-r--r--tmp/pids/.keep0
37 files changed, 899 insertions, 2566 deletions
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: &timestamppb.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
--- a/pkg/.keep
+++ /dev/null
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: <baseURL>[<prefix>]/<package>.<Service>/<Method>
- 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: <baseURL>[<prefix>]/<package>.<Service>/<Method>
- 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>]/<package>.<Service>/<Method>
- 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: "/<prefix>/<package>.<Service>/"
-// that is everything in a Twirp route except for the <Method>. 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: "/<prefix>/<package>.<Service>/"
- // that is, everything in a Twirp route except for the <Method> 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 <Method>).
-// 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: "[<prefix>]/<package>.<Service>/<Method>"
-// 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 &copy
-}
-
-// 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<CheckRequest>,
+ ) -> Result<Response<CheckResponse>, 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<dyn std::error::Error>> {
+ 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
--- a/tmp/cache/.keep
+++ /dev/null
diff --git a/tmp/pids/.keep b/tmp/pids/.keep
deleted file mode 100644
index e69de29b..00000000
--- a/tmp/pids/.keep
+++ /dev/null