From 3f9410d6e80a7ca2dd83142a31e0b22054be02d6 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Wed, 15 Feb 2023 11:06:09 +0000 Subject: [PATCH 01/32] rust stuff --- charts/nginz/templates/conf/_nginx.conf.tpl | 2 + charts/nginz/templates/secret.yaml | 1 + charts/nginz/values.yaml | 1 + hack/helm_vars/wire-server/values.yaml.gotmpl | 6 + libs/libzauth/libzauth-c/Cargo.lock | 767 +++++++++++++++++- libs/libzauth/libzauth-c/Cargo.toml | 4 + libs/libzauth/libzauth-c/src/lib.rs | 26 + libs/libzauth/libzauth/Cargo.toml | 9 + libs/libzauth/libzauth/src/lib.rs | 6 + libs/libzauth/libzauth/src/oauth.rs | 94 +++ .../integration-test/conf/nginz/nginx.conf | 1 + .../resources/oauth/ed25519_public.jwk | 1 + 12 files changed, 906 insertions(+), 12 deletions(-) create mode 100644 libs/libzauth/libzauth/src/oauth.rs create mode 100644 services/nginz/integration-test/resources/oauth/ed25519_public.jwk diff --git a/charts/nginz/templates/conf/_nginx.conf.tpl b/charts/nginz/templates/conf/_nginx.conf.tpl index dba5c08c50..b60a9a7f35 100644 --- a/charts/nginz/templates/conf/_nginx.conf.tpl +++ b/charts/nginz/templates/conf/_nginx.conf.tpl @@ -193,6 +193,7 @@ http { zauth_keystore {{ .Values.nginx_conf.zauth_keystore }}; zauth_acl {{ .Values.nginx_conf.zauth_acl }}; + oauth_key {{ .Values.nginx_conf.oauth_key }}; location /status { zauth off; @@ -345,6 +346,7 @@ http { # we need to specify zauth_keystore etc. zauth_keystore {{ .Values.nginx_conf.zauth_keystore }}; zauth_acl {{ .Values.nginx_conf.zauth_acl }}; + oauth_key {{ .Values.nginx_conf.oauth_key }}; listen {{ .Values.config.http.metricsPort }}; diff --git a/charts/nginz/templates/secret.yaml b/charts/nginz/templates/secret.yaml index 12779270f6..b7746d74f7 100644 --- a/charts/nginz/templates/secret.yaml +++ b/charts/nginz/templates/secret.yaml @@ -15,4 +15,5 @@ data: {{- with .Values.secrets }} zauth.conf: {{ .zAuth.publicKeys | b64enc | quote }} basic_auth.txt: {{ .basicAuth | b64enc | quote }} + oauth_ed25519_pub.jwk: {{ .oAuth.publicKeys | b64enc | quote }} {{- end }} diff --git a/charts/nginz/values.yaml b/charts/nginz/values.yaml index cf1630ab51..5992b13ab5 100644 --- a/charts/nginz/values.yaml +++ b/charts/nginz/values.yaml @@ -30,6 +30,7 @@ nginx_conf: zauth_keystore: /etc/wire/nginz/secrets/zauth.conf zauth_acl: /etc/wire/nginz/conf/zauth.acl basic_auth_file: /etc/wire/nginz/secrets/basic_auth.txt + oauth_key: /etc/wire/nginz/secrets/oauth_ed25519_pub.jwk worker_processes: auto worker_rlimit_nofile: 131072 worker_connections: 65536 diff --git a/hack/helm_vars/wire-server/values.yaml.gotmpl b/hack/helm_vars/wire-server/values.yaml.gotmpl index 4e42674cc3..da8493e084 100644 --- a/hack/helm_vars/wire-server/values.yaml.gotmpl +++ b/hack/helm_vars/wire-server/values.yaml.gotmpl @@ -244,6 +244,12 @@ nginz: zAuth: # this must match the key in brig! publicKeys: 0UW38se1yeoc5bVNEvf5LyrHWGZkyvcGTVilK2geGdU= + oAuth: | + { + "kty": "OKP", + "crv": "Ed25519", + "x": "mhP-NgFw3ifIXGZqJVB0kemt9L3BtD5P8q4Gah4Iklc" + } proxy: replicaCount: 1 imagePullPolicy: {{ .Values.imagePullPolicy }} diff --git a/libs/libzauth/libzauth-c/Cargo.lock b/libs/libzauth/libzauth-c/Cargo.lock index 7acceb85ae..c6b22c39df 100644 --- a/libs/libzauth/libzauth-c/Cargo.lock +++ b/libs/libzauth/libzauth-c/Cargo.lock @@ -11,12 +11,69 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" + [[package]] name = "asexp" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e368761ce758947307f1c2db1f46077b1aabb5af7f268b6cededd1b52802652" +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "base64ct" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" + +[[package]] +name = "binstring" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e0d60973d9320722cb1206f412740e162a33b8547ea8d6be75d7cff237c7a85" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "cc" version = "1.0.73" @@ -24,12 +81,260 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] -name = "ed25519" -version = "1.4.1" +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "coarsetime" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "454038500439e141804c655b4cd1bc6a70bcb95cd2bc9463af5661b6956f0e46" +dependencies = [ + "libc", + "once_cell", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5c4b5e5959dc2c2b89918d8e2cc40fcdd623cef026ed09d2f0ee05199dc8e4" +checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ - "signature", + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ct-codecs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12844141594ad74185a926d030f3b605f6a903b4e3fec351f3ea338ac5b7637e" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature 2.0.0", +] + +[[package]] +name = "ed25519-compact" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3d382e8464107391c8706b4c14b087808ecb909f6c15c34114bc42e53a9e4c" +dependencies = [ + "ct-codecs", + "getrandom", +] + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha1-compact" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e2440a0078e20c3b68ca01234cea4219f23e64b0c0bdb1200c5550d54239bb" + +[[package]] +name = "hmac-sha256" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc736091aacb31ddaa4cd5f6988b3c21e99913ac846b41f32538c5fae5d71bfe" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha512" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520c9c3f6040661669bc5c91e551b605a520c8e0a63a766a91a65adef734d151" +dependencies = [ + "digest", +] + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "jwt-simple" +version = "0.11.3" +source = "git+https://github.com/wireapp/rust-jwt-simple?tag=v0.11.3-pre.core-crypto-0.6.0#15a69f82288d68b74a75c1364e5d4bf681f1c07b" +dependencies = [ + "anyhow", + "binstring", + "coarsetime", + "ct-codecs", + "ed25519-compact", + "hmac-sha1-compact", + "hmac-sha256", + "hmac-sha512", + "k256", + "p256", + "p384", + "rand", + "rsa", + "serde", + "serde_json", + "spki", + "thiserror", + "zeroize", +] + +[[package]] +name = "k256" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92a55e0ff3b72c262bcf041d9e97f1b84492b68f1c1a384de2323d3dc9403397" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature 2.0.0", ] [[package]] @@ -37,6 +342,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -44,6 +352,12 @@ version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" +[[package]] +name = "libm" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + [[package]] name = "libsodium-sys" version = "0.2.7" @@ -56,18 +370,199 @@ dependencies = [ "walkdir", ] +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "num-bigint-dig" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2399c9463abc5f909349d8aa9ba080e0b88b3ce2885389b60b993f39b1a56905" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "p256" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49c124b3cbce43bcbac68c58ec181d98ed6cc7e6d0aa7c3ba97b2563410b0e55" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630a4a9b2618348ececfae61a4905f564b817063bf2d66cdfc2ced523fe1d2d4" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "pem-rfc7468" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pkcs1" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719" +dependencies = [ + "der", + "pkcs8", + "spki", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primeorder" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b54f7131b3dba65a2f414cf5bd25b66d4682e4608610668eae785750ba4c5b2" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "regex" version = "1.6.0" @@ -85,12 +580,50 @@ version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint", + "hmac", + "zeroize", +] + +[[package]] +name = "rsa" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "094052d5470cbcef561cb848a7209968c9f12dfa6d668f4bca048ac5de51099c" +dependencies = [ + "byteorder", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature 1.6.4", + "smallvec", + "subtle", + "zeroize", +] + [[package]] name = "rustc-serialize" version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + [[package]] name = "same-file" version = "1.0.6" @@ -100,30 +633,169 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "serde" -version = "1.0.137" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signature" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest", + "rand_core", +] [[package]] name = "signature" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" +checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "sodiumoxide" -version = "0.2.7" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e26be3acb6c2d9a7aac28482586a7856436af4cfe7100031d219de2d2ecb0028" +checksum = "585232e78a4fc18133eef9946d3080befdf68b906c51b621531c37e91787fa2b" dependencies = [ - "ed25519", "libc", "libsodium-sys", - "serde", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "walkdir" version = "2.3.2" @@ -135,6 +807,66 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + [[package]] name = "winapi" version = "0.3.9" @@ -171,10 +903,15 @@ name = "zauth" version = "3.1.0" dependencies = [ "asexp", + "base64", + "jwt-simple", "lazy_static", "regex", "rustc-serialize", + "serde", + "serde_json", "sodiumoxide", + "thiserror", ] [[package]] @@ -184,3 +921,9 @@ dependencies = [ "libc", "zauth", ] + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/libs/libzauth/libzauth-c/Cargo.toml b/libs/libzauth/libzauth-c/Cargo.toml index 73ef35cc2f..9aa25bbf2b 100644 --- a/libs/libzauth/libzauth-c/Cargo.toml +++ b/libs/libzauth/libzauth-c/Cargo.toml @@ -13,3 +13,7 @@ libc = ">= 0.2" [dependencies.zauth] path = "../libzauth" + +[patch.crates-io.jwt-simple] +git = "https://github.com/wireapp/rust-jwt-simple" +tag = "v0.11.3-pre.core-crypto-0.6.0" diff --git a/libs/libzauth/libzauth-c/src/lib.rs b/libs/libzauth/libzauth-c/src/lib.rs index 12850c4808..07408d17f4 100644 --- a/libs/libzauth/libzauth-c/src/lib.rs +++ b/libs/libzauth/libzauth-c/src/lib.rs @@ -289,3 +289,29 @@ impl From for ZauthTokenVerification { } } } + +#[no_mangle] +pub extern fn verify_oauth_token(jwk: *const u8, jwk_len: size_t, token: *const u8, token_len: size_t, scope: *const u8, scope_len: size_t, s: *mut *mut libc::c_char) -> ZauthResult { + if jwk.is_null() { + return ZauthResult::NullArg; + } + if token.is_null() { + return ZauthResult::NullArg; + } + if scope.is_null() { + return ZauthResult::NullArg; + } + catch_unwind(|| { + let bytes = unsafe { slice::from_raw_parts(jwk, jwk_len) }; + let jwk = try_unwrap!(str::from_utf8(bytes)); + let bytes = unsafe { slice::from_raw_parts(token, token_len) }; + let token = try_unwrap!(str::from_utf8(bytes)); + let bytes = unsafe { slice::from_raw_parts(scope, scope_len) }; + let scope = try_unwrap!(str::from_utf8(bytes)); + let subject = try_unwrap!(verify_oauth_token(jwk, token, scope)); + unsafe { + *s= Box::into_raw(Box::new(CString::new(subject).unwrap().into_raw())); + } + ZauthResult::Ok + }) +} diff --git a/libs/libzauth/libzauth/Cargo.toml b/libs/libzauth/libzauth/Cargo.toml index 34920bd6f2..49a3a3108f 100644 --- a/libs/libzauth/libzauth/Cargo.toml +++ b/libs/libzauth/libzauth/Cargo.toml @@ -13,6 +13,15 @@ rustc-serialize = ">= 0.3" sodiumoxide = "^0.2.7" regex = "1.6" lazy_static = "1.4" +jwt-simple = "0.11" +serde_json = "1.0" +serde = "1.0" +thiserror = "1.0" +base64 = "0.21" [dev-dependencies] clap = ">= 2.0" + +[patch.crates-io.jwt-simple] +git = "https://github.com/wireapp/rust-jwt-simple" +tag = "v0.11.3-pre.core-crypto-0.6.0" diff --git a/libs/libzauth/libzauth/src/lib.rs b/libs/libzauth/libzauth/src/lib.rs index 9d13e25627..31b005a540 100644 --- a/libs/libzauth/libzauth/src/lib.rs +++ b/libs/libzauth/libzauth/src/lib.rs @@ -20,10 +20,16 @@ extern crate lazy_static; extern crate regex; extern crate rustc_serialize; extern crate sodiumoxide; +extern crate jwt_simple; +extern crate serde_json; +extern crate serde; +extern crate thiserror; +extern crate base64; pub mod acl; pub mod error; pub mod zauth; +pub mod oauth; mod matcher; diff --git a/libs/libzauth/libzauth/src/oauth.rs b/libs/libzauth/libzauth/src/oauth.rs new file mode 100644 index 0000000000..ff55c7e0d8 --- /dev/null +++ b/libs/libzauth/libzauth/src/oauth.rs @@ -0,0 +1,94 @@ +use base64::Engine; +use jwt_simple::prelude::*; + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct OAuthToken { + pub scope: String, +} + +pub fn verify_oauth_token(jwk: &str, token: &str, scope: &str) -> Result { + let jwk = serde_json::from_str::(jwk)?; + let key = try_from_jwk(&jwk)?; + let options = VerificationOptions { + time_tolerance: Some(Duration::from_secs(1)), + ..Default::default() + }; + let claims = key.verify_token::(token, Some(options))?; + let subject = claims.subject.ok_or(OauthError::InvalidJwtNoSubject)?; + verify_scope(&claims.custom.scope, scope)?; + Ok(subject) +} + +fn verify_scope(scope: &str, required_scope: &str) -> Result<(), OauthError> { + let valid = scope.split_whitespace().any(|s| s == required_scope); + if !valid { + return Err(OauthError::InvalidScope); + } + Ok(()) +} + +fn try_from_jwk(jwk: &Jwk) -> Result { + Ok(match &jwk.algorithm { + AlgorithmParameters::OctetKeyPair(p) => { + let x = base64::prelude::BASE64_URL_SAFE_NO_PAD.decode(&p.x)?; + Ed25519PublicKey::from_bytes(&x)? + } + _ => return Err(OauthError::InvalidJwk), + }) +} + +#[derive(Debug, thiserror::Error)] +pub enum OauthError { + /// Json error + #[error(transparent)] + JsonError(#[from] serde_json::Error), + /// JWT error from jwt-simple crate + #[error(transparent)] + JwtSimpleError(#[from] jwt_simple::Error), + /// Base64 decoding error + #[error(transparent)] + Base64DecodeError(#[from] base64::DecodeError), + /// Invalid JWK + #[error("invalid jwk")] + InvalidJwk, + /// Invalid JWT missing subject + #[error("missing subject")] + InvalidJwtNoSubject, + /// Invalid scope + #[error("invalid scope")] + InvalidScope, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_verify_oauth_token() { + let uid = "842ddbc8-56ec-408d-9fa8-7a8c37ad22a7"; + let key = Ed25519KeyPair::generate(); + let jwk = mk_jwk(key.public_key()); + let token = Claims::with_custom_claims( + OAuthToken { + scope: "foo test bar".to_string(), + }, + Duration::from_secs(3600), + ) + .with_subject(uid); + let jwt = key.sign::(token).unwrap(); + let subject = + verify_oauth_token(&serde_json::to_string(&jwk).unwrap(), &jwt, "test").unwrap(); + assert_eq!(&subject, uid); + } + fn mk_jwk(key: Ed25519PublicKey) -> Jwk { + let x = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(&key.to_bytes()); + Jwk { + common: CommonParameters::default(), + algorithm: AlgorithmParameters::OctetKeyPair(OctetKeyPairParameters { + key_type: OctetKeyPairType::OctetKeyPair, + curve: EdwardCurve::Ed25519, + x, + }), + } + } +} diff --git a/services/nginz/integration-test/conf/nginz/nginx.conf b/services/nginz/integration-test/conf/nginz/nginx.conf index 974314c6fa..51d1c80a10 100644 --- a/services/nginz/integration-test/conf/nginz/nginx.conf +++ b/services/nginz/integration-test/conf/nginz/nginx.conf @@ -118,6 +118,7 @@ http { zauth_keystore resources/zauth/pubkeys.txt; zauth_acl conf/nginz/zauth_acl.txt; + oauth_key resources/oauth/ed25519_public.jwk; location /status { set $sanitized_request $request; diff --git a/services/nginz/integration-test/resources/oauth/ed25519_public.jwk b/services/nginz/integration-test/resources/oauth/ed25519_public.jwk new file mode 100644 index 0000000000..9ef33c1ce2 --- /dev/null +++ b/services/nginz/integration-test/resources/oauth/ed25519_public.jwk @@ -0,0 +1 @@ +{"kty":"OKP","crv":"Ed25519","x":"mhP-NgFw3ifIXGZqJVB0kemt9L3BtD5P8q4Gah4Iklc"} From a216a47c4ff6b8f3ddab757caaa303dede69a086 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Wed, 15 Feb 2023 14:14:02 +0000 Subject: [PATCH 02/32] rust compiles --- libs/libzauth/libzauth-c/Cargo.lock | 15 +- libs/libzauth/libzauth-c/src/lib.rs | 202 +++++++++++------- libs/libzauth/libzauth/src/lib.rs | 1 + nix/pkgs/zauth/default.nix | 2 +- .../nginx-zauth-module/zauth_module.c | 18 ++ 5 files changed, 159 insertions(+), 79 deletions(-) diff --git a/libs/libzauth/libzauth-c/Cargo.lock b/libs/libzauth/libzauth-c/Cargo.lock index c6b22c39df..051bf1a2bb 100644 --- a/libs/libzauth/libzauth-c/Cargo.lock +++ b/libs/libzauth/libzauth-c/Cargo.lock @@ -176,6 +176,15 @@ dependencies = [ "signature 2.0.0", ] +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature 1.6.4", +] + [[package]] name = "ed25519-compact" version = "2.0.4" @@ -717,12 +726,14 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "sodiumoxide" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585232e78a4fc18133eef9946d3080befdf68b906c51b621531c37e91787fa2b" +checksum = "e26be3acb6c2d9a7aac28482586a7856436af4cfe7100031d219de2d2ecb0028" dependencies = [ + "ed25519", "libc", "libsodium-sys", + "serde", ] [[package]] diff --git a/libs/libzauth/libzauth-c/src/lib.rs b/libs/libzauth/libzauth-c/src/lib.rs index 07408d17f4..5088a3a386 100644 --- a/libs/libzauth/libzauth-c/src/lib.rs +++ b/libs/libzauth/libzauth-c/src/lib.rs @@ -20,31 +20,32 @@ extern crate zauth; use libc::size_t; use std::char; +use std::ffi::CString; use std::fs::File; use std::io::{self, BufReader, Read}; +use std::panic::{self, UnwindSafe}; use std::path::Path; use std::ptr; use std::slice; use std::str; -use std::panic::{self, UnwindSafe}; -use zauth::{Acl, Error, Keystore, Token, TokenType, TokenVerification}; use zauth::acl; +use zauth::{Acl, Error, Keystore, OauthError, Token, TokenType, TokenVerification}; /// Variant of std::try! that returns the unwrapped error. macro_rules! try_unwrap { ($expr:expr) => { match $expr { - Ok(x) => x, - Err(e) => return From::from(e) + Ok(x) => x, + Err(e) => return From::from(e), } - } + }; } #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct Range { ptr: *const u8, - len: size_t + len: size_t, } pub struct ZauthAcl(zauth::Acl); @@ -52,58 +53,70 @@ pub struct ZauthKeystore(zauth::Keystore); pub struct ZauthToken(zauth::Token<'static>); #[no_mangle] -pub extern fn zauth_keystore_open(f: *const u8, n: size_t, s: *mut *mut ZauthKeystore) -> ZauthResult { +pub extern "C" fn zauth_keystore_open( + f: *const u8, + n: size_t, + s: *mut *mut ZauthKeystore, +) -> ZauthResult { if f.is_null() { return ZauthResult::NullArg; } catch_unwind(|| { let bytes = unsafe { slice::from_raw_parts(f, n) }; - let path = try_unwrap!(str::from_utf8(bytes)); + let path = try_unwrap!(str::from_utf8(bytes)); let store = try_unwrap!(Keystore::open(&Path::new(path))); unsafe { - *s= Box::into_raw(Box::new(ZauthKeystore(store))); + *s = Box::into_raw(Box::new(ZauthKeystore(store))); } ZauthResult::Ok }) } #[no_mangle] -pub extern fn zauth_keystore_delete(s: *mut ZauthKeystore) { +pub extern "C" fn zauth_keystore_delete(s: *mut ZauthKeystore) { catch_unwind(|| { - unsafe { Box::from_raw(s); } + unsafe { + Box::from_raw(s); + } ZauthResult::Ok }); } #[no_mangle] -pub extern fn zauth_acl_open(f: *const u8, n: size_t, a: *mut *mut ZauthAcl) -> ZauthResult { +pub extern "C" fn zauth_acl_open(f: *const u8, n: size_t, a: *mut *mut ZauthAcl) -> ZauthResult { if f.is_null() { return ZauthResult::NullArg; } catch_unwind(|| { let bytes = unsafe { slice::from_raw_parts(f, n) }; - let path = try_unwrap!(str::from_utf8(bytes)); + let path = try_unwrap!(str::from_utf8(bytes)); let mut rdr = BufReader::new(try_unwrap!(File::open(&Path::new(path)))); let mut txt = String::new(); try_unwrap!(rdr.read_to_string(&mut txt)); let acl = try_unwrap!(Acl::from_str(&txt)); unsafe { - *a= Box::into_raw(Box::new(ZauthAcl(acl))); + *a = Box::into_raw(Box::new(ZauthAcl(acl))); } ZauthResult::Ok }) } #[no_mangle] -pub extern fn zauth_acl_delete(a: *mut ZauthAcl) { +pub extern "C" fn zauth_acl_delete(a: *mut ZauthAcl) { catch_unwind(|| { - unsafe { Box::from_raw(a); } + unsafe { + Box::from_raw(a); + } ZauthResult::Ok }); } #[no_mangle] -pub extern fn zauth_token_parse(cs: *const u8, n: size_t, zt: *mut *mut ZauthToken) -> ZauthResult { +pub extern "C" fn zauth_token_parse( + cs: *const u8, + n: size_t, + zt: *mut *mut ZauthToken, +) -> ZauthResult { if cs.is_null() { return ZauthResult::NullArg; } @@ -119,23 +132,26 @@ pub extern fn zauth_token_parse(cs: *const u8, n: size_t, zt: *mut *mut ZauthTok } #[no_mangle] -pub extern fn zauth_token_verify(t: &mut ZauthToken, s: &ZauthKeystore) -> ZauthResult { +pub extern "C" fn zauth_token_verify(t: &mut ZauthToken, s: &ZauthKeystore) -> ZauthResult { let result = catch_unwind(|| { try_unwrap!(t.0.verify(&s.0)); ZauthResult::Ok }); - unsafe { - match result { - ZauthResult::Ok => t.0.verification = TokenVerification::Verified, - _ => t.0.verification = TokenVerification::Invalid - }; + match result { + ZauthResult::Ok => t.0.verification = TokenVerification::Verified, + _ => t.0.verification = TokenVerification::Invalid, }; result } #[no_mangle] -pub extern -fn zauth_token_allowed(t: &ZauthToken, acl: &ZauthAcl, cp: *const u8, n: size_t, out: *mut u8) -> ZauthResult { +pub extern "C" fn zauth_token_allowed( + t: &ZauthToken, + acl: &ZauthAcl, + cp: *const u8, + n: size_t, + out: *mut u8, +) -> ZauthResult { catch_unwind(|| { let b = unsafe { slice::from_raw_parts(cp, n) }; let s = try_unwrap!(str::from_utf8(b)); @@ -149,12 +165,12 @@ fn zauth_token_allowed(t: &ZauthToken, acl: &ZauthAcl, cp: *const u8, n: size_t, } #[no_mangle] -pub extern fn zauth_token_type(t: &ZauthToken) -> ZauthTokenType { +pub extern "C" fn zauth_token_type(t: &ZauthToken) -> ZauthTokenType { From::from(t.0.token_type) } #[no_mangle] -pub extern fn zauth_token_verification(t: &ZauthToken) -> ZauthTokenVerification { +pub extern "C" fn zauth_token_verification(t: &ZauthToken) -> ZauthTokenVerification { From::from(t.0.verification) } @@ -165,24 +181,32 @@ pub extern fn zauth_token_verification(t: &ZauthToken) -> ZauthTokenVerification //} #[no_mangle] -pub extern fn zauth_token_version(t: &ZauthToken) -> u8 { +pub extern "C" fn zauth_token_version(t: &ZauthToken) -> u8 { t.0.version } #[no_mangle] -pub extern fn zauth_token_lookup(t: &ZauthToken, c: u8) -> Range { +pub extern "C" fn zauth_token_lookup(t: &ZauthToken, c: u8) -> Range { if let Some(k) = char::from_u32(c as u32) { if let Some(s) = t.0.lookup(k) { - return Range { ptr: s.as_ptr(), len: s.len() } + return Range { + ptr: s.as_ptr(), + len: s.len(), + }; } } - Range { ptr: ptr::null(), len: 0 } + Range { + ptr: ptr::null(), + len: 0, + } } #[no_mangle] -pub extern fn zauth_token_delete(t: *mut ZauthToken) { +pub extern "C" fn zauth_token_delete(t: *mut ZauthToken) { catch_unwind(|| { - unsafe { Box::from_raw(t); } + unsafe { + Box::from_raw(t); + } ZauthResult::Ok }); } @@ -190,25 +214,25 @@ pub extern fn zauth_token_delete(t: *mut ZauthToken) { #[repr(C)] #[derive(Clone, Copy, Debug)] pub enum ZauthTokenType { - User = 0, - Bot = 1, - Access = 2, - Provider = 4, - LegalHoldUser = 5, + User = 0, + Bot = 1, + Access = 2, + Provider = 4, + LegalHoldUser = 5, LegalHoldAccess = 6, - Unknown = 3 + Unknown = 3, } impl From for ZauthTokenType { fn from(t: TokenType) -> ZauthTokenType { match t { - TokenType::User => ZauthTokenType::User, - TokenType::Access => ZauthTokenType::Access, - TokenType::Bot => ZauthTokenType::Bot, - TokenType::Provider => ZauthTokenType::Provider, - TokenType::LegalHoldUser => ZauthTokenType::LegalHoldUser, - TokenType::LegalHoldAccess => ZauthTokenType::LegalHoldAccess, - TokenType::Unknown => ZauthTokenType::Unknown + TokenType::User => ZauthTokenType::User, + TokenType::Access => ZauthTokenType::Access, + TokenType::Bot => ZauthTokenType::Bot, + TokenType::Provider => ZauthTokenType::Provider, + TokenType::LegalHoldUser => ZauthTokenType::LegalHoldUser, + TokenType::LegalHoldAccess => ZauthTokenType::LegalHoldAccess, + TokenType::Unknown => ZauthTokenType::Unknown, } } } @@ -216,32 +240,48 @@ impl From for ZauthTokenType { #[repr(C)] #[derive(Clone, Copy, Debug)] pub enum ZauthResult { - Ok = 0, - Base64Error = 1, - Expired = 2, - InvalidAttr = 3, - IoError = 4, - MissingAttr = 5, - NullArg = 6, - ParseError = 7, + Ok = 0, + Base64Error = 1, + Expired = 2, + InvalidAttr = 3, + IoError = 4, + MissingAttr = 5, + NullArg = 6, + ParseError = 7, SignatureMismatch = 8, - UnknownKey = 9, - Utf8Error = 10, - AclError = 11, - Panic = 99 + UnknownKey = 9, + Utf8Error = 10, + AclError = 11, + JsonError = 12, + JwtError = 13, + JwkError = 14, + Panic = 99, } impl From for ZauthResult { fn from(e: zauth::Error) -> ZauthResult { match e { - Error::Base64 => ZauthResult::Base64Error, - Error::Expired => ZauthResult::Expired, - Error::Invalid(_) => ZauthResult::InvalidAttr, - Error::Io(_) => ZauthResult::IoError, - Error::Missing(_) => ZauthResult::MissingAttr, - Error::Parse => ZauthResult::ParseError, + Error::Base64 => ZauthResult::Base64Error, + Error::Expired => ZauthResult::Expired, + Error::Invalid(_) => ZauthResult::InvalidAttr, + Error::Io(_) => ZauthResult::IoError, + Error::Missing(_) => ZauthResult::MissingAttr, + Error::Parse => ZauthResult::ParseError, Error::SignatureMismatch => ZauthResult::SignatureMismatch, - Error::UnknownKey(_) => ZauthResult::UnknownKey, + Error::UnknownKey(_) => ZauthResult::UnknownKey, + } + } +} + +impl From for ZauthResult { + fn from(e: zauth::OauthError) -> ZauthResult { + match e { + OauthError::JsonError(_) => Self::JsonError, + OauthError::InvalidJwtNoSubject + | OauthError::InvalidScope + | OauthError::JwtSimpleError(_) => Self::JwtError, + OauthError::Base64DecodeError(_) => Self::Base64Error, + OauthError::InvalidJwk => Self::JwkError, } } } @@ -265,10 +305,12 @@ impl From for ZauthResult { } fn catch_unwind(f: F) -> ZauthResult - where F: FnOnce() -> ZauthResult + UnwindSafe { +where + F: FnOnce() -> ZauthResult + UnwindSafe, +{ match panic::catch_unwind(f) { - Ok(x) => x, - Err(_) => ZauthResult::Panic + Ok(x) => x, + Err(_) => ZauthResult::Panic, } } @@ -284,14 +326,22 @@ impl From for ZauthTokenVerification { fn from(t: TokenVerification) -> ZauthTokenVerification { match t { TokenVerification::Verified => ZauthTokenVerification::Verified, - TokenVerification::Invalid => ZauthTokenVerification::Invalid, - TokenVerification::Pending => ZauthTokenVerification::Pending, + TokenVerification::Invalid => ZauthTokenVerification::Invalid, + TokenVerification::Pending => ZauthTokenVerification::Pending, } } } #[no_mangle] -pub extern fn verify_oauth_token(jwk: *const u8, jwk_len: size_t, token: *const u8, token_len: size_t, scope: *const u8, scope_len: size_t, s: *mut *mut libc::c_char) -> ZauthResult { +pub extern "C" fn verify_oauth_token( + jwk: *const u8, + jwk_len: size_t, + token: *const u8, + token_len: size_t, + scope: *const u8, + scope_len: size_t, + s: *mut *mut libc::c_char, +) -> ZauthResult { if jwk.is_null() { return ZauthResult::NullArg; } @@ -300,18 +350,18 @@ pub extern fn verify_oauth_token(jwk: *const u8, jwk_len: size_t, token: *const } if scope.is_null() { return ZauthResult::NullArg; - } + } catch_unwind(|| { let bytes = unsafe { slice::from_raw_parts(jwk, jwk_len) }; let jwk = try_unwrap!(str::from_utf8(bytes)); let bytes = unsafe { slice::from_raw_parts(token, token_len) }; let token = try_unwrap!(str::from_utf8(bytes)); let bytes = unsafe { slice::from_raw_parts(scope, scope_len) }; - let scope = try_unwrap!(str::from_utf8(bytes)); - let subject = try_unwrap!(verify_oauth_token(jwk, token, scope)); + let scope = try_unwrap!(str::from_utf8(bytes)); + let subject = try_unwrap!(zauth::verify_oauth_token(jwk, token, scope)); unsafe { - *s= Box::into_raw(Box::new(CString::new(subject).unwrap().into_raw())); - } + *s = CString::new(subject).unwrap().into_raw(); + } ZauthResult::Ok }) } diff --git a/libs/libzauth/libzauth/src/lib.rs b/libs/libzauth/libzauth/src/lib.rs index 31b005a540..195d06138b 100644 --- a/libs/libzauth/libzauth/src/lib.rs +++ b/libs/libzauth/libzauth/src/lib.rs @@ -36,3 +36,4 @@ mod matcher; pub use acl::Acl; pub use error::Error; pub use zauth::{Keystore, Token, TokenType, TokenVerification}; +pub use oauth::{verify_oauth_token, OauthError}; diff --git a/nix/pkgs/zauth/default.nix b/nix/pkgs/zauth/default.nix index ef1248b73c..5049370ad6 100644 --- a/nix/pkgs/zauth/default.nix +++ b/nix/pkgs/zauth/default.nix @@ -16,7 +16,7 @@ rustPlatform.buildRustPackage rec { src = nix-gitignore.gitignoreSourcePure [ ../../../.gitignore ] ../../../libs/libzauth; sourceRoot = "libzauth/libzauth-c"; - cargoSha256 = "sha256-od+O5dhAVC1KhDUz8U2fhjyqjXkqHjeEEhvVE0N9orI="; + cargoSha256 = "sha256-WVtCbmKHsBsNAeSHPxQyAmWVBpQFGthCKC2hJxmcMYI="; patchLibs = lib.optionalString stdenv.isDarwin '' install_name_tool -id $out/lib/libzauth.dylib $out/lib/libzauth.dylib diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index d2c8068364..7c89f2aa5b 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -23,6 +23,7 @@ static char * merge_loc_conf (ngx_conf_t *, void *, void *); static char * merge_srv_conf (ngx_conf_t *, void *, void *); static char * load_keystore (ngx_conf_t *, ngx_command_t *, void *); static char * load_acl (ngx_conf_t *, ngx_command_t *, void *); +static char * load_oauth_key (ngx_conf_t *, ngx_command_t *, void *); static void delete_srv_conf (void *); // Module setup @@ -94,6 +95,14 @@ static ngx_command_t zauth_commands [] = { , NULL } + , { ngx_string ("oauth_key") + , NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1 + , load_oauth_key + , NGX_HTTP_SRV_CONF_OFFSET + , 0 + , NULL + } + , ngx_null_command }; @@ -229,6 +238,15 @@ static char * load_acl (ngx_conf_t * conf, ngx_command_t * cmd, void * data) { return NGX_CONF_OK; } +static char * load_oauth_key (ngx_conf_t * conf, ngx_command_t * cmd, void * data) { + ZauthServerConf * sc = data; + if (sc == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + // Module setup ///////////////////////////////////////////////////////////// static ngx_int_t zauth_init (ngx_conf_t * conf) { From 93083b8854304f6aecf3e24ca3b437cdf520a464 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Wed, 15 Feb 2023 14:20:20 +0000 Subject: [PATCH 03/32] rust compiles --- libs/libzauth/libzauth-c/src/lib.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/libs/libzauth/libzauth-c/src/lib.rs b/libs/libzauth/libzauth-c/src/lib.rs index 5088a3a386..6ea29cb571 100644 --- a/libs/libzauth/libzauth-c/src/lib.rs +++ b/libs/libzauth/libzauth-c/src/lib.rs @@ -101,6 +101,24 @@ pub extern "C" fn zauth_acl_open(f: *const u8, n: size_t, a: *mut *mut ZauthAcl) }) } +#[no_mangle] +pub extern "C" fn zauth_oauth_key_open(f: *const u8, n: size_t, k: *mut *mut libc::c_char) -> ZauthResult { + if f.is_null() { + return ZauthResult::NullArg; + } + catch_unwind(|| { + let bytes = unsafe { slice::from_raw_parts(f, n) }; + let path = try_unwrap!(str::from_utf8(bytes)); + let mut rdr = BufReader::new(try_unwrap!(File::open(&Path::new(path)))); + let mut txt = String::new(); + try_unwrap!(rdr.read_to_string(&mut txt)); + unsafe { + *k = CString::new(&txt).unwrap().into_raw(); + } + ZauthResult::Ok + }) +} + #[no_mangle] pub extern "C" fn zauth_acl_delete(a: *mut ZauthAcl) { catch_unwind(|| { From 4e0f572dce66ab043e2d11c5da2e65a6939a948d Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Thu, 16 Feb 2023 08:18:17 +0000 Subject: [PATCH 04/32] wip --- charts/nginz/templates/conf/_nginx.conf.tpl | 4 +- charts/nginz/values.yaml | 6 +- libs/libzauth/libzauth-c/src/lib.rs | 2 +- libs/libzauth/libzauth-c/src/zauth.h | 3 + libs/wire-api/src/Wire/API/OAuth.hs | 1 + .../common_response_with_zauth_oauth.conf | 2 - .../integration-test/conf/nginz/nginx.conf | 9 +- .../nginx-zauth-module/zauth_module.c | 95 +++++++------------ 8 files changed, 49 insertions(+), 73 deletions(-) delete mode 100644 services/nginz/integration-test/conf/nginz/common_response_with_zauth_oauth.conf diff --git a/charts/nginz/templates/conf/_nginx.conf.tpl b/charts/nginz/templates/conf/_nginx.conf.tpl index b60a9a7f35..442ddd1200 100644 --- a/charts/nginz/templates/conf/_nginx.conf.tpl +++ b/charts/nginz/templates/conf/_nginx.conf.tpl @@ -259,8 +259,8 @@ http { {{- end }} {{- end }} - {{- if ($location.enable_oauth) }} - oauth on; + {{- if ($location.oauth_scope) }} + # todo(leif): set scope {{- end }} {{- if hasKey $location "specific_user_rate_limit" }} diff --git a/charts/nginz/values.yaml b/charts/nginz/values.yaml index 5992b13ab5..6aeadd6e42 100644 --- a/charts/nginz/values.yaml +++ b/charts/nginz/values.yaml @@ -164,7 +164,7 @@ nginx_conf: envs: - staging - path: /self$ # Matches exactly /self - enable_oauth: true + oauth_scope: self envs: - all - path: /self/name @@ -429,7 +429,7 @@ nginx_conf: envs: - all doc: true - enable_oauth: true + oauth_scope: conversations - path: /legalhold/conversations/(.*) envs: - all @@ -497,7 +497,7 @@ nginx_conf: - path: /feature-configs(.*) envs: - all - enable_oauth: true + oauth_scope: feature-configs - path: /mls/welcome envs: - all diff --git a/libs/libzauth/libzauth-c/src/lib.rs b/libs/libzauth/libzauth-c/src/lib.rs index 6ea29cb571..b50ef631de 100644 --- a/libs/libzauth/libzauth-c/src/lib.rs +++ b/libs/libzauth/libzauth-c/src/lib.rs @@ -113,7 +113,7 @@ pub extern "C" fn zauth_oauth_key_open(f: *const u8, n: size_t, k: *mut *mut lib let mut txt = String::new(); try_unwrap!(rdr.read_to_string(&mut txt)); unsafe { - *k = CString::new(&txt).unwrap().into_raw(); + *k = CString::new(txt).unwrap().into_raw(); } ZauthResult::Ok }) diff --git a/libs/libzauth/libzauth-c/src/zauth.h b/libs/libzauth/libzauth-c/src/zauth.h index 32cbcb38f5..e949971ba0 100644 --- a/libs/libzauth/libzauth-c/src/zauth.h +++ b/libs/libzauth/libzauth-c/src/zauth.h @@ -55,6 +55,9 @@ void zauth_keystore_delete(ZauthKeystore * store); ZauthResult zauth_acl_open(uint8_t const * fname, size_t len, ZauthAcl **); void zauth_acl_delete(ZauthAcl * store); +ZauthResult zauth_oauth_key_open(uint8_t const * fname, size_t len, char **); +// todo(leif): do we need to dereference the key? + ZauthResult zauth_token_parse(uint8_t const * str, size_t len, ZauthToken **); ZauthResult zauth_token_verify(ZauthToken const *, ZauthKeystore const *); ZauthTokenType zauth_token_type(ZauthToken const *); diff --git a/libs/wire-api/src/Wire/API/OAuth.hs b/libs/wire-api/src/Wire/API/OAuth.hs index bfb65d66bb..2c1fc220fe 100644 --- a/libs/wire-api/src/Wire/API/OAuth.hs +++ b/libs/wire-api/src/Wire/API/OAuth.hs @@ -179,6 +179,7 @@ instance ToSchema OAuthResponseType where -- in location settings for the endpoint(s) in question -- - Update `charts/nginz/values.yaml` and add `enable_oauth: true` to the endpoint in question -- - Consider writing an integration test +-- todo(leif): update these docs data OAuthScope = ReadFeatureConfigs | ReadSelf diff --git a/services/nginz/integration-test/conf/nginz/common_response_with_zauth_oauth.conf b/services/nginz/integration-test/conf/nginz/common_response_with_zauth_oauth.conf deleted file mode 100644 index aea96ff11a..0000000000 --- a/services/nginz/integration-test/conf/nginz/common_response_with_zauth_oauth.conf +++ /dev/null @@ -1,2 +0,0 @@ - oauth on; - include common_response.conf; diff --git a/services/nginz/integration-test/conf/nginz/nginx.conf b/services/nginz/integration-test/conf/nginz/nginx.conf index 51d1c80a10..b00da0c723 100644 --- a/services/nginz/integration-test/conf/nginz/nginx.conf +++ b/services/nginz/integration-test/conf/nginz/nginx.conf @@ -213,7 +213,10 @@ http { ## brig authenticated endpoints location ~* ^(/v[0-9]+)?/self$ { - include common_response_with_zauth_oauth.conf; + include common_response_with_zauth.conf; + if ($request_method = GET ) { + oauth_scope read:self; + } proxy_pass http://brig; } @@ -317,7 +320,7 @@ http { } location ~* ^(/v[0-9]+)?/conversations.* { - include common_response_with_zauth_oauth.conf; + include common_response_with_zauth.conf; proxy_pass http://galley; } @@ -377,7 +380,7 @@ http { } location ~* ^(/v[0-9]+)?/feature-configs(.*) { - include common_response_with_zauth_oauth.conf; + include common_response_with_zauth.conf; proxy_pass http://galley; } diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index 7c89f2aa5b..42b13cb946 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -9,11 +9,12 @@ typedef struct { ZauthKeystore * keystore; ZauthAcl * acl; + char * oauth_key; } ZauthServerConf; typedef struct { ngx_flag_t zauth; // 1=on, 0=off - ngx_flag_t oauth; // 1=on, 0=off + ngx_str_t oauth_scope; } ZauthLocationConf; // Configuration setup @@ -48,7 +49,6 @@ static void zauth_empty_val (ngx_http_variable_value_t *); // Utility functions static ngx_int_t zauth_handle_zauth_request (ngx_http_request_t *, const ZauthServerConf *); static ngx_int_t empty_authorization_header_in_headers_in(ngx_http_request_t *); -static ngx_int_t set_custom_header_in_headers_in(ngx_http_request_t *, ngx_str_t *, ngx_str_t *); static bool zauth_is_authorized_and_allowed(ngx_http_request_t *); static ngx_http_module_t zauth_module_ctx = { @@ -70,14 +70,14 @@ static ngx_command_t zauth_commands [] = { , offsetof (ZauthLocationConf, zauth) , NULL } - - , { ngx_string ("oauth") + + , { ngx_string ("oauth_scope") , NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1 - , ngx_conf_set_flag_slot + , ngx_conf_set_str_slot , NGX_HTTP_LOC_CONF_OFFSET - , offsetof (ZauthLocationConf, oauth) + , offsetof (ZauthLocationConf, oauth_scope) , NULL - } + } , { ngx_string ("zauth_keystore") , NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1 @@ -159,6 +159,10 @@ static char * merge_srv_conf (ngx_conf_t * c, void * pc, void * cc) { child->acl = parent->acl; } + if (child->oauth_key == NULL) { + child->oauth_key = parent->oauth_key; + } + if (child->keystore == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, c, 0, "missing 'zauth_keystore'"); return NGX_CONF_ERROR; @@ -169,6 +173,11 @@ static char * merge_srv_conf (ngx_conf_t * c, void * pc, void * cc) { return NGX_CONF_ERROR; } + if (child->oauth_key == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, c, 0, "missing 'oauth_key'"); + return NGX_CONF_ERROR; + } + return NGX_CONF_OK; } @@ -180,6 +189,7 @@ static void delete_srv_conf (void * data) { if (c->acl != NULL) { zauth_acl_delete(c->acl); } + // todo(leif): free oauth_key? } static void * create_loc_conf (ngx_conf_t * conf) { @@ -191,7 +201,6 @@ static void * create_loc_conf (ngx_conf_t * conf) { } lc->zauth = NGX_CONF_UNSET; - lc->oauth = NGX_CONF_UNSET; return lc; } @@ -200,7 +209,7 @@ static char * merge_loc_conf (ngx_conf_t * _, void * pc, void * cc) { ZauthLocationConf * parent = pc; ZauthLocationConf * child = cc; ngx_conf_merge_off_value(child->zauth, parent->zauth, 1); - ngx_conf_merge_off_value(child->oauth, parent->oauth, 0); + ngx_conf_merge_str_value(child->oauth_scope, parent->oauth_scope, NULL); return NGX_CONF_OK; } @@ -244,6 +253,14 @@ static char * load_oauth_key (ngx_conf_t * conf, ngx_command_t * cmd, void * dat return NGX_CONF_ERROR; } + ngx_str_t * const fname = conf->args->elts; + ZauthResult e = zauth_oauth_key_open(fname[1].data, fname[1].len, &sc->oauth_key); + + if (e != ZAUTH_OK || sc->oauth_key == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, conf, 0, "failed to load oauth key [%d]", e); + return NGX_CONF_ERROR; + } + return NGX_CONF_OK; } @@ -294,34 +311,14 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { return NGX_DECLINED; } + ngx_log_error(NGX_LOG_ERR, conf->log, 0, "scope: %d", lc->oauth_scope.data); + + // let's try to handle zauth ngx_int_t status = zauth_handle_zauth_request(r, sc); - // if parsing the token fails, - // and oauth is enabled, - // we try to set the Z-OAuth header, - // and empty the Authorization header - if (status != NGX_OK && status != NGX_HTTP_FORBIDDEN && lc->oauth == 1) { - if (r->headers_in.authorization == NULL) { - return NGX_ERROR; - } - ngx_str_t hdr = r->headers_in.authorization->value; - if (strncmp((char const *) hdr.data, "Bearer ", 7) != 0) { - return NGX_ERROR; - } - ngx_str_t z_oauth_hdr_name = ngx_string("Z-OAuth"); - ngx_int_t res = set_custom_header_in_headers_in(r, &z_oauth_hdr_name, &hdr); - if (res != NGX_OK) { - return NGX_ERROR; - } - res = empty_authorization_header_in_headers_in(r); - if (res != NGX_OK) { - return NGX_ERROR; - } - return NGX_DECLINED; - } // if zauth succeeds, we empty the Authorization header - else if (status == NGX_OK) { + if (status == NGX_OK) { return empty_authorization_header_in_headers_in(r); } // in all other cases (which should only be errors) we return the status @@ -341,17 +338,6 @@ ngx_int_t empty_authorization_header_in_headers_in(ngx_http_request_t *r) { return NGX_OK; } -ngx_int_t set_custom_header_in_headers_in(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value) { - ngx_table_elt_t *h = ngx_list_push(&r->headers_in.headers); - if (h == NULL) { - return NGX_ERROR; - } - h->key = *key; - h->value = *value; - h->hash = 1; - return NGX_OK; -} - static ngx_int_t zauth_handle_zauth_request (ngx_http_request_t * r, const ZauthServerConf * sc) { ZauthToken const * tkn = ngx_http_get_module_ctx(r, zauth_module); @@ -429,21 +415,7 @@ static ngx_int_t zauth_parse_request (ngx_http_request_t * r) { } if (res != ZAUTH_OK) { - ZauthLocationConf const *lc = ngx_http_get_module_loc_conf(r, zauth_module); - - bool is_zauth_parse_error () { - return - lc == NULL || - lc->oauth == 0 || - r->headers_in.authorization == NULL || - strncmp((char const *)r->headers_in.authorization->value.data, "Bearer ", 7) == 0; - } - - // only if parsing the request failed we produce a log entry (otherwise the request will be handled by wire-server as an oauth request) - if (is_zauth_parse_error()) - { - ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, "failed to parse token [%d]", res); - } + ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, "failed to parse token [%d]", res); } return NGX_OK; @@ -603,9 +575,8 @@ static ngx_int_t zauth_token_var (ngx_http_request_t * r, ngx_http_variable_valu // in this function the user, client, provider, and bot ID is retrieved from the token // and assigned to variables that are used in the nginx config to set the headers. - // therefore we want to make sure that the token is - // authorized (has a valid signature) - // and access is allowed (endpoint is either not denied or allowed according to the access control list configuration) + // therefore we want to make sure that the token is authorized (has a valid signature) + // and access is allowed (endpoint is either allowed or not denied according to the access control list configuration) // before we set these variables. if (!zauth_is_authorized_and_allowed(r)) { zauth_empty_val(v); From ba70964698b2d7e59c530250f6da87164d4ca075 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Thu, 16 Feb 2023 08:44:47 +0000 Subject: [PATCH 05/32] wip --- services/nginz/third_party/nginx-zauth-module/zauth_module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index 42b13cb946..c0bc10e766 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -311,7 +311,7 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { return NGX_DECLINED; } - ngx_log_error(NGX_LOG_ERR, conf->log, 0, "scope: %d", lc->oauth_scope.data); + // ngx_log_error(NGX_LOG_ERR, conf->log, 0, "scope: %d", lc->oauth_scope.data); // let's try to handle zauth From 4c292bc92b6044fcb8c956d685452baa487a3995 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Fri, 17 Feb 2023 13:26:04 +0000 Subject: [PATCH 06/32] wip --- hack/bin/oauth_test.sh | 2 +- libs/libzauth/libzauth-c/Makefile | 4 +- libs/libzauth/libzauth-c/src/lib.rs | 54 ++++++++++--------- libs/libzauth/libzauth-c/src/zauth.h | 5 +- libs/libzauth/libzauth/src/oauth.rs | 34 ++++++++++-- .../integration-test/conf/nginz/nginx.conf | 4 +- .../nginx-zauth-module/zauth_module.c | 25 +++++++-- 7 files changed, 87 insertions(+), 41 deletions(-) diff --git a/hack/bin/oauth_test.sh b/hack/bin/oauth_test.sh index fa1d395869..56ce721149 100755 --- a/hack/bin/oauth_test.sh +++ b/hack/bin/oauth_test.sh @@ -79,4 +79,4 @@ echo "access token : $ACCESS_TOKEN" echo "" echo "making a request to /self..." -curl -s -H 'Z-OAUTH: Bearer '"$ACCESS_TOKEN" -H "Content-Type: application/json" localhost:8082/self | jq . +curl -s -i -H 'Authorization: Bearer '"$ACCESS_TOKEN" -H "Content-Type: application/json" localhost:8080/self diff --git a/libs/libzauth/libzauth-c/Makefile b/libs/libzauth/libzauth-c/Makefile index 37e7c72853..9fa10a3935 100644 --- a/libs/libzauth/libzauth-c/Makefile +++ b/libs/libzauth/libzauth-c/Makefile @@ -27,7 +27,7 @@ build: cargo build build-release: - cargo build --release + cargo build install: build-release mkdir -p $(PREFIX_INSTALL)/include @@ -36,7 +36,7 @@ install: build-release sed -e "s~<>~$(VERSION)~" \ -e "s~<>~$(PREFIX_INSTALL)~" \ src/libzauth.pc > $(PREFIX_INSTALL)/lib/pkgconfig/libzauth.pc - cp target/release/libzauth.$(LIB_TYPE) $(PREFIX_INSTALL)/lib/ + cp target/debug/libzauth.$(LIB_TYPE) $(PREFIX_INSTALL)/lib/ uninstall: rm -f $(PREFIX_INSTALL)/include/zauth.h diff --git a/libs/libzauth/libzauth-c/src/lib.rs b/libs/libzauth/libzauth-c/src/lib.rs index b50ef631de..9a809f3cfc 100644 --- a/libs/libzauth/libzauth-c/src/lib.rs +++ b/libs/libzauth/libzauth-c/src/lib.rs @@ -51,6 +51,7 @@ pub struct Range { pub struct ZauthAcl(zauth::Acl); pub struct ZauthKeystore(zauth::Keystore); pub struct ZauthToken(zauth::Token<'static>); +pub struct OAuthJwk(String); #[no_mangle] pub extern "C" fn zauth_keystore_open( @@ -102,7 +103,7 @@ pub extern "C" fn zauth_acl_open(f: *const u8, n: size_t, a: *mut *mut ZauthAcl) } #[no_mangle] -pub extern "C" fn zauth_oauth_key_open(f: *const u8, n: size_t, k: *mut *mut libc::c_char) -> ZauthResult { +pub extern "C" fn zauth_oauth_key_open(f: *const u8, n: size_t, k: *mut *mut OAuthJwk) -> ZauthResult { if f.is_null() { return ZauthResult::NullArg; } @@ -112,8 +113,9 @@ pub extern "C" fn zauth_oauth_key_open(f: *const u8, n: size_t, k: *mut *mut lib let mut rdr = BufReader::new(try_unwrap!(File::open(&Path::new(path)))); let mut txt = String::new(); try_unwrap!(rdr.read_to_string(&mut txt)); + eprintln!("jwk loaded key: {:?}", txt); // todo(leif): remove unsafe { - *k = CString::new(txt).unwrap().into_raw(); + *k = Box::into_raw(Box::new(OAuthJwk(txt))); } ZauthResult::Ok }) @@ -350,36 +352,38 @@ impl From for ZauthTokenVerification { } } +#[no_mangle] +pub extern "C" fn print_jwk(jwk: &OAuthJwk) { + eprintln!("print jwk: {:?}", jwk.0); +} + #[no_mangle] pub extern "C" fn verify_oauth_token( - jwk: *const u8, - jwk_len: size_t, + jwk: &OAuthJwk, token: *const u8, token_len: size_t, scope: *const u8, - scope_len: size_t, - s: *mut *mut libc::c_char, -) -> ZauthResult { - if jwk.is_null() { - return ZauthResult::NullArg; - } + scope_len: size_t +) -> *mut libc::c_char { + eprintln!("verify_oauth_token"); if token.is_null() { - return ZauthResult::NullArg; + eprintln!("token is null"); + // return ZauthResult::NullArg; } + eprintln!("token len: {:?}", token_len); if scope.is_null() { - return ZauthResult::NullArg; + eprintln!("scope is null"); + // return ZauthResult::NullArg; } - catch_unwind(|| { - let bytes = unsafe { slice::from_raw_parts(jwk, jwk_len) }; - let jwk = try_unwrap!(str::from_utf8(bytes)); - let bytes = unsafe { slice::from_raw_parts(token, token_len) }; - let token = try_unwrap!(str::from_utf8(bytes)); - let bytes = unsafe { slice::from_raw_parts(scope, scope_len) }; - let scope = try_unwrap!(str::from_utf8(bytes)); - let subject = try_unwrap!(zauth::verify_oauth_token(jwk, token, scope)); - unsafe { - *s = CString::new(subject).unwrap().into_raw(); - } - ZauthResult::Ok - }) + eprintln!("scope len: {:?}", scope_len); + let bytes = unsafe { slice::from_raw_parts(token, token_len) }; + let token = str::from_utf8(bytes).unwrap(); + eprintln!("token: {:?}", token); + let bytes = unsafe { slice::from_raw_parts(scope, scope_len) }; + let scope = str::from_utf8(bytes).unwrap(); + eprintln!("scope: {:?}", scope); + let subject = zauth::verify_oauth_token(&jwk.0, token, scope).unwrap(); + eprintln!("subject: {:?}", subject); + let c_str_song = CString::new(subject).unwrap(); + c_str_song.into_raw() } diff --git a/libs/libzauth/libzauth-c/src/zauth.h b/libs/libzauth/libzauth-c/src/zauth.h index e949971ba0..de6933d6ac 100644 --- a/libs/libzauth/libzauth-c/src/zauth.h +++ b/libs/libzauth/libzauth-c/src/zauth.h @@ -48,6 +48,7 @@ typedef enum { typedef struct ZauthAcl ZauthAcl; typedef struct ZauthKeystore ZauthKeystore; typedef struct ZauthToken ZauthToken; +typedef struct OAuthJwk OAuthJwk; ZauthResult zauth_keystore_open(uint8_t const * fname, size_t len, ZauthKeystore **); void zauth_keystore_delete(ZauthKeystore * store); @@ -55,7 +56,7 @@ void zauth_keystore_delete(ZauthKeystore * store); ZauthResult zauth_acl_open(uint8_t const * fname, size_t len, ZauthAcl **); void zauth_acl_delete(ZauthAcl * store); -ZauthResult zauth_oauth_key_open(uint8_t const * fname, size_t len, char **); +ZauthResult zauth_oauth_key_open(uint8_t const * fname, size_t len, OAuthJwk **); // todo(leif): do we need to dereference the key? ZauthResult zauth_token_parse(uint8_t const * str, size_t len, ZauthToken **); @@ -67,6 +68,8 @@ uint8_t zauth_token_version(ZauthToken const *); Range zauth_token_lookup(ZauthToken const *, uint8_t); ZauthResult zauth_token_allowed(ZauthToken const *, ZauthAcl const *, uint8_t const * path, size_t len, uint8_t * result); void zauth_token_delete(ZauthToken *); +char * verify_oauth_token(OAuthJwk const *, uint8_t const * t, size_t t_len, uint8_t const * s, size_t s_len); +void print_jwk(OAuthJwk const *); #ifdef __cplusplus } diff --git a/libs/libzauth/libzauth/src/oauth.rs b/libs/libzauth/libzauth/src/oauth.rs index ff55c7e0d8..f9b20d012a 100644 --- a/libs/libzauth/libzauth/src/oauth.rs +++ b/libs/libzauth/libzauth/src/oauth.rs @@ -7,15 +7,41 @@ pub struct OAuthToken { } pub fn verify_oauth_token(jwk: &str, token: &str, scope: &str) -> Result { + println!("verify_oauth_token - jwk: {:?}", jwk); + println!("verify_oauth_token - token: {:?}", token); + println!("verify_oauth_token - scope: {:?}", scope); let jwk = serde_json::from_str::(jwk)?; + eprintln!("verify_oauth_token - jwk parsed"); let key = try_from_jwk(&jwk)?; + eprintln!("verify_oauth_token - key: {:?}", key); let options = VerificationOptions { - time_tolerance: Some(Duration::from_secs(1)), ..Default::default() }; - let claims = key.verify_token::(token, Some(options))?; - let subject = claims.subject.ok_or(OauthError::InvalidJwtNoSubject)?; - verify_scope(&claims.custom.scope, scope)?; + eprintln!("verify_oauth_token - options: {:?}", options); + let claims = match key.verify_token::(token, Some(options)) { + Ok(claims) => claims, + Err(e) => { + eprintln!("verify_oauth_token - error: {:?}", e); + panic!("verify_oauth_token - error: {:?}", e); + }, + }; + eprintln!("verify_oauth_token - claims parsed"); + let subject = match claims.subject.ok_or(OauthError::InvalidJwtNoSubject) { + Ok(subject) => subject, + Err(e) => { + eprintln!("verify_oauth_token - error: {:?}", e); + panic!("verify_oauth_token - error: {:?}", e); + }, + }; + eprintln!("verify_oauth_token - subject parsed"); + match verify_scope(&claims.custom.scope, scope) { + Ok(_) => {}, + Err(e) => { + eprintln!("verify_oauth_token - error: {:?}", e); + panic!("verify_oauth_token - error: {:?}", e); + }, + }; + eprintln!("verify_oauth_token - scope verified"); Ok(subject) } diff --git a/services/nginz/integration-test/conf/nginz/nginx.conf b/services/nginz/integration-test/conf/nginz/nginx.conf index b00da0c723..907bd65dc4 100644 --- a/services/nginz/integration-test/conf/nginz/nginx.conf +++ b/services/nginz/integration-test/conf/nginz/nginx.conf @@ -214,9 +214,7 @@ http { location ~* ^(/v[0-9]+)?/self$ { include common_response_with_zauth.conf; - if ($request_method = GET ) { - oauth_scope read:self; - } + oauth_scope read:self; proxy_pass http://brig; } diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index c0bc10e766..0c3a816b13 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -9,7 +9,7 @@ typedef struct { ZauthKeystore * keystore; ZauthAcl * acl; - char * oauth_key; + OAuthJwk * oauth_key; } ZauthServerConf; typedef struct { @@ -311,13 +311,28 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { return NGX_DECLINED; } - // ngx_log_error(NGX_LOG_ERR, conf->log, 0, "scope: %d", lc->oauth_scope.data); - - // let's try to handle zauth ngx_int_t status = zauth_handle_zauth_request(r, sc); - // if zauth succeeds, we empty the Authorization header + // if zauth fails, we try to handle oauth + if (status != NGX_OK && sc->oauth_key != NULL) { + print_jwk(sc->oauth_key); + ngx_str_t *hdr = &r->headers_in.authorization->value; + if (strncmp((char const *) hdr->data, "Bearer ", 7) == 0) { + ngx_str_t scope = lc->oauth_scope; + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "scope data: %s", scope.data); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "hdr data: %s", hdr->data); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "hdr len: %d", hdr->len - 7); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "scope len: %d", scope.len); + + char *uid = verify_oauth_token(sc->oauth_key, &hdr->data[7], hdr->len - 7, scope.data, scope.len); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth user id: %s", uid); + } else { + status = ZAUTH_PARSE_ERROR; + } + } + + // if zauth or oauth succeeds, we empty the Authorization header if (status == NGX_OK) { return empty_authorization_header_in_headers_in(r); } From 47ef9f26594b2df8bcb85a43382f6706d4c70513 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Fri, 17 Feb 2023 13:59:55 +0000 Subject: [PATCH 07/32] works --- .../nginx-zauth-module/zauth_module.c | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index 0c3a816b13..abb4a1e363 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -40,6 +40,7 @@ static void delete_token (void *); // Variable manipulation static ngx_int_t zauth_variables (ngx_conf_t *); static ngx_int_t zauth_token_var (ngx_http_request_t *, ngx_http_variable_value_t *, uintptr_t); +static ngx_int_t zauth_token_var_user (ngx_http_request_t *, ngx_http_variable_value_t *, uintptr_t); static ngx_int_t zauth_token_var_conn (ngx_http_request_t *, ngx_http_variable_value_t *, uintptr_t); static ngx_int_t zauth_token_var_conv (ngx_http_request_t *, ngx_http_variable_value_t *, uintptr_t); static ngx_int_t zauth_token_typeinfo (ngx_http_request_t *, ngx_http_variable_value_t *, uintptr_t); @@ -327,6 +328,19 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { char *uid = verify_oauth_token(sc->oauth_key, &hdr->data[7], hdr->len - 7, scope.data, scope.len); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth user id: %s", uid); + + ngx_pool_cleanup_t * finaliser = ngx_pool_cleanup_add(r->pool, 0); + if (finaliser == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "finaliser is null"); + return NGX_ERROR; + } + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "finaliser not null"); + + // finaliser->handler = delete_token; + finaliser->data = uid; + ngx_http_set_ctx(r, uid, zauth_module); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "user id set to context"); + status = NGX_OK; } else { status = ZAUTH_PARSE_ERROR; } @@ -502,7 +516,7 @@ static ngx_int_t zauth_variables (ngx_conf_t * conf) { z_type_var->get_handler = zauth_token_typeinfo; z_bot_var->get_handler = zauth_token_var; z_bot_var->data = 'b'; - z_user_var->get_handler = zauth_token_var; + z_user_var->get_handler = zauth_token_var_user; z_user_var->data = 'u'; z_client_var->get_handler = zauth_token_var; z_client_var->data = 'i'; @@ -601,6 +615,27 @@ static ngx_int_t zauth_token_var (ngx_http_request_t * r, ngx_http_variable_valu return zauth_set_var(r->pool, v, zauth_token_lookup(t, data)); } +static ngx_int_t zauth_token_var_user (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t _) { + ZauthToken const * t = ngx_http_get_module_ctx(r, zauth_module); + if (t != NULL && zauth_is_authorized_and_allowed(r)) { + return zauth_set_var(r->pool, v, zauth_token_lookup(t, 'u')); + } else { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "tkn is NULL"); + char const * oauth_uid = ngx_http_get_module_ctx(r, zauth_module); + if (oauth_uid != NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_uid (not null): %s", oauth_uid); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_uid (not null) len: %d", strlen(oauth_uid)); + ngx_int_t res = zauth_set_var(r->pool, v, (Range) { (u_char*) oauth_uid, strlen(oauth_uid) }); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "result: %d", res); + return res; + } else { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_uid is NULL"); + zauth_empty_val(v); + return NGX_OK; + } + } +} + static ngx_int_t zauth_token_var_conn (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t _) { ZauthToken const * t = ngx_http_get_module_ctx(r, zauth_module); if (t == NULL) { From 7997155c7cb68f180f4f58602d6c3fdf6a12251a Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Mon, 20 Feb 2023 11:31:00 +0000 Subject: [PATCH 08/32] scope access --- libs/libzauth/libzauth-c/src/lib.rs | 14 +-- libs/libzauth/libzauth-c/src/zauth.h | 2 +- libs/libzauth/libzauth/src/oauth.rs | 103 ++++++++++++------ .../integration-test/conf/nginz/nginx.conf | 2 +- .../nginx-zauth-module/zauth_module.c | 6 +- 5 files changed, 80 insertions(+), 47 deletions(-) diff --git a/libs/libzauth/libzauth-c/src/lib.rs b/libs/libzauth/libzauth-c/src/lib.rs index 9a809f3cfc..c375cd24ad 100644 --- a/libs/libzauth/libzauth-c/src/lib.rs +++ b/libs/libzauth/libzauth-c/src/lib.rs @@ -363,27 +363,25 @@ pub extern "C" fn verify_oauth_token( token: *const u8, token_len: size_t, scope: *const u8, - scope_len: size_t + scope_len: size_t, + method: *const u8, + method_len: size_t, ) -> *mut libc::c_char { - eprintln!("verify_oauth_token"); if token.is_null() { eprintln!("token is null"); // return ZauthResult::NullArg; } - eprintln!("token len: {:?}", token_len); if scope.is_null() { eprintln!("scope is null"); // return ZauthResult::NullArg; } - eprintln!("scope len: {:?}", scope_len); let bytes = unsafe { slice::from_raw_parts(token, token_len) }; let token = str::from_utf8(bytes).unwrap(); - eprintln!("token: {:?}", token); let bytes = unsafe { slice::from_raw_parts(scope, scope_len) }; let scope = str::from_utf8(bytes).unwrap(); - eprintln!("scope: {:?}", scope); - let subject = zauth::verify_oauth_token(&jwk.0, token, scope).unwrap(); - eprintln!("subject: {:?}", subject); + let bytes = unsafe { slice::from_raw_parts(method, method_len) }; + let method = str::from_utf8(bytes).unwrap(); + let subject = zauth::verify_oauth_token(&jwk.0, token, scope, method).unwrap(); let c_str_song = CString::new(subject).unwrap(); c_str_song.into_raw() } diff --git a/libs/libzauth/libzauth-c/src/zauth.h b/libs/libzauth/libzauth-c/src/zauth.h index de6933d6ac..a4c11dac26 100644 --- a/libs/libzauth/libzauth-c/src/zauth.h +++ b/libs/libzauth/libzauth-c/src/zauth.h @@ -68,7 +68,7 @@ uint8_t zauth_token_version(ZauthToken const *); Range zauth_token_lookup(ZauthToken const *, uint8_t); ZauthResult zauth_token_allowed(ZauthToken const *, ZauthAcl const *, uint8_t const * path, size_t len, uint8_t * result); void zauth_token_delete(ZauthToken *); -char * verify_oauth_token(OAuthJwk const *, uint8_t const * t, size_t t_len, uint8_t const * s, size_t s_len); +char * verify_oauth_token(OAuthJwk const *, uint8_t const * t, size_t t_len, uint8_t const * s, size_t s_len, uint8_t const * m, size_t m_len); void print_jwk(OAuthJwk const *); #ifdef __cplusplus diff --git a/libs/libzauth/libzauth/src/oauth.rs b/libs/libzauth/libzauth/src/oauth.rs index f9b20d012a..23fa5952f6 100644 --- a/libs/libzauth/libzauth/src/oauth.rs +++ b/libs/libzauth/libzauth/src/oauth.rs @@ -6,47 +6,48 @@ pub struct OAuthToken { pub scope: String, } -pub fn verify_oauth_token(jwk: &str, token: &str, scope: &str) -> Result { - println!("verify_oauth_token - jwk: {:?}", jwk); - println!("verify_oauth_token - token: {:?}", token); - println!("verify_oauth_token - scope: {:?}", scope); +pub fn verify_oauth_token( + jwk: &str, + token: &str, + required_scope: &str, + method: &str, +) -> Result { let jwk = serde_json::from_str::(jwk)?; - eprintln!("verify_oauth_token - jwk parsed"); let key = try_from_jwk(&jwk)?; - eprintln!("verify_oauth_token - key: {:?}", key); let options = VerificationOptions { + time_tolerance: Some(Duration::from_secs(1)), ..Default::default() }; - eprintln!("verify_oauth_token - options: {:?}", options); - let claims = match key.verify_token::(token, Some(options)) { - Ok(claims) => claims, - Err(e) => { - eprintln!("verify_oauth_token - error: {:?}", e); - panic!("verify_oauth_token - error: {:?}", e); - }, - }; - eprintln!("verify_oauth_token - claims parsed"); - let subject = match claims.subject.ok_or(OauthError::InvalidJwtNoSubject) { - Ok(subject) => subject, - Err(e) => { - eprintln!("verify_oauth_token - error: {:?}", e); - panic!("verify_oauth_token - error: {:?}", e); - }, - }; - eprintln!("verify_oauth_token - subject parsed"); - match verify_scope(&claims.custom.scope, scope) { - Ok(_) => {}, - Err(e) => { - eprintln!("verify_oauth_token - error: {:?}", e); - panic!("verify_oauth_token - error: {:?}", e); - }, - }; - eprintln!("verify_oauth_token - scope verified"); + let claims = key.verify_token::(token, Some(options))?; + let subject = claims.subject.ok_or(OauthError::InvalidJwtNoSubject)?; + verify_scope(&claims.custom.scope, required_scope, method)?; Ok(subject) } -fn verify_scope(scope: &str, required_scope: &str) -> Result<(), OauthError> { - let valid = scope.split_whitespace().any(|s| s == required_scope); +// if method is GET, authorized scopes must contain either read:_, write:_, or admin:_ +// if method is POST, authorized scopes must contain either write:_ or admin:_ +// if method is PUT, authorized scopes must contain either write:_ or admin:_ +// if method is DELETE, authorized scopes must contain admin:_ +fn verify_scope( + authorized_scopes: &str, + required_scope: &str, + method: &str, +) -> Result<(), OauthError> { + let valid_scopes = match method.to_uppercase().as_str() { + "GET" => Ok(vec!["read", "write", "admin"]), + "POST" => Ok(vec!["write", "admin"]), + "PUT" => Ok(vec!["write", "admin"]), + "DELETE" => Ok(vec!["admin"]), + _ => Err(OauthError::InvalidScope), + }? + .iter() + .map(|s| format!("{}:{}", s, required_scope)) + .collect::>(); + + let valid = authorized_scopes + .split_whitespace() + .any(|s| valid_scopes.contains(&s.to_string())); + if !valid { return Err(OauthError::InvalidScope); } @@ -89,6 +90,38 @@ pub enum OauthError { mod tests { use super::*; + #[test] + fn should_verify_scope_get() { + assert!(verify_scope("read:self foo bar", "self", "GET").is_ok()); + assert!(verify_scope("write:self foo bar", "self", "GET").is_ok()); + assert!(verify_scope("admin:self foo bar", "self", "GET").is_ok()); + assert!(verify_scope("foo bar", "self", "GET").is_err()); + } + + #[test] + fn should_verify_scope_post() { + assert!(verify_scope("write:self foo bar", "self", "POST").is_ok()); + assert!(verify_scope("admin:self foo bar", "self", "POST").is_ok()); + assert!(verify_scope("read:self foo bar", "self", "POST").is_err()); + assert!(verify_scope("foo bar", "self", "POST").is_err()); + } + + #[test] + fn should_verify_scope_put() { + assert!(verify_scope("write:self foo bar", "self", "PUT").is_ok()); + assert!(verify_scope("admin:self foo bar", "self", "PUT").is_ok()); + assert!(verify_scope("read:self foo bar", "self", "PUT").is_err()); + assert!(verify_scope("foo bar", "self", "PUT").is_err()); + } + + #[test] + fn should_verify_scope_delete() { + assert!(verify_scope("admin:self foo bar", "self", "DELETE").is_ok()); + assert!(verify_scope("write:self foo bar", "self", "DELETE").is_err()); + assert!(verify_scope("read:self foo bar", "self", "DELETE").is_err()); + assert!(verify_scope("foo bar", "self", "DELETE").is_err()); + } + #[test] fn should_verify_oauth_token() { let uid = "842ddbc8-56ec-408d-9fa8-7a8c37ad22a7"; @@ -96,14 +129,14 @@ mod tests { let jwk = mk_jwk(key.public_key()); let token = Claims::with_custom_claims( OAuthToken { - scope: "foo test bar".to_string(), + scope: "write:foo read:test admin:bar".to_string(), }, Duration::from_secs(3600), ) .with_subject(uid); let jwt = key.sign::(token).unwrap(); let subject = - verify_oauth_token(&serde_json::to_string(&jwk).unwrap(), &jwt, "test").unwrap(); + verify_oauth_token(&serde_json::to_string(&jwk).unwrap(), &jwt, "test", "GET").unwrap(); assert_eq!(&subject, uid); } fn mk_jwk(key: Ed25519PublicKey) -> Jwk { diff --git a/services/nginz/integration-test/conf/nginz/nginx.conf b/services/nginz/integration-test/conf/nginz/nginx.conf index 907bd65dc4..e94d8b6fc6 100644 --- a/services/nginz/integration-test/conf/nginz/nginx.conf +++ b/services/nginz/integration-test/conf/nginz/nginx.conf @@ -214,7 +214,7 @@ http { location ~* ^(/v[0-9]+)?/self$ { include common_response_with_zauth.conf; - oauth_scope read:self; + oauth_scope self; proxy_pass http://brig; } diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index abb4a1e363..d952d18d29 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -325,8 +325,9 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "hdr data: %s", hdr->data); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "hdr len: %d", hdr->len - 7); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "scope len: %d", scope.len); - - char *uid = verify_oauth_token(sc->oauth_key, &hdr->data[7], hdr->len - 7, scope.data, scope.len); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "method: %s", r->method_name.data); + + char *uid = verify_oauth_token(sc->oauth_key, &hdr->data[7], hdr->len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth user id: %s", uid); ngx_pool_cleanup_t * finaliser = ngx_pool_cleanup_add(r->pool, 0); @@ -336,6 +337,7 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { } ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "finaliser not null"); + // todo(leif): set deconstructor? // finaliser->handler = delete_token; finaliser->data = uid; ngx_http_set_ctx(r, uid, zauth_module); From 673003f17c811c976579d14cc97d6a833c47d314 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Mon, 20 Feb 2023 13:50:15 +0000 Subject: [PATCH 09/32] oauth integration tests only using nginz --- services/brig/test/integration/API/OAuth.hs | 105 ++++++++------------ services/brig/test/integration/Main.hs | 2 +- 2 files changed, 40 insertions(+), 67 deletions(-) diff --git a/services/brig/test/integration/API/OAuth.hs b/services/brig/test/integration/API/OAuth.hs index 51f333a044..ee1e9b6ef1 100644 --- a/services/brig/test/integration/API/OAuth.hs +++ b/services/brig/test/integration/API/OAuth.hs @@ -59,12 +59,12 @@ import Wire.API.Conversation.Protocol (ProtocolTag (ProtocolProteusTag)) import qualified Wire.API.Conversation.Role as Role import Wire.API.OAuth import Wire.API.Routes.Bearer (Bearer (Bearer, unBearer)) -import Wire.API.User (SelfProfile, User (userId), userEmail) +import Wire.API.User (User (userId), userEmail) import Wire.API.User.Auth (CookieType (PersistentCookie)) import Wire.Sem.Jwk (readJwk) -tests :: Manager -> C.ClientState -> Brig -> Galley -> Nginz -> Opts -> TestTree -tests m db b g n o = do +tests :: Manager -> C.ClientState -> Brig -> Nginz -> Opts -> TestTree +tests m db b n o = do testGroup "oauth" [ test m "register new oauth client" $ testRegisterNewOAuthClient b, @@ -94,32 +94,22 @@ tests m db b g n o = do ], testGroup "accessing a resource" - [ test m "success (internal)" $ testAccessResourceSuccessInternal b, - test m "success (nginz)" $ testAccessResourceSuccessNginz b n, - test m "insufficient scope" $ testAccessResourceInsufficientScope b, - test m "expired token" $ testAccessResourceExpiredToken o b, - test m "nonsense token" $ testAccessResourceNonsenseToken b, + [ test m "success (nginz)" $ testAccessResourceSuccessNginz b n, + test m "insufficient scope" $ testAccessResourceInsufficientScope b n, + test m "expired token" $ testAccessResourceExpiredToken o b n, + test m "nonsense token" $ testAccessResourceNonsenseToken n, test m "no token" $ testAccessResourceNoToken b, - test m "invalid signature" $ testAccessResourceInvalidSignature o b + test m "invalid signature" $ testAccessResourceInvalidSignature o b n ], testGroup "accessing resources" - [ testGroup - "internal" - [ test m "write:conversation" $ testWriteConversationSuccessInternal b g, - test m "read:feature_configs" $ testReadFeatureConfigsSuccessInternal b g, - test m "write:conversation_code" $ testWriteConversationCodeSuccessInternal b g - ], - testGroup - "nginz" - [ test m "write:conversation" $ testWriteConversationSuccessNginz b n, - test m "read:feature_configs" $ testReadFeatureConfigsSuccessNginz b n, - test m "write:conversation_code" $ testWriteConversationCodeSuccessNginz b n - ] + [ test m "write:conversation" $ testWriteConversationSuccessNginz b n, + test m "read:feature_configs" $ testReadFeatureConfigsSuccessNginz b n, + test m "write:conversation_code" $ testWriteConversationCodeSuccessNginz b n ], testGroup "refresh tokens" $ [ test m "max active tokens" $ testRefreshTokenMaxActiveTokens o db b, - test m "refresh access token - success" $ testRefreshTokenRetrieveAccessToken o b, + test m "refresh access token - success" $ testRefreshTokenRetrieveAccessToken o b n, test m "wrong signature - fail" $ testRefreshTokenWrongSignature o b, test m "no token id - fail" $ testRefreshTokenNoTokenId o b, test m "non-existing id - fail" $ testRefreshTokenNonExistingId o b, @@ -341,20 +331,6 @@ assertAccessDenied = do const 403 === statusCode const (Just "forbidden") === fmap Error.label . responseJsonMaybe -testAccessResourceSuccessInternal :: Brig -> Http () -testAccessResourceSuccessInternal brig = do - uid <- userId <$> createUser "alice" brig - let redirectUrl = mkUrl "https://example.com" - let scopes = OAuthScopes $ Set.fromList [ReadSelf] - (cid, secret, code) <- generateOAuthClientAndAuthCode brig uid scopes redirectUrl - let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid secret code redirectUrl - accessToken <- createOAuthAccessToken brig accessTokenRequest - -- should succeed with Z-User header - response :: SelfProfile <- responseJsonError =<< get (brig . paths ["self"] . zUser uid) Nginz -> Http () testAccessResourceSuccessNginz brig nginz = do -- with ZAuth header @@ -371,21 +347,21 @@ testAccessResourceSuccessNginz brig nginz = do oauthToken <- oatAccessToken <$> createOAuthAccessToken brig accessTokenRequest get (nginz . paths ["self"] . authHeader oauthToken) !!! const 200 === statusCode -testAccessResourceInsufficientScope :: Brig -> Http () -testAccessResourceInsufficientScope brig = do +testAccessResourceInsufficientScope :: Brig -> Nginz -> Http () +testAccessResourceInsufficientScope brig nginz = do uid <- userId <$> createUser "alice" brig let redirectUrl = mkUrl "https://example.com" let scopes = OAuthScopes $ Set.fromList [WriteConversation] (cid, secret, code) <- generateOAuthClientAndAuthCode brig uid scopes redirectUrl let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid secret code redirectUrl accessToken <- createOAuthAccessToken brig accessTokenRequest - get (brig . paths ["self"] . zOAuthHeader (oatAccessToken accessToken)) !!! do + get (nginz . paths ["self"] . authHeader (oatAccessToken accessToken)) !!! do const 403 === statusCode const "Access denied" === statusMessage const (Just "Insufficient scope") === responseBody -testAccessResourceExpiredToken :: Opt.Opts -> Brig -> Http () -testAccessResourceExpiredToken opts brig = +testAccessResourceExpiredToken :: Opt.Opts -> Brig -> Nginz -> Http () +testAccessResourceExpiredToken opts brig nginz = withSettingsOverrides (opts & Opt.optionSettings . Opt.oauthAccessTokenExpirationTimeSecsInternal ?~ 1) $ do uid <- userId <$> createUser "alice" brig let redirectUrl = mkUrl "https://example.com" @@ -394,14 +370,14 @@ testAccessResourceExpiredToken opts brig = let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid secret code redirectUrl accessToken <- createOAuthAccessToken brig accessTokenRequest liftIO $ threadDelay (1 * 1200 * 1000) - get (brig . paths ["self"] . zOAuthHeader (oatAccessToken accessToken)) !!! do + get (nginz . paths ["self"] . authHeader (oatAccessToken accessToken)) !!! do const 403 === statusCode const "Access denied" === statusMessage const (Just "Invalid token: JWTExpired") === responseBody -testAccessResourceNonsenseToken :: Brig -> Http () -testAccessResourceNonsenseToken brig = do - get (brig . paths ["self"] . zOAuthHeader @Text "foo") !!! do +testAccessResourceNonsenseToken :: Nginz -> Http () +testAccessResourceNonsenseToken nginz = do + get (nginz . paths ["self"] . authHeader @Text "foo") !!! do const 403 === statusCode const "Access denied" === statusMessage const (Just "Invalid token: Failed reading: JWSError") =~= responseBody @@ -412,8 +388,8 @@ testAccessResourceNoToken brig = const 403 === statusCode const "Access denied" === statusMessage -testAccessResourceInvalidSignature :: Opt.Opts -> Brig -> Http () -testAccessResourceInvalidSignature opts brig = do +testAccessResourceInvalidSignature :: Opt.Opts -> Brig -> Nginz -> Http () +testAccessResourceInvalidSignature opts brig nginz = do uid <- userId <$> createUser "alice" brig let redirectUrl = mkUrl "https://example.com" let scopes = OAuthScopes $ Set.fromList [ReadSelf] @@ -423,7 +399,7 @@ testAccessResourceInvalidSignature opts brig = do key <- liftIO $ readJwk (fromMaybe "path to jwk not set" (Opt.setOAuthJwkKeyPair $ Opt.optSettings opts)) <&> fromMaybe (error "invalid key") claimSet <- fromRight (error "token invalid") <$> liftIO (verify key (unOAuthToken $ oatAccessToken accessToken)) tokenSignedWithotherKey <- signAccessToken badKey claimSet - get (brig . paths ["self"] . zOAuthHeader (OAuthToken tokenSignedWithotherKey)) !!! do + get (nginz . paths ["self"] . authHeader (OAuthToken tokenSignedWithotherKey)) !!! do const 403 === statusCode const "Access denied" === statusMessage const (Just "Invalid token: JWSError JWSInvalidSignature") === responseBody @@ -486,8 +462,8 @@ testRefreshTokenMaxActiveTokens opts db brig = hasSameElems :: (Eq a) => [a] -> [a] -> Bool hasSameElems x y = null (x \\ y) && null (y \\ x) -testRefreshTokenRetrieveAccessToken :: Opts -> Brig -> Http () -testRefreshTokenRetrieveAccessToken opts brig = +testRefreshTokenRetrieveAccessToken :: Opts -> Brig -> Nginz -> Http () +testRefreshTokenRetrieveAccessToken opts brig nginz = -- overriding settings and set access token to expire in 2 seconds withSettingsOverrides (opts & Opt.optionSettings . Opt.oauthAccessTokenExpirationTimeSecsInternal ?~ 2) $ do uid <- userId <$> createUser "alice" brig @@ -496,12 +472,12 @@ testRefreshTokenRetrieveAccessToken opts brig = (cid, secret, code) <- generateOAuthClientAndAuthCode brig uid scopes redirectUrl let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid secret code redirectUrl accessToken <- createOAuthAccessToken brig accessTokenRequest - get (brig . paths ["self"] . zOAuthHeader (oatAccessToken accessToken)) !!! const 200 === statusCode + get (nginz . paths ["self"] . authHeader (oatAccessToken accessToken)) !!! const 200 === statusCode threadDelay $ 2 * 1000 * 1000 -- wait 2 seconds for access token to expire - get (brig . paths ["self"] . zOAuthHeader (oatAccessToken accessToken)) !!! const 403 === statusCode + get (nginz . paths ["self"] . authHeader (oatAccessToken accessToken)) !!! const 403 === statusCode let refreshAccessTokenRequest = OAuthRefreshAccessTokenRequest OAuthGrantTypeRefreshToken cid secret (oatRefreshToken accessToken) refreshedToken <- refreshOAuthAccessToken brig refreshAccessTokenRequest - get (brig . paths ["self"] . zOAuthHeader (oatAccessToken refreshedToken)) !!! const 200 === statusCode + get (nginz . paths ["self"] . authHeader (oatAccessToken refreshedToken)) !!! const 200 === statusCode testRefreshTokenWrongSignature :: Opts -> Brig -> Http () testRefreshTokenWrongSignature opts brig = do @@ -664,11 +640,11 @@ testRevokeApplicationAccountAccess brig = do liftIO $ assertEqual "apps" 0 (length apps) _ -> liftIO $ assertFailure "unexpected number of apps" -testWriteConversationSuccessInternal :: Brig -> Galley -> Http () -testWriteConversationSuccessInternal brig galley = do +testWriteConversationSuccessInternal :: Brig -> Nginz -> Http () +testWriteConversationSuccessInternal brig nginz = do (uid, tid) <- Team.createUserWithTeam brig accessToken <- getAccessTokenForScope brig uid [WriteConversation] - createTeamConv galley zOAuthHeader (oatAccessToken accessToken) tid "oauth test group" !!! do + createTeamConv nginz authHeader (oatAccessToken accessToken) tid "oauth test group" !!! do const 201 === statusCode testWriteConversationSuccessNginz :: Brig -> Nginz -> Http () @@ -678,11 +654,11 @@ testWriteConversationSuccessNginz brig nginz = do createTeamConv nginz authHeader (oatAccessToken accessToken) tid "oauth test group" !!! do const 201 === statusCode -testReadFeatureConfigsSuccessInternal :: Brig -> Galley -> Http () -testReadFeatureConfigsSuccessInternal brig galley = do +testReadFeatureConfigsSuccessInternal :: Brig -> Nginz -> Http () +testReadFeatureConfigsSuccessInternal brig nginz = do (uid, _) <- Team.createUserWithTeam brig accessToken <- getAccessTokenForScope brig uid [ReadFeatureConfigs] - getFeatureConfigs galley zOAuthHeader (oatAccessToken accessToken) !!! do + getFeatureConfigs nginz authHeader (oatAccessToken accessToken) !!! do const 200 === statusCode testReadFeatureConfigsSuccessNginz :: Brig -> Nginz -> Http () @@ -692,15 +668,15 @@ testReadFeatureConfigsSuccessNginz brig nginz = do getFeatureConfigs nginz authHeader (oatAccessToken accessToken) !!! do const 200 === statusCode -testWriteConversationCodeSuccessInternal :: Brig -> Galley -> Http () -testWriteConversationCodeSuccessInternal brig galley = do +testWriteConversationCodeSuccessInternal :: Brig -> Nginz -> Http () +testWriteConversationCodeSuccessInternal brig nginz = do (uid, tid) <- Team.createUserWithTeam brig accessToken <- getAccessTokenForScope brig uid [WriteConversation, WriteConversationCode] conv <- responseJsonError - =<< createTeamConv galley zOAuthHeader (oatAccessToken accessToken) tid "oauth test group" Nginz -> Http () @@ -773,9 +749,6 @@ verifyRefreshToken jwk jwt = authHeader :: ToHttpApiData a => a -> Request -> Request authHeader = bearer "Authorization" -zOAuthHeader :: ToHttpApiData a => a -> Request -> Request -zOAuthHeader = bearer "Z-OAuth" - bearer :: ToHttpApiData a => HeaderName -> a -> Request -> Request bearer name = header name . toHeader . Bearer diff --git a/services/brig/test/integration/Main.hs b/services/brig/test/integration/Main.hs index cb51eefd06..91b2f15e0b 100644 --- a/services/brig/test/integration/Main.hs +++ b/services/brig/test/integration/Main.hs @@ -160,7 +160,7 @@ runTests iConf brigOpts otherArgs = do let smtp = SMTP.tests mg lg versionApi = API.Version.tests mg brigOpts b mlsApi = MLS.tests mg b brigOpts - oauthAPI = API.OAuth.tests mg db b g n brigOpts + oauthAPI = API.OAuth.tests mg db b n brigOpts withArgs otherArgs . defaultMain $ testGroup From 7e2a584516a99c24515231313e83bbba0cb3deb9 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Tue, 21 Feb 2023 08:39:32 +0000 Subject: [PATCH 10/32] return a tuple --- libs/libzauth/libzauth-c/src/lib.rs | 34 +++++++++++++++--- libs/libzauth/libzauth-c/src/zauth.h | 10 ++++-- libs/libzauth/libzauth/src/oauth.rs | 2 ++ .../nginx-zauth-module/zauth_module.c | 35 +++++++++++-------- 4 files changed, 60 insertions(+), 21 deletions(-) diff --git a/libs/libzauth/libzauth-c/src/lib.rs b/libs/libzauth/libzauth-c/src/lib.rs index c375cd24ad..9b58aed31c 100644 --- a/libs/libzauth/libzauth-c/src/lib.rs +++ b/libs/libzauth/libzauth-c/src/lib.rs @@ -20,7 +20,6 @@ extern crate zauth; use libc::size_t; use std::char; -use std::ffi::CString; use std::fs::File; use std::io::{self, BufReader, Read}; use std::panic::{self, UnwindSafe}; @@ -30,6 +29,7 @@ use std::slice; use std::str; use zauth::acl; use zauth::{Acl, Error, Keystore, OauthError, Token, TokenType, TokenVerification}; +use std::ffi::CString; /// Variant of std::try! that returns the unwrapped error. macro_rules! try_unwrap { @@ -48,6 +48,13 @@ pub struct Range { len: size_t, } +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct OAuthResult { + uid: *const libc::c_char, + status: ZauthResult, +} + pub struct ZauthAcl(zauth::Acl); pub struct ZauthKeystore(zauth::Keystore); pub struct ZauthToken(zauth::Token<'static>); @@ -365,8 +372,8 @@ pub extern "C" fn verify_oauth_token( scope: *const u8, scope_len: size_t, method: *const u8, - method_len: size_t, -) -> *mut libc::c_char { + method_len: size_t +) -> OAuthResult { if token.is_null() { eprintln!("token is null"); // return ZauthResult::NullArg; @@ -382,6 +389,23 @@ pub extern "C" fn verify_oauth_token( let bytes = unsafe { slice::from_raw_parts(method, method_len) }; let method = str::from_utf8(bytes).unwrap(); let subject = zauth::verify_oauth_token(&jwk.0, token, scope, method).unwrap(); - let c_str_song = CString::new(subject).unwrap(); - c_str_song.into_raw() + eprintln!("subject: {:?}", subject); + let c_str = CString::new(subject).unwrap(); + OAuthResult { + uid: c_str.into_raw(), + status: ZauthResult::Ok, + } +} + +#[no_mangle] +pub extern "C" fn oauth_result_uid_free(s: *mut libc::c_char) { + catch_unwind(|| { + unsafe { + if s.is_null() { + return ZauthResult::Ok; + } + CString::from_raw(s) + }; + ZauthResult::Ok + }); } diff --git a/libs/libzauth/libzauth-c/src/zauth.h b/libs/libzauth/libzauth-c/src/zauth.h index a4c11dac26..1e0c5211a0 100644 --- a/libs/libzauth/libzauth-c/src/zauth.h +++ b/libs/libzauth/libzauth-c/src/zauth.h @@ -50,6 +50,11 @@ typedef struct ZauthKeystore ZauthKeystore; typedef struct ZauthToken ZauthToken; typedef struct OAuthJwk OAuthJwk; +typedef struct { + char * uid; + ZauthResult status; +} OAuthResult; + ZauthResult zauth_keystore_open(uint8_t const * fname, size_t len, ZauthKeystore **); void zauth_keystore_delete(ZauthKeystore * store); @@ -68,8 +73,9 @@ uint8_t zauth_token_version(ZauthToken const *); Range zauth_token_lookup(ZauthToken const *, uint8_t); ZauthResult zauth_token_allowed(ZauthToken const *, ZauthAcl const *, uint8_t const * path, size_t len, uint8_t * result); void zauth_token_delete(ZauthToken *); -char * verify_oauth_token(OAuthJwk const *, uint8_t const * t, size_t t_len, uint8_t const * s, size_t s_len, uint8_t const * m, size_t m_len); -void print_jwk(OAuthJwk const *); +OAuthResult verify_oauth_token(OAuthJwk const *, uint8_t const * t, size_t t_len, uint8_t const * s, size_t s_len, uint8_t const * m, size_t m_len); +void print_jwk(OAuthJwk const *); // todo(leif): remove +ZauthResult oauth_result_uid_free(char *); #ifdef __cplusplus } diff --git a/libs/libzauth/libzauth/src/oauth.rs b/libs/libzauth/libzauth/src/oauth.rs index 23fa5952f6..723f91810e 100644 --- a/libs/libzauth/libzauth/src/oauth.rs +++ b/libs/libzauth/libzauth/src/oauth.rs @@ -28,6 +28,8 @@ pub fn verify_oauth_token( // if method is POST, authorized scopes must contain either write:_ or admin:_ // if method is PUT, authorized scopes must contain either write:_ or admin:_ // if method is DELETE, authorized scopes must contain admin:_ +// FUTUREWORK: this works for now, but maybe we should consider using a more flexible scope system in the future +// e.g. by using a configuration file with a mapping of scopes to methods and paths fn verify_scope( authorized_scopes: &str, required_scope: &str, diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index d952d18d29..6f4e771f98 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -36,6 +36,7 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t *); static ZauthResult token_from_header (ngx_str_t const *, ZauthToken **); static ZauthResult token_from_query (ngx_str_t const *, ZauthToken **); static void delete_token (void *); +static void delete_oauth_uid (void *); // Variable manipulation static ngx_int_t zauth_variables (ngx_conf_t *); @@ -327,8 +328,12 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "scope len: %d", scope.len); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "method: %s", r->method_name.data); - char *uid = verify_oauth_token(sc->oauth_key, &hdr->data[7], hdr->len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth user id: %s", uid); + OAuthResult res = verify_oauth_token(sc->oauth_key, &hdr->data[7], hdr->len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "verify_oauth_token called"); + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth result status: %d", res.status); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth result: %s", res.uid); ngx_pool_cleanup_t * finaliser = ngx_pool_cleanup_add(r->pool, 0); if (finaliser == NULL) { @@ -337,10 +342,9 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { } ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "finaliser not null"); - // todo(leif): set deconstructor? - // finaliser->handler = delete_token; - finaliser->data = uid; - ngx_http_set_ctx(r, uid, zauth_module); + finaliser->handler = delete_oauth_uid; + finaliser->data = res.uid; + ngx_http_set_ctx(r, res.uid, zauth_module); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "user id set to context"); status = NGX_OK; } else { @@ -408,6 +412,10 @@ static void delete_token (void * data) { zauth_token_delete((ZauthToken *) data); } +static void delete_oauth_uid(void * data) { + oauth_result_uid_free((char *) data); +} + static ngx_int_t zauth_parse_request (ngx_http_request_t * r) { ZauthToken* tkn = NULL; ZauthResult res = ZAUTH_OK; @@ -623,15 +631,14 @@ static ngx_int_t zauth_token_var_user (ngx_http_request_t * r, ngx_http_variable return zauth_set_var(r->pool, v, zauth_token_lookup(t, 'u')); } else { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "tkn is NULL"); - char const * oauth_uid = ngx_http_get_module_ctx(r, zauth_module); - if (oauth_uid != NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_uid (not null): %s", oauth_uid); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_uid (not null) len: %d", strlen(oauth_uid)); - ngx_int_t res = zauth_set_var(r->pool, v, (Range) { (u_char*) oauth_uid, strlen(oauth_uid) }); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "result: %d", res); - return res; + char const * uid = ngx_http_get_module_ctx(r, zauth_module); + if (uid != NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_uid (not null): %s", uid); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_uid (not null) len: %d", strlen(uid)); + ngx_int_t status = zauth_set_var(r->pool, v, (Range) { (u_char*) uid, strlen(uid) }); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "result: %d", status); + return status; } else { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_uid is NULL"); zauth_empty_val(v); return NGX_OK; } From d94918f1273fbad19cd8ebfb0236992bf7ae972e Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Tue, 21 Feb 2023 08:45:26 +0000 Subject: [PATCH 11/32] free oauth key mem --- libs/libzauth/libzauth-c/src/lib.rs | 31 ++++++++++++------- libs/libzauth/libzauth-c/src/zauth.h | 3 +- .../nginx-zauth-module/zauth_module.c | 5 +-- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/libs/libzauth/libzauth-c/src/lib.rs b/libs/libzauth/libzauth-c/src/lib.rs index 9b58aed31c..211fc02d32 100644 --- a/libs/libzauth/libzauth-c/src/lib.rs +++ b/libs/libzauth/libzauth-c/src/lib.rs @@ -20,6 +20,7 @@ extern crate zauth; use libc::size_t; use std::char; +use std::ffi::CString; use std::fs::File; use std::io::{self, BufReader, Read}; use std::panic::{self, UnwindSafe}; @@ -29,7 +30,6 @@ use std::slice; use std::str; use zauth::acl; use zauth::{Acl, Error, Keystore, OauthError, Token, TokenType, TokenVerification}; -use std::ffi::CString; /// Variant of std::try! that returns the unwrapped error. macro_rules! try_unwrap { @@ -110,7 +110,21 @@ pub extern "C" fn zauth_acl_open(f: *const u8, n: size_t, a: *mut *mut ZauthAcl) } #[no_mangle] -pub extern "C" fn zauth_oauth_key_open(f: *const u8, n: size_t, k: *mut *mut OAuthJwk) -> ZauthResult { +pub extern "C" fn zauth_acl_delete(a: *mut ZauthAcl) { + catch_unwind(|| { + unsafe { + Box::from_raw(a); + } + ZauthResult::Ok + }); +} + +#[no_mangle] +pub extern "C" fn zauth_oauth_key_open( + f: *const u8, + n: size_t, + k: *mut *mut OAuthJwk, +) -> ZauthResult { if f.is_null() { return ZauthResult::NullArg; } @@ -120,7 +134,6 @@ pub extern "C" fn zauth_oauth_key_open(f: *const u8, n: size_t, k: *mut *mut OAu let mut rdr = BufReader::new(try_unwrap!(File::open(&Path::new(path)))); let mut txt = String::new(); try_unwrap!(rdr.read_to_string(&mut txt)); - eprintln!("jwk loaded key: {:?}", txt); // todo(leif): remove unsafe { *k = Box::into_raw(Box::new(OAuthJwk(txt))); } @@ -129,7 +142,7 @@ pub extern "C" fn zauth_oauth_key_open(f: *const u8, n: size_t, k: *mut *mut OAu } #[no_mangle] -pub extern "C" fn zauth_acl_delete(a: *mut ZauthAcl) { +pub extern "C" fn zauth_oauth_key_delete(a: *mut OAuthJwk) { catch_unwind(|| { unsafe { Box::from_raw(a); @@ -359,11 +372,6 @@ impl From for ZauthTokenVerification { } } -#[no_mangle] -pub extern "C" fn print_jwk(jwk: &OAuthJwk) { - eprintln!("print jwk: {:?}", jwk.0); -} - #[no_mangle] pub extern "C" fn verify_oauth_token( jwk: &OAuthJwk, @@ -372,7 +380,7 @@ pub extern "C" fn verify_oauth_token( scope: *const u8, scope_len: size_t, method: *const u8, - method_len: size_t + method_len: size_t, ) -> OAuthResult { if token.is_null() { eprintln!("token is null"); @@ -389,12 +397,11 @@ pub extern "C" fn verify_oauth_token( let bytes = unsafe { slice::from_raw_parts(method, method_len) }; let method = str::from_utf8(bytes).unwrap(); let subject = zauth::verify_oauth_token(&jwk.0, token, scope, method).unwrap(); - eprintln!("subject: {:?}", subject); let c_str = CString::new(subject).unwrap(); OAuthResult { uid: c_str.into_raw(), status: ZauthResult::Ok, - } + } } #[no_mangle] diff --git a/libs/libzauth/libzauth-c/src/zauth.h b/libs/libzauth/libzauth-c/src/zauth.h index 1e0c5211a0..e57492deed 100644 --- a/libs/libzauth/libzauth-c/src/zauth.h +++ b/libs/libzauth/libzauth-c/src/zauth.h @@ -62,7 +62,7 @@ ZauthResult zauth_acl_open(uint8_t const * fname, size_t len, ZauthAcl **); void zauth_acl_delete(ZauthAcl * store); ZauthResult zauth_oauth_key_open(uint8_t const * fname, size_t len, OAuthJwk **); -// todo(leif): do we need to dereference the key? +void zauth_oauth_key_delete(OAuthJwk * store); ZauthResult zauth_token_parse(uint8_t const * str, size_t len, ZauthToken **); ZauthResult zauth_token_verify(ZauthToken const *, ZauthKeystore const *); @@ -74,7 +74,6 @@ Range zauth_token_lookup(ZauthToken const *, uint8_t); ZauthResult zauth_token_allowed(ZauthToken const *, ZauthAcl const *, uint8_t const * path, size_t len, uint8_t * result); void zauth_token_delete(ZauthToken *); OAuthResult verify_oauth_token(OAuthJwk const *, uint8_t const * t, size_t t_len, uint8_t const * s, size_t s_len, uint8_t const * m, size_t m_len); -void print_jwk(OAuthJwk const *); // todo(leif): remove ZauthResult oauth_result_uid_free(char *); #ifdef __cplusplus diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index 6f4e771f98..fab01b42bc 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -191,7 +191,9 @@ static void delete_srv_conf (void * data) { if (c->acl != NULL) { zauth_acl_delete(c->acl); } - // todo(leif): free oauth_key? + if (c->oauth_key != NULL) { + zauth_oauth_key_delete(c->oauth_key); + } } static void * create_loc_conf (ngx_conf_t * conf) { @@ -318,7 +320,6 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { // if zauth fails, we try to handle oauth if (status != NGX_OK && sc->oauth_key != NULL) { - print_jwk(sc->oauth_key); ngx_str_t *hdr = &r->headers_in.authorization->value; if (strncmp((char const *) hdr->data, "Bearer ", 7) == 0) { ngx_str_t scope = lc->oauth_scope; From 37f4cf3389caaab9f258ceb8f6e2552c0993a3c2 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Tue, 21 Feb 2023 08:50:20 +0000 Subject: [PATCH 12/32] naming convenetions --- libs/libzauth/libzauth-c/src/lib.rs | 8 ++++---- libs/libzauth/libzauth-c/src/zauth.h | 8 ++++---- .../third_party/nginx-zauth-module/zauth_module.c | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/libs/libzauth/libzauth-c/src/lib.rs b/libs/libzauth/libzauth-c/src/lib.rs index 211fc02d32..268e471e25 100644 --- a/libs/libzauth/libzauth-c/src/lib.rs +++ b/libs/libzauth/libzauth-c/src/lib.rs @@ -120,7 +120,7 @@ pub extern "C" fn zauth_acl_delete(a: *mut ZauthAcl) { } #[no_mangle] -pub extern "C" fn zauth_oauth_key_open( +pub extern "C" fn oauth_key_open( f: *const u8, n: size_t, k: *mut *mut OAuthJwk, @@ -142,7 +142,7 @@ pub extern "C" fn zauth_oauth_key_open( } #[no_mangle] -pub extern "C" fn zauth_oauth_key_delete(a: *mut OAuthJwk) { +pub extern "C" fn oauth_key_delete(a: *mut OAuthJwk) { catch_unwind(|| { unsafe { Box::from_raw(a); @@ -373,7 +373,7 @@ impl From for ZauthTokenVerification { } #[no_mangle] -pub extern "C" fn verify_oauth_token( +pub extern "C" fn oauth_verify_token( jwk: &OAuthJwk, token: *const u8, token_len: size_t, @@ -405,7 +405,7 @@ pub extern "C" fn verify_oauth_token( } #[no_mangle] -pub extern "C" fn oauth_result_uid_free(s: *mut libc::c_char) { +pub extern "C" fn oauth_result_uid_delete(s: *mut libc::c_char) { catch_unwind(|| { unsafe { if s.is_null() { diff --git a/libs/libzauth/libzauth-c/src/zauth.h b/libs/libzauth/libzauth-c/src/zauth.h index e57492deed..b7177da311 100644 --- a/libs/libzauth/libzauth-c/src/zauth.h +++ b/libs/libzauth/libzauth-c/src/zauth.h @@ -61,8 +61,8 @@ void zauth_keystore_delete(ZauthKeystore * store); ZauthResult zauth_acl_open(uint8_t const * fname, size_t len, ZauthAcl **); void zauth_acl_delete(ZauthAcl * store); -ZauthResult zauth_oauth_key_open(uint8_t const * fname, size_t len, OAuthJwk **); -void zauth_oauth_key_delete(OAuthJwk * store); +ZauthResult oauth_key_open(uint8_t const * fname, size_t len, OAuthJwk **); +void oauth_key_delete(OAuthJwk * store); ZauthResult zauth_token_parse(uint8_t const * str, size_t len, ZauthToken **); ZauthResult zauth_token_verify(ZauthToken const *, ZauthKeystore const *); @@ -73,8 +73,8 @@ uint8_t zauth_token_version(ZauthToken const *); Range zauth_token_lookup(ZauthToken const *, uint8_t); ZauthResult zauth_token_allowed(ZauthToken const *, ZauthAcl const *, uint8_t const * path, size_t len, uint8_t * result); void zauth_token_delete(ZauthToken *); -OAuthResult verify_oauth_token(OAuthJwk const *, uint8_t const * t, size_t t_len, uint8_t const * s, size_t s_len, uint8_t const * m, size_t m_len); -ZauthResult oauth_result_uid_free(char *); +OAuthResult oauth_verify_token(OAuthJwk const *, uint8_t const * t, size_t t_len, uint8_t const * s, size_t s_len, uint8_t const * m, size_t m_len); +ZauthResult oauth_result_uid_delete(char *); #ifdef __cplusplus } diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index fab01b42bc..38830517ef 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -192,7 +192,7 @@ static void delete_srv_conf (void * data) { zauth_acl_delete(c->acl); } if (c->oauth_key != NULL) { - zauth_oauth_key_delete(c->oauth_key); + oauth_key_delete(c->oauth_key); } } @@ -258,7 +258,7 @@ static char * load_oauth_key (ngx_conf_t * conf, ngx_command_t * cmd, void * dat } ngx_str_t * const fname = conf->args->elts; - ZauthResult e = zauth_oauth_key_open(fname[1].data, fname[1].len, &sc->oauth_key); + ZauthResult e = oauth_key_open(fname[1].data, fname[1].len, &sc->oauth_key); if (e != ZAUTH_OK || sc->oauth_key == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, conf, 0, "failed to load oauth key [%d]", e); @@ -329,9 +329,9 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "scope len: %d", scope.len); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "method: %s", r->method_name.data); - OAuthResult res = verify_oauth_token(sc->oauth_key, &hdr->data[7], hdr->len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); + OAuthResult res = oauth_verify_token(sc->oauth_key, &hdr->data[7], hdr->len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "verify_oauth_token called"); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_verify_token called"); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth result status: %d", res.status); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth result: %s", res.uid); @@ -414,7 +414,7 @@ static void delete_token (void * data) { } static void delete_oauth_uid(void * data) { - oauth_result_uid_free((char *) data); + oauth_result_uid_delete((char *) data); } static ngx_int_t zauth_parse_request (ngx_http_request_t * r) { From 7de3a20a0894c0a2dddbe77d9f52767fd85368bd Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Tue, 21 Feb 2023 09:06:59 +0000 Subject: [PATCH 13/32] get rid of rust warnings --- libs/libzauth/libzauth-c/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/libzauth/libzauth-c/src/lib.rs b/libs/libzauth/libzauth-c/src/lib.rs index 268e471e25..690f44bf48 100644 --- a/libs/libzauth/libzauth-c/src/lib.rs +++ b/libs/libzauth/libzauth-c/src/lib.rs @@ -84,7 +84,7 @@ pub extern "C" fn zauth_keystore_open( pub extern "C" fn zauth_keystore_delete(s: *mut ZauthKeystore) { catch_unwind(|| { unsafe { - Box::from_raw(s); + drop(Box::from_raw(s)); } ZauthResult::Ok }); @@ -113,7 +113,7 @@ pub extern "C" fn zauth_acl_open(f: *const u8, n: size_t, a: *mut *mut ZauthAcl) pub extern "C" fn zauth_acl_delete(a: *mut ZauthAcl) { catch_unwind(|| { unsafe { - Box::from_raw(a); + drop(Box::from_raw(a)); } ZauthResult::Ok }); @@ -145,7 +145,7 @@ pub extern "C" fn oauth_key_open( pub extern "C" fn oauth_key_delete(a: *mut OAuthJwk) { catch_unwind(|| { unsafe { - Box::from_raw(a); + drop(Box::from_raw(a)); } ZauthResult::Ok }); @@ -245,7 +245,7 @@ pub extern "C" fn zauth_token_lookup(t: &ZauthToken, c: u8) -> Range { pub extern "C" fn zauth_token_delete(t: *mut ZauthToken) { catch_unwind(|| { unsafe { - Box::from_raw(t); + drop(Box::from_raw(t)); } ZauthResult::Ok }); From 341e642310c65f566655012af564d00e0cc92460 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Tue, 21 Feb 2023 09:26:49 +0000 Subject: [PATCH 14/32] formatting --- libs/libzauth/libzauth-c/src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/libs/libzauth/libzauth-c/src/lib.rs b/libs/libzauth/libzauth-c/src/lib.rs index 690f44bf48..9cae132ea4 100644 --- a/libs/libzauth/libzauth-c/src/lib.rs +++ b/libs/libzauth/libzauth-c/src/lib.rs @@ -120,11 +120,7 @@ pub extern "C" fn zauth_acl_delete(a: *mut ZauthAcl) { } #[no_mangle] -pub extern "C" fn oauth_key_open( - f: *const u8, - n: size_t, - k: *mut *mut OAuthJwk, -) -> ZauthResult { +pub extern "C" fn oauth_key_open(f: *const u8, n: size_t, k: *mut *mut OAuthJwk) -> ZauthResult { if f.is_null() { return ZauthResult::NullArg; } From 80f84093350ba6d2bfc924c74df6d5c0ee5a056a Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Tue, 21 Feb 2023 13:34:32 +0000 Subject: [PATCH 15/32] error propagation --- libs/libzauth/libzauth-c/src/lib.rs | 252 ++++++++++++------ libs/libzauth/libzauth-c/src/zauth.h | 20 +- .../nginx-zauth-module/zauth_module.c | 51 ++-- 3 files changed, 212 insertions(+), 111 deletions(-) diff --git a/libs/libzauth/libzauth-c/src/lib.rs b/libs/libzauth/libzauth-c/src/lib.rs index 9cae132ea4..5bf1968d7d 100644 --- a/libs/libzauth/libzauth-c/src/lib.rs +++ b/libs/libzauth/libzauth-c/src/lib.rs @@ -20,7 +20,7 @@ extern crate zauth; use libc::size_t; use std::char; -use std::ffi::CString; +use std::ffi::{CString, NulError}; use std::fs::File; use std::io::{self, BufReader, Read}; use std::panic::{self, UnwindSafe}; @@ -29,7 +29,9 @@ use std::ptr; use std::slice; use std::str; use zauth::acl; -use zauth::{Acl, Error, Keystore, OauthError, Token, TokenType, TokenVerification}; +use zauth::{ + verify_oauth_token, Acl, Error, Keystore, OauthError, Token, TokenType, TokenVerification, +}; /// Variant of std::try! that returns the unwrapped error. macro_rules! try_unwrap { @@ -48,17 +50,9 @@ pub struct Range { len: size_t, } -#[repr(C)] -#[derive(Clone, Copy, Debug)] -pub struct OAuthResult { - uid: *const libc::c_char, - status: ZauthResult, -} - pub struct ZauthAcl(zauth::Acl); pub struct ZauthKeystore(zauth::Keystore); pub struct ZauthToken(zauth::Token<'static>); -pub struct OAuthJwk(String); #[no_mangle] pub extern "C" fn zauth_keystore_open( @@ -119,34 +113,6 @@ pub extern "C" fn zauth_acl_delete(a: *mut ZauthAcl) { }); } -#[no_mangle] -pub extern "C" fn oauth_key_open(f: *const u8, n: size_t, k: *mut *mut OAuthJwk) -> ZauthResult { - if f.is_null() { - return ZauthResult::NullArg; - } - catch_unwind(|| { - let bytes = unsafe { slice::from_raw_parts(f, n) }; - let path = try_unwrap!(str::from_utf8(bytes)); - let mut rdr = BufReader::new(try_unwrap!(File::open(&Path::new(path)))); - let mut txt = String::new(); - try_unwrap!(rdr.read_to_string(&mut txt)); - unsafe { - *k = Box::into_raw(Box::new(OAuthJwk(txt))); - } - ZauthResult::Ok - }) -} - -#[no_mangle] -pub extern "C" fn oauth_key_delete(a: *mut OAuthJwk) { - catch_unwind(|| { - unsafe { - drop(Box::from_raw(a)); - } - ZauthResult::Ok - }); -} - #[no_mangle] pub extern "C" fn zauth_token_parse( cs: *const u8, @@ -288,9 +254,6 @@ pub enum ZauthResult { UnknownKey = 9, Utf8Error = 10, AclError = 11, - JsonError = 12, - JwtError = 13, - JwkError = 14, Panic = 99, } @@ -309,19 +272,6 @@ impl From for ZauthResult { } } -impl From for ZauthResult { - fn from(e: zauth::OauthError) -> ZauthResult { - match e { - OauthError::JsonError(_) => Self::JsonError, - OauthError::InvalidJwtNoSubject - | OauthError::InvalidScope - | OauthError::JwtSimpleError(_) => Self::JwtError, - OauthError::Base64DecodeError(_) => Self::Base64Error, - OauthError::InvalidJwk => Self::JwkError, - } - } -} - impl From for ZauthResult { fn from(_: acl::Error) -> ZauthResult { ZauthResult::AclError @@ -368,6 +318,129 @@ impl From for ZauthTokenVerification { } } +// ------------------------------------------------------------------------ +// OAuth + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub enum OAuthResultStatus { + Ok = 0, + InsufficientScope = 1, + NullArg = 2, + IoError = 3, + Utf8Error = 4, + Panic = 99, +} + +pub struct OAuthJwk(String); + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct OAuthResult { + uid: *const libc::c_char, + status: OAuthResultStatus, +} + +fn catch_unwind_with(f: F, r: R) -> R +where + F: FnOnce() -> R + UnwindSafe, +{ + match panic::catch_unwind(f) { + Ok(x) => x, + Err(_) => r, + } +} + +impl From for OAuthResultStatus { + fn from(e: OauthError) -> OAuthResultStatus { + match e { + OauthError::JsonError(_) => Self::Panic, + OauthError::JwtSimpleError(_) => Self::Panic, + OauthError::Base64DecodeError(_) => Self::Panic, + OauthError::InvalidJwk => Self::Panic, + OauthError::InvalidJwtNoSubject => Self::Panic, + OauthError::InvalidScope => Self::InsufficientScope, + } + } +} + +impl From for OAuthResultStatus { + fn from(_: io::Error) -> OAuthResultStatus { + OAuthResultStatus::IoError + } +} + +impl From for OAuthResultStatus { + fn from(_: str::Utf8Error) -> OAuthResultStatus { + OAuthResultStatus::Utf8Error + } +} + +impl From for OAuthResult { + fn from(_: str::Utf8Error) -> OAuthResult { + OAuthResult { + uid: ptr::null(), + status: OAuthResultStatus::Utf8Error, + } + } +} + +impl From for OAuthResult { + fn from(_: NulError) -> OAuthResult { + OAuthResult { + uid: ptr::null(), + status: OAuthResultStatus::Panic, + } + } +} + +impl From for OAuthResult { + fn from(e: OauthError) -> OAuthResult { + OAuthResult { + uid: ptr::null(), + status: e.into(), + } + } +} + +#[no_mangle] +pub extern "C" fn oauth_key_open( + f: *const u8, + n: size_t, + k: *mut *mut OAuthJwk, +) -> OAuthResultStatus { + if f.is_null() { + return OAuthResultStatus::NullArg; + } + catch_unwind_with( + || { + let bytes = unsafe { slice::from_raw_parts(f, n) }; + let path = try_unwrap!(str::from_utf8(bytes)); + let mut rdr = BufReader::new(try_unwrap!(File::open(&Path::new(path)))); + let mut txt = String::new(); + try_unwrap!(rdr.read_to_string(&mut txt)); + unsafe { + *k = Box::into_raw(Box::new(OAuthJwk(txt))); + } + OAuthResultStatus::Ok + }, + OAuthResultStatus::Panic, + ) +} + +#[no_mangle] +pub extern "C" fn oauth_key_delete(a: *mut OAuthJwk) { + catch_unwind_with( + || { + unsafe { + drop(Box::from_raw(a)); + } + OAuthResultStatus::Ok + }, + OAuthResultStatus::Panic, + ); +} + #[no_mangle] pub extern "C" fn oauth_verify_token( jwk: &OAuthJwk, @@ -378,37 +451,52 @@ pub extern "C" fn oauth_verify_token( method: *const u8, method_len: size_t, ) -> OAuthResult { - if token.is_null() { - eprintln!("token is null"); - // return ZauthResult::NullArg; - } - if scope.is_null() { - eprintln!("scope is null"); - // return ZauthResult::NullArg; - } - let bytes = unsafe { slice::from_raw_parts(token, token_len) }; - let token = str::from_utf8(bytes).unwrap(); - let bytes = unsafe { slice::from_raw_parts(scope, scope_len) }; - let scope = str::from_utf8(bytes).unwrap(); - let bytes = unsafe { slice::from_raw_parts(method, method_len) }; - let method = str::from_utf8(bytes).unwrap(); - let subject = zauth::verify_oauth_token(&jwk.0, token, scope, method).unwrap(); - let c_str = CString::new(subject).unwrap(); - OAuthResult { - uid: c_str.into_raw(), - status: ZauthResult::Ok, + match panic::catch_unwind(|| { + if token.is_null() { + return OAuthResult { + uid: ptr::null(), + status: OAuthResultStatus::NullArg, + }; + } + if scope.is_null() { + return OAuthResult { + uid: ptr::null(), + status: OAuthResultStatus::NullArg, + }; + } + let bytes = unsafe { slice::from_raw_parts(token, token_len) }; + let token = try_unwrap!(str::from_utf8(bytes)); + let bytes = unsafe { slice::from_raw_parts(scope, scope_len) }; + let scope = try_unwrap!(str::from_utf8(bytes)); + let bytes = unsafe { slice::from_raw_parts(method, method_len) }; + let method = str::from_utf8(bytes).unwrap(); + let subject = try_unwrap!(verify_oauth_token(&jwk.0, token, scope, method)); + let c_str = try_unwrap!(CString::new(subject)); + OAuthResult { + uid: c_str.into_raw(), + status: OAuthResultStatus::Ok, + } + }) { + Ok(x) => x, + Err(_) => OAuthResult { + uid: ptr::null(), + status: OAuthResultStatus::Panic, + }, } } #[no_mangle] pub extern "C" fn oauth_result_uid_delete(s: *mut libc::c_char) { - catch_unwind(|| { - unsafe { - if s.is_null() { - return ZauthResult::Ok; - } - CString::from_raw(s) - }; - ZauthResult::Ok - }); + catch_unwind_with( + || { + unsafe { + if s.is_null() { + return OAuthResultStatus::Ok; + } + CString::from_raw(s) + }; + OAuthResultStatus::Ok + }, + OAuthResultStatus::Panic, + ); } diff --git a/libs/libzauth/libzauth-c/src/zauth.h b/libs/libzauth/libzauth-c/src/zauth.h index b7177da311..c5cc24ea0b 100644 --- a/libs/libzauth/libzauth-c/src/zauth.h +++ b/libs/libzauth/libzauth-c/src/zauth.h @@ -48,11 +48,21 @@ typedef enum { typedef struct ZauthAcl ZauthAcl; typedef struct ZauthKeystore ZauthKeystore; typedef struct ZauthToken ZauthToken; + +typedef enum { + OAUTH_OK = 0, + OAUTH_INSUFFICIENT_SCOPE = 1, + OAUTH_NULL_ARG = 2, + OAUTH_IO_ERROR = 3, + OAUTH_UTF8_ERROR = 4, + OAUTH_PANIC = 99, +} OAuthResultStatus; + typedef struct OAuthJwk OAuthJwk; typedef struct { - char * uid; - ZauthResult status; + char * uid; + OAuthResultStatus status; } OAuthResult; ZauthResult zauth_keystore_open(uint8_t const * fname, size_t len, ZauthKeystore **); @@ -61,8 +71,8 @@ void zauth_keystore_delete(ZauthKeystore * store); ZauthResult zauth_acl_open(uint8_t const * fname, size_t len, ZauthAcl **); void zauth_acl_delete(ZauthAcl * store); -ZauthResult oauth_key_open(uint8_t const * fname, size_t len, OAuthJwk **); -void oauth_key_delete(OAuthJwk * store); +OAuthResultStatus oauth_key_open(uint8_t const * fname, size_t len, OAuthJwk **); +void oauth_key_delete(OAuthJwk * store); ZauthResult zauth_token_parse(uint8_t const * str, size_t len, ZauthToken **); ZauthResult zauth_token_verify(ZauthToken const *, ZauthKeystore const *); @@ -74,7 +84,7 @@ Range zauth_token_lookup(ZauthToken const *, uint8_t); ZauthResult zauth_token_allowed(ZauthToken const *, ZauthAcl const *, uint8_t const * path, size_t len, uint8_t * result); void zauth_token_delete(ZauthToken *); OAuthResult oauth_verify_token(OAuthJwk const *, uint8_t const * t, size_t t_len, uint8_t const * s, size_t s_len, uint8_t const * m, size_t m_len); -ZauthResult oauth_result_uid_delete(char *); +OAuthResultStatus oauth_result_uid_delete(char *); #ifdef __cplusplus } diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index 38830517ef..417214e1bf 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -258,10 +258,10 @@ static char * load_oauth_key (ngx_conf_t * conf, ngx_command_t * cmd, void * dat } ngx_str_t * const fname = conf->args->elts; - ZauthResult e = oauth_key_open(fname[1].data, fname[1].len, &sc->oauth_key); + OAuthResultStatus status = oauth_key_open(fname[1].data, fname[1].len, &sc->oauth_key); - if (e != ZAUTH_OK || sc->oauth_key == NULL) { - ngx_conf_log_error(NGX_LOG_EMERG, conf, 0, "failed to load oauth key [%d]", e); + if (status != OAUTH_OK || sc->oauth_key == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, conf, 0, "failed to load oauth key [%d]", status); return NGX_CONF_ERROR; } @@ -331,26 +331,29 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { OAuthResult res = oauth_verify_token(sc->oauth_key, &hdr->data[7], hdr->len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_verify_token called"); - - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth result status: %d", res.status); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth result: %s", res.uid); - - ngx_pool_cleanup_t * finaliser = ngx_pool_cleanup_add(r->pool, 0); - if (finaliser == NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "finaliser is null"); - return NGX_ERROR; + if (res.status == OAUTH_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_verify_token called"); + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth result status: %d", res.status); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth result: %s", res.uid); + + ngx_pool_cleanup_t * finaliser = ngx_pool_cleanup_add(r->pool, 0); + if (finaliser == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "finaliser is null"); + return NGX_ERROR; + } + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "finaliser not null"); + + finaliser->handler = delete_oauth_uid; + finaliser->data = res.uid; + ngx_http_set_ctx(r, res.uid, zauth_module); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "user id set to context"); + status = NGX_OK; + } else { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_verify_token failed"); + return NGX_HTTP_UNAUTHORIZED; } - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "finaliser not null"); - - finaliser->handler = delete_oauth_uid; - finaliser->data = res.uid; - ngx_http_set_ctx(r, res.uid, zauth_module); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "user id set to context"); - status = NGX_OK; - } else { - status = ZAUTH_PARSE_ERROR; - } + } } // if zauth or oauth succeeds, we empty the Authorization header @@ -576,8 +579,8 @@ static ngx_int_t zauth_token_typeinfo (ngx_http_request_t * r, ngx_http_variable } } -// check if the signature has been validated (authorized) -// and endpoint is either not denied or allowed according to the access control list (acl) configuration +// check if the signature has been validated +// and access is allowed (endpoint is either allowed or not denied according to the access control list (ACL) configuration) static bool zauth_is_authorized_and_allowed(ngx_http_request_t * r) { ZauthToken const * t = ngx_http_get_module_ctx(r, zauth_module); From 0b5bcec2f0f9613b63c99de70508b9277471a5ae Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Tue, 21 Feb 2023 14:23:12 +0000 Subject: [PATCH 16/32] oauth nginz pass locally --- services/brig/brig.integration.yaml | 2 +- services/brig/test/integration/API/OAuth.hs | 42 ++++++++----------- .../integration-test/conf/nginz/nginx.conf | 18 ++++++++ .../nginx-zauth-module/zauth_module.c | 23 ++-------- 4 files changed, 41 insertions(+), 44 deletions(-) diff --git a/services/brig/brig.integration.yaml b/services/brig/brig.integration.yaml index eeca275079..eb261c1ca2 100644 --- a/services/brig/brig.integration.yaml +++ b/services/brig/brig.integration.yaml @@ -195,7 +195,7 @@ optSettings: setEnableMLS: true setOAuthJwkKeyPair: test/resources/oauth/ed25519.jwk setOAuthAuthCodeExpirationTimeSecs: 3 # 3 secs - setOAuthAccessTokenExpirationTimeSecs: 1814400 # 3 weeks + setOAuthAccessTokenExpirationTimeSecs: 3 # 5 secs setOAuthEnabled: true setOAuthRefreshTokenExpirationTimeSecs: 14515200 # 24 weeks setOAuthMaxActiveRefreshTokens: 10 diff --git a/services/brig/test/integration/API/OAuth.hs b/services/brig/test/integration/API/OAuth.hs index ee1e9b6ef1..5169a4ae67 100644 --- a/services/brig/test/integration/API/OAuth.hs +++ b/services/brig/test/integration/API/OAuth.hs @@ -357,8 +357,7 @@ testAccessResourceInsufficientScope brig nginz = do accessToken <- createOAuthAccessToken brig accessTokenRequest get (nginz . paths ["self"] . authHeader (oatAccessToken accessToken)) !!! do const 403 === statusCode - const "Access denied" === statusMessage - const (Just "Insufficient scope") === responseBody + const "Forbidden" === statusMessage testAccessResourceExpiredToken :: Opt.Opts -> Brig -> Nginz -> Http () testAccessResourceExpiredToken opts brig nginz = @@ -373,14 +372,12 @@ testAccessResourceExpiredToken opts brig nginz = get (nginz . paths ["self"] . authHeader (oatAccessToken accessToken)) !!! do const 403 === statusCode const "Access denied" === statusMessage - const (Just "Invalid token: JWTExpired") === responseBody testAccessResourceNonsenseToken :: Nginz -> Http () testAccessResourceNonsenseToken nginz = do get (nginz . paths ["self"] . authHeader @Text "foo") !!! do - const 403 === statusCode - const "Access denied" === statusMessage - const (Just "Invalid token: Failed reading: JWSError") =~= responseBody + const 401 === statusCode + const "Unauthorized" === statusMessage testAccessResourceNoToken :: Brig -> Http () testAccessResourceNoToken brig = @@ -400,9 +397,8 @@ testAccessResourceInvalidSignature opts brig nginz = do claimSet <- fromRight (error "token invalid") <$> liftIO (verify key (unOAuthToken $ oatAccessToken accessToken)) tokenSignedWithotherKey <- signAccessToken badKey claimSet get (nginz . paths ["self"] . authHeader (OAuthToken tokenSignedWithotherKey)) !!! do - const 403 === statusCode - const "Access denied" === statusMessage - const (Just "Invalid token: JWSError JWSInvalidSignature") === responseBody + const 401 === statusCode + const "Unauthorized" === statusMessage testRefreshTokenMaxActiveTokens :: Opts -> C.ClientState -> Brig -> Http () testRefreshTokenMaxActiveTokens opts db brig = @@ -463,21 +459,19 @@ testRefreshTokenMaxActiveTokens opts db brig = hasSameElems x y = null (x \\ y) && null (y \\ x) testRefreshTokenRetrieveAccessToken :: Opts -> Brig -> Nginz -> Http () -testRefreshTokenRetrieveAccessToken opts brig nginz = - -- overriding settings and set access token to expire in 2 seconds - withSettingsOverrides (opts & Opt.optionSettings . Opt.oauthAccessTokenExpirationTimeSecsInternal ?~ 2) $ do - uid <- userId <$> createUser "alice" brig - let redirectUrl = mkUrl "https://example.com" - let scopes = OAuthScopes $ Set.fromList [ReadSelf] - (cid, secret, code) <- generateOAuthClientAndAuthCode brig uid scopes redirectUrl - let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid secret code redirectUrl - accessToken <- createOAuthAccessToken brig accessTokenRequest - get (nginz . paths ["self"] . authHeader (oatAccessToken accessToken)) !!! const 200 === statusCode - threadDelay $ 2 * 1000 * 1000 -- wait 2 seconds for access token to expire - get (nginz . paths ["self"] . authHeader (oatAccessToken accessToken)) !!! const 403 === statusCode - let refreshAccessTokenRequest = OAuthRefreshAccessTokenRequest OAuthGrantTypeRefreshToken cid secret (oatRefreshToken accessToken) - refreshedToken <- refreshOAuthAccessToken brig refreshAccessTokenRequest - get (nginz . paths ["self"] . authHeader (oatAccessToken refreshedToken)) !!! const 200 === statusCode +testRefreshTokenRetrieveAccessToken _opts brig nginz = do + uid <- userId <$> createUser "alice" brig + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [ReadSelf] + (cid, secret, code) <- generateOAuthClientAndAuthCode brig uid scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid secret code redirectUrl + accessToken <- createOAuthAccessToken brig accessTokenRequest + get (nginz . paths ["self"] . authHeader (oatAccessToken accessToken)) !!! const 200 === statusCode + threadDelay $ 5 * 1000 * 1000 -- wait 5 seconds for access token to expire + get (nginz . paths ["self"] . authHeader (oatAccessToken accessToken)) !!! const 401 === statusCode + let refreshAccessTokenRequest = OAuthRefreshAccessTokenRequest OAuthGrantTypeRefreshToken cid secret (oatRefreshToken accessToken) + refreshedToken <- refreshOAuthAccessToken brig refreshAccessTokenRequest + get (nginz . paths ["self"] . authHeader (oatAccessToken refreshedToken)) !!! const 200 === statusCode testRefreshTokenWrongSignature :: Opts -> Brig -> Http () testRefreshTokenWrongSignature opts brig = do diff --git a/services/nginz/integration-test/conf/nginz/nginx.conf b/services/nginz/integration-test/conf/nginz/nginx.conf index e94d8b6fc6..a921e9eaea 100644 --- a/services/nginz/integration-test/conf/nginz/nginx.conf +++ b/services/nginz/integration-test/conf/nginz/nginx.conf @@ -317,6 +317,18 @@ http { proxy_pass http://galley; } + location ~* ^(/v[0-9]+)?/conversations$ { + include common_response_with_zauth.conf; + oauth_scope conversation; + proxy_pass http://galley; + } + + location ~* ^(/v[0-9]+)?/conversations/([^/]*)/code { + include common_response_with_zauth.conf; + oauth_scope conversation_code; + proxy_pass http://galley; + } + location ~* ^(/v[0-9]+)?/conversations.* { include common_response_with_zauth.conf; proxy_pass http://galley; @@ -377,6 +389,12 @@ http { proxy_pass http://galley; } + location ~* ^(/v[0-9]+)?/feature-configs$ { + include common_response_with_zauth.conf; + oauth_scope feature_configs; + proxy_pass http://galley; + } + location ~* ^(/v[0-9]+)?/feature-configs(.*) { include common_response_with_zauth.conf; proxy_pass http://galley; diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index 417214e1bf..a9e2537e11 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -323,34 +323,23 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { ngx_str_t *hdr = &r->headers_in.authorization->value; if (strncmp((char const *) hdr->data, "Bearer ", 7) == 0) { ngx_str_t scope = lc->oauth_scope; - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "scope data: %s", scope.data); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "hdr data: %s", hdr->data); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "hdr len: %d", hdr->len - 7); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "scope len: %d", scope.len); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "method: %s", r->method_name.data); OAuthResult res = oauth_verify_token(sc->oauth_key, &hdr->data[7], hdr->len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); if (res.status == OAUTH_OK) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_verify_token called"); - - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth result status: %d", res.status); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth result: %s", res.uid); - ngx_pool_cleanup_t * finaliser = ngx_pool_cleanup_add(r->pool, 0); if (finaliser == NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "finaliser is null"); return NGX_ERROR; } - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "finaliser not null"); - finaliser->handler = delete_oauth_uid; finaliser->data = res.uid; ngx_http_set_ctx(r, res.uid, zauth_module); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "user id set to context"); status = NGX_OK; + } else if (res.status == OAUTH_INSUFFICIENT_SCOPE) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth insufficient cope"); + return NGX_HTTP_FORBIDDEN; } else { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_verify_token failed"); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth token verification failed with: %d", res.status); return NGX_HTTP_UNAUTHORIZED; } } @@ -634,13 +623,9 @@ static ngx_int_t zauth_token_var_user (ngx_http_request_t * r, ngx_http_variable if (t != NULL && zauth_is_authorized_and_allowed(r)) { return zauth_set_var(r->pool, v, zauth_token_lookup(t, 'u')); } else { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "tkn is NULL"); char const * uid = ngx_http_get_module_ctx(r, zauth_module); if (uid != NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_uid (not null): %s", uid); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "oauth_uid (not null) len: %d", strlen(uid)); ngx_int_t status = zauth_set_var(r->pool, v, (Range) { (u_char*) uid, strlen(uid) }); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "result: %d", status); return status; } else { zauth_empty_val(v); From befeca0246d5db086c7ef25e525cca90970ba606 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Tue, 21 Feb 2023 14:31:34 +0000 Subject: [PATCH 17/32] nginx config for CI --- charts/nginz/templates/conf/_nginx.conf.tpl | 2 +- charts/nginz/values.yaml | 13 +++++++++++-- libs/wire-api/src/Wire/API/OAuth.hs | 8 ++++---- services/brig/brig.integration.yaml | 2 +- .../nginz/integration-test/conf/nginz/nginx.conf | 4 ++-- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/charts/nginz/templates/conf/_nginx.conf.tpl b/charts/nginz/templates/conf/_nginx.conf.tpl index 442ddd1200..2c5dcd2734 100644 --- a/charts/nginz/templates/conf/_nginx.conf.tpl +++ b/charts/nginz/templates/conf/_nginx.conf.tpl @@ -260,7 +260,7 @@ http { {{- end }} {{- if ($location.oauth_scope) }} - # todo(leif): set scope + oauth_scope $oauth_scope; {{- end }} {{- if hasKey $location "specific_user_rate_limit" }} diff --git a/charts/nginz/values.yaml b/charts/nginz/values.yaml index 6aeadd6e42..9cb41104c3 100644 --- a/charts/nginz/values.yaml +++ b/charts/nginz/values.yaml @@ -425,11 +425,20 @@ nginx_conf: - all max_body_size: 40m body_buffer_size: 256k - - path: /conversations + - path: /conversations$ envs: - all doc: true oauth_scope: conversations + - path: /conversations/([^/]*)/code + envs: + - all + doc: true + oauth_scope: conversations_code + - path: /conversations + envs: + - all + doc: true - path: /legalhold/conversations/(.*) envs: - all @@ -497,7 +506,7 @@ nginx_conf: - path: /feature-configs(.*) envs: - all - oauth_scope: feature-configs + oauth_scope: feature_configs - path: /mls/welcome envs: - all diff --git a/libs/wire-api/src/Wire/API/OAuth.hs b/libs/wire-api/src/Wire/API/OAuth.hs index 2c1fc220fe..2503fa406a 100644 --- a/libs/wire-api/src/Wire/API/OAuth.hs +++ b/libs/wire-api/src/Wire/API/OAuth.hs @@ -220,8 +220,8 @@ instance (IsOAuthScope scope, IsOAuthScopes scopes) => IsOAuthScopes (scope ': s instance ToByteString OAuthScope where builder = \case - WriteConversation -> "write:conversation" - WriteConversationCode -> "write:conversation_code" + WriteConversation -> "write:conversations" + WriteConversationCode -> "write:conversations_code" ReadSelf -> "read:self" ReadFeatureConfigs -> "read:feature_configs" @@ -229,8 +229,8 @@ instance FromByteString OAuthScope where parser = do s <- parser case s & T.toLower of - "write:conversation" -> pure WriteConversation - "write:conversation_code" -> pure WriteConversationCode + "write:conversations" -> pure WriteConversation + "write:conversations_code" -> pure WriteConversationCode "read:self" -> pure ReadSelf "read:feature_configs" -> pure ReadFeatureConfigs _ -> fail "invalid scope" diff --git a/services/brig/brig.integration.yaml b/services/brig/brig.integration.yaml index eb261c1ca2..3a2be7f91e 100644 --- a/services/brig/brig.integration.yaml +++ b/services/brig/brig.integration.yaml @@ -195,7 +195,7 @@ optSettings: setEnableMLS: true setOAuthJwkKeyPair: test/resources/oauth/ed25519.jwk setOAuthAuthCodeExpirationTimeSecs: 3 # 3 secs - setOAuthAccessTokenExpirationTimeSecs: 3 # 5 secs + setOAuthAccessTokenExpirationTimeSecs: 3 # 3 secs setOAuthEnabled: true setOAuthRefreshTokenExpirationTimeSecs: 14515200 # 24 weeks setOAuthMaxActiveRefreshTokens: 10 diff --git a/services/nginz/integration-test/conf/nginz/nginx.conf b/services/nginz/integration-test/conf/nginz/nginx.conf index a921e9eaea..843b8ea103 100644 --- a/services/nginz/integration-test/conf/nginz/nginx.conf +++ b/services/nginz/integration-test/conf/nginz/nginx.conf @@ -319,13 +319,13 @@ http { location ~* ^(/v[0-9]+)?/conversations$ { include common_response_with_zauth.conf; - oauth_scope conversation; + oauth_scope conversations; proxy_pass http://galley; } location ~* ^(/v[0-9]+)?/conversations/([^/]*)/code { include common_response_with_zauth.conf; - oauth_scope conversation_code; + oauth_scope conversations_code; proxy_pass http://galley; } From 223b0fbc3c13fdfc55a2d966e5aff3800cf23c03 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Tue, 21 Feb 2023 15:06:55 +0000 Subject: [PATCH 18/32] oauth public key for nginz CI --- hack/helm_vars/wire-server/values.yaml.gotmpl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/hack/helm_vars/wire-server/values.yaml.gotmpl b/hack/helm_vars/wire-server/values.yaml.gotmpl index da8493e084..a247d9629e 100644 --- a/hack/helm_vars/wire-server/values.yaml.gotmpl +++ b/hack/helm_vars/wire-server/values.yaml.gotmpl @@ -244,12 +244,13 @@ nginz: zAuth: # this must match the key in brig! publicKeys: 0UW38se1yeoc5bVNEvf5LyrHWGZkyvcGTVilK2geGdU= - oAuth: | - { - "kty": "OKP", - "crv": "Ed25519", - "x": "mhP-NgFw3ifIXGZqJVB0kemt9L3BtD5P8q4Gah4Iklc" - } + oAuth: + publicKeys: | + { + "kty": "OKP", + "crv": "Ed25519", + "x": "mhP-NgFw3ifIXGZqJVB0kemt9L3BtD5P8q4Gah4Iklc" + } proxy: replicaCount: 1 imagePullPolicy: {{ .Values.imagePullPolicy }} From 8ba3108a72cbae46f79bef413e32e3cfe41e403c Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Tue, 21 Feb 2023 15:18:30 +0000 Subject: [PATCH 19/32] better rust --- libs/libzauth/libzauth-c/Makefile | 2 +- libs/libzauth/libzauth-c/src/lib.rs | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/libs/libzauth/libzauth-c/Makefile b/libs/libzauth/libzauth-c/Makefile index 9fa10a3935..228563cbad 100644 --- a/libs/libzauth/libzauth-c/Makefile +++ b/libs/libzauth/libzauth-c/Makefile @@ -27,7 +27,7 @@ build: cargo build build-release: - cargo build + cargo build --release install: build-release mkdir -p $(PREFIX_INSTALL)/include diff --git a/libs/libzauth/libzauth-c/src/lib.rs b/libs/libzauth/libzauth-c/src/lib.rs index 5bf1968d7d..9df1dae3f2 100644 --- a/libs/libzauth/libzauth-c/src/lib.rs +++ b/libs/libzauth/libzauth-c/src/lib.rs @@ -335,7 +335,7 @@ pub enum OAuthResultStatus { pub struct OAuthJwk(String); #[repr(C)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub struct OAuthResult { uid: *const libc::c_char, status: OAuthResultStatus, @@ -352,7 +352,7 @@ where } impl From for OAuthResultStatus { - fn from(e: OauthError) -> OAuthResultStatus { + fn from(e: OauthError) -> Self { match e { OauthError::JsonError(_) => Self::Panic, OauthError::JwtSimpleError(_) => Self::Panic, @@ -365,19 +365,19 @@ impl From for OAuthResultStatus { } impl From for OAuthResultStatus { - fn from(_: io::Error) -> OAuthResultStatus { - OAuthResultStatus::IoError + fn from(_: io::Error) -> Self { + Self::IoError } } impl From for OAuthResultStatus { - fn from(_: str::Utf8Error) -> OAuthResultStatus { - OAuthResultStatus::Utf8Error + fn from(_: str::Utf8Error) -> Self { + Self::Utf8Error } } impl From for OAuthResult { - fn from(_: str::Utf8Error) -> OAuthResult { + fn from(_: str::Utf8Error) -> Self { OAuthResult { uid: ptr::null(), status: OAuthResultStatus::Utf8Error, @@ -386,7 +386,7 @@ impl From for OAuthResult { } impl From for OAuthResult { - fn from(_: NulError) -> OAuthResult { + fn from(_: NulError) -> Self { OAuthResult { uid: ptr::null(), status: OAuthResultStatus::Panic, @@ -395,7 +395,7 @@ impl From for OAuthResult { } impl From for OAuthResult { - fn from(e: OauthError) -> OAuthResult { + fn from(e: OauthError) -> Self { OAuthResult { uid: ptr::null(), status: e.into(), @@ -464,6 +464,12 @@ pub extern "C" fn oauth_verify_token( status: OAuthResultStatus::NullArg, }; } + if method.is_null() { + return OAuthResult { + uid: ptr::null(), + status: OAuthResultStatus::NullArg, + }; + } let bytes = unsafe { slice::from_raw_parts(token, token_len) }; let token = try_unwrap!(str::from_utf8(bytes)); let bytes = unsafe { slice::from_raw_parts(scope, scope_len) }; From 45418cd94935dd3e882705881cc24d34346bb66a Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Tue, 21 Feb 2023 16:16:14 +0000 Subject: [PATCH 20/32] remove pub keys from brig and galley, fix stuff --- charts/galley/templates/configmap.yaml | 5 +- charts/galley/templates/secret.yaml | 3 - .../src/developer/reference/config-options.md | 10 - hack/helm_vars/wire-server/values.yaml.gotmpl | 8 +- libs/wire-api/src/Wire/API/Routes/API.hs | 40 +-- libs/wire-api/src/Wire/API/Routes/Public.hs | 248 ++++-------------- .../src/Wire/API/Routes/Public/Brig.hs | 3 +- .../API/Routes/Public/Galley/Conversation.hs | 7 +- .../Wire/API/Routes/Public/Galley/Feature.hs | 3 +- services/brig/src/Brig/Run.hs | 17 +- services/brig/test/integration/API/OAuth.hs | 45 ++-- services/galley/galley.integration.yaml | 1 - services/galley/src/Galley/Options.hs | 4 +- services/galley/src/Galley/Run.hs | 12 +- .../test/resources/oauth/ed25519_public.jwk | 1 - .../nginx-zauth-module/zauth_module.c | 7 +- 16 files changed, 98 insertions(+), 316 deletions(-) delete mode 100644 services/galley/test/resources/oauth/ed25519_public.jwk diff --git a/charts/galley/templates/configmap.yaml b/charts/galley/templates/configmap.yaml index 52cdee3cb3..3138b50f55 100644 --- a/charts/galley/templates/configmap.yaml +++ b/charts/galley/templates/configmap.yaml @@ -71,10 +71,7 @@ data: {{- end -}} {{- if .settings.disabledAPIVersions }} disabledAPIVersions: {{ .settings.disabledAPIVersions }} - {{- end }} - {{- if $.Values.secrets.oauthPublicJwk }} - oauthPublicJwk: /etc/wire/galley/secrets/oauth_ed25519_pub.jwk - {{- end }} + {{- end }} {{- if .settings.featureFlags }} featureFlags: sso: {{ .settings.featureFlags.sso }} diff --git a/charts/galley/templates/secret.yaml b/charts/galley/templates/secret.yaml index 81b1ca205a..354c52fc1e 100644 --- a/charts/galley/templates/secret.yaml +++ b/charts/galley/templates/secret.yaml @@ -14,6 +14,3 @@ data: removal_ed25519.pem: {{ .Values.secrets.mlsPrivateKeys.removal.ed25519 | b64enc | quote }} {{- end -}} {{- end -}} - {{- if .Values.secrets.oauthPublicJwk }} - oauth_ed25519_pub.jwk: {{ .Values.secrets.oauthPublicJwk | b64enc | quote }} - {{- end -}} diff --git a/docs/src/developer/reference/config-options.md b/docs/src/developer/reference/config-options.md index 6681fa50fc..177c91d9d8 100644 --- a/docs/src/developer/reference/config-options.md +++ b/docs/src/developer/reference/config-options.md @@ -54,16 +54,6 @@ certificate, is to run the following command: openssl req -nodes -newkey ed25519 -keyout ed25519.pem -out /dev/null -subj / ``` -### Public JWK for OAuth - -Set the path to the public JWK key for OAuth like this: - -```yml -# [galley.yaml] -settings: - oauthPublicJwk: test/resources/oauth/ed25519_public.jwk -``` - ## Feature flags > Also see [Wire docs](https://docs.wire.com/how-to/install/team-feature-settings.html) where some of the feature flags are documented from an operations point of view. diff --git a/hack/helm_vars/wire-server/values.yaml.gotmpl b/hack/helm_vars/wire-server/values.yaml.gotmpl index a247d9629e..e770419dc4 100644 --- a/hack/helm_vars/wire-server/values.yaml.gotmpl +++ b/hack/helm_vars/wire-server/values.yaml.gotmpl @@ -86,7 +86,7 @@ brig: setDpopTokenExpirationTimeSecs: 300 setEnableMLS: true setOAuthAuthCodeExpirationTimeSecs: 3 # 3 secs - setOAuthAccessTokenExpirationTimeSecs: 1814400 # 3 weeks + setOAuthAccessTokenExpirationTimeSecs: 3 # 3 secs setOAuthEnabled: true setOAuthRefreshTokenExpirationTimeSecs: 14515200 # 24 weeks setOAuthMaxActiveRefreshTokens: 10 @@ -193,12 +193,6 @@ galley: -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIAocCDXsKIAjb65gOUn5vEF0RIKnVJkKR4ebQzuZ709c -----END PRIVATE KEY----- - oauthPublicJwk: | - { - "kty": "OKP", - "crv": "Ed25519", - "x": "mhP-NgFw3ifIXGZqJVB0kemt9L3BtD5P8q4Gah4Iklc" - } gundeck: replicaCount: 1 diff --git a/libs/wire-api/src/Wire/API/Routes/API.hs b/libs/wire-api/src/Wire/API/Routes/API.hs index 0500983e1b..607933e2ed 100644 --- a/libs/wire-api/src/Wire/API/Routes/API.hs +++ b/libs/wire-api/src/Wire/API/Routes/API.hs @@ -17,21 +17,18 @@ module Wire.API.Routes.API ( API, - hoistAPI, hoistAPIHandler, + hoistAPI, mkAPI, mkNamedAPI, - hoistServerWithDomain, - hoistServerWithDomainAndJwk, (<@>), ServerEffect (..), ServerEffects (..), + hoistServerWithDomain, ) where -import Crypto.JOSE (JWK) import Data.Domain -import Data.Kind (Type) import Data.Proxy import Imports import Polysemy @@ -50,29 +47,15 @@ mkAPI :: (HasServer api '[Domain], ServerEffects (DeclaredErrorEffects api) r0) => ServerT api (Sem (Append (DeclaredErrorEffects api) r0)) -> API api r0 -mkAPI = mkAPIWithContext @'[Domain] - -mkAPIWithContext :: - forall (context :: [Type]) r0 api. - (HasServer api context, ServerEffects (DeclaredErrorEffects api) r0) => - ServerT api (Sem (Append (DeclaredErrorEffects api) r0)) -> - API api r0 -mkAPIWithContext h = API $ hoistServerWithContext (Proxy @api) (Proxy @context) (interpretServerEffects @(DeclaredErrorEffects api) @r0) h +mkAPI h = API $ hoistServerWithDomain @api (interpretServerEffects @(DeclaredErrorEffects api) @r0) h -- | Convert a polysemy handler to a named 'API' value. mkNamedAPI :: forall name r0 api. - (HasServer api '[Domain, Maybe JWK], ServerEffects (DeclaredErrorEffects api) r0) => - ServerT api (Sem (Append (DeclaredErrorEffects api) r0)) -> - API (Named name api) r0 -mkNamedAPI = mkNamedAPIWithContext @'[Domain, Maybe JWK] - -mkNamedAPIWithContext :: - forall (context :: [Type]) name r0 api. - (HasServer api context, ServerEffects (DeclaredErrorEffects api) r0) => + (HasServer api '[Domain], ServerEffects (DeclaredErrorEffects api) r0) => ServerT api (Sem (Append (DeclaredErrorEffects api) r0)) -> API (Named name api) r0 -mkNamedAPIWithContext = API . Named . unAPI . mkAPIWithContext @context @r0 @api +mkNamedAPI = API . Named . unAPI . mkAPI @r0 @api -- | Combine APIs. (<@>) :: API api1 r -> API api2 r -> API (api1 :<|> api2) r @@ -94,22 +77,13 @@ hoistServerWithDomain :: ServerT api n hoistServerWithDomain = hoistServerWithContext (Proxy @api) (Proxy @'[Domain]) --- | Like `hoistServerWithDomain`, but with a additional 'Maybe JWK' context. -hoistServerWithDomainAndJwk :: - forall api m n. - HasServer api '[Domain, Maybe JWK] => - (forall x. m x -> n x) -> - ServerT api m -> - ServerT api n -hoistServerWithDomainAndJwk = hoistServerWithContext (Proxy @api) (Proxy @'[Domain, Maybe JWK]) - hoistAPIHandler :: forall api r n. - HasServer api '[Domain, Maybe JWK] => + HasServer api '[Domain] => (forall x. Sem r x -> n x) -> API api r -> ServerT api n -hoistAPIHandler f = hoistServerWithContext (Proxy @api) (Proxy @'[Domain, Maybe JWK]) f . unAPI +hoistAPIHandler f = hoistServerWithDomain @api f . unAPI hoistAPI :: forall api1 api2 r1 r2. diff --git a/libs/wire-api/src/Wire/API/Routes/Public.hs b/libs/wire-api/src/Wire/API/Routes/Public.hs index bdeb4ef5f0..bd2222e736 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public.hs @@ -30,42 +30,26 @@ module Wire.API.Routes.Public ZBot, ZConversation, ZProvider, - - -- * OAuth combinators - ZOauthUser, - ZOAuthLocalUser, ) where -import Control.Lens hiding (Context) -import Control.Monad.Except -import Crypto.JWT hiding (Context, params, uri, verify) -import Data.ByteString.Conversion (fromByteString) +import Control.Lens ((<>~)) import Data.Domain -import Data.Either.Combinators import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap import Data.Id as Id import Data.Kind import Data.Metrics.Servant import Data.Qualified -import Data.SOP -import Data.String.Conversions (cs) import Data.Swagger -import Data.Typeable (typeRep) import GHC.Base (Symbol) -import GHC.TypeLits (KnownSymbol, symbolVal) -import Imports hiding (All, exp, head) -import Network.Wai +import GHC.TypeLits (KnownSymbol) +import Imports hiding (All, head) import qualified Network.Wai as Wai -import Servant hiding (Handler, JSON, Tagged, addHeader, respond) +import Servant hiding (Handler, JSON, addHeader, respond) import Servant.API.Modifiers import Servant.Server.Internal.Delayed import Servant.Server.Internal.DelayedIO -import Servant.Server.Internal.Router import Servant.Swagger (HasSwagger (toSwagger)) -import Servant.Swagger.Internal.Orphans () -import Wire.API.OAuth -import Wire.API.Routes.Bearer mapRequestArgument :: forall mods a b. @@ -80,7 +64,7 @@ mapRequestArgument f x = (SFalse, STrue) -> (fmap . fmap) f x (SFalse, SFalse) -> fmap f x --- | This type exists for the special 'HasSwagger' and 'HasServer' instances. It +-- This type exists for the special 'HasSwagger' and 'HasServer' instances. It -- shows the "Authorization" header in the swagger docs, but expects the -- "Z-Auth" header in the server. This helps keep the swagger docs usable -- through nginz. @@ -168,7 +152,7 @@ instance IsZType 'ZAuthProvider ctx where instance HasTokenType 'ZAuthProvider where tokenType = Just "provider" -data ZAuthServant (ztype :: ZType) (opts :: [Type]) (scopes :: Maybe [OAuthScope]) +data ZAuthServant (ztype :: ZType) (opts :: [Type]) type InternalAuthDefOpts = '[Servant.Required, Servant.Strict] @@ -178,179 +162,52 @@ type InternalAuth ztype opts = (ZHeader ztype) (ZParam ztype) -type ZLocalUser = ZAuthServant 'ZLocalAuthUser InternalAuthDefOpts 'Nothing - -type ZUser = ZAuthServant 'ZAuthUser InternalAuthDefOpts 'Nothing - -type ZClient = ZAuthServant 'ZAuthClient InternalAuthDefOpts 'Nothing +type ZLocalUser = ZAuthServant 'ZLocalAuthUser InternalAuthDefOpts -type ZConn = ZAuthServant 'ZAuthConn InternalAuthDefOpts 'Nothing +type ZUser = ZAuthServant 'ZAuthUser InternalAuthDefOpts -type ZBot = ZAuthServant 'ZAuthBot InternalAuthDefOpts 'Nothing +type ZClient = ZAuthServant 'ZAuthClient InternalAuthDefOpts -type ZConversation = ZAuthServant 'ZAuthConv InternalAuthDefOpts 'Nothing +type ZConn = ZAuthServant 'ZAuthConn InternalAuthDefOpts -type ZProvider = ZAuthServant 'ZAuthProvider InternalAuthDefOpts 'Nothing +type ZBot = ZAuthServant 'ZAuthBot InternalAuthDefOpts -type ZOptUser = ZAuthServant 'ZAuthUser '[Servant.Optional, Servant.Strict] 'Nothing +type ZConversation = ZAuthServant 'ZAuthConv InternalAuthDefOpts -type ZOptClient = ZAuthServant 'ZAuthClient '[Servant.Optional, Servant.Strict] 'Nothing +type ZProvider = ZAuthServant 'ZAuthProvider InternalAuthDefOpts -type ZOptConn = ZAuthServant 'ZAuthConn '[Servant.Optional, Servant.Strict] 'Nothing +type ZOptUser = ZAuthServant 'ZAuthUser '[Servant.Optional, Servant.Strict] -type ZOAuthLocalUser (scopes :: [OAuthScope]) = ZAuthServant 'ZLocalAuthUser InternalAuthDefOpts ('Just scopes) +type ZOptClient = ZAuthServant 'ZAuthClient '[Servant.Optional, Servant.Strict] -type ZOauthUser (scopes :: [OAuthScope]) = ZAuthServant 'ZAuthUser InternalAuthDefOpts ('Just scopes) +type ZOptConn = ZAuthServant 'ZAuthConn '[Servant.Optional, Servant.Strict] -instance - (HasSwagger api, IsOAuthScopes scopes, scopes ~ (s ': ss), Typeable ztype) => - HasSwagger (ZAuthServant (ztype :: ZType) _opts ('Just scopes) :> api) - where - toSwagger _ = - toSwagger (Proxy @(ZAuthServant ztype _opts ('Nothing :: Maybe [OAuthScope]) :> api)) - & securityDefinitions <>~ SecurityDefinitions (InsOrdHashMap.singleton "OAuth" secScheme) - & security <>~ [SecurityRequirement $ InsOrdHashMap.singleton "OAuth" []] - & addScopeDescription - where - secScheme = - SecurityScheme - { _securitySchemeType = SecuritySchemeApiKey (ApiKeyParams "Authorization" ApiKeyHeader), - _securitySchemeDescription = - Just $ - "Must be a token retrieved with an oauth handshake. It must be presented in the form 'Bearer '. \ - \See also info on swagger top-level." - } - - addScopeDescription :: Swagger -> Swagger - addScopeDescription = allOperations . description %~ Just . (<> "\nOAuth scope(s): " <> showOAuthScopeList @scopes) . fold - -instance (HasSwagger api, Typeable ztype) => HasSwagger (ZAuthServant (ztype :: ZType) _opts 'Nothing :> api) where +instance HasSwagger api => HasSwagger (ZAuthServant 'ZAuthUser _opts :> api) where toSwagger _ = toSwagger (Proxy @api) & securityDefinitions <>~ SecurityDefinitions (InsOrdHashMap.singleton "ZAuth" secScheme) & security <>~ [SecurityRequirement $ InsOrdHashMap.singleton "ZAuth" []] - & addZTypeInfo where secScheme = SecurityScheme { _securitySchemeType = SecuritySchemeApiKey (ApiKeyParams "Authorization" ApiKeyHeader), - _securitySchemeDescription = - Just $ - "Must be a token retrieved by calling 'POST /login' or 'POST /access'. It must be presented in the form 'Bearer '. \ - \See also info on swagger top-level." + _securitySchemeDescription = Just "Must be a token retrieved by calling 'POST /login' or 'POST /access'. It must be presented in this format: 'Bearer \\'." } - addZTypeInfo :: Swagger -> Swagger - addZTypeInfo = - -- Don't use `tokenType @ztype` here, it's `Nothing` for everything but bot, provider! - allOperations . description %~ Just . (<> "\nZAuth token type: " <> (cs . show . typeRep $ (Proxy @ztype))) . fold +instance HasSwagger api => HasSwagger (ZAuthServant 'ZLocalAuthUser opts :> api) where + toSwagger _ = toSwagger (Proxy @(ZAuthServant 'ZAuthUser opts :> api)) -instance HasLink endpoint => HasLink (ZAuthServant usr opts scopes :> endpoint) where - type MkLink (ZAuthServant _ _ _ :> endpoint) a = MkLink endpoint a +instance HasLink endpoint => HasLink (ZAuthServant usr opts :> endpoint) where + type MkLink (ZAuthServant _ _ :> endpoint) a = MkLink endpoint a toLink toA _ = toLink toA (Proxy @endpoint) --- | Handle routes that support both ZAuth and OAuth, tried in that order (scopes is Just). --- --- The difference between the two `HasServer` instances for (1) `ZAuthServant ztype opts --- ('Just scopes)` and (2) `ZAuthServant ztype opts Nothing`, resp.: --- --- (1) zauth-or-oauth: enforce required, strict parsing; lookup headers directly, don't --- implement in terms of another servant combinator type. --- --- (2) zauth-only: allow for optional, lenient parsing of `Z-*` headers; implemented in terms --- of `InternalAuth ztype opts`. --- --- Due to these differences, the two instances, or the two functions `finalizeZAuthOrOAuth` --- and `checkZType`, are a bit awkward to consolidate. We just leave the two implementations --- independent for now. instance - ( IsZType ztype ctx, - HasContextEntry ctx (Maybe JWK), - opts ~ InternalAuthDefOpts, - HasServer api ctx, - IsOAuthScopes (scopes :: [OAuthScope]), - ZParam ztype ~ Id a - ) => - HasServer (ZAuthServant ztype opts ('Just scopes) :> api) ctx + {-# OVERLAPPABLE #-} + HasSwagger api => + HasSwagger (ZAuthServant ztype _opts :> api) where - type - ServerT (ZAuthServant ztype opts ('Just scopes) :> api) m = - ZQualifiedParam ztype -> ServerT api m - - route :: - Proxy (ZAuthServant ztype opts ('Just scopes) :> api) -> - Context ctx -> - Delayed env (Server (ZAuthServant ztype opts ('Just scopes) :> api)) -> - Router env - route _ ctx subserver = - Servant.route - (Proxy @api) - ctx - (addAuthCheck subserver (withRequest (fmap (qualifyZParam @ztype ctx) . finalizeZAuthOrOAuth @ztype @scopes @ctx ctx (tokenType @ztype)))) + toSwagger _ = toSwagger (Proxy @api) - hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s - --- | This function has been derived from `checkZType` below. -finalizeZAuthOrOAuth :: - forall ztype scopes ctx a. - ( IsZType ztype ctx, - HasContextEntry ctx (Maybe JWK), - IsOAuthScopes scopes, - ZParam ztype ~ Id a - ) => - Context ctx -> - Maybe ByteString {- FUTUREWORK: use type-level `ztype` instead? does `tokenType @ztype` inside this function have to be incoherent? -} -> - Request -> - DelayedIO (ZParam ztype) -finalizeZAuthOrOAuth ctx mTokenType req = - case lookupHeaders of - -- if the ztype requires a Z-Type header (instance of 'HasTokenType' returns a Just ...), we expect it to match the type we're looking for - (Just expType, Just actType, Just t, Nothing) | expType == actType -> zauth t - -- auth fails if the ztype doesn't match - (Just _, _, _, _) -> delayedFailFatal error403 - -- if the ztype does not require a Z-Type header, we just care for the ZParam ('Z-User' etc.) header - (Nothing, _, Just t, Nothing) -> zauth t - -- if *only* the 'Z-Oauth' header is present, we try to authenticate with OAuth - (Nothing, Nothing, Nothing, Just t) -> oauth t - -- any other case should fail - (Nothing, _, _, _) -> delayedFailFatal error403 - where - lookupHeaders :: (Maybe ByteString, Maybe ByteString, Maybe ByteString, Maybe ByteString) - lookupHeaders = (mTokenType, lookup "Z-Type" hs, lookup headerName hs, lookup "Z-OAuth" hs) - where - hs = requestHeaders req - - headerName :: IsString n => n - headerName = fromString $ symbolVal (Proxy @(ZHeader ztype)) - - zauth :: ByteString -> DelayedIO (ZParam ztype) - zauth = maybe (delayedFailFatal error403) pure . fromByteString @(ZParam ztype) - - oauth :: ByteString -> DelayedIO (ZParam ztype) - oauth = doOAuth (getContextEntry ctx) >=> either delayedFailFatal pure - - doOAuth :: Maybe JWK -> ByteString -> DelayedIO (Either ServerError (ZParam ztype)) - doOAuth mJwk h = tryOAuth - where - tryOAuth :: DelayedIO (Either ServerError (ZParam ztype)) - tryOAuth = do - let jwkOrError = maybeToRight jwtError mJwk - let tokenOrError = mapLeft invalidOAuthToken $ parseHeader h - either (pure . Left) verifyOAuthToken $ (,) <$> tokenOrError <*> jwkOrError - - verifyOAuthToken :: (Bearer OAuthAccessToken, JWK) -> DelayedIO (Either ServerError (ZParam ztype)) - verifyOAuthToken (token, key) = do - verifiedOrError <- mapLeft (invalidOAuthToken . cs . show) <$> liftIO (verify key (unOAuthToken . unBearer $ token)) - pure $ - verifiedOrError >>= \claimSet -> - if hasScope @scopes claimSet - then maybeToRight (invalidOAuthToken "Invalid token: Missing or invalid sub claim") (hcsSub claimSet) - else Left insufficientScope - --- | Handle routes that support ZAuth, but not OAuth (scopes is Nothing). --- --- See `HasServer` instance for `ZAuthServant ztype opts ('Just scopes)` for comparison --- (especially if you plan to change the code here). instance ( IsZType ztype ctx, HasContextEntry (ctx .++ DefaultErrorFormatters) ErrorFormatters, @@ -358,56 +215,39 @@ instance SBoolI (FoldRequired opts), HasServer api ctx ) => - HasServer (ZAuthServant ztype opts 'Nothing :> api) ctx + HasServer (ZAuthServant ztype opts :> api) ctx where type - ServerT (ZAuthServant ztype opts 'Nothing :> api) m = + ServerT (ZAuthServant ztype opts :> api) m = RequestArgument opts (ZQualifiedParam ztype) -> ServerT api m - route :: - Proxy (ZAuthServant ztype opts 'Nothing :> api) -> - Context ctx -> - Delayed env (Server (ZAuthServant ztype opts 'Nothing :> api)) -> - Router env route _ ctx subserver = do Servant.route (Proxy @(InternalAuth ztype opts :> api)) ctx ( fmap (. mapRequestArgument @opts (qualifyZParam @ztype ctx)) - (addAuthCheck (fmap const subserver) (withRequest (checkZType (tokenType @ztype)))) + (addAcceptCheck subserver (withRequest (checkType (tokenType @ztype)))) ) + where + checkType :: Maybe ByteString -> Wai.Request -> DelayedIO () + checkType token req = case (token, lookup "Z-Type" (Wai.requestHeaders req)) of + (Just t, value) + | value /= Just t -> + delayedFail + ServerError + { errHTTPCode = 403, + errReasonPhrase = "Access denied", + errBody = "", + errHeaders = [] + } + _ -> pure () + hoistServerWithContext _ pc nt s = hoistServerWithContext (Proxy :: Proxy api) pc nt . s -checkZType :: Maybe ByteString -> Wai.Request -> DelayedIO () -checkZType token req = case (token, lookup "Z-Type" (Wai.requestHeaders req)) of - (Just t, value) | value /= Just t -> delayedFail error403 - _ -> pure () - -error403 :: ServerError -error403 = - ServerError - { errHTTPCode = 403, - errReasonPhrase = "Access denied", - errBody = "", - errHeaders = [] - } - -instance RoutesToPaths api => RoutesToPaths (ZAuthServant ztype opts scopes :> api) where +instance RoutesToPaths api => RoutesToPaths (ZAuthServant ztype opts :> api) where getRoutes = getRoutes @api -- FUTUREWORK: Make a PR to the servant-swagger package with this instance instance ToSchema a => ToSchema (Headers ls a) where declareNamedSchema _ = declareNamedSchema (Proxy @a) - --------------------------------------------------------------------------------- --- Util - -insufficientScope :: ServerError -insufficientScope = err403 {errReasonPhrase = "Access denied", errBody = "Insufficient scope"} - -jwtError :: ServerError -jwtError = err500 {errReasonPhrase = "jwt-error", errBody = "Internal error while handling JWT token"} - -invalidOAuthToken :: Text -> ServerError -invalidOAuthToken t = err403 {errReasonPhrase = "Access denied", errBody = "Invalid token: " <> cs t} diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs b/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs index f445954e49..8771a05ab7 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs @@ -49,7 +49,6 @@ import Wire.API.Error.Empty import Wire.API.MLS.KeyPackage import Wire.API.MLS.Servant import Wire.API.MakesFederatedCall -import Wire.API.OAuth import Wire.API.Properties import Wire.API.Routes.Bearer import Wire.API.Routes.Cookies @@ -262,7 +261,7 @@ type SelfAPI = Named "get-self" ( Summary "Get your own profile" - :> ZOauthUser '[ 'ReadSelf] + :> ZUser :> "self" :> Get '[JSON] SelfProfile ) diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs index 77f5a50424..3f47ba4f19 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs @@ -33,7 +33,6 @@ import Wire.API.Event.Conversation import Wire.API.MLS.PublicGroupState import Wire.API.MLS.Servant import Wire.API.MakesFederatedCall -import Wire.API.OAuth import Wire.API.Routes.MultiVerb import Wire.API.Routes.Named import Wire.API.Routes.Public @@ -339,7 +338,7 @@ type ConversationAPI = :> CanThrow OperationDenied :> CanThrow 'MissingLegalholdConsent :> Description "This returns 201 when a new conversation is created, and 200 when the conversation already existed" - :> ZOAuthLocalUser '[ 'WriteConversation] + :> ZLocalUser :> ZOptConn :> "conversations" :> VersionedReqBody 'V2 '[Servant.JSON] NewConv @@ -358,7 +357,7 @@ type ConversationAPI = :> CanThrow OperationDenied :> CanThrow 'MissingLegalholdConsent :> Description "This returns 201 when a new conversation is created, and 200 when the conversation already existed" - :> ZOAuthLocalUser '[ 'WriteConversation] + :> ZLocalUser :> ZOptConn :> "conversations" :> ReqBody '[Servant.JSON] NewConv @@ -586,7 +585,7 @@ type ConversationAPI = :> CanThrow 'ConvAccessDenied :> CanThrow 'ConvNotFound :> CanThrow 'GuestLinksDisabled - :> ZOauthUser '[ 'WriteConversationCode] + :> ZUser :> ZOptConn :> "conversations" :> Capture' '[Description "Conversation ID"] "cnv" ConvId diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Feature.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Feature.hs index 8f8bd140d9..a07a09fdfb 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Feature.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Feature.hs @@ -26,7 +26,6 @@ import Wire.API.Conversation.Role import Wire.API.Error import Wire.API.Error.Galley import Wire.API.MakesFederatedCall -import Wire.API.OAuth import Wire.API.Routes.MultiVerb import Wire.API.Routes.Named import Wire.API.Routes.Public @@ -215,7 +214,7 @@ type AllFeatureConfigsUserGet = :> Description "Gets feature configs for a user. If the user is a member of a team and has the required permissions, this will return the team's feature configs.\ \If the user is not a member of a team, this will return the personal feature configs (the server defaults)." - :> ZOauthUser '[ 'ReadFeatureConfigs] + :> ZUser :> CanThrow 'NotATeamMember :> CanThrow OperationDenied :> CanThrow 'TeamNotFound diff --git a/services/brig/src/Brig/Run.hs b/services/brig/src/Brig/Run.hs index c691e95656..b4035c3beb 100644 --- a/services/brig/src/Brig/Run.hs +++ b/services/brig/src/Brig/Run.hs @@ -48,7 +48,6 @@ import Control.Exception.Safe (catchAny) import Control.Lens (view, (.~), (^.)) import Control.Monad.Catch (MonadCatch, finally) import Control.Monad.Random (randomRIO) -import Crypto.JWT import qualified Data.Aeson as Aeson import Data.Default (Default (def)) import Data.Id (RequestId (..)) @@ -68,7 +67,7 @@ import Network.Wai.Routing.Route (App) import Network.Wai.Utilities (lookupRequestId) import Network.Wai.Utilities.Server import qualified Network.Wai.Utilities.Server as Server -import Polysemy (Members) +import Polysemy (Member) import Servant (Context ((:.)), (:<|>) (..)) import qualified Servant import System.Logger (msg, val, (.=), (~~)) @@ -79,7 +78,6 @@ import Wire.API.Routes.API import Wire.API.Routes.Public.Brig import Wire.API.Routes.Version import Wire.API.Routes.Version.Wai -import Wire.Sem.Jwk (readJwk) import qualified Wire.Sem.Paging as P -- FUTUREWORK: If any of these async threads die, we will have no clue about it @@ -121,8 +119,7 @@ run o = do mkApp :: Opts -> IO (Wai.Application, Env) mkApp o = do e <- newEnv o - mJwk <- join <$> forM (setOAuthJwkKeyPair $ view settings e) readJwk - pure (middleware e $ \reqId -> servantApp mJwk (e & requestId .~ reqId), e) + pure (middleware e $ \reqId -> servantApp (e & requestId .~ reqId), e) where rtree :: Tree (App (Handler BrigCanonicalEffects)) rtree = compile sitemap @@ -139,14 +136,14 @@ mkApp o = do app e r k = runHandler e r (Server.route rtree r k) k -- the servant API wraps the one defined using wai-routing - servantApp :: Maybe JWK -> Env -> Wai.Application - servantApp mJwk e = + servantApp :: Env -> Wai.Application + servantApp e = let localDomain = view (settings . federationDomain) e in Servant.serveWithContext (Proxy @ServantCombinedAPI) - (mJwk :. customFormatters :. localDomain :. Servant.EmptyContext) + (customFormatters :. localDomain :. Servant.EmptyContext) ( docsAPI - :<|> hoistServerWithDomainAndJwk @BrigAPI (toServantHandler e) servantSitemap + :<|> hoistServerWithDomain @BrigAPI (toServantHandler e) servantSitemap :<|> hoistServerWithDomain @IAPI.API (toServantHandler e) IAPI.servantSitemap :<|> hoistServerWithDomain @FederationAPI (toServantHandler e) federationSitemap :<|> hoistServerWithDomain @VersionAPI (toServantHandler e) versionAPI @@ -188,7 +185,7 @@ bodyParserErrorFormatter _ _ errMsg = Servant.errHeaders = [(HTTP.hContentType, HTTPMedia.renderHeader (Servant.contentType (Proxy @Servant.JSON)))] } -pendingActivationCleanup :: forall r p. (P.Paging p, Members '[UserPendingActivationStore p] r) => AppT r () +pendingActivationCleanup :: forall r p. (P.Paging p, Member (UserPendingActivationStore p) r) => AppT r () pendingActivationCleanup = do safeForever "pendingActivationCleanup" $ do now <- liftIO =<< view currentTime diff --git a/services/brig/test/integration/API/OAuth.hs b/services/brig/test/integration/API/OAuth.hs index 5169a4ae67..d52bdd9272 100644 --- a/services/brig/test/integration/API/OAuth.hs +++ b/services/brig/test/integration/API/OAuth.hs @@ -96,9 +96,9 @@ tests m db b n o = do "accessing a resource" [ test m "success (nginz)" $ testAccessResourceSuccessNginz b n, test m "insufficient scope" $ testAccessResourceInsufficientScope b n, - test m "expired token" $ testAccessResourceExpiredToken o b n, + test m "expired token" $ testAccessResourceExpiredToken b n, test m "nonsense token" $ testAccessResourceNonsenseToken n, - test m "no token" $ testAccessResourceNoToken b, + test m "no token" $ testAccessResourceNoToken n, test m "invalid signature" $ testAccessResourceInvalidSignature o b n ], testGroup @@ -109,7 +109,7 @@ tests m db b n o = do ], testGroup "refresh tokens" $ [ test m "max active tokens" $ testRefreshTokenMaxActiveTokens o db b, - test m "refresh access token - success" $ testRefreshTokenRetrieveAccessToken o b n, + test m "refresh access token - success" $ testRefreshTokenRetrieveAccessToken b n, test m "wrong signature - fail" $ testRefreshTokenWrongSignature o b, test m "no token id - fail" $ testRefreshTokenNoTokenId o b, test m "non-existing id - fail" $ testRefreshTokenNonExistingId o b, @@ -359,19 +359,18 @@ testAccessResourceInsufficientScope brig nginz = do const 403 === statusCode const "Forbidden" === statusMessage -testAccessResourceExpiredToken :: Opt.Opts -> Brig -> Nginz -> Http () -testAccessResourceExpiredToken opts brig nginz = - withSettingsOverrides (opts & Opt.optionSettings . Opt.oauthAccessTokenExpirationTimeSecsInternal ?~ 1) $ do - uid <- userId <$> createUser "alice" brig - let redirectUrl = mkUrl "https://example.com" - let scopes = OAuthScopes $ Set.fromList [ReadSelf] - (cid, secret, code) <- generateOAuthClientAndAuthCode brig uid scopes redirectUrl - let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid secret code redirectUrl - accessToken <- createOAuthAccessToken brig accessTokenRequest - liftIO $ threadDelay (1 * 1200 * 1000) - get (nginz . paths ["self"] . authHeader (oatAccessToken accessToken)) !!! do - const 403 === statusCode - const "Access denied" === statusMessage +testAccessResourceExpiredToken :: Brig -> Nginz -> Http () +testAccessResourceExpiredToken brig nginz = do + uid <- userId <$> createUser "alice" brig + let redirectUrl = mkUrl "https://example.com" + let scopes = OAuthScopes $ Set.fromList [ReadSelf] + (cid, secret, code) <- generateOAuthClientAndAuthCode brig uid scopes redirectUrl + let accessTokenRequest = OAuthAccessTokenRequest OAuthGrantTypeAuthorizationCode cid secret code redirectUrl + accessToken <- createOAuthAccessToken brig accessTokenRequest + liftIO $ threadDelay (5 * 1000 * 1000) + get (nginz . paths ["self"] . authHeader (oatAccessToken accessToken)) !!! do + const 401 === statusCode + const "Unauthorized" === statusMessage testAccessResourceNonsenseToken :: Nginz -> Http () testAccessResourceNonsenseToken nginz = do @@ -379,11 +378,11 @@ testAccessResourceNonsenseToken nginz = do const 401 === statusCode const "Unauthorized" === statusMessage -testAccessResourceNoToken :: Brig -> Http () -testAccessResourceNoToken brig = - get (brig . paths ["self"]) !!! do - const 403 === statusCode - const "Access denied" === statusMessage +testAccessResourceNoToken :: Nginz -> Http () +testAccessResourceNoToken nginz = + get (nginz . paths ["self"]) !!! do + const 401 === statusCode + const "Unauthorized" === statusMessage testAccessResourceInvalidSignature :: Opt.Opts -> Brig -> Nginz -> Http () testAccessResourceInvalidSignature opts brig nginz = do @@ -458,8 +457,8 @@ testRefreshTokenMaxActiveTokens opts db brig = hasSameElems :: (Eq a) => [a] -> [a] -> Bool hasSameElems x y = null (x \\ y) && null (y \\ x) -testRefreshTokenRetrieveAccessToken :: Opts -> Brig -> Nginz -> Http () -testRefreshTokenRetrieveAccessToken _opts brig nginz = do +testRefreshTokenRetrieveAccessToken :: Brig -> Nginz -> Http () +testRefreshTokenRetrieveAccessToken brig nginz = do uid <- userId <$> createUser "alice" brig let redirectUrl = mkUrl "https://example.com" let scopes = OAuthScopes $ Set.fromList [ReadSelf] diff --git a/services/galley/galley.integration.yaml b/services/galley/galley.integration.yaml index 58e32a3c10..87f02e7e34 100644 --- a/services/galley/galley.integration.yaml +++ b/services/galley/galley.integration.yaml @@ -46,7 +46,6 @@ settings: mlsPrivateKeyPaths: removal: ed25519: test/resources/ed25519.pem - oauthPublicJwk: test/resources/oauth/ed25519_public.jwk featureFlags: # see #RefConfigOptions in `/docs/reference` sso: disabled-by-default diff --git a/services/galley/src/Galley/Options.hs b/services/galley/src/Galley/Options.hs index 60511985eb..844ca39064 100644 --- a/services/galley/src/Galley/Options.hs +++ b/services/galley/src/Galley/Options.hs @@ -36,7 +36,6 @@ module Galley.Options defConcurrentDeletionEvents, defDeleteConvThrottleMillis, defFanoutLimit, - setOauthPublicJwk, JournalOpts (JournalOpts), awsQueueName, awsEndpoint, @@ -117,8 +116,7 @@ data Settings = Settings _setMlsPrivateKeyPaths :: !(Maybe MLSPrivateKeyPaths), -- | FUTUREWORK: 'setFeatureFlags' should be renamed to 'setFeatureConfigs' in all types. _setFeatureFlags :: !FeatureFlags, - _setDisabledAPIVersions :: Maybe (Set Version), - _setOauthPublicJwk :: !(Maybe FilePath) + _setDisabledAPIVersions :: Maybe (Set Version) } deriving (Show, Generic) diff --git a/services/galley/src/Galley/Run.hs b/services/galley/src/Galley/Run.hs index 5978492bbc..b528a6c054 100644 --- a/services/galley/src/Galley/Run.hs +++ b/services/galley/src/Galley/Run.hs @@ -64,7 +64,6 @@ import Util.Options import Wire.API.Routes.API import qualified Wire.API.Routes.Public.Galley as GalleyAPI import Wire.API.Routes.Version.Wai -import Wire.Sem.Jwk (readJwk) run :: Opts -> IO () run opts = lowerCodensity $ do @@ -90,7 +89,7 @@ mkApp opts = metrics <- lift $ M.metrics env <- lift $ App.createEnv metrics opts lift $ runClient (env ^. cstate) $ versionCheck schemaVersion - mJwk <- lift $ join <$> forM (opts ^. optSettings . setOauthPublicJwk) readJwk + let logger = env ^. App.applog let middlewares = @@ -103,21 +102,20 @@ mkApp opts = Log.info logger $ Log.msg @Text "Galley application finished." Log.flush logger Log.close logger - pure (middlewares $ servantApp env mJwk, env) + pure (middlewares $ servantApp env, env) where rtree = compile API.sitemap runGalley e r k = evalGalleyToIO e (route rtree r k) -- the servant API wraps the one defined using wai-routing - servantApp e0 mJwk r = + servantApp e0 r = let e = reqId .~ lookupReqId r $ e0 in Servant.serveWithContext (Proxy @CombinedAPI) - ( mJwk - :. view (options . optSettings . setFederationDomain) e + ( view (options . optSettings . setFederationDomain) e :. customFormatters :. Servant.EmptyContext ) - ( hoistAPIHandler @GalleyAPI.ServantAPI (toServantHandler e) API.servantSitemap + ( hoistAPIHandler (toServantHandler e) API.servantSitemap :<|> hoistAPIHandler (toServantHandler e) internalAPI :<|> hoistServerWithDomain @FederationAPI (toServantHandler e) federationSitemap :<|> Servant.Tagged (runGalley e) diff --git a/services/galley/test/resources/oauth/ed25519_public.jwk b/services/galley/test/resources/oauth/ed25519_public.jwk deleted file mode 100644 index 9ef33c1ce2..0000000000 --- a/services/galley/test/resources/oauth/ed25519_public.jwk +++ /dev/null @@ -1 +0,0 @@ -{"kty":"OKP","crv":"Ed25519","x":"mhP-NgFw3ifIXGZqJVB0kemt9L3BtD5P8q4Gah4Iklc"} diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index a9e2537e11..57834711d9 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -318,11 +318,14 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { // let's try to handle zauth ngx_int_t status = zauth_handle_zauth_request(r, sc); + ngx_str_t scope = lc->oauth_scope; + // if the status us forbidden, we do not want to handle oauth + if (status == NGX_HTTP_FORBIDDEN) { + return status; // if zauth fails, we try to handle oauth - if (status != NGX_OK && sc->oauth_key != NULL) { + } else if (status != NGX_OK && sc->oauth_key != NULL && scope.len > 0 && r->headers_in.authorization != NULL) { ngx_str_t *hdr = &r->headers_in.authorization->value; if (strncmp((char const *) hdr->data, "Bearer ", 7) == 0) { - ngx_str_t scope = lc->oauth_scope; OAuthResult res = oauth_verify_token(sc->oauth_key, &hdr->data[7], hdr->len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); From 4961fefe1d8dcdb71529dfb999316649b081de89 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Wed, 22 Feb 2023 09:54:55 +0000 Subject: [PATCH 21/32] clean up --- charts/nginz/templates/conf/_nginx.conf.tpl | 4 + .../nginz/common_response_with_zauth.conf | 1 + .../nginx-zauth-module/zauth_module.c | 78 +++++++------------ 3 files changed, 33 insertions(+), 50 deletions(-) diff --git a/charts/nginz/templates/conf/_nginx.conf.tpl b/charts/nginz/templates/conf/_nginx.conf.tpl index 2c5dcd2734..4f7b243414 100644 --- a/charts/nginz/templates/conf/_nginx.conf.tpl +++ b/charts/nginz/templates/conf/_nginx.conf.tpl @@ -294,6 +294,10 @@ http { proxy_set_header Connection ""; {{ end -}} + {{- if not ($location.disable_zauth) }} + proxy_set_header Authorization ""; + {{- end }} + proxy_set_header Z-Type $zauth_type; proxy_set_header Z-User $zauth_user; proxy_set_header Z-Client $zauth_client; diff --git a/services/nginz/integration-test/conf/nginz/common_response_with_zauth.conf b/services/nginz/integration-test/conf/nginz/common_response_with_zauth.conf index ebb5d1d467..97bfb043d8 100644 --- a/services/nginz/integration-test/conf/nginz/common_response_with_zauth.conf +++ b/services/nginz/integration-test/conf/nginz/common_response_with_zauth.conf @@ -1 +1,2 @@ include common_response.conf; + proxy_set_header Authorization ""; diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index 57834711d9..0c57ffd760 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -49,9 +49,9 @@ static ngx_int_t zauth_set_var (ngx_pool_t *, ngx_http_variable_value_t * static void zauth_empty_val (ngx_http_variable_value_t *); // Utility functions -static ngx_int_t zauth_handle_zauth_request (ngx_http_request_t *, const ZauthServerConf *); -static ngx_int_t empty_authorization_header_in_headers_in(ngx_http_request_t *); +static ngx_int_t zauth_handle_request (ngx_http_request_t *, const ZauthServerConf *); static bool zauth_is_authorized_and_allowed(ngx_http_request_t *); +static ngx_int_t oauth_handle_request(ngx_http_request_t *, ngx_str_t const *, OAuthJwk const *, ngx_str_t const); static ngx_http_module_t zauth_module_ctx = { zauth_variables // pre-configuration @@ -315,61 +315,39 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { return NGX_DECLINED; } - // let's try to handle zauth - ngx_int_t status = zauth_handle_zauth_request(r, sc); - - ngx_str_t scope = lc->oauth_scope; - // if the status us forbidden, we do not want to handle oauth - if (status == NGX_HTTP_FORBIDDEN) { - return status; - // if zauth fails, we try to handle oauth - } else if (status != NGX_OK && sc->oauth_key != NULL && scope.len > 0 && r->headers_in.authorization != NULL) { - ngx_str_t *hdr = &r->headers_in.authorization->value; - if (strncmp((char const *) hdr->data, "Bearer ", 7) == 0) { - - OAuthResult res = oauth_verify_token(sc->oauth_key, &hdr->data[7], hdr->len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); - - if (res.status == OAUTH_OK) { - ngx_pool_cleanup_t * finaliser = ngx_pool_cleanup_add(r->pool, 0); - if (finaliser == NULL) { - return NGX_ERROR; - } - finaliser->handler = delete_oauth_uid; - finaliser->data = res.uid; - ngx_http_set_ctx(r, res.uid, zauth_module); - status = NGX_OK; - } else if (res.status == OAUTH_INSUFFICIENT_SCOPE) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth insufficient cope"); - return NGX_HTTP_FORBIDDEN; - } else { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth token verification failed with: %d", res.status); - return NGX_HTTP_UNAUTHORIZED; - } - } - } + ngx_int_t status = zauth_handle_request(r, sc); - // if zauth or oauth succeeds, we empty the Authorization header - if (status == NGX_OK) { - return empty_authorization_header_in_headers_in(r); - } - // in all other cases (which should only be errors) we return the status - else { - return status; + if (status == NGX_HTTP_UNAUTHORIZED && sc->oauth_key != NULL && lc->oauth_scope.len > 0 && r->headers_in.authorization != NULL) { + status = oauth_handle_request(r, &r->headers_in.authorization->value, sc->oauth_key, lc->oauth_scope); } + return status; } -ngx_int_t empty_authorization_header_in_headers_in(ngx_http_request_t *r) { - ngx_table_elt_t * h = r->headers_in.authorization; - if (h == NULL) { - return NGX_OK; +ngx_int_t oauth_handle_request(ngx_http_request_t *r, ngx_str_t const * hdr, OAuthJwk const * key, ngx_str_t const scope) { + if (strncmp((char const *) hdr->data, "Bearer ", 7) == 0) { + OAuthResult res = oauth_verify_token(key, &hdr->data[7], hdr->len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); + if (res.status == OAUTH_OK) { + ngx_pool_cleanup_t * finaliser = ngx_pool_cleanup_add(r->pool, 0); + if (finaliser == NULL) { + return NGX_ERROR; + } + finaliser->handler = delete_oauth_uid; + finaliser->data = res.uid; + ngx_http_set_ctx(r, res.uid, zauth_module); + return NGX_OK; + } else if (res.status == OAUTH_INSUFFICIENT_SCOPE) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth insufficient cope"); + return NGX_HTTP_FORBIDDEN; + } else { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth token verification failed with: %d", res.status); + return NGX_HTTP_UNAUTHORIZED; + } + } else { + return NGX_HTTP_UNAUTHORIZED; } - ngx_str_t value = ngx_string(""); - h->value = value; - h->hash = 1; - return NGX_OK; } -static ngx_int_t zauth_handle_zauth_request (ngx_http_request_t * r, const ZauthServerConf * sc) { +static ngx_int_t zauth_handle_request (ngx_http_request_t * r, const ZauthServerConf * sc) { ZauthToken const * tkn = ngx_http_get_module_ctx(r, zauth_module); // internal redirects clear module contexts => try to parse again From ec955fb2e297025db0aa5653079097d0751f1fa5 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Wed, 22 Feb 2023 10:23:02 +0000 Subject: [PATCH 22/32] commt --- .../third_party/nginx-zauth-module/zauth_module.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index 0c57ffd760..18a7763beb 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -549,8 +549,8 @@ static ngx_int_t zauth_token_typeinfo (ngx_http_request_t * r, ngx_http_variable } } -// check if the signature has been validated -// and access is allowed (endpoint is either allowed or not denied according to the access control list (ACL) configuration) +// this function checks if the signature has been validated successfully, +// and if access is allowed (endpoint is either allowed or not denied) according to the access control list (ACL) configuration static bool zauth_is_authorized_and_allowed(ngx_http_request_t * r) { ZauthToken const * t = ngx_http_get_module_ctx(r, zauth_module); @@ -586,11 +586,12 @@ static ngx_int_t zauth_token_var (ngx_http_request_t * r, ngx_http_variable_valu return NGX_ERROR; } - // in this function the user, client, provider, and bot ID is retrieved from the token - // and assigned to variables that are used in the nginx config to set the headers. + // in this function client, provider, and bot ID is retrieved from the ZAuth token + // and assigned to variables that are used in the nginx config to set the corresponding headers (e.g. Z-Client, Z-Provider, ...). // therefore we want to make sure that the token is authorized (has a valid signature) - // and access is allowed (endpoint is either allowed or not denied according to the access control list configuration) - // before we set these variables. + // and access is allowed (endpoint is either allowed or not denied) according to the access control list (ACL) configuration + // before we set the variable + // otherwise 'zauth_token_lookup' will crash for OAuth requests if (!zauth_is_authorized_and_allowed(r)) { zauth_empty_val(v); return NGX_OK; From 367e905a8dbaaee9efb0372c1e056cd315b06522 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Wed, 22 Feb 2023 10:39:43 +0000 Subject: [PATCH 23/32] set oauth_scope in template correctly --- charts/nginz/templates/conf/_nginx.conf.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/nginz/templates/conf/_nginx.conf.tpl b/charts/nginz/templates/conf/_nginx.conf.tpl index 4f7b243414..7fe5c66038 100644 --- a/charts/nginz/templates/conf/_nginx.conf.tpl +++ b/charts/nginz/templates/conf/_nginx.conf.tpl @@ -260,7 +260,7 @@ http { {{- end }} {{- if ($location.oauth_scope) }} - oauth_scope $oauth_scope; + oauth_scope {{ $location.oauth_scope }};; {{- end }} {{- if hasKey $location "specific_user_rate_limit" }} From 4bc19762197bb445813caaa441f8d85f4a19803d Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Wed, 22 Feb 2023 11:12:46 +0000 Subject: [PATCH 24/32] script --- hack/bin/oauth_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/bin/oauth_test.sh b/hack/bin/oauth_test.sh index 56ce721149..bc04a780fc 100755 --- a/hack/bin/oauth_test.sh +++ b/hack/bin/oauth_test.sh @@ -79,4 +79,4 @@ echo "access token : $ACCESS_TOKEN" echo "" echo "making a request to /self..." -curl -s -i -H 'Authorization: Bearer '"$ACCESS_TOKEN" -H "Content-Type: application/json" localhost:8080/self +curl -s -H 'Authorization: Bearer '"$ACCESS_TOKEN" -H "Content-Type: application/json" localhost:8080/self | jq From 90cd5f1a7a6ad5c5fc91526d684f56a5ee3daafd Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Wed, 22 Feb 2023 14:24:23 +0100 Subject: [PATCH 25/32] Update services/nginz/third_party/nginx-zauth-module/zauth_module.c Co-authored-by: Paolo Capriotti --- services/nginz/third_party/nginx-zauth-module/zauth_module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index 18a7763beb..3edd7699f2 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -336,7 +336,7 @@ ngx_int_t oauth_handle_request(ngx_http_request_t *r, ngx_str_t const * hdr, OAu ngx_http_set_ctx(r, res.uid, zauth_module); return NGX_OK; } else if (res.status == OAUTH_INSUFFICIENT_SCOPE) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth insufficient cope"); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth insufficient scope"); return NGX_HTTP_FORBIDDEN; } else { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth token verification failed with: %d", res.status); From e26a03e7bcfe7bd69cff319f34e368b0ed2cfff3 Mon Sep 17 00:00:00 2001 From: Paolo Capriotti Date: Wed, 22 Feb 2023 15:30:24 +0100 Subject: [PATCH 26/32] WIP --- .../nginx-zauth-module/zauth_module.c | 83 +++++++++++++++---- 1 file changed, 67 insertions(+), 16 deletions(-) diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index 3edd7699f2..c799a74663 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -17,6 +17,22 @@ typedef struct { ngx_str_t oauth_scope; } ZauthLocationConf; +enum { + ZAUTH_CONTEXT_NONE = 0, + ZAUTH_CONTEXT_ZAUTH, + ZAUTH_CONTEXT_OAUTH +}; + +typedef struct { + ngx_int_t tag; + union { + /* valid if tag == ZAUTH_CONTEXT_ZAUTH */ + ZauthToken * token; + /* valid if tag == ZAUTH_CONTEXT_OAUTH */ + char const * user_id; + }; +} ZauthContext; + // Configuration setup static void * create_srv_conf (ngx_conf_t *); static void * create_loc_conf (ngx_conf_t *); @@ -327,13 +343,13 @@ ngx_int_t oauth_handle_request(ngx_http_request_t *r, ngx_str_t const * hdr, OAu if (strncmp((char const *) hdr->data, "Bearer ", 7) == 0) { OAuthResult res = oauth_verify_token(key, &hdr->data[7], hdr->len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); if (res.status == OAUTH_OK) { - ngx_pool_cleanup_t * finaliser = ngx_pool_cleanup_add(r->pool, 0); - if (finaliser == NULL) { - return NGX_ERROR; + ZauthContext * ctx = alloc_oauth_context(r, res.uid); + ngx_int_t e = setup_zauth_context(r, ctx); + if (e != NGX_OK) { + ngx_free(ctx); + return e; } - finaliser->handler = delete_oauth_uid; - finaliser->data = res.uid; - ngx_http_set_ctx(r, res.uid, zauth_module); + return NGX_OK; } else if (res.status == OAUTH_INSUFFICIENT_SCOPE) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth insufficient scope"); @@ -382,12 +398,47 @@ static ngx_int_t zauth_handle_request (ngx_http_request_t * r, const ZauthServer return NGX_OK; } -static void delete_token (void * data) { - zauth_token_delete((ZauthToken *) data); +static ngx_int_t setup_zauth_context(ngx_http_request_t * r, ZauthContext * ctx) { + ngx_pool_cleanup_t * finaliser = ngx_pool_cleanup_add(r->pool, 0); + if (finaliser == NULL) { + return NGX_ERROR; + } + + finaliser->handler = delete_zauth_context; + finaliser->data = ctx; + ngx_http_set_ctx(r, ctx, zauth_module); + + return NGX_OK; +} + +static ZauthContext alloc_zauth_context(ngx_http_request_t * r, ZauthToken * token) { + ZauthContext * ctx = ngx_alloc(sizeof(ZauthContext), r->connection->log); + if (ctx == NULL) { + return NGX_ERROR; + } + ctx->tag = ZAUTH_CONTEXT_ZAUTH; + ctx->user_id = token; +} + + +static ZauthContext alloc_oauth_context(ngx_http_request_t * r, char const * user_id) { + ZauthContext * ctx = ngx_alloc(sizeof(ZauthContext), r->connection->log); + if (ctx == NULL) { + return NGX_ERROR; + } + ctx->tag = ZAUTH_CONTEXT_OAUTH; + ctx->user_id = user_id; } -static void delete_oauth_uid(void * data) { - oauth_result_uid_delete((char *) data); +static void delete_zauth_context(void * data) { + ZauthContext *ctx = data; + if (ctx->tag == ZAUTH_CONTEXT_ZAUTH) { + zauth_token_delete(ctx->token); + } + else if (ctx->tag == ZAUTH_CONTEXT_OAUTH) { + oauth_result_uid_delete(ctx->user_id); + } + ngx_free(ctx); } static ngx_int_t zauth_parse_request (ngx_http_request_t * r) { @@ -417,13 +468,13 @@ static ngx_int_t zauth_parse_request (ngx_http_request_t * r) { } if (res == ZAUTH_OK && tkn != NULL) { - ngx_pool_cleanup_t * finaliser = ngx_pool_cleanup_add(r->pool, 0); - if (finaliser == NULL) { - return NGX_ERROR; + ZauthContext * ctx = alloc_zauth_context(r, tkn); + + ngx_int_t e = setup_zauth_context(r); + if (e != NGX_OK) { + ngx_free(ctx); + return e; } - finaliser->handler = delete_token; - finaliser->data = tkn; - ngx_http_set_ctx(r, tkn, zauth_module); return NGX_OK; } From fc92f75876bf42b9507cb36e430b6a244ce4ecc3 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Wed, 22 Feb 2023 15:54:08 +0000 Subject: [PATCH 27/32] clean up --- .../nginx-zauth-module/zauth_module.c | 178 +++++++++--------- 1 file changed, 94 insertions(+), 84 deletions(-) diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index c799a74663..34ff91ec2e 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -26,10 +26,10 @@ enum { typedef struct { ngx_int_t tag; union { - /* valid if tag == ZAUTH_CONTEXT_ZAUTH */ + // valid if tag == ZAUTH_CONTEXT_ZAUTH ZauthToken * token; - /* valid if tag == ZAUTH_CONTEXT_OAUTH */ - char const * user_id; + // valid if tag == ZAUTH_CONTEXT_OAUTH + char * user_id; }; } ZauthContext; @@ -44,15 +44,17 @@ static char * load_oauth_key (ngx_conf_t *, ngx_command_t *, void *); static void delete_srv_conf (void *); // Module setup -static ngx_int_t zauth_init (ngx_conf_t *); -static ngx_int_t zauth_parse_request (ngx_http_request_t *); +static ngx_int_t zauth_init (ngx_conf_t *); +static ngx_int_t zauth_parse_request (ngx_http_request_t *); static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t *); // Request Inspection -static ZauthResult token_from_header (ngx_str_t const *, ZauthToken **); -static ZauthResult token_from_query (ngx_str_t const *, ZauthToken **); -static void delete_token (void *); -static void delete_oauth_uid (void *); +static ZauthResult token_from_header (ngx_str_t const *, ZauthToken **); +static ZauthResult token_from_query (ngx_str_t const *, ZauthToken **); +static ZauthContext * alloc_zauth_context (ngx_http_request_t * r, ZauthToken *); +static ZauthContext * alloc_oauth_context (ngx_http_request_t * r, char *); +static ngx_int_t setup_zauth_context (ngx_http_request_t * , ZauthContext *); +static void delete_zauth_context (void *); // Variable manipulation static ngx_int_t zauth_variables (ngx_conf_t *); @@ -65,9 +67,9 @@ static ngx_int_t zauth_set_var (ngx_pool_t *, ngx_http_variable_value_t * static void zauth_empty_val (ngx_http_variable_value_t *); // Utility functions -static ngx_int_t zauth_handle_request (ngx_http_request_t *, const ZauthServerConf *); -static bool zauth_is_authorized_and_allowed(ngx_http_request_t *); -static ngx_int_t oauth_handle_request(ngx_http_request_t *, ngx_str_t const *, OAuthJwk const *, ngx_str_t const); +static ngx_int_t zauth_handle_request (ngx_http_request_t *, const ZauthServerConf *, ZauthToken const *); +static bool zauth_is_authorized_and_allowed(ngx_http_request_t *); +static ngx_int_t oauth_handle_request(ngx_http_request_t *, OAuthJwk const *, ngx_str_t const); static ngx_http_module_t zauth_module_ctx = { zauth_variables // pre-configuration @@ -331,51 +333,26 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { return NGX_DECLINED; } - ngx_int_t status = zauth_handle_request(r, sc); - - if (status == NGX_HTTP_UNAUTHORIZED && sc->oauth_key != NULL && lc->oauth_scope.len > 0 && r->headers_in.authorization != NULL) { - status = oauth_handle_request(r, &r->headers_in.authorization->value, sc->oauth_key, lc->oauth_scope); - } - return status; -} - -ngx_int_t oauth_handle_request(ngx_http_request_t *r, ngx_str_t const * hdr, OAuthJwk const * key, ngx_str_t const scope) { - if (strncmp((char const *) hdr->data, "Bearer ", 7) == 0) { - OAuthResult res = oauth_verify_token(key, &hdr->data[7], hdr->len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); - if (res.status == OAUTH_OK) { - ZauthContext * ctx = alloc_oauth_context(r, res.uid); - ngx_int_t e = setup_zauth_context(r, ctx); - if (e != NGX_OK) { - ngx_free(ctx); - return e; - } - - return NGX_OK; - } else if (res.status == OAUTH_INSUFFICIENT_SCOPE) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth insufficient scope"); - return NGX_HTTP_FORBIDDEN; - } else { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth token verification failed with: %d", res.status); - return NGX_HTTP_UNAUTHORIZED; - } - } else { - return NGX_HTTP_UNAUTHORIZED; - } -} - -static ngx_int_t zauth_handle_request (ngx_http_request_t * r, const ZauthServerConf * sc) { - ZauthToken const * tkn = ngx_http_get_module_ctx(r, zauth_module); + ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); // internal redirects clear module contexts => try to parse again - if (tkn == NULL && r->internal) { + if (ctx == NULL && r->internal) { ngx_int_t status = zauth_parse_request(r); if (status != NGX_OK) { return status; } else { - tkn = ngx_http_get_module_ctx(r, zauth_module); + ctx = ngx_http_get_module_ctx(r, zauth_module); } } + if (ctx->tag == ZAUTH_CONTEXT_ZAUTH) { + return zauth_handle_request(r, sc, ctx->token); + } else { + return oauth_handle_request(r, sc->oauth_key, lc->oauth_scope); + } +} + +static ngx_int_t zauth_handle_request (ngx_http_request_t * r, const ZauthServerConf * sc, ZauthToken const * tkn) { if (tkn == NULL) { return NGX_HTTP_UNAUTHORIZED; } @@ -398,6 +375,34 @@ static ngx_int_t zauth_handle_request (ngx_http_request_t * r, const ZauthServer return NGX_OK; } +ngx_int_t oauth_handle_request(ngx_http_request_t *r, OAuthJwk const * key, ngx_str_t const scope) { + ngx_str_t hdr = ngx_null_string; + if (r->headers_in.authorization != NULL) { + hdr = r->headers_in.authorization->value; + } + if (strncmp((char const *) hdr.data, "Bearer ", 7) == 0) { + OAuthResult res = oauth_verify_token(key, &hdr.data[7], hdr.len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); + if (res.status == OAUTH_OK) { + ZauthContext * ctx = alloc_oauth_context(r, res.uid); + ngx_int_t e = setup_zauth_context(r, ctx); + if (e != NGX_OK) { + ngx_free(ctx); + return e; + } + + return NGX_OK; + } else if (res.status == OAUTH_INSUFFICIENT_SCOPE) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth insufficient scope"); + return NGX_HTTP_FORBIDDEN; + } else { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth token verification failed with: %d", res.status); + return NGX_HTTP_UNAUTHORIZED; + } + } else { + return NGX_HTTP_UNAUTHORIZED; + } +} + static ngx_int_t setup_zauth_context(ngx_http_request_t * r, ZauthContext * ctx) { ngx_pool_cleanup_t * finaliser = ngx_pool_cleanup_add(r->pool, 0); if (finaliser == NULL) { @@ -411,23 +416,25 @@ static ngx_int_t setup_zauth_context(ngx_http_request_t * r, ZauthContext * ctx) return NGX_OK; } -static ZauthContext alloc_zauth_context(ngx_http_request_t * r, ZauthToken * token) { +static ZauthContext * alloc_zauth_context(ngx_http_request_t * r, ZauthToken * token) { ZauthContext * ctx = ngx_alloc(sizeof(ZauthContext), r->connection->log); if (ctx == NULL) { - return NGX_ERROR; + return ctx; } ctx->tag = ZAUTH_CONTEXT_ZAUTH; - ctx->user_id = token; + ctx->token = token; + return ctx; } -static ZauthContext alloc_oauth_context(ngx_http_request_t * r, char const * user_id) { +static ZauthContext * alloc_oauth_context(ngx_http_request_t * r, char * user_id) { ZauthContext * ctx = ngx_alloc(sizeof(ZauthContext), r->connection->log); if (ctx == NULL) { - return NGX_ERROR; + return ctx; } ctx->tag = ZAUTH_CONTEXT_OAUTH; ctx->user_id = user_id; + return ctx; } static void delete_zauth_context(void * data) { @@ -470,7 +477,7 @@ static ngx_int_t zauth_parse_request (ngx_http_request_t * r) { if (res == ZAUTH_OK && tkn != NULL) { ZauthContext * ctx = alloc_zauth_context(r, tkn); - ngx_int_t e = setup_zauth_context(r); + ngx_int_t e = setup_zauth_context(r, ctx); if (e != NGX_OK) { ngx_free(ctx); return e; @@ -564,11 +571,14 @@ static ngx_int_t zauth_variables (ngx_conf_t * conf) { } static ngx_int_t zauth_token_typeinfo (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t _) { - ZauthToken const * t = ngx_http_get_module_ctx(r, zauth_module); - if (t == NULL) { + ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); + if (ctx == NULL) { return NGX_ERROR; } - switch (zauth_token_type(t)) { + if (ctx->tag != ZAUTH_CONTEXT_ZAUTH) { + return NGX_OK; + } + switch (zauth_token_type(ctx->token)) { case ZAUTH_TOKEN_TYPE_BOT: { Range range = { (u_char*) "bot", 3 }; return zauth_set_var(r->pool, v, range); @@ -603,7 +613,12 @@ static ngx_int_t zauth_token_typeinfo (ngx_http_request_t * r, ngx_http_variable // this function checks if the signature has been validated successfully, // and if access is allowed (endpoint is either allowed or not denied) according to the access control list (ACL) configuration static bool zauth_is_authorized_and_allowed(ngx_http_request_t * r) { - ZauthToken const * t = ngx_http_get_module_ctx(r, zauth_module); + ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); + if (ctx == NULL || ctx->tag != ZAUTH_CONTEXT_ZAUTH) { + return false; + } + + ZauthToken const * t = ctx->token; if (t == NULL) { return false; @@ -632,8 +647,8 @@ static bool zauth_is_authorized_and_allowed(ngx_http_request_t * r) { } static ngx_int_t zauth_token_var (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t data) { - ZauthToken const * t = ngx_http_get_module_ctx(r, zauth_module); - if (t == NULL) { + ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); + if (ctx == NULL) { return NGX_ERROR; } @@ -643,37 +658,33 @@ static ngx_int_t zauth_token_var (ngx_http_request_t * r, ngx_http_variable_valu // and access is allowed (endpoint is either allowed or not denied) according to the access control list (ACL) configuration // before we set the variable // otherwise 'zauth_token_lookup' will crash for OAuth requests - if (!zauth_is_authorized_and_allowed(r)) { + if (ctx->tag != ZAUTH_CONTEXT_ZAUTH || !zauth_is_authorized_and_allowed(r)) { zauth_empty_val(v); return NGX_OK; } - - return zauth_set_var(r->pool, v, zauth_token_lookup(t, data)); + return zauth_set_var(r->pool, v, zauth_token_lookup(ctx->token, data)); } static ngx_int_t zauth_token_var_user (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t _) { - ZauthToken const * t = ngx_http_get_module_ctx(r, zauth_module); - if (t != NULL && zauth_is_authorized_and_allowed(r)) { - return zauth_set_var(r->pool, v, zauth_token_lookup(t, 'u')); + ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); + if (ctx == NULL) { + return NGX_ERROR; + } else if (ctx->tag == ZAUTH_CONTEXT_ZAUTH) { + return zauth_set_var(r->pool, v, zauth_token_lookup(ctx->token, 'u')); + } else if (ctx->tag == ZAUTH_CONTEXT_OAUTH) { + return zauth_set_var(r->pool, v, (Range) { (u_char*) ctx->user_id, strlen(ctx->user_id) }); } else { - char const * uid = ngx_http_get_module_ctx(r, zauth_module); - if (uid != NULL) { - ngx_int_t status = zauth_set_var(r->pool, v, (Range) { (u_char*) uid, strlen(uid) }); - return status; - } else { - zauth_empty_val(v); - return NGX_OK; - } + zauth_empty_val(v); + return NGX_OK; } } static ngx_int_t zauth_token_var_conn (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t _) { - ZauthToken const * t = ngx_http_get_module_ctx(r, zauth_module); - if (t == NULL) { + ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); + if (ctx == NULL) { return NGX_ERROR; - } - if (zauth_token_type(t) == ZAUTH_TOKEN_TYPE_ACCESS || zauth_token_type(t) == ZAUTH_TOKEN_TYPE_LEGAL_HOLD_ACCESS) { - return zauth_set_var(r->pool, v, zauth_token_lookup(t, 'c')); + } else if (ctx->tag == ZAUTH_CONTEXT_ZAUTH && (zauth_token_type(ctx->token) == ZAUTH_TOKEN_TYPE_ACCESS || zauth_token_type(ctx->token) == ZAUTH_TOKEN_TYPE_LEGAL_HOLD_ACCESS)) { + return zauth_set_var(r->pool, v, zauth_token_lookup(ctx->token, 'c')); } else { zauth_empty_val(v); return NGX_OK; @@ -681,12 +692,11 @@ static ngx_int_t zauth_token_var_conn (ngx_http_request_t * r, ngx_http_variable } static ngx_int_t zauth_token_var_conv (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t _) { - ZauthToken const * t = ngx_http_get_module_ctx(r, zauth_module); - if (t == NULL) { + ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); + if (ctx == NULL) { return NGX_ERROR; - } - if (zauth_token_type(t) == ZAUTH_TOKEN_TYPE_BOT) { - return zauth_set_var(r->pool, v, zauth_token_lookup(t, 'c')); + } else if (ctx->tag == ZAUTH_CONTEXT_ZAUTH && zauth_token_type(ctx->token) == ZAUTH_TOKEN_TYPE_BOT) { + return zauth_set_var(r->pool, v, zauth_token_lookup(ctx->token, 'c')); } else { zauth_empty_val(v); return NGX_OK; From 296b30afa0fd47dd46e95f8ac8cad24e73a761ef Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Thu, 23 Feb 2023 11:38:33 +0000 Subject: [PATCH 28/32] zauth module clean up --- .../nginx-zauth-module/zauth_module.c | 122 +++++++++--------- 1 file changed, 58 insertions(+), 64 deletions(-) diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index 34ff91ec2e..c1f6475446 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -18,17 +18,17 @@ typedef struct { } ZauthLocationConf; enum { - ZAUTH_CONTEXT_NONE = 0, - ZAUTH_CONTEXT_ZAUTH, - ZAUTH_CONTEXT_OAUTH + CONTEXT_NONE = 0, + CONTEXT_ZAUTH, + CONTEXT_OAUTH }; typedef struct { ngx_int_t tag; union { - // valid if tag == ZAUTH_CONTEXT_ZAUTH + // valid if tag == CONTEXT_ZAUTH ZauthToken * token; - // valid if tag == ZAUTH_CONTEXT_OAUTH + // valid if tag == CONTEXT_OAUTH char * user_id; }; } ZauthContext; @@ -68,7 +68,6 @@ static void zauth_empty_val (ngx_http_variable_value_t *); // Utility functions static ngx_int_t zauth_handle_request (ngx_http_request_t *, const ZauthServerConf *, ZauthToken const *); -static bool zauth_is_authorized_and_allowed(ngx_http_request_t *); static ngx_int_t oauth_handle_request(ngx_http_request_t *, OAuthJwk const *, ngx_str_t const); static ngx_http_module_t zauth_module_ctx = { @@ -345,10 +344,12 @@ static ngx_int_t zauth_and_oauth_handle_request (ngx_http_request_t * r) { } } - if (ctx->tag == ZAUTH_CONTEXT_ZAUTH) { + if (ctx != NULL && ctx->tag == CONTEXT_ZAUTH) { return zauth_handle_request(r, sc, ctx->token); - } else { + } else if (ctx == NULL) { return oauth_handle_request(r, sc->oauth_key, lc->oauth_scope); + } else { + return NGX_HTTP_UNAUTHORIZED; } } @@ -376,10 +377,12 @@ static ngx_int_t zauth_handle_request (ngx_http_request_t * r, const ZauthServer } ngx_int_t oauth_handle_request(ngx_http_request_t *r, OAuthJwk const * key, ngx_str_t const scope) { - ngx_str_t hdr = ngx_null_string; - if (r->headers_in.authorization != NULL) { - hdr = r->headers_in.authorization->value; + if (r->headers_in.authorization == NULL) { + return NGX_HTTP_UNAUTHORIZED; } + + ngx_str_t hdr = r->headers_in.authorization->value; + if (strncmp((char const *) hdr.data, "Bearer ", 7) == 0) { OAuthResult res = oauth_verify_token(key, &hdr.data[7], hdr.len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); if (res.status == OAUTH_OK) { @@ -392,10 +395,10 @@ ngx_int_t oauth_handle_request(ngx_http_request_t *r, OAuthJwk const * key, ngx_ return NGX_OK; } else if (res.status == OAUTH_INSUFFICIENT_SCOPE) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth insufficient scope"); + ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, "OAuth insufficient scope"); return NGX_HTTP_FORBIDDEN; } else { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OAuth token verification failed with: %d", res.status); + ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, "OAuth token verification failed with: %d", res.status); return NGX_HTTP_UNAUTHORIZED; } } else { @@ -421,7 +424,7 @@ static ZauthContext * alloc_zauth_context(ngx_http_request_t * r, ZauthToken * t if (ctx == NULL) { return ctx; } - ctx->tag = ZAUTH_CONTEXT_ZAUTH; + ctx->tag = CONTEXT_ZAUTH; ctx->token = token; return ctx; } @@ -432,17 +435,17 @@ static ZauthContext * alloc_oauth_context(ngx_http_request_t * r, char * user_id if (ctx == NULL) { return ctx; } - ctx->tag = ZAUTH_CONTEXT_OAUTH; + ctx->tag = CONTEXT_OAUTH; ctx->user_id = user_id; return ctx; } static void delete_zauth_context(void * data) { ZauthContext *ctx = data; - if (ctx->tag == ZAUTH_CONTEXT_ZAUTH) { + if (ctx->tag == CONTEXT_ZAUTH) { zauth_token_delete(ctx->token); } - else if (ctx->tag == ZAUTH_CONTEXT_OAUTH) { + else if (ctx->tag == CONTEXT_OAUTH) { oauth_result_uid_delete(ctx->user_id); } ngx_free(ctx); @@ -476,7 +479,6 @@ static ngx_int_t zauth_parse_request (ngx_http_request_t * r) { if (res == ZAUTH_OK && tkn != NULL) { ZauthContext * ctx = alloc_zauth_context(r, tkn); - ngx_int_t e = setup_zauth_context(r, ctx); if (e != NGX_OK) { ngx_free(ctx); @@ -485,6 +487,7 @@ static ngx_int_t zauth_parse_request (ngx_http_request_t * r) { return NGX_OK; } + if (res != ZAUTH_OK) { ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, "failed to parse token [%d]", res); } @@ -575,7 +578,7 @@ static ngx_int_t zauth_token_typeinfo (ngx_http_request_t * r, ngx_http_variable if (ctx == NULL) { return NGX_ERROR; } - if (ctx->tag != ZAUTH_CONTEXT_ZAUTH) { + if (ctx->tag != CONTEXT_ZAUTH) { return NGX_OK; } switch (zauth_token_type(ctx->token)) { @@ -610,47 +613,43 @@ static ngx_int_t zauth_token_typeinfo (ngx_http_request_t * r, ngx_http_variable } } -// this function checks if the signature has been validated successfully, -// and if access is allowed (endpoint is either allowed or not denied) according to the access control list (ACL) configuration -static bool zauth_is_authorized_and_allowed(ngx_http_request_t * r) { +static ngx_int_t zauth_token_var (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t data) { ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); - if (ctx == NULL || ctx->tag != ZAUTH_CONTEXT_ZAUTH) { - return false; - } - ZauthToken const * t = ctx->token; + // this function checks if the signature has been validated successfully, + // and if access is allowed (endpoint is either allowed or not denied) according to the access control list (ACL) configuration + bool zauth_is_authorized_and_allowed() { + if (ctx == NULL || ctx->tag != CONTEXT_ZAUTH) { + return false; + } - if (t == NULL) { - return false; - } - - if (zauth_token_verification(t) != ZAUTH_TOKEN_VERIFICATION_SUCCESS) { - return false; - } + ZauthToken const * t = ctx->token; - ZauthServerConf const * sc = - ngx_http_get_module_srv_conf(r, zauth_module); + if (t == NULL) { + return false; + } + + if (zauth_token_verification(t) != ZAUTH_TOKEN_VERIFICATION_SUCCESS) { + return false; + } - if (sc == NULL || sc->acl == NULL) { - return false; - } + ZauthServerConf const * sc = + ngx_http_get_module_srv_conf(r, zauth_module); - uint8_t is_allowed = 0; - - ngx_int_t res = zauth_token_allowed(t, sc->acl, r->uri.data, r->uri.len, &is_allowed); - - if (res != NGX_OK) { - return false; - } + if (sc == NULL || sc->acl == NULL) { + return false; + } - return is_allowed == 1; -} + uint8_t is_allowed = 0; + + ngx_int_t res = zauth_token_allowed(t, sc->acl, r->uri.data, r->uri.len, &is_allowed); + + if (res != NGX_OK) { + return false; + } -static ngx_int_t zauth_token_var (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t data) { - ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); - if (ctx == NULL) { - return NGX_ERROR; - } + return is_allowed == 1; + } // in this function client, provider, and bot ID is retrieved from the ZAuth token // and assigned to variables that are used in the nginx config to set the corresponding headers (e.g. Z-Client, Z-Provider, ...). @@ -658,20 +657,19 @@ static ngx_int_t zauth_token_var (ngx_http_request_t * r, ngx_http_variable_valu // and access is allowed (endpoint is either allowed or not denied) according to the access control list (ACL) configuration // before we set the variable // otherwise 'zauth_token_lookup' will crash for OAuth requests - if (ctx->tag != ZAUTH_CONTEXT_ZAUTH || !zauth_is_authorized_and_allowed(r)) { + if (ctx != NULL && ctx->tag == CONTEXT_ZAUTH && zauth_is_authorized_and_allowed()) { + return zauth_set_var(r->pool, v, zauth_token_lookup(ctx->token, data)); + } else { zauth_empty_val(v); return NGX_OK; } - return zauth_set_var(r->pool, v, zauth_token_lookup(ctx->token, data)); } static ngx_int_t zauth_token_var_user (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t _) { ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); - if (ctx == NULL) { - return NGX_ERROR; - } else if (ctx->tag == ZAUTH_CONTEXT_ZAUTH) { + if (ctx != NULL && ctx->tag == CONTEXT_ZAUTH) { return zauth_set_var(r->pool, v, zauth_token_lookup(ctx->token, 'u')); - } else if (ctx->tag == ZAUTH_CONTEXT_OAUTH) { + } else if (ctx != NULL && ctx->tag == CONTEXT_OAUTH) { return zauth_set_var(r->pool, v, (Range) { (u_char*) ctx->user_id, strlen(ctx->user_id) }); } else { zauth_empty_val(v); @@ -681,9 +679,7 @@ static ngx_int_t zauth_token_var_user (ngx_http_request_t * r, ngx_http_variable static ngx_int_t zauth_token_var_conn (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t _) { ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); - if (ctx == NULL) { - return NGX_ERROR; - } else if (ctx->tag == ZAUTH_CONTEXT_ZAUTH && (zauth_token_type(ctx->token) == ZAUTH_TOKEN_TYPE_ACCESS || zauth_token_type(ctx->token) == ZAUTH_TOKEN_TYPE_LEGAL_HOLD_ACCESS)) { + if (ctx != NULL && ctx->tag == CONTEXT_ZAUTH && (zauth_token_type(ctx->token) == ZAUTH_TOKEN_TYPE_ACCESS || zauth_token_type(ctx->token) == ZAUTH_TOKEN_TYPE_LEGAL_HOLD_ACCESS)) { return zauth_set_var(r->pool, v, zauth_token_lookup(ctx->token, 'c')); } else { zauth_empty_val(v); @@ -693,9 +689,7 @@ static ngx_int_t zauth_token_var_conn (ngx_http_request_t * r, ngx_http_variable static ngx_int_t zauth_token_var_conv (ngx_http_request_t * r, ngx_http_variable_value_t * v, uintptr_t _) { ZauthContext const * ctx = ngx_http_get_module_ctx(r, zauth_module); - if (ctx == NULL) { - return NGX_ERROR; - } else if (ctx->tag == ZAUTH_CONTEXT_ZAUTH && zauth_token_type(ctx->token) == ZAUTH_TOKEN_TYPE_BOT) { + if (ctx != NULL && ctx->tag == CONTEXT_ZAUTH && zauth_token_type(ctx->token) == ZAUTH_TOKEN_TYPE_BOT) { return zauth_set_var(r->pool, v, zauth_token_lookup(ctx->token, 'c')); } else { zauth_empty_val(v); From 924dba4fe0d87c148e196aeed02b3b5cc40fed8c Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Thu, 23 Feb 2023 11:41:56 +0000 Subject: [PATCH 29/32] clean up --- services/nginz/third_party/nginx-zauth-module/zauth_module.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index c1f6475446..6f12e83071 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -89,14 +89,14 @@ static ngx_command_t zauth_commands [] = { , offsetof (ZauthLocationConf, zauth) , NULL } - + , { ngx_string ("oauth_scope") , NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1 , ngx_conf_set_str_slot , NGX_HTTP_LOC_CONF_OFFSET , offsetof (ZauthLocationConf, oauth_scope) , NULL - } + } , { ngx_string ("zauth_keystore") , NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1 From 49d1fd961ab61949a2f2fd3ca07145391fb77758 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Thu, 23 Feb 2023 11:44:58 +0000 Subject: [PATCH 30/32] remove spaces --- services/nginz/third_party/nginx-zauth-module/zauth_module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index 6f12e83071..6015002d36 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -120,7 +120,7 @@ static ngx_command_t zauth_commands [] = { , NGX_HTTP_SRV_CONF_OFFSET , 0 , NULL - } + } , ngx_null_command }; From 626ddb1dc4e823e65d8a27e37ce0218436e46c45 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Thu, 23 Feb 2023 13:38:36 +0000 Subject: [PATCH 31/32] typo in nginx.conf --- charts/nginz/templates/conf/_nginx.conf.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/nginz/templates/conf/_nginx.conf.tpl b/charts/nginz/templates/conf/_nginx.conf.tpl index 7fe5c66038..75f32f7a1a 100644 --- a/charts/nginz/templates/conf/_nginx.conf.tpl +++ b/charts/nginz/templates/conf/_nginx.conf.tpl @@ -260,7 +260,7 @@ http { {{- end }} {{- if ($location.oauth_scope) }} - oauth_scope {{ $location.oauth_scope }};; + oauth_scope {{ $location.oauth_scope }}; {{- end }} {{- if hasKey $location "specific_user_rate_limit" }} From 5b07b111c96e92fda50f94739dfddad08a7e4a32 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Thu, 23 Feb 2023 14:48:01 +0000 Subject: [PATCH 32/32] check for oom error --- services/nginz/third_party/nginx-zauth-module/zauth_module.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/nginz/third_party/nginx-zauth-module/zauth_module.c b/services/nginz/third_party/nginx-zauth-module/zauth_module.c index 6015002d36..6912484196 100644 --- a/services/nginz/third_party/nginx-zauth-module/zauth_module.c +++ b/services/nginz/third_party/nginx-zauth-module/zauth_module.c @@ -387,6 +387,7 @@ ngx_int_t oauth_handle_request(ngx_http_request_t *r, OAuthJwk const * key, ngx_ OAuthResult res = oauth_verify_token(key, &hdr.data[7], hdr.len - 7, scope.data, scope.len, r->method_name.data, r->method_name.len); if (res.status == OAUTH_OK) { ZauthContext * ctx = alloc_oauth_context(r, res.uid); + if (ctx == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR; // for OOM-safety ngx_int_t e = setup_zauth_context(r, ctx); if (e != NGX_OK) { ngx_free(ctx); @@ -479,6 +480,7 @@ static ngx_int_t zauth_parse_request (ngx_http_request_t * r) { if (res == ZAUTH_OK && tkn != NULL) { ZauthContext * ctx = alloc_zauth_context(r, tkn); + if (ctx == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR; // for OOM-safety ngx_int_t e = setup_zauth_context(r, ctx); if (e != NGX_OK) { ngx_free(ctx);