Пример #1
0
def validate_jwt(jwt):
    """ Check the incoming JWT and verify that it has all of the fields that
    we require.

    :param jwt a JSON Web Token as a string
    :return the JWT string
    :raise ValidationError if validation fails
    """
    if not jwt:
        return None

    # Validate header.
    header = brkt_jwt.get_header(jwt)
    expected_fields = ['typ', 'alg', 'kid']
    missing_fields = [f for f in expected_fields if f not in header]
    if missing_fields:
        raise ValidationError(
            'Missing fields in token header: %s.  Use the %s command '
            'to generate a valid token.' %
            (','.join(missing_fields), brkt_jwt.SUBCOMMAND_NAME))

    # Validate payload.
    payload = brkt_jwt.get_payload(jwt)
    if not payload.get('jti'):
        raise ValidationError(
            'Token payload does not contain the jti field.  Use the %s '
            'command to generate a valid token.' % brkt_jwt.SUBCOMMAND_NAME)

    return jwt
Пример #2
0
def parse_timestamp(ts_string):
    """ Return a datetime that represents the given timestamp
    string.  The string can be a Unix timestamp in seconds or an ISO 8601
    timestamp.

    :raise ValidationError if ts_string is malformed
    """
    now = int(time.time())

    # Parse integer timestamp.
    m = re.match('\d+(\.\d+)?$', ts_string)
    if m:
        t = float(ts_string)
        if t < now:
            raise ValidationError(
                '%s is earlier than the current timestamp (%s).' %
                (ts_string, now))
        return _timestamp_to_datetime(t)

    # Parse ISO 8601 timestamp.
    dt_now = _timestamp_to_datetime(now)
    try:
        dt = iso8601.parse_date(ts_string)
    except iso8601.ParseError:
        raise ValidationError(
            'Timestamp "%s" must either be a Unix timestamp or in iso8601 '
            'format (2016-05-10T19:15:36Z).' % ts_string)
    if dt < dt_now:
        raise ValidationError(
            '%s is earlier than the current timestamp (%s).' %
            (ts_string, dt_now))
    return dt
Пример #3
0
def _validate_guest_encrypted_ami(aws_svc, ami_id, encryptor_ami_id):
    """ Validate that this image was encrypted by Bracket by checking
        tags.

    :raise: ValidationError if validation fails
    :return: the Image object
    """
    ami = _validate_ami(aws_svc, ami_id)

    # Is this encrypted by Bracket?
    tags = ami.tags
    expected_tags = (TAG_ENCRYPTOR, TAG_ENCRYPTOR_SESSION_ID,
                     TAG_ENCRYPTOR_AMI)
    missing_tags = set(expected_tags) - set(tags.keys())
    if missing_tags:
        raise ValidationError('%s is missing tags: %s' %
                              (ami.id, ', '.join(missing_tags)))

    # See if this image was already encrypted by the given encryptor AMI.
    original_encryptor_id = tags.get(TAG_ENCRYPTOR_AMI)
    if original_encryptor_id == encryptor_ami_id:
        msg = '%s was already encrypted with Bracket Encryptor %s' % (
            ami.id, encryptor_ami_id)
        raise ValidationError(msg)

    return ami
Пример #4
0
def read_private_key(pem_path):
    """ Read a private key from a PEM file.

    :return a brkt_cli.crypto.Crypto object
    :raise ValidationError if the file cannot be read or is malformed, or
    if the PEM does not represent a 384-bit ECDSA private key.
    """
    key_format_err = (
        'Signing key must be a 384-bit ECDSA private key (NIST P-384)')

    try:
        with open(pem_path) as f:
            pem = f.read()
        if not brkt_cli.crypto.is_private_key(pem):
            raise ValidationError(key_format_err)

        password = None
        if brkt_cli.crypto.is_encrypted_key(pem):
            password = getpass.getpass('Encrypted private key password: '******'Unable to load signing key from %s', pem_path)
        raise ValidationError('Unable to load signing key: %s' % e)

    log.debug('crypto.curve=%s', crypto.curve)
    if crypto.curve != brkt_cli.crypto.SECP384R1:
        raise ValidationError(key_format_err)
    return crypto
