Skip to content

Commit

Permalink
Minor updates, add tests for special case
Browse files Browse the repository at this point in the history
  • Loading branch information
iameskild committed Jul 20, 2023
1 parent 4ea1cd9 commit a3e51b0
Show file tree
Hide file tree
Showing 7 changed files with 293 additions and 5 deletions.
12 changes: 8 additions & 4 deletions nebari_workflow_controller/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

ARGO_CLIENT_ID = "argo-server-sso"
# mounted to nebari-workflow-controller deployment as a configmap
VALID_ARGO_ROLES_CONFIGMAP = "/etc/config/valid-argo-roles"
VALID_ARGO_ROLES_CONFIGMAP = "/etc/argo/valid-argo-roles"


def process_unhandled_exception(e, return_response, logger):
Expand Down Expand Up @@ -85,8 +85,8 @@ def desanitize_label(s: str) -> str:
return re.sub(pattern, lambda x: chr(int(x.group(1), 16)), s)


def get_keycloak_user(request) -> KeycloakUser:
kcadm = KeycloakAdmin(
def create_keycloak_admin() -> KeycloakAdmin:
return KeycloakAdmin(
server_url=os.environ["KEYCLOAK_URL"],
username=os.environ["KEYCLOAK_USERNAME"],
password=os.environ["KEYCLOAK_PASSWORD"],
Expand All @@ -95,6 +95,10 @@ def get_keycloak_user(request) -> KeycloakUser:
client_id="admin-cli",
)


def get_keycloak_user(request) -> KeycloakUser:
kcadm = create_keycloak_admin()

config.incluster_config.load_incluster_config()

keycloak_uid, keycloak_username = get_keycloak_uid_username(
Expand All @@ -111,7 +115,7 @@ def get_keycloak_user(request) -> KeycloakUser:


def get_keycloak_uid_username(
kcadm,
kcadm: KeycloakAdmin,
workflow: dict,
k8s_client: client.ApiClient,
) -> KeycloakUser:
Expand Down
77 changes: 77 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import pytest
import yaml
from keycloak.exceptions import KeycloakGetError

from nebari_workflow_controller.models import KeycloakGroup, KeycloakUser

Expand Down Expand Up @@ -41,6 +42,28 @@ def all_request_paths(valid_request_paths, invalid_request_paths):
return valid_request_paths + invalid_request_paths


def _valid_service_account_request_paths():
return sorted(
[
str(p)
for p in Path("./tests/test_data/requests/valid-service-account").glob(
"*.yaml"
)
]
)


def _invalid_service_account_request_paths():
return sorted(
[
str(p)
for p in Path("./tests/test_data/requests/invalid-service-account").glob(
"*.yaml"
)
]
)


def load_request(request_path):
with open(request_path) as f:
return yaml.load(f, Loader=yaml.FullLoader)
Expand Down Expand Up @@ -102,3 +125,57 @@ def mocked_get_user_pod_spec(mocker):
"nebari_workflow_controller.app.get_user_pod_spec",
return_value=jupyterlab_pod_spec,
)


class MockedKeycloakAdmin:
def get_user(self, *args, **kwargs):
raise KeycloakGetError("mocked error")

def get_users(self, *args, **kwargs):
return [
{
"id": "a1234567-abcd-89ef-0123-456789abcdef",
"username": "[email protected]",
"email": "[email protected]",
"firstName": "Valid",
"lastName": "User",
"enabled": True,
"groups": ["admin", "users"],
},
]

def get_user_groups(self, *args, **kwargs):
return [
{
"id": "group123-id",
"name": "admin",
"path": "/admin",
"attributes": {},
},
{
"id": "group456-id",
"name": "users",
"path": "/users",
"attributes": {},
},
]


VALID_ARGO_ROLES = ["argo-admin", "argo-developer"]


@pytest.fixture(scope="function")
def mock_special_case(mocker):
mocker.patch(
"nebari_workflow_controller.utils.create_keycloak_admin",
return_value=MockedKeycloakAdmin(),
)
mocker.patch("nebari_workflow_controller.utils.config", return_value=None)
mocker.patch(
"nebari_workflow_controller.utils.sent_by_argo",
return_value="workflows.argoproj.io/creator",
)
mocker.patch(
"nebari_workflow_controller.utils.valid_argo_roles",
return_value=VALID_ARGO_ROLES,
)
25 changes: 24 additions & 1 deletion tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@
get_spec_keep_portions,
mutate_template,
)
from tests.conftest import _invalid_request_paths, _valid_request_paths
from tests.conftest import (
_invalid_request_paths,
_invalid_service_account_request_paths,
_valid_request_paths,
_valid_service_account_request_paths,
)


