diff --git a/personal/vuko/shells/README.rst b/personal/vuko/shells/README.rst new file mode 100644 index 00000000..5e81d458 --- /dev/null +++ b/personal/vuko/shells/README.rst @@ -0,0 +1,9 @@ +Hosting for Hackerspace Three Shell System announcement. Currently uploading +is performed using sftp. + +.. code::bash + scp index.html shells@185.236.240.58:index.html + +TODO: + * web interface for shells rotation + * access for other members? diff --git a/personal/vuko/shells/create-secrets.py b/personal/vuko/shells/create-secrets.py new file mode 100644 index 00000000..7d5df82d --- /dev/null +++ b/personal/vuko/shells/create-secrets.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +""" generate ssh keys for shells SFTP container """ +from pathlib import Path +from subprocess import run +import json +import tempfile + +with tempfile.TemporaryDirectory() as tmp: + tmp = Path(tmp).absolute() + keyfile = tmp.joinpath("ssh_host_ed25519_key") + run(["ssh-keygen", "-f", keyfile, "-N", "", "-t", "ed25519"], check=True) + + # https://kubernetes.io/docs/concepts/configuration/secret/#generating-a-secret-from-files + generator = { + "secretGenerator": [ + { + "name": "shells-ssh-host-key", + "files": [ + str(f.relative_to(tmp)) + for f in [keyfile, keyfile.with_suffix(".pub")] + ], + } + ] + } + tmp.joinpath("kustomization.yaml").write_text(json.dumps(generator)) + run(["kubectl", "-n", "personal-vuko", "apply", "-k", tmp], check=True) diff --git a/personal/vuko/shells/prod.jsonnet b/personal/vuko/shells/prod.jsonnet new file mode 100644 index 00000000..463087e9 --- /dev/null +++ b/personal/vuko/shells/prod.jsonnet @@ -0,0 +1,163 @@ +# this is libjsonnet library for kubernetes related things +local kube = import '../../../kube/kube.libsonnet'; + +{ + local shells = self, + local cfg = shells.cfg, + + # namespace defining parameters used by other functions + # double colon "::" prevents it from appearing in output file + cfg:: { + namespace: "personal-vuko", + appName: "three-shell-system", + domain: "shells.vuko.pl", + + nginx_tag: "latest", + nginx_image: "nginxinc/nginx-unprivileged:stable-alpine", + + storageClassName: "waw-hdd-redundant-2", + + resources: { + requests: { + cpu: "25m", + memory: "50Mi", + }, + limits: { + cpu: "100m", + memory: "200Mi", + }, + }, + }, + + # kubernete namespace personal-${name} for personal usage + namespace: kube.Namespace(cfg.namespace), + + # function used for configuring components metatada + metadata(component):: { + namespace: cfg.namespace, + labels: { + "app.kubernetes.io/name": cfg.appName, + "app.kubernetes.io/managed-by": "kubecfg", + "app.kubernetes.io/component": component, + }, + }, + + # component - persistant (non volatile) memory + # https://kubernetes.io/docs/concepts/storage/persistent-volumes/ + dataVolume: kube.PersistentVolumeClaim("html-data") { + # override default PersistentVolumeClaim metatada with values defined + # in medadata function prevoiusly created + # "+" sign before means override + metadata+: shells.metadata("html-data"), + spec+: { + storageClassName: cfg.storageClassName, + # can be connected to multiple containers + accessModes: [ "ReadWriteMany" ], + resources: { + requests: { + # amount of storage space: 500Mb + storage: "500Mi", + }, + }, + }, + }, + + # deployment declares pods + # https://kubernetes.io/docs/concepts/workloads/controllers/deployment/ + deployment: kube.Deployment("shells") { + metadata+: shells.metadata("shells"), + spec+: { + replicas: 1, + template+: { + spec+: { + # names ending with _ have special meaning in this context + # this is specified in ../../../kube/kube.upstream.jsonnet + # volumes_ { key: { ... } } is converted to volumes [{ name: key, ... }] + volumes_: { + # sftp container host keys secrets saved to kubernetes semi-manually using create-secrets.py + # https://kubernetes.io/docs/concepts/configuration/secret/ + host_keys: { secret: { secretName: "shells-ssh-host-key-bd65mg4gbt" } }, + # sftp container authorized_keys saved to kubernetes using command: + # kubectl -n personal-vuko create secret generic shells-ssh-authorized-keys --from-file="authorized_keys=${HOME}/.ssh/id_ed25519.pub" + authorized_keys: { secret: { secretName: "shells-ssh-authorized-keys", defaultMode: 256 } }, + # to use created volume in deployment we need to claim it + html: kube.PersistentVolumeClaimVolume(shells.dataVolume), + }, + # here are containers defined + # when they are defined in one deployment + containers_: { + shells: kube.Container("nginx") { + image: cfg.nginx_image, + ports_: { + http: { containerPort: 80 }, + }, + resources: cfg.resources, + volumeMounts_: { + html: { mountPath: "/usr/share/nginx/html" }, + }, + }, + sftp: kube.Container("sftp") { + image: "registry.k0.hswaw.net/vuko/hs-shells-sftp:latest", + ports_: { + sftp: { containerPort: 2222 }, + }, + command: [ "/bin/start" ], + resources: cfg.resources, + securityContext: { + # specify uid of user running command + runAsUser: 1, + }, + volumeMounts_: { + # here volumes defined in volumes_ can be mounted + host_keys: { mountPath: "/etc/ssh/host" }, + authorized_keys: { mountPath: "/etc/ssh/auth" }, + html: { mountPath: "/data" }, + }, + }, + }, + }, + }, + }, + }, + + # defining a service of type LoadBancer gives you acces from internet + # run: kubectl -n personal-${user} get services to see ip address + svc: kube.Service("shells") { + metadata+: shells.metadata("shells"), + target_pod:: shells.deployment.spec.template, + spec+: { + ports: [ + { name: "http", port: 80, targetPort: 8080, protocol: "TCP" }, + { name: "sftp", port: 22, targetPort: 2222, protocol: "TCP" }, + ], + type: "LoadBalancer", + externalTrafficPolicy: "Local", + }, + }, + + # ingress creates VirtualHost on ingress.k0.hswaw.net forwaring http(s) + # requests to your domain to specified Pod/container + ingress: kube.Ingress("frontend") { + metadata+: shells.metadata("frontend") { + annotations+: { + "kubernetes.io/tls-acme": "true", + "certmanager.k8s.io/cluster-issuer": "letsencrypt-prod", + }, + }, + spec+: { + tls: [ + { hosts: [cfg.domain], secretName: "shells-frontend-tls"} + ], + rules: [ + { + host: cfg.domain, + http: { + paths: [ + { path: "/", backend: shells.svc.name_port }, + ], + }, + }, + ], + }, + }, +} diff --git a/personal/vuko/shells/sftp.nix b/personal/vuko/shells/sftp.nix new file mode 100644 index 00000000..706dc475 --- /dev/null +++ b/personal/vuko/shells/sftp.nix @@ -0,0 +1,75 @@ +{ pkgs ? import {} }: +let + #dockertarpusher = pkgs.python37Packages.buildPythonPackage { + # pname = "dockertarpusher"; + # version = "0.16"; + # src = pkgs.fetchFromGitHub { + # owner = "Razikus"; + # repo = "dockerregistrypusher"; + # rev = "217894b79181a9a02ebc6744e0628777a0f89c36"; + # sha256 = "09cqzd9gz42xw30x1jp9mx056k25i20kjzzdg3bk78a4bis29kd4"; + # }; + # propagatedBuildInputs = with pkgs; [ + # python37Packages.requests + # ]; + #}; + #hsregistry_push = import ./registrypush {}; + config = pkgs.runCommand "sshd_config" {} '' + mkdir -p $out/etc/ssh/ + cp ${./sshd_config} $out/etc/ssh/sshd_config + #cp ${./test_keys/test_host_key} $out/etc/ssh/ssh_host_ed25519_key + #cp ${./test_keys/test_host_key.pub} $out/etc/ssh/ssh_host_ed25519_key.pub + #cp ${./test_keys/authorized_keys} $out/etc/ssh/authorized_keys + ''; + name = "vuko/hs-shells-sftp"; + base = pkgs.dockerTools.buildImage { + name = "vuko/ssh-base"; + tag = "latest"; + contents = [pkgs.openssh pkgs.busybox]; + }; + image = pkgs.dockerTools.buildImage { + inherit name; + tag = "latest"; + fromImage = base; + contents = [config]; + + runAsRoot = '' + #!${pkgs.runtimeShell} + mkdir /data/ + #echo "root:x:0:0::/root:/bin/nologin" > /etc/passwd + echo "shells:x:1:1::/data:/bin/sh" >> /etc/passwd + mkdir -p /etc/ssh/host/ + mkdir -p /etc/ssh/auth/ + mkdir -m 700 /tmp + chown 1:1 /tmp + + cat < /bin/start + #!/bin/sh + cp /etc/ssh/auth/authorized_keys /tmp/authorized_keys + /bin/sshd -D -e -f /etc/ssh/sshd_config + EOF + chmod +x /bin/start + ''; + + #https://serverfault.com/questions/344295/is-it-possible-to-run-sshd-as-a-normal-user + config = { + Cmd = [ "/bin/start" ]; + WorkingDir = "/"; + ExposedPorts = { + "2222/tcp" = {}; + }; + }; + }; + push = pkgs.writeShellScriptBin "push" '' + BASEDIR=$(realpath $(dirname ''${BASH_SOURCE})) + docker load < "''${BASEDIR}/../images/sftp.tar.gz" + docker tag ${name}:latest registry.k0.hswaw.net/${name} + docker push registry.k0.hswaw.net/${name} + #exec {hsregistry_push}/bin/hsregistry-push "$BASEDIR/../images/sftp.tar.gz" "$@" + ''; +in pkgs.runCommand "hs-shells-sftp" {} '' + mkdir $out + mkdir -p $out/images $out/bin + ln -s ${image} $out/images/sftp.tar.gz + install ${push}/bin/push $out/bin/ +'' diff --git a/personal/vuko/shells/sshd_config b/personal/vuko/shells/sshd_config new file mode 100644 index 00000000..ac4a9baf --- /dev/null +++ b/personal/vuko/shells/sshd_config @@ -0,0 +1,17 @@ +Port 2222 +AddressFamily any +ListenAddress 0.0.0.0 +#ListenAddress :: +#UsePrivilegeSeparation no +UsePAM no +PermitEmptyPasswords no +PasswordAuthentication no +AuthorizedKeysFile /tmp/authorized_keys +HostKey /etc/ssh/host/ssh_host_ed25519_key +Subsystem sftp /libexec/sftp-server +PidFile /tmp/sshd.pid + +#ForceCommand internal-sftp +AllowTcpForwarding no +X11Forwarding no +PasswordAuthentication no