personal/vuko/shells initial commit

Change-Id: Icba91e8d4ffe53fc8a7ab7946f3a1b45daf20290
diff --git a/personal/vuko/shells/README.rst b/personal/vuko/shells/README.rst
new file mode 100644
index 0000000..5e81d45
--- /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 0000000..7d5df82
--- /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 0000000..463087e
--- /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 0000000..706dc47
--- /dev/null
+++ b/personal/vuko/shells/sftp.nix
@@ -0,0 +1,75 @@
+{ pkgs ? import <nixpkgs> {} }:
+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 <<EOF > /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 0000000..ac4a9ba
--- /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