From 3b9ea9c5b1bd7cc6128f17947ce4c3c6fa715faf Mon Sep 17 00:00:00 2001 From: Grant Date: Fri, 6 Sep 2024 17:15:59 -0500 Subject: [PATCH 1/3] provide users with appropriate access to resources Users need access to a number of resources so that they can run scenarios. --- resources/charts/namespaces/values.yaml | 26 +++++++++- .../namespace-defaults.yaml | 2 +- .../two_namespaces_two_users/namespaces.yaml | 52 ++++++++++++++++++- 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/resources/charts/namespaces/values.yaml b/resources/charts/namespaces/values.yaml index c28d2d0df..61f946879 100644 --- a/resources/charts/namespaces/values.yaml +++ b/resources/charts/namespaces/values.yaml @@ -9,8 +9,32 @@ roles: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["pods/log", "pods/exec", "pods/attach", "pods/portforward"] + verbs: ["get"] + - apiGroups: [""] + resources: ["configmaps", "secrets"] + verbs: ["get"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list"] + - apiGroups: [""] + resources: ["events"] + verbs: ["get"] - name: pod-manager rules: - apiGroups: [""] resources: ["pods"] - verbs: ["get", "list", "watch", "create", "update", "delete"] + verbs: ["get", "list", "watch", "create", "delete", "update"] + - apiGroups: [""] + resources: ["pods/log", "pods/exec", "pods/attach", "pods/portforward"] + verbs: ["get", "create"] + - apiGroups: [""] + resources: ["configmaps", "secrets"] + verbs: ["get", "create"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list"] + - apiGroups: [""] + resources: ["events"] + verbs: ["get"] \ No newline at end of file diff --git a/resources/namespaces/two_namespaces_two_users/namespace-defaults.yaml b/resources/namespaces/two_namespaces_two_users/namespace-defaults.yaml index c28d2d0df..91ac2fc67 100644 --- a/resources/namespaces/two_namespaces_two_users/namespace-defaults.yaml +++ b/resources/namespaces/two_namespaces_two_users/namespace-defaults.yaml @@ -12,5 +12,5 @@ roles: - name: pod-manager rules: - apiGroups: [""] - resources: ["pods"] + resources: ["pods", "configmaps"] verbs: ["get", "list", "watch", "create", "update", "delete"] diff --git a/resources/namespaces/two_namespaces_two_users/namespaces.yaml b/resources/namespaces/two_namespaces_two_users/namespaces.yaml index 03b31696a..4172657b8 100644 --- a/resources/namespaces/two_namespaces_two_users/namespaces.yaml +++ b/resources/namespaces/two_namespaces_two_users/namespaces.yaml @@ -14,11 +14,35 @@ namespaces: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["pods/log", "pods/exec", "pods/attach", "pods/portforward"] + verbs: ["get"] + - apiGroups: [""] + resources: ["configmaps", "secrets"] + verbs: ["get"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list"] + - apiGroups: [""] + resources: ["events"] + verbs: ["get"] - name: pod-manager rules: - apiGroups: [""] resources: ["pods"] - verbs: ["get", "list", "watch", "create", "update", "delete"] + verbs: ["get", "list", "watch", "create", "delete", "update"] + - apiGroups: [""] + resources: ["pods/log", "pods/exec", "pods/attach", "pods/portforward"] + verbs: ["get", "create"] + - apiGroups: [""] + resources: ["configmaps", "secrets"] + verbs: ["get", "create"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list"] + - apiGroups: [""] + resources: ["events"] + verbs: ["get"] - name: warnet-blue-team users: - name: mallory @@ -34,8 +58,32 @@ namespaces: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["pods/log", "pods/exec", "pods/attach", "pods/portforward"] + verbs: ["get"] + - apiGroups: [""] + resources: ["configmaps", "secrets"] + verbs: ["get"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list"] + - apiGroups: [""] + resources: ["events"] + verbs: ["get"] - name: pod-manager rules: - apiGroups: [""] resources: ["pods"] - verbs: ["get", "list", "watch", "create", "update", "delete"] + verbs: ["get", "list", "watch", "create", "delete", "update"] + - apiGroups: [""] + resources: ["pods/log", "pods/exec", "pods/attach", "pods/portforward"] + verbs: ["get", "create"] + - apiGroups: [""] + resources: ["configmaps", "secrets"] + verbs: ["get", "create"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list"] + - apiGroups: [""] + resources: ["events"] + verbs: ["get"] From f03e987cdc4f6d13657b16da745ffe6ed783f9d9 Mon Sep 17 00:00:00 2001 From: Grant Date: Sat, 7 Sep 2024 01:52:06 -0500 Subject: [PATCH 2/3] add create-kubeconfigs to `warnet admin` I transmuted the setup_user_contexts.sh script into python code. I also made it so that it would query all namespaces that start "warnet-" looking for service accounts, and then create token credentials for those accounts. Currently, user namespaces must start with "warnet-". --- src/warnet/admin.py | 130 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/src/warnet/admin.py b/src/warnet/admin.py index f194e16bd..9f1c05305 100644 --- a/src/warnet/admin.py +++ b/src/warnet/admin.py @@ -1,4 +1,5 @@ import os +import subprocess from pathlib import Path import click @@ -33,3 +34,132 @@ def init(): f"[green]Copied network and namespace example files to {Path(current_dir) / NETWORK_DIR.name}[/green]" ) richprint(f"[green]Created warnet project structure in {current_dir}[/green]") + + +# Get kubectl values +def get_kubectl_value(jsonpath): + return subprocess.check_output( + ["kubectl", "config", "view", "--minify", "-o", f"jsonpath={jsonpath}"] + ).decode("utf-8") + + +# Get all namespaces that start with "warnet-" +def get_warnet_namespaces(): + namespaces = ( + subprocess.check_output( + ["kubectl", "get", "namespaces", "-o", "jsonpath={.items[*].metadata.name}"] + ) + .decode("utf-8") + .split() + ) + return [ns for ns in namespaces if ns.startswith("warnet-")] + + +# Get all service accounts for a given namespace +def get_service_accounts(namespace): + return ( + subprocess.check_output( + [ + "kubectl", + "get", + "serviceaccounts", + "-n", + namespace, + "-o", + "jsonpath={.items[*].metadata.name}", + ] + ) + .decode("utf-8") + .split() + ) + + +@admin.command() +@click.option( + "--kubeconfig-dir", + default="kubeconfigs", + help="Directory to store kubeconfig files (default: kubeconfigs)", +) +@click.option( + "--token-duration", + default=172800, + type=int, + help="Duration of the token in seconds (default: 48 hours)", +) +def create_kubeconfigs(kubeconfig_dir, token_duration): + """Create kubeconfig files for all ServiceAccounts in namespaces starting with 'warnet-'.""" + kubeconfig_dir = os.path.expanduser(kubeconfig_dir) + + cluster_name = get_kubectl_value("{.clusters[0].name}") + cluster_server = get_kubectl_value("{.clusters[0].cluster.server}") + cluster_ca = get_kubectl_value("{.clusters[0].cluster.certificate-authority-data}") + + os.makedirs(kubeconfig_dir, exist_ok=True) + + # Get all namespaces that start with "warnet-" + warnet_namespaces = get_warnet_namespaces() + + for namespace in warnet_namespaces: + click.echo(f"Processing namespace: {namespace}") + service_accounts = get_service_accounts(namespace) + + for sa in service_accounts: + click.echo(f"Processing ServiceAccount: {sa}") + + # Create a token for the ServiceAccount with specified duration + try: + token = ( + subprocess.check_output( + [ + "kubectl", + "create", + "token", + sa, + "-n", + namespace, + f"--duration={token_duration}s", + ] + ) + .decode("utf-8") + .strip() + ) + except subprocess.CalledProcessError: + click.echo(f"Failed to create token for ServiceAccount {sa}. Skipping...") + continue + + # Create a kubeconfig file for the user + kubeconfig_file = os.path.join(kubeconfig_dir, f"{sa}-{namespace}-kubeconfig") + + kubeconfig_content = f"""apiVersion: v1 +kind: Config +clusters: +- name: {cluster_name} + cluster: + server: {cluster_server} + certificate-authority-data: {cluster_ca} +users: +- name: {sa} + user: + token: {token} +contexts: +- name: {sa}-{namespace} + context: + cluster: {cluster_name} + namespace: {namespace} + user: {sa} +current-context: {sa}-{namespace} +""" + with open(kubeconfig_file, "w") as f: + f.write(kubeconfig_content) + + click.echo(f"Created kubeconfig file for {sa}: {kubeconfig_file}") + click.echo(f"Token duration: {token_duration} seconds") + click.echo(f"To use this config, run: kubectl --kubeconfig={kubeconfig_file} get pods") + click.echo("---") + + click.echo(f"All kubeconfig files have been created in the '{kubeconfig_dir}' directory.") + click.echo("Distribute these files to the respective users.") + click.echo( + "Users can then use them with kubectl by specifying the --kubeconfig flag or by setting the KUBECONFIG environment variable." + ) + click.echo(f"Note: The tokens will expire after {token_duration} seconds.") From 1d583d0eb5dce73596c5e2b7a2ad41ca20b219d6 Mon Sep 17 00:00:00 2001 From: Grant Date: Sat, 7 Sep 2024 10:33:43 -0500 Subject: [PATCH 3/3] add Josie's notes --- src/warnet/admin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/warnet/admin.py b/src/warnet/admin.py index 9f1c05305..dfdd18a94 100644 --- a/src/warnet/admin.py +++ b/src/warnet/admin.py @@ -1,5 +1,5 @@ import os -import subprocess +import subprocess # TODO: use run_command or similar; NOT subprocess from pathlib import Path import click @@ -45,6 +45,7 @@ def get_kubectl_value(jsonpath): # Get all namespaces that start with "warnet-" def get_warnet_namespaces(): + # TODO: paramaterize with prefix namespaces = ( subprocess.check_output( ["kubectl", "get", "namespaces", "-o", "jsonpath={.items[*].metadata.name}"] @@ -130,6 +131,7 @@ def create_kubeconfigs(kubeconfig_dir, token_duration): # Create a kubeconfig file for the user kubeconfig_file = os.path.join(kubeconfig_dir, f"{sa}-{namespace}-kubeconfig") + # TODO: move yaml out of python code kubeconfig_content = f"""apiVersion: v1 kind: Config clusters: