Commit 691529b3 authored by Alain Takoudjou's avatar Alain Takoudjou

fixup for generate certificate. Node will not get certificate every time

parent 5d1130cf
...@@ -370,7 +370,8 @@ class SlapTool(BaseTool): ...@@ -370,7 +370,8 @@ class SlapTool(BaseTool):
if certificate_request is None: if certificate_request is None:
certificate_pem = software_instance.getCertificate() certificate_pem = software_instance.getCertificate()
else: else:
certificate_pem = software_instance.getCertificate() certificate_pem = software_instance.requestCertificate(
certificate_request)
certificate_dict = dict( certificate_dict = dict(
key='', key='',
certificate=certificate_pem certificate=certificate_pem
...@@ -768,15 +769,16 @@ class SlapTool(BaseTool): ...@@ -768,15 +769,16 @@ class SlapTool(BaseTool):
) )
result = { result = {
'certificate': self.REQUEST.get('computer_certificate').decode("UTF-8"), 'certificate': self.REQUEST.get('computer_certificate').decode("UTF-8"),
'key': '',
'url': self.REQUEST.get('computer_certificate_url').decode("UTF-8") 'url': self.REQUEST.get('computer_certificate_url').decode("UTF-8")
} }
return xml_marshaller.xml_marshaller.dumps(result) return xml_marshaller.xml_marshaller.dumps(result)
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'generateComputerCertificate') 'generateComputerCertificate')
def generateComputerCertificate(self, computer_id): def generateComputerCertificate(self, computer_id, certificate_request):
"""Fetches new computer certificate""" """Fetches new computer certificate"""
return self._generateComputerCertificate(computer_id) return self._generateComputerCertificate(computer_id, certificate_request)
@convertToREST @convertToREST
def _revokeComputerCertificate(self, computer_id): def _revokeComputerCertificate(self, computer_id):
......
...@@ -32,7 +32,7 @@ import os ...@@ -32,7 +32,7 @@ import os
import subprocess import subprocess
import sqlite3 import sqlite3
import re import re
from OpenSSL import crypto from OpenSSL import crypto, SSL
from datetime import datetime, timedelta from datetime import datetime, timedelta
...@@ -48,7 +48,7 @@ def parse_certificate_from_html(html): ...@@ -48,7 +48,7 @@ def parse_certificate_from_html(html):
return certificate return certificate
def generateCertificateRequest(self, key_string, cn, def generateCertificateRequest(key_string, cn,
country='', state='', locality='', email='', organization='', country='', state='', locality='', email='', organization='',
organization_unit='', csr_file=None, digest="sha256"): organization_unit='', csr_file=None, digest="sha256"):
""" """
...@@ -97,20 +97,36 @@ def generatePkey(size=2048): ...@@ -97,20 +97,36 @@ def generatePkey(size=2048):
key.generate_key(crypto.TYPE_RSA, size) key.generate_key(crypto.TYPE_RSA, size)
return crypto.dump_privatekey(crypto.FILETYPE_PEM, key) return crypto.dump_privatekey(crypto.FILETYPE_PEM, key)
def generatePrivatekey(self, output_file, size=2048): def generatePrivatekey(key_file, size=2048, uid=None, gid=None):
""" """
Generate private key into `output_file` and return the pkey string Generate private key into `key_file` and return the pkey string
""" """
try: try:
key_fd = os.open(output_file, key_fd = os.open(key_file,
os.O_CREAT|os.O_WRONLY|os.O_EXCL|os.O_TRUNC, os.O_CREAT|os.O_WRONLY|os.O_EXCL|os.O_TRUNC,
0600) 0600)
except OSError, e: except OSError, e:
if e.errno != errno.EEXIST: if e.errno != errno.EEXIST:
raise raise
# return existing certificate content
return open(key_file).read()
else: else:
pkey = generatePkey(size) pkey = generatePkey(size)
os.write(key_fd, pkey) os.write(key_fd, pkey)
os.close(key_fd) os.close(key_fd)
if uid and gid:
os.chown(key_file, uid, gid)
return pkey return pkey
def validateCertAndKey(cert_file, key_file):
with open(cert_file) as ct:
x509 = crypto.load_certificate(crypto.FILETYPE_PEM, ct.read())
with open(key_file) as kf:
key = crypto.load_privatekey(crypto.FILETYPE_PEM, kf.read())
ctx = SSL.Context(SSL.TLSv1_METHOD)
ctx.use_privatekey(key)
ctx.use_certificate(x509)
ctx.check_privatekey()
...@@ -51,7 +51,9 @@ from slapos.grid.exception import (BuildoutFailedError, WrongPermissionError, ...@@ -51,7 +51,9 @@ from slapos.grid.exception import (BuildoutFailedError, WrongPermissionError,
PathDoesNotExistError, DiskSpaceError) PathDoesNotExistError, DiskSpaceError)
from slapos.grid.networkcache import download_network_cached, upload_network_cached from slapos.grid.networkcache import download_network_cached, upload_network_cached
from slapos.human import bytes2human from slapos.human import bytes2human
from slapos.certificate import generateCertificateRequest, generatePrivatekey from slapos.certificate import (generateCertificateRequest,
generatePrivatekey,
validateCertAndKey)
WATCHDOG_MARK = '-on-watch' WATCHDOG_MARK = '-on-watch'
...@@ -407,7 +409,26 @@ class Partition(object): ...@@ -407,7 +409,26 @@ class Partition(object):
required=bytes2human(required))) required=bytes2human(required)))
def _updateCertificate(self): def _updateCertificate(self):
key_string = generatePrivatekey(self.key_file) """
Get or update instance certificate.
The Master can't decide to update the certificate, only the node can request
to renew it or generate a new one.
The node generate the private key and send
"""
try:
cert_fd = os.open(self.cert_file,
os.O_CREAT|os.O_WRONLY|os.O_EXCL|os.O_TRUNC,
0600)
except OSError, e:
if e.errno != errno.EEXIST:
raise
# the certificate exists, no need to download it
return
uid, gid = self.getUserGroupId()
key_string = generatePrivatekey(self.key_file, uid, gid)
csr_string = generateCertificateRequest(key_string, cn=str(uuid.uuid4())) csr_string = generateCertificateRequest(key_string, cn=str(uuid.uuid4()))
try: try:
partition_certificate = self.computer_partition.getCertificate( partition_certificate = self.computer_partition.getCertificate(
...@@ -416,23 +437,22 @@ class Partition(object): ...@@ -416,23 +437,22 @@ class Partition(object):
raise NotFoundError('Partition %s is not known by SlapOS Master.' % raise NotFoundError('Partition %s is not known by SlapOS Master.' %
self.partition_id) self.partition_id)
uid, gid = self.getUserGroupId() os.write(cert_fd, partition_certificate)
os.close(cert_fd)
os.chown(self.cert_file, uid, gid)
self.logger.info('Certificate file saved at %r' % self.cert_file)
# Check that certificate and key are OK
try:
validateCertAndKey(self.key_file, self.cert_file)
except crypto.Error:
# Invalid Certificate file
if os.path.exists(self.cert_file):
os.unlink(self.cert_file)
raise
# except SSL.Error
# Raise when certificate and key didn't match
for name, path in [('certificate', self.cert_file)]:
new_content = partition_certificate[name]
old_content = None
if os.path.exists(path):
old_content = open(path).read()
if old_content != new_content:
if old_content is None:
self.logger.info('Missing %s file. Creating %r' % (name, path))
else:
self.logger.info('Changed %s content. Updating %r' % (name, path))
with os.fdopen(os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o400), 'wb') as fout:
fout.write(new_content)
os.chown(path, uid, gid)
def getUserGroupId(self): def getUserGroupId(self):
"""Returns tuple of (uid, gid) of partition""" """Returns tuple of (uid, gid) of partition"""
......
...@@ -272,11 +272,14 @@ class IComputerPartition(IBuildoutController, IRequester): ...@@ -272,11 +272,14 @@ class IComputerPartition(IBuildoutController, IRequester):
log -- a text explaining why the method was called log -- a text explaining why the method was called
""" """
def getCertificate(): def getCertificate(certificate_request=None):
""" """
Returns a dictionnary containing the authentification certificates Returns a dictionnary containing the authentification certificates
associated to the computer partition. associated to the computer partition.
The dictionnary keys are:
certificate_request -- is a string containing the CSR in PEM format
The returned dictionnary keys are:
key -- value is a SSL key key -- value is a SSL key
certificate -- value is a SSL certificate certificate -- value is a SSL certificate
...@@ -406,13 +409,17 @@ class IComputer(Interface): ...@@ -406,13 +409,17 @@ class IComputer(Interface):
text -- message log of the status text -- message log of the status
""" """
def generateCertificate(): def generateCertificate(certificate_request):
""" """
Returns a dictionnary containing the new certificate files for Returns a dictionnary containing the new certificate files for
the computer. the computer.
certificate_request -- a string with CSR in PEM format
The dictionnary keys are: The dictionnary keys are:
key -- key file key -- key file
certificate -- certificate file certificate -- certificate file
url -- url that can be used to download the certificate
Raise ValueError is another certificate is already valid. Raise ValueError is another certificate is already valid.
""" """
......
...@@ -374,7 +374,8 @@ class Computer(SlapDocument): ...@@ -374,7 +374,8 @@ class Computer(SlapDocument):
def generateCertificate(self, certificate_request): def generateCertificate(self, certificate_request):
xml = self._connection_helper.POST('generateComputerCertificate', data={ xml = self._connection_helper.POST('generateComputerCertificate', data={
'computer_id': self._computer_id, certificate_request=certificate_request}) 'computer_id': self._computer_id,
'certificate_request': certificate_request})
return xml_marshaller.loads(xml) return xml_marshaller.loads(xml)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment