diff options
37 files changed, 899 insertions, 2566 deletions
@@ -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 = "*" @@ -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"] @@ -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) @@ -52,28 +52,18 @@ It integrates with an identity provider (IdP) and uses message queues to stay in ```sh $ mise install - $ make install-tools ``` 1. Start servers: ```sh - $ mage servers + $ cargo run ``` -## Questions - -See the [FAQ][9] - ## References -* [go tool][5] * [gRPC][6] * [protocol buffers][7] -* [twirp][8] -[1]: https://github.com/twitchtv/twirp -[5]: https://tip.golang.org/doc/modules/managing-dependencies#tools [6]: https://grpc.io/docs/ [7]: https://protobuf.dev/programming-guides/proto3/ -[8]: https://github.com/arthurnn/twirp-ruby/wiki/Code-Generation diff --git a/app/app.go b/app/app.go deleted file mode 100644 index f79b67b1..00000000 --- a/app/app.go +++ /dev/null @@ -1,31 +0,0 @@ -package app - -import ( - "net/http" - - "github.com/rs/zerolog" - "github.com/xlgmokha/x/pkg/ioc" - "github.com/xlgmokha/x/pkg/log" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/app/services" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/rpc" -) - -func New() http.Handler { - mux := http.NewServeMux() - for _, handler := range handlers() { - mux.Handle(handler.PathPrefix(), handler) - } - - mux.Handle("/health", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - })) - - logger := ioc.MustResolve[*zerolog.Logger](ioc.Default) - return log.HTTP(logger)(mux) -} - -func handlers() []rpc.TwirpServer { - return []rpc.TwirpServer{ - rpc.NewAbilityServer(services.NewAbilityService()), - } -} diff --git a/app/app_test.go b/app/app_test.go deleted file mode 100644 index f0068e87..00000000 --- a/app/app_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package app - -import ( - http "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/rpc" -) - -func TestApp(t *testing.T) { - handler := New() - srv := httptest.NewServer(handler) - defer srv.Close() - - t.Run("Ability.Allowed", func(t *testing.T) { - client := rpc.NewAbilityProtobufClient(srv.URL, &http.Client{}) - - t.Run("forbids", func(t *testing.T) { - reply, err := client.Allowed(t.Context(), &rpc.AllowRequest{ - Subject: "", - Permission: "", - Resource: "", - }) - require.NoError(t, err) - assert.False(t, reply.Result) - }) - - t.Run("allows gid://User/1 read gid://Organization/2", func(t *testing.T) { - reply, err := client.Allowed(t.Context(), &rpc.AllowRequest{ - Subject: "gid://example/User/1", - Permission: "read", - Resource: "gid://example/Organization/2", - }) - require.NoError(t, err) - assert.True(t, reply.Result) - }) - }) - - t.Run("GET /health", func(t *testing.T) { - t.Run("returns OK", func(t *testing.T) { - r := httptest.NewRequest("GET", "/health", nil) - w := httptest.NewRecorder() - - handler.ServeHTTP(w, r) - - assert.Equal(t, http.StatusOK, w.Code) - }) - }) -} diff --git a/app/init.go b/app/init.go deleted file mode 100644 index 3c4e757e..00000000 --- a/app/init.go +++ /dev/null @@ -1,15 +0,0 @@ -package app - -import ( - "os" - - "github.com/rs/zerolog" - "github.com/xlgmokha/x/pkg/ioc" - "github.com/xlgmokha/x/pkg/log" -) - -func init() { - ioc.RegisterSingleton[*zerolog.Logger](ioc.Default, func() *zerolog.Logger { - return log.New(os.Stdout, log.Fields{"app": "authzd"}) - }) -} diff --git a/app/server.go b/app/server.go deleted file mode 100644 index 3ce0aadc..00000000 --- a/app/server.go +++ /dev/null @@ -1,24 +0,0 @@ -package app - -import ( - "context" - - auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/app/services" - "google.golang.org/grpc" - "google.golang.org/grpc/reflection" -) - -type Server struct { - *grpc.Server -} - -func NewServer(ctx context.Context, options ...grpc.ServerOption) *Server { - server := grpc.NewServer(options...) - auth.RegisterAuthorizationServer(server, services.NewCheckService()) - reflection.Register(server) - - return &Server{ - Server: server, - } -} diff --git a/app/server_test.go b/app/server_test.go deleted file mode 100644 index ff34487a..00000000 --- a/app/server_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package app - -import ( - "context" - "net" - "testing" - - auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/test/bufconn" -) - -type HTTPRequest = auth.AttributeContext_HttpRequest - -func TestServer(t *testing.T) { - socket := bufconn.Listen(1024 * 1024) - srv := NewServer(t.Context()) - - defer srv.GracefulStop() - go func() { - require.NoError(t, srv.Serve(socket)) - }() - - connection, err := grpc.DialContext( - t.Context(), - "bufnet", - grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { - return socket.Dial() - }), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - require.NoError(t, err) - defer connection.Close() - - client := auth.NewAuthorizationClient(connection) - - t.Run("CheckRequest", func(t *testing.T) { - tt := []struct { - http *HTTPRequest - status codes.Code - }{ - {status: codes.OK, http: &HTTPRequest{Method: "GET", Path: "/"}}, - } - - for _, example := range tt { - t.Run(example.http.Path, func(t *testing.T) { - response, err := client.Check(t.Context(), &auth.CheckRequest{ - Attributes: &auth.AttributeContext{ - Request: &auth.AttributeContext_Request{ - Http: example.http, - }, - }, - }) - require.NoError(t, err) - assert.Equal(t, int32(example.status), response.Status.Code) - }) - } - }) -} diff --git a/app/services/ability.go b/app/services/ability.go deleted file mode 100644 index f0379513..00000000 --- a/app/services/ability.go +++ /dev/null @@ -1,27 +0,0 @@ -package services - -import ( - context "context" - - "github.com/cedar-policy/cedar-go" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/gid" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/policies" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/rpc" -) - -type AbilityService struct { -} - -func NewAbilityService() *AbilityService { - return &AbilityService{} -} - -func (h *AbilityService) Allowed(ctx context.Context, req *rpc.AllowRequest) (*rpc.AllowReply, error) { - ok := policies.Allowed(ctx, cedar.Request{ - Principal: gid.NewEntityUID(req.Subject), - Action: cedar.NewEntityUID("Permission", cedar.String(req.Permission)), - Resource: gid.NewEntityUID(req.Resource), - Context: cedar.NewRecord(cedar.RecordMap{}), - }) - return &rpc.AllowReply{Result: ok}, nil -} diff --git a/app/services/check.go b/app/services/check.go deleted file mode 100644 index 23deecb9..00000000 --- a/app/services/check.go +++ /dev/null @@ -1,72 +0,0 @@ -package services - -import ( - "context" - - "github.com/cedar-policy/cedar-go" - core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" - types "github.com/envoyproxy/go-control-plane/envoy/type/v3" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/gid" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/policies" - status "google.golang.org/genproto/googleapis/rpc/status" - "google.golang.org/grpc/codes" -) - -type CheckService struct { - auth.UnimplementedAuthorizationServer -} - -func NewCheckService() *CheckService { - return &CheckService{} -} - -func (svc *CheckService) Check(ctx context.Context, request *auth.CheckRequest) (*auth.CheckResponse, error) { - if svc.isAllowed(ctx, request) { - return svc.OK(ctx), nil - } - return svc.Denied(ctx), nil -} - -func (svc *CheckService) isAllowed(ctx context.Context, r *auth.CheckRequest) bool { - return policies.Allowed(ctx, cedar.Request{ - Principal: gid.NewEntityUID("gid://gitlab/User/*"), - Action: cedar.NewEntityUID("HttpMethod", cedar.String(r.Attributes.Request.Http.Method)), - Resource: cedar.NewEntityUID("HttpPath", cedar.String(r.Attributes.Request.Http.Path)), - Context: cedar.NewRecord(cedar.RecordMap{ - "host": cedar.String(r.Attributes.Request.Http.Host), - }), - }) - -} - -func (svc *CheckService) OK(ctx context.Context) *auth.CheckResponse { - return &auth.CheckResponse{ - Status: &status.Status{ - Code: int32(codes.OK), - }, - HttpResponse: &auth.CheckResponse_OkResponse{ - OkResponse: &auth.OkHttpResponse{ - Headers: []*core.HeaderValueOption{}, - HeadersToRemove: []string{}, - ResponseHeadersToAdd: []*core.HeaderValueOption{}, - }, - }, - } -} - -func (svc *CheckService) Denied(ctx context.Context) *auth.CheckResponse { - return &auth.CheckResponse{ - Status: &status.Status{ - Code: int32(codes.PermissionDenied), - }, - HttpResponse: &auth.CheckResponse_DeniedResponse{ - DeniedResponse: &auth.DeniedHttpResponse{ - Status: &types.HttpStatus{ - Code: types.StatusCode_Unauthorized, - }, - Headers: []*core.HeaderValueOption{}, - }, - }, - } -} diff --git a/app/services/check_test.go b/app/services/check_test.go deleted file mode 100644 index 4eb396bb..00000000 --- a/app/services/check_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package services - -import ( - "strings" - "testing" - - core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/timestamppb" -) - -func TestCheckService(t *testing.T) { - svc := NewCheckService() - - t.Run("allows access", func(t *testing.T) { - idToken := "header.payload.signature" - accessToken := "f88f60df11e458b594c80b299aee05f8e5805c65c3e779cc6fbc606c4ac36227" - refreshToken := "0847d325d6e4f021c4baaae0ddb425dbd8795807a4751cd2131bec8e8a9aee24" - - cookies := []string{ - "bearer_token=" + accessToken + ";", - "id_token=" + idToken + ";", - "refresh_token=" + refreshToken, - } - - response, err := svc.Check(t.Context(), &auth.CheckRequest{ - Attributes: &auth.AttributeContext{ - Source: &auth.AttributeContext_Peer{ - Address: &core.Address{ - Address: &core.Address_SocketAddress{ - SocketAddress: &core.SocketAddress{ - Address: "127.0.0.1", - PortSpecifier: &core.SocketAddress_PortValue{ - PortValue: 52358, - }, - }, - }, - }, - }, - Destination: &auth.AttributeContext_Peer{ - Address: &core.Address{ - Address: &core.Address_SocketAddress{ - SocketAddress: &core.SocketAddress{ - Address: "127.0.0.1", - PortSpecifier: &core.SocketAddress_PortValue{ - PortValue: 10000, - }, - }, - }, - }, - }, - Request: &auth.AttributeContext_Request{ - Time: ×tamppb.Timestamp{Seconds: 1747937928, Nanos: 476481000}, - Http: &auth.AttributeContext_HttpRequest{ - Id: "1248474133684962828", - Method: "GET", - Headers: map[string]string{ - ":authority": "localhost:10000", - ":method": "GET", - ":path": "/health", - ":scheme": "http", - "accept": "*/*", - "accept-encoding": "gzip, deflate, br, zstd", - "accept-language": "en-US,en;q=0.9", - "cache-control": "max-age=0", - "content-length": "64", - "content-type": "application/json", - "cookie": strings.Join(cookies, "; "), - "origin": "http://localhost:10000", - "referer": "http://localhost:10000/dashboard", - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": "Linux", - "sec-fetch-dest": "empty", - "sec-fetch-mode": "cors", - "sec-fetch-site": "same-origin", - "x-forwarded-proto": "http", - "x-request-id": "7e064610-9e19-4a38-8354-0de0b5fbd7c6", - }, - Path: "/health", - Host: "localhost:10000", - Scheme: "http", - Protocol: "HTTP/1.1", - }, - }, - MetadataContext: &core.Metadata{}, - RouteMetadataContext: &core.Metadata{}, - }, - }) - - require.NoError(t, err) - assert.NotNil(t, response.GetOkResponse()) - }) -} diff --git a/cmd/authzd/main.go b/cmd/authzd/main.go deleted file mode 100644 index 05b0b148..00000000 --- a/cmd/authzd/main.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - - "github.com/xlgmokha/x/pkg/env" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/app" -) - -func main() { - bindAddr := env.Fetch("BIND_ADDR", "localhost:8080") - fmt.Printf("Listening on %v\n", bindAddr) - log.Fatal(http.ListenAndServe(bindAddr, app.New())) -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 0da45df9..00000000 --- a/go.mod +++ /dev/null @@ -1,48 +0,0 @@ -module gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git - -go 1.24 - -require ( - github.com/cedar-policy/cedar-go v1.2.1 - github.com/envoyproxy/go-control-plane/envoy v1.32.4 - github.com/magefile/mage v1.15.0 - github.com/rs/zerolog v1.34.0 - github.com/stretchr/testify v1.10.0 - github.com/twitchtv/twirp v8.1.3+incompatible - github.com/xlgmokha/x v0.0.0-20250430185455-b691eda27477 - google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 - google.golang.org/grpc v1.71.0 - google.golang.org/protobuf v1.36.6 -) - -require ( - github.com/arthurnn/twirp-ruby v1.13.0 // indirect - github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/golobby/container/v3 v3.3.2 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect - golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect - golang.org/x/net v0.37.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/text v0.24.0 // indirect - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) - -tool ( - github.com/arthurnn/twirp-ruby/protoc-gen-twirp_ruby - github.com/magefile/mage - github.com/twitchtv/twirp/protoc-gen-twirp - google.golang.org/grpc/cmd/protoc-gen-go-grpc - google.golang.org/protobuf/cmd/protoc-gen-go -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 0a713aca..00000000 --- a/go.sum +++ /dev/null @@ -1,102 +0,0 @@ -github.com/arthurnn/twirp-ruby v1.13.0 h1:j0T7I5oxe2niKFdfjiiCmkiydwYeegrbwVMs+Gajm6M= -github.com/arthurnn/twirp-ruby v1.13.0/go.mod h1:1fVOQuSLzwXoPi9/ejlDYG3roilJIPAZN2sw+A3o48o= -github.com/cedar-policy/cedar-go v1.2.1 h1:f4Ie1j6OT2TqtmnbVcR7OyJWfyPEUvYrFYaurQZhTAo= -github.com/cedar-policy/cedar-go v1.2.1/go.mod h1:Ahhlu3DxDzvGTR88v/+j/EeiMEyvta11hlkdDgj4AZU= -github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q= -github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= -github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golobby/container/v3 v3.3.2 h1:7u+RgNnsdVlhGoS8gY4EXAG601vpMMzLZlYqSp77Quw= -github.com/golobby/container/v3 v3.3.2/go.mod h1:RDdKpnKpV1Of11PFBe7Dxc2C1k2KaLE4FD47FflAmj0= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonapi v1.0.0 h1:qIGgO5Smu3yJmSs+QlvhQnrscdZfFhiV6S8ryJAglqU= -github.com/google/jsonapi v1.0.0/go.mod h1:YYHiRPJT8ARXGER8In9VuLv4qvLfDmA9ULQqptbLE4s= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= -github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= -github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU= -github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= -github.com/xlgmokha/x v0.0.0-20250430185455-b691eda27477 h1:oeKrn9BSDSic5MTXKhQesxhIhB7byxl86IHtCD+yw4k= -github.com/xlgmokha/x v0.0.0-20250430185455-b691eda27477/go.mod h1:axGPKzoJCNTmPJxYqN5l+Z9gGbPe0yolkT61a5p3QiI= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= -golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/magefile.go b/magefile.go deleted file mode 100644 index 49cd4c87..00000000 --- a/magefile.go +++ /dev/null @@ -1,55 +0,0 @@ -//go:build mage -// +build mage - -package main - -import ( - "context" - "path/filepath" - - "github.com/magefile/mage/mg" - "github.com/magefile/mage/sh" - "github.com/xlgmokha/x/pkg/x" -) - -// Default target to run when none is specified -// If not set, running mage will list available targets -var Default = Servers - -// Run the Authzd Service -func Authzd() error { - env := map[string]string{ - "BIND_ADDR": ":8080", - } - return sh.RunWithV(env, "go", "run", "./cmd/authzd/main.go") -} - -// Generate gRPC from protocal buffers -func Protos() error { - for _, file := range x.Must(filepath.Glob("./protos/*.proto")) { - if err := sh.RunV( - "protoc", - "--proto_path=./protos", - "--go_out=.", - "--twirp_out=.", - file, - ); err != nil { - return err - } - } - - return nil -} - -// Run All the servers -func Servers(ctx context.Context) { - mg.CtxDeps(ctx, Authzd) -} - -// Run the end to end tests -func Test(ctx context.Context) error { - mg.CtxDeps(ctx, func() error { - return sh.RunV("go", "clean", "-testcache") - }) - return sh.RunV("go", "test", "-shuffle=on", "-v", "./...") -} diff --git a/mise.toml b/mise.toml new file mode 100644 index 00000000..87a29b17 --- /dev/null +++ b/mise.toml @@ -0,0 +1,5 @@ +[tools] +cargo = "latest" +make = "latest" +rust = "stable" +yamlfmt = "latest" diff --git a/pkg/.keep b/pkg/.keep deleted file mode 100644 index e69de29b..00000000 --- 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 © -} - -// 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 |