Пример #5
0
    def _parse_endpoint(endpoint):
        host_port_pattern = r'([^:]+):(\d+)$'
        m = re.match(host_port_pattern, endpoint)
        if not m:
            raise ValidationError(error_msg)
        host = m.group(1)
        port = int(m.group(2))

        if not util.validate_dns_name_ip_address(host):
            raise ValidationError('Invalid hostname: ' + host)
        return host, port
Пример #6
0
def check_args(values, gce_svc):
    if not gce_svc.network_exists(values.network):
        raise ValidationError("Network provided does not exist")
    if values.encryptor_image:
        if values.bucket != 'prod':
            raise ValidationError(
                "Please provided either an encryptor image or an image bucket")
    if not values.token:
        raise ValidationError('Must provide a token')

    brkt_env = brkt_cli.brkt_env_from_values(values)
    brkt_cli.check_jwt_auth(brkt_env, values.token)
Пример #7
0
def make_instance_config(values=None,
                         brkt_env=None,
                         mode=INSTANCE_CREATOR_MODE):
    log.debug('Creating instance config with %s', brkt_env)

    brkt_config = {}
    if not values:
        return InstanceConfig(brkt_config, mode)

    if brkt_env:
        add_brkt_env_to_brkt_config(brkt_env, brkt_config)

    if values.token:
        brkt_config['identity_token'] = values.token

    if values.ntp_servers:
        brkt_config['ntp_servers'] = values.ntp_servers

    if mode in (INSTANCE_CREATOR_MODE, INSTANCE_UPDATER_MODE):
        brkt_config['status_port'] = (values.status_port or
                                      encryptor_service.ENCRYPTOR_STATUS_PORT)

    ic = InstanceConfig(brkt_config, mode)

    # Now handle the args that cause files to be added to brkt-files
    proxy_config = get_proxy_config(values)
    if proxy_config:
        ic.add_brkt_file('proxy.yaml', proxy_config)

    if 'ca_cert' in values and values.ca_cert:
        if mode != INSTANCE_CREATOR_MODE:
            raise ValidationError(
                'Can only specify ca-cert for instance in Creator mode')
        if not values.brkt_env:
            raise ValidationError(
                'Must specify brkt-env when specifying ca-cert.')
        try:
            with open(values.ca_cert, 'r') as f:
                ca_cert_data = f.read()
        except IOError as e:
            raise ValidationError(e)
        try:
            x509.load_pem_x509_certificate(ca_cert_data, default_backend())
        except Exception as e:
            raise ValidationError('Error validating CA cert: %s' % e)

        domain = get_domain_from_brkt_env(brkt_env)

        ca_cert_filename = 'ca_cert.pem.' + domain
        ic.add_brkt_file(ca_cert_filename, ca_cert_data)

    return ic
Пример #8
0
def _validate_ami(aws_svc, ami_id):
    """
    :return the Image object
    :raise ValidationError if the image doesn't exist
    """
    try:
        image = aws_svc.get_image(ami_id)
    except EC2ResponseError, e:
        if e.error_code.startswith('InvalidAMIID'):
            raise ValidationError('Could not find ' + ami_id + ': ' +
                                  e.error_code)
        else:
            raise ValidationError(e.error_message)
Пример #9
0
def validate_tag_key(key):
    """ Verify that the key is a valid EC2 tag key.

    :return: the key if it's valid
    :raises ValidationError if the key is invalid
    """
    if len(key) > 127:
        raise ValidationError(
            'Tag key cannot be longer than 127 characters'
        )
    if key.startswith('aws:'):
        raise ValidationError(
            'Tag key cannot start with "aws:"'
        )
    return key
Пример #10
0
def validate_tag_value(value):
    """ Verify that the value is a valid EC2 tag value.

    :return: the value if it's valid
    :raises ValidationError if the value is invalid
    """
    if len(value) > 255:
        raise ValidationError(
            'Tag value cannot be longer than 255 characters'
        )
    if value.startswith('aws:'):
        raise ValidationError(
            'Tag value cannot start with "aws:"'
        )
    return value
