Skip to content

Commit 26ca399

Browse files
authored
Merge pull request #616 from mplsgrant/2024-09-create-kube-configs-stepthru
Using team namespaces: Kubeconfig Step Thru
2 parents 7b4dd1e + 424eba8 commit 26ca399

File tree

22 files changed

+1028
-258
lines changed

22 files changed

+1028
-258
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ jobs:
4848
- services_test.py
4949
- signet_test.py
5050
- scenarios_test.py
51+
- namespace_admin_test.py
5152
steps:
5253
- uses: actions/checkout@v4
5354
- uses: azure/[email protected]

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ warnet.egg-info
66
.env
77
dist/
88
build/
9+
**/kubeconfigs/

docs/admin.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Admin
2+
3+
## Connect to your cluster
4+
5+
Ensure you are connected to your cluster because Warnet will use your current configuration to generate configurations for your users.
6+
7+
```shell
8+
$ warnet status
9+
```
10+
11+
Observe that the output of the command matches your cluster.
12+
13+
## Create an *admin* directory
14+
15+
```shell
16+
$ mkdir admin
17+
$ cd admin
18+
$ warnet admin init
19+
```
20+
21+
Observe that there are now two folders within the *admin* directory: *namespaces* and *networks*
22+
23+
## The *namespaces* directory
24+
This directory contains a Helm chart named *two_namespaces_two_users*.
25+
26+
Modify this chart based on the number of teams and users you have.
27+
28+
Deploy the *two_namespaces_two_users* chart.
29+
30+
```shell
31+
$ warnet deploy namespaces/two_namespaces_two_users
32+
```
33+
34+
Observe that this creates service accounts and namespaces in the cluster:
35+
36+
```shell
37+
$ kubectl get ns
38+
$ kubectl get sa -A
39+
```
40+
41+
### Creating Warnet invites
42+
A Warnet invite is a Kubernetes config file.
43+
44+
Create invites for each of your users.
45+
46+
```shell
47+
$ warnet admin create-kubeconfigs
48+
```
49+
50+
Observe the *kubeconfigs* directory. It holds invites for each user.
51+
52+
### Using Warnet invites
53+
Users can connect to your wargame using their invite.
54+
55+
```shell
56+
$ warnet auth alice-wargames-red-team-kubeconfig
57+
```
58+
59+
### Set up a network for your users
60+
Before letting the users into your cluster, make sure to create a network of tanks for them to view.
61+
62+
63+
```shell
64+
$ warnet deploy networks/mynet --to-all-users
65+
```
66+
67+
Observe that the *wargames-red-team* namespace now has tanks in it.
68+
69+
**TODO**: What's the logging approach here?

resources/charts/namespaces/values.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,33 @@ roles:
77
- name: pod-viewer
88
rules:
99
- apiGroups: [""]
10-
resources: ["pods"]
10+
resources: ["pods", "services"]
1111
verbs: ["get", "list", "watch"]
1212
- apiGroups: [""]
1313
resources: ["pods/log", "pods/exec", "pods/attach", "pods/portforward"]
1414
verbs: ["get"]
1515
- apiGroups: [""]
1616
resources: ["configmaps", "secrets"]
17-
verbs: ["get"]
17+
verbs: ["get", "list"]
1818
- apiGroups: [""]
19-
resources: ["persistentvolumeclaims"]
19+
resources: ["persistentvolumeclaims", "namespaces"]
2020
verbs: ["get", "list"]
2121
- apiGroups: [""]
2222
resources: ["events"]
2323
verbs: ["get"]
2424
- name: pod-manager
2525
rules:
2626
- apiGroups: [""]
27-
resources: ["pods"]
27+
resources: ["pods", "services"]
2828
verbs: ["get", "list", "watch", "create", "delete", "update"]
2929
- apiGroups: [""]
3030
resources: ["pods/log", "pods/exec", "pods/attach", "pods/portforward"]
3131
verbs: ["get", "create"]
3232
- apiGroups: [""]
3333
resources: ["configmaps", "secrets"]
34-
verbs: ["get", "create"]
34+
verbs: ["get", "list", "create"]
3535
- apiGroups: [""]
36-
resources: ["persistentvolumeclaims"]
36+
resources: ["persistentvolumeclaims", "namespaces"]
3737
verbs: ["get", "list"]
3838
- apiGroups: [""]
3939
resources: ["events"]

