Skip to content

Commit

Permalink
Adding cli option to attach IAM policy to user (#494)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #494

Adding cli option to attach IAM policy to user

Reviewed By: gitfish77

Differential Revision: D32940773

fbshipit-source-id: 90139fcc44d90d8b005c5221548186202bd6ad13
  • Loading branch information
ankushksingh authored and facebook-github-bot committed Jan 4, 2022
1 parent ca5eb4a commit bd5d6f2
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 32 deletions.
38 changes: 38 additions & 0 deletions fbpcs/infra/cloud_bridge/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ def main():
aws_create_iam_policy_parser_arguments(aws_create_parser)
aws_destroy_iam_policy_parser_arguments(aws_destroy_parser)

aws_attach_iam_policy_parser_arguments(aws_create_parser)
aws_detach_iam_policy_parser_arguments(aws_destroy_parser)

cli_args = cli_parser.parse_args()

if cli_args.platform == "aws":
Expand Down Expand Up @@ -135,6 +138,22 @@ def aws_create_iam_policy_parser_arguments(aws_parser: argparse):
)


def aws_attach_iam_policy_parser_arguments(aws_parser: argparse):
iam_policy_command_group = aws_parser.add_argument_group(
"iam_policy_attach", "Arguments to attach IAM policy to the user"
)

iam_policy_command_group.add_argument(
"--attach_iam_policy", action="store_true", help="Attaches IAM policy to a user"
)
iam_policy_command_group.add_argument(
"--iam_policy_name", type=str, required=False, help="Policy to be attached"
)
iam_policy_command_group.add_argument(
"--iam_user_name", type=str, required=False, help="Attach policy to user"
)


def aws_destroy_iam_user_parser_arguments(aws_parser: argparse):
iam_user_command_group = aws_parser.add_argument_group(
"iam_user", "Arguments to delete iam user"
Expand Down Expand Up @@ -163,5 +182,24 @@ def aws_destroy_iam_policy_parser_arguments(aws_parser: argparse):
)


def aws_detach_iam_policy_parser_arguments(aws_parser: argparse):
iam_policy_command_group = aws_parser.add_argument_group(
"iam_policy_detach", "Arguments to detach IAM policy to the user"
)

iam_policy_command_group.add_argument(
"--detach_iam_policy", action="store_true", help="Detaches IAM policy to a user"
)
iam_policy_command_group.add_argument(
"--iam_policy_name",
type=str,
required=False,
help="Policy that is to be detached",
)
iam_policy_command_group.add_argument(
"--iam_user_name", type=str, required=False, help="Detach policy from user"
)


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def __init__(
self.iam = boto3.client("iam")

def create_user(self, user_name: str):
self.log.info(f"Creating user {user_name}")
try:
self.iam.create_user(UserName=user_name)
self.log.info(f"Created user with user name {user_name}")
Expand All @@ -105,6 +106,7 @@ def create_user(self, user_name: str):
)

def delete_user(self, user_name: str):
self.log.info(f"Deleting user {user_name}")
try:
self.iam.delete_user(UserName=user_name)
self.log.info(f"Deleted user with user name {user_name}")
Expand All @@ -122,6 +124,7 @@ def delete_user(self, user_name: str):
def create_policy(
self, policy_name: str, policy_params: PolicyParams, user_name: str = None
):
self.log.info(f"Adding policy {policy_name}")

# directly reading the json file from iam_policies folder
# TODO: pass the policy to be added from cli.py when we need more granular control
Expand Down Expand Up @@ -149,6 +152,7 @@ def create_policy(
self.log.error(f"Unexpected error occurred in policy {policy_name}")

def delete_policy(self, policy_name: str):
self.log.info(f"Deleting policy {policy_name}")
policy_arn = self.POLICY_ARN.format(self.account_id, policy_name)
try:
self.iam.delete_policy(PolicyArn=policy_arn)
Expand All @@ -160,6 +164,21 @@ def delete_policy(self, policy_name: str):
self.log.error(f"Unexpected error occurred in deleting policy: {error}")

def attach_user_policy(self, policy_name: str, user_name: str):
self.log.info(f"Attaching policy {policy_name} to user {user_name}")

current_policies = self.list_policies()
current_users = self.list_users()

if policy_name not in current_policies:
self.log.error(f"Policy {policy_name} is not present for this AWS account")
self.log.error("Please check policy name or add a new policy")
raise Exception(f"Policy {policy_name} not found")

if user_name not in current_users:
self.log.error(f"User {user_name} is not present for this AWS account")
self.log.error("Please check user name or add a new user")
raise Exception(f"User {user_name} not found")

policy_arn = self.POLICY_ARN.format(self.account_id, policy_name)
try:
self.iam.attach_user_policy(UserName=user_name, PolicyArn=policy_arn)
Expand All @@ -172,6 +191,21 @@ def attach_user_policy(self, policy_name: str, user_name: str):
)

def detach_user_policy(self, policy_name: str, user_name: str):
self.log.info(f"Detaching policy {policy_name} from the user {user_name}")

current_policies = self.list_policies()
current_users = self.list_users()

if policy_name not in current_policies:
self.log.error(f"Policy {policy_name} is not present for this AWS account")
self.log.error("Please check policy name or add a new policy")
raise Exception(f"Policy {policy_name} not found")

if user_name not in current_users:
self.log.error(f"User {user_name} is not present for this AWS account")
self.log.error("Please check user name or add a new user")
raise Exception(f"User {user_name} not found")

policy_arn = self.POLICY_ARN.format(self.account_id, policy_name)
try:
self.iam.detach_user_policy(UserName=user_name, PolicyArn=policy_arn)
Expand All @@ -184,15 +218,28 @@ def detach_user_policy(self, policy_name: str, user_name: str):
)