Пример #11
0
def validate_image_name(name):
    """ Verify that the name is a valid GCE image name. Return the name
        if it is valid.

    : raises ValidationError if name is invalid
    """
    if not (name and len(name) <= 63):
        raise ValidationError('Image name may be at most 63 characters')

    m = re.match(r'[a-z0-9\-]*[a-z0-9]$', name)
    if not m:
        raise ValidationError(
            "GCE image must be lower case letters, numbers and hyphens "
            "and must end with a lower case letter or a number")
    return name
Пример #12
0
def check_jwt_auth(brkt_env, jwt):
    """ Authenticate with Yeti using the given JWT and make sure that the
    associated public key is registered with the account.

    :param brkt_env a BracketEnvironment object
    :param jwt a JWT string
    :raise ValidationError if the token fails auth or the public key is not
    registered with the given account
    """
    validate_jwt(jwt)

    uri = 'https://%s:%d/api/v1/customer/self' % (brkt_env.public_api_host,
                                                  brkt_env.public_api_port)
    log.debug('Validating token against %s', uri)
    request = urllib2.Request(uri,
                              headers={'Authorization': 'Bearer %s' % jwt})
    try:
        response = urllib2.urlopen(request, timeout=10.0)
        log.debug('Server returned %d', response.getcode())
    except urllib2.HTTPError as e:
        if e.code == 401:
            raise ValidationError('Unauthorized token.')
        else:
            # Unexpected server response.  Log a warning and continue, so
            # that we don't unnecessarily interrupt the encryption process.
            log.debug('Server response: %s', e.msg)
            log.warn('Unable to validate token.  Server returned error %d.  '
                     'Use --no-validate to disable validation.' % e.code)
    except IOError:
        if log.isEnabledFor(logging.DEBUG):
            log.exception('')
        log.warn(
            'Unable to validate token against %s.  Use --no-validate to '
            'disable validation.', uri)
Пример #13
0
def _get_encryptor_ami(region_name, pv=False):
    """ Read the list of AMIs from the AMI endpoint and return the AMI ID
    for the given region.

    :raise ValidationError if the region is not supported
    :raise BracketError if the list of AMIs cannot be read
    """
    if pv:
        bucket_url = PV_ENCRYPTOR_AMIS_URL
    else:
        bucket_url = ENCRYPTOR_AMIS_URL

    log.debug('Getting encryptor AMI list from %s', bucket_url)
    r = urllib2.urlopen(bucket_url)
    if r.getcode() not in (200, 201):
        raise BracketError('Getting %s gave response: %s' %
                           (bucket_url, r.text))
    resp_json = json.loads(r.read())
    ami = resp_json.get(region_name)

    if not ami:
        regions = resp_json.keys()
        raise ValidationError('Encryptor AMI is only available in %s' %
                              ', '.join(regions))
    return ami
Пример #14
0
def parse_brkt_env(brkt_env_string):
    """ Parse the --brkt-env value.  The value is in the following format:

    api_host:port,hsmproxy_host:port

    :return: a BracketEnvironment object
    :raise: ValidationError if brkt_env is malformed
    """
    error_msg = ('--brkt-env value must be in the following format: '
                 '<api-host>:<api-port>,<hsm-proxy-host>:<hsm-proxy-port>')
    endpoints = brkt_env_string.split(',')
    if len(endpoints) != 2:
        raise ValidationError(error_msg)

    def _parse_endpoint(endpoint):
        host_port_pattern = r'([^:]+):(\d+)$'
        m = re.match(host_port_pattern, endpoint)
        if not m:
            raise ValidationError(error_msg)
        host = m.group(1)
        port = int(m.group(2))

        if not util.validate_dns_name_ip_address(host):
            raise ValidationError('Invalid hostname: ' + host)
        return host, port

    be = BracketEnvironment()
    (be.api_host, be.api_port) = _parse_endpoint(endpoints[0])
    # set public api host based on the same prefix assumption
    # service-domain makes. Hopefully we'll remove brkt-env
    # soon and we can get rid of it
    be.public_api_host = be.api_host.replace('yetiapi', 'api')
    (be.hsmproxy_host, be.hsmproxy_port) = _parse_endpoint(endpoints[1])
    return be
