Improve security of Juju deployed clusters
This commit is contained in:
@@ -303,9 +303,10 @@ def watch_for_changes(kube_api, kube_control, cni):
|
||||
@when('kubernetes-worker.snaps.installed', 'kube-api-endpoint.available',
|
||||
'tls_client.ca.saved', 'tls_client.client.certificate.saved',
|
||||
'tls_client.client.key.saved', 'tls_client.server.certificate.saved',
|
||||
'tls_client.server.key.saved', 'kube-control.dns.available',
|
||||
'tls_client.server.key.saved',
|
||||
'kube-control.dns.available', 'kube-control.auth.available',
|
||||
'cni.available', 'kubernetes-worker.restart-needed')
|
||||
def start_worker(kube_api, kube_control, cni):
|
||||
def start_worker(kube_api, kube_control, auth_control, cni):
|
||||
''' Start kubelet using the provided API and DNS info.'''
|
||||
servers = get_kube_api_servers(kube_api)
|
||||
# Note that the DNS server doesn't necessarily exist at this point. We know
|
||||
@@ -320,10 +321,13 @@ def start_worker(kube_api, kube_control, cni):
|
||||
hookenv.log('Waiting for cluster cidr.')
|
||||
return
|
||||
|
||||
creds = kube_control.get_auth_credentials()
|
||||
data_changed('kube-control.creds', creds)
|
||||
|
||||
# set --allow-privileged flag for kubelet
|
||||
set_privileged()
|
||||
|
||||
create_config(random.choice(servers))
|
||||
create_config(random.choice(servers), creds)
|
||||
configure_worker_services(servers, dns, cluster_cidr)
|
||||
set_state('kubernetes-worker.config.created')
|
||||
restart_unit_services()
|
||||
@@ -428,27 +432,25 @@ def arch():
|
||||
return architecture
|
||||
|
||||
|
||||
def create_config(server):
|
||||
def create_config(server, creds):
|
||||
'''Create a kubernetes configuration for the worker unit.'''
|
||||
# Get the options from the tls-client layer.
|
||||
layer_options = layer.options('tls-client')
|
||||
# Get all the paths to the tls information required for kubeconfig.
|
||||
ca = layer_options.get('ca_certificate_path')
|
||||
key = layer_options.get('client_key_path')
|
||||
cert = layer_options.get('client_certificate_path')
|
||||
|
||||
# Create kubernetes configuration in the default location for ubuntu.
|
||||
create_kubeconfig('/home/ubuntu/.kube/config', server, ca, key, cert,
|
||||
user='ubuntu')
|
||||
create_kubeconfig('/home/ubuntu/.kube/config', server, ca,
|
||||
token=creds['client_token'], user='ubuntu')
|
||||
# Make the config dir readable by the ubuntu users so juju scp works.
|
||||
cmd = ['chown', '-R', 'ubuntu:ubuntu', '/home/ubuntu/.kube']
|
||||
check_call(cmd)
|
||||
# Create kubernetes configuration in the default location for root.
|
||||
create_kubeconfig('/root/.kube/config', server, ca, key, cert,
|
||||
user='root')
|
||||
create_kubeconfig('/root/.kube/config', server, ca,
|
||||
token=creds['client_token'], user='root')
|
||||
# Create kubernetes configuration for kubelet, and kube-proxy services.
|
||||
create_kubeconfig(kubeconfig_path, server, ca, key, cert,
|
||||
user='kubelet')
|
||||
create_kubeconfig(kubeconfig_path, server, ca,
|
||||
token=creds['kubelet_token'], user='kubelet')
|
||||
|
||||
|
||||
def configure_worker_services(api_servers, dns, cluster_cidr):
|
||||
@@ -463,16 +465,15 @@ def configure_worker_services(api_servers, dns, cluster_cidr):
|
||||
kubelet_opts.add('require-kubeconfig', 'true')
|
||||
kubelet_opts.add('kubeconfig', kubeconfig_path)
|
||||
kubelet_opts.add('network-plugin', 'cni')
|
||||
kubelet_opts.add('logtostderr', 'true')
|
||||
kubelet_opts.add('v', '0')
|
||||
kubelet_opts.add('address', '0.0.0.0')
|
||||
kubelet_opts.add('port', '10250')
|
||||
kubelet_opts.add('cluster-dns', dns['sdn-ip'])
|
||||
kubelet_opts.add('cluster-domain', dns['domain'])
|
||||
kubelet_opts.add('anonymous-auth', 'false')
|
||||
kubelet_opts.add('client-ca-file', ca_cert_path)
|
||||
kubelet_opts.add('tls-cert-file', server_cert_path)
|
||||
kubelet_opts.add('tls-private-key-file', server_key_path)
|
||||
kubelet_opts.add('logtostderr', 'true')
|
||||
|
||||
kube_proxy_opts = FlagManager('kube-proxy')
|
||||
kube_proxy_opts.add('cluster-cidr', cluster_cidr)
|
||||
@@ -487,19 +488,40 @@ def configure_worker_services(api_servers, dns, cluster_cidr):
|
||||
check_call(cmd)
|
||||
|
||||
|
||||
def create_kubeconfig(kubeconfig, server, ca, key, certificate, user='ubuntu',
|
||||
context='juju-context', cluster='juju-cluster'):
|
||||
def create_kubeconfig(kubeconfig, server, ca, key=None, certificate=None,
|
||||
user='ubuntu', context='juju-context',
|
||||
cluster='juju-cluster', password=None, token=None):
|
||||
'''Create a configuration for Kubernetes based on path using the supplied
|
||||
arguments for values of the Kubernetes server, CA, key, certificate, user
|
||||
context and cluster.'''
|
||||
if not key and not certificate and not password and not token:
|
||||
raise ValueError('Missing authentication mechanism.')
|
||||
|
||||
# token and password are mutually exclusive. Error early if both are
|
||||
# present. The developer has requested an impossible situation.
|
||||
# see: kubectl config set-credentials --help
|
||||
if token and password:
|
||||
raise ValueError('Token and Password are mutually exclusive.')
|
||||
# Create the config file with the address of the master server.
|
||||
cmd = 'kubectl config --kubeconfig={0} set-cluster {1} ' \
|
||||
'--server={2} --certificate-authority={3} --embed-certs=true'
|
||||
check_call(split(cmd.format(kubeconfig, cluster, server, ca)))
|
||||
# Delete old users
|
||||
cmd = 'kubectl config --kubeconfig={0} unset users'
|
||||
check_call(split(cmd.format(kubeconfig)))
|
||||
# Create the credentials using the client flags.
|
||||
cmd = 'kubectl config --kubeconfig={0} set-credentials {1} ' \
|
||||
'--client-key={2} --client-certificate={3} --embed-certs=true'
|
||||
check_call(split(cmd.format(kubeconfig, user, key, certificate)))
|
||||
cmd = 'kubectl config --kubeconfig={0} ' \
|
||||
'set-credentials {1} '.format(kubeconfig, user)
|
||||
|
||||
if key and certificate:
|
||||
cmd = '{0} --client-key={1} --client-certificate={2} '\
|
||||
'--embed-certs=true'.format(cmd, key, certificate)
|
||||
if password:
|
||||
cmd = "{0} --username={1} --password={2}".format(cmd, user, password)
|
||||
# This is mutually exclusive from password. They will not work together.
|
||||
if token:
|
||||
cmd = "{0} --token={1}".format(cmd, token)
|
||||
check_call(split(cmd))
|
||||
# Create a default context with the cluster.
|
||||
cmd = 'kubectl config --kubeconfig={0} set-context {1} ' \
|
||||
'--cluster={2} --user={3}'
|
||||
@@ -761,6 +783,26 @@ def notify_master_gpu_not_enabled(kube_control):
|
||||
kube_control.set_gpu(False)
|
||||
|
||||
|
||||
@when('kube-control.connected')
|
||||
def request_kubelet_and_proxy_credentials(kube_control):
|
||||
""" Request kubelet node authorization with a well formed kubelet user.
|
||||
This also implies that we are requesting kube-proxy auth. """
|
||||
|
||||
# The kube-cotrol interface is created to support RBAC.
|
||||
# At this point we might as well do the right thing and return the hostname
|
||||
# even if it will only be used when we enable RBAC
|
||||
nodeuser = 'system:node:{}'.format(gethostname())
|
||||
kube_control.set_auth_request(nodeuser)
|
||||
|
||||
|
||||
@when('kube-control.auth.available')
|
||||
def catch_change_in_creds(kube_control):
|
||||
"""Request a service restart in case credential updates were detected."""
|
||||
creds = kube_control.get_auth_credentials()
|
||||
if data_changed('kube-control.creds', creds):
|
||||
set_state('kubernetes-worker.restart-needed')
|
||||
|
||||
|
||||
@when_not('kube-control.connected')
|
||||
def missing_kube_control():
|
||||
"""Inform the operator they need to add the kube-control relation.
|
||||
|
Reference in New Issue
Block a user