tests/e2e: add forward auth e2e test (#11374)
* add nginx forward_auth e2e tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add envoy Signed-off-by: Jens Langhammer <jens@goauthentik.io> * cleanup Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove even more duplicate code Signed-off-by: Jens Langhammer <jens@goauthentik.io> * cleanup more Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add traefik static config Signed-off-by: Jens Langhammer <jens@goauthentik.io> * more cleanup, don't generate dex config cause they support env variables Signed-off-by: Jens Langhammer <jens@goauthentik.io> * use default dex entrypoint to use templating Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove options that are always set as default Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix compose flag Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add caddy Signed-off-by: Jens Langhammer <jens@goauthentik.io> * merge python files Signed-off-by: Jens Langhammer <jens@goauthentik.io> * use whoami api to check better Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix envoy config Signed-off-by: Jens Langhammer <jens@goauthentik.io> * set invalidation flow Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix logout checks Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
		
							
								
								
									
										2
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							| @ -180,7 +180,7 @@ jobs: | ||||
|         uses: ./.github/actions/setup | ||||
|       - name: Setup e2e env (chrome, etc) | ||||
|         run: | | ||||
|           docker compose -f tests/e2e/docker-compose.yml up -d | ||||
|           docker compose -f tests/e2e/docker-compose.yml up -d --quiet-pull | ||||
|       - id: cache-web | ||||
|         uses: actions/cache@v4 | ||||
|         with: | ||||
|  | ||||
							
								
								
									
										21
									
								
								tests/e2e/proxy_forward_auth/caddy_single/Caddyfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								tests/e2e/proxy_forward_auth/caddy_single/Caddyfile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| http://localhost { | ||||
|     # directive execution order is only as stated if enclosed with route. | ||||
|     route { | ||||
|         # always forward outpost path to actual outpost | ||||
|         reverse_proxy /outpost.goauthentik.io/* http://ak-test-outpost:9000 | ||||
|  | ||||
|         # forward authentication to outpost | ||||
|         forward_auth http://ak-test-outpost:9000 { | ||||
|             uri /outpost.goauthentik.io/auth/caddy | ||||
|  | ||||
|             # capitalization of the headers is important, otherwise they will be empty | ||||
|             copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version | ||||
|  | ||||
|             # optional, in this config trust all private ranges, should probably be set to the outposts IP | ||||
|             trusted_proxies private_ranges | ||||
|         } | ||||
|  | ||||
|         # actual site configuration below, for example | ||||
|         reverse_proxy ak-whoami | ||||
|     } | ||||
| } | ||||
							
								
								
									
										99
									
								
								tests/e2e/proxy_forward_auth/envoy_single/envoy.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								tests/e2e/proxy_forward_auth/envoy_single/envoy.yaml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,99 @@ | ||||
| # yaml-language-server: $schema=https://github.com/jcchavezs/envoy-config-schema/releases/download/v1.21.0/v3_Bootstrap.json | ||||
| static_resources: | ||||
|   listeners: | ||||
|     - name: main_listener | ||||
|       address: | ||||
|         socket_address: | ||||
|           address: 0.0.0.0 | ||||
|           port_value: 10000 | ||||
|       filter_chains: | ||||
|         - filters: | ||||
|             - name: envoy.filters.network.http_connection_manager | ||||
|               typed_config: | ||||
|                 "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager | ||||
|                 stat_prefix: ingress_http | ||||
|                 upgrade_configs: | ||||
|                   - upgrade_type: websocket | ||||
|                 access_log: | ||||
|                   - name: envoy.access_loggers.stdout | ||||
|                     typed_config: | ||||
|                       "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog | ||||
|                 http_filters: | ||||
|                   - name: envoy.filters.http.ext_authz | ||||
|                     typed_config: | ||||
|                       "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz | ||||
|                       transport_api_version: V3 | ||||
|                       http_service: | ||||
|                         path_prefix: /outpost.goauthentik.io/auth/envoy | ||||
|                         server_uri: | ||||
|                           uri: http://ak-test-outpost:9000 | ||||
|                           cluster: authentik_outpost | ||||
|                           timeout: 0.25s | ||||
|                         authorization_request: | ||||
|                           allowed_headers: | ||||
|                             patterns: | ||||
|                               - exact: "cookie" | ||||
|                                 ignore_case: true | ||||
|                         authorization_response: | ||||
|                           allowed_upstream_headers: | ||||
|                             patterns: | ||||
|                               - exact: "set-cookie" | ||||
|                                 ignore_case: true | ||||
|                               - prefix: "x-authentik-" | ||||
|                                 ignore_case: true | ||||
|                           allowed_client_headers_on_success: | ||||
|                             patterns: | ||||
|                               - exact: "cookie" | ||||
|                                 ignore_case: true | ||||
|                   - name: envoy.filters.http.router | ||||
|                     typed_config: | ||||
|                       "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router | ||||
|                 route_config: | ||||
|                   name: local_route | ||||
|                   virtual_hosts: | ||||
|                     - name: local_service | ||||
|                       domains: ["localhost"] | ||||
|                       routes: | ||||
|                         - match: | ||||
|                             prefix: "/outpost.goauthentik.io" | ||||
|                           route: | ||||
|                             cluster: authentik_outpost | ||||
|                         - match: | ||||
|                             prefix: "/" | ||||
|                           route: | ||||
|                             cluster: whoami | ||||
|                     - name: local_service | ||||
|                       domains: ["*"] | ||||
|                       typed_per_filter_config: | ||||
|                         envoy.filters.http.ext_authz: | ||||
|                           "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute | ||||
|                           disabled: true | ||||
|                       routes: | ||||
|                         - match: | ||||
|                             prefix: "/" | ||||
|                           route: | ||||
|                             cluster: authentik_outpost | ||||
|  | ||||
|   clusters: | ||||
|     - name: authentik_outpost | ||||
|       type: LOGICAL_DNS | ||||
|       load_assignment: | ||||
|         cluster_name: authentik_outpost | ||||
|         endpoints: | ||||
|           - lb_endpoints: | ||||
|               - endpoint: | ||||
|                   address: | ||||
|                     socket_address: | ||||
|                       address: ak-test-outpost | ||||
|                       port_value: 9000 | ||||
|     - name: whoami | ||||
|       type: LOGICAL_DNS | ||||
|       load_assignment: | ||||
|         cluster_name: whoami | ||||
|         endpoints: | ||||
|           - lb_endpoints: | ||||
|               - endpoint: | ||||
|                   address: | ||||
|                     socket_address: | ||||
|                       address: ak-whoami | ||||
|                       port_value: 80 | ||||
							
								
								
									
										59
									
								
								tests/e2e/proxy_forward_auth/nginx_single/nginx.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								tests/e2e/proxy_forward_auth/nginx_single/nginx.conf
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | ||||
| server { | ||||
|     listen 80; | ||||
|     server_name _; | ||||
|  | ||||
|     # Increase buffer size for large headers | ||||
|     # This is needed only if you get 'upstream sent too big header while reading response | ||||
|     # header from upstream' error when trying to access an application protected by goauthentik | ||||
|     proxy_buffers 8 16k; | ||||
|     proxy_buffer_size 32k; | ||||
|  | ||||
|     location / { | ||||
|         proxy_pass http://ak-whoami; | ||||
|         proxy_set_header    Host $host; | ||||
|  | ||||
|         ############################## | ||||
|         # authentik-specific config | ||||
|         ############################## | ||||
|         auth_request /outpost.goauthentik.io/auth/nginx; | ||||
|         error_page 401 = @goauthentik_proxy_signin; | ||||
|         auth_request_set $auth_cookie $upstream_http_set_cookie; | ||||
|         add_header Set-Cookie $auth_cookie; | ||||
|  | ||||
|         # translate headers from the outposts back to the actual upstream | ||||
|         auth_request_set $authentik_username $upstream_http_x_authentik_username; | ||||
|         auth_request_set $authentik_groups $upstream_http_x_authentik_groups; | ||||
|         auth_request_set $authentik_email $upstream_http_x_authentik_email; | ||||
|         auth_request_set $authentik_name $upstream_http_x_authentik_name; | ||||
|         auth_request_set $authentik_uid $upstream_http_x_authentik_uid; | ||||
|  | ||||
|         proxy_set_header X-authentik-username $authentik_username; | ||||
|         proxy_set_header X-authentik-groups $authentik_groups; | ||||
|         proxy_set_header X-authentik-email $authentik_email; | ||||
|         proxy_set_header X-authentik-name $authentik_name; | ||||
|         proxy_set_header X-authentik-uid $authentik_uid; | ||||
|     } | ||||
|  | ||||
|     # all requests to /outpost.goauthentik.io must be accessible without authentication | ||||
|     location /outpost.goauthentik.io { | ||||
|         proxy_pass          http://ak-test-outpost:9000/outpost.goauthentik.io; | ||||
|         # ensure the host of this vserver matches your external URL you've configured | ||||
|         # in authentik | ||||
|         proxy_set_header    Host $host; | ||||
|         proxy_set_header    X-Original-URL $scheme://$http_host$request_uri; | ||||
|         add_header          Set-Cookie $auth_cookie; | ||||
|         auth_request_set    $auth_cookie $upstream_http_set_cookie; | ||||
|         proxy_pass_request_body off; | ||||
|         proxy_set_header Content-Length ""; | ||||
|     } | ||||
|  | ||||
|     # Special location for when the /auth endpoint returns a 401, | ||||
|     # redirect to the /start URL which initiates SSO | ||||
|     location @goauthentik_proxy_signin { | ||||
|         internal; | ||||
|         add_header Set-Cookie $auth_cookie; | ||||
|         return 302 /outpost.goauthentik.io/start?rd=$request_uri; | ||||
|         # For domain level, use the below error_page to redirect to your authentik server with the full redirect path | ||||
|         # return 302 https://localhost/outpost.goauthentik.io/start?rd=$scheme://$http_host$request_uri; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,57 @@ | ||||
| # yaml-language-server: $schema=https://json.schemastore.org/traefik-v2.json | ||||
| api: | ||||
|   insecure: true | ||||
|   debug: true | ||||
|  | ||||
| log: | ||||
|   level: debug | ||||
| accessLog: | ||||
|   filePath: /dev/stdout | ||||
|  | ||||
| entryPoints: | ||||
|   web: | ||||
|     address: ":80" | ||||
|  | ||||
| # Re-use the same config file to define everything | ||||
| providers: | ||||
|   file: | ||||
|     filename: /etc/traefik/traefik.yml | ||||
|  | ||||
| http: | ||||
|   middlewares: | ||||
|     authentik: | ||||
|       forwardAuth: | ||||
|         address: http://ak-test-outpost:9000/outpost.goauthentik.io/auth/traefik | ||||
|         trustForwardHeader: true | ||||
|         authResponseHeaders: | ||||
|           - X-authentik-username | ||||
|           - X-authentik-groups | ||||
|           - X-authentik-email | ||||
|           - X-authentik-name | ||||
|           - X-authentik-uid | ||||
|           - X-authentik-jwt | ||||
|           - X-authentik-meta-jwks | ||||
|           - X-authentik-meta-outpost | ||||
|           - X-authentik-meta-provider | ||||
|           - X-authentik-meta-app | ||||
|           - X-authentik-meta-version | ||||
|   routers: | ||||
|     default-router: | ||||
|       rule: "Host(`localhost`)" | ||||
|       middlewares: | ||||
|         - authentik | ||||
|       priority: 10 | ||||
|       service: app | ||||
|     default-router-auth: | ||||
|       rule: "Host(`localhost`) && PathPrefix(`/outpost.goauthentik.io/`)" | ||||
|       priority: 15 | ||||
|       service: authentik | ||||
|   services: | ||||
|     app: | ||||
|       loadBalancer: | ||||
|         servers: | ||||
|           - url: http://ak-whoami | ||||
|     authentik: | ||||
|       loadBalancer: | ||||
|         servers: | ||||
|           - url: http://ak-test-outpost:9000/outpost.goauthentik.io | ||||
							
								
								
									
										23
									
								
								tests/e2e/sources_oauth2_dex/dex.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								tests/e2e/sources_oauth2_dex/dex.yaml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| --- | ||||
| enablePasswordDB: true | ||||
| issuer: http://127.0.0.1:5556/dex | ||||
| logger: | ||||
|   level: debug | ||||
| staticClients: | ||||
|   - id: example-app | ||||
|     name: Example App | ||||
|     redirectURIs: | ||||
|       - {{ .Env.AK_REDIRECT_URL }} | ||||
|     secret: {{ .Env.AK_CLIENT_SECRET }} | ||||
| staticPasswords: | ||||
|   - email: admin@example.com | ||||
|     # hash for 'password', for testing | ||||
|     hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W" | ||||
|     userID: "08a8684b-db88-4b73-90a9-3cd1661f5466" | ||||
|     username: admin | ||||
| storage: | ||||
|   config: | ||||
|     file: "/tmp/dex.db" | ||||
|   type: sqlite3 | ||||
| web: | ||||
|   http: 0.0.0.0:5556 | ||||
| @ -3,8 +3,6 @@ | ||||
| from dataclasses import asdict | ||||
| from time import sleep | ||||
|  | ||||
| from docker.client import DockerClient, from_env | ||||
| from docker.models.containers import Container | ||||
| from guardian.shortcuts import assign_perm | ||||
| from ldap3 import ALL, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, SUBTREE, Connection, Server | ||||
| from ldap3.core.exceptions import LDAPInvalidCredentialsResult | ||||
| @ -24,29 +22,18 @@ from tests.e2e.utils import SeleniumTestCase, retry | ||||
| class TestProviderLDAP(SeleniumTestCase): | ||||
|     """LDAP and Outpost e2e tests""" | ||||
|  | ||||
|     ldap_container: Container | ||||
|  | ||||
|     def tearDown(self) -> None: | ||||
|         super().tearDown() | ||||
|         self.output_container_logs(self.ldap_container) | ||||
|         self.ldap_container.kill() | ||||
|  | ||||
|     def start_ldap(self, outpost: Outpost) -> Container: | ||||
|     def start_ldap(self, outpost: Outpost): | ||||
|         """Start ldap container based on outpost created""" | ||||
|         client: DockerClient = from_env() | ||||
|         container = client.containers.run( | ||||
|         self.run_container( | ||||
|             image=self.get_container_image("ghcr.io/goauthentik/dev-ldap"), | ||||
|             detach=True, | ||||
|             ports={ | ||||
|                 "3389": "3389", | ||||
|                 "6636": "6636", | ||||
|             }, | ||||
|             environment={ | ||||
|                 "AUTHENTIK_HOST": self.live_server_url, | ||||
|                 "AUTHENTIK_TOKEN": outpost.token.key, | ||||
|             }, | ||||
|         ) | ||||
|         return container | ||||
|  | ||||
|     def _prepare(self) -> User: | ||||
|         """prepare user, provider, app and container""" | ||||
| @ -68,7 +55,7 @@ class TestProviderLDAP(SeleniumTestCase): | ||||
|         ) | ||||
|         outpost.providers.add(ldap) | ||||
|  | ||||
|         self.ldap_container = self.start_ldap(outpost) | ||||
|         self.start_ldap(outpost) | ||||
|  | ||||
|         # Wait until outpost healthcheck succeeds | ||||
|         healthcheck_retries = 0 | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| """test OAuth Provider flow""" | ||||
|  | ||||
| from time import sleep | ||||
| from typing import Any | ||||
|  | ||||
| from docker.types import Healthcheck | ||||
| from selenium.webdriver.common.by import By | ||||
| @ -24,22 +23,17 @@ class TestProviderOAuth2Github(SeleniumTestCase): | ||||
|         self.client_id = generate_id() | ||||
|         self.client_secret = generate_key() | ||||
|         super().setUp() | ||||
|  | ||||
|     def get_container_specs(self) -> dict[str, Any] | None: | ||||
|         """Setup client grafana container which we test OAuth against""" | ||||
|         return { | ||||
|             "image": "grafana/grafana:7.1.0", | ||||
|             "detach": True, | ||||
|             "ports": { | ||||
|         self.run_container( | ||||
|             image="grafana/grafana:7.1.0", | ||||
|             ports={ | ||||
|                 "3000": "3000", | ||||
|             }, | ||||
|             "auto_remove": True, | ||||
|             "healthcheck": Healthcheck( | ||||
|             healthcheck=Healthcheck( | ||||
|                 test=["CMD", "wget", "--spider", "http://localhost:3000"], | ||||
|                 interval=5 * 1_000 * 1_000_000, | ||||
|                 start_period=1 * 1_000 * 1_000_000, | ||||
|             ), | ||||
|             "environment": { | ||||
|             environment={ | ||||
|                 "GF_AUTH_GITHUB_ENABLED": "true", | ||||
|                 "GF_AUTH_GITHUB_ALLOW_SIGN_UP": "true", | ||||
|                 "GF_AUTH_GITHUB_CLIENT_ID": self.client_id, | ||||
| @ -54,7 +48,7 @@ class TestProviderOAuth2Github(SeleniumTestCase): | ||||
|                 "GF_AUTH_GITHUB_API_URL": self.url("authentik_providers_oauth2_root:github-user"), | ||||
|                 "GF_LOG_LEVEL": "debug", | ||||
|             }, | ||||
|         } | ||||
|         ) | ||||
|  | ||||
|     @retry() | ||||
|     @apply_blueprint( | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| """test OAuth2 OpenID Provider flow""" | ||||
|  | ||||
| from time import sleep | ||||
| from typing import Any | ||||
|  | ||||
| from docker.types import Healthcheck | ||||
| from selenium.webdriver.common.by import By | ||||
| @ -32,21 +31,17 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): | ||||
|         self.client_secret = generate_key() | ||||
|         self.app_slug = generate_id(20) | ||||
|         super().setUp() | ||||
|  | ||||
|     def get_container_specs(self) -> dict[str, Any] | None: | ||||
|         return { | ||||
|             "image": "grafana/grafana:7.1.0", | ||||
|             "detach": True, | ||||
|             "auto_remove": True, | ||||
|             "healthcheck": Healthcheck( | ||||
|         self.run_container( | ||||
|             image="grafana/grafana:7.1.0", | ||||
|             ports={ | ||||
|                 "3000": "3000", | ||||
|             }, | ||||
|             healthcheck=Healthcheck( | ||||
|                 test=["CMD", "wget", "--spider", "http://localhost:3000"], | ||||
|                 interval=5 * 1_000 * 1_000_000, | ||||
|                 start_period=1 * 1_000 * 1_000_000, | ||||
|             ), | ||||
|             "ports": { | ||||
|                 "3000": "3000", | ||||
|             }, | ||||
|             "environment": { | ||||
|             environment={ | ||||
|                 "GF_AUTH_GENERIC_OAUTH_ENABLED": "true", | ||||
|                 "GF_AUTH_GENERIC_OAUTH_CLIENT_ID": self.client_id, | ||||
|                 "GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET": self.client_secret, | ||||
| @ -60,7 +55,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): | ||||
|                 ), | ||||
|                 "GF_LOG_LEVEL": "debug", | ||||
|             }, | ||||
|         } | ||||
|         ) | ||||
|  | ||||
|     @retry() | ||||
|     @apply_blueprint( | ||||
|  | ||||
| @ -3,8 +3,6 @@ | ||||
| from json import loads | ||||
| from time import sleep | ||||
|  | ||||
| from docker import DockerClient, from_env | ||||
| from docker.models.containers import Container | ||||
| from selenium.webdriver.common.by import By | ||||
| from selenium.webdriver.support import expected_conditions as ec | ||||
|  | ||||
| @ -34,13 +32,11 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): | ||||
|         self.application_slug = generate_id() | ||||
|         super().setUp() | ||||
|  | ||||
|     def setup_client(self) -> Container: | ||||
|     def setup_client(self): | ||||
|         """Setup client oidc-test-client container which we test OIDC against""" | ||||
|         sleep(1) | ||||
|         client: DockerClient = from_env() | ||||
|         container = client.containers.run( | ||||
|         self.run_container( | ||||
|             image="ghcr.io/beryju/oidc-test-client:2.1", | ||||
|             detach=True, | ||||
|             ports={ | ||||
|                 "9009": "9009", | ||||
|             }, | ||||
| @ -50,8 +46,6 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): | ||||
|                 "OIDC_PROVIDER": f"{self.live_server_url}/application/o/{self.application_slug}/", | ||||
|             }, | ||||
|         ) | ||||
|         self.wait_for_container(container) | ||||
|         return container | ||||
|  | ||||
|     @retry() | ||||
|     @apply_blueprint( | ||||
| @ -91,7 +85,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): | ||||
|             slug=self.application_slug, | ||||
|             provider=provider, | ||||
|         ) | ||||
|         self.container = self.setup_client() | ||||
|         self.setup_client() | ||||
|  | ||||
|         self.driver.get("http://localhost:9009") | ||||
|         sleep(2) | ||||
| @ -140,7 +134,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): | ||||
|             slug=self.application_slug, | ||||
|             provider=provider, | ||||
|         ) | ||||
|         self.container = self.setup_client() | ||||
|         self.setup_client() | ||||
|  | ||||
|         self.driver.get("http://localhost:9009") | ||||
|         self.login() | ||||
| @ -210,7 +204,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): | ||||
|             slug=self.application_slug, | ||||
|             provider=provider, | ||||
|         ) | ||||
|         self.container = self.setup_client() | ||||
|         self.setup_client() | ||||
|  | ||||
|         self.driver.get("http://localhost:9009") | ||||
|         self.login() | ||||
| @ -287,7 +281,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): | ||||
|         ) | ||||
|         PolicyBinding.objects.create(target=app, policy=negative_policy, order=0) | ||||
|  | ||||
|         self.container = self.setup_client() | ||||
|         self.setup_client() | ||||
|         self.driver.get("http://localhost:9009") | ||||
|         self.login() | ||||
|         self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "header > h1"))) | ||||
|  | ||||
| @ -3,8 +3,6 @@ | ||||
| from json import loads | ||||
| from time import sleep | ||||
|  | ||||
| from docker import DockerClient, from_env | ||||
| from docker.models.containers import Container | ||||
| from selenium.webdriver.common.by import By | ||||
| from selenium.webdriver.support import expected_conditions as ec | ||||
|  | ||||
| @ -34,13 +32,11 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): | ||||
|         self.application_slug = "test" | ||||
|         super().setUp() | ||||
|  | ||||
|     def setup_client(self) -> Container: | ||||
|     def setup_client(self): | ||||
|         """Setup client oidc-test-client container which we test OIDC against""" | ||||
|         sleep(1) | ||||
|         client: DockerClient = from_env() | ||||
|         container = client.containers.run( | ||||
|         self.run_container( | ||||
|             image="ghcr.io/beryju/oidc-test-client:2.1", | ||||
|             detach=True, | ||||
|             ports={ | ||||
|                 "9009": "9009", | ||||
|             }, | ||||
| @ -50,8 +46,6 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): | ||||
|                 "OIDC_PROVIDER": f"{self.live_server_url}/application/o/{self.application_slug}/", | ||||
|             }, | ||||
|         ) | ||||
|         self.wait_for_container(container) | ||||
|         return container | ||||
|  | ||||
|     @retry() | ||||
|     @apply_blueprint( | ||||
| @ -93,7 +87,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): | ||||
|             slug=self.application_slug, | ||||
|             provider=provider, | ||||
|         ) | ||||
|         self.container = self.setup_client() | ||||
|         self.setup_client() | ||||
|  | ||||
|         self.driver.get("http://localhost:9009/implicit/") | ||||
|         sleep(2) | ||||
| @ -142,7 +136,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): | ||||
|             slug=self.application_slug, | ||||
|             provider=provider, | ||||
|         ) | ||||
|         self.container = self.setup_client() | ||||
|         self.setup_client() | ||||
|  | ||||
|         self.driver.get("http://localhost:9009/implicit/") | ||||
|         self.wait.until(ec.title_contains("authentik")) | ||||
| @ -194,7 +188,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): | ||||
|             slug=self.application_slug, | ||||
|             provider=provider, | ||||
|         ) | ||||
|         self.container = self.setup_client() | ||||
|         self.setup_client() | ||||
|  | ||||
|         self.driver.get("http://localhost:9009/implicit/") | ||||
|         self.wait.until(ec.title_contains("authentik")) | ||||
| @ -268,7 +262,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): | ||||
|         ) | ||||
|         PolicyBinding.objects.create(target=app, policy=negative_policy, order=0) | ||||
|  | ||||
|         self.container = self.setup_client() | ||||
|         self.setup_client() | ||||
|         self.driver.get("http://localhost:9009/implicit/") | ||||
|         self.wait.until(ec.title_contains("authentik")) | ||||
|         self.login() | ||||
|  | ||||
| @ -2,14 +2,12 @@ | ||||
|  | ||||
| from base64 import b64encode | ||||
| from dataclasses import asdict | ||||
| from json import loads | ||||
| from sys import platform | ||||
| from time import sleep | ||||
| from typing import Any | ||||
| from unittest.case import skip, skipUnless | ||||
|  | ||||
| from channels.testing import ChannelsLiveServerTestCase | ||||
| from docker.client import DockerClient, from_env | ||||
| from docker.models.containers import Container | ||||
| from selenium.webdriver.common.by import By | ||||
|  | ||||
| from authentik.blueprints.tests import apply_blueprint, reconcile_app | ||||
| @ -25,38 +23,26 @@ from tests.e2e.utils import SeleniumTestCase, retry | ||||
| class TestProviderProxy(SeleniumTestCase): | ||||
|     """Proxy and Outpost e2e tests""" | ||||
|  | ||||
|     proxy_container: Container | ||||
|  | ||||
|     def tearDown(self) -> None: | ||||
|         super().tearDown() | ||||
|         self.output_container_logs(self.proxy_container) | ||||
|         self.proxy_container.kill() | ||||
|  | ||||
|     def get_container_specs(self) -> dict[str, Any] | None: | ||||
|         return { | ||||
|             "image": "traefik/whoami:latest", | ||||
|             "detach": True, | ||||
|             "ports": { | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         self.run_container( | ||||
|             image="traefik/whoami:latest", | ||||
|             ports={ | ||||
|                 "80": "80", | ||||
|             }, | ||||
|             "auto_remove": True, | ||||
|         } | ||||
|         ) | ||||
|  | ||||
|     def start_proxy(self, outpost: Outpost) -> Container: | ||||
|     def start_proxy(self, outpost: Outpost): | ||||
|         """Start proxy container based on outpost created""" | ||||
|         client: DockerClient = from_env() | ||||
|         container = client.containers.run( | ||||
|         self.run_container( | ||||
|             image=self.get_container_image("ghcr.io/goauthentik/dev-proxy"), | ||||
|             detach=True, | ||||
|             ports={ | ||||
|                 "9000": "9000", | ||||
|             }, | ||||
|             environment={ | ||||
|                 "AUTHENTIK_HOST": self.live_server_url, | ||||
|                 "AUTHENTIK_TOKEN": outpost.token.key, | ||||
|             }, | ||||
|         ) | ||||
|         return container | ||||
|  | ||||
|     @retry() | ||||
|     @apply_blueprint( | ||||
| @ -99,7 +85,7 @@ class TestProviderProxy(SeleniumTestCase): | ||||
|         outpost.providers.add(proxy) | ||||
|         outpost.build_user_permissions(outpost.user) | ||||
|  | ||||
|         self.proxy_container = self.start_proxy(outpost) | ||||
|         self.start_proxy(outpost) | ||||
|  | ||||
|         # Wait until outpost healthcheck succeeds | ||||
|         healthcheck_retries = 0 | ||||
| @ -112,13 +98,15 @@ class TestProviderProxy(SeleniumTestCase): | ||||
|             sleep(0.5) | ||||
|         sleep(5) | ||||
|  | ||||
|         self.driver.get("http://localhost:9000") | ||||
|         self.driver.get("http://localhost:9000/api") | ||||
|         self.login() | ||||
|         sleep(1) | ||||
|  | ||||
|         full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text | ||||
|         self.assertIn(f"X-Authentik-Username: {self.user.username}", full_body_text) | ||||
|         self.assertIn("X-Foo: bar", full_body_text) | ||||
|         body = loads(full_body_text) | ||||
|  | ||||
|         self.assertEqual(body["headers"]["X-Authentik-Username"], [self.user.username]) | ||||
|         self.assertEqual(body["headers"]["X-Foo"], ["bar"]) | ||||
|  | ||||
|         self.driver.get("http://localhost:9000/outpost.goauthentik.io/sign_out") | ||||
|         sleep(2) | ||||
| @ -173,7 +161,7 @@ class TestProviderProxy(SeleniumTestCase): | ||||
|         outpost.providers.add(proxy) | ||||
|         outpost.build_user_permissions(outpost.user) | ||||
|  | ||||
|         self.proxy_container = self.start_proxy(outpost) | ||||
|         self.start_proxy(outpost) | ||||
|  | ||||
|         # Wait until outpost healthcheck succeeds | ||||
|         healthcheck_retries = 0 | ||||
| @ -186,14 +174,16 @@ class TestProviderProxy(SeleniumTestCase): | ||||
|             sleep(0.5) | ||||
|         sleep(5) | ||||
|  | ||||
|         self.driver.get("http://localhost:9000") | ||||
|         self.driver.get("http://localhost:9000/api") | ||||
|         self.login() | ||||
|         sleep(1) | ||||
|  | ||||
|         full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text | ||||
|         self.assertIn(f"X-Authentik-Username: {self.user.username}", full_body_text) | ||||
|         body = loads(full_body_text) | ||||
|  | ||||
|         self.assertEqual(body["headers"]["X-Authentik-Username"], [self.user.username]) | ||||
|         auth_header = b64encode(f"{cred}:{cred}".encode()).decode() | ||||
|         self.assertIn(f"Authorization: Basic {auth_header}", full_body_text) | ||||
|         self.assertEqual(body["headers"]["Authorization"], [f"Basic {auth_header}"]) | ||||
|  | ||||
|         self.driver.get("http://localhost:9000/outpost.goauthentik.io/sign_out") | ||||
|         sleep(2) | ||||
|  | ||||
							
								
								
									
										227
									
								
								tests/e2e/test_provider_proxy_forward.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								tests/e2e/test_provider_proxy_forward.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,227 @@ | ||||
| """Proxy and Outpost e2e tests""" | ||||
|  | ||||
| from json import loads | ||||
| from pathlib import Path | ||||
| from time import sleep | ||||
|  | ||||
| from selenium.webdriver.common.by import By | ||||
|  | ||||
| from authentik.blueprints.tests import apply_blueprint, reconcile_app | ||||
| from authentik.core.models import Application | ||||
| from authentik.flows.models import Flow | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.outposts.models import Outpost, OutpostType | ||||
| from authentik.providers.proxy.models import ProxyMode, ProxyProvider | ||||
| from tests.e2e.utils import SeleniumTestCase, retry | ||||
|  | ||||
|  | ||||
| class TestProviderProxyForward(SeleniumTestCase): | ||||
|     """Proxy and Outpost e2e tests""" | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         self.run_container( | ||||
|             image="traefik/whoami:latest", | ||||
|             name="ak-whoami", | ||||
|         ) | ||||
|  | ||||
|     def start_outpost(self, outpost: Outpost): | ||||
|         """Start proxy container based on outpost created""" | ||||
|         self.run_container( | ||||
|             image=self.get_container_image("ghcr.io/goauthentik/dev-proxy"), | ||||
|             ports={ | ||||
|                 "9000": "9000", | ||||
|             }, | ||||
|             environment={ | ||||
|                 "AUTHENTIK_TOKEN": outpost.token.key, | ||||
|             }, | ||||
|             name="ak-test-outpost", | ||||
|         ) | ||||
|  | ||||
|     @apply_blueprint( | ||||
|         "default/flow-default-authentication-flow.yaml", | ||||
|         "default/flow-default-invalidation-flow.yaml", | ||||
|     ) | ||||
|     @apply_blueprint( | ||||
|         "default/flow-default-provider-authorization-implicit-consent.yaml", | ||||
|         "default/flow-default-provider-invalidation.yaml", | ||||
|     ) | ||||
|     @apply_blueprint( | ||||
|         "system/providers-oauth2.yaml", | ||||
|         "system/providers-proxy.yaml", | ||||
|     ) | ||||
|     @reconcile_app("authentik_crypto") | ||||
|     def prepare(self): | ||||
|         proxy: ProxyProvider = ProxyProvider.objects.create( | ||||
|             name=generate_id(), | ||||
|             mode=ProxyMode.FORWARD_SINGLE, | ||||
|             authorization_flow=Flow.objects.get( | ||||
|                 slug="default-provider-authorization-implicit-consent" | ||||
|             ), | ||||
|             invalidation_flow=Flow.objects.get(slug="default-provider-invalidation-flow"), | ||||
|             internal_host=f"http://{self.host}", | ||||
|             external_host="http://localhost", | ||||
|         ) | ||||
|         # Ensure OAuth2 Params are set | ||||
|         proxy.set_oauth_defaults() | ||||
|         proxy.save() | ||||
|         # we need to create an application to actually access the proxy | ||||
|         Application.objects.create(name=generate_id(), slug=generate_id(), provider=proxy) | ||||
|         outpost: Outpost = Outpost.objects.create( | ||||
|             name=generate_id(), | ||||
|             type=OutpostType.PROXY, | ||||
|         ) | ||||
|         outpost.providers.add(proxy) | ||||
|         outpost.build_user_permissions(outpost.user) | ||||
|  | ||||
|         self.start_outpost(outpost) | ||||
|  | ||||
|         # Wait until outpost healthcheck succeeds | ||||
|         healthcheck_retries = 0 | ||||
|         while healthcheck_retries < 50:  # noqa: PLR2004 | ||||
|             if len(outpost.state) > 0: | ||||
|                 state = outpost.state[0] | ||||
|                 if state.last_seen: | ||||
|                     break | ||||
|             healthcheck_retries += 1 | ||||
|             sleep(0.5) | ||||
|         sleep(5) | ||||
|  | ||||
|     @retry() | ||||
|     def test_traefik(self): | ||||
|         """Test traefik""" | ||||
|         local_config_path = ( | ||||
|             Path(__file__).parent / "proxy_forward_auth" / "traefik_single" / "config-static.yaml" | ||||
|         ) | ||||
|         self.run_container( | ||||
|             image="docker.io/library/traefik:3.1", | ||||
|             ports={ | ||||
|                 "80": "80", | ||||
|             }, | ||||
|             volumes={ | ||||
|                 local_config_path: { | ||||
|                     "bind": "/etc/traefik/traefik.yml", | ||||
|                 } | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|         self.prepare() | ||||
|  | ||||
|         self.driver.get("http://localhost/api") | ||||
|         self.login() | ||||
|         sleep(1) | ||||
|  | ||||
|         full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text | ||||
|         body = loads(full_body_text) | ||||
|  | ||||
|         self.assertEqual(body["headers"]["X-Authentik-Username"], [self.user.username]) | ||||
|  | ||||
|         self.driver.get("http://localhost/outpost.goauthentik.io/sign_out") | ||||
|         sleep(2) | ||||
|         flow_executor = self.get_shadow_root("ak-flow-executor") | ||||
|         session_end_stage = self.get_shadow_root("ak-stage-session-end", flow_executor) | ||||
|         title = session_end_stage.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text | ||||
|         self.assertIn("You've logged out of", title) | ||||
|  | ||||
|     @retry() | ||||
|     def test_nginx(self): | ||||
|         """Test nginx""" | ||||
|         self.prepare() | ||||
|  | ||||
|         # Start nginx last so all hosts are resolvable, otherwise nginx exits | ||||
|         self.run_container( | ||||
|             image="docker.io/library/nginx:1.27", | ||||
|             ports={ | ||||
|                 "80": "80", | ||||
|             }, | ||||
|             volumes={ | ||||
|                 f"{Path(__file__).parent / "proxy_forward_auth" / "nginx_single" / "nginx.conf"}": { | ||||
|                     "bind": "/etc/nginx/conf.d/default.conf", | ||||
|                 } | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|         self.driver.get("http://localhost/api") | ||||
|         self.login() | ||||
|         sleep(1) | ||||
|  | ||||
|         full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text | ||||
|         body = loads(full_body_text) | ||||
|  | ||||
|         self.assertEqual(body["headers"]["X-Authentik-Username"], [self.user.username]) | ||||
|  | ||||
|         self.driver.get("http://localhost/outpost.goauthentik.io/sign_out") | ||||
|         sleep(2) | ||||
|         flow_executor = self.get_shadow_root("ak-flow-executor") | ||||
|         session_end_stage = self.get_shadow_root("ak-stage-session-end", flow_executor) | ||||
|         title = session_end_stage.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text | ||||
|         self.assertIn("You've logged out of", title) | ||||
|  | ||||
|     @retry() | ||||
|     def test_envoy(self): | ||||
|         """Test envoy""" | ||||
|         self.run_container( | ||||
|             image="docker.io/envoyproxy/envoy:v1.25-latest", | ||||
|             ports={ | ||||
|                 "10000": "80", | ||||
|             }, | ||||
|             volumes={ | ||||
|                 f"{Path(__file__).parent / "proxy_forward_auth" / "envoy_single" / "envoy.yaml"}": { | ||||
|                     "bind": "/etc/envoy/envoy.yaml", | ||||
|                 } | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|         self.prepare() | ||||
|  | ||||
|         self.driver.get("http://localhost/api") | ||||
|         self.login() | ||||
|         sleep(1) | ||||
|  | ||||
|         full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text | ||||
|         body = loads(full_body_text) | ||||
|  | ||||
|         self.assertEqual(body["headers"]["X-Authentik-Username"], [self.user.username]) | ||||
|  | ||||
|         self.driver.get("http://localhost/outpost.goauthentik.io/sign_out") | ||||
|         sleep(2) | ||||
|         flow_executor = self.get_shadow_root("ak-flow-executor") | ||||
|         session_end_stage = self.get_shadow_root("ak-stage-session-end", flow_executor) | ||||
|         title = session_end_stage.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text | ||||
|         self.assertIn("You've logged out of", title) | ||||
|  | ||||
|     @retry() | ||||
|     def test_caddy(self): | ||||
|         """Test caddy""" | ||||
|         local_config_path = ( | ||||
|             Path(__file__).parent / "proxy_forward_auth" / "caddy_single" / "Caddyfile" | ||||
|         ) | ||||
|         self.run_container( | ||||
|             image="docker.io/library/caddy:2.8", | ||||
|             ports={ | ||||
|                 "80": "80", | ||||
|             }, | ||||
|             volumes={ | ||||
|                 local_config_path: { | ||||
|                     "bind": "/etc/caddy/Caddyfile", | ||||
|                 } | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|         self.prepare() | ||||
|  | ||||
|         self.driver.get("http://localhost/api") | ||||
|         self.login() | ||||
|         sleep(1) | ||||
|  | ||||
|         full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text | ||||
|         body = loads(full_body_text) | ||||
|  | ||||
|         self.assertEqual(body["headers"]["X-Authentik-Username"], [self.user.username]) | ||||
|  | ||||
|         self.driver.get("http://localhost/outpost.goauthentik.io/sign_out") | ||||
|         sleep(2) | ||||
|         flow_executor = self.get_shadow_root("ak-flow-executor") | ||||
|         session_end_stage = self.get_shadow_root("ak-stage-session-end", flow_executor) | ||||
|         title = session_end_stage.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text | ||||
|         self.assertIn("You've logged out of", title) | ||||
| @ -3,8 +3,6 @@ | ||||
| from dataclasses import asdict | ||||
| from time import sleep | ||||
|  | ||||
| from docker.client import DockerClient, from_env | ||||
| from docker.models.containers import Container | ||||
| from pyrad.client import Client | ||||
| from pyrad.dictionary import Dictionary | ||||
| from pyrad.packet import AccessAccept, AccessReject, AccessRequest | ||||
| @ -21,30 +19,19 @@ from tests.e2e.utils import SeleniumTestCase, retry | ||||
| class TestProviderRadius(SeleniumTestCase): | ||||
|     """Radius Outpost e2e tests""" | ||||
|  | ||||
|     radius_container: Container | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         self.shared_secret = generate_key() | ||||
|  | ||||
|     def tearDown(self) -> None: | ||||
|         super().tearDown() | ||||
|         self.output_container_logs(self.radius_container) | ||||
|         self.radius_container.kill() | ||||
|  | ||||
|     def start_radius(self, outpost: Outpost) -> Container: | ||||
|     def start_radius(self, outpost: Outpost): | ||||
|         """Start radius container based on outpost created""" | ||||
|         client: DockerClient = from_env() | ||||
|         container = client.containers.run( | ||||
|         self.run_container( | ||||
|             image=self.get_container_image("ghcr.io/goauthentik/dev-radius"), | ||||
|             detach=True, | ||||
|             ports={"1812/udp": "1812/udp"}, | ||||
|             environment={ | ||||
|                 "AUTHENTIK_HOST": self.live_server_url, | ||||
|                 "AUTHENTIK_TOKEN": outpost.token.key, | ||||
|             }, | ||||
|         ) | ||||
|         return container | ||||
|  | ||||
|     def _prepare(self) -> User: | ||||
|         """prepare user, provider, app and container""" | ||||
| @ -62,7 +49,7 @@ class TestProviderRadius(SeleniumTestCase): | ||||
|         ) | ||||
|         outpost.providers.add(radius) | ||||
|  | ||||
|         self.radius_container = self.start_radius(outpost) | ||||
|         self.start_radius(outpost) | ||||
|  | ||||
|         # Wait until outpost healthcheck succeeds | ||||
|         healthcheck_retries = 0 | ||||
|  | ||||
| @ -3,8 +3,6 @@ | ||||
| from json import loads | ||||
| from time import sleep | ||||
|  | ||||
| from docker import DockerClient, from_env | ||||
| from docker.models.containers import Container | ||||
| from selenium.webdriver.common.by import By | ||||
| from selenium.webdriver.support import expected_conditions as ec | ||||
|  | ||||
| @ -22,11 +20,8 @@ from tests.e2e.utils import SeleniumTestCase, retry | ||||
| class TestProviderSAML(SeleniumTestCase): | ||||
|     """test SAML Provider flow""" | ||||
|  | ||||
|     container: Container | ||||
|  | ||||
|     def setup_client(self, provider: SAMLProvider, force_post: bool = False) -> Container: | ||||
|     def setup_client(self, provider: SAMLProvider, force_post: bool = False): | ||||
|         """Setup client saml-sp container which we test SAML against""" | ||||
|         client: DockerClient = from_env() | ||||
|         metadata_url = ( | ||||
|             self.url( | ||||
|                 "authentik_api:samlprovider-metadata", | ||||
| @ -36,9 +31,8 @@ class TestProviderSAML(SeleniumTestCase): | ||||
|         ) | ||||
|         if force_post: | ||||
|             metadata_url += f"&force_binding={SAML_BINDING_POST}" | ||||
|         container = client.containers.run( | ||||
|         self.run_container( | ||||
|             image="ghcr.io/beryju/saml-test-sp:1.1", | ||||
|             detach=True, | ||||
|             ports={ | ||||
|                 "9009": "9009", | ||||
|             }, | ||||
| @ -48,8 +42,6 @@ class TestProviderSAML(SeleniumTestCase): | ||||
|                 "SP_METADATA_URL": metadata_url, | ||||
|             }, | ||||
|         ) | ||||
|         self.wait_for_container(container) | ||||
|         return container | ||||
|  | ||||
|     @retry() | ||||
|     @apply_blueprint( | ||||
| @ -85,7 +77,7 @@ class TestProviderSAML(SeleniumTestCase): | ||||
|             slug="authentik-saml", | ||||
|             provider=provider, | ||||
|         ) | ||||
|         self.container = self.setup_client(provider) | ||||
|         self.setup_client(provider) | ||||
|         self.driver.get("http://localhost:9009") | ||||
|         self.login() | ||||
|         self.wait_for_url("http://localhost:9009/") | ||||
| @ -153,7 +145,7 @@ class TestProviderSAML(SeleniumTestCase): | ||||
|             slug="authentik-saml", | ||||
|             provider=provider, | ||||
|         ) | ||||
|         self.container = self.setup_client(provider) | ||||
|         self.setup_client(provider) | ||||
|         self.driver.get("http://localhost:9009") | ||||
|         self.login() | ||||
|  | ||||
| @ -236,7 +228,7 @@ class TestProviderSAML(SeleniumTestCase): | ||||
|             slug="authentik-saml", | ||||
|             provider=provider, | ||||
|         ) | ||||
|         self.container = self.setup_client(provider, True) | ||||
|         self.setup_client(provider, True) | ||||
|         self.driver.get("http://localhost:9009") | ||||
|         self.login() | ||||
|  | ||||
| @ -319,7 +311,7 @@ class TestProviderSAML(SeleniumTestCase): | ||||
|             slug="authentik-saml", | ||||
|             provider=provider, | ||||
|         ) | ||||
|         self.container = self.setup_client(provider) | ||||
|         self.setup_client(provider) | ||||
|         self.driver.get( | ||||
|             self.url( | ||||
|                 "authentik_providers_saml:sso-init", | ||||
| @ -397,7 +389,7 @@ class TestProviderSAML(SeleniumTestCase): | ||||
|             provider=provider, | ||||
|         ) | ||||
|         PolicyBinding.objects.create(target=app, policy=negative_policy, order=0) | ||||
|         self.container = self.setup_client(provider) | ||||
|         self.setup_client(provider) | ||||
|         self.driver.get("http://localhost:9009/") | ||||
|         self.login() | ||||
|  | ||||
| @ -444,7 +436,7 @@ class TestProviderSAML(SeleniumTestCase): | ||||
|             slug="authentik-saml", | ||||
|             provider=provider, | ||||
|         ) | ||||
|         self.container = self.setup_client(provider) | ||||
|         self.setup_client(provider) | ||||
|         self.driver.get("http://localhost:9009") | ||||
|         self.login() | ||||
|         self.wait_for_url("http://localhost:9009/") | ||||
|  | ||||
| @ -1,7 +1,5 @@ | ||||
| """test LDAP Source""" | ||||
|  | ||||
| from typing import Any | ||||
|  | ||||
| from django.db.models import Q | ||||
| from ldap3.core.exceptions import LDAPSessionTerminatedByServerError | ||||
|  | ||||
| @ -22,22 +20,18 @@ class TestSourceLDAPSamba(SeleniumTestCase): | ||||
|     def setUp(self): | ||||
|         self.admin_password = generate_key() | ||||
|         super().setUp() | ||||
|  | ||||
|     def get_container_specs(self) -> dict[str, Any] | None: | ||||
|         return { | ||||
|             "image": "ghcr.io/beryju/test-samba-dc:latest", | ||||
|             "detach": True, | ||||
|             "cap_add": ["SYS_ADMIN"], | ||||
|             "ports": { | ||||
|         self.samba = self.run_container( | ||||
|             image="ghcr.io/beryju/test-samba-dc:latest", | ||||
|             cap_add=["SYS_ADMIN"], | ||||
|             ports={ | ||||
|                 "389": "389/tcp", | ||||
|             }, | ||||
|             "auto_remove": True, | ||||
|             "environment": { | ||||
|             environment={ | ||||
|                 "SMB_DOMAIN": "test.goauthentik.io", | ||||
|                 "SMB_NETBIOS": "goauthentik", | ||||
|                 "SMB_ADMIN_PASSWORD": self.admin_password, | ||||
|             }, | ||||
|         } | ||||
|         ) | ||||
|  | ||||
|     @retry(exceptions=[LDAPSessionTerminatedByServerError]) | ||||
|     @apply_blueprint( | ||||
| @ -148,7 +142,7 @@ class TestSourceLDAPSamba(SeleniumTestCase): | ||||
|         UserLDAPSynchronizer(source).sync_full() | ||||
|         username = "bob" | ||||
|         password = generate_id() | ||||
|         result = self.container.exec_run( | ||||
|         result = self.samba.exec_run( | ||||
|             ["samba-tool", "user", "setpassword", username, "--newpassword", password] | ||||
|         ) | ||||
|         self.assertEqual(result.exit_code, 0) | ||||
| @ -163,7 +157,7 @@ class TestSourceLDAPSamba(SeleniumTestCase): | ||||
|         self.assertTrue(user.check_password(password)) | ||||
|         # Set new password | ||||
|         new_password = generate_id() | ||||
|         result = self.container.exec_run( | ||||
|         result = self.samba.exec_run( | ||||
|             ["samba-tool", "user", "setpassword", username, "--newpassword", new_password] | ||||
|         ) | ||||
|         self.assertEqual(result.exit_code, 0) | ||||
|  | ||||
| @ -56,14 +56,10 @@ class TestSourceOAuth1(SeleniumTestCase): | ||||
|         self.client_secret = generate_key() | ||||
|         self.source_slug = generate_id() | ||||
|         super().setUp() | ||||
|  | ||||
|     def get_container_specs(self) -> dict[str, Any] | None: | ||||
|         return { | ||||
|             "image": "ghcr.io/beryju/oauth1-test-server:v1.1", | ||||
|             "detach": True, | ||||
|             "ports": {"5000": "5001"}, | ||||
|             "auto_remove": True, | ||||
|             "environment": { | ||||
|         self.run_container( | ||||
|             image="ghcr.io/beryju/oauth1-test-server:v1.1", | ||||
|             ports={"5000": "5001"}, | ||||
|             environment={ | ||||
|                 "OAUTH1_CLIENT_ID": self.client_id, | ||||
|                 "OAUTH1_CLIENT_SECRET": self.client_secret, | ||||
|                 "OAUTH1_REDIRECT_URI": self.url( | ||||
| @ -71,7 +67,7 @@ class TestSourceOAuth1(SeleniumTestCase): | ||||
|                     source_slug=self.source_slug, | ||||
|                 ), | ||||
|             }, | ||||
|         } | ||||
|         ) | ||||
|  | ||||
|     def create_objects(self): | ||||
|         """Create required objects""" | ||||
|  | ||||
| @ -3,15 +3,12 @@ | ||||
| from json import loads | ||||
| from pathlib import Path | ||||
| from time import sleep | ||||
| from typing import Any | ||||
|  | ||||
| from docker.models.containers import Container | ||||
| from docker.types import Healthcheck | ||||
| from selenium.webdriver.common.by import By | ||||
| from selenium.webdriver.common.keys import Keys | ||||
| from selenium.webdriver.support import expected_conditions as ec | ||||
| from selenium.webdriver.support.wait import WebDriverWait | ||||
| from yaml import safe_dump | ||||
|  | ||||
| from authentik.blueprints.tests import apply_blueprint | ||||
| from authentik.core.models import User | ||||
| @ -21,69 +18,35 @@ from authentik.sources.oauth.models import OAuthSource | ||||
| from authentik.stages.identification.models import IdentificationStage | ||||
| from tests.e2e.utils import SeleniumTestCase, retry | ||||
|  | ||||
| CONFIG_PATH = "/tmp/dex.yml"  # nosec | ||||
|  | ||||
|  | ||||
| class TestSourceOAuth2(SeleniumTestCase): | ||||
|     """test OAuth Source flow""" | ||||
|  | ||||
|     container: Container | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.client_secret = generate_key() | ||||
|         self.slug = generate_id() | ||||
|         self.prepare_dex_config() | ||||
|         super().setUp() | ||||
|  | ||||
|     def prepare_dex_config(self): | ||||
|         """Since Dex does not document which environment | ||||
|         variables can be used to configure clients""" | ||||
|         config = { | ||||
|             "enablePasswordDB": True, | ||||
|             "issuer": "http://127.0.0.1:5556/dex", | ||||
|             "logger": {"level": "debug"}, | ||||
|             "staticClients": [ | ||||
|                 { | ||||
|                     "id": "example-app", | ||||
|                     "name": "Example App", | ||||
|                     "redirectURIs": [ | ||||
|                         self.url( | ||||
|                             "authentik_sources_oauth:oauth-client-callback", | ||||
|                             source_slug=self.slug, | ||||
|                         ) | ||||
|                     ], | ||||
|                     "secret": self.client_secret, | ||||
|                 } | ||||
|             ], | ||||
|             "staticPasswords": [ | ||||
|                 { | ||||
|                     "email": "admin@example.com", | ||||
|                     # hash for password | ||||
|                     "hash": "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W", | ||||
|                     "userID": "08a8684b-db88-4b73-90a9-3cd1661f5466", | ||||
|                     "username": "admin", | ||||
|                 } | ||||
|             ], | ||||
|             "storage": {"config": {"file": "/tmp/dex.db"}, "type": "sqlite3"},  # nosec | ||||
|             "web": {"http": "0.0.0.0:5556"}, | ||||
|         } | ||||
|         with open(CONFIG_PATH, "w+", encoding="utf8") as _file: | ||||
|             safe_dump(config, _file) | ||||
|  | ||||
|     def get_container_specs(self) -> dict[str, Any] | None: | ||||
|         return { | ||||
|             "image": "ghcr.io/dexidp/dex:v2.28.1", | ||||
|             "detach": True, | ||||
|             "ports": {"5556": "5556"}, | ||||
|             "auto_remove": True, | ||||
|             "command": "dex serve /config.yml", | ||||
|             "healthcheck": Healthcheck( | ||||
|         self.run_container( | ||||
|             image="ghcr.io/dexidp/dex:v2.28.1", | ||||
|             ports={"5556": "5556"}, | ||||
|             healthcheck=Healthcheck( | ||||
|                 test=["CMD", "wget", "--spider", "http://localhost:5556/dex/healthz"], | ||||
|                 interval=5 * 1_000 * 1_000_000, | ||||
|                 start_period=1 * 1_000 * 1_000_000, | ||||
|             ), | ||||
|             "volumes": {str(Path(CONFIG_PATH).absolute()): {"bind": "/config.yml", "mode": "ro"}}, | ||||
|         } | ||||
|             environment={ | ||||
|                 "AK_REDIRECT_URL": self.url( | ||||
|                     "authentik_sources_oauth:oauth-client-callback", | ||||
|                     source_slug=self.slug, | ||||
|                 ), | ||||
|                 "AK_CLIENT_SECRET": self.client_secret, | ||||
|             }, | ||||
|             volumes={ | ||||
|                 f"{Path(__file__).parent / "sources_oauth2_dex" / "dex.yaml"}": { | ||||
|                     "bind": "/etc/dex/config.docker.yaml", | ||||
|                 } | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     def create_objects(self): | ||||
|         """Create required objects""" | ||||
|  | ||||
| @ -2,7 +2,6 @@ | ||||
|  | ||||
| from pathlib import Path | ||||
| from time import sleep | ||||
| from typing import Any | ||||
|  | ||||
| from docker.types import Healthcheck | ||||
| from selenium.webdriver.common.by import By | ||||
| @ -77,19 +76,15 @@ class TestSourceSAML(SeleniumTestCase): | ||||
|     def setUp(self): | ||||
|         self.slug = generate_id() | ||||
|         super().setUp() | ||||
|  | ||||
|     def get_container_specs(self) -> dict[str, Any] | None: | ||||
|         return { | ||||
|             "image": "kristophjunge/test-saml-idp:1.15", | ||||
|             "detach": True, | ||||
|             "ports": {"8080": "8080"}, | ||||
|             "auto_remove": True, | ||||
|             "healthcheck": Healthcheck( | ||||
|         self.run_container( | ||||
|             image="kristophjunge/test-saml-idp:1.15", | ||||
|             ports={"8080": "8080"}, | ||||
|             healthcheck=Healthcheck( | ||||
|                 test=["CMD", "curl", "http://localhost:8080"], | ||||
|                 interval=5 * 1_000 * 1_000_000, | ||||
|                 start_period=1 * 1_000 * 1_000_000, | ||||
|             ), | ||||
|             "volumes": { | ||||
|             volumes={ | ||||
|                 str( | ||||
|                     (Path(__file__).parent / Path("test-saml-idp/saml20-sp-remote.php")).absolute() | ||||
|                 ): { | ||||
| @ -97,7 +92,7 @@ class TestSourceSAML(SeleniumTestCase): | ||||
|                     "mode": "ro", | ||||
|                 } | ||||
|             }, | ||||
|             "environment": { | ||||
|             environment={ | ||||
|                 "SIMPLESAMLPHP_SP_ENTITY_ID": "entity-id", | ||||
|                 "SIMPLESAMLPHP_SP_NAME_ID_FORMAT": ( | ||||
|                     "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" | ||||
| @ -107,7 +102,7 @@ class TestSourceSAML(SeleniumTestCase): | ||||
|                     self.url("authentik_sources_saml:acs", source_slug=self.slug) | ||||
|                 ), | ||||
|             }, | ||||
|         } | ||||
|         ) | ||||
|  | ||||
|     @retry() | ||||
|     @apply_blueprint( | ||||
|  | ||||
| @ -2,7 +2,6 @@ | ||||
|  | ||||
| from pprint import pformat | ||||
| from time import sleep | ||||
| from typing import Any | ||||
|  | ||||
| from docker.types import Healthcheck | ||||
|  | ||||
| @ -20,22 +19,18 @@ class TestSourceSCIM(SeleniumTestCase): | ||||
|     def setUp(self): | ||||
|         self.slug = generate_id() | ||||
|         super().setUp() | ||||
|  | ||||
|     def get_container_specs(self) -> dict[str, Any] | None: | ||||
|         return { | ||||
|             "image": ( | ||||
|         self.run_container( | ||||
|             image=( | ||||
|                 "ghcr.io/suvera/scim2-compliance-test-utility@sha256:eca913bb73" | ||||
|                 "c46892cd1fb2dfd2fef1c5881e6abc5cb0eec7e92fb78c1b933ece" | ||||
|             ), | ||||
|             "detach": True, | ||||
|             "ports": {"8080": "8080"}, | ||||
|             "auto_remove": True, | ||||
|             "healthcheck": Healthcheck( | ||||
|             ports={"8080": "8080"}, | ||||
|             healthcheck=Healthcheck( | ||||
|                 test=["CMD", "curl", "http://localhost:8080"], | ||||
|                 interval=5 * 1_000 * 1_000_000, | ||||
|                 start_period=1 * 1_000 * 1_000_000, | ||||
|             ), | ||||
|         } | ||||
|         ) | ||||
|  | ||||
|     @retry() | ||||
|     def test_scim_conformance(self): | ||||
|  | ||||
| @ -9,6 +9,7 @@ from os import environ | ||||
| from sys import stderr | ||||
| from time import sleep | ||||
| from typing import Any | ||||
| from unittest.case import TestCase | ||||
|  | ||||
| from django.apps import apps | ||||
| from django.contrib.staticfiles.testing import StaticLiveServerTestCase | ||||
| @ -19,6 +20,7 @@ from django.urls import reverse | ||||
| from docker import DockerClient, from_env | ||||
| from docker.errors import DockerException | ||||
| from docker.models.containers import Container | ||||
| from docker.models.networks import Network | ||||
| from selenium import webdriver | ||||
| from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException | ||||
| from selenium.webdriver.common.by import By | ||||
| @ -31,6 +33,7 @@ from structlog.stdlib import get_logger | ||||
| from authentik.core.api.users import UserSerializer | ||||
| from authentik.core.models import User | ||||
| from authentik.core.tests.utils import create_test_admin_user | ||||
| from authentik.lib.generators import generate_id | ||||
|  | ||||
| RETRIES = int(environ.get("RETRIES", "3")) | ||||
| IS_CI = "CI" in environ | ||||
| @ -54,9 +57,32 @@ def get_local_ip() -> str: | ||||
|     return ip_addr | ||||
|  | ||||
|  | ||||
| class DockerTestCase: | ||||
| class DockerTestCase(TestCase): | ||||
|     """Mixin for dealing with containers""" | ||||
|  | ||||
|     max_healthcheck_attempts = 30 | ||||
|  | ||||
|     __client: DockerClient | ||||
|     __network: Network | ||||
|  | ||||
|     __label_id = generate_id() | ||||
|  | ||||
|     def setUp(self) -> None: | ||||
|         self.__client = from_env() | ||||
|         self.__network = self.docker_client.networks.create(name=f"authentik-test-{generate_id()}") | ||||
|  | ||||
|     @property | ||||
|     def docker_client(self) -> DockerClient: | ||||
|         return self.__client | ||||
|  | ||||
|     @property | ||||
|     def docker_network(self) -> Network: | ||||
|         return self.__network | ||||
|  | ||||
|     @property | ||||
|     def docker_labels(self) -> dict: | ||||
|         return {"io.goauthentik.test": self.__label_id} | ||||
|  | ||||
|     def wait_for_container(self, container: Container): | ||||
|         """Check that container is health""" | ||||
|         attempt = 0 | ||||
| @ -67,47 +93,30 @@ class DockerTestCase: | ||||
|                 return container | ||||
|             sleep(1) | ||||
|             attempt += 1 | ||||
|             if attempt >= 30:  # noqa: PLR2004 | ||||
|             if attempt >= self.max_healthcheck_attempts: | ||||
|                 self.failureException("Container failed to start") | ||||
|  | ||||
|  | ||||
| class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase): | ||||
|     """StaticLiveServerTestCase which automatically creates a Webdriver instance""" | ||||
|  | ||||
|     host = get_local_ip() | ||||
|     container: Container | None = None | ||||
|     wait_timeout: int | ||||
|     user: User | ||||
|  | ||||
|     def setUp(self): | ||||
|         if IS_CI: | ||||
|             print("::group::authentik Logs", file=stderr) | ||||
|         super().setUp() | ||||
|         apps.get_app_config("authentik_tenants").ready() | ||||
|         self.wait_timeout = 60 | ||||
|         self.driver = self._get_driver() | ||||
|         self.driver.implicitly_wait(30) | ||||
|         self.wait = WebDriverWait(self.driver, self.wait_timeout) | ||||
|         self.logger = get_logger() | ||||
|         self.user = create_test_admin_user() | ||||
|         if specs := self.get_container_specs(): | ||||
|             self.container = self._start_container(specs) | ||||
|  | ||||
|     def get_container_image(self, base: str) -> str: | ||||
|         """Try to pull docker image based on git branch, fallback to main if not found.""" | ||||
|         client: DockerClient = from_env() | ||||
|         image = f"{base}:gh-main" | ||||
|         try: | ||||
|             branch_image = f"{base}:{get_docker_tag()}" | ||||
|             client.images.pull(branch_image) | ||||
|             self.docker_client.images.pull(branch_image) | ||||
|             return branch_image | ||||
|         except DockerException: | ||||
|             client.images.pull(image) | ||||
|             self.docker_client.images.pull(image) | ||||
|         return image | ||||
|  | ||||
|     def _start_container(self, specs: dict[str, Any]) -> Container: | ||||
|         client: DockerClient = from_env() | ||||
|         container = client.containers.run(**specs) | ||||
|     def run_container(self, **specs: dict[str, Any]) -> Container: | ||||
|         if "network_mode" not in specs: | ||||
|             specs["network"] = self.__network.name | ||||
|         specs["labels"] = self.docker_labels | ||||
|         specs["detach"] = True | ||||
|         specs["auto_remove"] = True | ||||
|         if hasattr(self, "live_server_url"): | ||||
|             specs.setdefault("environment", {}) | ||||
|             specs["environment"]["AUTHENTIK_HOST"] = self.live_server_url | ||||
|         container = self.docker_client.containers.run(**specs) | ||||
|         container.reload() | ||||
|         state = container.attrs.get("State", {}) | ||||
|         if "Health" not in state: | ||||
| @ -117,25 +126,53 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase): | ||||
|  | ||||
|     def output_container_logs(self, container: Container | None = None): | ||||
|         """Output the container logs to our STDOUT""" | ||||
|         _container = container or self.container | ||||
|         if IS_CI: | ||||
|             image = _container.image | ||||
|             image = container.image | ||||
|             tags = image.tags[0] if len(image.tags) > 0 else str(image) | ||||
|             print(f"::group::Container logs - {tags}") | ||||
|         for log in _container.logs().decode().split("\n"): | ||||
|         for log in container.logs().decode().split("\n"): | ||||
|             print(log) | ||||
|         if IS_CI: | ||||
|             print("::endgroup::") | ||||
|  | ||||
|     def get_container_specs(self) -> dict[str, Any] | None: | ||||
|         """Optionally get container specs which will launched on setup, wait for the container to | ||||
|         be healthy, and deleted again on tearDown""" | ||||
|         return None | ||||
|     def tearDown(self): | ||||
|         containers = self.docker_client.containers.list( | ||||
|             filters={"label": ",".join(f"{x}={y}" for x, y in self.docker_labels.items())} | ||||
|         ) | ||||
|         for container in containers: | ||||
|             self.output_container_logs(container) | ||||
|             try: | ||||
|                 container.kill() | ||||
|             except DockerException: | ||||
|                 pass | ||||
|         self.__network.remove() | ||||
|  | ||||
|  | ||||
| class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase): | ||||
|     """StaticLiveServerTestCase which automatically creates a Webdriver instance""" | ||||
|  | ||||
|     host = get_local_ip() | ||||
|     wait_timeout: int | ||||
|     user: User | ||||
|  | ||||
|     def setUp(self): | ||||
|         if IS_CI: | ||||
|             print("::group::authentik Logs", file=stderr) | ||||
|         apps.get_app_config("authentik_tenants").ready() | ||||
|         self.wait_timeout = 60 | ||||
|         self.driver = self._get_driver() | ||||
|         self.driver.implicitly_wait(30) | ||||
|         self.wait = WebDriverWait(self.driver, self.wait_timeout) | ||||
|         self.logger = get_logger() | ||||
|         self.user = create_test_admin_user() | ||||
|         super().setUp() | ||||
|  | ||||
|     def _get_driver(self) -> WebDriver: | ||||
|         count = 0 | ||||
|         try: | ||||
|             return webdriver.Chrome() | ||||
|             opts = webdriver.ChromeOptions() | ||||
|             opts.add_argument("--disable-search-engine-choice-screen") | ||||
|             return webdriver.Chrome(options=opts) | ||||
|         except WebDriverException: | ||||
|             pass | ||||
|         while count < RETRIES: | ||||
| @ -151,18 +188,15 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase): | ||||
|         raise ValueError(f"Webdriver failed after {RETRIES}.") | ||||
|  | ||||
|     def tearDown(self): | ||||
|         super().tearDown() | ||||
|         if IS_CI: | ||||
|             print("::endgroup::", file=stderr) | ||||
|         super().tearDown() | ||||
|         if IS_CI: | ||||
|             print("::group::Browser logs") | ||||
|         for line in self.driver.get_log("browser"): | ||||
|             print(line["message"]) | ||||
|         if IS_CI: | ||||
|             print("::endgroup::") | ||||
|         if self.container: | ||||
|             self.output_container_logs() | ||||
|             self.container.kill() | ||||
|         self.driver.quit() | ||||
|  | ||||
|     def wait_for_url(self, desired_url): | ||||
|  | ||||
| @ -6,8 +6,6 @@ from tempfile import mkdtemp | ||||
| import pytest | ||||
| import yaml | ||||
| from channels.testing import ChannelsLiveServerTestCase | ||||
| from docker import DockerClient, from_env | ||||
| from docker.models.containers import Container | ||||
| from docker.types.healthcheck import Healthcheck | ||||
|  | ||||
| from authentik.core.tests.utils import create_test_flow | ||||
| @ -27,11 +25,11 @@ from tests.e2e.utils import DockerTestCase, get_docker_tag | ||||
| class OutpostDockerTests(DockerTestCase, ChannelsLiveServerTestCase): | ||||
|     """Test Docker Controllers""" | ||||
|  | ||||
|     def _start_container(self, ssl_folder: str) -> Container: | ||||
|         client: DockerClient = from_env() | ||||
|         container = client.containers.run( | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         self.ssl_folder = mkdtemp() | ||||
|         self.run_container( | ||||
|             image="library/docker:dind", | ||||
|             detach=True, | ||||
|             network_mode="host", | ||||
|             privileged=True, | ||||
|             healthcheck=Healthcheck( | ||||
| @ -41,18 +39,11 @@ class OutpostDockerTests(DockerTestCase, ChannelsLiveServerTestCase): | ||||
|             ), | ||||
|             environment={"DOCKER_TLS_CERTDIR": "/ssl"}, | ||||
|             volumes={ | ||||
|                 f"{ssl_folder}/": { | ||||
|                 f"{self.ssl_folder}/": { | ||||
|                     "bind": "/ssl", | ||||
|                 } | ||||
|             }, | ||||
|         ) | ||||
|         self.wait_for_container(container) | ||||
|         return container | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         self.ssl_folder = mkdtemp() | ||||
|         self.container = self._start_container(self.ssl_folder) | ||||
|         # Ensure that local connection have been created | ||||
|         outpost_connection_discovery() | ||||
|         self.provider: ProxyProvider = ProxyProvider.objects.create( | ||||
| @ -91,7 +82,6 @@ class OutpostDockerTests(DockerTestCase, ChannelsLiveServerTestCase): | ||||
|  | ||||
|     def tearDown(self) -> None: | ||||
|         super().tearDown() | ||||
|         self.container.kill() | ||||
|         try: | ||||
|             rmtree(self.ssl_folder) | ||||
|         except PermissionError: | ||||
|  | ||||
| @ -6,8 +6,6 @@ from tempfile import mkdtemp | ||||
| import pytest | ||||
| import yaml | ||||
| from channels.testing.live import ChannelsLiveServerTestCase | ||||
| from docker import DockerClient, from_env | ||||
| from docker.models.containers import Container | ||||
| from docker.types.healthcheck import Healthcheck | ||||
|  | ||||
| from authentik.core.tests.utils import create_test_flow | ||||
| @ -27,11 +25,11 @@ from tests.e2e.utils import DockerTestCase, get_docker_tag | ||||
| class TestProxyDocker(DockerTestCase, ChannelsLiveServerTestCase): | ||||
|     """Test Docker Controllers""" | ||||
|  | ||||
|     def _start_container(self, ssl_folder: str) -> Container: | ||||
|         client: DockerClient = from_env() | ||||
|         container = client.containers.run( | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         self.ssl_folder = mkdtemp() | ||||
|         self.run_container( | ||||
|             image="library/docker:dind", | ||||
|             detach=True, | ||||
|             network_mode="host", | ||||
|             privileged=True, | ||||
|             healthcheck=Healthcheck( | ||||
| @ -41,18 +39,11 @@ class TestProxyDocker(DockerTestCase, ChannelsLiveServerTestCase): | ||||
|             ), | ||||
|             environment={"DOCKER_TLS_CERTDIR": "/ssl"}, | ||||
|             volumes={ | ||||
|                 f"{ssl_folder}/": { | ||||
|                 f"{self.ssl_folder}/": { | ||||
|                     "bind": "/ssl", | ||||
|                 } | ||||
|             }, | ||||
|         ) | ||||
|         self.wait_for_container(container) | ||||
|         return container | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         self.ssl_folder = mkdtemp() | ||||
|         self.container = self._start_container(self.ssl_folder) | ||||
|         # Ensure that local connection have been created | ||||
|         outpost_connection_discovery() | ||||
|         self.provider: ProxyProvider = ProxyProvider.objects.create( | ||||
| @ -91,7 +82,6 @@ class TestProxyDocker(DockerTestCase, ChannelsLiveServerTestCase): | ||||
|  | ||||
|     def tearDown(self) -> None: | ||||
|         super().tearDown() | ||||
|         self.container.kill() | ||||
|         try: | ||||
|             rmtree(self.ssl_folder) | ||||
|         except PermissionError: | ||||
|  | ||||
| @ -32,7 +32,7 @@ http: | ||||
|         app: | ||||
|             loadBalancer: | ||||
|                 servers: | ||||
|                     - url: http://ipp.internal | ||||
|                     - url: http://ip.internal | ||||
|         authentik: | ||||
|             loadBalancer: | ||||
|                 servers: | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens L.
					Jens L.