Skip to content

Commit

Permalink
Merge 24af4b4 into 80f3c1b
Browse files Browse the repository at this point in the history
  • Loading branch information
stoggi committed Nov 4, 2019
2 parents 80f3c1b + 24af4b4 commit db97cda
Show file tree
Hide file tree
Showing 10 changed files with 428 additions and 5 deletions.
37 changes: 37 additions & 0 deletions bless/aws_lambda/bless_lambda_user.py
Expand Up @@ -17,6 +17,13 @@
KMSAUTH_REMOTE_USERNAMES_ALLOWED_OPTION, \
VALIDATE_REMOTE_USERNAMES_AGAINST_IAM_GROUPS_OPTION, \
KMSAUTH_SERVICE_ID_OPTION, \
JWTAUTH_SECTION, \
JWTAUTH_USEJWTAUTH_OPTION, \
JWTAUTH_SIGNATURE_JWK_OPTION, \
JWTAUTH_AUDIENCE_OPTION, \
JWTAUTH_ISSUER_OPTION, \
JWTAUTH_SIGNATURE_ALGORITHM_OPTION, \
JWTAUTH_USERNAME_CLAIM_OPTION, \
TEST_USER_OPTION, \
CERTIFICATE_EXTENSIONS_OPTION, \
REMOTE_USERNAMES_VALIDATION_OPTION, \
Expand All @@ -29,6 +36,8 @@
from bless.ssh.certificates.ssh_certificate_builder_factory import get_ssh_certificate_builder
from kmsauth import KMSTokenValidator, TokenValidationError
from marshmallow.exceptions import ValidationError
from jose import jwt
from jose.exceptions import JWTError


