Commit 5d1130cf authored by Alain Takoudjou's avatar Alain Takoudjou

update certificate client request

parent 2fc808b8
...@@ -48,6 +48,7 @@ setup(name=name, ...@@ -48,6 +48,7 @@ setup(name=name,
'netifaces', # to fetch information about network devices 'netifaces', # to fetch information about network devices
'setuptools', # namespaces 'setuptools', # namespaces
'supervisor', # slapgrid uses supervisor to manage processes 'supervisor', # slapgrid uses supervisor to manage processes
'pyOpenSSL', # manage ssl certificates
'psutil>=2.0.0', 'psutil>=2.0.0',
'xml_marshaller>=0.9.3', # to unmarshall/marshall python objects to/from 'xml_marshaller>=0.9.3', # to unmarshall/marshall python objects to/from
# XML # XML
...@@ -56,7 +57,6 @@ setup(name=name, ...@@ -56,7 +57,6 @@ setup(name=name,
'cliff', 'cliff',
'requests>=2.4.3', 'requests>=2.4.3',
'six', 'six',
'caucase', # used to manage ca client request
'uritemplate', # used by hateoas navigator 'uritemplate', # used by hateoas navigator
] + additional_install_requires, ] + additional_install_requires,
extras_require={ extras_require={
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2010-2014 Vifib SARL and Contributors.
# All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; either version 2.1
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import errno
import os
import subprocess
import sqlite3
import re
from OpenSSL import crypto
from datetime import datetime, timedelta
def parse_certificate_from_html(html):
"""
Extract certificate from an HTML page received by SlapOS Master.
"""
regex = r"(-{5}BEGIN\sCERTIFICATE-{5}.*-{5}END\sCERTIFICATE-{5})"
result = re.search(regex, html, re.DOTALL)
if result:
return result.groups()[0]
return certificate
def generateCertificateRequest(self, key_string, cn,
country='', state='', locality='', email='', organization='',
organization_unit='', csr_file=None, digest="sha256"):
"""
Generate certificate Signature request.
Parameter `cn` is mandatory
"""
key = crypto.load_privatekey(crypto.FILETYPE_PEM, key_string)
req = crypto.X509Req()
subject = req.get_subject()
subject.CN = cn
if country:
subject.C = country
if state:
subject.ST = state
if locality:
subject.L = locality
if organization:
subject.O = organization
if organization_unit:
subject.OU = organization_unit
if email:
subject.emailAddress = email
req.set_pubkey(key)
req.add_extensions([
crypto.X509Extension("basicConstraints", False, "CA:FALSE"),
crypto.X509Extension("keyUsage", False,
"nonRepudiation, digitalSignature, keyEncipherment")
])
req.sign(key, digest)
csr = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
if csr_file is not None:
with open(csr_file, 'w') as req_file:
req_file.write(csr)
os.chmod(csr_file, 0640)
return csr
def generatePkey(size=2048):
key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, size)
return crypto.dump_privatekey(crypto.FILETYPE_PEM, key)
def generatePrivatekey(self, output_file, size=2048):
"""
Generate private key into `output_file` and return the pkey string
"""
try:
key_fd = os.open(output_file,
os.O_CREAT|os.O_WRONLY|os.O_EXCL|os.O_TRUNC,
0600)
except OSError, e:
if e.errno != errno.EEXIST:
raise
else:
pkey = generatePkey(size)
os.write(key_fd, pkey)
os.close(key_fd)
return pkey
...@@ -36,7 +36,10 @@ import requests ...@@ -36,7 +36,10 @@ import requests
from caucase.cli_flask import CertificateAuthorityRequest from caucase.cli_flask import CertificateAuthorityRequest
from slapos.cli.config import ClientConfigCommand from slapos.cli.config import ClientConfigCommand
from slapos.util import mkdir_p, parse_certificate_key_pair from slapos.util import mkdir_p
from slapos.certificate import (parse_certificate_from_html,
generateCertificateRequest,
generatePkey)
class ConfigureClientCommand(ClientConfigCommand): class ConfigureClientCommand(ClientConfigCommand):
...@@ -138,9 +141,9 @@ def do_configure_client(logger, master_url_web, token, config_path, master_url): ...@@ -138,9 +141,9 @@ def do_configure_client(logger, master_url_web, token, config_path, master_url):
ca_url='') ca_url='')
logger.debug('Generating key to %s', key_path) logger.debug('Generating key to %s', key_path)
ca_client.generatePrivatekey(key_path, size=2048) key_string =generatePrivatekey(key_path, size=2048)
csr_string = ca_client.generateCertificateRequest( csr_string = generateCertificateRequest(
key_path, key_string,
cn=str(uuid.uuid4())) cn=str(uuid.uuid4()))
# retrieve a template for the configuration file # retrieve a template for the configuration file
......
...@@ -37,9 +37,10 @@ import pkg_resources ...@@ -37,9 +37,10 @@ import pkg_resources
import requests import requests
import uuid import uuid
from caucase.cli_flask import CertificateAuthorityRequest
from slapos.cli.command import Command, must_be_root from slapos.cli.command import Command, must_be_root
from slapos.util import parse_certificate_key_pair from slapos.certificate import (parse_certificate_from_html,
generateCertificateRequest,
generatePkey)
class RegisterCommand(Command): class RegisterCommand(Command):
...@@ -183,8 +184,10 @@ def sign_certificate(logger, master_url_web, node_name, csr_string, ...@@ -183,8 +184,10 @@ def sign_certificate(logger, master_url_web, node_name, csr_string,
else: else:
req.raise_for_status() req.raise_for_status()
certificate = parse_certificate_from_html(req.text)
return (get_computer_name(req.text), parse_certificate_from_html(req.text)) if certificate is None:
raise Exception("Computer Certificate was not returned by the Master")
return (get_computer_name(req.text), certificate)
def get_computer_name(text_string): def get_computer_name(text_string):
...@@ -247,12 +250,16 @@ def slapconfig(conf): ...@@ -247,12 +250,16 @@ def slapconfig(conf):
if not dry_run: if not dry_run:
os.mkdir(user_certificate_repository_path, 0o711) os.mkdir(user_certificate_repository_path, 0o711)
conf.logger.info('Copying to %r, and setting minimum privileges', conf.certificate_path) key_file = os.path.join(user_certificate_repository_path, 'key')
cert_file = os.path.join(user_certificate_repository_path, 'certificate')
for src, dst in [(conf.key, key_file), (conf.certificate, cert_file)]:
conf.logger.info('Copying to %r, and setting minimum privileges', dst)
if not dry_run: if not dry_run:
with open(conf.certificate_path, 'w') as destination: with open(dst, 'w') as destination:
destination.write(''.join(src)) destination.write(''.join(src))
os.chmod(conf.certificate_path, 0o600) os.chmod(dst, 0o600)
os.chown(conf.certificate_path, 0, 0) os.chown(dst, 0, 0)
certificate_repository_path = os.path.join(slap_conf_dir, 'ssl', 'partition_pki') certificate_repository_path = os.path.join(slap_conf_dir, 'ssl', 'partition_pki')
if not os.path.exists(certificate_repository_path): if not os.path.exists(certificate_repository_path):
...@@ -299,6 +306,7 @@ class RegisterConfig(object): ...@@ -299,6 +306,7 @@ class RegisterConfig(object):
self.logger = logger self.logger = logger
self.computer_id = None self.computer_id = None
self.certificate = None self.certificate = None
self.dry_run = False
def setConfig(self, options): def setConfig(self, options):
""" """
...@@ -308,11 +316,12 @@ class RegisterConfig(object): ...@@ -308,11 +316,12 @@ class RegisterConfig(object):
for option, value in options.__dict__.items(): for option, value in options.__dict__.items():
setattr(self, option, value) setattr(self, option, value)
def COMPConfig(self, slapos_configuration): def COMPConfig(self, slapos_configuration, key):
ssl_path = os.path.join(slapos_configuration, 'ssl') ssl_path = os.path.join(slapos_configuration, 'ssl')
self.slapos_configuration = slapos_configuration self.slapos_configuration = slapos_configuration
self.certificate_path = os.path.join(ssl_path, 'certificate') self.certificate_path = os.path.join(ssl_path, 'certificate')
self.key_path = os.path.join(ssl_path, 'key') self.key_path = os.path.join(ssl_path, 'key')
self.key = key
def displayUserConfig(self): def displayUserConfig(self):
self.logger.debug('Computer Name: %s', self.node_name) self.logger.debug('Computer Name: %s', self.node_name)
...@@ -339,20 +348,10 @@ def do_register(conf): ...@@ -339,20 +348,10 @@ def do_register(conf):
"""Register new computer on SlapOS Master and generate slapos.cfg""" """Register new computer on SlapOS Master and generate slapos.cfg"""
# Getting configuration parameters # Getting configuration parameters
conf.COMPConfig(slapos_configuration='/etc/opt/slapos/') conf.COMPConfig(slapos_configuration='/etc/opt/slapos/',
key=generatePkey(size=2048))
# create certificate authority client
ca_client = CertificateAuthorityRequest(
conf.key_path,
conf.certificate_path,
ca_cert_path,
ca_url='')
conf.logger.info('Generating private key to %s', conf.key_path) csr_string = generateCertificateRequest(conf.key, cn=str(uuid.uuid4()))
ca_client.generatePrivatekey(conf.key_path, size=2048)
csr_string = ca_client.generateCertificateRequest(
conf.key_path,
cn=str(uuid.uuid4()))
if conf.login or conf.login_auth: if conf.login or conf.login_auth:
for login, password in gen_auth(conf): for login, password in gen_auth(conf):
...@@ -388,6 +387,6 @@ def do_register(conf): ...@@ -388,6 +387,6 @@ def do_register(conf):
# Prepare Slapos Configuration # Prepare Slapos Configuration
slapconfig(conf) slapconfig(conf)
conf.logger.info('Node has successfully been configured as %s.', COMP) conf.logger.info('Node has successfully been configured as %s.', computer_id)
conf.logger.info('Now please invoke slapos node boot on your site.') conf.logger.info('Now please invoke slapos node boot on your site.')
return 0 return 0
...@@ -38,6 +38,7 @@ import tarfile ...@@ -38,6 +38,7 @@ import tarfile
import tempfile import tempfile
import time import time
import xmlrpclib import xmlrpclib
import uuid
from supervisor import xmlrpc from supervisor import xmlrpc
...@@ -50,6 +51,7 @@ from slapos.grid.exception import (BuildoutFailedError, WrongPermissionError, ...@@ -50,6 +51,7 @@ 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
WATCHDOG_MARK = '-on-watch' WATCHDOG_MARK = '-on-watch'
...@@ -405,15 +407,18 @@ class Partition(object): ...@@ -405,15 +407,18 @@ class Partition(object):
required=bytes2human(required))) required=bytes2human(required)))
def _updateCertificate(self): def _updateCertificate(self):
key_string = generatePrivatekey(self.key_file)
csr_string = generateCertificateRequest(key_string, cn=str(uuid.uuid4()))
try: try:
partition_certificate = self.computer_partition.getCertificate() partition_certificate = self.computer_partition.getCertificate(
certificate_request=csr_string)
except NotFoundError: except NotFoundError:
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() uid, gid = self.getUserGroupId()
for name, path in [('certificate', self.cert_file), ('key', self.key_file)]: for name, path in [('certificate', self.cert_file)]:
new_content = partition_certificate[name] new_content = partition_certificate[name]
old_content = None old_content = None
if os.path.exists(path): if os.path.exists(path):
......
...@@ -372,9 +372,9 @@ class Computer(SlapDocument): ...@@ -372,9 +372,9 @@ class Computer(SlapDocument):
self._connection_helper.POST('revokeComputerCertificate', data={ self._connection_helper.POST('revokeComputerCertificate', data={
'computer_id': self._computer_id}) 'computer_id': self._computer_id})
def generateCertificate(self): def generateCertificate(self, certificate_request):
xml = self._connection_helper.POST('generateComputerCertificate', data={ xml = self._connection_helper.POST('generateComputerCertificate', data={
'computer_id': self._computer_id}) 'computer_id': self._computer_id, certificate_request=certificate_request})
return xml_marshaller.loads(xml) return xml_marshaller.loads(xml)
...@@ -641,11 +641,12 @@ class ComputerPartition(SlapRequester): ...@@ -641,11 +641,12 @@ class ComputerPartition(SlapRequester):
# XXX: this implementation has not been reviewed # XXX: this implementation has not been reviewed
self.usage = usage_log self.usage = usage_log
def getCertificate(self): def getCertificate(self, certificate_request=None):
xml = self._connection_helper.GET('getComputerPartitionCertificate', xml = self._connection_helper.GET('getComputerPartitionCertificate',
params={ params={
'computer_id': self._computer_id, 'computer_id': self._computer_id,
'computer_partition_id': self._partition_id, 'computer_partition_id': self._partition_id,
'certificate_request': certificate_request,
} }
) )
return xml_marshaller.loads(xml) return xml_marshaller.loads(xml)
......
...@@ -31,6 +31,7 @@ import errno ...@@ -31,6 +31,7 @@ import errno
import os import os
import subprocess import subprocess
import sqlite3 import sqlite3
import re
def mkdir_p(path, mode=0o700): def mkdir_p(path, mode=0o700):
...@@ -60,18 +61,6 @@ def chownDirectory(path, uid, gid): ...@@ -60,18 +61,6 @@ def chownDirectory(path, uid, gid):
]) ])
def parse_certificate_from_html(html):
"""
Extract certificate from an HTML page received by SlapOS Master.
"""
c_start = html.find("Certificate:")
c_end = html.find("</textarea>", c_start)
certificate = html[c_start:c_end]
return certificate
def string_to_boolean(string): def string_to_boolean(string):
""" """
Return True if the value of the "string" parameter can be parsed as True. Return True if the value of the "string" parameter can be parsed as True.
......
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