Skip to content

Commit

Permalink
feat(scripts): add script to create users
Browse files Browse the repository at this point in the history
Signed-off-by: Bradley Reynolds <[email protected]>
Signed-off-by: Siddhesh Mhadnak <[email protected]>
  • Loading branch information
shenanigansd authored and sid-maddy committed Aug 5, 2024
1 parent e393ffc commit f5107be
Show file tree
Hide file tree
Showing 5 changed files with 336 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ secrets.yaml

# Helm
kubernetes/chart/charts/*.tgz

# Kube Configs
*.config
18 changes: 18 additions & 0 deletions kubernetes/manifests/_clusterroles/vipyrsec-admins.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole

metadata:
name: vipyrsec-admins

rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
- nonResourceURLs:
- '*'
verbs:
- '*'
52 changes: 52 additions & 0 deletions kubernetes/manifests/_clusterroles/vipyrsec-core-devs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole

metadata:
name: vipyrsec-core-devs

rules:
- apiGroups:
- ''
resources:
- pods
- services
- configmaps
- secrets
verbs:
- list
- get
- patch
- update

- apiGroups:
- apps
resources:
- daemonsets
- replicasets
- deployments
verbs:
- list
- get
- patch
- update

- apiGroups:
- batch
resources:
- jobs
verbs:
- list
- get
- patch
- update

- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs:
- list
- get
- patch
- update
40 changes: 40 additions & 0 deletions kubernetes/manifests/_clusterroles/vipyrsec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole

metadata:
name: vipyrsec

rules:
- apiGroups:
- ''
resources:
- pods
- services
- configmaps
- secrets
verbs:
- list

- apiGroups:
- apps
resources:
- daemonsets
- replicasets
- deployments
verbs:
- list

- apiGroups:
- batch
resources:
- jobs
verbs:
- list

- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs:
- list
223 changes: 223 additions & 0 deletions scripts/create_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
#!/usr/bin/env python3

import argparse
import json
import string
import base64
import logging
import subprocess
import tempfile
from pathlib import Path

logging.basicConfig(
format="[%(asctime)s] [%(levelname)-8s] %(message)s",
level=logging.INFO,
)
log = logging.getLogger()

CSR_TEMPLATE = string.Template("""\
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: $user
spec:
request: $request
signerName: kubernetes.io/kube-apiserver-client
usages:
- client auth
""")
CRB_PATCH_TEMPLATE = string.Template(
json.dumps(
[
{
"op": "add",
"path": "/subjects/-1",
"value": {"kind": "User", "name": "$user"},
}
]
)
)


def run_command(command: str, input: str | None = None) -> str:
"""Run a command in a shell.
Args:
command: The command to run.
input: The input for the command, if any.
Returns:
The output of the command.
"""
log.debug("Running command %r", command)
try:
proc = subprocess.run(
command,
shell=True,
capture_output=True,
check=True,
input=input,
text=True,
)
return proc.stdout
except subprocess.CalledProcessError as cpe:
log.exception(
"Failed to run command %r (%d): %s",
cpe.cmd,
cpe.returncode,
cpe.stderr,
)
raise


def generate_key() -> str:
"""Generate a private key.
Returns:
The private key.
"""
return run_command("openssl genrsa 2048")


def create_csr(user: str, groups: list[str], key: str) -> str:
"""Create a certificate signing request (CSR).
Args:
user: The user for which to create the CSR.
groups: The groups to which the user belongs.
key: The private key of the user.
Returns:
The CSR.
"""
cert_req = run_command(
f"openssl req -new -key /dev/stdin -subj {''.join(f'/O={group}' for group in groups)}/CN={user}",
input=key,
)
return base64.b64encode(cert_req.encode("utf-8")).decode("utf-8").replace("\n", "")


def approve_csr(csr: str, user: str) -> str:
"""Approve the certificate signing request (CSR).
Args:
csr: The CSR to approve.
user: The user associated to the CSR.
Returns:
The approved certificate.
"""
csr_manifest = CSR_TEMPLATE.substitute(user=user, request=csr)
run_command("kubectl apply -f -", input=csr_manifest)
run_command(f"kubectl certificate approve {user}")
cert = run_command(f"kubectl get csr {user} -ojsonpath='{{.status.certificate}}'")
run_command(f"kubectl delete csr {user}")
return base64.b64decode(cert).decode("utf-8")


def grant_permissions(user: str, group: str) -> None:
"""Grant permissions to the user.
Args:
user: The user to which to grant permissions.
group: The group for which to grant the user permission.
"""
crb_patch = CRB_PATCH_TEMPLATE.substitute(user=user)
run_command(
f"kubectl patch clusterrolebinding {group} --type=json --patch='{crb_patch}'"
)


def generate_kube_config(user: str, key: str, cert: str) -> None:
"""Generate a kube config for the user.
Args:
user: The user for which to generate the kube config.
key: The private key of the user.
cert: The approved certificate of the user.
Returns:
The kube config.
"""
config_path = Path(f"{user}.config")
config = run_command("kubectl config view --flatten --minify")
config_path.write_text(config)
kubectl = f"kubectl --kubeconfig={config_path}"

clusters = run_command(f"{kubectl} config get-clusters")
cluster = clusters.splitlines()[-1]

current_context = run_command(f"{kubectl} config current-context")
run_command(f"{kubectl} config delete-context {current_context.strip('\n')}")

users = run_command(f"{kubectl} config get-users")
current_user = users.splitlines()[-1]
run_command(f"{kubectl} config delete-user {current_user}")

with (
tempfile.NamedTemporaryFile("w", suffix=f"{user}.key") as key_file,
tempfile.NamedTemporaryFile("w", suffix=f"{user}.crt") as cert_file,
):
key_file.write(key)
key_file.flush()
cert_file.write(cert)
cert_file.flush()

run_command(
f"{kubectl} config set-credentials {user} --client-key={key_file.name} --client-certificate={cert_file.name} --embed-certs"
)

context = f"{user}@{cluster}"
run_command(
f"{kubectl} config set-context {context} --cluster={cluster} --user={user}",
)
run_command(f"{kubectl} config use-context {context}")

return config_path


if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Create a user with a given role",
allow_abbrev=False,
)
parser.add_argument("user", help="The user to create")
parser.add_argument(
"-g",
"--group",
action="append",
help="The group(s) to which to add the user",
choices=["vipyrsec-core-devs", "vipyrsec-admins"],
default=[],
)
args = parser.parse_args()
log.debug("Args: %s", args)

user: str = args.user
groups: list[str] = args.group
groups = ["vipyrsec"] + groups

log.debug("Generating private key")
key = generate_key()
log.info("Generated private key")

log.debug("Creating CSR for user %r with groups %s", user, ",".join(groups))
csr = create_csr(user, groups, key)
log.info("Created CSR for user %r with groups %s", user, ",".join(groups))

log.debug("Approving CSR for user %r", user)
cert = approve_csr(csr, user)
log.info("Approved CSR for user %r", user)

for group in groups:
log.debug("Granting permissions to user %r for group %r", user, group)
grant_permissions(user, group)
log.info("Granted permissions to user %r for group %r", user, group)

log.debug("Generating kubeconfig for user %r", user)
config_path = generate_kube_config(user, key, cert)
log.debug("Generated kubeconfig for user %r", user)

log.info(f"Config written to {config_path.resolve()}.")

0 comments on commit f5107be

Please sign in to comment.