enforce license across registry blueprints

This commit is contained in:
Jimmy Zelinskie 2016-10-10 16:23:42 -04:00 committed by Joseph Schorr
parent 8fe29c5b89
commit 0c5400b7d1
8 changed files with 118 additions and 39 deletions

View file

@ -1,18 +1,22 @@
import yaml
import logging
import yaml
from util.license import LICENSE_FILENAME, LicenseError, decode_license
from util.config.provider.license import LICENSE_FILENAME, LicenseError, decode_license
logger = logging.getLogger(__name__)
class CannotWriteConfigException(Exception):
""" Exception raised when the config cannot be written. """
pass
class SetupIncompleteException(Exception):
""" Exception raised when attempting to verify config that has not yet been setup. """
pass
def import_yaml(config_obj, config_file):
with open(config_file) as f:
c = yaml.safe_load(f)
@ -33,6 +37,7 @@ def import_yaml(config_obj, config_file):
def get_yaml(config_obj):
return yaml.safe_dump(config_obj, encoding='utf-8', allow_unicode=True)
def export_yaml(config_obj, config_file):
try:
with open(config_file, 'w') as f:

View file

@ -1,20 +1,29 @@
import json
import logging
import multiprocessing
import time
from dateutil import parser
from ctypes import c_bool
from datetime import datetime, timedelta
from threading import Thread
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_public_key
from dateutil import parser
from flask import abort
import jwt
import json
logger = logging.getLogger(__name__)
TRIAL_GRACE_PERIOD = timedelta(7, 0) # 1 week
MONTHLY_GRACE_PERIOD = timedelta(30, 0) # 1 month
YEARLY_GRACE_PERIOD = timedelta(90, 0) # 3 months
LICENSE_PRODUCT_NAME = "quay-enterprise"
LICENSE_FILENAME = 'license'
class LicenseError(Exception):
""" Exception raised if the license could not be read, decoded or has expired. """
@ -115,9 +124,6 @@ class License(object):
return True
LICENSE_FILENAME = 'license'
_PROD_LICENSE_PUBLIC_KEY_DATA = """
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuCkRnkuqox3A0djgRnHR
@ -129,8 +135,6 @@ ScjObTKaSUOGen6aYFF5Bd6V/ucxHmcmJlycwNZOKGFpbhLU173/oBJ+okvDbJpN
qwIDAQAB
-----END PUBLIC KEY-----
"""
_PROD_LICENSE_PUBLIC_KEY = load_pem_public_key(_PROD_LICENSE_PUBLIC_KEY_DATA,
backend=default_backend())
@ -150,3 +154,58 @@ def decode_license(license_contents, public_key_instance=None):
raise LicenseDecodeError('Could not decode license found: %s' % ve.message)
return License(decoded)
LICENSE_VALIDATION_INTERVAL = 3600 # seconds
LICENSE_VALIDATION_EXPIRED_INTERVAL = 60 # seconds
class LicenseValidator(Thread):
"""
LicenseValidator is a thread that asynchronously reloads and validates license files.
This thread is meant to be run before registry gunicorn workers fork and uses shared memory as a
synchronization primitive.
"""
def __init__(self, license_path):
self._license_path = license_path
# multiprocessing.Value does not ensure consistent write-after-reads, but we don't need that.
self._license_is_expired = multiprocessing.Value(c_bool, True)
super(LicenseValidator, self).__init__()
@property
def expired(self):
return self._license_is_expired.value
def _check_expiration(self):
try:
with open(self._license_path) as f:
current_license = decode_license(f.read())
logger.debug('updating license expiration to %s', current_license.is_expired)
self._license_is_expired.value = current_license.is_expired
return current_license.is_expired
except (IOError, LicenseError):
logger.exception('failed to validate license')
self._license_is_expired.value = True
return True
def run(self):
logger.debug('Starting license validation thread')
while True:
expired = self._check_expiration()
sleep_time = LICENSE_VALIDATION_EXPIRED_INTERVAL if expired else LICENSE_VALIDATION_INTERVAL
logger.debug('waiting %d seconds before retrying to validate license', sleep_time)
time.sleep(sleep_time)
def enforce_license_before_request(license_validator, blueprint):
"""
Adds a pre-check to a Flask blueprint such that if the provided license_validator determines the
license has become invalid, the client will receive a HTTP 402 response.
"""
def _enforce_license():
if license_validator.expired:
abort(402)
blueprint.before_request(_enforce_license)