*: k0.hswaw.net somewhat working
diff --git a/tools/BUILD b/tools/BUILD
index 9a1df68..dfdef2f 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -1,25 +1,36 @@
+load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar", "pkg_deb")
+load("@py_deps//:requirements.bzl", "requirement")
 load("//bzl:rules.bzl", "copy_go_binary")
 
 py_binary(
     name = "secretstore",
     srcs = ["secretstore.py"],
+    visibility = ["//visibility:public"],
+)
+
+py_binary(
+    name = "clustercfg",
+    srcs = ["clustercfg.py"],
+    visibility = ["//visibility:public"],
+    deps = [
+        requirement("fabric"),
+    ],
+)
+
+py_binary(
+    name = "pass",
+    srcs = ["pass.py"],
+    visibility = ["//visibility:public"],
 )
 
 copy_go_binary(
     name = "kubectl",
     src = "@io_k8s_kubernetes//cmd/kubectl:kubectl",
+    visibility = ["//visibility:public"],
 )
 
 copy_go_binary(
     name = "kubecfg",
     src = "@com_github_ksonnet_kubecfg//:kubecfg",
-)
-
-filegroup(
-    name = "tools",
-    srcs = [
-        ":secretstore",
-        ":kubectl",
-        ":kubecfg",
-    ],
+    visibility = ["//visibility:public"],
 )