resources/namespaces/two_namespaces_two_users/namespace-defaults.yaml

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ users:
33
roles:
44
- pod-viewer
55
- pod-manager
6-
roles:
7-
- name: pod-viewer
8-
rules:
9-
- apiGroups: [""]
10-
resources: ["pods"]
11-
verbs: ["get", "list", "watch"]
12-
- name: pod-manager
13-
rules:
14-
- apiGroups: [""]
15-
resources: ["pods", "configmaps"]
16-
verbs: ["get", "list", "watch", "create", "update", "delete"]
6+
# the pod-viewer and pod-manager roles are the default
7+
# roles defined in values.yaml for the namespaces charts
8+
#
9+
# if you need a different set of roles for a particular namespaces
10+
# deployment, you can override values.yaml by providing your own
11+
# role definitions below
12+
#
13+
# roles:
14+
# - name: my-custom-role
15+
# rules:
16+
# - apiGroups: ""
17+
# resources: ""
18+
# verbs: ""
Lines changed: 2 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
namespaces:
2-
- name: warnet-red-team
2+
- name: wargames-red-team
33
users:
44
- name: alice
55
roles:
@@ -8,42 +8,7 @@ namespaces:
88
roles:
99
- pod-viewer
1010
- pod-manager
11-
roles:
12-
- name: pod-viewer
13-
rules:
14-
- apiGroups: [""]
15-
resources: ["pods"]
16-
verbs: ["get", "list", "watch"]
17-
- apiGroups: [""]
18-
resources: ["pods/log", "pods/exec", "pods/attach", "pods/portforward"]
19-
verbs: ["get"]
20-
- apiGroups: [""]
21-
resources: ["configmaps", "secrets"]
22-
verbs: ["get"]
23-
- apiGroups: [""]
24-
resources: ["persistentvolumeclaims"]
25-
verbs: ["get", "list"]
26-
- apiGroups: [""]
27-
resources: ["events"]
28-
verbs: ["get"]
29-
- name: pod-manager
30-
rules:
31-
- apiGroups: [""]
32-
resources: ["pods"]
33-
verbs: ["get", "list", "watch", "create", "delete", "update"]
34-
- apiGroups: [""]
35-
resources: ["pods/log", "pods/exec", "pods/attach", "pods/portforward"]
36-
verbs: ["get", "create"]
37-
- apiGroups: [""]
38-
resources: ["configmaps", "secrets"]
39-
verbs: ["get", "create"]
40-
- apiGroups: [""]
41-
resources: ["persistentvolumeclaims"]
42-
verbs: ["get", "list"]
43-
- apiGroups: [""]
44-
resources: ["events"]
45-
verbs: ["get"]
46-
- name: warnet-blue-team
11+
- name: wargames-blue-team
4712
users:
4813
- name: mallory
4914
roles:
@@ -52,38 +17,3 @@ namespaces:
5217
roles:
5318
- pod-viewer
5419
- pod-manager
55-
roles:
56-
- name: pod-viewer
57-
rules:
58-
- apiGroups: [""]
59-
resources: ["pods"]
60-
verbs: ["get", "list", "watch"]
61-
- apiGroups: [""]
62-
resources: ["pods/log", "pods/exec", "pods/attach", "pods/portforward"]
63-
verbs: ["get"]
64-
- apiGroups: [""]
65-
resources: ["configmaps", "secrets"]
66-
verbs: ["get"]
67-
- apiGroups: [""]
68-
resources: ["persistentvolumeclaims"]
69-
verbs: ["get", "list"]
70-
- apiGroups: [""]
71-
resources: ["events"]
72-
verbs: ["get"]
73-
- name: pod-manager
74-
rules:
75-
- apiGroups: [""]
76-
resources: ["pods"]
77-
verbs: ["get", "list", "watch", "create", "delete", "update"]
78-
- apiGroups: [""]
79-
resources: ["pods/log", "pods/exec", "pods/attach", "pods/portforward"]
80-
verbs: ["get", "create"]
81-
- apiGroups: [""]
82-
resources: ["configmaps", "secrets"]
83-
verbs: ["get", "create"]
84-
- apiGroups: [""]
85-
resources: ["persistentvolumeclaims"]
86-
verbs: ["get", "list"]
87-
- apiGroups: [""]
88-
resources: ["events"]
89-
verbs: ["get"]

src/warnet/admin.py

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
import os
2+
import sys
23
from pathlib import Path
34

45
import click
6+
import yaml
57
from rich import print as richprint
68

7-
from .constants import NETWORK_DIR
9+
from .constants import KUBECONFIG, NETWORK_DIR, WARGAMES_NAMESPACE_PREFIX
10+
from .k8s import (
11+
K8sError,
12+
get_cluster_of_current_context,
13+
get_namespaces_by_type,
14+
get_service_accounts_in_namespace,
15+
open_kubeconfig,
16+
)
817
from .namespaces import copy_namespaces_defaults, namespaces
918
from .network import copy_network_defaults
19+
from .process import run_command
1020

