I'm trying to get the access key age for each user in multiple AWS accounts. I currently have code that prints out the access key age but the code doesn't return it properly from an error. The code I'm working with is,
import boto3
from time import gmtime, strftime
from datetime import datetime
sts = boto3.client('sts')
def lambda_handler(event, context):
rolesessionname = "rolename"
account = "123456789"
response = sts.assume_role(
RoleArn = "arn:aws:iam::" + str(account) + ":role/audit",
RoleSessionName= rolesessionname
)
credentials = response['Credentials']
iam = boto3.client(
'iam',
aws_access_key_id = credentials['AccessKeyId'],
aws_secret_access_key = credentials['SecretAccessKey'],
aws_session_token = credentials['SessionToken']
)
response = iam.list_users()
nameList = []
todaysDate = strftime("%Y-%m-%d %H:%M:%S", gmtime())
todaysDate = str(todaysDate)
todaysDate = todaysDate[0:10]
todaysDate = datetime.strptime(todaysDate, "%Y-%m-%d")
for person in response["Users"]:
curPersonName = person["UserName"]
keys = iam.list_access_keys(UserName=curPersonName)
for keyData in keys["AccessKeyMetadata"]:
keyID = keyData["AccessKeyId"]
status = keyData["Status"]
CreateDate = keyData.get("CreateDate","none")
CreateDate = str(CreateDate)
CreateDate = CreateDate[0:10]
CreateDate = datetime.strptime(CreateDate, "%Y-%m-%d")
totalDays = abs((CreateDate - todaysDate).days)
print (totalDays-1)
nameList.append({
"UserName:":curPersonName,
"Status:": status,
"Create Date": CreateDate
#"Total days:" : totalDays-1
})
return nameList
My problem is that if I comment out the mentions of
CreateDate = datetime.strptime(CreateDate, "%Y-%m-%d")
and
totalDays = abs((CreateDate - todaysDate).days)
I get successful build and return data, just without the age of the key which is mainly what I want. However if I keep those lines in and print out to see if it's getting the age correctly it is. However It only prints them out and then errors with.
{
"errorMessage": "datetime.datetime(2017, 1, 11, 0, 0) is not JSON serializable",
"errorType": "TypeError",
"stackTrace": [
[
"/var/lang/lib/python3.6/json/__init__.py",
238,
"dumps",
"**kw).encode(obj)"
],
[
"/var/lang/lib/python3.6/json/encoder.py",
199,
"encode",
"chunks = self.iterencode(o, _one_shot=True)"
],
[
"/var/lang/lib/python3.6/json/encoder.py",
257,
"iterencode",
"return _iterencode(o, 0)"
],
[
"/var/runtime/awslambda/bootstrap.py",
110,
"decimal_serializer",
"raise TypeError(repr(o) + \" is not JSON serializable\")"
]
]
}
You're getting this error because datetime is not JSON serializable. You are saving a datetime object in CreateDate with
datetime.strptime(CreateDate, "%Y-%m-%d")
It seems like the answer would be to use this value to calculate your days:
totalDays = abs((CreateDate - todaysDate).days)
and then change it to a string representation before adding it to the return object:
CreateDate = CreateDate.isoformat() #or whatever return format you want.
Related
I have a lambda which i want to run every morning showing the users with AccessKeys older than 90 days. What i am noticing is that the return of users is limited
import boto3, json, time, datetime, sys, pprint
sns = boto3.client('sns')
usernames = []
mylist = []
sts_client = boto3.client('sts')
assumed_role_object=sts_client.assume_role(
RoleArn="arn:aws:iam::11111111:role/lambda_role",
RoleSessionName="AssumedRoleSession4"
)
credentials=assumed_role_object['Credentials']
client=boto3.client(
'iam',
aws_access_key_id=credentials['AccessKeyId'],
aws_secret_access_key=credentials['SecretAccessKey'],
aws_session_token=credentials['SessionToken'],
)
def lambda_handler(event, context):
users = client.list_users()
for key in users['Users']:
a = str(key['UserName'])
usernames.append(a)
for username in usernames:
try:
res = client.list_access_keys(UserName=username)
accesskeydate = res['AccessKeyMetadata'][0]['CreateDate']
accesskeydate = accesskeydate.strftime("%Y-%m-%d %H:%M:%S")
currentdate = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
accesskeyd = time.mktime(datetime.datetime.strptime(accesskeydate, "%Y-%m-%d %H:%M:%S").timetuple())
currentd = time.mktime(datetime.datetime.strptime(currentdate, "%Y-%m-%d %H:%M:%S").timetuple())
active_days = (currentd - accesskeyd)/60/60/24 ### We get the data in seconds. converting it to days
if 90 < active_days:
a = str(username)
c = int(int(round(active_days)))
mylist.append(a)
mylist.append(c)
except:
f = str('')
print(mylist)
finallist = ''.join(str(mylist))
finallist = finallist
sns_message = (finallist)
response = sns.publish(
TopicArn='arn:aws:sns:eu-west-2:111111:sns',
Message= sns_message,
Subject='Access Keys which need rotating',
)
How do i ensure all the users are captured in the python query and then emailed out via SNS?
Thanks
If you read the IAM list_users() documentation, you will see that results can be truncated and you need to paginate. The response you get back will include IsTruncated=True in which case you need to re-issue the list_users() call, supplying Marker, which you retrieve from the previous response.
Alternatively, and probably preferably, you can use a built-in paginator.
I have a script below which I would like to schedule to run it everyday through SQL Server and store the calendar events to database but the problem is I would need to have my outlook open in order to run this script, how can I add my credential in the script so I don't have to open outlook and have the script run without keeping my outlook opened? Thank you
import win32com.client, datetime
from datetime import date
from dateutil.parser import *
import calendar
import pandas as pd
import pyodbc
Outlook = win32com.client.Dispatch("Outlook.Application")
ns = Outlook.GetNamespace("MAPI")
Item = Outlook.CreateItem ( 1 )
Recip = Item.Recipients.Add ( 'Corporate Master Calendar' )
appts = ns.GetSharedDefaultFolder(Recip,9).Items
appts.Sort("[Start]")
appts.IncludeRecurrences = "False"
eoy=date(date.today().year, 12, 31)
eoy=eoy.strftime("%m/%d/%Y")
begin = date.today()
begin = begin.strftime("%m/%d/%Y")
appts = appts.Restrict("[Start] >= '" +begin+ "' AND [Start] <= '" +eoy+ "'")
apptDict = {}
item = 0
for indx, a in enumerate(appts):
organizer = str(a.Organizer)
start = a.Start
end = a.End
subject = str(a.Subject)
location = str(a.Location)
categories = str(a.Categories)
body=str(a.Body)
itemid=str(a.GlobalAppointmentId)
lmt=str(a.LastModificationTime)
apptDict[item] = {"GlobalAppointmentId": itemid, "Organizer": organizer, "Subject": subject, "Location": location, "Start": start, "End": end, "Body": body,"Categories": categories, "LMT":lmt}
item = item + 1
You can't - with MFA, even storing the credentials in the keyring (which only deals with basic credentials) won't do anything.
Here is my django model
class Data(models.Model):
created = models.DateTimeField(null=True, blank=True, editable=False)
modified = models.DateTimeField(null=True, blank=True)
raw = models.TextField(null=True, blank=True)
uuid = models.CharField(blank=True, null=True, max_length=48,unique=True)
used = models.BooleanField(default=False,null=True)
name = models.CharField(blank=True, null=True, max_length=200)
geohash = models.CharField(blank=True, null=True, max_length=200)
def __str__(self):
return str(self.created) + ":" + str(self.raw)
def __unicode__(self):
return str(self.created) + ":" + str(self.raw)
def save(self, *args, **kwargs):
""" On save, update timestamps """
if not self.uuid :
self.uuid = str(uuid.uuid4().hex) +str(random.randint(1000,9999) )
if not self.id:
self.created = timezone.now()
self.modified = timezone.now()
# if not self.geoHash and (self.gpsLat and self.gpsLong):
# Geohash.encode(self.gpsLat, self.gpsLong)
return super(DataLbc, self).save(*args, **kwargs)
def toJson(self):
ret = {}
ret["Created"] = str(self.created)
ret["Modified"] = str(self.modified)
ret["Used"] = self.used
ret["Raw"] = self.raw
return ret
Here is the way that i send it to my golang server :
from RawOffer.models import Data
while True:
try :
for data in Data.objects.all()[:10]:
requests.post("http://127.0.0.1:8087/convert/",json=data.toJson())
except Exception as e:
print(e)
time.sleep(5)
Now my golang server :
package main
import (
"database/sql"
"encoding/json"
"fmt"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
"strings"
"gopkg.in/guregu/null.v3"
)
type RawOffer struct {
RawOfferData string `json:"Raw"`
Modified null.Time `json:"Modified"`
Created null.Time `json:"Created"`
}
func convertLbc(c *gin.Context) {
var rawOffer RawOffer
c.BindJSON(&rawOffer)
fmt.Println(rawOffer.Created)
var err error
s := strings.Split(rawOffer.RawOfferData, `"ads": `)
s2 := `{ "ads": ` + s[1]
result := strings.Replace(s2, `, "status": "ready"}]`, ``, -1)
//fmt.Println(result)
var rawOfferLbc RawOfferLbc
if err = json.Unmarshal([]byte(result), &rawOfferLbc); err != nil {
fmt.Println(result)
panic(err)
}
}
var db *sqlx.DB
func main() {
var err error
fmt.Println("begin")
r := gin.Default()
r.Use(cors.Default())
r.POST("/convert/",convert)
r.Run((":8087"))
}
but for Created and modified when i try to receive it i have {0001-01-01 00:00:00 +0000 UTC false}
How to serialize a django datetime to send it in json and how to get it in golang?
My main goal so is to send an object with the date from the django app to a microservice in golang. So I need to serialize the django date and I need to write a lot of text because stackoverflow won't let me post my program if i don't write enough text ...
Regards
Here is a working example related to the question that could be helpful to someone in the future.
server.go
package main
import (
"fmt"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/jmoiron/sqlx"
"gopkg.in/guregu/null.v3"
"strconv"
)
type RawOffer struct {
Used_f bool `json:"Used_f"`
Used_t bool `json:"Used_t"`
RawOfferData string `json:"Raw"`
Modified null.Time `json:"Modified"`
Created null.Time `json:"Created"`
}
func convert(c *gin.Context) {
var rawOffer RawOffer
c.BindJSON(&rawOffer)
fmt.Println(`Used_f = ` + strconv.FormatBool(rawOffer.Used_f))
fmt.Println(`Used_t = ` + strconv.FormatBool(rawOffer.Used_t))
fmt.Println(`RawOfferData = `, rawOffer.RawOfferData)
fmt.Println(`Modified = `, rawOffer.Modified)
fmt.Println(`Created = `, rawOffer.Created)
}
var db *sqlx.DB
func main() {
fmt.Println("begin")
r := gin.Default()
r.Use(cors.Default())
r.POST("/convert/", convert)
r.Run((":8087"))
}
test.py
import requests
import json
import datetime
def default(o):
if isinstance(o, (datetime.date, datetime.datetime)):
return o.isoformat() + 'Z'
try :
data = dict(
Created = datetime.datetime.utcnow(),
Modified = datetime.datetime.utcnow(),
Used_f = False,
Used_t = True,
Raw = 'some raw data here',
)
datastr = json.dumps(data, default=default)
print(datastr)
requests.post("http://127.0.0.1:8087/convert/", datastr)
except Exception as e:
print(e)
Log on test.py:
$ python test.py
{"Created": "2019-06-09T15:48:38.978230Z", "Modified": "2019-06-09T15:48:38.978689Z", "Used_f": false, "Used_t": true, "Raw": "some raw data here"}
Log on Server:
begin
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] POST /convert/ --> main.convert (4 handlers)
[GIN-debug] Listening and serving HTTP on :8087
Used_f = false
Used_t = true
RawOfferData = some raw data here
Modified = {2019-06-09 15:48:38.978689 +0000 UTC true}
Created = {2019-06-09 15:48:38.97823 +0000 UTC true}
[GIN] 2019/06/09 - 11:48:39 |[97;42m 200 [0m| 16.979399ms | 127.0.0.1 |[97;46m POST [0m /convert/
by default django datetime field had timezone support. you need think other way . or you can use django-extentions . this third party package comes with a created and modified datetime field which is don't record timezone, use those two field you will get output like {0001-01-01 00:00:00 }
I've used a dirty trick to serialize the date :
def toJson(self):
ret = {}
date_handler = lambda obj: (
obj.isoformat()
if isinstance(obj, (datetime.datetime, datetime.date))
else None
)
ret["Created"] = str(json.dumps(self.created, default=date_handler)).replace("\"","")
ret["Modified"] = str(json.dumps(self.modified, default=date_handler)).replace("\"","")
ret["Used"] = self.used
ret["Raw"] = self.raw
return ret
I hope someone will find a better solution
I didn't get an answer to my original question, but I've tweaked my code and I'm leaving this here in case anyone is trying to figure this out in the future. I'm new to Python, but this worked. This is a Python script I created for a Lambda function that checks the users in your AWS account and publishes a notification to an SNS topic. I've scheduled a CloudWatch rule with a cron expression to run it every day.
import boto3, json, time, datetime, sys, re
iam_client = boto3.client('iam')
sns_client = boto3.client('sns')
users = iam_client.list_users()
user_list = []
for key in users['Users']:
user_list = key['UserName']
accesskeys = iam_client.list_access_keys(UserName=key['UserName'])
for items in user_list.split('\n'):
for key in accesskeys['AccessKeyMetadata']:
accesskeydate = accesskeys['AccessKeyMetadata'][0]['CreateDate']
accesskeydate = accesskeydate.strftime("%Y-%m-%d %H:%M:%S")
currentdate = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
accesskeyd = time.mktime(datetime.datetime.strptime(accesskeydate, "%Y-%m-%d %H:%M:%S").timetuple())
currentd = time.mktime(datetime.datetime.strptime(currentdate, "%Y-%m-%d %H:%M:%S").timetuple())
active_days = (currentd - accesskeyd)/60/60/24
message = (key['UserName'],int(round(active_days))),
message = re.sub(r'[^a-zA-Z0-9 ]', "", str(message))
message = re.sub(r' ', ' is ', str(message))
if active_days >= 90:
sns_client.publish(
TopicArn='arn:aws:sns:us-west-2:xxxxxxxxxxxxx:topic-name',
Subject='User with Old Access Key Detected',
Message="The access key for " + str(message) + " days old. This user access key should be replaced ASAP.",
)
I'm using both the boto3 and warrant libraries to try to get a device authenticated to skip multi-factor authentication after it's been recognized. I've got through a user/password auth but can't seem to figure out the right way to authenticate the device. The following is my code:
from warrant import aws_srp
from warrant.aws_srp import AWSSRP
import boto3
client = boto3.client('cognito-idp')
import datetime
username='xxx'
password='xxx'
client_id='xxx'
aws = AWSSRP(username=username, password=password, pool_id='xxx',
client_id=client_id, client=client)
auth_init = client.initiate_auth(
AuthFlow='USER_SRP_AUTH',
AuthParameters={
'USERNAME': username,
'SRP_A': aws_srp.long_to_hex(aws.large_a_value),
},
ClientId=client_id,
)
cr = aws.process_challenge(auth_init['ChallengeParameters'])
response = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName=auth_init['ChallengeName'],
ChallengeResponses=cr
)
response = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName='SMS_MFA',
Session=response['Session'],
ChallengeResponses={
'USERNAME': username,
'SMS_MFA_CODE':'xxx'
}
)
response_dev = client.confirm_device(
AccessToken=response['AuthenticationResult']['AccessToken'],
DeviceKey=response['AuthenticationResult']['NewDeviceMetadata']['DeviceKey'],
DeviceSecretVerifierConfig={
"PasswordVerifier": aws_srp.long_to_hex(aws.large_a_value),
"Salt": aws_srp.long_to_hex(aws.small_a_value)
}
)
response = client.update_device_status(
AccessToken=response['AuthenticationResult']['AccessToken'],
DeviceKey=device,
DeviceRememberedStatus='remembered'
)
Then on a clean session, do:
device='xxx'
auth_init = client.initiate_auth(
AuthFlow='USER_SRP_AUTH',
AuthParameters={
'USERNAME': username,
'SRP_A': aws_srp.long_to_hex(aws.large_a_value),
'DEVICE_KEY':device
},
ClientId=client_id,
)
cr = aws.process_challenge(auth_init['ChallengeParameters'])
cr['DEVICE_KEY'] = device
response = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName='DEVICE_SRP_AUTH',
ChallengeResponses={
'USERNAME': username,
'SRP_A': aws_srp.long_to_hex(aws.large_a_value),
'DEVICE_KEY': device,
'TIMESTAMP': datetime.datetime.utcnow().strftime( "%a %b %d %H:%M:%S UTC %Y").upper()
}
)
challenge_params = response['ChallengeParameters']
challenge_params['USER_ID_FOR_SRP'] = challenge_params['USERNAME']
cr2 = aws.process_challenge(challenge_params)
response2 = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName=response['ChallengeName'],
ChallengeResponses={
'USERNAME': username,
'PASSWORD_CLAIM_SIGNATURE': cr2['PASSWORD_CLAIM_SIGNATURE'],
'PASSWORD_CLAIM_SECRET_BLOCK': response['ChallengeParameters']['SECRET_BLOCK'],
'DEVICE_KEY': device,
'TIMESTAMP': datetime.datetime.utcnow().strftime( "%a %b %d %H:%M:%S UTC %Y").upper()
}
)
Everything seems to work properly until the last respond_to_auth_challenge which results in:
botocore.errorfactory.NotAuthorizedException: An error occurred (NotAuthorizedException) when calling the RespondToAuthChallenge operation: Incorrect username or password.
Should I be using a different User/pass for a DEVICE_PASSWORD_VERIFIER challenge that I haven't included? The documentation is a bit light, just saying:
DEVICE_PASSWORD_VERIFIER: Similar to PASSWORD_VERIFIER, but for devices only. source
The reason your solution did not work is because in order for device verifier and salt to work, they have to be calculated based on device_group_key and device_key. Also calculation of a challenge response for device authentication differs from the standard password SRP flow. Here is how it worked out for me:
First, confirm and remember the device (Amazon Cognito Identity SDK for JavaScript source):
from warrant import aws_srp
from warrant.aws_srp import AWSSRP
import boto3
import base64
import os
def generate_hash_device(device_group_key, device_key):
# source: https://github.com/amazon-archives/amazon-cognito-identity-js/blob/6b87f1a30a998072b4d98facb49dcaf8780d15b0/src/AuthenticationHelper.js#L137
# random device password, which will be used for DEVICE_SRP_AUTH flow
device_password = base64.standard_b64encode(os.urandom(40)).decode('utf-8')
combined_string = '%s%s:%s' % (device_group_key, device_key, device_password)
combined_string_hash = aws_srp.hash_sha256(combined_string.encode('utf-8'))
salt = aws_srp.pad_hex(aws_srp.get_random(16))
x_value = aws_srp.hex_to_long(aws_srp.hex_hash(salt + combined_string_hash))
g = aws_srp.hex_to_long(aws_srp.g_hex)
big_n = aws_srp.hex_to_long(aws_srp.n_hex)
verifier_device_not_padded = pow(g, x_value, big_n)
verifier = aws_srp.pad_hex(verifier_device_not_padded)
device_secret_verifier_config = {
"PasswordVerifier": base64.standard_b64encode(bytearray.fromhex(verifier)).decode('utf-8'),
"Salt": base64.standard_b64encode(bytearray.fromhex(salt)).decode('utf-8')
}
return device_password, device_secret_verifier_config
client = boto3.client('cognito-idp')
username='xxx'
password='xxx'
client_id='xxx'
client_secret='xxx'
pool_id='xxx'
# 1. Login with the password via standard SRP flow
aws = AWSSRP(username=username, password=password, pool_id=pool_id,
client_id=client_id, client_secret=client_secret, client=client)
auth_init = client.initiate_auth(
AuthFlow='USER_SRP_AUTH',
AuthParameters=aws.get_auth_params(),
ClientId=client_id,
)
cr = aws.process_challenge(auth_init['ChallengeParameters'])
response = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName=auth_init['ChallengeName'],
ChallengeResponses=cr
)
# 2. Get device_key and device_group_key returned after successful login
device_key = response['AuthenticationResult']['NewDeviceMetadata']['DeviceKey']
device_group_key = response['AuthenticationResult']['NewDeviceMetadata']['DeviceGroupKey']
# 3. Generate random device password, device salt and verifier
device_password, device_secret_verifier_config = generate_hash_device(device_group_key, device_key)
response_dev = client.confirm_device(
AccessToken=response['AuthenticationResult']['AccessToken'],
DeviceKey=device_key,
DeviceSecretVerifierConfig=device_secret_verifier_config,
DeviceName='some_device_name'
)
# 4. Remember the device
response_dev_upd = client.update_device_status(
AccessToken=response['AuthenticationResult']['AccessToken'],
DeviceKey=device_key,
DeviceRememberedStatus='remembered'
)
Then on a clean session you can login using device credentials (Amazon Cognito Identity SDK for JavaScript source):
import re
import datetime
import base64
import hmac
import hashlib
import boto3
from warrant import aws_srp
from warrant.aws_srp import AWSSRP
class AWSSRPDEV(AWSSRP):
# source: https://github.com/amazon-archives/amazon-cognito-identity-js/blob/6b87f1a30a998072b4d98facb49dcaf8780d15b0/src/CognitoUser.js#L498
def __init__(self, username, device_group_key, device_key, device_password,
client_id, client, region=None, client_secret=None):
self.username = username
self.device_group_key = device_group_key
self.device_key = device_key
self.device_password = device_password
self.client_id = client_id
self.client_secret = client_secret
self.client = client or boto3.client('cognito-idp', region_name=region)
self.big_n = aws_srp.hex_to_long(aws_srp.n_hex)
self.g = aws_srp.hex_to_long(aws_srp.g_hex)
self.k = aws_srp.hex_to_long(aws_srp.hex_hash('00' + aws_srp.n_hex + '0' + aws_srp.g_hex))
self.small_a_value = self.generate_random_small_a()
self.large_a_value = self.calculate_a()
def get_auth_params(self):
auth_params = super(AWSSRPDEV, self).get_auth_params()
auth_params['DEVICE_KEY'] = self.device_key
return auth_params
def get_device_authentication_key(self, device_group_key, device_key, device_password, server_b_value, salt):
u_value = aws_srp.calculate_u(self.large_a_value, server_b_value)
if u_value == 0:
raise ValueError('U cannot be zero.')
username_password = '%s%s:%s' % (device_group_key, device_key, device_password)
username_password_hash = aws_srp.hash_sha256(username_password.encode('utf-8'))
x_value = aws_srp.hex_to_long(aws_srp.hex_hash(aws_srp.pad_hex(salt) + username_password_hash))
g_mod_pow_xn = pow(self.g, x_value, self.big_n)
int_value2 = server_b_value - self.k * g_mod_pow_xn
s_value = pow(int_value2, self.small_a_value + u_value * x_value, self.big_n)
hkdf = aws_srp.compute_hkdf(bytearray.fromhex(aws_srp.pad_hex(s_value)),
bytearray.fromhex(aws_srp.pad_hex(aws_srp.long_to_hex(u_value))))
return hkdf
def process_device_challenge(self, challenge_parameters):
username = challenge_parameters['USERNAME']
salt_hex = challenge_parameters['SALT']
srp_b_hex = challenge_parameters['SRP_B']
secret_block_b64 = challenge_parameters['SECRET_BLOCK']
# re strips leading zero from a day number (required by AWS Cognito)
timestamp = re.sub(r" 0(\d) ", r" \1 ",
datetime.datetime.utcnow().strftime("%a %b %d %H:%M:%S UTC %Y"))
hkdf = self.get_device_authentication_key(self.device_group_key,
self.device_key,
self.device_password,
aws_srp.hex_to_long(srp_b_hex),
salt_hex)
secret_block_bytes = base64.standard_b64decode(secret_block_b64)
msg = bytearray(self.device_group_key, 'utf-8') + bytearray(self.device_key, 'utf-8') + \
bytearray(secret_block_bytes) + bytearray(timestamp, 'utf-8')
hmac_obj = hmac.new(hkdf, msg, digestmod=hashlib.sha256)
signature_string = base64.standard_b64encode(hmac_obj.digest())
response = {'TIMESTAMP': timestamp,
'USERNAME': username,
'PASSWORD_CLAIM_SECRET_BLOCK': secret_block_b64,
'PASSWORD_CLAIM_SIGNATURE': signature_string.decode('utf-8'),
'DEVICE_KEY': self.device_key}
if self.client_secret is not None:
response.update({
"SECRET_HASH":
self.get_secret_hash(username, self.client_id, self.client_secret)})
return response
client = boto3.client('cognito-idp')
username='xxx'
client_id='xxx'
client_secret='xxx'
device_key = 'xxx'
device_group_key = 'xxx'
device_password = 'xxx'
aws_dev = AWSSRPDEV(username=username,
device_group_key=device_group_key, device_key=device_key, device_password=device_password,
client_id=client_id, client_secret=client_secret, client=client)
# Note that device auth flow doesn't start with client.initiate_auth(),
# but rather with client.respond_to_auth_challenge() straight away
response_auth = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName='DEVICE_SRP_AUTH',
ChallengeResponses=aws_dev.get_auth_params()
)
cr = aws_dev.process_device_challenge(response_auth['ChallengeParameters'])
response_verifier = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName='DEVICE_PASSWORD_VERIFIER',
ChallengeResponses=cr
)
Note that in my case Cognito client does have a client_secret, however the code above should potentially work if it doesn't.
To make things a little bit more consice you can use Pycognito python library
So in case you want to pass sms mfs (or software token mfs) challenge:
from pycognito import Cognito
from pycognito.exceptions import SoftwareTokenMFAChallengeException, SMSMFAChallengeException
client_id = 'client_id'
user_pool_id = 'pool_id'
username = 'username'
password = 'password'
user = Cognito(user_pool_id,client_id, username=username)
try:
user.authenticate(password)
except SoftwareTokenMFAChallengeException:
code = input("Enter the code from your authenticator app: ")
user.respond_to_software_token_mfa_challenge(code)
except SMSMFAChallengeException:
code = input("Enter the SMS code: ")
user.respond_to_sms_mfa_challenge(code)
print(f"My access token: {user.access_token}")