How to disable a user's password in AWS using boto3 - amazon-web-services

I am auditing user passwords in AWS using boto3 and I'm not finding a way to accomplish the following CIS Benchmark: "Ensure credentials (with password enabled) unused for 90 days or greater are disabled."
I have the code to pull the password age and to pull the last time the password was used, but I do not find anything to make inactive a password.
For access keys (but not passwords), we have the following:
client = session.client('iam')
... (get user and keyid) ...
last_used = client.get_access_key_last_used(AccessKeyId=keyid)
... (determine the age of the key) ...
if age >= 90:
client.update_access_key(AccessKeyId=keyid, Status='Inactive', UserName=user)
Does anyone have any pointers?

delete_login_profile is the one you should use if you want to delete the password for the specified IAM user, which terminates the user's ability to access AWS services through the AWS Management Console.
However to prevent all user access (including CLI and API access) you must also either make any access keys inactive or delete them.
From Boto3 Documentation:
Warning
Deleting a user's password does not prevent a user from accessing AWS
through the command line interface or the API. To prevent all user
access you must also either make any access keys inactive or delete
them. For more information about making keys inactive or deleting
them, see UpdateAccessKey and DeleteAccessKey.

If you want to change the password, you should use update_login_profile boto3 API. If you want to disable the password, you need to use delete_login_profile.
boto3 documentation for update_login_profile can be found here.
boto3 documentation for delete_login_profile can be found here.

Thanks to the responders, delete_login_profile followed by a password reset using create_login_profile is exactly what I needed. I saw it in the docs, but "delete" just sounded too scary.
def getPassword(client, user):
''' get the password data from aws '''
try:
response = client.get_login_profile(UserName=user)
return response
except client.exceptions.NoSuchEntityException as e:
print(e)
return ''
# setup the client handler
client = session.client('iam')
# set the user
user = 'some.user'
# if the user has a password, execute this code block
if getPassword(client=client, user=user):
... code to test the password age here ...
... if it's too old, then ...
# remove the login_profile/password/ability to use the Console
client.delete_login_profile(UserName=user)
# set the new password
passwd = raw_input('Enter New Password: ')
# create the new login_profile with the new password and force the user to change the password on the next login
client.create_login_profile(UserName=user, Password=passwd, PasswordResetRequired=True)

Related

Google API user creation with service account

I'm trying to create a user using Googles Directory API and a service account. However I'm getting the error
googleapiclient.errors.HttpError: <HttpError 403 when requesting https://admin.googleapis.com/admin/directory/v1/users?alt=json returned "Not Authorized to access this resource/api". Details: "Not Authorized to access this resource/api">
I've created a service account on the Google Console and allowed Domain wide delegation. It also says the Admin SDK API is enabled for my project. However I can't seem to create a user. The documentation is confusing me slightly. Here is my implementation
def create_googleuser(content, randpass):
''' This function creates a Google Apps account for a user passing webhook contents and password as arguments '''
# Get User info from Webhook and store them in variables
firstname = get_firstname(content)
secondname = get_secondname(content)
emailaddress = firstname + "." + secondname + "#example.com"
# Connect to google API
userscope = ['https://www.googleapis.com/auth/admin.directory.user']
service_account_credentials = ('serviceaccountcredentials.json')
credentials = service_account.Credentials.from_service_account_file(service_account_credentials, scopes=userscope)
userservice = googleapiclient.discovery.build('admin', 'directory_v1', credentials=credentials)
# Create a user dictionary with user details
userinfo = {"primaryEmail": emailaddress,"name":{"givenName":firstname,"familyName":secondname},"password":randpass}
print (emailaddress)
# Create user through googleAPI
userservice.users().insert(body = userinfo).execute()
I'm thinking that my implementation is wrong rather than the permissions as the serviceaccountcredentials.json should have the correct permissions. Any suggestions?
There are two possibilities for getting this error.
If the API method requires an impersonated user to be used.
If the impersonated user has not the relevant service enabled.
Solution for case 1:
Follow the documentation to impersonate a user account.
Solution for case 2:
In the Admin console, open user information and check that the user is not suspended.
Open the "Apps" panel and check that the relevant service is "On".
May be caused by a user not having a license which allows access to the service (Cloud Identity instead of Google Workspace), or a user being in an organizational unit which has the service disabled.
Also this link might be helpful.
Thanks for the input. You were both correct to a point. Basically there were two issues. The service account user needs to be delegated domain administrator privileges that require domain admin actions, domain wide delegation isn't enough. Also the domain scope needed to be broader in the Admin console and the scope definition within the code. There is github issue open which helped here:
https://github.com/googleapis/google-api-nodejs-client/issues/1884
My working code looks like this
def create_googleuser(content, randpass):
''' This function creates a Google Apps account for a user passing webhook contents and password as arguments '''
# Get User info from Webhook and store them in variables
username = get_username(content)
firstname = get_firstname(content)
secondname = get_secondname(content)
emailaddress = firstname + "." + secondname + "#example.com"
# Connect to google API
userscope = ['https://www.googleapis.com/auth/admin.directory.user', 'https://www.googleapis.com/auth/admin.directory.user.security']
service_account_credentials = ('serviceaccountcredentials.json')
credentials = service_account.Credentials.from_service_account_file(service_account_credentials, scopes=userscope)
delegated_credentials = credentials.with_subject('domain.admin#example.com')
userservice = googleapiclient.discovery.build('admin', 'directory_v1', credentials=delegated_credentials)
# Create a user dictionary with user details
userinfo = {"primaryEmail": emailaddress,"name":{"givenName":firstname,"familyName":secondname},"password":randpass}
# Create user through googleAPI
userservice.users().insert(body = userinfo).execute()

