diff --git a/.github/data/matrix-smoke-plus.json b/.github/data/matrix-smoke-plus.json index 572d6e4d8..5e67ff5c5 100644 --- a/.github/data/matrix-smoke-plus.json +++ b/.github/data/matrix-smoke-plus.json @@ -76,6 +76,13 @@ "type": "plus", "marker": "'policies_ac or policies_jwt or policies_mtls'", "platforms": "linux/arm64, linux/amd64, linux/s390x" + }, + { + "label": "OIDC-UI 1/1", + "image": "debian-plus", + "type": "plus", + "marker": "oidc", + "platforms": "linux/arm64, linux/amd64" } ], "k8s": [] diff --git a/pyproject.toml b/pyproject.toml index 429f5e105..6719dad5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,7 @@ markers =[ "hsts", "ingresses", "multi_ns", + "oidc", "policies", "policies_rl", "policies_jwt", diff --git a/tests/Dockerfile b/tests/Dockerfile index ee36f9102..3db062168 100644 --- a/tests/Dockerfile +++ b/tests/Dockerfile @@ -15,6 +15,7 @@ WORKDIR /workspace/tests COPY --link tests/requirements.txt /workspace/tests/ RUN pip install --require-hashes -r requirements.txt --no-deps +RUN playwright install --with-deps chromium COPY --link deployments /workspace/deployments COPY --link config /workspace/config diff --git a/tests/data/common/app/keycloak/app.yaml b/tests/data/common/app/keycloak/app.yaml new file mode 100644 index 000000000..727ef3e9d --- /dev/null +++ b/tests/data/common/app/keycloak/app.yaml @@ -0,0 +1,50 @@ +apiVersion: v1 +kind: Service +metadata: + name: keycloak + labels: + app: keycloak +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: keycloak +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: keycloak + labels: + app: keycloak +spec: + replicas: 1 + selector: + matchLabels: + app: keycloak + template: + metadata: + labels: + app: keycloak + spec: + containers: + - name: keycloak + image: quay.io/keycloak/keycloak:25.0.2 + args: ["start-dev"] + env: + - name: KEYCLOAK_ADMIN + value: "admin" + - name: KEYCLOAK_ADMIN_PASSWORD + value: "admin" + - name: KC_PROXY + value: "edge" + ports: + - name: http + containerPort: 8080 + - name: https + containerPort: 8443 + readinessProbe: + httpGet: + path: /realms/master + port: 8080 diff --git a/tests/data/oidc/client-secret.yaml b/tests/data/oidc/client-secret.yaml new file mode 100644 index 000000000..11d481407 --- /dev/null +++ b/tests/data/oidc/client-secret.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: oidc-secret +type: nginx.org/oidc +data: + client-secret: diff --git a/tests/data/oidc/nginx-config.yaml b/tests/data/oidc/nginx-config.yaml new file mode 100644 index 000000000..6e9aa9b17 --- /dev/null +++ b/tests/data/oidc/nginx-config.yaml @@ -0,0 +1,15 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + name: nginx-config + namespace: nginx-ingress +data: + stream-snippets: | + server { + listen 12345; + listen [::]:12345; + zone_sync; + zone_sync_server nginx-ingress-headless.nginx-ingress.svc.cluster.local:12345 resolve; + } + resolver-addresses: kube-dns.kube-system.svc.cluster.local + resolver-valid: 5s diff --git a/tests/data/oidc/nginx-ingress-headless.yaml b/tests/data/oidc/nginx-ingress-headless.yaml new file mode 100644 index 000000000..0f13b83e7 --- /dev/null +++ b/tests/data/oidc/nginx-ingress-headless.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Service +metadata: + name: nginx-ingress-headless +spec: + clusterIP: None + selector: + app: nginx-ingress diff --git a/tests/data/oidc/oidc.yaml b/tests/data/oidc/oidc.yaml new file mode 100644 index 000000000..990924f3d --- /dev/null +++ b/tests/data/oidc/oidc.yaml @@ -0,0 +1,14 @@ +apiVersion: k8s.nginx.org/v1 +kind: Policy +metadata: + name: oidc-policy +spec: + oidc: + clientID: nginx-plus + clientSecret: oidc-secret + authEndpoint: https://keycloak.example.com/realms/master/protocol/openid-connect/auth + tokenEndpoint: http://keycloak.default.svc.cluster.local:8080/realms/master/protocol/openid-connect/token + jwksURI: http://keycloak.default.svc.cluster.local:8080/realms/master/protocol/openid-connect/certs + endSessionEndpoint: https://keycloak.example.com/realms/master/protocol/openid-connect/logout + scope: openid+profile+email + accessTokenEnable: true diff --git a/tests/data/oidc/virtual-server-idp.yaml b/tests/data/oidc/virtual-server-idp.yaml new file mode 100644 index 000000000..9bfb9a5f4 --- /dev/null +++ b/tests/data/oidc/virtual-server-idp.yaml @@ -0,0 +1,18 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServer +metadata: + name: keycloak +spec: + host: keycloak.example.com + tls: + secret: tls-secret + redirect: + enable: true + upstreams: + - name: keycloak + service: keycloak + port: 8080 + routes: + - path: / + action: + pass: keycloak diff --git a/tests/data/oidc/virtual-server.yaml b/tests/data/oidc/virtual-server.yaml new file mode 100644 index 000000000..62f1e5fa4 --- /dev/null +++ b/tests/data/oidc/virtual-server.yaml @@ -0,0 +1,18 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServer +metadata: + name: virtual-server-tls +spec: + host: virtual-server-tls.example.com + tls: + secret: tls-secret + upstreams: + - name: backend1 + service: backend1-svc + port: 80 + routes: + - path: / + policies: + - name: oidc-policy + action: + pass: backend1 diff --git a/tests/requirements.in b/tests/requirements.in new file mode 100644 index 000000000..b1e96d226 --- /dev/null +++ b/tests/requirements.in @@ -0,0 +1,42 @@ +attrs==24.3.0 +cachetools==5.5.0 +certifi==2024.12.14 +cffi==1.17.1 +charset-normalizer==3.4.0 +cryptography==44.0.0 +durationpy==0.9 +flaky==3.8.1 +forcediphttpsadapter==1.1.0 +google-auth==2.37.0 +gprof2dot==2024.6.6 +grpcio==1.68.1 +grpcio-tools==1.68.1 +idna==3.10 +iniconfig==2.0.0 +jinja2==3.1.4 +kubernetes==31.0.0 +markupsafe==3.0.2 +mock==5.1.0 +more-itertools==10.5.0 +oauthlib==3.2.2 +packaging==24.2 +playwright==1.49.1 +pluggy==1.5.0 +protobuf==5.29.1 +py==1.11.0 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 +pycparser==2.22 +pyopenssl==24.3.0 +pyparsing==3.2.0 +pytest==8.3.4 +pytest-html==4.1.1 +pytest-metadata==3.1.1 +python-dateutil==2.9.0.post0 +pyyaml==6.0.2 +requests==2.32.3 +requests-oauthlib==2.0.0 +rsa==4.9 +six==1.17.0 +urllib3==2.2.3 +websocket-client==1.8.0 diff --git a/tests/requirements.txt b/tests/requirements.txt index 7abfc0618..e8b5a8a3e 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -2,23 +2,23 @@ # This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --generate-hashes --resolver=backtracking requirements.txt +# pip-compile --generate-hashes --resolver=backtracking --output-file=requirements.txt requirements.in # attrs==24.3.0 \ --hash=sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff \ --hash=sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308 - # via -r requirements.txt + # via -r requirements.in cachetools==5.5.0 \ --hash=sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292 \ --hash=sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a # via - # -r requirements.txt + # -r requirements.in # google-auth certifi==2024.12.14 \ --hash=sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56 \ --hash=sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db # via - # -r requirements.txt + # -r requirements.in # kubernetes # requests cffi==1.17.1 \ @@ -90,7 +90,7 @@ cffi==1.17.1 \ --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \ --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b # via - # -r requirements.txt + # -r requirements.in # cryptography charset-normalizer==3.4.0 \ --hash=sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621 \ @@ -199,7 +199,7 @@ charset-normalizer==3.4.0 \ --hash=sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079 \ --hash=sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482 # via - # -r requirements.txt + # -r requirements.in # requests cryptography==44.0.0 \ --hash=sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7 \ @@ -232,32 +232,107 @@ cryptography==44.0.0 \ --hash=sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756 \ --hash=sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4 # via - # -r requirements.txt + # -r requirements.in # pyopenssl durationpy==0.9 \ --hash=sha256:e65359a7af5cedad07fb77a2dd3f390f8eb0b74cb845589fa6c057086834dd38 \ --hash=sha256:fd3feb0a69a0057d582ef643c355c40d2fa1c942191f914d12203b1a01ac722a # via - # -r requirements.txt + # -r requirements.in # kubernetes flaky==3.8.1 \ --hash=sha256:194ccf4f0d3a22b2de7130f4b62e45e977ac1b5ccad74d4d48f3005dcc38815e \ --hash=sha256:47204a81ec905f3d5acfbd61daeabcada8f9d4031616d9bcb0618461729699f5 - # via -r requirements.txt + # via -r requirements.in forcediphttpsadapter==1.1.0 \ --hash=sha256:0d224cf6e8e50eb788c9f5994a7afa6d389bac6dbe540b7dfd77a32590ad0153 \ --hash=sha256:5e7662ece61735585332d09b87d94fffe4752469d5c0d3feff48746e5d70744b - # via -r requirements.txt + # via -r requirements.in google-auth==2.37.0 \ --hash=sha256:0054623abf1f9c83492c63d3f47e77f0a544caa3d40b2d98e099a611c2dd5d00 \ --hash=sha256:42664f18290a6be591be5329a96fe30184be1a1badb7292a7f686a9659de9ca0 # via - # -r requirements.txt + # -r requirements.in # kubernetes gprof2dot==2024.6.6 \ --hash=sha256:45b14ad7ce64e299c8f526881007b9eb2c6b75505d5613e96e66ee4d5ab33696 \ --hash=sha256:fa1420c60025a9eb7734f65225b4da02a10fc6dd741b37fa129bc6b41951e5ab - # via -r requirements.txt + # via -r requirements.in +greenlet==3.1.1 \ + --hash=sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e \ + --hash=sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7 \ + --hash=sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01 \ + --hash=sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1 \ + --hash=sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159 \ + --hash=sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563 \ + --hash=sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83 \ + --hash=sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9 \ + --hash=sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395 \ + --hash=sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa \ + --hash=sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942 \ + --hash=sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1 \ + --hash=sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441 \ + --hash=sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22 \ + --hash=sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9 \ + --hash=sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0 \ + --hash=sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba \ + --hash=sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3 \ + --hash=sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1 \ + --hash=sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6 \ + --hash=sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291 \ + --hash=sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39 \ + --hash=sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d \ + --hash=sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467 \ + --hash=sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475 \ + --hash=sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef \ + --hash=sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c \ + --hash=sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511 \ + --hash=sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c \ + --hash=sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822 \ + --hash=sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a \ + --hash=sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8 \ + --hash=sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d \ + --hash=sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01 \ + --hash=sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145 \ + --hash=sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80 \ + --hash=sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13 \ + --hash=sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e \ + --hash=sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b \ + --hash=sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1 \ + --hash=sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef \ + --hash=sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc \ + --hash=sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff \ + --hash=sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120 \ + --hash=sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437 \ + --hash=sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd \ + --hash=sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981 \ + --hash=sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36 \ + --hash=sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a \ + --hash=sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798 \ + --hash=sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7 \ + --hash=sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761 \ + --hash=sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0 \ + --hash=sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e \ + --hash=sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af \ + --hash=sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa \ + --hash=sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c \ + --hash=sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42 \ + --hash=sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e \ + --hash=sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81 \ + --hash=sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e \ + --hash=sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617 \ + --hash=sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc \ + --hash=sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de \ + --hash=sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111 \ + --hash=sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383 \ + --hash=sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70 \ + --hash=sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6 \ + --hash=sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4 \ + --hash=sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011 \ + --hash=sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803 \ + --hash=sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79 \ + --hash=sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f + # via playwright grpcio==1.68.1 \ --hash=sha256:025f790c056815b3bf53da850dd70ebb849fd755a4b1ac822cb65cd631e37d43 \ --hash=sha256:04cfd68bf4f38f5bb959ee2361a7546916bd9a50f78617a346b3aeb2b42e2161 \ @@ -315,7 +390,7 @@ grpcio==1.68.1 \ --hash=sha256:eeb38ff04ab6e5756a2aef6ad8d94e89bb4a51ef96e20f45c44ba190fa0bcaad \ --hash=sha256:f8261fa2a5f679abeb2a0a93ad056d765cdca1c47745eda3f2d87f874ff4b8c9 # via - # -r requirements.txt + # -r requirements.in # grpcio-tools grpcio-tools==1.68.1 \ --hash=sha256:02f04de42834129eb54bb12469160ab631a0395d6a2b77975381c02b994086c3 \ @@ -373,29 +448,29 @@ grpcio-tools==1.68.1 \ --hash=sha256:eb7cae5f0232aba9057f26a45ef6b0a5633d36627fe49442c0985b6f44b67822 \ --hash=sha256:f0bdccb00709bf6180a80a353a99fa844cc0bb2d450cdf7fc6ab22c988bb6b4c \ --hash=sha256:ff6ae5031a03ab90e9c508d12914438b73efd44b5eed9946bf8974c453d0ed57 - # via -r requirements.txt + # via -r requirements.in idna==3.10 \ --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 # via - # -r requirements.txt + # -r requirements.in # requests iniconfig==2.0.0 \ --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 # via - # -r requirements.txt + # -r requirements.in # pytest jinja2==3.1.4 \ --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d # via - # -r requirements.txt + # -r requirements.in # pytest-html kubernetes==31.0.0 \ --hash=sha256:28945de906c8c259c1ebe62703b56a03b714049372196f854105afe4e6d014c0 \ --hash=sha256:bf141e2d380c8520eada8b351f4e319ffee9636328c137aa432bc486ca1200e1 - # via -r requirements.txt + # via -r requirements.in markupsafe==3.0.2 \ --hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \ --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \ @@ -459,35 +534,44 @@ markupsafe==3.0.2 \ --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \ --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50 # via - # -r requirements.txt + # -r requirements.in # jinja2 mock==5.1.0 \ --hash=sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744 \ --hash=sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d - # via -r requirements.txt + # via -r requirements.in more-itertools==10.5.0 \ --hash=sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef \ --hash=sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6 - # via -r requirements.txt + # via -r requirements.in oauthlib==3.2.2 \ --hash=sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca \ --hash=sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918 # via - # -r requirements.txt + # -r requirements.in # kubernetes # requests-oauthlib packaging==24.2 \ --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f # via - # -r requirements.txt + # -r requirements.in # forcediphttpsadapter # pytest +playwright==1.49.1 \ + --hash=sha256:1041ffb45a0d0bc44d698d3a5aa3ac4b67c9bd03540da43a0b70616ad52592b8 \ + --hash=sha256:3be48c6d26dc819ca0a26567c1ae36a980a0303dcd4249feb6f59e115aaddfb8 \ + --hash=sha256:43b304be67f096058e587dac453ece550eff87b8fbed28de30f4f022cc1745bb \ + --hash=sha256:47b23cb346283278f5b4d1e1990bcb6d6302f80c0aa0ca93dd0601a1400191df \ + --hash=sha256:753ca90ee31b4b03d165cfd36e477309ebf2b4381953f2a982ff612d85b147d2 \ + --hash=sha256:9f38ed3d0c1f4e0a6d1c92e73dd9a61f8855133249d6f0cec28648d38a7137be \ + --hash=sha256:cd9bc8dab37aa25198a01f555f0a2e2c3813fe200fef018ac34dfe86b34994b9 + # via -r requirements.in pluggy==1.5.0 \ --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 # via - # -r requirements.txt + # -r requirements.in # pytest protobuf==5.29.1 \ --hash=sha256:012ce28d862ff417fd629285aca5d9772807f15ceb1a0dbd15b88f58c776c98c \ @@ -502,61 +586,65 @@ protobuf==5.29.1 \ --hash=sha256:b5ba1d0e4c8a40ae0496d0e2ecfdbb82e1776928a205106d14ad6985a09ec155 \ --hash=sha256:d473655e29c0c4bbf8b69e9a8fb54645bc289dead6d753b952e7aa660254ae18 # via - # -r requirements.txt + # -r requirements.in # grpcio-tools py==1.11.0 \ --hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \ --hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 - # via -r requirements.txt + # via -r requirements.in pyasn1==0.6.1 \ --hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \ --hash=sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034 # via - # -r requirements.txt + # -r requirements.in # pyasn1-modules # rsa pyasn1-modules==0.4.1 \ --hash=sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd \ --hash=sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c # via - # -r requirements.txt + # -r requirements.in # google-auth pycparser==2.22 \ --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc # via - # -r requirements.txt + # -r requirements.in # cffi +pyee==12.0.0 \ + --hash=sha256:7b14b74320600049ccc7d0e0b1becd3b4bd0a03c745758225e31a59f4095c990 \ + --hash=sha256:c480603f4aa2927d4766eb41fa82793fe60a82cbfdb8d688e0d08c55a534e145 + # via playwright pyopenssl==24.3.0 \ --hash=sha256:49f7a019577d834746bc55c5fce6ecbcec0f2b4ec5ce1cf43a9a173b8138bb36 \ --hash=sha256:e474f5a473cd7f92221cc04976e48f4d11502804657a08a989fb3be5514c904a - # via -r requirements.txt + # via -r requirements.in pyparsing==3.2.0 \ --hash=sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84 \ --hash=sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c - # via -r requirements.txt + # via -r requirements.in pytest==8.3.4 \ --hash=sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6 \ --hash=sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761 # via - # -r requirements.txt + # -r requirements.in # pytest-html # pytest-metadata pytest-html==4.1.1 \ --hash=sha256:70a01e8ae5800f4a074b56a4cb1025c8f4f9b038bba5fe31e3c98eb996686f07 \ --hash=sha256:c8152cea03bd4e9bee6d525573b67bbc6622967b72b9628dda0ea3e2a0b5dd71 - # via -r requirements.txt + # via -r requirements.in pytest-metadata==3.1.1 \ --hash=sha256:c8e0844db684ee1c798cfa38908d20d67d0463ecb6137c72e91f418558dd5f4b \ --hash=sha256:d2a29b0355fbc03f168aa96d41ff88b1a3b44a3b02acbe491801c98a048017c8 # via - # -r requirements.txt + # -r requirements.in # pytest-html python-dateutil==2.9.0.post0 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 # via - # -r requirements.txt + # -r requirements.in # kubernetes pyyaml==6.0.2 \ --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ @@ -613,13 +701,13 @@ pyyaml==6.0.2 \ --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 # via - # -r requirements.txt + # -r requirements.in # kubernetes requests==2.32.3 \ --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 # via - # -r requirements.txt + # -r requirements.in # forcediphttpsadapter # kubernetes # requests-oauthlib @@ -627,33 +715,37 @@ requests-oauthlib==2.0.0 \ --hash=sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36 \ --hash=sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9 # via - # -r requirements.txt + # -r requirements.in # kubernetes rsa==4.9 \ --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 # via - # -r requirements.txt + # -r requirements.in # google-auth six==1.17.0 \ --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 # via - # -r requirements.txt + # -r requirements.in # kubernetes # python-dateutil +typing-extensions==4.12.2 \ + --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ + --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 + # via pyee urllib3==2.2.3 \ --hash=sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac \ --hash=sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9 # via - # -r requirements.txt + # -r requirements.in # kubernetes # requests websocket-client==1.8.0 \ --hash=sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526 \ --hash=sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da # via - # -r requirements.txt + # -r requirements.in # kubernetes # WARNING: The following packages were not pinned, but pip requires them to be diff --git a/tests/suite/test_oidc.py b/tests/suite/test_oidc.py new file mode 100644 index 000000000..3fd155073 --- /dev/null +++ b/tests/suite/test_oidc.py @@ -0,0 +1,221 @@ +import base64 +import secrets + +import pytest +import requests +import yaml +from playwright.sync_api import Error, sync_playwright +from settings import DEPLOYMENTS, TEST_DATA +from suite.utils.policy_resources_utils import delete_policy +from suite.utils.resources_utils import ( + create_example_app, + create_items_from_yaml, + create_secret, + create_secret_from_yaml, + delete_common_app, + delete_secret, + replace_configmap_from_yaml, + wait_before_test, + wait_until_all_pods_are_ready, +) +from suite.utils.vs_vsr_resources_utils import ( + create_virtual_server_from_yaml, + delete_virtual_server, + patch_virtual_server_from_yaml, +) + +username = "nginx-user-" + secrets.token_hex(4) +password = secrets.token_hex(8) +keycloak_src = f"{TEST_DATA}/oidc/keycloak.yaml" +keycloak_vs_src = f"{TEST_DATA}/oidc/virtual-server-idp.yaml" +oidc_secret_src = f"{TEST_DATA}/oidc/client-secret.yaml" +oidc_pol_src = f"{TEST_DATA}/oidc/oidc.yaml" +oidc_vs_src = f"{TEST_DATA}/oidc/virtual-server.yaml" +orig_vs_src = f"{TEST_DATA}/virtual-server-tls/standard/virtual-server.yaml" +cm_src = f"{TEST_DATA}/oidc/nginx-config.yaml" +orig_cm_src = f"{DEPLOYMENTS}/common/nginx-config.yaml" +svc_src = f"{TEST_DATA}/oidc/nginx-ingress-headless.yaml" + + +class KeycloakSetup: + """ + Attributes: + secret (str): + """ + + def __init__(self, secret): + self.secret = secret + + +@pytest.fixture(scope="class") +def keycloak_setup(request, kube_apis, test_namespace, ingress_controller_endpoint, virtual_server_setup): + + # Create Keycloak resources and setup Keycloak idp + + secret_name = create_secret_from_yaml( + kube_apis.v1, virtual_server_setup.namespace, f"{TEST_DATA}/virtual-server-tls/tls-secret.yaml" + ) + keycloak_address = "keycloak.example.com" + create_example_app(kube_apis, "keycloak", test_namespace) + wait_before_test() + wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) + keycloak_vs_name = create_virtual_server_from_yaml(kube_apis.custom_objects, keycloak_vs_src, test_namespace) + wait_before_test() + + # Get token + url = f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port_ssl}/realms/master/protocol/openid-connect/token" + headers = {"Host": keycloak_address, "Content-Type": "application/x-www-form-urlencoded"} + data = {"username": "admin", "password": "admin", "grant_type": "password", "client_id": "admin-cli"} + + response = requests.post(url, headers=headers, data=data, verify=False) + token = response.json()["access_token"] + + # Create a user and set credentials + create_user_url = f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port_ssl}/admin/realms/master/users" + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}", "Host": keycloak_address} + user_payload = { + "username": username, + "enabled": True, + "credentials": [{"type": "password", "value": password, "temporary": False}], + } + response = requests.post(create_user_url, headers=headers, json=user_payload, verify=False) + + # Create client "nginx-plus" and get secret + create_client_url = f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port_ssl}/realms/master/clients-registrations/default" + client_payload = { + "clientId": "nginx-plus", + "redirectUris": ["https://virtual-server-tls.example.com:443/_codexch"], + "attributes": {"post.logout.redirect.uris": "https://virtual-server-tls.example.com:443/*"}, + } + client_resp = requests.post(create_client_url, headers=headers, json=client_payload, verify=False) + client_resp.raise_for_status() + secret = client_resp.json().get("secret") + + # Base64 encode the secret + encoded_secret = base64.b64encode(secret.encode()).decode() + + print(f"Keycloak setup complete. Base64 encoded client secret") + + def fin(): + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Delete Keycloak resources") + delete_virtual_server(kube_apis.custom_objects, keycloak_vs_name, test_namespace) + delete_common_app(kube_apis, "keycloak", test_namespace) + delete_secret(kube_apis.v1, secret_name, test_namespace) + + request.addfinalizer(fin) + + return KeycloakSetup(encoded_secret) + + +@pytest.mark.oidc +@pytest.mark.skip_for_nginx_oss +@pytest.mark.parametrize( + "crd_ingress_controller, virtual_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + f"-enable-oidc", + ], + }, + {"example": "virtual-server-tls", "app_type": "simple"}, + ) + ], + indirect=True, +) +class TestOIDC: + def test_oidc( + self, + request, + kube_apis, + ingress_controller_endpoint, + ingress_controller_prerequisites, + crd_ingress_controller, + test_namespace, + virtual_server_setup, + keycloak_setup, + ): + print(f"Create oidc secret") + with open(oidc_secret_src) as f: + secret_data = yaml.safe_load(f) + secret_data["data"]["client-secret"] = keycloak_setup.secret + secret_name = create_secret(kube_apis.v1, test_namespace, secret_data) + + print(f"Create oidc policy") + with open(oidc_pol_src) as f: + doc = yaml.safe_load(f) + pol = doc["metadata"]["name"] + doc["spec"]["oidc"]["tokenEndpoint"] = doc["spec"]["oidc"]["tokenEndpoint"].replace("default", test_namespace) + doc["spec"]["oidc"]["jwksURI"] = doc["spec"]["oidc"]["jwksURI"].replace("default", test_namespace) + kube_apis.custom_objects.create_namespaced_custom_object("k8s.nginx.org", "v1", test_namespace, "policies", doc) + print(f"Policy created with name {pol}") + wait_before_test() + + print(f"Create virtual server") + patch_virtual_server_from_yaml( + kube_apis.custom_objects, virtual_server_setup.vs_name, oidc_vs_src, test_namespace + ) + wait_before_test() + + print(f"Update nginx configmap") + replace_configmap_from_yaml( + kube_apis.v1, + ingress_controller_prerequisites.config_map["metadata"]["name"], + ingress_controller_prerequisites.namespace, + cm_src, + ) + wait_before_test() + print(f"Create headless service") + create_items_from_yaml(kube_apis, svc_src, ingress_controller_prerequisites.namespace) + + with sync_playwright() as playwright: + run_oidc(playwright.chromium, ingress_controller_endpoint.public_ip, ingress_controller_endpoint.port_ssl) + + replace_configmap_from_yaml( + kube_apis.v1, + ingress_controller_prerequisites.config_map["metadata"]["name"], + ingress_controller_prerequisites.namespace, + cm_src, + ) + delete_secret(kube_apis.v1, secret_name, test_namespace) + delete_policy(kube_apis.custom_objects, pol, test_namespace) + patch_virtual_server_from_yaml( + kube_apis.custom_objects, virtual_server_setup.vs_name, orig_vs_src, test_namespace + ) + + +def run_oidc(browser_type, ip_address, port): + + browser = browser_type.launch(headless=True, args=[f"--host-resolver-rules=MAP * {ip_address}:{port}"]) + context = browser.new_context(ignore_https_errors=True) + + try: + page = context.new_page() + + page.goto("https://virtual-server-tls.example.com") + page.wait_for_selector('input[name="username"]') + page.fill('input[name="username"]', username) + page.wait_for_selector('input[name="password"]', timeout=5000) + page.fill('input[name="password"]', password) + + with page.expect_navigation(): + page.click('input[type="submit"]') + page.wait_for_load_state("load") + page_text = page.text_content("body") + fields_to_check = [ + "Server address:", + "Server name:", + "Date:", + "Request ID:", + ] + for field in fields_to_check: + assert field in page_text, f"'{field}' not found in page text" + + except Error as e: + assert False, f"Error: {e}" + + finally: + context.close() + browser.close()