예제 #1
0
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)
예제 #2
0
 def __init__(self, amazon):
     self.amazon = amazon
     self.documents = AmazonDocuments()
     self.connection = amazon.s3_connection
예제 #3
0
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]
예제 #4
0
 def __init__(self, amazon):
     self.amazon = amazon
     self.documents = AmazonDocuments()
     self.connection = amazon.connection
예제 #5
0
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)
예제 #6
0
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)
예제 #7
0
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]