class AmazonBuckets(AmazonMixin, object): def __init__(self, amazon): self.amazon = amazon self.documents = AmazonDocuments() self.connection = amazon.s3_connection def bucket_info(self, name): """Return what amazon knows about this bucket""" try: return self.connection.get_bucket(name) except boto.exception.S3ResponseError as error: if error.status == 404: return False raise def current_policy(self, bucket): """Return the current policy for this bucket""" try: return bucket.get_policy().decode('utf-8') except boto.exception.S3ResponseError as error: if error.status == 404: return "{}" raise def current_tags(self, bucket): """Return the tags associated with this bucket""" try: return dict( chain.from_iterable([(tag.key, tag.value) for tag in tags] for tags in bucket.get_tags())) except boto.exception.S3ResponseError as error: if error.status == 404: return {} raise def has_bucket(self, name): """Return whether amazon has info about this role""" return bool(self.bucket_info(name)) def create_bucket(self, name, location, permission_document=None, tags=None): """Create a role""" with self.catch_boto_400("Couldn't create bucket", name=name): for _ in self.change("+", "bucket[{0}] ".format(location), name=name): self.connection.create_bucket(name, location=location) # And add our permissions if permission_document: with self.catch_boto_400( "Couldn't add policy", "Bucket {0} - Permission document".format(name), permission_document, bucket=name): for _ in self.change("+", "bucket_policy", bucket=name, document=permission_document): self.bucket_info(name).set_policy(permission_document) def modify_bucket(self, name, location, permission_document, tags): """Modify a bucket""" log.info("Inspecting bucket\tname=%s", name) bucket = self.bucket_info(name) if not bucket: return current_location = bucket.get_location() if current_location != location: raise BadPolicy( "The location of the bucket is wrong. You need to delete and recreate the bucket to have it in your specified location", current=current_location, wanted=location) current_policy = self.current_policy(bucket) changes = list( self.documents.compare_two_documents(current_policy, permission_document)) if changes: with self.catch_boto_400("Couldn't modify policy", "Bucket {0} policy".format(name), permission_document, bucket=name): for _ in self.change("M", "bucket_policy", bucket=name, changes=changes): bucket.set_policy(permission_document) self.modify_bucket_tags(name, bucket, tags) def modify_bucket_tags(self, name, bucket, tags): """Modify the tags on a bucket""" changes = {} new_tags = TagSet() current_tags = self.current_tags(bucket) for tag_name, tag_val in tags.items(): if tag_name in current_tags: if current_tags[tag_name] != tag_val: changes[tag_name] = ("modify", tag_name, current_tags[tag_name], tag_val) elif tag_name not in current_tags: changes[tag_name] = ("create", tag_name, None, tag_val) new_tags.add_tag(tag_name, tag_val) for tag_name in current_tags: if tag_name not in tags: changes[tag_name] = ("delete", tag_name, current_tags[tag_name], None) if changes: if not new_tags: for _ in self.change("D", "bucket_tags", bucket=name, changes=["Delete all tags"]): bucket.delete_tags() else: one_letter = "M" if any( typ in ("modify", "delete") for typ, _, _, _ in changes.values()) else "C" for _ in self.change( one_letter, "bucket_tag", bucket=name, changes=[ "{0} {1} from {2} to {3}".format(*change) for change in changes.values() ]): t = Tags() t.add_tag_set(new_tags) bucket.set_tags(t)
def __init__(self, amazon): self.amazon = amazon self.documents = AmazonDocuments() self.connection = amazon.s3_connection
class AmazonKms(AmazonMixin, object): def __init__(self, amazon, connection): self.amazon = amazon self.documents = AmazonDocuments() self.connection = connection def key_info(self, alias): """Return what amazon knows about this key""" try: return self.connection.describe_key( "alias/{0}".format(alias))["KeyMetadata"] except boto.kms.exceptions.NotFoundException: return False def current_policy(self, key): """Return the current policy for this key""" try: return self.connection.get_key_policy(key["KeyId"], "default")["Policy"] except boto.kms.exceptions.NotFoundException: return "{}" def has_key(self, alias): """Return whether amazon has info about this key""" return bool(self.key_info(alias)) def create_key(self, alias, description, permission_document=None): """Create a key""" with self.catch_boto_400("Couldn't create key", document=permission_document, alias=alias): for _ in self.change("+", "key", alias=alias): key = self.connection.create_key(permission_document, description)["KeyMetadata"] self.connection.create_alias("alias/{0}".format(alias), key["KeyId"]) def modify_key(self, alias, description, permission_document): """Modify a key""" key = self.key_info(alias) if not key: return current_description = key["Description"] if current_description != description: for _ in self.change("M", "key_description", key=alias, description=description): self.connection.update_key_description(key["KeyId"], description) current_policy = self.current_policy(key) changes = list( self.documents.compare_two_documents(current_policy, permission_document)) if changes: with self.catch_boto_400("Couldn't modify policy", "Key {0} policy".format(alias), permission_document, key=alias): for _ in self.change("M", "key_policy", key=alias, changes=changes, description=description): self.connection.put_key_policy(key["KeyId"], 'default', permission_document) def modify_grant(self, alias, description, grant): """Modify grants on a key""" key = self.key_info(alias) if not key: raise BadAlias("Where did the key go?", alias=alias) key_id = key["KeyId"] new_grants = [] current_grants = self.connection.list_grants(key_id)["Grants"] for policy in current_grants: policy["Operations"] = sorted(policy["Operations"]) for policy in grant: nxt = { "GranteePrincipal": policy["grantee"], "RetireePrincipal": policy.get("Retiree"), "Operations": sorted(policy["operations"]), "Constraints": policy.get("constraints"), "GrantTokens": policy.get("grant_tokens") } nxt = dict( (key, val) for key, val in nxt.items() if val is not None) if not any( all(current[key] == val for key, val in nxt.items() if key not in ("GrantId", "IssuingAccount")) for current in current_grants): new_grants.append(policy) for policy in new_grants: for translateable in ("GranteePrincipal", "RetireePrincipal"): if translateable in policy: policy[translateable] = self.user_from_arn( policy[translateable]) for _ in self.change("+", "key_grant", key=alias, grantee=policy["grantee"]): self.connection.create_grant( key_id, policy["grantee"], retiring_principal=policy.get("retiree"), operations=policy["operations"], constraints=policy.get("constraints"), grant_tokens=policy.get("grant_tokens")) def user_from_arn(self, arn): """Convert an arn into the user id""" if arn is None: return user_ids = [ item["user_id"] for item in self.amazon.all_users if item['arn'] == arn ] role_ids = [ item["role_id"] for item in self.amazon.all_roles if item['arn'] == arn ] if user_ids and role_ids or len(user_ids) > 1 or len(role_ids) > 1: raise BadRole("Didn't find a single role id for specified arn", got_roles=role_ids, got_users=user_ids, arn=arn) if role_ids: return role_ids[0] else: return user_ids[0]
def __init__(self, amazon): self.amazon = amazon self.documents = AmazonDocuments() self.connection = amazon.connection
class AmazonRoles(AmazonMixin, object): def __init__(self, amazon): self.amazon = amazon self.documents = AmazonDocuments() self.connection = amazon.connection def split_role_name(self, name): """Split a role name into it's (name, path)""" split = name.split('/') role_name = split[-1] path = '/'.join(split[0:-1]) or None if path and not path.startswith('/'): path = "/{0}".format(path) if path and not path.endswith("/"): path = "{0}/".format(path) return role_name, path def role_info(self, name): """Return what amazon knows about this role""" try: role_name, _ = self.split_role_name(name) return self.connection.get_role(role_name)["get_role_response"]["get_role_result"] except boto.exception.BotoServerError as error: if error.status == 404: return False raise def has_role(self, name): """Return whether amazon has info about this role""" return bool(self.role_info(name)) def info_for_profile(self, name): """Return what roles are attached to this profile if it exists""" profiles = [] role_name, _ = self.split_role_name(name) with self.ignore_boto_404(): with self.catch_boto_400("Couldn't list instance profiles associated with a role", role=role_name): result = self.connection.list_instance_profiles_for_role(role_name) profiles = result["list_instance_profiles_for_role_response"]["list_instance_profiles_for_role_result"]["instance_profiles"] existing_profile = None for profile in profiles: if profile["instance_profile_name"] == name: existing_profile = profile break if existing_profile: if "member" in existing_profile["roles"]: return [existing_profile["roles"]["member"]["role_name"]] else: return [] def make_instance_profile(self, name): """Make an instance profile with this name containing this role""" role_name, _ = self.split_role_name(name) existing_roles_in_profile = self.info_for_profile(role_name) if existing_roles_in_profile is None: try: with self.catch_boto_400("Couldn't create instance profile", instance_profile=role_name): for _ in self.change("+", "instance_profile", profile=role_name): self.connection.create_instance_profile(role_name) except boto.exception.BotoServerError as error: if error.status == 409 and error.code == "EntityAlreadyExists": # I'd rather ignore this conflict, than list all the instance_profiles # Basically, the instance exists but isn't associated with the role pass else: raise if existing_roles_in_profile and any(rl != role_name for rl in existing_roles_in_profile): for role in [rl for rl in existing_roles_in_profile if rl != role_name]: with self.catch_boto_400("Couldn't remove role from an instance profile", profile=role_name, role=role): for _ in self.change("-", "instance_profile_role", profile=role_name, role=role): self.connection.remove_role_from_instance_profile(role_name, role) if not existing_roles_in_profile or not any(rl == role_name for rl in existing_roles_in_profile): with self.catch_boto_400("Couldn't add role to an instance profile", role=role_name, instance_profile=role_name): for _ in self.change("+", "instance_profile_role", profile=role_name, role=role_name): self.connection.add_role_to_instance_profile(role_name, role_name) def create_role(self, name, trust_document, policies=None): """Create a role""" role_name, role_path = self.split_role_name(name) with self.catch_boto_400("Couldn't create role", "{0} trust document".format(name), trust_document, role=name): for _ in self.change("+", "role", role=role_name, document=trust_document): self.connection.create_role(role_name, assume_role_policy_document=trust_document, path=role_path) # And add our permissions if policies: for policy_name, document in policies.items(): if document: with self.catch_boto_400("Couldn't add policy", "{0} - {1} Permission document".format(role_name, policy_name), document, role=role_name, policy_name=policy_name): for _ in self.change("+", "role_policy", role=role_name, policy=policy_name, document=document): self.connection.put_role_policy(role_name, policy_name, document) def modify_role(self, role_info, name, trust_document, policies=LeaveAlone): """Modify a role""" role_name, _ = self.split_role_name(name) if trust_document: changes = list(self.documents.compare_trust_document(role_info, trust_document)) if changes: with self.catch_boto_400("Couldn't modify trust document", "{0} assume document".format(role_name), trust_document, role=role_name): for _ in self.change("M", "trust_document", role=role_name, changes=changes): self.connection.update_assume_role_policy(role_name, trust_document) if policies is LeaveAlone: return elif policies is None: policies = {} unknown = [] with self.catch_boto_400("Couldn't get policies for a role", role=role_name): current_policies = self.current_role_policies(role_name, comparing=[pn for pn in policies]) unknown = [key for key in current_policies if key not in policies] if unknown: log.info("Role has unknown policies that will be disassociated\trole=%s\tunknown=%s", role_name, unknown) for policy in unknown: with self.catch_boto_400("Couldn't delete a policy from a role", policy=policy, role=role_name): for _ in self.change("-", "role_policy", role=role_name, policy=policy): self.connection.delete_role_policy(role_name, policy) for policy, document in policies.items(): if not document: if policy in current_policies: with self.catch_boto_400("Couldn't delete a policy from a role", policy=policy, role=role_name): for _ in self.change("-", "policy", role=role_name, policy=policy): self.connection.delete_role_policy(role_name, policy) else: needed = False changes = None if policy in current_policies: changes = list(self.documents.compare_two_documents(current_policies.get(policy), document)) if changes: log.info("Overriding existing policy\trole=%s\tpolicy=%s", role_name, policy) needed = True else: log.info("Adding policy to existing role\trole=%s\tpolicy=%s", role_name, policy) needed = True if needed: with self.catch_boto_400("Couldn't add policy document", "{0} - {1} policy document".format(role_name, policy), document, role=role_name, policy=policy): symbol = "M" if changes else "+" for _ in self.change(symbol, "role_policy", role=role_name, policy=policy, changes=changes, document=document): self.connection.put_role_policy(role_name, policy, document) def current_role_policies(self, name, comparing): """Get the current policies for some role""" role_name, _ = self.split_role_name(name) with self.catch_boto_400("Couldn't get policies for a role", role=name): policies = self.connection.list_role_policies(role_name)["list_role_policies_response"]["list_role_policies_result"]["policy_names"] found = {} for policy in policies: document = None if policy in comparing: with self.catch_boto_400("Couldn't get policy document for some policy", policy=policy, role=name): doc = self.connection.get_role_policy(role_name, policy)["get_role_policy_response"]["get_role_policy_result"]["policy_document"] document = json.dumps(json.loads(parse.unquote(doc)), indent=2).strip() found[policy] = document return found def remove_role(self, name): """Remove the role if it exists""" role_name, _ = self.split_role_name(name) if self.has_role(role_name): with self.catch_boto_400("Couldn't get policies for a role", role=role_name): current_policies = self.current_role_policies(role_name, comparing=[]) for policy in current_policies: with self.catch_boto_400("Couldn't delete a policy from a role", policy=policy, role=role_name): for _ in self.change("-", "policy", role=role_name, policy=policy): self.connection.delete_role_policy(role_name, policy) with self.catch_boto_400("Couldn't delete a role", role=role_name): for _ in self.change("-", "role", role=role_name): self.connection.delete_role(role_name) else: log.info("Role already deleted\trole=%s", role_name)
class AmazonBuckets(AmazonMixin, object): def __init__(self, amazon): self.amazon = amazon self.documents = AmazonDocuments() self.connection = amazon.s3_connection def bucket_info(self, name): """Return what amazon knows about this bucket""" try: return self.connection.get_bucket(name) except boto.exception.S3ResponseError as error: if error.status == 404: return False raise def current_policy(self, bucket): """Return the current policy for this bucket""" try: return bucket.get_policy().decode('utf-8') except boto.exception.S3ResponseError as error: if error.status == 404: return "{}" raise def current_tags(self, bucket): """Return the tags associated with this bucket""" try: return dict(chain.from_iterable([(tag.key, tag.value) for tag in tags] for tags in bucket.get_tags())) except boto.exception.S3ResponseError as error: if error.status == 404: return {} raise def has_bucket(self, name): """Return whether amazon has info about this role""" return bool(self.bucket_info(name)) def create_bucket(self, name, location, permission_document=None, tags=None): """Create a role""" with self.catch_boto_400("Couldn't create bucket", name=name): for _ in self.change("+", "bucket[{0}] ".format(location), name=name): self.connection.create_bucket(name, location=location) # And add our permissions if permission_document: with self.catch_boto_400("Couldn't add policy", "Bucket {0} - Permission document".format(name), permission_document, bucket=name): for _ in self.change("+", "bucket_policy", bucket=name, document=permission_document): self.bucket_info(name).set_policy(permission_document) def modify_bucket(self, name, location, permission_document, tags): """Modify a bucket""" log.info("Inspecting bucket\tname=%s", name) bucket = self.bucket_info(name) if not bucket: return current_location = bucket.get_location() if current_location != location: raise BadPolicy("The location of the bucket is wrong. You need to delete and recreate the bucket to have it in your specified location", current=current_location, wanted=location) current_policy = self.current_policy(bucket) changes = list(self.documents.compare_two_documents(current_policy, permission_document)) if changes: with self.catch_boto_400("Couldn't modify policy", "Bucket {0} policy".format(name), permission_document, bucket=name): for _ in self.change("M", "bucket_policy", bucket=name, changes=changes): bucket.set_policy(permission_document) self.modify_bucket_tags(name, bucket, tags) def modify_bucket_tags(self, name, bucket, tags): """Modify the tags on a bucket""" changes = {} new_tags = TagSet() current_tags = self.current_tags(bucket) for tag_name, tag_val in tags.items(): if tag_name in current_tags: if current_tags[tag_name] != tag_val: changes[tag_name] = ("modify", tag_name, current_tags[tag_name], tag_val) elif tag_name not in current_tags: changes[tag_name] = ("create", tag_name, None, tag_val) new_tags.add_tag(tag_name, tag_val) for tag_name in current_tags: if tag_name not in tags: changes[tag_name] = ("delete", tag_name, current_tags[tag_name], None) if changes: if not new_tags: for _ in self.change("D", "bucket_tags", bucket=name, changes=["Delete all tags"]): bucket.delete_tags() else: one_letter = "M" if any(typ in ("modify", "delete") for typ, _, _, _ in changes.values()) else "C" for _ in self.change(one_letter, "bucket_tag", bucket=name, changes=["{0} {1} from {2} to {3}".format(*change) for change in changes.values()]): t = Tags() t.add_tag_set(new_tags) bucket.set_tags(t)
class AmazonKms(AmazonMixin, object): def __init__(self, amazon, connection): self.amazon = amazon self.documents = AmazonDocuments() self.connection = connection def key_info(self, alias): """Return what amazon knows about this key""" try: return self.connection.describe_key("alias/{0}".format(alias))["KeyMetadata"] except boto.kms.exceptions.NotFoundException: return False def current_policy(self, key): """Return the current policy for this key""" try: return self.connection.get_key_policy(key["KeyId"], "default")["Policy"] except boto.kms.exceptions.NotFoundException: return "{}" def has_key(self, alias): """Return whether amazon has info about this key""" return bool(self.key_info(alias)) def create_key(self, alias, description, permission_document=None): """Create a key""" with self.catch_boto_400("Couldn't create key", document=permission_document, alias=alias): for _ in self.change("+", "key", alias=alias): key = self.connection.create_key(permission_document, description)["KeyMetadata"] self.connection.create_alias("alias/{0}".format(alias), key["KeyId"]) def modify_key(self, alias, description, permission_document): """Modify a key""" key = self.key_info(alias) if not key: return current_description = key["Description"] if current_description != description: for _ in self.change("M", "key_description", key=alias, description=description): self.connection.update_key_description(key["KeyId"], description) current_policy = self.current_policy(key) changes = list(self.documents.compare_two_documents(current_policy, permission_document)) if changes: with self.catch_boto_400("Couldn't modify policy", "Key {0} policy".format(alias), permission_document, key=alias): for _ in self.change("M", "key_policy", key=alias, changes=changes, description=description): self.connection.put_key_policy(key["KeyId"], 'default', permission_document) def modify_grant(self, alias, description, grant): """Modify grants on a key""" key = self.key_info(alias) if not key: raise BadAlias("Where did the key go?", alias=alias) key_id = key["KeyId"] new_grants = [] current_grants = self.connection.list_grants(key_id)["Grants"] for policy in current_grants: policy["Operations"] = sorted(policy["Operations"]) for policy in grant: nxt = {"GranteePrincipal": policy["grantee"], "RetireePrincipal": policy.get("Retiree"), "Operations": sorted(policy["operations"]), "Constraints": policy.get("constraints"), "GrantTokens": policy.get("grant_tokens")} nxt = dict((key, val) for key, val in nxt.items() if val is not None) if not any(all(current[key] == val for key, val in nxt.items() if key not in ("GrantId", "IssuingAccount")) for current in current_grants): new_grants.append(policy) for policy in new_grants: for translateable in ("GranteePrincipal", "RetireePrincipal"): if translateable in policy: policy[translateable] = self.user_from_arn(policy[translateable]) for _ in self.change("+", "key_grant", key=alias, grantee=policy["grantee"]): self.connection.create_grant(key_id, policy["grantee"], retiring_principal=policy.get("retiree"), operations=policy["operations"], constraints=policy.get("constraints"), grant_tokens=policy.get("grant_tokens")) def user_from_arn(self, arn): """Convert an arn into the user id""" if arn is None: return user_ids = [item["user_id"] for item in self.amazon.all_users if item['arn'] == arn] role_ids = [item["role_id"] for item in self.amazon.all_roles if item['arn'] == arn] if user_ids and role_ids or len(user_ids) > 1 or len(role_ids) > 1: raise BadRole("Didn't find a single role id for specified arn", got_roles=role_ids, got_users=user_ids, arn=arn) if role_ids: return role_ids[0] else: return user_ids[0]