Пример #15
0
 def _unset_option(self, opt):
     """Unset the specified option"""
     try:
         self.parsed_config.unset_option(opt)
     except InvalidOptionError:
         raise ValidationError('Error: unknown option "%s".' % (opt,))
     self._write_config()
     return 0
Пример #16
0
def _write_file(path, content):
    try:
        with open(path, 'w') as f:
            f.write(content)
    except IOError as e:
        if log.isEnabledFor(logging.DEBUG):
            log.exception('Unable to write to %s', path)
        raise ValidationError('Unable to write to %s: %s' % (path, e))
Пример #17
0
 def _get_option(self, opt):
     try:
         val = self.parsed_config.get_option(opt)
     except InvalidOptionError:
         raise ValidationError('Error: unknown option "%s".' % (opt,))
     if val:
         self.stdout.write("%s\n" % (val,))
     return 0
Пример #18
0
def validate_ntp_servers(ntp_servers):
    if ntp_servers is None:
        return
    for server in ntp_servers:
        if not validate_dns_name_ip_address(server):
            raise ValidationError(
                'Invalid ntp-server %s specified. '
                'Should be either a host name or an IPv4 address' % server)
Пример #19
0
def command_diag(values):
    nonce = util.make_nonce()

    aws_svc = aws_service.AWSService(
        nonce,
        retry_timeout=values.retry_timeout,
        retry_initial_sleep_seconds=values.retry_initial_sleep_seconds)
    log.debug('Retry timeout=%.02f, initial sleep seconds=%.02f',
              aws_svc.retry_timeout, aws_svc.retry_initial_sleep_seconds)

    if values.snapshot_id and values.instance_id:
        raise ValidationError("Only one of --instance-id or --snapshot-id "
                              "may be specified")

    if not values.snapshot_id and not values.instance_id:
        raise ValidationError("--instance-id or --snapshot-id "
                              "must be specified")

    if values.validate:
        # Validate the region before connecting.
        region_names = [r.name for r in aws_svc.get_regions()]
        if values.region not in region_names:
            raise ValidationError(
                'Invalid region %s.  Supported regions: %s.' %
                (values.region, ', '.join(region_names)))

    aws_svc.connect(values.region, key_name=values.key_name)
    default_tags = {}
    default_tags.update(brkt_cli.parse_tags(values.tags))
    aws_svc.default_tags = default_tags

    if values.validate:
        if values.key_name:
            aws_svc.get_key_pair(values.key_name)
        if values.instance_id:
            _validate_log_instance(aws_svc, values.instance_id)
        _validate_subnet_and_security_groups(aws_svc, values.subnet_id,
                                             values.security_group_ids)
    else:
        log.info('Skipping validation.')

    diag.diag(aws_svc,
              instance_id=values.instance_id,
              snapshot_id=values.snapshot_id,
              ssh_keypair=values.key_name)
    return 0
Пример #20
0
def validate_image_name(name):
    """ Verify that the name is a valid EC2 image name.  Return the name
        if it's valid.

    :raises ValidationError if the name is invalid
    """
    if not (name and 3 <= len(name) <= 128):
        raise ValidationError(
            'Image name must be between 3 and 128 characters long')

    m = re.match(r'[A-Za-z0-9()\[\] ./\-\'@_]+$', name)
    if not m:
        raise ValidationError(
            "Image name may only contain letters, numbers, spaces, "
            "and the following characters: ()[]./-'@_"
        )
    return name
Пример #21
0
def _validate_region(aws_svc, region_name):
    """ Check that the specified region is a valid AWS region.

    :raise ValidationError if the region is invalid
    """
    region_names = [r.name for r in aws_svc.get_regions()]
    if region_name not in region_names:
        raise ValidationError('%s does not exist.  AWS regions are %s' %
                              (region_name, ', '.join(region_names)))
Пример #22
0
def _validate_encryptor_ami(aws_svc, ami_id):
    """ Validate that the image exists and is a Bracket encryptor image.

    :raise ValidationError if validation fails
    """
    image = _validate_ami(aws_svc, ami_id)
    if 'brkt-avatar' not in image.name:
        raise ValidationError('%s (%s) is not a Bracket Encryptor image' %
                              (ami_id, image.name))
    return None