1121

1222
@click.group(name="admin", hidden=True)
@@ -33,3 +43,94 @@ def init():
3343
f"[green]Copied network and namespace example files to {Path(current_dir) / NETWORK_DIR.name}[/green]"
3444
)
3545
richprint(f"[green]Created warnet project structure in {current_dir}[/green]")
46+
47+
48+
@admin.command()
49+
@click.option(
50+
"--kubeconfig-dir",
51+
default="kubeconfigs",
52+
help="Directory to store kubeconfig files (default: kubeconfigs)",
53+
)
54+
@click.option(
55+
"--token-duration",
56+
default=172800,
57+
type=int,
58+
help="Duration of the token in seconds (default: 48 hours)",
59+
)
60+
def create_kubeconfigs(kubeconfig_dir, token_duration):
61+
"""Create kubeconfig files for ServiceAccounts"""
62+
kubeconfig_dir = os.path.expanduser(kubeconfig_dir)
63+
64+
try:
65+
kubeconfig_data = open_kubeconfig(KUBECONFIG)
66+
except K8sError as e:
67+
click.secho(e, fg="yellow")
68+
click.secho(f"Could not open auth_config: {KUBECONFIG}", fg="red")
69+
sys.exit(1)
70+
71+
cluster = get_cluster_of_current_context(kubeconfig_data)
72+
73+
os.makedirs(kubeconfig_dir, exist_ok=True)
74+
75+
# Get all namespaces that start with prefix
76+
# This assumes when deploying multiple namespaces for the purpose of team games, all namespaces start with a prefix,
77+
# e.g., tabconf-wargames-*. Currently, this is a bit brittle, but we can improve on this in the future
78+
# by automatically applying a TEAM_PREFIX when creating the get_warnet_namespaces
79+
# TODO: choose a prefix convention and have it managed by the helm charts instead of requiring the
80+
# admin user to pipe through the correct string in multiple places. Another would be to use
81+
# labels instead of namespace naming conventions
82+
warnet_namespaces = get_namespaces_by_type(WARGAMES_NAMESPACE_PREFIX)
83+
84+
for v1namespace in warnet_namespaces:
85+
namespace = v1namespace.metadata.name
86+
click.echo(f"Processing namespace: {namespace}")
87+
service_accounts = get_service_accounts_in_namespace(namespace)
88+
89+
for sa in service_accounts:
90+
# Create a token for the ServiceAccount with specified duration
91+
command = f"kubectl create token {sa} -n {namespace} --duration={token_duration}s"
92+
try:
93+
token = run_command(command)
94+
except Exception as e:
95+
click.echo(
96+
f"Failed to create token for ServiceAccount {sa} in namespace {namespace}. Error: {str(e)}. Skipping..."
97+
)
98+
continue
99+
100+
# Create a kubeconfig file for the user
101+
kubeconfig_file = os.path.join(kubeconfig_dir, f"{sa}-{namespace}-kubeconfig")
102+
103+
# TODO: move yaml out of python code to resources/manifests/
104+
#
105+
# might not be worth it since we are just reading the yaml to then create a bunch of values and its not
106+
# actually used to deploy anything into the cluster
107+
# Then benefit would be making this code a bit cleaner and easy to follow, fwiw
108+
kubeconfig_dict = {
109+
"apiVersion": "v1",
110+
"kind": "Config",
111+
"clusters": [cluster],
112+
"users": [{"name": sa, "user": {"token": token}}],
113+
"contexts": [
114+
{
115+
"name": f"{sa}-{namespace}",
116+
"context": {"cluster": cluster["name"], "namespace": namespace, "user": sa},
117+
}
118+
],
119+
"current-context": f"{sa}-{namespace}",
120+
}
121+
122+
# Write to a YAML file
123+
with open(kubeconfig_file, "w") as f:
124+
yaml.dump(kubeconfig_dict, f, default_flow_style=False)
125+
126+
click.echo(f" Created kubeconfig file for {sa}: {kubeconfig_file}")
127+
128+
click.echo("---")
129+
click.echo(
130+
f"All kubeconfig files have been created in the '{kubeconfig_dir}' directory with a duration of {token_duration} seconds."
131+
)
132+
click.echo("Distribute these files to the respective users.")
133+
click.echo(
134+
"Users can then use by running `warnet auth <file>` or with kubectl by specifying the --kubeconfig flag or by setting the KUBECONFIG environment variable."
135+
)
136+
click.echo(f"Note: The tokens will expire after {token_duration} seconds.")

0 commit comments

Comments
 (0)