Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cmd for creating user kubeconfigs (cont. from #545) #598

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions resources/charts/namespaces/templates/namespace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ apiVersion: v1
kind: Namespace
metadata:
name: {{ .Values.namespaceName | default .Release.Name }}
labels:
type: {{ .Values.type }}
3 changes: 2 additions & 1 deletion resources/charts/namespaces/values.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
type: "assets"
users:
- name: warnet-user
roles:
Expand Down Expand Up @@ -37,4 +38,4 @@ roles:
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["events"]
verbs: ["get"]
verbs: ["get"]
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"]
92 changes: 92 additions & 0 deletions src/warnet/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
from rich import print as richprint

from .constants import NETWORK_DIR
from .k8s import get_kubeconfig_value, get_namespaces_by_prefix, get_service_accounts_in_namespace
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 +35,93 @@ 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.argument("prefix", type=str, required=True)
@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(prefix: str, kubeconfig_dir, token_duration):
"""Create kubeconfig files for all ServiceAccounts in warnet team namespaces starting with <prefix>."""
kubeconfig_dir = os.path.expanduser(kubeconfig_dir)

cluster_name = get_kubeconfig_value("{.clusters[0].name}")
cluster_server = get_kubeconfig_value("{.clusters[0].cluster.server}")
cluster_ca = get_kubeconfig_value("{.clusters[0].cluster.certificate-authority-data}")

os.makedirs(kubeconfig_dir, exist_ok=True)

# Get all namespaces that start with prefix
# This assumes when deploying multiple namespacs for the purpose of team games, all namespaces start with a prefix,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s "namespaces"

# e.g., tabconf-wargames-*. Currently, this is a bit brittle, but we can improve on this in the future
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah it feel like instead of using naming conventions k8s probably wants us to use metadata labels or something.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it would be great to have a "cluster level" configmap. We could have a wargames-admin namespace , where we create a configmap for all of the war-games related metadata (e.g., all of the namespaces , players), and then any administration commands can reference that to get all of the correct values? This admin namespace could also be used for deploying the signet miner, etc.

# 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_prefix(prefix)

for namespace in warnet_namespaces:
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/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could use yaml.dump() like in deploy_namespaces() and deploy_network()

#
# 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_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("---")
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.")
1 change: 1 addition & 0 deletions src/warnet/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
LOGGING_NAMESPACE = "warnet-logging"
INGRESS_NAMESPACE = "ingress"
HELM_COMMAND = "helm upgrade --install --create-namespace"
WARNET_ASSETS = "assets"

# Directories and files for non-python assets, e.g., helm charts, example scenarios, default configs
SRC_DIR = files("warnet")
Expand Down
12 changes: 7 additions & 5 deletions src/warnet/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,9 @@ def delete_pod(pod_name, namespace):

# Delete remaining pods
pods = get_pods()
for pod in pods.items:
futures.append(executor.submit(delete_pod, pod.metadata.name, pod.metadata.namespace))
for pod_list in pods:
for pod in pod_list.items:
futures.append(executor.submit(delete_pod, pod.metadata.name, pod.metadata.namespace))

# Wait for all tasks to complete and print results
for future in as_completed(futures):
Expand All @@ -159,9 +160,10 @@ def get_active_network(namespace):


@click.command(context_settings={"ignore_unknown_options": True})
@click.option("--namespace", "-n", type=str, help="Namespace to run scenario in (overrides the current namespace in kubectl)")
@click.argument("scenario_file", type=click.Path(exists=True, file_okay=True, dir_okay=False))
@click.argument("additional_args", nargs=-1, type=click.UNPROCESSED)
def run(scenario_file: str, additional_args: tuple[str]):
def run(namespace: str, scenario_file: str, additional_args: tuple[str]):
"""
Run a scenario from a file.
Pass `-- --help` to get individual scenario help
Expand All @@ -173,7 +175,7 @@ def run(scenario_file: str, additional_args: tuple[str]):
scenario_data = base64.b64encode(file.read()).decode()

name = f"commander-{scenario_name.replace('_', '')}-{int(time.time())}"
namespace = get_default_namespace()
ns = namespace if namespace else get_default_namespace()
tankpods = get_mission("tank")
tanks = [
{
Expand All @@ -198,7 +200,7 @@ def run(scenario_file: str, additional_args: tuple[str]):
"upgrade",
"--install",
"--namespace",
namespace,
ns,
"--set",
f"fullnameOverride={name}",
"--set",
Expand Down
22 changes: 9 additions & 13 deletions src/warnet/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,23 @@ def validate_directory(ctx, param, value):
callback=validate_directory,
)
@click.option("--debug", is_flag=True)
def deploy(directory, debug):
@click.option("--namespace", "-n", type=str, help="Namespace to deploy network into (overrides the current namespace in kubectl)")
def deploy(directory, debug, namespace):
"""Deploy a warnet with topology loaded from <directory>"""
directory = Path(directory)

if (directory / NETWORK_FILE).exists():
dl = deploy_logging_stack(directory, debug)
deploy_network(directory, debug)
deploy_network(directory, namespace, debug)
df = deploy_fork_observer(directory, debug)
if dl | df:
deploy_ingress(debug)
deploy_caddy(directory, debug)
elif (directory / NAMESPACES_FILE).exists():
deploy_namespaces(directory)
if namespace:
click.echo("Cannot specify a --namespace when deploying a namespaces chart.")
else:
deploy_namespaces(directory)
else:
click.echo(
"Error: Neither network.yaml nor namespaces.yaml found in the specified directory."
Expand Down Expand Up @@ -189,14 +193,14 @@ def deploy_fork_observer(directory: Path, debug: bool) -> bool:
return True


def deploy_network(directory: Path, debug: bool = False):
def deploy_network(directory: Path, namespace_override: str, debug: bool = False):
network_file_path = directory / NETWORK_FILE
defaults_file_path = directory / DEFAULTS_FILE

with network_file_path.open() as f:
network_file = yaml.safe_load(f)

namespace = get_default_namespace()
namespace = namespace_override if namespace_override else get_default_namespace()

for node in network_file["nodes"]:
click.echo(f"Deploying node: {node.get('name')}")
Expand Down Expand Up @@ -235,14 +239,6 @@ def deploy_namespaces(directory: Path):
with namespaces_file_path.open() as f:
namespaces_file = yaml.safe_load(f)

names = [n.get("name") for n in namespaces_file["namespaces"]]
for n in names:
if not n.startswith("warnet-"):
click.echo(
f"Failed to create namespace: {n}. Namespaces must start with a 'warnet-' prefix."
)
return

for namespace in namespaces_file["namespaces"]:
click.echo(f"Deploying namespace: {namespace.get('name')}")
try:
Expand Down
Loading
Loading