Lambda code getting error while accessing secrets manager - python-3.x

I'm pretty new to lambda and python. i got a readymade code from google for an activity by creating Username and Password based SFTP with Lambda as IdP in AWS Transfer Family.
I have no clue which field i need to add the values of server id, user, password, region, etc... I'm typically cloud admin. This is the cloudwatch error logs getting from this.
ERRORS AUTH_FAILURE Method=password User=new Message="{"errorMessage": "'s-2eccd17fcd5244f68'", "errorType": "KeyError", "requestId": "61a86f3d-7a5b-4763-97e9-f97b74c77d58", "stackTrace": [" File \"/var/task/lambda_function.py\", line 22, in lambda_handler\n input_serverId = event[\"s-2eccd17fcd5244f68\"]\n"]}" SourceIP=x.x.x.x
Lambda unable to fetch the password from secrets manager it looks like. i have given sufficient IAM permission for this
Here is the lambda code..
import os
import json
import boto3
import base64
from ipaddress import ip_network, ip_address
from botocore.exceptions import ClientError
def lambda_handler(event, context):
# Get the required parameters
required_param_list = ["serverId", "username", "protocol", "sourceIp"]
for parameter in required_param_list:
if parameter not in event:
print("Incoming " + parameter + " missing - Unexpected")
return {}
input_serverId = event["serverId"]
input_username = event["username"]
input_protocol = event["protocol"]
input_sourceIp = event["sourceIp"]
input_password = event.get("password", "")
print("ServerId: {}, Username: {}, Protocol: {}, SourceIp: {}"
.format(input_serverId, input_username, input_protocol, input_sourceIp))
# Check for password and set authentication type appropriately. No password means SSH auth
print("Start User Authentication Flow")
if input_password != "":
print("Using PASSWORD authentication")
authentication_type = "PASSWORD"
else:
if input_protocol == 'FTP' or input_protocol == 'FTPS':
print("Empty password not allowed for FTP/S")
return {}
print("Using SSH authentication")
authentication_type = "SSH"
# Retrieve our user details from the secret. For all key-value pairs stored in SecretManager,
# checking the protocol-specified secret first, then use generic ones.
# e.g. If SFTPPassword and Password both exists, will be using SFTPPassword for authentication
secret = get_secret(input_serverId + "/" + input_username)
if secret is not None:
secret_dict = json.loads(secret)
# Run our password checks
user_authenticated = authenticate_user(authentication_type, secret_dict, input_password, input_protocol)
# Run sourceIp checks
ip_match = check_ipaddress(secret_dict, input_sourceIp, input_protocol)
if user_authenticated and ip_match:
print("User authenticated, calling build_response with: " + authentication_type)
return build_response(secret_dict, authentication_type, input_protocol)
else:
print("User failed authentication return empty response")
return {}
else:
# Otherwise something went wrong. Most likely the object name is not there
print("Secrets Manager exception thrown - Returning empty response")
# Return an empty data response meaning the user was not authenticated
return {}
def lookup(secret_dict, key, input_protocol):
if input_protocol + key in secret_dict:
print("Found protocol-specified {}".format(key))
return secret_dict[input_protocol + key]
else:
return secret_dict.get(key, None)
def check_ipaddress(secret_dict, input_sourceIp, input_protocol):
accepted_ip_network = lookup(secret_dict, "AcceptedIpNetwork", input_protocol)
if not accepted_ip_network:
# No IP provided so skip checks
print("No IP range provided - Skip IP check")
return True
net = ip_network(accepted_ip_network)
if ip_address(input_sourceIp) in net:
print("Source IP address match")
return True
else:
print("Source IP address not in range")
return False
def authenticate_user(auth_type, secret_dict, input_password, input_protocol):
# Function returns True if: auth_type is password and passwords match or auth_type is SSH. Otherwise returns False
if auth_type == "SSH":
# Place for additional checks in future
print("Skip password check as SSH login request")
return True
# auth_type could only be SSH or PASSWORD
else:
# Retrieve the password from the secret if exists
password = lookup(secret_dict, "Password", input_protocol)
if not password:
print("Unable to authenticate user - No field match in Secret for password")
return False
if input_password == password:
return True
else:
print("Unable to authenticate user - Incoming password does not match stored")
return False
# Build out our response data for an authenticated response
def build_response(secret_dict, auth_type, input_protocol):
response_data = {}
# Check for each key value pair. These are required so set to empty string if missing
role = lookup(secret_dict, "Role", input_protocol)
if role:
response_data["Role"] = role
else:
print("No field match for role - Set empty string in response")
response_data["Role"] = ""
# These are optional so ignore if not present
policy = lookup(secret_dict, "Policy", input_protocol)
if policy:
response_data["Policy"] = policy
# External Auth providers support chroot and virtual folder assignments so we'll check for that
home_directory_details = lookup(secret_dict, "HomeDirectoryDetails", input_protocol)
if home_directory_details:
print("HomeDirectoryDetails found - Applying setting for virtual folders - "
"Note: Cannot be used in conjunction with key: HomeDirectory")
response_data["HomeDirectoryDetails"] = home_directory_details
# If we have a virtual folder setup then we also need to set HomeDirectoryType to "Logical"
print("Setting HomeDirectoryType to LOGICAL")
response_data["HomeDirectoryType"] = "LOGICAL"
# Note that HomeDirectory and HomeDirectoryDetails / Logical mode
# can't be used together but we're not checking for this
home_directory = lookup(secret_dict, "HomeDirectory", input_protocol)
if home_directory:
print("HomeDirectory found - Note: Cannot be used in conjunction with key: HomeDirectoryDetails")
response_data["HomeDirectory"] = home_directory
if auth_type == "SSH":
public_key = lookup(secret_dict, "PublicKey", input_protocol)
if public_key:
response_data["PublicKeys"] = [public_key]
else:
# SSH Auth Flow - We don't have keys so we can't help
print("Unable to authenticate user - No public keys found")
return {}
return response_data
def get_secret(id):
region = os.environ["SecretsManagerRegion"]
print("Secrets Manager Region: " + region)
print("Secret Name: " + id)
# Create a Secrets Manager client
client = boto3.session.Session().client(service_name="secretsmanager", region_name=region)
try:
resp = client.get_secret_value(SecretId=id)
# Decrypts secret using the associated KMS CMK.
# Depending on whether the secret is a string or binary, one of these fields will be populated.
if "SecretString" in resp:
print("Found Secret String")
return resp["SecretString"]
else:
print("Found Binary Secret")
return base64.b64decode(resp["SecretBinary"])
except ClientError as err:
print("Error Talking to SecretsManager: " + err.response["Error"]["Code"] + ", Message: " +
err.response["Error"]["Message"])
return None
`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````

If you want to get Python code working with Secrets Manager, I recommend getting this Secret Manager Python code working from an IDE. Make sure you can get it working and you understand it.
https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/python/example_code/secretsmanager/secretsmanager_basics.py
Once you understand that, you can then move to building a Lambda function using this Python code. When you build a Lambda function, you need to make sure that the IAM role you run the Lambda with has permissions to invoke Secret Manager.

Related

implement authentication in request send to LDAP server for connecting in python3

I have used Django in python3 to develop website.
Now I want to implement LDAP login.
In our LDAP server, a fixed authentication string should be send in the request to LDAP server.
That is, in headers of the request, authentication item should be: "example_auth"
However, in connection class offered by ldap3 package, authentication could only be set to SIMPLE, ANONYMOUS, SASL, NTLM.
Then how could I set authentication to my authentication code for LDAP login?
class LDAPBackend:
def authenticate(self, request, username=None, password=None, **kwargs):
# set username to lowercase for consistency
username = username.lower()
# get the bind client to resolve DN
logger.info('authenticating %s' % username)
# set your server
server = Server("example.com/REST/json/service/loginStaff", port=389)
try:
conn = Connection(server, user=username, password=password, auto_bind=True,)
conn.open()
conn.bind()
except LDAPBindError as e:
logger.info('LDAP authentication failed')
logger.info(e)
return None
user = UserModel.objects.update_or_create(username=username)
return user
def get_user(self, user_id):
try:
return UserModel._default_manager.get(pk=user_id)
except UserModel.DoesNotExist:
return None
For anyone else who finds this page, this worked for me but I had to combine the above with elements from the source code that the author mentions. To make someone else's life easier, here's the complete config I needed to replace the authenticate method with LDAP:
# Add to settings.py:
... <YOUR OTHER SETTINGS ABOVE>
LDAP_HOST = '<YOUR LDAP HOST>'
LDAP_DOMAIN = '<YOUR DOMAIN'
LDAP_BASE_DN = 'OU=<YOU OU>,dc=< YOUR DC...>'
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
# Age of cookie, in seconds (default: 2 weeks, here set to 26 weeks).
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 26
AUTHENTICATION_BACKENDS = [
'django_react.backends.LDAPBackend',
'django.contrib.auth.backends.ModelBackend'
]
---- end config file
And this:
# add to django_react/backends.py:
---- start config file
import logging
from ldap3 import Server, Connection, SAFE_SYNC, SUBTREE
from ldap3.core.exceptions import *
logger = logging.getLogger(__name__)
from django.conf import settings
from django.contrib.auth import get_user_model
UserModel = get_user_model()
class LDAPBackend:
def get_user(self, user_id):
try:
return UserModel._default_manager.get(pk=user_id)
except UserModel.DoesNotExist:
return None
def authenticate(self, request, username=None, password=None, **kwargs):
# set username to lowercase for consistency
username = username.lower()
# get the bind client to resolve DN
logger.info('authenticating %s' % username)
# set your server
server = Server(settings.LDAP_HOST, get_info='ALL')
try:
conn = Connection(server, f"{username}#{settings.LDAP_DOMAIN}", password=password, client_strategy=SAFE_SYNC,
auto_bind=True)
status, result, response, _ = conn.search(
search_base=settings.LDAP_BASE_DN,
search_filter='(&(objectClass=person)(samaccountname=' + username + '))',
search_scope=SUBTREE,
attributes=['samaccountname', 'givenName', 'mail', 'sn']
)
except LDAPBindError as e:
logger.info('LDAP authentication failed')
logger.info(e)
return None
user, created = UserModel.objects.update_or_create(username=username,
defaults={
'first_name': response[0]['attributes']['givenName'],
'last_name': response[0]['attributes']['sn'],
'email': response[0]['attributes']['mail']
}
)
return user
---- end config file
You need to change the "Server". LDAP does not support URLs and as far as I know, it does not support headers as it is a different protocol than http/s
Set:
settings.LDAP_HOST to your LDAP server IP or FQDN (e.g. 192.168.x.x or pdc.ad.example.com)
settings.LDAP_DOMAIN to you LDAP Domain (e.g. ad.example.com)
settings.LDAP_BASE_DN to the Organization Unit(OU) under which your users exist. (e.g. OU=Users,dc=ad,dc=example,dc=com)
Below code will authenticate users against LDAP and Create or Update user in Django on successful authentication.
Tested on MS Active Directory via LDAP.
Original code taken from https://stackoverflow.com/a/63510132
from ldap3 import Server, Connection, SAFE_SYNC, SUBTREE
from ldap3.core.exceptions import *
def authenticate(self, request, username=None, password=None, **kwargs):
# set username to lowercase for consistency
username = username.lower()
# get the bind client to resolve DN
logger.info('authenticating %s' % username)
# set your server
server = Server(settings.LDAP_HOST, get_info='ALL')
try:
conn = Connection(server, f"{username}#{settings.LDAP_DOMAIN}", password=password, client_strategy=SAFE_SYNC, auto_bind=True)
status, result, response, _ = conn.search(
search_base = settings.LDAP_BASE_DN,
search_filter = '(&(objectClass=person)(samaccountname='+username+'))',
search_scope = SUBTREE,
attributes = ['samaccountname', 'givenName', 'mail', 'sn']
)
except LDAPBindError as e:
logger.info('LDAP authentication failed')
logger.info(e)
return None
user, created = UserModel.objects.update_or_create(username=username,
defaults={
'first_name': response[0]['attributes']['givenName'],
'last_name':response[0]['attributes']['sn'],
'email':response[0]['attributes']['mail']
}
)
return user

Error cannot unpack non-iterable NoneType object

not sure why the code is breaking down but am assigning 3 variables to a function call that are all used in the call and i keep getting an Nonetype error with the class function call.
i am trying to create a user and passing a success, user and message.
If success is true then it will then proceed to create and send the user an activation email
user is the user created.
message is if there is an error(there user has been created before and exists)
if email_address and first_name and last_name:
#register user and check other params
success, user, message = User.create(email_address=email_address, first_name=first_name, last_name=last_name)
if success:
# send activation email
success, message = User.send_activation_email(email_address=email_address)
if success:
return make_response(jsonify({"Message" : "Registration Successful"}))
#Todo include the correct url
#return render_template_with_translations("public/auth/register_success.html", **params)
else:
return abort(403, description=message)
else:
params["register_error_message"] = message
return make_response(jsonify({"Message" : params}))
#return render_template_with_translations("public/auth/register_error.html", **params)
The class being called in the users.model
#classmethod
def create(cls, email_address, password=None, admin=False, first_name=None, last_name=None):
try:
# check if there's any user with the same email address already
user = db.query("Select * FROM User WHERE email_address = {}".formart(email_address))
if not user: # if user does not yet exist, create one
hashed = None
if password:
# use bcrypt to hash the password
hashed = bcrypt.hashpw(password=str.encode(password), salt=bcrypt.gensalt(12))
# create the user object and store it into Datastore
user = cls(email_address=email_address, password_hash=hashed, admin=admin, first_name=first_name,
last_name=last_name)
user.put()
return True, user, "Success" # succes, user, message
else:
return False, user, "User with this email address is already registered. Please go to the " \
"Login page and try to log in."
except:
print ("Error with application")

How to pass variables to my SSM Run Command Document from my Lambda function

I am trying to pass my secrets value to my SSM document from my lambda function. Though I am able to read from my lambda's output - I am not able to put it into my document to call it as a variable. Please Suggest.
import boto3 # Required to interact with AWS
import json # Required for return object parsing
from botocore.exceptions import ClientError
# Set required variables
secret_name = "***/***/***"
endpoint_url = "https://secretsmanager.eu-west-1.amazonaws.com"
region_name = "eu-west-1"
session = boto3.session.Session()
client = session.client(
service_name='secretsmanager',
region_name=region_name,
endpoint_url=endpoint_url
)
try:
get_secret_value_response = client.get_secret_value(
SecretId=secret_name
)
except ClientError as e:
if e.response['Error']['Code'] == 'ResourceNotFoundException':
print("The requested secret " + secret_name + " was not found")
elif e.response['Error']['Code'] == 'InvalidRequestException':
print("The request was invalid due to:", e)
elif e.response['Error']['Code'] == 'InvalidParameterException':
print("The request had invalid params:", e)
else:
# Decrypted secret using the associated KMS CMK
# Depending on whether the secret was a string or binary, one of these fields will be populated
if 'SecretString' in get_secret_value_response:
secret = json.loads(get_secret_value_response['SecretString'])
else:
binary_secret_data = get_secret_value_response['SecretBinary']
access_key = secret['AWS_ACCESS_KEY_ID']
secret_key = secret['AWS_SECRET_ACCESS_KEY']
region = secret['AWS_DEFAULT_REGION']
ssm = boto3.client('ssm')
ec2 = boto3.resource('ec2')
def lambda_handler(event, context):
running_with = []
running_without = []
for instance in ec2.instances.all():
if instance.state['Name'] != 'running':
continue
has_tag = False
for tag in instance.tags:
if tag['Key'] == 'AutoDiskGrowth' and tag['Value'] == 'True':
has_tag = True
break
if has_tag:
running_with.append(instance.id)
else:
running_without.append(instance.id)
print("access_key: %s" % access_key)
print("Instances found with AutoDiskGrowth Tag: %s" % running_with)
print("Instances without AutoDiskGrowth Tag: %s" % running_without)
ssmCommand = ssm.send_command(
Targets = [
{'Key': 'tag:AutoDiskGrowth',
'Values': [
'True']
}
],
DocumentName = 'Secrets_Management',
TimeoutSeconds = 6000,
Comment = 'Extending disk volume by 50%',
Parameters={
'AWS_ACCESS_KEY_ID': [
'secret_key',
]
}
)
Here, in the above print secret_key, I am able to see the value of the secret stored. But I need it to be sent to the Secrets_Management document as a variable.Here's what I get when I run this.
Current Output:
Response:
{
"errorMessage": "An error occurred (InvalidParameters) when calling the SendCommand operation: ",
"errorType": "InvalidParameters",
The InvalidParameters response indicates that your code is not sending the Parameters expected by the document.
If you go to the document in the Systems Manager console and look in the Parameters tab, you will see the list of Parameters that are permitted. They are case-sensitive.

How to handle callback data in flask

I'm trying to do payment on my website through paytm. But I don't know how to handle its call back url in flask.
Thank in advance.
I've tried google for same many times.
Here is my code:-
#app.route('/shop/',methods=['POST'])
def shop():
name=request.form['name']
email=request.form['email']
paytmno=request.form['paytmno']
amount=request.form['amount']
# password1=request.form['password1']
# password2=request.form['password2']
print(name,email,paytmno,amount)
deepak_dict = {
'MID': MERCHANT_ID,
'ORDER_ID': str(12345),
'TXN_AMOUNT': str(amount),
'CUST_ID': email,
'INDUSTRY_TYPE_ID': 'Retail',
'WEBSITE': 'WEBSTAGING',
'CHANNEL_ID': 'WEB',
'CALLBACK_URL':'http://localhost:5000/shop/handlerequest/',
}
deepak_dict['CHECKSUMHASH'] = Checksum.generate_checksum(deepak_dict, MERCHANT_KEY)
return render_template('payment/paytm.html', deepak_dict=deepak_dict)
#app.route('/shop/handlerequest/',methods=['POST'])
def shop_handlerequest():
# what to do
response_dict={}
return render_template('payment/paytm_response.html',response=response_dict)
I want to read its call back data.
Based on what I learned from Paytm's Developer Docs:-
import Checksum
import requests
MERCHANT_KEY = 'YourMerchantKeyHere';
#app.route('/shop/handlerequest/',methods=['POST'])
def shop_handlerequest():
respons_dict = {}
for i in request.form.keys():
respons_dict[i]=request.form[i] #Getting all the attributes from form in a dictionary to use it later.
if i=='CHECKSUMHASH':
checksum = request.form[i] #Getting Checksum to verify its authenticity
if 'GATEWAYNAME' in respons_dict:
if respons_dict['GATEWAYNAME'] == 'WALLET':
respons_dict['BANKNAME'] = 'null'; #If Gateway is user's paytm wallet setting bankname to null
verify = Checksum.verify_checksum(respons_dict, MERCHANT_KEY, checksum) # returns true or false based on calculations
print(verify)
if verify:
if respons_dict['RESPCODE'] == '01' # 01 Code means successful transaction
print("order successful")
else:
print("order unsuccessful because"+respons_dict['RESPMSG'])
else:
print("order unsuccessful because"+respons_dict['RESPMSG'])
You can Use test credentials from Paytm to test your integration

How to fix "TypeError: Unicode-objects must be encoded before hashing" on bcrypt.hashpw

I am setting up a server, and on my login page I use bycrpt's hashpw function to hash the passwords before storing them in the database. When I click login, I get the error message "TypeError: Unicode-objects must be encoded before hashing" I have tried encoding the passwords before hashing them both with .encode() and .encdoe('utf-8') in multiple locations and times.
This is a copy of Windows Server 2019 (on an AWS EC2 instance), running Python 3.7 and Sqlite3. This was running fine for a while, and when I changed computers, it stopped working.
This is my code for the login page:
def loginpage():
global error
if request.method == "POST":
usrname = request.form['username']
pswd = request.form['password']
if not db.checkExists(usrname):
error = "Invalid username or password!"
elif not db.checkValidUser(usrname, pswd.encode('utf-8')):
error = "Invalid username or password!"
else:
session['loggedIn'] = True
session['user'] = request.form['username']
if session.get('prevurl') == None:
return redirect(url_for('index'))
else:
prev = session.get('prevurl')
session['prevurl'] = ''
return redirect(url_for(prev))
return render_template('login.html', **globals())
And this is my database handler code:
def checkValidUser(username, password):
#get salt and hash passwords
if not checkExists(username):
return False
salt = getSalt(username)
salt = salt[0][0]
#password = password.encode('utf-8')
password = bcrypt.hashpw(password, salt)
#connect to database
con = sql.connect(sqlite_db_file)
cur = con.cursor()
cur.execute("SELECT * FROM users WHERE username=? AND password=?", (username, password))
validUser = cur.fetchall()
con.close()
if not validUser:
return False
return True
When I click login, I should either see an error saying "Incorrect username or password" or get redirected to the main page while being logged in. Instead I get a typeError telling me that unicode objects must be encoded. If you know something that could help, I appreciate your help!
It could be that the issue is related to the differences between bcrypt and py-bcrypt as discussed here on github.
Those are different modules. bcrypt expects bytes (encoded strings), py-bcrypt expets strings (str). So if you don't want to change your code, you'd have to install py-bcrypt.
I'm not certain whether it has the same feature set.

Resources