-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(scripts): add script to create users
Signed-off-by: Bradley Reynolds <[email protected]> Signed-off-by: Siddhesh Mhadnak <[email protected]>
- Loading branch information
1 parent
e393ffc
commit f5107be
Showing
5 changed files
with
336 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,6 @@ secrets.yaml | |
|
||
# Helm | ||
kubernetes/chart/charts/*.tgz | ||
|
||
# Kube Configs | ||
*.config |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
52
kubernetes/manifests/_clusterroles/vipyrsec-core-devs.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()}.") |