signature.py 6.82 KB
Newer Older
Rafael Monnerat's avatar
Rafael Monnerat committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2012-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 advised 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 General Public License
# as published by the Free Software Foundation; either version 3
# 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 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.
#
##############################################################################

30
from six.moves import configparser
Rafael Monnerat's avatar
Rafael Monnerat committed
31 32 33 34 35 36 37 38 39 40 41
import os
import time
import traceback
import tempfile
import datetime
import shutil
import hashlib
import base64
from random import choice
from string import ascii_lowercase

42
from slapos.libnetworkcache import NetworkcacheClient
Rafael Monnerat's avatar
Rafael Monnerat committed
43 44 45 46 47 48 49 50 51 52 53


def strategy(entry_list):
  """Get the latest entry. """
  timestamp = 0
  best_entry = None
  for entry in entry_list:
    if entry['timestamp'] > timestamp:
      best_entry = entry
      timestamp = entry['timestamp']

54
  return best_entry
Rafael Monnerat's avatar
Rafael Monnerat committed
55 56 57 58 59 60

class Signature:

  def __init__(self, config, logger=None):
    self.config = config
    self.logger = logger
61 62 63 64 65 66 67
    self.shacache = NetworkcacheClient(open(self.config.slapos_configuration, 'r'))
    network_cache_info = configparser.RawConfigParser()
    network_cache_info.read(self.config.slapos_configuration)
    if network_cache_info.has_section('shacache'):
      self.key = network_cache_info.get('shacache', 'key')
    else:
      self.key = "slapos-upgrade-testing-key"
Rafael Monnerat's avatar
Rafael Monnerat committed
68

69
  def log(self, message, *args):
Rafael Monnerat's avatar
Rafael Monnerat committed
70
    if self.logger is not None:
71 72 73
      self.logger.debug(message, *args)
    elif args:
      print(message % args)
Rafael Monnerat's avatar
Rafael Monnerat committed
74
    else:
75
      print(message)
Rafael Monnerat's avatar
Rafael Monnerat committed
76 77

  def get_file_hash(self, path):
78
    with open(path, 'rb') as f:
Rafael Monnerat's avatar
Rafael Monnerat committed
79 80 81 82 83 84
      h = hashlib.sha256()
      h.update(f.read())
      base = base64.b64encode(h.digest())
    return base

  def save_file_hash(self, path, destination):
85
    base = self.get_file_hash(path)
86
    with open(destination, "wb") as f:
Rafael Monnerat's avatar
Rafael Monnerat committed
87 88
      f.write(base)

89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
  def _download_once(self, path, wanted_metadata_dict={},
                 required_key_list=[], is_sha256file=False):

    if is_sha256file:
      key = self.key + "-sha256-content"
    else:
      key = self.key

    self.log('Downloading %s...', key)
    result = self.shacache.select(key, wanted_metadata_dict, required_key_list)
    entry = None
    result = list(result)
    if result:
      entry = strategy(result)
      if not entry: # XXX: this should be the choice of 'strategy' function
        self.log("Can't find best entry matching strategy, selecting "
            "random one between acceptable ones.")
        entry = result[0]
    if not entry:
      self.log('No entry matching key %s', key)
    else:
      # XXX check if nc filters signature_certificate_list!
      # Creates a file with content to desired path.
      f = open(path, 'w+b')
      fd_download = self.shacache.download(entry['sha512'])
      try:
        shutil.copyfileobj(fd_download, f)
        # XXX method should check MD5.
        return entry
      finally:
        f.close()
        fd_download.close()
    return False

Rafael Monnerat's avatar
Rafael Monnerat committed
123 124 125 126
  def _download(self, path):
    """
    Download a tar of the repository from cache, and untar it.
    """
127 128 129 130 131
    try:
      download_metadata_dict = self._download_once(path=path,
              required_key_list=['timestamp'])
    except:
      return False
Rafael Monnerat's avatar
Rafael Monnerat committed
132
    if download_metadata_dict:
