Skip to content
This repository has been archived by the owner on Aug 7, 2023. It is now read-only.

Commit

Permalink
Update bless to v0.3.0 (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
Eduardo authored Aug 2, 2018
1 parent 3e635f5 commit f245ae0
Show file tree
Hide file tree
Showing 2,465 changed files with 118,412 additions and 108,790 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.1
0.2.2
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed bless_lambda/bless_ca/__pycache__/six.cpython-35.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed bless_lambda/bless_ca/_cffi_backend.so
Binary file not shown.
54 changes: 27 additions & 27 deletions bless_lambda/bless_ca/asn1crypto-0.24.0.dist-info/RECORD
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,30 @@ asn1crypto-0.24.0.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0S
asn1crypto-0.24.0.dist-info/metadata.json,sha256=GWUZOttbqBGOXofIFkjNpPeItn-MlCQZLATLKFjoHL4,1177
asn1crypto-0.24.0.dist-info/top_level.txt,sha256=z8-jF_Q-jgzGox7T2XYian3-yeptLS2I7MjoJLBaq1Y,11
asn1crypto-0.24.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
asn1crypto/keys.pyc,,
asn1crypto/__init__.pyc,,
asn1crypto/pdf.pyc,,
asn1crypto/_int.pyc,,
asn1crypto/_inet.pyc,,
asn1crypto/_iri.pyc,,
asn1crypto/_ffi.pyc,,
asn1crypto/x509.pyc,,
asn1crypto/crl.pyc,,
asn1crypto/pem.pyc,,
asn1crypto/tsp.pyc,,
asn1crypto/_types.pyc,,
asn1crypto/parser.pyc,,
asn1crypto/_perf/_big_num_ctypes.pyc,,
asn1crypto/algos.pyc,,
asn1crypto/_elliptic_curve.pyc,,
asn1crypto/core.pyc,,
asn1crypto/version.pyc,,
asn1crypto/cms.pyc,,
asn1crypto/_perf/__init__.pyc,,
asn1crypto/pkcs12.pyc,,
asn1crypto/csr.pyc,,
asn1crypto/_ordereddict.pyc,,
asn1crypto/ocsp.pyc,,
asn1crypto/util.pyc,,
asn1crypto/_errors.pyc,,
asn1crypto/_teletex_codec.pyc,,
asn1crypto/__pycache__/_ordereddict.cpython-36.pyc,,
asn1crypto/__pycache__/_iri.cpython-36.pyc,,
asn1crypto/__pycache__/_types.cpython-36.pyc,,
asn1crypto/__pycache__/crl.cpython-36.pyc,,
asn1crypto/__pycache__/ocsp.cpython-36.pyc,,
asn1crypto/__pycache__/keys.cpython-36.pyc,,
asn1crypto/__pycache__/_ffi.cpython-36.pyc,,
asn1crypto/__pycache__/x509.cpython-36.pyc,,
asn1crypto/__pycache__/parser.cpython-36.pyc,,
asn1crypto/__pycache__/tsp.cpython-36.pyc,,
asn1crypto/__pycache__/algos.cpython-36.pyc,,
asn1crypto/__pycache__/_errors.cpython-36.pyc,,
asn1crypto/__pycache__/_elliptic_curve.cpython-36.pyc,,
asn1crypto/__pycache__/_teletex_codec.cpython-36.pyc,,
asn1crypto/__pycache__/pem.cpython-36.pyc,,
asn1crypto/__pycache__/core.cpython-36.pyc,,
asn1crypto/__pycache__/pdf.cpython-36.pyc,,
asn1crypto/__pycache__/csr.cpython-36.pyc,,
asn1crypto/__pycache__/_int.cpython-36.pyc,,
asn1crypto/__pycache__/_inet.cpython-36.pyc,,
asn1crypto/__pycache__/__init__.cpython-36.pyc,,
asn1crypto/__pycache__/pkcs12.cpython-36.pyc,,
asn1crypto/__pycache__/cms.cpython-36.pyc,,
asn1crypto/__pycache__/util.cpython-36.pyc,,
asn1crypto/__pycache__/version.cpython-36.pyc,,
asn1crypto/_perf/__pycache__/_big_num_ctypes.cpython-36.pyc,,
asn1crypto/_perf/__pycache__/__init__.cpython-36.pyc,,
Binary file removed bless_lambda/bless_ca/asn1crypto/__init__.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/_elliptic_curve.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/_errors.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/_ffi.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/_inet.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/_int.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/_iri.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/_ordereddict.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/_perf/__init__.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/_teletex_codec.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/_types.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/algos.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/cms.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/core.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/crl.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/csr.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/keys.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/ocsp.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/parser.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/pdf.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/pem.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/pkcs12.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/tsp.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/util.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/version.pyc
Binary file not shown.
Binary file removed bless_lambda/bless_ca/asn1crypto/x509.pyc
Binary file not shown.
4 changes: 1 addition & 3 deletions bless_lambda/bless_ca/bless/__about__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import absolute_import, division, print_function

__all__ = [
"__title__", "__summary__", "__uri__", "__version__", "__author__",
"__email__", "__license__", "__copyright__",
Expand All @@ -11,7 +9,7 @@
"sign SSH public keys.")
__uri__ = "https://github.com/Netflix/bless"

__version__ = "0.2.0"
__version__ = "0.3.0"

__author__ = "The BLESS developers"
__email__ = "[email protected]"
Expand Down
Binary file not shown.
Empty file.
44 changes: 44 additions & 0 deletions bless_lambda/bless_ca/bless/cache/bless_lambda_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import base64
import os

import boto3
from bless.config.bless_config import BlessConfig
from botocore.exceptions import ClientError


class BlessLambdaCache:
region = None
config = None
ca_private_key_password = None
ca_private_key_password_error = None

def __init__(self, ca_private_key_password=None,
config_file=None):
"""
:param ca_private_key_password: For local testing, if the password is provided, skip the KMS
decrypt.
:param config_file: The config file to load the SSH CA private key from, and additional settings.
"""
# AWS Region determines configs related to KMS
if 'AWS_REGION' in os.environ:
self.region = os.environ['AWS_REGION']
else:
self.region = 'us-west-2'

# Load the deployment config values
self.config = BlessConfig(self.region, config_file=config_file)

password_ciphertext_b64 = self.config.getpassword()

# decrypt ca private key password
if ca_private_key_password is None:
kms_client = boto3.client('kms', region_name=self.region)
try:
ca_password = kms_client.decrypt(
CiphertextBlob=base64.b64decode(password_ciphertext_b64))
self.ca_private_key_password = ca_password['Plaintext']
except ClientError as e:
self.ca_private_key_password_error = str(e)
else:
self.ca_private_key_password = ca_private_key_password
Binary file not shown.
Binary file not shown.
66 changes: 56 additions & 10 deletions bless_lambda/bless_ca/bless/config/bless_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
:copyright: (c) 2016 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
"""
import ConfigParser
import configparser
import base64
import os
import re
import zlib
import bz2

BLESS_OPTIONS_SECTION = 'Bless Options'
CERTIFICATE_VALIDITY_BEFORE_SEC_OPTION = 'certificate_validity_before_seconds'
Expand Down Expand Up @@ -36,6 +38,8 @@
BLESS_CA_SECTION = 'Bless CA'
CA_PRIVATE_KEY_FILE_OPTION = 'ca_private_key_file'
CA_PRIVATE_KEY_OPTION = 'ca_private_key'
CA_PRIVATE_KEY_COMPRESSION_OPTION = 'ca_private_key_compression'
CA_PRIVATE_KEY_COMPRESSION_OPTION_DEFAULT = None

REGION_PASSWORD_OPTION_SUFFIX = '_password'

Expand All @@ -58,8 +62,17 @@
REMOTE_USERNAMES_VALIDATION_OPTION = 'remote_usernames_validation'
REMOTE_USERNAMES_VALIDATION_DEFAULT = 'principal'

VALIDATE_REMOTE_USERNAMES_AGAINST_IAM_GROUPS_OPTION = 'kmsauth_validate_remote_usernames_against_iam_groups'
VALIDATE_REMOTE_USERNAMES_AGAINST_IAM_GROUPS_DEFAULT = False

class BlessConfig(ConfigParser.RawConfigParser, object):
IAM_GROUP_NAME_VALIDATION_FORMAT_OPTION = 'kmsauth_iam_group_name_format'
IAM_GROUP_NAME_VALIDATION_FORMAT_DEFAULT = 'ssh-{}'

REMOTE_USERNAMES_BLACKLIST_OPTION = 'remote_usernames_blacklist'
REMOTE_USERNAMES_BLACKLIST_DEFAULT = None


class BlessConfig(configparser.RawConfigParser, object):
def __init__(self, aws_region, config_file):
"""
Parses the BLESS config file, and provides some reasonable default values if they are
Expand All @@ -84,11 +97,18 @@ def __init__(self, aws_region, config_file):
KMSAUTH_USEKMSAUTH_OPTION: KMSAUTH_USEKMSAUTH_DEFAULT,
CERTIFICATE_EXTENSIONS_OPTION: CERTIFICATE_EXTENSIONS_DEFAULT,
USERNAME_VALIDATION_OPTION: USERNAME_VALIDATION_DEFAULT,
REMOTE_USERNAMES_VALIDATION_OPTION: REMOTE_USERNAMES_VALIDATION_DEFAULT
REMOTE_USERNAMES_VALIDATION_OPTION: REMOTE_USERNAMES_VALIDATION_DEFAULT,
VALIDATE_REMOTE_USERNAMES_AGAINST_IAM_GROUPS_OPTION: VALIDATE_REMOTE_USERNAMES_AGAINST_IAM_GROUPS_DEFAULT,
IAM_GROUP_NAME_VALIDATION_FORMAT_OPTION: IAM_GROUP_NAME_VALIDATION_FORMAT_DEFAULT,
REMOTE_USERNAMES_BLACKLIST_OPTION: REMOTE_USERNAMES_BLACKLIST_DEFAULT,
CA_PRIVATE_KEY_COMPRESSION_OPTION: CA_PRIVATE_KEY_COMPRESSION_OPTION_DEFAULT
}
ConfigParser.RawConfigParser.__init__(self, defaults=defaults)
configparser.RawConfigParser.__init__(self, defaults=defaults)
self.read(config_file)

