Skip to content

Commit

Permalink
Merge pull request #616 from mplsgrant/2024-09-create-kube-configs-st…
Browse files Browse the repository at this point in the history
…epthru

Using team namespaces: Kubeconfig Step Thru
  • Loading branch information
pinheadmz authored Oct 8, 2024
2 parents 7b4dd1e + 424eba8 commit 26ca399
Show file tree
Hide file tree
Showing 22 changed files with 1,028 additions and 258 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ jobs:
- services_test.py
- signet_test.py
- scenarios_test.py
- namespace_admin_test.py
steps:
- uses: actions/checkout@v4
- uses: azure/[email protected]
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ warnet.egg-info
.env
dist/
build/
**/kubeconfigs/
69 changes: 69 additions & 0 deletions docs/admin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Admin

## Connect to your cluster

Ensure you are connected to your cluster because Warnet will use your current configuration to generate configurations for your users.

```shell
$ warnet status
```

Observe that the output of the command matches your cluster.

## Create an *admin* directory

```shell
$ mkdir admin
$ cd admin
$ warnet admin init
```

Observe that there are now two folders within the *admin* directory: *namespaces* and *networks*

## The *namespaces* directory
This directory contains a Helm chart named *two_namespaces_two_users*.

Modify this chart based on the number of teams and users you have.

Deploy the *two_namespaces_two_users* chart.

```shell
$ warnet deploy namespaces/two_namespaces_two_users
```

Observe that this creates service accounts and namespaces in the cluster:

```shell
$ kubectl get ns
$ kubectl get sa -A
```

### Creating Warnet invites
A Warnet invite is a Kubernetes config file.

Create invites for each of your users.

```shell
$ warnet admin create-kubeconfigs
```

Observe the *kubeconfigs* directory. It holds invites for each user.

### Using Warnet invites
Users can connect to your wargame using their invite.

```shell
$ warnet auth alice-wargames-red-team-kubeconfig
```

### Set up a network for your users
Before letting the users into your cluster, make sure to create a network of tanks for them to view.


```shell
$ warnet deploy networks/mynet --to-all-users
```

Observe that the *wargames-red-team* namespace now has tanks in it.

**TODO**: What's the logging approach here?
12 changes: 6 additions & 6 deletions resources/charts/namespaces/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,33 @@ roles:
- name: pod-viewer
rules:
- apiGroups: [""]
resources: ["pods"]
resources: ["pods", "services"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log", "pods/exec", "pods/attach", "pods/portforward"]
verbs: ["get"]
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
resources: ["persistentvolumeclaims", "namespaces"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["events"]
verbs: ["get"]
- name: pod-manager
rules:
- apiGroups: [""]
resources: ["pods"]
resources: ["pods", "services"]
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"]
verbs: ["get", "list", "create"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
resources: ["persistentvolumeclaims", "namespaces"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["events"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ users:
roles:
- pod-viewer
- pod-manager
roles:
- name: pod-viewer
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- name: pod-manager
rules:
- apiGroups: [""]
resources: ["pods", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "delete"]
# the pod-viewer and pod-manager roles are the default
# roles defined in values.yaml for the namespaces charts
#
# if you need a different set of roles for a particular namespaces
# deployment, you can override values.yaml by providing your own
# role definitions below
#
# roles:
# - name: my-custom-role
# rules:
# - apiGroups: ""
# resources: ""
# verbs: ""
74 changes: 2 additions & 72 deletions resources/namespaces/two_namespaces_two_users/namespaces.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
namespaces:
- name: warnet-red-team
- name: wargames-red-team
users:
- name: alice
roles:
Expand All @@ -8,42 +8,7 @@ namespaces:
roles:
- pod-viewer
- pod-manager
roles:
- name: pod-viewer
rules:
- 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", "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
- name: wargames-blue-team
users:
- name: mallory
roles:
Expand All @@ -52,38 +17,3 @@ namespaces:
roles:
- pod-viewer
- pod-manager
roles:
- name: pod-viewer
rules:
- 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", "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"]
103 changes: 102 additions & 1 deletion src/warnet/admin.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import os
import sys
from pathlib import Path

import click
import yaml
from rich import print as richprint

from .constants import NETWORK_DIR
from .constants import KUBECONFIG, NETWORK_DIR, WARGAMES_NAMESPACE_PREFIX
from .k8s import (
K8sError,
get_cluster_of_current_context,
get_namespaces_by_type,
get_service_accounts_in_namespace,
open_kubeconfig,
)
from .namespaces import copy_namespaces_defaults, namespaces
from .network import copy_network_defaults
from .process import run_command


@click.group(name="admin", hidden=True)
Expand All @@ -33,3 +43,94 @@ 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]")


@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 ServiceAccounts"""
kubeconfig_dir = os.path.expanduser(kubeconfig_dir)

try:
kubeconfig_data = open_kubeconfig(KUBECONFIG)
except K8sError as e:
click.secho(e, fg="yellow")
click.secho(f"Could not open auth_config: {KUBECONFIG}", fg="red")
sys.exit(1)

cluster = get_cluster_of_current_context(kubeconfig_data)

os.makedirs(kubeconfig_dir, exist_ok=True)

# Get all namespaces that start with prefix
# This assumes when deploying multiple namespaces for the purpose of team games, all namespaces start with a prefix,
# e.g., tabconf-wargames-*. Currently, this is a bit brittle, but we can improve on this in the future
# by automatically applying a TEAM_PREFIX when creating the get_warnet_namespaces
# TODO: choose a prefix convention and have it managed by the helm charts instead of requiring the
# admin user to pipe through the correct string in multiple places. Another would be to use
# labels instead of namespace naming conventions
warnet_namespaces = get_namespaces_by_type(WARGAMES_NAMESPACE_PREFIX)

for v1namespace in warnet_namespaces:
namespace = v1namespace.metadata.name
click.echo(f"Processing namespace: {namespace}")
service_accounts = get_service_accounts_in_namespace(namespace)

for sa in service_accounts:
# Create a token for the ServiceAccount with specified duration
command = f"kubectl create token {sa} -n {namespace} --duration={token_duration}s"
try:
token = run_command(command)
except Exception as e:
click.echo(
f"Failed to create token for ServiceAccount {sa} in namespace {namespace}. Error: {str(e)}. Skipping..."
)
continue

# 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 to resources/manifests/
#
# might not be worth it since we are just reading the yaml to then create a bunch of values and its not
# actually used to deploy anything into the cluster
# Then benefit would be making this code a bit cleaner and easy to follow, fwiw
kubeconfig_dict = {
"apiVersion": "v1",
"kind": "Config",
"clusters": [cluster],
"users": [{"name": sa, "user": {"token": token}}],
"contexts": [
{
"name": f"{sa}-{namespace}",
"context": {"cluster": cluster["name"], "namespace": namespace, "user": sa},
}
],
"current-context": f"{sa}-{namespace}",
}

# Write to a YAML file
with open(kubeconfig_file, "w") as f:
yaml.dump(kubeconfig_dict, f, default_flow_style=False)

click.echo(f" Created kubeconfig file for {sa}: {kubeconfig_file}")

click.echo("---")
click.echo(
f"All kubeconfig files have been created in the '{kubeconfig_dir}' directory with a duration of {token_duration} seconds."
)
click.echo("Distribute these files to the respective users.")
click.echo(
"Users can then use by running `warnet auth <file>` or 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.")
Loading

0 comments on commit 26ca399

Please sign in to comment.