How to revoke refresh token on password reset?

I am using django-rest-framework-simplejwt to get access token and refresh token .
The problem is that refresh token is not becoming invalid if I change the password of the user. Basically I can continue to send refresh token and get new access tokens even after user has changed password.
What I would like instead is to ask user to re-submit the username and new password to get a new pair of access and refresh tokens.
How would I accomplish this?
PS: Just because I am curious, shouldn't this be the default behaviour of the library? In what case would we want to retain the refresh token after credentials have changed?
I figured how to get this working.
What I am did is put a signal that tracks if any required parameter has changed. If so, it blacklists all the refresh tokens associated with that user.
Here is the code:
First add 'rest_framework_simplejwt.token_blacklist' in installed apps. Then:
#receiver(signals.pre_save, sender=User)
def revoke_tokens(sender, instance, update_fields, **kwargs):
if not instance._state.adding: #instance._state.adding gives true if object is being created for the first time
existing_user = User.objects.get(pk=instance.pk)
if instance.password != existing_user.password or instance.email != existing_user.email or instance.username != existing_user.username:
# If any of these params have changed, blacklist the tokens
outstanding_tokens = OutstandingToken.objects.filter(user__pk=instance.pk)
# Not checking for expiry date as cron is supposed to flush the expired tokens
# using manage.py flushexpiredtokens. But if You are not using cron,
# then you can add another filter that expiry_date__gt=datetime.datetime.now()
for out_token in outstanding_tokens:
if hasattr(out_token, 'blacklistedtoken'):
# Token already blacklisted. Skip
continue
BlacklistedToken.objects.create(token=out_token)
WHat this code basically does is , gets all outstanding tokens for the user, then adds all of them to blacklist. You can get more info on outstanding/blacklisted tokens here.
https://github.com/davesque/django-rest-framework-simplejwt#blacklist-app

Restore password for FORCE_CHANGE_PASSWORD status

