blob: a438a4c55adceddaef242b689bc265cda4b3ad92 [file] [log] [blame]
#!/usr/bin/env python
from builtins import object
import datetime
from io import BytesIO
import json
import logging
import os
import tempfile
import subprocess
import sys
from cryptography import x509
from cryptography.hazmat.backends import default_backend
import fabric
from tools import secretstore
import ca
local_root = os.getenv('hscloud_root')
if local_root is None:
raise Exception("Please source env.sh")
cluster = 'k0.hswaw.net'
remote_root = '/opt/hscloud'
ss = secretstore.SecretStore(
plain_root=os.path.join(local_root, 'cluster/secrets/plain'),
cipher_root=os.path.join(local_root, 'cluster/secrets/cipher'))
logger = logging.getLogger()
logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(levelname)s - %(message)s')
sh = logging.StreamHandler()
sh.setFormatter(formatter)
logger.addHandler(sh)
def pki_config(key):
raw = subprocess.check_output([
'nix', 'eval', '--raw',
'( (import ' + local_root + '/nix/toplevel.nix ).pki.' + key + '.json )',
])
return json.loads(raw)
def _file_exists(c, filename):
res = c.run('stat "{}"'.format(filename), warn=True, hide=True)
return res.exited == 0
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]
## Make kube certificates.
certs_root = os.path.join(local_root, 'cluster/certs')
ca_kube = ca.CA(ss, certs_root, 'kube', 'kubernetes main CA')
local_key = os.path.join(local_root, '.kubectl/admin.key')
local_crt = os.path.join(local_root, '.kubectl/admin.crt')
kubectl = os.path.join(local_root, '.kubectl')
if not os.path.exists(kubectl):
os.mkdir(kubectl)
generate_cert = False
if not os.path.exists(local_key):
generate_cert = True
if os.path.exists(local_crt):
with open(local_crt, '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, ca_kube._cert, local_crt, local_key)
key, csr = ca_kube.gen_key(hosts=['admin', username], o='system:masters', ou='Kube Admin Account')
crt = ca_kube.sign(csr)
with open(local_key, 'w') as f:
f.write(key)
with open(local_crt, 'w') as f:
f.write(crt)
configure_k8s(username, ca_kube._cert, local_crt, 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))
r = fabric.Connection('root@{}'.format(fqdn))
cfg = dict((k, pki_config(k)) for k in [
'etcdPeer', 'etcd.server', 'etcd.kube'
])
certs_root = os.path.join(local_root, 'cluster/certs')
# Make etcd peer certificate for node.
ca_etcd_peer = ca.CA(ss, certs_root, 'etcdpeer', 'etcd peer ca')
ca_etcd_peer.upload(r, cfg['etcdPeer']['ca'])
c = ca_etcd_peer.make_cert('etcdpeer-{}'.format(fqdn), hosts=[fqdn], ou='node etcd peer certificate')
c.upload_pki(r, cfg['etcdPeer'])
# Make etcd server certificate for node and client certificate for kube.
ca_etcd = ca.CA(ss, certs_root, 'etcd', 'etcd ca')
ca_etcd.upload(r, cfg['etcd.server']['ca'])
c = ca_etcd.make_cert('etcd-{}'.format(fqdn), hosts=[fqdn], ou='node etcd server certificate')
c.upload_pki(r, cfg['etcd.server'])
c = ca_etcd.make_cert('etcd-kube', hosts=['kube'], ou='kube etcd client certificate')
c.upload_pki(r, cfg['etcd.kube'])
# Make root etcd client (do not upload).
ca_etcd.make_cert('etcd-root', hosts=['root'], ou='root etcd client certificate')
# Make calico etcd client (do not upload, used by jsonnet).
ca_etcd.make_cert('etcd-calico', hosts=['calico'], ou='root etcd client certificate')
## Make kube certificates.
ca_kube = ca.CA(ss, certs_root, 'kube', 'kubernetes main CA')
# Make kubelet certificate (per node).
c = ca_kube.make_cert('kube-kubelet-'+fqdn, o='system:nodes', ou='Kubelet', hosts=['system:node:'+fqdn, fqdn])
c.upload_pki(r, pki_config('kube.kubelet'))
# Make apiserver certificate.
c = ca_kube.make_cert('kube-apiserver', ou='Kubernetes API', hosts=[cluster, '10.10.12.1'])
c.upload_pki(r, pki_config('kube.apiserver'), concat_ca=True)
# Make service accounts decryption key (as cert for consistency).
c = ca_kube.make_cert('kube-serviceaccounts', ou='Kubernetes Service Accounts Signer', hosts=['serviceaccounts'])
c.upload_pki(r, pki_config('kube.serviceaccounts'))
# Make kube component certificates.
kube_components = ['controllermanager', 'scheduler', 'proxy']
cfg = dict((k, pki_config('kube.' + k)) for k in kube_components)
for k in kube_components:
ca_kube.upload(r, cfg[k]['ca'])
# meh
if k == 'controllermanager':
o = 'system:kube-controller-manager'
else:
o = 'system:kube-'+k
ou = 'Kubernetes Component '+k
c = ca_kube.make_cert('kube-'+k, ou=ou, o=o, hosts=[o,])
c.upload_pki(r, cfg[k])
## Make kubefront certificates.
ca_kubefront = ca.CA(ss, certs_root, 'kubefront', 'kubernetes frontend CA')
ca_kubefront.upload(r, pki_config('kubeFront.apiserver')['ca'])
c = ca_kubefront.make_cert('kubefront-apiserver', ou='Kubernetes Frontend', hosts=['apiserver'])
c.upload_pki(r, pki_config('kubeFront.apiserver'))
# Upload NixOS config
for f in ['toplevel', 'cluster-configuration']:
r.put(local=os.path.join(local_root, 'nix/{}.nix'.format(f)),
remote='/etc/nixos/{}.nix'.format(f))
r.run('nixos-rebuild switch')
def usage():
sys.stderr.write("Usage: {} <nodestrap|admincreds|config>\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:])
elif mode == "config":
print('etcd peer:')
print(json.dumps(pki_config('etcdPeer'), indent=2))
print('etcd client:')
print(json.dumps(pki_config('etcdClient'), indent=2))
else:
usage()
return 1
if __name__ == '__main__':
sys.exit(main() or 0)