def lambda_handler_user(
Expand Down Expand Up @@ -159,6 +168,34 @@ def lambda_handler_user(
else:
return error_response('InputValidationError', 'Invalid request, missing kmsauth token')

# Authenticate the user with JWT, if key is configured
if config.getboolean(JWTAUTH_SECTION, JWTAUTH_USEJWTAUTH_OPTION):
if request.jwtauth_token:

if request.remote_usernames != request.bastion_user:
return error_response('JWTAuthValidationError',
'remote_usernames must be the same as bastion_user')
try:
claims = jwt.decode(
request.jwtauth_token,
config.get(JWTAUTH_SECTION, JWTAUTH_SIGNATURE_JWK_OPTION),
audience=config.get(JWTAUTH_SECTION, JWTAUTH_AUDIENCE_OPTION),
issuer=config.get(JWTAUTH_SECTION, JWTAUTH_ISSUER_OPTION),
algorithms=config.get(JWTAUTH_SECTION, JWTAUTH_SIGNATURE_ALGORITHM_OPTION),
options={'verify_at_hash': False}
)
username_claim = config.get(JWTAUTH_SECTION, JWTAUTH_USERNAME_CLAIM_OPTION)
if username_claim not in claims.keys():
return error_response('JWTAuthValidationError',
'missing {} claim in jwt'.format(username_claim))
if request.bastion_user != claims[username_claim]:
return error_response('JWTAuthValidationError',
'bastion_user must equal {} claim in jwt'.format(username_claim))
except JWTError as e:
return error_response('JWTAuthValidationError', str(e))
else:
return error_response('InputValidationError', 'Invalid request, missing jwtauth_token')

# Build the cert
ca = get_ssh_certificate_authority(ca_private_key, ca_private_key_password)
cert_builder = get_ssh_certificate_builder(ca, SSHCertificateType.USER,
Expand Down
28 changes: 28 additions & 0 deletions bless/config/bless_config.py
Expand Up @@ -63,6 +63,25 @@
KMSAUTH_SERVICE_ID_OPTION = 'kmsauth_serviceid'
KMSAUTH_SERVICE_ID_DEFAULT = None

JWTAUTH_SECTION = 'JWT Auth'
JWTAUTH_USEJWTAUTH_OPTION = 'use_jwtauth'
JWTAUTH_USEJWTAUTH_DEFAULT = 'False'

JWTAUTH_SIGNATURE_JWK_OPTION = 'jwtauth_signature_jwk'
JWTAUTH_SIGNATURE_JWK_DEFAULT = ''

JWTAUTH_SIGNATURE_ALGORITHM_OPTION = 'jwtauth_signature_algorithm'
JWTAUTH_SIGNATURE_ALGORITHM_DEFAULT = 'RS256'

JWTAUTH_ISSUER_OPTION = 'jwtauth_issuer'
JWTAUTH_ISSUER_DEFAULT = ''

JWTAUTH_AUDIENCE_OPTION = 'jwtauth_audience'
JWTAUTH_AUDIENCE_DEFAULT = ''

JWTAUTH_USERNAME_CLAIM_OPTION = 'jwtauth_username_claim'
JWTAUTH_USERNAME_CLAIM_DEFAULT = 'email'

USERNAME_VALIDATION_OPTION = 'username_validation'
USERNAME_VALIDATION_DEFAULT = 'useradd'

Expand Down Expand Up @@ -102,6 +121,12 @@ def __init__(self, aws_region, config_file):
KMSAUTH_KEY_ID_OPTION: KMSAUTH_KEY_ID_DEFAULT,
KMSAUTH_REMOTE_USERNAMES_ALLOWED_OPTION: KMSAUTH_REMOTE_USERNAMES_ALLOWED_OPTION_DEFAULT,
KMSAUTH_USEKMSAUTH_OPTION: KMSAUTH_USEKMSAUTH_DEFAULT,
JWTAUTH_USEJWTAUTH_OPTION: JWTAUTH_USEJWTAUTH_DEFAULT,
JWTAUTH_SIGNATURE_JWK_OPTION: JWTAUTH_SIGNATURE_JWK_DEFAULT,
JWTAUTH_SIGNATURE_ALGORITHM_OPTION: JWTAUTH_SIGNATURE_ALGORITHM_DEFAULT,
JWTAUTH_ISSUER_OPTION: JWTAUTH_ISSUER_DEFAULT,
JWTAUTH_AUDIENCE_OPTION: JWTAUTH_AUDIENCE_DEFAULT,
JWTAUTH_USERNAME_CLAIM_OPTION: JWTAUTH_USERNAME_CLAIM_DEFAULT,
CERTIFICATE_EXTENSIONS_OPTION: CERTIFICATE_EXTENSIONS_DEFAULT,
USERNAME_VALIDATION_OPTION: USERNAME_VALIDATION_DEFAULT,
REMOTE_USERNAMES_VALIDATION_OPTION: REMOTE_USERNAMES_VALIDATION_DEFAULT,
Expand All @@ -125,6 +150,9 @@ def __init__(self, aws_region, config_file):
if not self.has_section(KMSAUTH_SECTION):
self.add_section(KMSAUTH_SECTION)

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

if not self.has_option(BLESS_CA_SECTION, self.aws_region + REGION_PASSWORD_OPTION_SUFFIX):
if not self.has_option(BLESS_CA_SECTION, 'default' + REGION_PASSWORD_OPTION_SUFFIX):
raise ValueError("No Region Specific And No Default Password Provided.")
Expand Down
21 changes: 21 additions & 0 deletions bless/config/bless_deploy_example.cfg
Expand Up @@ -63,3 +63,24 @@ ca_private_key_file = <INSERT_YOUR_ENCRYPTED_PEM_FILE_NAME>
# 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-{}

# This section is optional
[JWT Auth]
# Enable authentication via a JWT, to ensure a username matches a claim in a valid JWT
# use_jwtauth = True

# The JWK containing the public key used to verify the JWT signature, in JSON Web Key format https://tools.ietf.org/html/rfc7517
# jwtauth_signature_jwk = {"kty": "RSA","e": "...","use": "sig","kid": "...","alg": "RS256","n": "..."}

# The expected signature algorithm. JWTs signed with a different algorithm will be rejected.
# jwtauth_signature_algorithm = RS256

# The issuer present as the iss claim in the JWT. This must match the issuer from your identity provider.
# jwtauth_issuer = https://accounts.google.com

# The audience present as the aud claim in the JWT. This must match the audience from your identity provider.
# jwtauth_audience = 1234567890-1234567890abcd.apps.googleusercontent.com

# The claim in the JWT that contains the username. This is compared to the requested username, and will be set in the
# principals list of the SSH certificate.
# jwtauth_username_claim = email
5 changes: 4 additions & 1 deletion bless/request/bless_request_user.py
Expand Up @@ -90,6 +90,7 @@ class BlessUserSchema(Schema):
public_key_to_sign = fields.Str(validate=validate_ssh_public_key, required=True)
remote_usernames = fields.Str(required=True)
kmsauth_token = fields.Str(required=False)
jwtauth_token = fields.Str(required=False)

@validates_schema(pass_original=True)
def check_unknown_fields(self, data, original_data):
Expand Down Expand Up @@ -125,7 +126,7 @@ def validate_remote_usernames(self, remote_usernames):

class BlessUserRequest:
def __init__(self, bastion_ips, bastion_user, bastion_user_ip, command, public_key_to_sign,
remote_usernames, kmsauth_token=None):
remote_usernames, kmsauth_token=None, jwtauth_token=None):
"""
A BlessRequest must have the following key value pairs to be valid.
:param bastion_ips: The source IPs where the SSH connection will be initiated from. This is
Expand All @@ -138,6 +139,7 @@ def __init__(self, bastion_ips, bastion_user, bastion_user_ip, command, public_k
:param remote_usernames: Comma-separated list of username(s) or authorized principals on the remote
server that will be used in the SSH request. This is enforced in the issued certificate.
:param kmsauth_token: An optional kms auth token to authenticate the user.
:param jwtauth_token: An optional jwt token to authenticate the user.
"""
self.bastion_ips = bastion_ips
self.bastion_user = bastion_user
Expand All @@ -146,6 +148,7 @@ def __init__(self, bastion_ips, bastion_user, bastion_user_ip, command, public_k
self.public_key_to_sign = public_key_to_sign
self.remote_usernames = remote_usernames
self.kmsauth_token = kmsauth_token
self.jwtauth_token = jwtauth_token

def __eq__(self, other):
return self.__dict__ == other.__dict__
1 change: 1 addition & 0 deletions requirements.txt
Expand Up @@ -14,3 +14,4 @@ python-dateutil==2.8.0
s3transfer==0.2.0
six==1.12.0
urllib3==1.24.3
python-jose[cryptography]==3.0.1
5 changes: 3 additions & 2 deletions setup.py
Expand Up @@ -21,8 +21,9 @@
'boto3',
'cryptography',
'ipaddress',
'marshmallow',
'kmsauth'
'marshmallow<3',
'kmsauth',
'python-jose[cryptography]'
],
extras_require={
'tests': [
Expand Down
12 changes: 12 additions & 0 deletions tests/aws_lambda/bless-test-jwtauth.cfg
@@ -0,0 +1,12 @@
[Bless CA]
ca_private_key_file = tests/aws_lambda/only-use-for-unit-tests.pem
us-east-1_password = bogus-password-for-unit-test
us-west-2_password = bogus-password-for-unit-test

[JWT Auth]
use_jwtauth = True
jwtauth_signature_jwk = {"kty": "RSA","e": "AQAB","use": "sig","kid": "key1","alg": "RS256","n": "7V4O45XkdzKedgfbg3U1X_UeGF00wQH6APcuRX_702h-3QZI4VmAbBFgDDAJgHa1wunKPUKmwfmzFodLX6Bd2UvgHtzhHDAnrHYSOpV0jci7zxUhPN84PBbNRKNG-yAGPvNk4YbCWHywz7BKmTVnG9q4KSdWaHpyhljxedMdkt2JqdTJcwaAEfqT_0A-gcBWxyCPwRJJRLColM9g6lZU7-17Y3UNHwBFC4lahfd009CXY7WMbKIJMG0LuBjsmCE4L__IlrFlevVFyA0ShDjDh07gKD-f5WJ6WdgcZOL7X3rf-DK6MRBUW4ItIpG7DVVWN0Vj6SNQT3x1kwq55mIZTw"}
jwtauth_signature_algorithm = RS256
jwtauth_issuer = https://issuer.example.com
jwtauth_audience = 6c1d8893-9240-4f87-be95-1f21ef664ce0
jwtauth_username_claim = username

0 comments on commit db97cda

Please sign in to comment.