I need to restore or reset user password when his status is FORCE_CHANGE_PASSWORD. This situation happened when user try to restore password using "forgot password" feature and he lost email with temporary password. Now he can't do anything because he don't remember password and he can't reset password again
This code handle forgot password
return CognitoIdentitySP.forgotPassword(params, (err, resp) => {
if (err) { ... }
...
})
And I receive error (in case of FORCE_CHANGE_PASSWORD status)
NotAuthorizedException: User password cannot be reset in the current state.
Is there any way to reset password in such state?
You can use aws-cli to do it. Here is a sample command, replace POOL_ID and EMAIL_ADDRESS accordingly:
aws cognito-idp admin-create-user --user-pool-id <POOL_ID> --username <EMAIL_ADDRESS> --message-action RESEND --profile <AWS_PROFILE>
You can also use the admin-set-user-password command in this situation of the temporary password being lost or expired:
aws cognito-idp admin-set-user-password --user-pool-id <POOL_ID> --username <USERNAME> --password <PASSWORD> --no-permanent
This will set a new temporary password of whatever you set the password to be but importantly will force the user to set a new password as soon as they log in, so security is maintained.
You will need to communicate this to the user but we found this extremely useful when your company's security policies prevent you from being able to run the create user command.
You can call admin create user again with the MessageAction set to RESEND in which case Cognito will resend the invitation message to a user that already exists and reset the expiration limit on the user's account. Set to "SUPPRESS" to suppress sending the message. Only one value can be specified.
When you create a user from the admin or with the admin sdk from the frontend you have to use the authentication flow "USER_PASSWORD_AUTH" previously you should have configured it in the app client:
https://docs.amplify.aws/lib/auth/switch-auth/q/platform/js/

cognito autoConfirmUser and autoVerifyEmail

I have cognito set up with a pre sign up lambda which returns event with following set:
event.response.autoConfirmUser = true;
event.response.autoVerifyEmail = true;
this correctly marks user email and user itself as confirmed in aws console. But while trying to login I get the error "User does not exist." from cognito aws api, any ideas?
Cognito Pool is set up to use "email address" as "username" and this makes following even weirder - I can create new user with the same email address as above and authenticate fine. But what I ending up with are two confirmed users with the same email address!
My use case is relatively simple - I invite users to join via email so the email is confirmed already in a sense, that's why am using pre sign up lambda to mark user and email as confirmed/verified.
Any help greatly appreciated!
Thanks,
Tomek

Aws Cognito- User pools, how to recover / set password for a user when it has no email or phone

Aws Cognito- User pools, how to recover / set password for a user when it has no email or phone.
I am using this on the web for a small business locally.
and want user to use only username.
not use email and phone.
On the verification tab, I leave both checkboxes: phone and email blank.
Then it displays the following red warning.
You have not selected either email or phone number verification,
so your users will not be able to
recover their passwords without contacting you for support.
So it is okay that I want them to contact support.
But I cannot find and API to set their password or recovery by admin.
If users contact me, how can I do it?
At the moment, there is a workaround through the API. Just set an email/phone where you/the admin can receive the one-off confirmation code (eg: support#test.com)
Just tested on an old cognito user pool that for some unknown reason, gets the emailed_verified attribute set to false every now and then (ref).
The User pool has the same configuration: No verification options are enabled.
However, you can ensure the email_verified attribute is ok, through an AWS user with dev credentials.
Example using CLI (tested on aws-cli/1.16.3 Python/2.7.10 Darwin/18.2.0 botocore/1.11.3):
USER=test#test.com
POOL_ID=us_east_1-123
POOL_APP_CLIENT_ID=fake123
# Ensure the email_verified attribute is set to true
# https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/admin-update-user-attributes.html
aws cognito-idp admin-update-user-attributes --user-pool-id $POOL_ID --username $USER --user-attributes Name=email_verified,Value=true
# Check the attribute is set/added if missing
# https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/list-users.html
aws cognito-idp list-users --user-pool-id $POOL_ID --query 'Users[?Username==`$USER`].[*]'
# Run Admin Reset PWD
# https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/admin-reset-user-password.html
aws cognito-idp admin-reset-user-password --user-pool-id <Pool ID> --username <USER>
# The email/phone for the user should get a confirmation code
# Set the new pwd
# https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/confirm-forgot-password.html
aws cognito-idp confirm-forgot-password --confirmation-code <Code> --password <New PWD> --username $USER --client-id $POOL_APP_CLIENT_ID
Basically that means that your users will not have to verify the email or phone number. Those can be auto verified either by writing a lambda function that verifies them or you could verify them on their behalf from the console.
Once the phone number or email are marked as verified, they can be used by users in a forgotPassword flow, they will basically get a code that they can use to reset the password.
You can also reset the user password on their behalf (from the console) which means that users will not be able to login and will have to complete a forgotPassword flow before logging in. Again, phone number and email can be marked as verified from the console.