diff --git a/tools/clustercfg.py b/tools/clustercfg.py
new file mode 100644
index 0000000..332d6e6
--- /dev/null
+++ b/tools/clustercfg.py
@@ -0,0 +1,352 @@
+#!/usr/bin/env python
+
+from builtins import object
+
+import datetime
+from io import BytesIO
+import logging
+import os
+import tempfile
+import subprocess
+import sys
+
+from cryptography import x509
+from cryptography.hazmat.backends import default_backend
+import fabric
+
+import secretstore
+
+
+cluster = 'k0.hswaw.net'
+remote_root = '/opt/hscloud'
+local_root = os.getenv('hscloud_root')
+
+if local_root is None:
+    raise Exception("Please source env.sh")
+
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.DEBUG)
+logger.addHandler(logging.StreamHandler())
+
+
+def decrypt(base):
+    src = os.path.join(local_root, 'cluster/secrets/cipher', base)
+    dst = os.path.join(local_root, 'cluster/secrets/plain', base)
+    secretstore.decrypt(src, dst)
+
+
+class PKI(object):
+    def __init__(self):
+        self.cacert = os.path.join(local_root, 'cluster/certs/ca.crt')
+        self.cakey = os.path.join(local_root, 'cluster/secrets/plain/ca.key')
+
+        if not os.path.exists(self.cakey):
+            decrypt('ca.key')
+
+    def sign(self, csr, crt, conf, days=365):
+        logger.info('pki: signing {} for {} days'.format(csr, days))
+        subprocess.check_call([
+            'openssl', 'x509', '-req',
+            '-in', csr,
+            '-CA', self.cacert,
+            '-CAkey', self.cakey,
+            '-out', crt,
+            '-extensions', 'SAN', '-extfile', conf,
+            '-days', str(days),
+        ])
+
+
+class Subject(object):
+    hswaw = "Stowarzyszenie Warszawski Hackerspace"
+    def __init__(self, o, ou, cn):
+        self.c = 'PL'
+        self.st = 'Mazowieckie'
+        self.l = 'Warszawa'
+        self.o = o
+        self.ou = ou
+        self.cn = cn
+
+    @property
+    def parts(self):
+        return {
+            'C': self.c,
+            'ST': self.st,
+            'L': self.l,
+            'O': self.o,
+            'OU': self.ou,
+            'CN': self.cn,
+        }
+
+    def __str__(self):
+        parts = self.parts
+        res = []
+        for p in ['C', 'ST', 'L', 'O', 'OU', 'CN']:
+            res.append('/{}={}'.format(p, parts[p]))
+        return ''.join(res)
+
+def _file_exists(c, filename):
+    res = c.run('stat "{}"'.format(filename), warn=True, hide=True)
+    return res.exited == 0
+
+def openssl_config(san):
+    with open(os.path.join(local_root, 'cluster/openssl.cnf'), 'rb') as f:
+        config = BytesIO(f.read())
+
+    config.seek(0, 2)
+    config.write(b'\n[SAN]\n')
+    for s in san:
+        config.write('subjectAltName=DNS:{}\n'.format(s).encode())
+
+    f = tempfile.NamedTemporaryFile(delete=False)
+    path = f.name
+    f.write(config.getvalue())
+    f.close()
+
+    return path
+
+def remote_cert(pki, c, fqdn, cert_name, subj, san=[], days=365):
+    logger.info("{}/{}: remote cert".format(fqdn, cert_name))
+
+    remote_key = os.path.join(remote_root, '{}.key'.format(cert_name))
+    remote_cert = os.path.join(remote_root, '{}.crt'.format(cert_name))
+    remote_csr = os.path.join(remote_root, '{}.csr'.format(cert_name))
+    remote_config = os.path.join(remote_root, 'openssl.cnf')
+
+    generate_cert = False
+    if not _file_exists(c, remote_key):
+        logger.info("{}/{}: generating key".format(fqdn, cert_name))
+        c.run('openssl genrsa -out "{}" 4096'.format(remote_key), hide=True)
+        genereate_cert = True
+
+    b = BytesIO()
+    try:
+        c.get(local=b, remote=remote_cert)
+        cert = x509.load_pem_x509_certificate(b.getvalue(), default_backend())
+        delta = cert.not_valid_after - datetime.datetime.now()
+        logger.info("{}/{}: existing cert expiry: {}".format(fqdn, cert_name, delta))
+        if delta.total_seconds() < 3600 * 24 * 60:
+            logger.info("{}/{}: expires soon, regenerating".format(fqdn, cert_name))
+            generate_cert = True
+    except (FileNotFoundError, ValueError):
+        generate_cert = True
+
+    if not generate_cert:
+        return False
+
+
+    local_config = openssl_config(san)
+    c.put(local=local_config, remote=remote_config)
+
+    c.run("""
+        nix-shell -p openssl --command "openssl req -new -key {remote_key} -out {remote_csr} -subj '{subj}' -config {remote_config} -reqexts SAN"
+    """.format(remote_key=remote_key, remote_csr=remote_csr, subj=str(subj), remote_config=remote_config))
+
+    local_csr_f = tempfile.NamedTemporaryFile(delete=False)
+    local_csr = local_csr_f.name
+    local_csr_f.close()
+
+    local_cert = os.path.join(local_root, 'cluster/certs', '{}-{}.crt'.format(fqdn, cert_name))
+
+    c.get(local=local_csr, remote=remote_csr)
+
+    pki.sign(local_csr, local_cert, local_config, days)
+
+    c.put(local=local_cert, remote=remote_cert)
+
+    os.remove(local_csr)
+    os.remove(local_config)
+
+    return True
+
+
+def shared_cert(pki, c, fqdn, cert_name, subj, san=[], days=365):
+    logger.info("{}/{}: shared cert".format(fqdn, cert_name))
+
+    local_key = os.path.join(local_root, 'cluster/secrets/plain', '{}.key'.format(cert_name))
+    local_cert = os.path.join(local_root, 'cluster/certs', '{}.crt'.format(cert_name))
+    remote_key = os.path.join(remote_root, '{}.key'.format(cert_name))
+    remote_cert = os.path.join(remote_root, '{}.crt'.format(cert_name))
+
+    generate_cert = False
+    if not os.path.exists(local_key):
+        try:
+            decrypt('{}.key'.format(cert_name))
+        except subprocess.CalledProcessError:
+            logger.info("{}/{}: generating key".format(fqdn, cert_name))
+            subprocess.check_call([
+                'openssl', 'genrsa', '-out', local_key, '4096',
+            ])
+            generate_cert = True
+
+    if os.path.exists(local_cert):
+        with open(local_cert, 'rb') as f:
+            b = f.read()
+            cert = x509.load_pem_x509_certificate(b, default_backend())
+            delta = cert.not_valid_after - datetime.datetime.now()
+            logger.info("{}/{}: existing cert expiry: {}".format(fqdn, cert_name, delta))
+            if delta.total_seconds() < 3600 * 24 * 60:
+                logger.info("{}/{}: expires soon, regenerating".format(fqdn, cert_name))
+                generate_cert = True
+    else:
+        generate_cert = True
+
+    if not generate_cert:
+        return False
+
+    local_csr_f = tempfile.NamedTemporaryFile(delete=False)
+    local_csr = local_csr_f.name
+    local_csr_f.close()
+
+    local_config = openssl_config(san)
+
+    subprocess.check_call([
+        'openssl', 'req', '-new',
+        '-key', local_key,
+        '-out', local_csr,
+        '-subj', str(subj),
+        '-config', local_config,
+        '-reqexts', 'SAN',
+    ])
+
+    pki.sign(local_csr, local_cert, local_config, days)
+
+    c.put(local=local_key, remote=remote_key)
+    c.put(local=local_cert, remote=remote_cert)
+
+    os.remove(local_csr)
+    os.remove(local_config)
+    return True
+
+
+def configure_k8s(username, ca, cert, key):
+    subprocess.check_call([
+        'kubectl', 'config',
+        'set-cluster', cluster,
+        '--certificate-authority=' + ca,
+        '--embed-certs=true',
+        '--server=https://' + cluster + ':4001',
+    ])
+    subprocess.check_call([
+        'kubectl', 'config',
+        'set-credentials', username,
+        '--client-certificate=' + cert,
+        '--client-key=' + key,
+        '--embed-certs=true',
+    ])
+    subprocess.check_call([
+        'kubectl', 'config',
+        'set-context', cluster,
+        '--cluster=' + cluster,
+        '--user=' + username,
+    ])
+    subprocess.check_call([
+        'kubectl', 'config',
+        'use-context', cluster,
+    ])
+
+def admincreds(args):
+    if len(args) != 1:
+        sys.stderr.write("Usage: admincreds q3k\n")
+        return 1
+    username = args[0]
+
+    pki = PKI()
+
+    local_key = os.path.join(local_root, '.kubectl/admin.key')
+    local_cert = os.path.join(local_root, '.kubectl/admin.crt')
+    local_csr = os.path.join(local_root, '.kubectl/admin.csr')
+
+    generate_cert = False
+    if not os.path.exists(local_key):
+        subprocess.check_call([
+            'openssl', 'genrsa', '-out', local_key, '4096',
+        ])
+        generate_cert = True
+
+    if os.path.exists(local_cert):
+        with open(local_cert, 'rb') as f:
+            b = f.read()
+            cert = x509.load_pem_x509_certificate(b, default_backend())
+            delta = cert.not_valid_after - datetime.datetime.now()
+            logger.info("admin: existing cert expiry: {}".format(delta))
+            if delta.total_seconds() < 3600 * 24:
+                logger.info("admin: expires soon, regenerating")
+                generate_cert = True
+    else:
+        generate_cert = True
+
+    if not generate_cert:
+        return configure_k8s(username, pki.cacert, local_cert, local_key)
+
+    local_config = openssl_config([])
+    subj = Subject('system:masters', "Kubernetes Admin Account for {}".format(username), username)
+
+    subprocess.check_call([
+        'openssl', 'req', '-new',
+        '-key', local_key,
+        '-out', local_csr,
+        '-subj', str(subj),
+        '-config', local_config,
+        '-reqexts', 'SAN',
+    ])
+
+    pki.sign(local_csr, local_cert, local_config, 5)
+    os.remove(local_config)
+
+    configure_k8s(username, pki.cacert, local_cert, local_key)
+
+
+def nodestrap(args):
+    if len(args) != 1:
+        sys.stderr.write("Usage: nodestrap bc01n01.hswaw.net\n")
+        return 1
+    fqdn = args[0]
+
+    logger.info("Nodestrapping {}...".format(fqdn))
+
+    c = fabric.Connection('root@{}'.format(fqdn))
+    p = PKI()
+
+    modified = False
+    modified |= remote_cert(p, c, fqdn, "node", Subject(Subject.hswaw, 'Node Certificate', fqdn))
+    modified |= remote_cert(p, c, fqdn, "kube-node", Subject('system:nodes', 'Kubelet Certificate', 'system:node:' + fqdn), san=[fqdn,])
+    for component in ['controller-manager', 'proxy', 'scheduler']:
+        o = 'system:kube-{}'.format(component)
+        ou = 'Kuberneter Component {}'.format(component)
+        modified |= shared_cert(p, c, fqdn, 'kube-{}'.format(component), Subject(o, ou, o))
+    modified |= shared_cert(p, c, fqdn, 'kube-apiserver', Subject(Subject.hswaw, 'Kubernetes API', cluster))
+    modified |= shared_cert(p, c, fqdn, 'kube-serviceaccounts', Subject(Subject.hswaw, 'Kubernetes Service Account Signer', 'service-accounts'))
+
+    if modified:
+        logger.info('{}: cert(s) modified, restarting services...'.format(fqdn))
+
+        services = [
+            'kubelet', 'kube-proxy',
+            'kube-apiserver', 'kube-controller-manager', 'kube-scheduler',
+            'etcd'
+        ]
+
+        for s in services:
+            c.run('systemctl stop {}'.format(s))
+        for s in services[::-1]:
+            c.run('systemctl start {}'.format(s))
+
+def usage():
+    sys.stderr.write("Usage: {} <nodestrap|admincreds>\n".format(sys.argv[0]))
+
+def main():
+    if len(sys.argv) < 2:
+        usage()
+        return 1
+
+    mode = sys.argv[1]
+    if mode == "nodestrap":
+        return nodestrap(sys.argv[2:])
+    elif mode == "admincreds":
+        return admincreds(sys.argv[2:])
+    else:
+        usage()
+        return 1
+
+if __name__ == '__main__':
+    sys.exit(main() or 0)
diff --git a/tools/install.sh b/tools/install.sh
new file mode 100755
index 0000000..c16e14a
--- /dev/null
+++ b/tools/install.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+if [ -z "$hscloud_root" ]; then
+    echo 2>&1 "Please first source env.sh"
+    exit 1
+fi
+
+cd "${hscloud_root}"
+
+bazel build \
+        //tools:kubectl //tools:kubecfg //tools:clustercfg //tools:secretstore \
+        //tools:pass
diff --git a/tools/pass.py b/tools/pass.py
new file mode 100644
index 0000000..f291205
--- /dev/null
+++ b/tools/pass.py
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+
+# This is a fake `pass` to make docker-credential-helpers shut up.
+
+import sys
+sys.exit(1)
diff --git a/tools/secretstore.py b/tools/secretstore.py
index 6b88d28..c6a171b 100644
--- a/tools/secretstore.py
+++ b/tools/secretstore.py
@@ -10,6 +10,18 @@
     "482FF104C29294AD1CAF827BA43890A3DE74ECC7", # inf
 ]
 