@pytest.mark.parametrize(
Expand All @@ -29,6 +34,24 @@ def test_validate(request_file, allowed, mocked_get_keycloak_user):
assert response["response"]["status"]["message"]


@pytest.mark.parametrize(
"request_file,allowed",
[(str(p), True) for p in _valid_service_account_request_paths()]
+ [(str(p), False) for p in _invalid_service_account_request_paths()],
)
def test_validate_with_service_account(request_file, allowed, mock_special_case):
# The general case is where the user is directly validated against Keycloak.
# This is the special case, where the user is validated against Keycloak via a service account.

with open(request_file) as f:
request = yaml.load(f, Loader=yaml.FullLoader)
response = validate(request)

assert response["response"]["allowed"] == allowed
if not allowed:
assert response["response"]["status"]["message"]


def test_mutate_template_doesnt_error(request_templates, jupyterlab_pod_spec):
api = client.ApiClient()
container_keep_portions = get_container_keep_portions(jupyterlab_pod_spec, api)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
apiVersion: admission.k8s.io/v1
kind: AdmissionReview
request:
dryRun: false
kind:
group: argoproj.io
kind: Workflow
version: v1alpha1
name: hello-world
namespace: dev
object:
api: argoproj.io/v1alpha1
kind: Workflow
metadata:
name: sparkly-python
namespace: dev
labels:
example: 'true'
jupyterflow-override: 'true'
workflows.argoproj.io/creator-preferred-username: valid-2duser-40mail-2ecom
workflows.argoproj.io/creator: system-serviceaccount-dev-argo-analyst # valid user but wrong permissions
spec:
templates:
- name: argosay
container:
name: notebook
image: quay.io/nebari/nebari-jupyterlab:2023.1.1
command:
- sh
- '-c'
args:
- conda run -n nebari-git-dask python script.py
resources:
limits:
cpu: '3000m'
nodeSelector:
mylabel: myValue
entrypoint: argosay
uid: c1bba5c6-2189-41ff-9487-be504c04487b
userInfo:
groups:
- system:serviceaccounts
- system:serviceaccounts:dev
- system:authenticated
uid: eac0d7ab-af84-4c3f-a5fd-71845ff9e8c9
username: system:serviceaccount:dev:argo-admin
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
apiVersion: admission.k8s.io/v1
kind: AdmissionReview
request:
dryRun: false
kind:
group: argoproj.io
kind: Workflow
version: v1alpha1
name: hello-world
namespace: dev
object:
api: argoproj.io/v1alpha1
kind: Workflow
metadata:
name: sparkly-python
namespace: dev
labels:
example: 'true'
jupyterflow-override: 'true'
workflows.argoproj.io/creator-preferred-username: invalid-2duser-40mail-2ecom # username not in keycloak
workflows.argoproj.io/creator: system-serviceaccount-dev-argo-admin
spec:
templates:
- name: argosay
container:
name: notebook
image: quay.io/nebari/nebari-jupyterlab:2023.1.1
command:
- sh
- '-c'
args:
- conda run -n nebari-git-dask python script.py
resources:
limits:
cpu: '3000m'
nodeSelector:
mylabel: myValue
entrypoint: argosay
uid: c1bba5c6-2189-41ff-9487-be504c04487b
userInfo:
groups:
- system:serviceaccounts
- system:serviceaccounts:dev
- system:authenticated
uid: eac0d7ab-af84-4c3f-a5fd-71845ff9e8c9
username: system:serviceaccount:dev:argo-admin
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
apiVersion: admission.k8s.io/v1
kind: AdmissionReview
request:
dryRun: false
kind:
group: argoproj.io
kind: Workflow
version: v1alpha1
name: hello-world
namespace: dev
object:
api: argoproj.io/v1alpha1
kind: Workflow
metadata:
name: sparkly-python
namespace: dev
labels:
example: 'true'
jupyterflow-override: 'true'
workflows.argoproj.io/creator-preferred-username: valid-2duser-40mail-2ecom
workflows.argoproj.io/creator: system-serviceaccount-dev-argo-admin
spec:
templates:
- name: argosay
container:
name: notebook
image: quay.io/nebari/nebari-jupyterlab:2023.1.1
command:
- sh
- '-c'
args:
- conda run -n nebari-git-dask python script.py
resources:
limits:
cpu: '3000m'
nodeSelector:
mylabel: myValue
entrypoint: argosay
uid: c1bba5c6-2189-41ff-9487-be504c04487b
userInfo:
groups:
- system:serviceaccounts
- system:serviceaccounts:dev
- system:authenticated
uid: eac0d7ab-af84-4c3f-a5fd-71845ff9e8c9
username: system:serviceaccount:dev:argo-admin
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
apiVersion: admission.k8s.io/v1
kind: AdmissionReview
request:
dryRun: false
kind:
group: argoproj.io
kind: Workflow
version: v1alpha1
name: hello-world
namespace: dev
object:
api: argoproj.io/v1alpha1
kind: Workflow
metadata:
name: sparkly-python
namespace: dev
labels:
example: 'true'
jupyterflow-override: 'true'
workflows.argoproj.io/creator-preferred-username: valid-2duser-40mail-2ecom
workflows.argoproj.io/creator: system-serviceaccount-dev-argo-developer
spec:
templates:
- name: argosay
container:
name: notebook
image: quay.io/nebari/nebari-jupyterlab:2023.1.1
command:
- sh
- '-c'
args:
- conda run -n nebari-git-dask python script.py
resources:
limits:
cpu: '3000m'
nodeSelector:
mylabel: myValue
entrypoint: argosay
uid: c1bba5c6-2189-41ff-9487-be504c04487b
userInfo:
groups:
- system:serviceaccounts
- system:serviceaccounts:dev
- system:authenticated
uid: eac0d7ab-af84-4c3f-a5fd-71845ff9e8c9
username: system:serviceaccount:dev:argo-admin

0 comments on commit a3e51b0

Please sign in to comment.