133
      self.log('File downloaded in %s', path)
Rafael Monnerat's avatar
Rafael Monnerat committed
134
      current_sha256 = self.get_file_hash(path)
Thomas Gambier's avatar
Thomas Gambier committed
135 136
      with tempfile.NamedTemporaryFile() as f_256:
        sha256path = f_256.name
137 138
        try:
          sha256sum_present = self._download_once(path=sha256path, required_key_list=['timestamp'], is_sha256file=True)
Thomas Gambier's avatar
Thomas Gambier committed
139
          self.log('sha 256 downloaded in %s', sha256path)
140 141
        except:
          sha256sum_present = False
Thomas Gambier's avatar
Thomas Gambier committed
142

143 144
        if sha256sum_present:
          expected_sha256 = f_256.read()
Thomas Gambier's avatar
Thomas Gambier committed
145 146 147 148 149
          if current_sha256 == expected_sha256:
            return True
          else:
            raise ValueError("%s != %s" % (current_sha256, expected_sha256))

Rafael Monnerat's avatar
Rafael Monnerat committed
150 151 152 153 154
  def download(self):
    """
    Get status information and return its path
    """
    info, path = tempfile.mkstemp()
Thomas Gambier's avatar
Thomas Gambier committed
155 156 157 158 159 160 161
    if self._download(path):
      try:
        shutil.move(path, self.config.destination)
      except Exception as e:
        self.log(e)
        self.log('Fail to move %s to %s, maybe permission problem?', path, self.config.destination)
        os.remove(path)
Rafael Monnerat's avatar
Rafael Monnerat committed
162
    else:
Thomas Gambier's avatar
Thomas Gambier committed
163
      os.remove(path)
Rafael Monnerat's avatar
Rafael Monnerat committed
164 165 166 167 168 169 170 171
      raise ValueError("No result from shacache")

  def _upload(self, path):
    """
    Creates uploads repository to cache.
    """
    sha256path = path + ".sha256"
    self.save_file_hash(path, sha256path)
172

Rafael Monnerat's avatar
Rafael Monnerat committed
173 174 175 176
    metadata_dict = {
      # XXX: we set date from client side. It can be potentially dangerous
      # as it can be badly configured.
      'timestamp': time.time(),
177 178 179 180
      'token': ''.join([choice(ascii_lowercase) for _ in range(128)]),
      # backward compatibility
      'file': 'notused',
      'urlmd5': 'notused'
Rafael Monnerat's avatar
Rafael Monnerat committed
181 182
    }
    try:
183
      sha512sum = self.shacache.upload(open(path, 'rb'), self.key, **metadata_dict)
184 185 186
      if sha512sum:
        self.log(
          'Uploaded %s to cache (using %s key) with SHA512 %s.', path,
187 188 189 190 191
          self.key, sha512sum)
        sha512sum_path = self.shacache.upload(
            open(sha256path, 'rb'),
            self.key + "-sha256-content",
            **metadata_dict)
192 193 194
        if sha512sum_path:
          self.log(
            'Uploaded %s to cache (using %s key) with SHA512 %s.', sha256path,
195
            self.key, sha512sum_path)
Rafael Monnerat's avatar
Rafael Monnerat committed
196 197 198
        else:
          self.log('Fail to upload sha256file file to cache.')
      else:
199
        self.log('Fail to upload %s to cache.', path)
Rafael Monnerat's avatar
Rafael Monnerat committed
200
    except Exception:
201
      self.log('Unable to upload to cache:\n%s.', traceback.format_exc())
Rafael Monnerat's avatar
Rafael Monnerat committed
202 203 204 205
      return

  def upload(self):
    self._upload(self.config.file)
206

Rafael Monnerat's avatar
Rafael Monnerat committed
207 208 209 210 211 212 213 214
# Class containing all parameters needed for configuration
class Config:
  def __init__(self, option_dict=None):
    if option_dict is not None:
      # Set options parameters
      for option, value in option_dict.__dict__.items():
        setattr(self, option, value)