if not self.has_section(BLESS_CA_SECTION):
self.add_section(BLESS_CA_SECTION)

if not self.has_section(BLESS_OPTIONS_SECTION):
self.add_section(BLESS_OPTIONS_SECTION)

Expand All @@ -114,17 +134,24 @@ def getkmsauthkeyids(self):
in one region can validate in another).
:return: A list of kmsauth key ids
"""
return map(str.strip, self.get(KMSAUTH_SECTION, KMSAUTH_KEY_ID_OPTION).split(','))
return list(map(str.strip, self.get(KMSAUTH_SECTION, KMSAUTH_KEY_ID_OPTION).split(',')))

def getprivatekey(self):
"""
Get a private key from either a file specified in the config file, or from an environment variable. Env
Vars in Lambda can't contain a 4096 RSA key uncompressed, so compressed keys are also supported.
:return: byte string that contains the private key in PEM format (ascii).
"""
compression = self.get(BLESS_CA_SECTION, CA_PRIVATE_KEY_COMPRESSION_OPTION)

if self.has_option(BLESS_CA_SECTION, CA_PRIVATE_KEY_OPTION):
return base64.b64decode(self.get(BLESS_CA_SECTION, CA_PRIVATE_KEY_OPTION))
return self._decompress(base64.b64decode(self.get(BLESS_CA_SECTION, CA_PRIVATE_KEY_OPTION)), compression)

ca_private_key_file = self.get(BLESS_CA_SECTION, CA_PRIVATE_KEY_FILE_OPTION)

# read the private key .pem
with open(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, ca_private_key_file), 'r') as f:
return f.read()
with open(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, ca_private_key_file), 'rb') as f:
return self._decompress(f.read(), compression)

def has_option(self, section, option):
"""
Expand All @@ -141,7 +168,7 @@ def has_option(self, section, option):
else:
return super(BlessConfig, self).has_option(section, option)

