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
|
uses: ./.github/actions/setup
|
||||||
- name: Setup e2e env (chrome, etc)
|
- name: Setup e2e env (chrome, etc)
|
||||||
run: |
|
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
|
- id: cache-web
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
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 dataclasses import asdict
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from docker.client import DockerClient, from_env
|
|
||||||
from docker.models.containers import Container
|
|
||||||
from guardian.shortcuts import assign_perm
|
from guardian.shortcuts import assign_perm
|
||||||
from ldap3 import ALL, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, SUBTREE, Connection, Server
|
from ldap3 import ALL, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, SUBTREE, Connection, Server
|
||||||
from ldap3.core.exceptions import LDAPInvalidCredentialsResult
|
from ldap3.core.exceptions import LDAPInvalidCredentialsResult
|
||||||
@ -24,29 +22,18 @@ from tests.e2e.utils import SeleniumTestCase, retry
|
|||||||
class TestProviderLDAP(SeleniumTestCase):
|
class TestProviderLDAP(SeleniumTestCase):
|
||||||
"""LDAP and Outpost e2e tests"""
|
"""LDAP and Outpost e2e tests"""
|
||||||
|
|
||||||
ldap_container: Container
|
def start_ldap(self, outpost: Outpost):
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
|
||||||
super().tearDown()
|
|
||||||
self.output_container_logs(self.ldap_container)
|
|
||||||
self.ldap_container.kill()
|
|
||||||
|
|
||||||
def start_ldap(self, outpost: Outpost) -> Container:
|
|
||||||
"""Start ldap container based on outpost created"""
|
"""Start ldap container based on outpost created"""
|
||||||
client: DockerClient = from_env()
|
self.run_container(
|
||||||
container = client.containers.run(
|
|
||||||
image=self.get_container_image("ghcr.io/goauthentik/dev-ldap"),
|
image=self.get_container_image("ghcr.io/goauthentik/dev-ldap"),
|
||||||
detach=True,
|
|
||||||
ports={
|
ports={
|
||||||
"3389": "3389",
|
"3389": "3389",
|
||||||
"6636": "6636",
|
"6636": "6636",
|
||||||
},
|
},
|
||||||
environment={
|
environment={
|
||||||
"AUTHENTIK_HOST": self.live_server_url,
|
|
||||||
"AUTHENTIK_TOKEN": outpost.token.key,
|
"AUTHENTIK_TOKEN": outpost.token.key,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return container
|
|
||||||
|
|
||||||
def _prepare(self) -> User:
|
def _prepare(self) -> User:
|
||||||
"""prepare user, provider, app and container"""
|
"""prepare user, provider, app and container"""
|
||||||
@ -68,7 +55,7 @@ class TestProviderLDAP(SeleniumTestCase):
|
|||||||
)
|
)
|
||||||
outpost.providers.add(ldap)
|
outpost.providers.add(ldap)
|
||||||
|
|
||||||
self.ldap_container = self.start_ldap(outpost)
|
self.start_ldap(outpost)
|
||||||
|
|
||||||
# Wait until outpost healthcheck succeeds
|
# Wait until outpost healthcheck succeeds
|
||||||
healthcheck_retries = 0
|
healthcheck_retries = 0
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"""test OAuth Provider flow"""
|
"""test OAuth Provider flow"""
|
||||||
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from docker.types import Healthcheck
|
from docker.types import Healthcheck
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
@ -24,22 +23,17 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
|||||||
self.client_id = generate_id()
|
self.client_id = generate_id()
|
||||||
self.client_secret = generate_key()
|
self.client_secret = generate_key()
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
self.run_container(
|
||||||
def get_container_specs(self) -> dict[str, Any] | None:
|
image="grafana/grafana:7.1.0",
|
||||||
"""Setup client grafana container which we test OAuth against"""
|
ports={
|
||||||
return {
|
|
||||||
"image": "grafana/grafana:7.1.0",
|
|
||||||
"detach": True,
|
|
||||||
"ports": {
|
|
||||||
"3000": "3000",
|
"3000": "3000",
|
||||||
},
|
},
|
||||||
"auto_remove": True,
|
healthcheck=Healthcheck(
|
||||||
"healthcheck": Healthcheck(
|
|
||||||
test=["CMD", "wget", "--spider", "http://localhost:3000"],
|
test=["CMD", "wget", "--spider", "http://localhost:3000"],
|
||||||
interval=5 * 1_000 * 1_000_000,
|
interval=5 * 1_000 * 1_000_000,
|
||||||
start_period=1 * 1_000 * 1_000_000,
|
start_period=1 * 1_000 * 1_000_000,
|
||||||
),
|
),
|
||||||
"environment": {
|
environment={
|
||||||
"GF_AUTH_GITHUB_ENABLED": "true",
|
"GF_AUTH_GITHUB_ENABLED": "true",
|
||||||
"GF_AUTH_GITHUB_ALLOW_SIGN_UP": "true",
|
"GF_AUTH_GITHUB_ALLOW_SIGN_UP": "true",
|
||||||
"GF_AUTH_GITHUB_CLIENT_ID": self.client_id,
|
"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_AUTH_GITHUB_API_URL": self.url("authentik_providers_oauth2_root:github-user"),
|
||||||
"GF_LOG_LEVEL": "debug",
|
"GF_LOG_LEVEL": "debug",
|
||||||
},
|
},
|
||||||
}
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"""test OAuth2 OpenID Provider flow"""
|
"""test OAuth2 OpenID Provider flow"""
|
||||||
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from docker.types import Healthcheck
|
from docker.types import Healthcheck
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
@ -32,21 +31,17 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||||||
self.client_secret = generate_key()
|
self.client_secret = generate_key()
|
||||||
self.app_slug = generate_id(20)
|
self.app_slug = generate_id(20)
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
self.run_container(
|
||||||
def get_container_specs(self) -> dict[str, Any] | None:
|
image="grafana/grafana:7.1.0",
|
||||||
return {
|
ports={
|
||||||
"image": "grafana/grafana:7.1.0",
|
"3000": "3000",
|
||||||
"detach": True,
|
},
|
||||||
"auto_remove": True,
|
healthcheck=Healthcheck(
|
||||||
"healthcheck": Healthcheck(
|
|
||||||
test=["CMD", "wget", "--spider", "http://localhost:3000"],
|
test=["CMD", "wget", "--spider", "http://localhost:3000"],
|
||||||
interval=5 * 1_000 * 1_000_000,
|
interval=5 * 1_000 * 1_000_000,
|
||||||
start_period=1 * 1_000 * 1_000_000,
|
start_period=1 * 1_000 * 1_000_000,
|
||||||
),
|
),
|
||||||
"ports": {
|
environment={
|
||||||
"3000": "3000",
|
|
||||||
},
|
|
||||||
"environment": {
|
|
||||||
"GF_AUTH_GENERIC_OAUTH_ENABLED": "true",
|
"GF_AUTH_GENERIC_OAUTH_ENABLED": "true",
|
||||||
"GF_AUTH_GENERIC_OAUTH_CLIENT_ID": self.client_id,
|
"GF_AUTH_GENERIC_OAUTH_CLIENT_ID": self.client_id,
|
||||||
"GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET": self.client_secret,
|
"GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET": self.client_secret,
|
||||||
@ -60,7 +55,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||||||
),
|
),
|
||||||
"GF_LOG_LEVEL": "debug",
|
"GF_LOG_LEVEL": "debug",
|
||||||
},
|
},
|
||||||
}
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
from json import loads
|
from json import loads
|
||||||
from time import sleep
|
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.common.by import By
|
||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
|
|
||||||
@ -34,13 +32,11 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||||||
self.application_slug = generate_id()
|
self.application_slug = generate_id()
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
def setup_client(self) -> Container:
|
def setup_client(self):
|
||||||
"""Setup client oidc-test-client container which we test OIDC against"""
|
"""Setup client oidc-test-client container which we test OIDC against"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
client: DockerClient = from_env()
|
self.run_container(
|
||||||
container = client.containers.run(
|
|
||||||
image="ghcr.io/beryju/oidc-test-client:2.1",
|
image="ghcr.io/beryju/oidc-test-client:2.1",
|
||||||
detach=True,
|
|
||||||
ports={
|
ports={
|
||||||
"9009": "9009",
|
"9009": "9009",
|
||||||
},
|
},
|
||||||
@ -50,8 +46,6 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||||||
"OIDC_PROVIDER": f"{self.live_server_url}/application/o/{self.application_slug}/",
|
"OIDC_PROVIDER": f"{self.live_server_url}/application/o/{self.application_slug}/",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.wait_for_container(container)
|
|
||||||
return container
|
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
@ -91,7 +85,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||||||
slug=self.application_slug,
|
slug=self.application_slug,
|
||||||
provider=provider,
|
provider=provider,
|
||||||
)
|
)
|
||||||
self.container = self.setup_client()
|
self.setup_client()
|
||||||
|
|
||||||
self.driver.get("http://localhost:9009")
|
self.driver.get("http://localhost:9009")
|
||||||
sleep(2)
|
sleep(2)
|
||||||
@ -140,7 +134,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||||||
slug=self.application_slug,
|
slug=self.application_slug,
|
||||||
provider=provider,
|
provider=provider,
|
||||||
)
|
)
|
||||||
self.container = self.setup_client()
|
self.setup_client()
|
||||||
|
|
||||||
self.driver.get("http://localhost:9009")
|
self.driver.get("http://localhost:9009")
|
||||||
self.login()
|
self.login()
|
||||||
@ -210,7 +204,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||||||
slug=self.application_slug,
|
slug=self.application_slug,
|
||||||
provider=provider,
|
provider=provider,
|
||||||
)
|
)
|
||||||
self.container = self.setup_client()
|
self.setup_client()
|
||||||
|
|
||||||
self.driver.get("http://localhost:9009")
|
self.driver.get("http://localhost:9009")
|
||||||
self.login()
|
self.login()
|
||||||
@ -287,7 +281,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||||||
)
|
)
|
||||||
PolicyBinding.objects.create(target=app, policy=negative_policy, order=0)
|
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.driver.get("http://localhost:9009")
|
||||||
self.login()
|
self.login()
|
||||||
self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "header > h1")))
|
self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "header > h1")))
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
from json import loads
|
from json import loads
|
||||||
from time import sleep
|
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.common.by import By
|
||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
|
|
||||||
@ -34,13 +32,11 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||||||
self.application_slug = "test"
|
self.application_slug = "test"
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
def setup_client(self) -> Container:
|
def setup_client(self):
|
||||||
"""Setup client oidc-test-client container which we test OIDC against"""
|
"""Setup client oidc-test-client container which we test OIDC against"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
client: DockerClient = from_env()
|
self.run_container(
|
||||||
container = client.containers.run(
|
|
||||||
image="ghcr.io/beryju/oidc-test-client:2.1",
|
image="ghcr.io/beryju/oidc-test-client:2.1",
|
||||||
detach=True,
|
|
||||||
ports={
|
ports={
|
||||||
"9009": "9009",
|
"9009": "9009",
|
||||||
},
|
},
|
||||||
@ -50,8 +46,6 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||||||
"OIDC_PROVIDER": f"{self.live_server_url}/application/o/{self.application_slug}/",
|
"OIDC_PROVIDER": f"{self.live_server_url}/application/o/{self.application_slug}/",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.wait_for_container(container)
|
|
||||||
return container
|
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
@ -93,7 +87,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||||||
slug=self.application_slug,
|
slug=self.application_slug,
|
||||||
provider=provider,
|
provider=provider,
|
||||||
)
|
)
|
||||||
self.container = self.setup_client()
|
self.setup_client()
|
||||||
|
|
||||||
self.driver.get("http://localhost:9009/implicit/")
|
self.driver.get("http://localhost:9009/implicit/")
|
||||||
sleep(2)
|
sleep(2)
|
||||||
@ -142,7 +136,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||||||
slug=self.application_slug,
|
slug=self.application_slug,
|
||||||
provider=provider,
|
provider=provider,
|
||||||
)
|
)
|
||||||
self.container = self.setup_client()
|
self.setup_client()
|
||||||
|
|
||||||
self.driver.get("http://localhost:9009/implicit/")
|
self.driver.get("http://localhost:9009/implicit/")
|
||||||
self.wait.until(ec.title_contains("authentik"))
|
self.wait.until(ec.title_contains("authentik"))
|
||||||
@ -194,7 +188,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||||||
slug=self.application_slug,
|
slug=self.application_slug,
|
||||||
provider=provider,
|
provider=provider,
|
||||||
)
|
)
|
||||||
self.container = self.setup_client()
|
self.setup_client()
|
||||||
|
|
||||||
self.driver.get("http://localhost:9009/implicit/")
|
self.driver.get("http://localhost:9009/implicit/")
|
||||||
self.wait.until(ec.title_contains("authentik"))
|
self.wait.until(ec.title_contains("authentik"))
|
||||||
@ -268,7 +262,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||||||
)
|
)
|
||||||
PolicyBinding.objects.create(target=app, policy=negative_policy, order=0)
|
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.driver.get("http://localhost:9009/implicit/")
|
||||||
self.wait.until(ec.title_contains("authentik"))
|
self.wait.until(ec.title_contains("authentik"))
|
||||||
self.login()
|
self.login()
|
||||||
|
@ -2,14 +2,12 @@
|
|||||||
|
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
|
from json import loads
|
||||||
from sys import platform
|
from sys import platform
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Any
|
|
||||||
from unittest.case import skip, skipUnless
|
from unittest.case import skip, skipUnless
|
||||||
|
|
||||||
from channels.testing import ChannelsLiveServerTestCase
|
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 selenium.webdriver.common.by import By
|
||||||
|
|
||||||
from authentik.blueprints.tests import apply_blueprint, reconcile_app
|
from authentik.blueprints.tests import apply_blueprint, reconcile_app
|
||||||
@ -25,38 +23,26 @@ from tests.e2e.utils import SeleniumTestCase, retry
|
|||||||
class TestProviderProxy(SeleniumTestCase):
|
class TestProviderProxy(SeleniumTestCase):
|
||||||
"""Proxy and Outpost e2e tests"""
|
"""Proxy and Outpost e2e tests"""
|
||||||
|
|
||||||
proxy_container: Container
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
def tearDown(self) -> None:
|
self.run_container(
|
||||||
super().tearDown()
|
image="traefik/whoami:latest",
|
||||||
self.output_container_logs(self.proxy_container)
|
ports={
|
||||||
self.proxy_container.kill()
|
|
||||||
|
|
||||||
def get_container_specs(self) -> dict[str, Any] | None:
|
|
||||||
return {
|
|
||||||
"image": "traefik/whoami:latest",
|
|
||||||
"detach": True,
|
|
||||||
"ports": {
|
|
||||||
"80": "80",
|
"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"""
|
"""Start proxy container based on outpost created"""
|
||||||
client: DockerClient = from_env()
|
self.run_container(
|
||||||
container = client.containers.run(
|
|
||||||
image=self.get_container_image("ghcr.io/goauthentik/dev-proxy"),
|
image=self.get_container_image("ghcr.io/goauthentik/dev-proxy"),
|
||||||
detach=True,
|
|
||||||
ports={
|
ports={
|
||||||
"9000": "9000",
|
"9000": "9000",
|
||||||
},
|
},
|
||||||
environment={
|
environment={
|
||||||
"AUTHENTIK_HOST": self.live_server_url,
|
|
||||||
"AUTHENTIK_TOKEN": outpost.token.key,
|
"AUTHENTIK_TOKEN": outpost.token.key,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return container
|
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
@ -99,7 +85,7 @@ class TestProviderProxy(SeleniumTestCase):
|
|||||||
outpost.providers.add(proxy)
|
outpost.providers.add(proxy)
|
||||||
outpost.build_user_permissions(outpost.user)
|
outpost.build_user_permissions(outpost.user)
|
||||||
|
|
||||||
self.proxy_container = self.start_proxy(outpost)
|
self.start_proxy(outpost)
|
||||||
|
|
||||||
# Wait until outpost healthcheck succeeds
|
# Wait until outpost healthcheck succeeds
|
||||||
healthcheck_retries = 0
|
healthcheck_retries = 0
|
||||||
@ -112,13 +98,15 @@ class TestProviderProxy(SeleniumTestCase):
|
|||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
sleep(5)
|
sleep(5)
|
||||||
|
|
||||||
self.driver.get("http://localhost:9000")
|
self.driver.get("http://localhost:9000/api")
|
||||||
self.login()
|
self.login()
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text
|
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.assertIn("X-Foo: bar", 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")
|
self.driver.get("http://localhost:9000/outpost.goauthentik.io/sign_out")
|
||||||
sleep(2)
|
sleep(2)
|
||||||
@ -173,7 +161,7 @@ class TestProviderProxy(SeleniumTestCase):
|
|||||||
outpost.providers.add(proxy)
|
outpost.providers.add(proxy)
|
||||||
outpost.build_user_permissions(outpost.user)
|
outpost.build_user_permissions(outpost.user)
|
||||||
|
|
||||||
self.proxy_container = self.start_proxy(outpost)
|
self.start_proxy(outpost)
|
||||||
|
|
||||||
# Wait until outpost healthcheck succeeds
|
# Wait until outpost healthcheck succeeds
|
||||||
healthcheck_retries = 0
|
healthcheck_retries = 0
|
||||||
@ -186,14 +174,16 @@ class TestProviderProxy(SeleniumTestCase):
|
|||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
sleep(5)
|
sleep(5)
|
||||||
|
|
||||||
self.driver.get("http://localhost:9000")
|
self.driver.get("http://localhost:9000/api")
|
||||||
self.login()
|
self.login()
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text
|
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()
|
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")
|
self.driver.get("http://localhost:9000/outpost.goauthentik.io/sign_out")
|
||||||
sleep(2)
|
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 dataclasses import asdict
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from docker.client import DockerClient, from_env
|
|
||||||
from docker.models.containers import Container
|
|
||||||
from pyrad.client import Client
|
from pyrad.client import Client
|
||||||
from pyrad.dictionary import Dictionary
|
from pyrad.dictionary import Dictionary
|
||||||
from pyrad.packet import AccessAccept, AccessReject, AccessRequest
|
from pyrad.packet import AccessAccept, AccessReject, AccessRequest
|
||||||
@ -21,30 +19,19 @@ from tests.e2e.utils import SeleniumTestCase, retry
|
|||||||
class TestProviderRadius(SeleniumTestCase):
|
class TestProviderRadius(SeleniumTestCase):
|
||||||
"""Radius Outpost e2e tests"""
|
"""Radius Outpost e2e tests"""
|
||||||
|
|
||||||
radius_container: Container
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.shared_secret = generate_key()
|
self.shared_secret = generate_key()
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
def start_radius(self, outpost: Outpost):
|
||||||
super().tearDown()
|
|
||||||
self.output_container_logs(self.radius_container)
|
|
||||||
self.radius_container.kill()
|
|
||||||
|
|
||||||
def start_radius(self, outpost: Outpost) -> Container:
|
|
||||||
"""Start radius container based on outpost created"""
|
"""Start radius container based on outpost created"""
|
||||||
client: DockerClient = from_env()
|
self.run_container(
|
||||||
container = client.containers.run(
|
|
||||||
image=self.get_container_image("ghcr.io/goauthentik/dev-radius"),
|
image=self.get_container_image("ghcr.io/goauthentik/dev-radius"),
|
||||||
detach=True,
|
|
||||||
ports={"1812/udp": "1812/udp"},
|
ports={"1812/udp": "1812/udp"},
|
||||||
environment={
|
environment={
|
||||||
"AUTHENTIK_HOST": self.live_server_url,
|
|
||||||
"AUTHENTIK_TOKEN": outpost.token.key,
|
"AUTHENTIK_TOKEN": outpost.token.key,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return container
|
|
||||||
|
|
||||||
def _prepare(self) -> User:
|
def _prepare(self) -> User:
|
||||||
"""prepare user, provider, app and container"""
|
"""prepare user, provider, app and container"""
|
||||||
@ -62,7 +49,7 @@ class TestProviderRadius(SeleniumTestCase):
|
|||||||
)
|
)
|
||||||
outpost.providers.add(radius)
|
outpost.providers.add(radius)
|
||||||
|
|
||||||
self.radius_container = self.start_radius(outpost)
|
self.start_radius(outpost)
|
||||||
|
|
||||||
# Wait until outpost healthcheck succeeds
|
# Wait until outpost healthcheck succeeds
|
||||||
healthcheck_retries = 0
|
healthcheck_retries = 0
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
from json import loads
|
from json import loads
|
||||||
from time import sleep
|
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.common.by import By
|
||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
|
|
||||||
@ -22,11 +20,8 @@ from tests.e2e.utils import SeleniumTestCase, retry
|
|||||||
class TestProviderSAML(SeleniumTestCase):
|
class TestProviderSAML(SeleniumTestCase):
|
||||||
"""test SAML Provider flow"""
|
"""test SAML Provider flow"""
|
||||||
|
|
||||||
container: Container
|
def setup_client(self, provider: SAMLProvider, force_post: bool = False):
|
||||||
|
|
||||||
def setup_client(self, provider: SAMLProvider, force_post: bool = False) -> Container:
|
|
||||||
"""Setup client saml-sp container which we test SAML against"""
|
"""Setup client saml-sp container which we test SAML against"""
|
||||||
client: DockerClient = from_env()
|
|
||||||
metadata_url = (
|
metadata_url = (
|
||||||
self.url(
|
self.url(
|
||||||
"authentik_api:samlprovider-metadata",
|
"authentik_api:samlprovider-metadata",
|
||||||
@ -36,9 +31,8 @@ class TestProviderSAML(SeleniumTestCase):
|
|||||||
)
|
)
|
||||||
if force_post:
|
if force_post:
|
||||||
metadata_url += f"&force_binding={SAML_BINDING_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",
|
image="ghcr.io/beryju/saml-test-sp:1.1",
|
||||||
detach=True,
|
|
||||||
ports={
|
ports={
|
||||||
"9009": "9009",
|
"9009": "9009",
|
||||||
},
|
},
|
||||||
@ -48,8 +42,6 @@ class TestProviderSAML(SeleniumTestCase):
|
|||||||
"SP_METADATA_URL": metadata_url,
|
"SP_METADATA_URL": metadata_url,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.wait_for_container(container)
|
|
||||||
return container
|
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
@ -85,7 +77,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||||||
slug="authentik-saml",
|
slug="authentik-saml",
|
||||||
provider=provider,
|
provider=provider,
|
||||||
)
|
)
|
||||||
self.container = self.setup_client(provider)
|
self.setup_client(provider)
|
||||||
self.driver.get("http://localhost:9009")
|
self.driver.get("http://localhost:9009")
|
||||||
self.login()
|
self.login()
|
||||||
self.wait_for_url("http://localhost:9009/")
|
self.wait_for_url("http://localhost:9009/")
|
||||||
@ -153,7 +145,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||||||
slug="authentik-saml",
|
slug="authentik-saml",
|
||||||
provider=provider,
|
provider=provider,
|
||||||
)
|
)
|
||||||
self.container = self.setup_client(provider)
|
self.setup_client(provider)
|
||||||
self.driver.get("http://localhost:9009")
|
self.driver.get("http://localhost:9009")
|
||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
@ -236,7 +228,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||||||
slug="authentik-saml",
|
slug="authentik-saml",
|
||||||
provider=provider,
|
provider=provider,
|
||||||
)
|
)
|
||||||
self.container = self.setup_client(provider, True)
|
self.setup_client(provider, True)
|
||||||
self.driver.get("http://localhost:9009")
|
self.driver.get("http://localhost:9009")
|
||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
@ -319,7 +311,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||||||
slug="authentik-saml",
|
slug="authentik-saml",
|
||||||
provider=provider,
|
provider=provider,
|
||||||
)
|
)
|
||||||
self.container = self.setup_client(provider)
|
self.setup_client(provider)
|
||||||
self.driver.get(
|
self.driver.get(
|
||||||
self.url(
|
self.url(
|
||||||
"authentik_providers_saml:sso-init",
|
"authentik_providers_saml:sso-init",
|
||||||
@ -397,7 +389,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||||||
provider=provider,
|
provider=provider,
|
||||||
)
|
)
|
||||||
PolicyBinding.objects.create(target=app, policy=negative_policy, order=0)
|
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.driver.get("http://localhost:9009/")
|
||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
@ -444,7 +436,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||||||
slug="authentik-saml",
|
slug="authentik-saml",
|
||||||
provider=provider,
|
provider=provider,
|
||||||
)
|
)
|
||||||
self.container = self.setup_client(provider)
|
self.setup_client(provider)
|
||||||
self.driver.get("http://localhost:9009")
|
self.driver.get("http://localhost:9009")
|
||||||
self.login()
|
self.login()
|
||||||
self.wait_for_url("http://localhost:9009/")
|
self.wait_for_url("http://localhost:9009/")
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
"""test LDAP Source"""
|
"""test LDAP Source"""
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from ldap3.core.exceptions import LDAPSessionTerminatedByServerError
|
from ldap3.core.exceptions import LDAPSessionTerminatedByServerError
|
||||||
|
|
||||||
@ -22,22 +20,18 @@ class TestSourceLDAPSamba(SeleniumTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.admin_password = generate_key()
|
self.admin_password = generate_key()
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
self.samba = self.run_container(
|
||||||
def get_container_specs(self) -> dict[str, Any] | None:
|
image="ghcr.io/beryju/test-samba-dc:latest",
|
||||||
return {
|
cap_add=["SYS_ADMIN"],
|
||||||
"image": "ghcr.io/beryju/test-samba-dc:latest",
|
ports={
|
||||||
"detach": True,
|
|
||||||
"cap_add": ["SYS_ADMIN"],
|
|
||||||
"ports": {
|
|
||||||
"389": "389/tcp",
|
"389": "389/tcp",
|
||||||
},
|
},
|
||||||
"auto_remove": True,
|
environment={
|
||||||
"environment": {
|
|
||||||
"SMB_DOMAIN": "test.goauthentik.io",
|
"SMB_DOMAIN": "test.goauthentik.io",
|
||||||
"SMB_NETBIOS": "goauthentik",
|
"SMB_NETBIOS": "goauthentik",
|
||||||
"SMB_ADMIN_PASSWORD": self.admin_password,
|
"SMB_ADMIN_PASSWORD": self.admin_password,
|
||||||
},
|
},
|
||||||
}
|
)
|
||||||
|
|
||||||
@retry(exceptions=[LDAPSessionTerminatedByServerError])
|
@retry(exceptions=[LDAPSessionTerminatedByServerError])
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
@ -148,7 +142,7 @@ class TestSourceLDAPSamba(SeleniumTestCase):
|
|||||||
UserLDAPSynchronizer(source).sync_full()
|
UserLDAPSynchronizer(source).sync_full()
|
||||||
username = "bob"
|
username = "bob"
|
||||||
password = generate_id()
|
password = generate_id()
|
||||||
result = self.container.exec_run(
|
result = self.samba.exec_run(
|
||||||
["samba-tool", "user", "setpassword", username, "--newpassword", password]
|
["samba-tool", "user", "setpassword", username, "--newpassword", password]
|
||||||
)
|
)
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
@ -163,7 +157,7 @@ class TestSourceLDAPSamba(SeleniumTestCase):
|
|||||||
self.assertTrue(user.check_password(password))
|
self.assertTrue(user.check_password(password))
|
||||||
# Set new password
|
# Set new password
|
||||||
new_password = generate_id()
|
new_password = generate_id()
|
||||||
result = self.container.exec_run(
|
result = self.samba.exec_run(
|
||||||
["samba-tool", "user", "setpassword", username, "--newpassword", new_password]
|
["samba-tool", "user", "setpassword", username, "--newpassword", new_password]
|
||||||
)
|
)
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
@ -56,14 +56,10 @@ class TestSourceOAuth1(SeleniumTestCase):
|
|||||||
self.client_secret = generate_key()
|
self.client_secret = generate_key()
|
||||||
self.source_slug = generate_id()
|
self.source_slug = generate_id()
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
self.run_container(
|
||||||
def get_container_specs(self) -> dict[str, Any] | None:
|
image="ghcr.io/beryju/oauth1-test-server:v1.1",
|
||||||
return {
|
ports={"5000": "5001"},
|
||||||
"image": "ghcr.io/beryju/oauth1-test-server:v1.1",
|
environment={
|
||||||
"detach": True,
|
|
||||||
"ports": {"5000": "5001"},
|
|
||||||
"auto_remove": True,
|
|
||||||
"environment": {
|
|
||||||
"OAUTH1_CLIENT_ID": self.client_id,
|
"OAUTH1_CLIENT_ID": self.client_id,
|
||||||
"OAUTH1_CLIENT_SECRET": self.client_secret,
|
"OAUTH1_CLIENT_SECRET": self.client_secret,
|
||||||
"OAUTH1_REDIRECT_URI": self.url(
|
"OAUTH1_REDIRECT_URI": self.url(
|
||||||
@ -71,7 +67,7 @@ class TestSourceOAuth1(SeleniumTestCase):
|
|||||||
source_slug=self.source_slug,
|
source_slug=self.source_slug,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
)
|
||||||
|
|
||||||
def create_objects(self):
|
def create_objects(self):
|
||||||
"""Create required objects"""
|
"""Create required objects"""
|
||||||
|
@ -3,15 +3,12 @@
|
|||||||
from json import loads
|
from json import loads
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from docker.models.containers import Container
|
|
||||||
from docker.types import Healthcheck
|
from docker.types import Healthcheck
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.common.keys import Keys
|
from selenium.webdriver.common.keys import Keys
|
||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
from selenium.webdriver.support.wait import WebDriverWait
|
from selenium.webdriver.support.wait import WebDriverWait
|
||||||
from yaml import safe_dump
|
|
||||||
|
|
||||||
from authentik.blueprints.tests import apply_blueprint
|
from authentik.blueprints.tests import apply_blueprint
|
||||||
from authentik.core.models import User
|
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 authentik.stages.identification.models import IdentificationStage
|
||||||
from tests.e2e.utils import SeleniumTestCase, retry
|
from tests.e2e.utils import SeleniumTestCase, retry
|
||||||
|
|
||||||
CONFIG_PATH = "/tmp/dex.yml" # nosec
|
|
||||||
|
|
||||||
|
|
||||||
class TestSourceOAuth2(SeleniumTestCase):
|
class TestSourceOAuth2(SeleniumTestCase):
|
||||||
"""test OAuth Source flow"""
|
"""test OAuth Source flow"""
|
||||||
|
|
||||||
container: Container
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client_secret = generate_key()
|
self.client_secret = generate_key()
|
||||||
self.slug = generate_id()
|
self.slug = generate_id()
|
||||||
self.prepare_dex_config()
|
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
self.run_container(
|
||||||
def prepare_dex_config(self):
|
image="ghcr.io/dexidp/dex:v2.28.1",
|
||||||
"""Since Dex does not document which environment
|
ports={"5556": "5556"},
|
||||||
variables can be used to configure clients"""
|
healthcheck=Healthcheck(
|
||||||
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(
|
|
||||||
test=["CMD", "wget", "--spider", "http://localhost:5556/dex/healthz"],
|
test=["CMD", "wget", "--spider", "http://localhost:5556/dex/healthz"],
|
||||||
interval=5 * 1_000 * 1_000_000,
|
interval=5 * 1_000 * 1_000_000,
|
||||||
start_period=1 * 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):
|
def create_objects(self):
|
||||||
"""Create required objects"""
|
"""Create required objects"""
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from docker.types import Healthcheck
|
from docker.types import Healthcheck
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
@ -77,19 +76,15 @@ class TestSourceSAML(SeleniumTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.slug = generate_id()
|
self.slug = generate_id()
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
self.run_container(
|
||||||
def get_container_specs(self) -> dict[str, Any] | None:
|
image="kristophjunge/test-saml-idp:1.15",
|
||||||
return {
|
ports={"8080": "8080"},
|
||||||
"image": "kristophjunge/test-saml-idp:1.15",
|
healthcheck=Healthcheck(
|
||||||
"detach": True,
|
|
||||||
"ports": {"8080": "8080"},
|
|
||||||
"auto_remove": True,
|
|
||||||
"healthcheck": Healthcheck(
|
|
||||||
test=["CMD", "curl", "http://localhost:8080"],
|
test=["CMD", "curl", "http://localhost:8080"],
|
||||||
interval=5 * 1_000 * 1_000_000,
|
interval=5 * 1_000 * 1_000_000,
|
||||||
start_period=1 * 1_000 * 1_000_000,
|
start_period=1 * 1_000 * 1_000_000,
|
||||||
),
|
),
|
||||||
"volumes": {
|
volumes={
|
||||||
str(
|
str(
|
||||||
(Path(__file__).parent / Path("test-saml-idp/saml20-sp-remote.php")).absolute()
|
(Path(__file__).parent / Path("test-saml-idp/saml20-sp-remote.php")).absolute()
|
||||||
): {
|
): {
|
||||||
@ -97,7 +92,7 @@ class TestSourceSAML(SeleniumTestCase):
|
|||||||
"mode": "ro",
|
"mode": "ro",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"environment": {
|
environment={
|
||||||
"SIMPLESAMLPHP_SP_ENTITY_ID": "entity-id",
|
"SIMPLESAMLPHP_SP_ENTITY_ID": "entity-id",
|
||||||
"SIMPLESAMLPHP_SP_NAME_ID_FORMAT": (
|
"SIMPLESAMLPHP_SP_NAME_ID_FORMAT": (
|
||||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
"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)
|
self.url("authentik_sources_saml:acs", source_slug=self.slug)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_blueprint(
|
@apply_blueprint(
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from docker.types import Healthcheck
|
from docker.types import Healthcheck
|
||||||
|
|
||||||
@ -20,22 +19,18 @@ class TestSourceSCIM(SeleniumTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.slug = generate_id()
|
self.slug = generate_id()
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
self.run_container(
|
||||||
def get_container_specs(self) -> dict[str, Any] | None:
|
image=(
|
||||||
return {
|
|
||||||
"image": (
|
|
||||||
"ghcr.io/suvera/scim2-compliance-test-utility@sha256:eca913bb73"
|
"ghcr.io/suvera/scim2-compliance-test-utility@sha256:eca913bb73"
|
||||||
"c46892cd1fb2dfd2fef1c5881e6abc5cb0eec7e92fb78c1b933ece"
|
"c46892cd1fb2dfd2fef1c5881e6abc5cb0eec7e92fb78c1b933ece"
|
||||||
),
|
),
|
||||||
"detach": True,
|
ports={"8080": "8080"},
|
||||||
"ports": {"8080": "8080"},
|
healthcheck=Healthcheck(
|
||||||
"auto_remove": True,
|
|
||||||
"healthcheck": Healthcheck(
|
|
||||||
test=["CMD", "curl", "http://localhost:8080"],
|
test=["CMD", "curl", "http://localhost:8080"],
|
||||||
interval=5 * 1_000 * 1_000_000,
|
interval=5 * 1_000 * 1_000_000,
|
||||||
start_period=1 * 1_000 * 1_000_000,
|
start_period=1 * 1_000 * 1_000_000,
|
||||||
),
|
),
|
||||||
}
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
def test_scim_conformance(self):
|
def test_scim_conformance(self):
|
||||||
|
@ -9,6 +9,7 @@ from os import environ
|
|||||||
from sys import stderr
|
from sys import stderr
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from unittest.case import TestCase
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
||||||
@ -19,6 +20,7 @@ from django.urls import reverse
|
|||||||
from docker import DockerClient, from_env
|
from docker import DockerClient, from_env
|
||||||
from docker.errors import DockerException
|
from docker.errors import DockerException
|
||||||
from docker.models.containers import Container
|
from docker.models.containers import Container
|
||||||
|
from docker.models.networks import Network
|
||||||
from selenium import webdriver
|
from selenium import webdriver
|
||||||
from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException
|
from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException
|
||||||
from selenium.webdriver.common.by import By
|
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.api.users import UserSerializer
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
from authentik.core.tests.utils import create_test_admin_user
|
from authentik.core.tests.utils import create_test_admin_user
|
||||||
|
from authentik.lib.generators import generate_id
|
||||||
|
|
||||||
RETRIES = int(environ.get("RETRIES", "3"))
|
RETRIES = int(environ.get("RETRIES", "3"))
|
||||||
IS_CI = "CI" in environ
|
IS_CI = "CI" in environ
|
||||||
@ -54,9 +57,32 @@ def get_local_ip() -> str:
|
|||||||
return ip_addr
|
return ip_addr
|
||||||
|
|
||||||
|
|
||||||
class DockerTestCase:
|
class DockerTestCase(TestCase):
|
||||||
"""Mixin for dealing with containers"""
|
"""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):
|
def wait_for_container(self, container: Container):
|
||||||
"""Check that container is health"""
|
"""Check that container is health"""
|
||||||
attempt = 0
|
attempt = 0
|
||||||
@ -67,47 +93,30 @@ class DockerTestCase:
|
|||||||
return container
|
return container
|
||||||
sleep(1)
|
sleep(1)
|
||||||
attempt += 1
|
attempt += 1
|
||||||
if attempt >= 30: # noqa: PLR2004
|
if attempt >= self.max_healthcheck_attempts:
|
||||||
self.failureException("Container failed to start")
|
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:
|
def get_container_image(self, base: str) -> str:
|
||||||
"""Try to pull docker image based on git branch, fallback to main if not found."""
|
"""Try to pull docker image based on git branch, fallback to main if not found."""
|
||||||
client: DockerClient = from_env()
|
|
||||||
image = f"{base}:gh-main"
|
image = f"{base}:gh-main"
|
||||||
try:
|
try:
|
||||||
branch_image = f"{base}:{get_docker_tag()}"
|
branch_image = f"{base}:{get_docker_tag()}"
|
||||||
client.images.pull(branch_image)
|
self.docker_client.images.pull(branch_image)
|
||||||
return branch_image
|
return branch_image
|
||||||
except DockerException:
|
except DockerException:
|
||||||
client.images.pull(image)
|
self.docker_client.images.pull(image)
|
||||||
return image
|
return image
|
||||||
|
|
||||||
def _start_container(self, specs: dict[str, Any]) -> Container:
|
def run_container(self, **specs: dict[str, Any]) -> Container:
|
||||||
client: DockerClient = from_env()
|
if "network_mode" not in specs:
|
||||||
container = client.containers.run(**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()
|
container.reload()
|
||||||
state = container.attrs.get("State", {})
|
state = container.attrs.get("State", {})
|
||||||
if "Health" not in state:
|
if "Health" not in state:
|
||||||
@ -117,25 +126,53 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
|||||||
|
|
||||||
def output_container_logs(self, container: Container | None = None):
|
def output_container_logs(self, container: Container | None = None):
|
||||||
"""Output the container logs to our STDOUT"""
|
"""Output the container logs to our STDOUT"""
|
||||||
_container = container or self.container
|
|
||||||
if IS_CI:
|
if IS_CI:
|
||||||
image = _container.image
|
image = container.image
|
||||||
tags = image.tags[0] if len(image.tags) > 0 else str(image)
|
tags = image.tags[0] if len(image.tags) > 0 else str(image)
|
||||||
print(f"::group::Container logs - {tags}")
|
print(f"::group::Container logs - {tags}")
|
||||||
for log in _container.logs().decode().split("\n"):
|
for log in container.logs().decode().split("\n"):
|
||||||
print(log)
|
print(log)
|
||||||
if IS_CI:
|
if IS_CI:
|
||||||
print("::endgroup::")
|
print("::endgroup::")
|
||||||
|
|
||||||
def get_container_specs(self) -> dict[str, Any] | None:
|
def tearDown(self):
|
||||||
"""Optionally get container specs which will launched on setup, wait for the container to
|
containers = self.docker_client.containers.list(
|
||||||
be healthy, and deleted again on tearDown"""
|
filters={"label": ",".join(f"{x}={y}" for x, y in self.docker_labels.items())}
|
||||||
return None
|
)
|
||||||
|
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:
|
def _get_driver(self) -> WebDriver:
|
||||||
count = 0
|
count = 0
|
||||||
try:
|
try:
|
||||||
return webdriver.Chrome()
|
opts = webdriver.ChromeOptions()
|
||||||
|
opts.add_argument("--disable-search-engine-choice-screen")
|
||||||
|
return webdriver.Chrome(options=opts)
|
||||||
except WebDriverException:
|
except WebDriverException:
|
||||||
pass
|
pass
|
||||||
while count < RETRIES:
|
while count < RETRIES:
|
||||||
@ -151,18 +188,15 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
|||||||
raise ValueError(f"Webdriver failed after {RETRIES}.")
|
raise ValueError(f"Webdriver failed after {RETRIES}.")
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
|
||||||
if IS_CI:
|
if IS_CI:
|
||||||
print("::endgroup::", file=stderr)
|
print("::endgroup::", file=stderr)
|
||||||
|
super().tearDown()
|
||||||
if IS_CI:
|
if IS_CI:
|
||||||
print("::group::Browser logs")
|
print("::group::Browser logs")
|
||||||
for line in self.driver.get_log("browser"):
|
for line in self.driver.get_log("browser"):
|
||||||
print(line["message"])
|
print(line["message"])
|
||||||
if IS_CI:
|
if IS_CI:
|
||||||
print("::endgroup::")
|
print("::endgroup::")
|
||||||
if self.container:
|
|
||||||
self.output_container_logs()
|
|
||||||
self.container.kill()
|
|
||||||
self.driver.quit()
|
self.driver.quit()
|
||||||
|
|
||||||
def wait_for_url(self, desired_url):
|
def wait_for_url(self, desired_url):
|
||||||
|
@ -6,8 +6,6 @@ from tempfile import mkdtemp
|
|||||||
import pytest
|
import pytest
|
||||||
import yaml
|
import yaml
|
||||||
from channels.testing import ChannelsLiveServerTestCase
|
from channels.testing import ChannelsLiveServerTestCase
|
||||||
from docker import DockerClient, from_env
|
|
||||||
from docker.models.containers import Container
|
|
||||||
from docker.types.healthcheck import Healthcheck
|
from docker.types.healthcheck import Healthcheck
|
||||||
|
|
||||||
from authentik.core.tests.utils import create_test_flow
|
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):
|
class OutpostDockerTests(DockerTestCase, ChannelsLiveServerTestCase):
|
||||||
"""Test Docker Controllers"""
|
"""Test Docker Controllers"""
|
||||||
|
|
||||||
def _start_container(self, ssl_folder: str) -> Container:
|
def setUp(self):
|
||||||
client: DockerClient = from_env()
|
super().setUp()
|
||||||
container = client.containers.run(
|
self.ssl_folder = mkdtemp()
|
||||||
|
self.run_container(
|
||||||
image="library/docker:dind",
|
image="library/docker:dind",
|
||||||
detach=True,
|
|
||||||
network_mode="host",
|
network_mode="host",
|
||||||
privileged=True,
|
privileged=True,
|
||||||
healthcheck=Healthcheck(
|
healthcheck=Healthcheck(
|
||||||
@ -41,18 +39,11 @@ class OutpostDockerTests(DockerTestCase, ChannelsLiveServerTestCase):
|
|||||||
),
|
),
|
||||||
environment={"DOCKER_TLS_CERTDIR": "/ssl"},
|
environment={"DOCKER_TLS_CERTDIR": "/ssl"},
|
||||||
volumes={
|
volumes={
|
||||||
f"{ssl_folder}/": {
|
f"{self.ssl_folder}/": {
|
||||||
"bind": "/ssl",
|
"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
|
# Ensure that local connection have been created
|
||||||
outpost_connection_discovery()
|
outpost_connection_discovery()
|
||||||
self.provider: ProxyProvider = ProxyProvider.objects.create(
|
self.provider: ProxyProvider = ProxyProvider.objects.create(
|
||||||
@ -91,7 +82,6 @@ class OutpostDockerTests(DockerTestCase, ChannelsLiveServerTestCase):
|
|||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
self.container.kill()
|
|
||||||
try:
|
try:
|
||||||
rmtree(self.ssl_folder)
|
rmtree(self.ssl_folder)
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
|
@ -6,8 +6,6 @@ from tempfile import mkdtemp
|
|||||||
import pytest
|
import pytest
|
||||||
import yaml
|
import yaml
|
||||||
from channels.testing.live import ChannelsLiveServerTestCase
|
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 docker.types.healthcheck import Healthcheck
|
||||||
|
|
||||||
from authentik.core.tests.utils import create_test_flow
|
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):
|
class TestProxyDocker(DockerTestCase, ChannelsLiveServerTestCase):
|
||||||
"""Test Docker Controllers"""
|
"""Test Docker Controllers"""
|
||||||
|
|
||||||
def _start_container(self, ssl_folder: str) -> Container:
|
def setUp(self):
|
||||||
client: DockerClient = from_env()
|
super().setUp()
|
||||||
container = client.containers.run(
|
self.ssl_folder = mkdtemp()
|
||||||
|
self.run_container(
|
||||||
image="library/docker:dind",
|
image="library/docker:dind",
|
||||||
detach=True,
|
|
||||||
network_mode="host",
|
network_mode="host",
|
||||||
privileged=True,
|
privileged=True,
|
||||||
healthcheck=Healthcheck(
|
healthcheck=Healthcheck(
|
||||||
@ -41,18 +39,11 @@ class TestProxyDocker(DockerTestCase, ChannelsLiveServerTestCase):
|
|||||||
),
|
),
|
||||||
environment={"DOCKER_TLS_CERTDIR": "/ssl"},
|
environment={"DOCKER_TLS_CERTDIR": "/ssl"},
|
||||||
volumes={
|
volumes={
|
||||||
f"{ssl_folder}/": {
|
f"{self.ssl_folder}/": {
|
||||||
"bind": "/ssl",
|
"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
|
# Ensure that local connection have been created
|
||||||
outpost_connection_discovery()
|
outpost_connection_discovery()
|
||||||
self.provider: ProxyProvider = ProxyProvider.objects.create(
|
self.provider: ProxyProvider = ProxyProvider.objects.create(
|
||||||
@ -91,7 +82,6 @@ class TestProxyDocker(DockerTestCase, ChannelsLiveServerTestCase):
|
|||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
self.container.kill()
|
|
||||||
try:
|
try:
|
||||||
rmtree(self.ssl_folder)
|
rmtree(self.ssl_folder)
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
|
@ -32,7 +32,7 @@ http:
|
|||||||
app:
|
app:
|
||||||
loadBalancer:
|
loadBalancer:
|
||||||
servers:
|
servers:
|
||||||
- url: http://ipp.internal
|
- url: http://ip.internal
|
||||||
authentik:
|
authentik:
|
||||||
loadBalancer:
|
loadBalancer:
|
||||||
servers:
|
servers:
|
||||||
|
Reference in New Issue
Block a user