+def encrypt(src, dst):
+    cmd = ['gpg' , '--encrypt', '--armor', '--batch', '--yes', '--output', dst]
+    for k in keys:
+        cmd.append('--recipient')
+        cmd.append(k)
+    cmd.append(src)
+    subprocess.check_call(cmd)
+
+def decrypt(src, dst):
+    cmd = ['gpg', '--decrypt', '--output', dst, src]
+    subprocess.check_call(cmd)
+
 def main():
     if len(sys.argv) < 3 or sys.argv[1] not in ('encrypt', 'decrypt'):
         sys.stderr.write("Usage: {} encrypt/decrypt file\n".format(sys.argv[0]))
@@ -20,15 +32,9 @@
     src = sys.argv[2]
 
     if action == 'encrypt':
-        cmd = ['gpg' , '--encrypt', '--armor', '--batch', '--yes', '--output', '-']
-        for k in keys:
-            cmd.append('--recipient')
-            cmd.append(k)
-        cmd.append(src)
-        subprocess.check_call(cmd)
+        encrypt(src, '-')
     else:
-        cmd = ['gpg', '--decrypt', '--output', '-', src]
-        subprocess.check_call(cmd)
+        decrypt(src, '-')
 
 if __name__ == '__main__':
     sys.exit(main() or 0)