def get(self, section, option):
def get(self, section, option, **kwargs):
"""
Gets a value from the configuration.
Expand All @@ -153,9 +180,28 @@ def get(self, section, option):
environment_key = self._environment_key(section, option)
output = os.environ.get(environment_key, None)
if output is None:
output = super(BlessConfig, self).get(section, option)
output = super(BlessConfig, self).get(section, option, **kwargs)
return output

@staticmethod
def _environment_key(section, option):
return (re.sub('\W+', '_', section) + '_' + re.sub('\W+', '_', option)).lower()

@staticmethod
def _decompress(data, algorithm):
"""
Decompress a byte string based of the provided algorithm.
:param data: byte string
:param algorithm: string with the name of the compression algorithm used
:return: decompressed byte string.
"""
if algorithm is None or algorithm == 'none':
result = data
elif algorithm == 'zlib':
result = zlib.decompress(data)
elif algorithm == 'bz2':
result = bz2.decompress(data)
else:
raise ValueError("Compression {} is not supported.".format(algorithm))

return result
14 changes: 13 additions & 1 deletion bless_lambda/bless_ca/bless/config/bless_deploy_example.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ logging_level = INFO
# username_validation = useradd
# Configure how remote_usernames names are validated.
# remote_usernames_validation = principal
# Configure a regex of blacklisted remote_usernames that will be rejected for any value of remote_usernames_validation.
# remote_usernames_blacklist = root|admin.*

# These values are all required to be modified for deployment
[Bless CA]
Expand Down Expand Up @@ -45,4 +47,14 @@ ca_private_key_file = <INSERT_YOUR_ENCRYPTED_PEM_FILE_NAME>
# By default, kmsauth requires that requested bastion_user must be the same as the requested remote_usernames. If you
# want Bless to sign a certificate for a different remote_usernames (like root, or a shared admin account), you must
# specify those allowed names here. * will allow signing for all remote_usernames
# kmsauth_remote_usernames_allowed = ubuntu,root,ec2-user,stufflikethat
# kmsauth_remote_usernames_allowed = ubuntu,root,ec2-user,stufflikethat

# If the kmsauth_remote_usernames_allowed option is set, kmsauth will allow certifiates for those usernames
# to be generated by any user who can invoke the lambda function. If you would like to ensure that users have to
# be in a an IAM group pertaining to the remote_username, enable this option.
# kmsauth_validate_remote_usernames_against_iam_groups = False

# For use with the kmsauth_validate_remote_usernames_against_iam_groups option. By default the required format for
# the group name is "ssh-{}".format(remote_username), but that can be changed here. The groups must have a
# consistent naming scheme and must all contain the remote_username once. For example, ssh-ubuntu.
# kmsauth_iam_group_name_format = ssh-{}
20 changes: 15 additions & 5 deletions bless_lambda/bless_ca/bless/request/bless_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
from marshmallow.validate import Email

from bless.config.bless_config import USERNAME_VALIDATION_OPTION, REMOTE_USERNAMES_VALIDATION_OPTION, \
USERNAME_VALIDATION_DEFAULT, REMOTE_USERNAMES_VALIDATION_DEFAULT
USERNAME_VALIDATION_DEFAULT, REMOTE_USERNAMES_VALIDATION_DEFAULT, REMOTE_USERNAMES_BLACKLIST_OPTION, \
REMOTE_USERNAMES_BLACKLIST_DEFAULT

# man 8 useradd
USERNAME_PATTERN = re.compile('[a-z_][a-z0-9_-]*[$]?\Z')
Expand All @@ -29,12 +30,13 @@
# There doesn't seem to be any practical size limits of an SSH Certificate Principal (> 4096B allowed).
PRINCIPAL_PATTERN = re.compile(r'[\d\w!"$%&\'()*+\-./:;<=>?@\[\\\]\^`{|}~]+\Z')
VALID_SSH_RSA_PUBLIC_KEY_HEADER = "ssh-rsa AAAAB3NzaC1yc2"
VALID_SSH_ED25519_PUBLIC_KEY_HEADER = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5"

USERNAME_VALIDATION_OPTIONS = Enum('UserNameValidationOptions',
'useradd ' # Allowable usernames per 'man 8 useradd'
'debian ' # Allowable usernames on debian systems.
'email ' # username is a valid email address.
'principal ' # SSH Certificate Principal. See 'man 5 sshd_con# fig'.
'principal ' # SSH Certificate Principal. See 'man 5 sshd_config'.
'disabled') # no additional validation of the string.


Expand All @@ -46,7 +48,11 @@ def validate_ips(ips):
raise ValidationError('Invalid IP address.')


def validate_user(user, username_validation):
def validate_user(user, username_validation, username_blacklist=None):
if username_blacklist:
if re.match(username_blacklist, user) is not None:
raise ValidationError('Username contains invalid characters.')

if username_validation == USERNAME_VALIDATION_OPTIONS.disabled:
return
elif username_validation == USERNAME_VALIDATION_OPTIONS.email:
Expand Down Expand Up @@ -79,7 +85,7 @@ def _validate_principal(principal):


def validate_ssh_public_key(public_key):
if public_key.startswith(VALID_SSH_RSA_PUBLIC_KEY_HEADER):
if public_key.startswith(VALID_SSH_RSA_PUBLIC_KEY_HEADER) or public_key.startswith(VALID_SSH_ED25519_PUBLIC_KEY_HEADER):
pass
# todo other key types
else:
Expand Down Expand Up @@ -119,8 +125,12 @@ def validate_remote_usernames(self, remote_usernames):
username_validation = USERNAME_VALIDATION_OPTIONS[self.context[REMOTE_USERNAMES_VALIDATION_OPTION]]
else:
username_validation = USERNAME_VALIDATION_OPTIONS[REMOTE_USERNAMES_VALIDATION_DEFAULT]
if REMOTE_USERNAMES_BLACKLIST_OPTION in self.context:
username_blacklist = self.context[REMOTE_USERNAMES_BLACKLIST_OPTION]
else:
username_blacklist = REMOTE_USERNAMES_BLACKLIST_DEFAULT
for remote_username in remote_usernames.split(','):
validate_user(remote_username, username_validation)
validate_user(remote_username, username_validation, username_blacklist)


class BlessRequest:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ def __init__(self, pem_private_key, private_key_password=None):
private_key_password,
default_backend())

self.signer = self.private_key.signer(padding.PKCS1v15(),
hashes.SHA1())
ca_pub_numbers = self.private_key.public_key().public_numbers()

self.e = ca_pub_numbers.e
Expand All @@ -55,7 +53,6 @@ def sign(self, body):
signature key.
:return: SSH RSA Signature.
"""
self.signer.update(body)
signature = self.signer.finalize()
signature = self.private_key.sign(body, padding.PKCS1v15(), hashes.SHA1())

return self._serialize_signature(signature)
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
def get_ssh_certificate_authority(private_key, password=None):
"""
Returns the proper SSHCertificateAuthority instance based off the private_key type.
:param private_key: SSH compatible Private Key (e.g., PEM or SSH Protocol 2 Private Key).
:param private_key: ASCII bytes of an SSH compatible Private Key (e.g., PEM or SSH Protocol 2 Private Key).
It should be encrypted with a password, but that is not required.
:param password: Password to decrypt the Private Key, if it is encrypted. Which it should be.
:param password: ASCII bytes of the Password to decrypt the Private Key, if it is encrypted. Which it should be.
:return: An SSHCertificateAuthority instance.
"""
if private_key.startswith(SSHCertificateAuthorityPrivateKeyType.RSA):
if private_key.decode('ascii').startswith(SSHCertificateAuthorityPrivateKeyType.RSA):
return RSACertificateAuthority(private_key, password)
else:
raise TypeError("Unsupported CA Private Key Type")
Loading

0 comments on commit f245ae0

Please sign in to comment.