def list_policies(self):
iam = boto3.client("iam")
paginator = iam.get_paginator("list_policies")
for response in paginator.paginate(Scope="Local"):
for policy in response["Policies"]:
self.log.info(
f"Policy Name: {policy['PolicyName']} ARN: {policy['Arn']}"
)
policy_name_list = []
try:
response = self.iam.list_policies().get("Policies", [])
for policy_dict in response:
policy_name_list.append(policy_dict["PolicyName"])
except ClientError as error:
self.log.error(f"Failed to list policies: {error}")
return policy_name_list

def create_access_key(self, policy_name: str, user_name: str):
def list_users(self):
user_name_list = []
try:
response = self.iam.list_users().get("Users", [])
for users_dict in response:
user_name_list.append(users_dict["UserName"])
except ClientError as error:
self.log.error(f"Failed to list users: {error}")
return user_name_list

def create_access_key(self, user_name: str):
self.log.info(f"Creating access and secret keys for user {user_name}.")
self.log.info("Access and secrect keys will not be printed in this log file.")
try:
response = self.iam.create_access_key(UserName=user_name)
self.log.info(f"Creating access and secret key for the user {user_name}")
Expand All @@ -204,13 +251,13 @@ def create_access_key(self, policy_name: str, user_name: str):
print(f"Access Key = {access_key}")
print(f"Secret Key = {secret_key}")
print(f"User = {user_name}")
print(f"Policy = {policy_name}")
except ClientError as error:
self.log.error(
f"Error in generating access and secret for user {user_name}: {error}"
)

def delete_access_key(self, user_name: str, access_key: str):
self.log.info(f"Deleting access and secret keys for user {user_name}.")
try:
self.iam.delete_access_key(UserName=user_name, AccessKeyId=access_key)
self.log.info(f"Deleting access and secret key for the user {user_name}")
Expand Down Expand Up @@ -254,45 +301,29 @@ def read_json_file(
return json_data

def create_user_workflow(self, user_name: str):
policy_name = f"{user_name}_policy"

self.log.info(
f"""Cli to create user is triggered. Following actions will be performed
1. User {user_name} will be created
2. Policy {policy_name} will be created and attached to {user_name}
3. Access and security keys for {user_name} will be created
2. Access and security keys for {user_name} will be created
"""
)

# create user
self.create_user(user_name=user_name)

# create policy
self.create_policy(policy_name=policy_name, user_name=user_name)

# attach policy to the user
self.attach_user_policy(policy_name=policy_name, user_name=user_name)

# generate access and secret keys
self.create_access_key(policy_name=policy_name, user_name=user_name)
self.create_access_key(user_name=user_name)
self.log.info("Creation operation completed.")

def delete_user_workflow(self, user_name: str):
policy_name = f"{user_name}_policy"

self.log.info(
f"""Cli to create user is triggered. Following actions will be performed
1. User {user_name} will be deleted
2. Policy {policy_name} will be deleted and detached from {user_name}
3. All access and security keys of {user_name} will be deleted
2. All access and security keys of {user_name} will be deleted
"""
)

# detach policy from the user
self.detach_user_policy(policy_name=policy_name, user_name=user_name)

# delete policy from the aws account
self.delete_policy(policy_name=policy_name)

# delete all the access keys for the user
access_key_list = self.list_access_keys(user_name=user_name)
for access_key in access_key_list:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,18 @@ def create(self):
if self.cli_args.add_iam_user:
if self.cli_args.user_name is None:
raise Exception(
"Need username to add user. Please add username using --user_name argument in cli.py"
"Need username to add user. Please add username using"
" --user_name argument in cli.py"
)
self.aws_deployment_helper_obj.create_user_workflow(
user_name=self.cli_args.user_name
)

if self.cli_args.add_iam_policy:
if self.cli_args.policy_name is None:
if self.cli_args.policy_name is None or self.cli_args.region is None:
raise Exception(
"Need policy name to add IAM policy. Please add username using --policy_name argument in cli.py"
"Need policy name to add IAM policy. Please add username using"
" --policy_name argument in cli.py"
)
policy_params = PolicyParams(
firehose_stream_name=self.cli_args.firehose_stream_name,
Expand All @@ -47,6 +49,20 @@ def create(self):
policy_name=self.cli_args.policy_name, policy_params=policy_params
)

if self.cli_args.attach_iam_policy:
if (
self.cli_args.iam_policy_name is None
or self.cli_args.iam_user_name is None
):
raise Exception(
"Need username and policy_name to attach policy to user. Please use"
" --user_name and --policy_name arguments in cli.py"
)
self.aws_deployment_helper_obj.attach_user_policy(
policy_name=self.cli_args.iam_policy_name,
user_name=self.cli_args.iam_user_name,
)

def destroy(self):
if self.cli_args.delete_iam_user:
self.aws_deployment_helper_obj.delete_user_workflow(
Expand All @@ -57,3 +73,16 @@ def destroy(self):
self.aws_deployment_helper_obj.delete_policy(
policy_name=self.cli_args.policy_name
)
if self.cli_args.detach_iam_policy:
if (
self.cli_args.iam_policy_name is None
or self.cli_args.iam_user_name is None
):
raise Exception(
"Need username and policy_name to detach policy to user. Please use"
" --user_name and --policy_name arguments in cli.py"
)
self.aws_deployment_helper_obj.detach_user_policy(
policy_name=self.cli_args.iam_policy_name,
user_name=self.cli_args.iam_user_name,
)

0 comments on commit bd5d6f2

Please sign in to comment.