def __init__(self, base_url, opener, headers=None): self._metadata = None self.base_url = base_url self.xsrf_url = util.JoinURL(base_url, self.XSRF_PATH) if self.ESCROW_PATH is None: raise ValueError( 'ESCROW_PATH must be set by CauliflowerVestClient subclasses.') self.escrow_url = util.JoinURL(base_url, self.ESCROW_PATH) self.opener = opener self.headers = headers or {}
def __init__(self, base_url, opener, headers=None): self._metadata = None self.base_url = base_url self.xsrf_url = util.JoinURL(base_url, self.XSRF_PATH) if self.ESCROW_PATH is None: raise ValueError('ESCROW_PATH must be set by CauliflowerVestClient subclasses.') self.escrow_url = util.JoinURL(base_url, self.ESCROW_PATH) self.opener = opener self.headers = headers or {} self._ca_certs_file = settings.ROOT_CA_CERT_CHAIN_PEM_FILE_PATH
def UploadPassphrase(self, volume_uuid, passphrase): """Uploads a volume uuid/passphrase pair with metadata. Args: volume_uuid: str, UUID of an encrypted volume. passphrase: str, passphrase that can be used to unlock the volume. Raises: RequestError: there was an error uploading to the server. """ xsrf_token = self._FetchXsrfToken(base_settings.SET_PASSPHRASE_ACTION) # Ugh, urllib2 only does GET and POST?! class PutRequest(fancy_urllib.FancyRequest): def __init__(self, *args, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers']['Content-Type'] = 'application/octet-stream' fancy_urllib.FancyRequest.__init__(self, *args, **kwargs) self._method = 'PUT' def get_method(self): # pylint: disable=g-bad-name return 'PUT' if not self._metadata: self.GetAndValidateMetadata() self._metadata['xsrf-token'] = xsrf_token url = '%s?%s' % (util.JoinURL( self.escrow_url, volume_uuid), urllib.urlencode(self._metadata)) request = PutRequest(url, data=passphrase) request.set_ssl_info(ca_certs=self._ca_certs_file) self._RetryRequest(request, 'Uploading passphrase')
def RetrieveSecret(self, target_id): """Fetches and returns the passphrase. Args: target_id: str, Target ID to fetch the passphrase for. Returns: str: passphrase. Raises: RequestError: there was an error downloading the passphrase. NotFoundError: no passphrase was found for the given target_id. """ xsrf_token = self._FetchXsrfToken(base_settings.GET_PASSPHRASE_ACTION) url = '%s?%s' % (util.JoinURL(self.escrow_url, urllib.quote(target_id)), urllib.urlencode({'xsrf-token': xsrf_token})) request = fancy_urllib.FancyRequest(url) request.set_ssl_info(ca_certs=self._ca_certs_file) try: response = self.opener.open(request) except urllib2.URLError as e: # Parent of urllib2.HTTPError. if isinstance(e, urllib2.HTTPError): e.msg += ': ' + e.read() if e.code == httplib.NOT_FOUND: raise NotFoundError('Failed to retrieve passphrase. %s' % e) raise RequestError('Failed to retrieve passphrase. %s' % e) content = response.read() if not content.startswith(JSON_PREFIX): raise RequestError('Expected JSON prefix missing.') data = json.loads(content[len(JSON_PREFIX):]) return data[self.PASSPHRASE_KEY]
def IsKeyRotationNeeded(self, target_id, tag='default'): """Check whether a key rotation is required. Args: target_id: str, Target ID. tag: str, passphrase tag. Raises: RequestError: there was an error getting status from server. Returns: bool: True if a key rotation is required. """ url = '%s?%s' % (util.JoinURL( self.base_url, '/api/v1/rekey-required/', self.ESCROW_PATH, target_id), urllib.urlencode({'tag': tag})) request = fancy_urllib.FancyRequest(url) request.set_ssl_info(ca_certs=self._ca_certs_file) try: response = self.opener.open(request) except urllib2.URLError as e: # Parent of urllib2.HTTPError. if isinstance(e, urllib2.HTTPError): e.msg += ': ' + e.read() raise RequestError('Failed to get status. %s' % e) content = response.read() if not content.startswith(JSON_PREFIX): raise RequestError('Expected JSON prefix missing.') return json.loads(content[len(JSON_PREFIX):])
def RetrievePassphrase(self, volume_uuid): """Fetches and returns the FileVault passphrase. Args: volume_uuid: str, Volume UUID to fetch the keychain for. Returns: str: passphrase to unlock the keychain. Raises: DownloadError: there was an error downloading the keychain. """ request = fancy_urllib.FancyRequest( util.JoinURL(self.filevault_url, volume_uuid)) request.set_ssl_info(ca_certs=self._ca_certs_file) try: response = self.opener.open(request) except urllib2.HTTPError, e: raise DownloadError('Failed to retrieve passphrase. %s' % str(e))
def RetrieveSecret(self, volume_uuid): """Fetches and returns the passphrase. Args: volume_uuid: str, Volume UUID to fetch the passphrase for. Returns: str: passphrase to unlock an encrypted volume. Raises: RequestError: there was an error downloading the passphrase. """ xsrf_token = self._FetchXsrfToken(base_settings.GET_PASSPHRASE_ACTION) url = '%s?%s' % (util.JoinURL(self.escrow_url, volume_uuid), urllib.urlencode({'xsrf-token': xsrf_token})) request = fancy_urllib.FancyRequest(url) request.set_ssl_info(ca_certs=self._ca_certs_file) try: response = self.opener.open(request) except urllib2.HTTPError, e: raise RequestError('Failed to retrieve passphrase. %s' % str(e))
def IsKeyRotationNeeded(self, target_id, tag='default'): """Check whether a key rotation is required. Args: target_id: str, Target ID. tag: str, passphrase tag. Raises: RequestError: there was an error getting status from server. Returns: bool: True if a key rotation is required. """ url = '%s?%s' % (util.JoinURL( self.base_url, '/api/v1/rekey-required/', self.ESCROW_PATH, target_id), urllib.urlencode({'tag': tag})) request = fancy_urllib.FancyRequest(url) request.set_ssl_info(ca_certs=self._ca_certs_file) try: response = self.opener.open(request) except urllib2.HTTPError, e: raise RequestError('Failed to get status. %s' % str(e))
def VerifyEscrow(self, volume_uuid): """Verifies if a Volume UUID has a passphrase escrowed or not. Args: volume_uuid: str, Volume UUID to verify escrow for. Returns: Boolean. True if a passphrase is escrowed, False otherwise. Raises: RequestError: there was an error querying the server. """ request = fancy_urllib.FancyRequest( util.JoinURL(self.escrow_url, volume_uuid, '?only_verify_escrow=1')) request.set_ssl_info(ca_certs=self._ca_certs_file) try: self._RetryRequest(request, 'Verifying escrow') except urllib2.HTTPError, e: if e.code == 404: return False else: raise RequestError('Failed to verify escrow. HTTP %s' % e.code)
def UploadPassphrase(self, volume_uuid, passphrase): """Uploads a FileVault volume uuid/passphrase pair with metadata. Args: volume_uuid: str, UUID of FileVault encrypted volume. passphrase: str, passphrase that can be used to unlock the volume. Raises: UploadError: there was an error uploading to the server. """ # Ugh, urllib2 only does GET and POST?! class PutRequest(fancy_urllib.FancyRequest): def __init__(self, *args, **kwargs): fancy_urllib.FancyRequest.__init__(self, *args, **kwargs) self._method = 'PUT' def get_method(self): # pylint: disable-msg=C6409 return 'PUT' if not self._metadata: self.GetAndValidateMetadata() url = '%s?%s' % (util.JoinURL( self.filevault_url, volume_uuid), urllib.urlencode(self._metadata)) for try_num in range(self.MAX_TRIES): request = PutRequest(url, data=passphrase) request.set_ssl_info(ca_certs=self._ca_certs_file) try: self.opener.open(request) break except urllib2.HTTPError, e: if try_num == self.MAX_TRIES - 1: logging.exception( 'Uploading passphrase failed permanently.') raise UploadError( 'Uploading passphrase failed permanently: %s', str(e)) logging.warning( 'Uploading passphrase failed with HTTP %s. Retrying ...', e.code) time.sleep((try_num + 1) * self.TRY_DELAY_FACTOR)
def testJoinURLWithTrailingSlashOnLastURLPart(self): base_url = 'http://example.com' part1 = 'foo' part2 = 'bar/' out = util.JoinURL(base_url, part1, part2) self.assertEqual(out, 'http://example.com/foo/bar/')
def testJoinURLWithLeadingAndTrailingSlashOnInnerURLPart(self): base_url = 'http://example.com' part1 = '/foo/' part2 = '/bar' out = util.JoinURL(base_url, part1, part2) self.assertEqual(out, 'http://example.com/foo/bar')
def testJoinURL(self): base_url = 'http://example.com' part1 = 'foo' part2 = 'bar' out = util.JoinURL(base_url, part1, part2) self.assertEqual(out, 'http://example.com/foo/bar')