Пример #23
0
def get_domain_from_brkt_env(brkt_env):
    """Return the domain string from the api_host in the brkt_env. """

    api_host = brkt_env.api_host
    if not api_host:
        raise ValidationError('api_host endpoint not in brkt_env: %s' %
                              brkt_env)

    # Consider the domain to be everything after the first '.' in
    # the api_host.
    return api_host.split('.', 1)[1]
Пример #24
0
def get_header(jwt_string):
    """ Return all of the headers in the given JWT.

    :return the headers as a dictionary
    """
    try:
        return jwt.get_unverified_header(jwt_string)
    except jwt.InvalidTokenError as e:
        if log.isEnabledFor(logging.DEBUG):
            log.exception('')
        raise ValidationError('Unable to decode token: %s' % e)
Пример #25
0
def get_payload(jwt_string):
    """ Return the payload of the given JWT.

    :return the payload as a dictionary
    """
    try:
        return jwt.decode(jwt_string, verify=False)
    except jwt.InvalidTokenError as e:
        if log.isEnabledFor(logging.DEBUG):
            log.exception('')
        raise ValidationError('Unable to decode token: %s' % e)
Пример #26
0
def validate_images(gce_svc,
                    encrypted_image_name,
                    encryptor,
                    guest_image,
                    image_project=None):
    # check that image to be updated exists
    if not gce_svc.image_exists(guest_image, image_project):
        raise ValidationError('Guest image or image project invalid')

    # check that encryptor exists
    if encryptor and not gce_svc.image_exists(encryptor):
        raise ValidationError(
            'Encryptor image %s does not exist. Encryption failed.' %
            encryptor)

    # check that there is no existing image named encrypted_image_name
    if gce_svc.image_exists(encrypted_image_name):
        raise ValidationError(
            'An image already exists with name %s. Encryption Failed.' %
            encrypted_image_name)
Пример #27
0
def _parse_proxies(*proxy_host_ports):
    """ Parse proxies specified on the command line.

    :param proxy_host_ports: a list of strings in "host:port" format
    :return: a list of Proxy objects
    :raise: ValidationError if any of the items are malformed
    """
    proxies = []
    for s in proxy_host_ports:
        m = re.match(r'([^:]+):(\d+)$', s)
        if not m:
            raise ValidationError('%s is not in host:port format' % s)
        host = m.group(1)
        port = int(m.group(2))
        if not util.validate_dns_name_ip_address(host):
            raise ValidationError('%s is not a valid hostname' % host)
        proxy = Proxy(host, port)
        proxies.append(proxy)

    return proxies
Пример #28
0
def _base64_decode_json(base64_string):
    """ Decode the given base64 string, and return the parsed JSON as a
    dictionary.
    :raise ValidationError if either the base64 or JSON is malformed
    """
    try:
        json_string = util.urlsafe_b64decode(base64_string)
        return json.loads(json_string)
    except (TypeError, ValueError) as e:
        raise ValidationError('Unable to decode %s as JSON: %s' %
                              (base64_string, e))
Пример #29
0
def parse_name_value(name_value):
    """ Parse a string in NAME=VALUE format.

    :return: a tuple of name, value
    :raise: ValidationError if name_value is malformed
    """
    m = re.match(r'([^=]+)=(.+)', name_value)
    if not m:
        raise ValidationError('%s is not in the format NAME=VALUE' %
                              name_value)
    return m.group(1), m.group(2)
Пример #30
0
def _validate_guest_ami(aws_svc, ami_id):
    """ Validate that we are able to encrypt this image.

    :return: the Image object
    :raise: ValidationError if the AMI id is invalid
    """
    image = _validate_ami(aws_svc, ami_id)
    if TAG_ENCRYPTOR in image.tags:
        raise ValidationError('%s is already an encrypted image' % ami_id)

    # Amazon's API only returns 'windows' or nothing.  We're not currently
    # able to detect individual Linux distros.
    if image.platform == 'windows':
        raise ValidationError('Windows is not a supported platform')

    if image.root_device_type != 'ebs':
        raise ValidationError('%s does not use EBS storage.' % ami_id)
    if image.hypervisor != 'xen':
        raise ValidationError('%s uses hypervisor %s.  Only xen is supported' %
                              (ami_id, image.hypervisor))
    return image