Organizations - implement Policy Type functionality (#3207)
* Add organizations.enable_policy_type * Add organizations.disable_policy_type * Add support for AISERVICES_OPT_OUT_POLICY
This commit is contained in:
parent
3342d49a43
commit
252d679b27
@ -74,3 +74,41 @@ class DuplicatePolicyException(JsonRESTError):
|
|||||||
super(DuplicatePolicyException, self).__init__(
|
super(DuplicatePolicyException, self).__init__(
|
||||||
"DuplicatePolicyException", "A policy with the same name already exists."
|
"DuplicatePolicyException", "A policy with the same name already exists."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PolicyTypeAlreadyEnabledException(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(PolicyTypeAlreadyEnabledException, self).__init__(
|
||||||
|
"PolicyTypeAlreadyEnabledException",
|
||||||
|
"The specified policy type is already enabled.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PolicyTypeNotEnabledException(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(PolicyTypeNotEnabledException, self).__init__(
|
||||||
|
"PolicyTypeNotEnabledException",
|
||||||
|
"This operation can be performed only for enabled policy types.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RootNotFoundException(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(RootNotFoundException, self).__init__(
|
||||||
|
"RootNotFoundException", "You specified a root that doesn't exist."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TargetNotFoundException(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(TargetNotFoundException, self).__init__(
|
||||||
|
"TargetNotFoundException", "You specified a target that doesn't exist."
|
||||||
|
)
|
||||||
|
@ -17,6 +17,10 @@ from moto.organizations.exceptions import (
|
|||||||
AccountAlreadyRegisteredException,
|
AccountAlreadyRegisteredException,
|
||||||
AWSOrganizationsNotInUseException,
|
AWSOrganizationsNotInUseException,
|
||||||
AccountNotRegisteredException,
|
AccountNotRegisteredException,
|
||||||
|
RootNotFoundException,
|
||||||
|
PolicyTypeAlreadyEnabledException,
|
||||||
|
PolicyTypeNotEnabledException,
|
||||||
|
TargetNotFoundException,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -124,6 +128,13 @@ class FakeOrganizationalUnit(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class FakeRoot(FakeOrganizationalUnit):
|
class FakeRoot(FakeOrganizationalUnit):
|
||||||
|
SUPPORTED_POLICY_TYPES = [
|
||||||
|
"AISERVICES_OPT_OUT_POLICY",
|
||||||
|
"BACKUP_POLICY",
|
||||||
|
"SERVICE_CONTROL_POLICY",
|
||||||
|
"TAG_POLICY",
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, organization, **kwargs):
|
def __init__(self, organization, **kwargs):
|
||||||
super(FakeRoot, self).__init__(organization, **kwargs)
|
super(FakeRoot, self).__init__(organization, **kwargs)
|
||||||
self.type = "ROOT"
|
self.type = "ROOT"
|
||||||
@ -141,20 +152,55 @@ class FakeRoot(FakeOrganizationalUnit):
|
|||||||
"PolicyTypes": self.policy_types,
|
"PolicyTypes": self.policy_types,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def add_policy_type(self, policy_type):
|
||||||
|
if policy_type not in self.SUPPORTED_POLICY_TYPES:
|
||||||
|
raise InvalidInputException("You specified an invalid value.")
|
||||||
|
|
||||||
|
if any(type["Type"] == policy_type for type in self.policy_types):
|
||||||
|
raise PolicyTypeAlreadyEnabledException
|
||||||
|
|
||||||
|
self.policy_types.append({"Type": policy_type, "Status": "ENABLED"})
|
||||||
|
|
||||||
|
def remove_policy_type(self, policy_type):
|
||||||
|
if not FakePolicy.supported_policy_type(policy_type):
|
||||||
|
raise InvalidInputException("You specified an invalid value.")
|
||||||
|
|
||||||
|
if all(type["Type"] != policy_type for type in self.policy_types):
|
||||||
|
raise PolicyTypeNotEnabledException
|
||||||
|
|
||||||
|
self.policy_types.remove({"Type": policy_type, "Status": "ENABLED"})
|
||||||
|
|
||||||
|
|
||||||
|
class FakePolicy(BaseModel):
|
||||||
|
SUPPORTED_POLICY_TYPES = [
|
||||||
|
"AISERVICES_OPT_OUT_POLICY",
|
||||||
|
"BACKUP_POLICY",
|
||||||
|
"SERVICE_CONTROL_POLICY",
|
||||||
|
"TAG_POLICY",
|
||||||
|
]
|
||||||
|
|
||||||
class FakeServiceControlPolicy(BaseModel):
|
|
||||||
def __init__(self, organization, **kwargs):
|
def __init__(self, organization, **kwargs):
|
||||||
self.content = kwargs.get("Content")
|
self.content = kwargs.get("Content")
|
||||||
self.description = kwargs.get("Description")
|
self.description = kwargs.get("Description")
|
||||||
self.name = kwargs.get("Name")
|
self.name = kwargs.get("Name")
|
||||||
self.type = kwargs.get("Type")
|
self.type = kwargs.get("Type")
|
||||||
self.id = utils.make_random_service_control_policy_id()
|
self.id = utils.make_random_policy_id()
|
||||||
self.aws_managed = False
|
self.aws_managed = False
|
||||||
self.organization_id = organization.id
|
self.organization_id = organization.id
|
||||||
self.master_account_id = organization.master_account_id
|
self.master_account_id = organization.master_account_id
|
||||||
self._arn_format = utils.SCP_ARN_FORMAT
|
|
||||||
self.attachments = []
|
self.attachments = []
|
||||||
|
|
||||||
|
if not FakePolicy.supported_policy_type(self.type):
|
||||||
|
raise InvalidInputException("You specified an invalid value.")
|
||||||
|
elif self.type == "AISERVICES_OPT_OUT_POLICY":
|
||||||
|
self._arn_format = utils.AI_POLICY_ARN_FORMAT
|
||||||
|
elif self.type == "SERVICE_CONTROL_POLICY":
|
||||||
|
self._arn_format = utils.SCP_ARN_FORMAT
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(
|
||||||
|
"The {0} policy type has not been implemented".format(self.type)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def arn(self):
|
def arn(self):
|
||||||
return self._arn_format.format(
|
return self._arn_format.format(
|
||||||
@ -176,6 +222,10 @@ class FakeServiceControlPolicy(BaseModel):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def supported_policy_type(policy_type):
|
||||||
|
return policy_type in FakePolicy.SUPPORTED_POLICY_TYPES
|
||||||
|
|
||||||
|
|
||||||
class FakeServiceAccess(BaseModel):
|
class FakeServiceAccess(BaseModel):
|
||||||
# List of trusted services, which support trusted access with Organizations
|
# List of trusted services, which support trusted access with Organizations
|
||||||
@ -283,6 +333,13 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
self.services = []
|
self.services = []
|
||||||
self.admins = []
|
self.admins = []
|
||||||
|
|
||||||
|
def _get_root_by_id(self, root_id):
|
||||||
|
root = next((ou for ou in self.ou if ou.id == root_id), None)
|
||||||
|
if not root:
|
||||||
|
raise RootNotFoundException
|
||||||
|
|
||||||
|
return root
|
||||||
|
|
||||||
def create_organization(self, **kwargs):
|
def create_organization(self, **kwargs):
|
||||||
self.org = FakeOrganization(kwargs["FeatureSet"])
|
self.org = FakeOrganization(kwargs["FeatureSet"])
|
||||||
root_ou = FakeRoot(self.org)
|
root_ou = FakeRoot(self.org)
|
||||||
@ -292,7 +349,7 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
)
|
)
|
||||||
master_account.id = self.org.master_account_id
|
master_account.id = self.org.master_account_id
|
||||||
self.accounts.append(master_account)
|
self.accounts.append(master_account)
|
||||||
default_policy = FakeServiceControlPolicy(
|
default_policy = FakePolicy(
|
||||||
self.org,
|
self.org,
|
||||||
Name="FullAWSAccess",
|
Name="FullAWSAccess",
|
||||||
Description="Allows access to every operation",
|
Description="Allows access to every operation",
|
||||||
@ -452,7 +509,7 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def create_policy(self, **kwargs):
|
def create_policy(self, **kwargs):
|
||||||
new_policy = FakeServiceControlPolicy(self.org, **kwargs)
|
new_policy = FakePolicy(self.org, **kwargs)
|
||||||
for policy in self.policies:
|
for policy in self.policies:
|
||||||
if kwargs["Name"] == policy.name:
|
if kwargs["Name"] == policy.name:
|
||||||
raise DuplicatePolicyException
|
raise DuplicatePolicyException
|
||||||
@ -460,7 +517,7 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
return new_policy.describe()
|
return new_policy.describe()
|
||||||
|
|
||||||
def describe_policy(self, **kwargs):
|
def describe_policy(self, **kwargs):
|
||||||
if re.compile(utils.SCP_ID_REGEX).match(kwargs["PolicyId"]):
|
if re.compile(utils.POLICY_ID_REGEX).match(kwargs["PolicyId"]):
|
||||||
policy = next(
|
policy = next(
|
||||||
(p for p in self.policies if p.id == kwargs["PolicyId"]), None
|
(p for p in self.policies if p.id == kwargs["PolicyId"]), None
|
||||||
)
|
)
|
||||||
@ -540,7 +597,13 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def list_policies_for_target(self, **kwargs):
|
def list_policies_for_target(self, **kwargs):
|
||||||
if re.compile(utils.OU_ID_REGEX).match(kwargs["TargetId"]):
|
filter = kwargs["Filter"]
|
||||||
|
|
||||||
|
if re.match(utils.ROOT_ID_REGEX, kwargs["TargetId"]):
|
||||||
|
obj = next((ou for ou in self.ou if ou.id == kwargs["TargetId"]), None)
|
||||||
|
if obj is None:
|
||||||
|
raise TargetNotFoundException
|
||||||
|
elif re.compile(utils.OU_ID_REGEX).match(kwargs["TargetId"]):
|
||||||
obj = next((ou for ou in self.ou if ou.id == kwargs["TargetId"]), None)
|
obj = next((ou for ou in self.ou if ou.id == kwargs["TargetId"]), None)
|
||||||
if obj is None:
|
if obj is None:
|
||||||
raise RESTError(
|
raise RESTError(
|
||||||
@ -553,14 +616,25 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
raise AccountNotFoundException
|
raise AccountNotFoundException
|
||||||
else:
|
else:
|
||||||
raise InvalidInputException("You specified an invalid value.")
|
raise InvalidInputException("You specified an invalid value.")
|
||||||
|
|
||||||
|
if not FakePolicy.supported_policy_type(filter):
|
||||||
|
raise InvalidInputException("You specified an invalid value.")
|
||||||
|
|
||||||
|
if filter not in ["AISERVICES_OPT_OUT_POLICY", "SERVICE_CONTROL_POLICY"]:
|
||||||
|
raise NotImplementedError(
|
||||||
|
"The {0} policy type has not been implemented".format(filter)
|
||||||
|
)
|
||||||
|
|
||||||
return dict(
|
return dict(
|
||||||
Policies=[
|
Policies=[
|
||||||
p.describe()["Policy"]["PolicySummary"] for p in obj.attached_policies
|
p.describe()["Policy"]["PolicySummary"]
|
||||||
|
for p in obj.attached_policies
|
||||||
|
if p.type == filter
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def list_targets_for_policy(self, **kwargs):
|
def list_targets_for_policy(self, **kwargs):
|
||||||
if re.compile(utils.SCP_ID_REGEX).match(kwargs["PolicyId"]):
|
if re.compile(utils.POLICY_ID_REGEX).match(kwargs["PolicyId"]):
|
||||||
policy = next(
|
policy = next(
|
||||||
(p for p in self.policies if p.id == kwargs["PolicyId"]), None
|
(p for p in self.policies if p.id == kwargs["PolicyId"]), None
|
||||||
)
|
)
|
||||||
@ -733,5 +807,19 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
if not admin.services:
|
if not admin.services:
|
||||||
self.admins.remove(admin)
|
self.admins.remove(admin)
|
||||||
|
|
||||||
|
def enable_policy_type(self, **kwargs):
|
||||||
|
root = self._get_root_by_id(kwargs["RootId"])
|
||||||
|
|
||||||
|
root.add_policy_type(kwargs["PolicyType"])
|
||||||
|
|
||||||
|
return dict(Root=root.describe())
|
||||||
|
|
||||||
|
def disable_policy_type(self, **kwargs):
|
||||||
|
root = self._get_root_by_id(kwargs["RootId"])
|
||||||
|
|
||||||
|
root.remove_policy_type(kwargs["PolicyType"])
|
||||||
|
|
||||||
|
return dict(Root=root.describe())
|
||||||
|
|
||||||
|
|
||||||
organizations_backend = OrganizationsBackend()
|
organizations_backend = OrganizationsBackend()
|
||||||
|
@ -191,3 +191,13 @@ class OrganizationsResponse(BaseResponse):
|
|||||||
**self.request_params
|
**self.request_params
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def enable_policy_type(self):
|
||||||
|
return json.dumps(
|
||||||
|
self.organizations_backend.enable_policy_type(**self.request_params)
|
||||||
|
)
|
||||||
|
|
||||||
|
def disable_policy_type(self):
|
||||||
|
return json.dumps(
|
||||||
|
self.organizations_backend.disable_policy_type(**self.request_params)
|
||||||
|
)
|
||||||
|
@ -14,6 +14,9 @@ ACCOUNT_ARN_FORMAT = "arn:aws:organizations::{0}:account/{1}/{2}"
|
|||||||
ROOT_ARN_FORMAT = "arn:aws:organizations::{0}:root/{1}/{2}"
|
ROOT_ARN_FORMAT = "arn:aws:organizations::{0}:root/{1}/{2}"
|
||||||
OU_ARN_FORMAT = "arn:aws:organizations::{0}:ou/{1}/{2}"
|
OU_ARN_FORMAT = "arn:aws:organizations::{0}:ou/{1}/{2}"
|
||||||
SCP_ARN_FORMAT = "arn:aws:organizations::{0}:policy/{1}/service_control_policy/{2}"
|
SCP_ARN_FORMAT = "arn:aws:organizations::{0}:policy/{1}/service_control_policy/{2}"
|
||||||
|
AI_POLICY_ARN_FORMAT = (
|
||||||
|
"arn:aws:organizations::{0}:policy/{1}/aiservices_opt_out_policy/{2}"
|
||||||
|
)
|
||||||
|
|
||||||
CHARSET = string.ascii_lowercase + string.digits
|
CHARSET = string.ascii_lowercase + string.digits
|
||||||
ORG_ID_SIZE = 10
|
ORG_ID_SIZE = 10
|
||||||
@ -21,7 +24,7 @@ ROOT_ID_SIZE = 4
|
|||||||
ACCOUNT_ID_SIZE = 12
|
ACCOUNT_ID_SIZE = 12
|
||||||
OU_ID_SUFFIX_SIZE = 8
|
OU_ID_SUFFIX_SIZE = 8
|
||||||
CREATE_ACCOUNT_STATUS_ID_SIZE = 8
|
CREATE_ACCOUNT_STATUS_ID_SIZE = 8
|
||||||
SCP_ID_SIZE = 8
|
POLICY_ID_SIZE = 8
|
||||||
|
|
||||||
EMAIL_REGEX = "^.+@[a-zA-Z0-9-.]+.[a-zA-Z]{2,3}|[0-9]{1,3}$"
|
EMAIL_REGEX = "^.+@[a-zA-Z0-9-.]+.[a-zA-Z]{2,3}|[0-9]{1,3}$"
|
||||||
ORG_ID_REGEX = r"o-[a-z0-9]{%s}" % ORG_ID_SIZE
|
ORG_ID_REGEX = r"o-[a-z0-9]{%s}" % ORG_ID_SIZE
|
||||||
@ -29,7 +32,7 @@ ROOT_ID_REGEX = r"r-[a-z0-9]{%s}" % ROOT_ID_SIZE
|
|||||||
OU_ID_REGEX = r"ou-[a-z0-9]{%s}-[a-z0-9]{%s}" % (ROOT_ID_SIZE, OU_ID_SUFFIX_SIZE)
|
OU_ID_REGEX = r"ou-[a-z0-9]{%s}-[a-z0-9]{%s}" % (ROOT_ID_SIZE, OU_ID_SUFFIX_SIZE)
|
||||||
ACCOUNT_ID_REGEX = r"[0-9]{%s}" % ACCOUNT_ID_SIZE
|
ACCOUNT_ID_REGEX = r"[0-9]{%s}" % ACCOUNT_ID_SIZE
|
||||||
CREATE_ACCOUNT_STATUS_ID_REGEX = r"car-[a-z0-9]{%s}" % CREATE_ACCOUNT_STATUS_ID_SIZE
|
CREATE_ACCOUNT_STATUS_ID_REGEX = r"car-[a-z0-9]{%s}" % CREATE_ACCOUNT_STATUS_ID_SIZE
|
||||||
SCP_ID_REGEX = r"%s|p-[a-z0-9]{%s}" % (DEFAULT_POLICY_ID, SCP_ID_SIZE)
|
POLICY_ID_REGEX = r"%s|p-[a-z0-9]{%s}" % (DEFAULT_POLICY_ID, POLICY_ID_SIZE)
|
||||||
|
|
||||||
|
|
||||||
def make_random_org_id():
|
def make_random_org_id():
|
||||||
@ -76,8 +79,8 @@ def make_random_create_account_status_id():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def make_random_service_control_policy_id():
|
def make_random_policy_id():
|
||||||
# The regex pattern for a policy ID string requires "p-" followed by
|
# The regex pattern for a policy ID string requires "p-" followed by
|
||||||
# from 8 to 128 lower-case letters or digits.
|
# from 8 to 128 lower-case letters or digits.
|
||||||
# e.g. 'p-k2av4a8a'
|
# e.g. 'p-k2av4a8a'
|
||||||
return "p-" + "".join(random.choice(CHARSET) for x in range(SCP_ID_SIZE))
|
return "p-" + "".join(random.choice(CHARSET) for x in range(POLICY_ID_SIZE))
|
||||||
|
@ -31,9 +31,9 @@ def test_make_random_create_account_status_id():
|
|||||||
create_account_status_id.should.match(utils.CREATE_ACCOUNT_STATUS_ID_REGEX)
|
create_account_status_id.should.match(utils.CREATE_ACCOUNT_STATUS_ID_REGEX)
|
||||||
|
|
||||||
|
|
||||||
def test_make_random_service_control_policy_id():
|
def test_make_random_policy_id():
|
||||||
service_control_policy_id = utils.make_random_service_control_policy_id()
|
policy_id = utils.make_random_policy_id()
|
||||||
service_control_policy_id.should.match(utils.SCP_ID_REGEX)
|
policy_id.should.match(utils.POLICY_ID_REGEX)
|
||||||
|
|
||||||
|
|
||||||
def validate_organization(response):
|
def validate_organization(response):
|
||||||
@ -128,7 +128,7 @@ def validate_create_account_status(create_status):
|
|||||||
|
|
||||||
def validate_policy_summary(org, summary):
|
def validate_policy_summary(org, summary):
|
||||||
summary.should.be.a(dict)
|
summary.should.be.a(dict)
|
||||||
summary.should.have.key("Id").should.match(utils.SCP_ID_REGEX)
|
summary.should.have.key("Id").should.match(utils.POLICY_ID_REGEX)
|
||||||
summary.should.have.key("Arn").should.equal(
|
summary.should.have.key("Arn").should.equal(
|
||||||
utils.SCP_ARN_FORMAT.format(org["MasterAccountId"], org["Id"], summary["Id"])
|
utils.SCP_ARN_FORMAT.format(org["MasterAccountId"], org["Id"], summary["Id"])
|
||||||
)
|
)
|
||||||
|
@ -379,6 +379,30 @@ def test_create_policy():
|
|||||||
policy["Content"].should.equal(json.dumps(policy_doc01))
|
policy["Content"].should.equal(json.dumps(policy_doc01))
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_create_policy_errors():
|
||||||
|
# given
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
client.create_organization(FeatureSet="ALL")
|
||||||
|
|
||||||
|
# invalid policy type
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.create_policy(
|
||||||
|
Content=json.dumps(policy_doc01),
|
||||||
|
Description="moto",
|
||||||
|
Name="moto",
|
||||||
|
Type="MOTO",
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("CreatePolicy")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
|
ex.response["Error"]["Message"].should.equal("You specified an invalid value.")
|
||||||
|
|
||||||
|
|
||||||
@mock_organizations
|
@mock_organizations
|
||||||
def test_describe_policy():
|
def test_describe_policy():
|
||||||
client = boto3.client("organizations", region_name="us-east-1")
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
@ -468,7 +492,7 @@ def test_delete_policy():
|
|||||||
def test_delete_policy_exception():
|
def test_delete_policy_exception():
|
||||||
client = boto3.client("organizations", region_name="us-east-1")
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
org = client.create_organization(FeatureSet="ALL")["Organization"]
|
org = client.create_organization(FeatureSet="ALL")["Organization"]
|
||||||
non_existent_policy_id = utils.make_random_service_control_policy_id()
|
non_existent_policy_id = utils.make_random_policy_id()
|
||||||
with assert_raises(ClientError) as e:
|
with assert_raises(ClientError) as e:
|
||||||
response = client.delete_policy(PolicyId=non_existent_policy_id)
|
response = client.delete_policy(PolicyId=non_existent_policy_id)
|
||||||
ex = e.exception
|
ex = e.exception
|
||||||
@ -571,7 +595,7 @@ def test_update_policy():
|
|||||||
def test_update_policy_exception():
|
def test_update_policy_exception():
|
||||||
client = boto3.client("organizations", region_name="us-east-1")
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
org = client.create_organization(FeatureSet="ALL")["Organization"]
|
org = client.create_organization(FeatureSet="ALL")["Organization"]
|
||||||
non_existent_policy_id = utils.make_random_service_control_policy_id()
|
non_existent_policy_id = utils.make_random_policy_id()
|
||||||
with assert_raises(ClientError) as e:
|
with assert_raises(ClientError) as e:
|
||||||
response = client.update_policy(PolicyId=non_existent_policy_id)
|
response = client.update_policy(PolicyId=non_existent_policy_id)
|
||||||
ex = e.exception
|
ex = e.exception
|
||||||
@ -631,6 +655,7 @@ def test_list_policies_for_target():
|
|||||||
def test_list_policies_for_target_exception():
|
def test_list_policies_for_target_exception():
|
||||||
client = boto3.client("organizations", region_name="us-east-1")
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
client.create_organization(FeatureSet="ALL")["Organization"]
|
client.create_organization(FeatureSet="ALL")["Organization"]
|
||||||
|
root_id = client.list_roots()["Roots"][0]["Id"]
|
||||||
ou_id = "ou-gi99-i7r8eh2i2"
|
ou_id = "ou-gi99-i7r8eh2i2"
|
||||||
account_id = "126644886543"
|
account_id = "126644886543"
|
||||||
with assert_raises(ClientError) as e:
|
with assert_raises(ClientError) as e:
|
||||||
@ -664,6 +689,34 @@ def test_list_policies_for_target_exception():
|
|||||||
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
ex.response["Error"]["Message"].should.equal("You specified an invalid value.")
|
ex.response["Error"]["Message"].should.equal("You specified an invalid value.")
|
||||||
|
|
||||||
|
# not existing root
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.list_policies_for_target(
|
||||||
|
TargetId="r-0000", Filter="SERVICE_CONTROL_POLICY"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("ListPoliciesForTarget")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("TargetNotFoundException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"You specified a target that doesn't exist."
|
||||||
|
)
|
||||||
|
|
||||||
|
# invalid policy type
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.list_policies_for_target(TargetId=root_id, Filter="MOTO")
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("ListPoliciesForTarget")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
|
ex.response["Error"]["Message"].should.equal("You specified an invalid value.")
|
||||||
|
|
||||||
|
|
||||||
@mock_organizations
|
@mock_organizations
|
||||||
def test_list_targets_for_policy():
|
def test_list_targets_for_policy():
|
||||||
@ -1305,3 +1358,211 @@ def test_deregister_delegated_administrator_erros():
|
|||||||
ex.response["Error"]["Message"].should.equal(
|
ex.response["Error"]["Message"].should.equal(
|
||||||
"You specified an unrecognized service principal."
|
"You specified an unrecognized service principal."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_enable_policy_type():
|
||||||
|
# given
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
org = client.create_organization(FeatureSet="ALL")["Organization"]
|
||||||
|
root_id = client.list_roots()["Roots"][0]["Id"]
|
||||||
|
|
||||||
|
# when
|
||||||
|
response = client.enable_policy_type(
|
||||||
|
RootId=root_id, PolicyType="AISERVICES_OPT_OUT_POLICY"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
root = response["Root"]
|
||||||
|
root["Id"].should.equal(root_id)
|
||||||
|
root["Arn"].should.equal(
|
||||||
|
utils.ROOT_ARN_FORMAT.format(org["MasterAccountId"], org["Id"], root_id)
|
||||||
|
)
|
||||||
|
root["Name"].should.equal("Root")
|
||||||
|
sorted(root["PolicyTypes"], key=lambda x: x["Type"]).should.equal(
|
||||||
|
[
|
||||||
|
{"Type": "AISERVICES_OPT_OUT_POLICY", "Status": "ENABLED"},
|
||||||
|
{"Type": "SERVICE_CONTROL_POLICY", "Status": "ENABLED"},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_enable_policy_type_errors():
|
||||||
|
# given
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
client.create_organization(FeatureSet="ALL")
|
||||||
|
root_id = client.list_roots()["Roots"][0]["Id"]
|
||||||
|
|
||||||
|
# not existing root
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.enable_policy_type(
|
||||||
|
RootId="r-0000", PolicyType="AISERVICES_OPT_OUT_POLICY"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("EnablePolicyType")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("RootNotFoundException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"You specified a root that doesn't exist."
|
||||||
|
)
|
||||||
|
|
||||||
|
# enable policy again ('SERVICE_CONTROL_POLICY' is enabled by default)
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.enable_policy_type(RootId=root_id, PolicyType="SERVICE_CONTROL_POLICY")
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("EnablePolicyType")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("PolicyTypeAlreadyEnabledException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"The specified policy type is already enabled."
|
||||||
|
)
|
||||||
|
|
||||||
|
# invalid policy type
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.enable_policy_type(RootId=root_id, PolicyType="MOTO")
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("EnablePolicyType")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
|
ex.response["Error"]["Message"].should.equal("You specified an invalid value.")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_disable_policy_type():
|
||||||
|
# given
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
org = client.create_organization(FeatureSet="ALL")["Organization"]
|
||||||
|
root_id = client.list_roots()["Roots"][0]["Id"]
|
||||||
|
client.enable_policy_type(RootId=root_id, PolicyType="AISERVICES_OPT_OUT_POLICY")
|
||||||
|
|
||||||
|
# when
|
||||||
|
response = client.disable_policy_type(
|
||||||
|
RootId=root_id, PolicyType="AISERVICES_OPT_OUT_POLICY"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
root = response["Root"]
|
||||||
|
root["Id"].should.equal(root_id)
|
||||||
|
root["Arn"].should.equal(
|
||||||
|
utils.ROOT_ARN_FORMAT.format(org["MasterAccountId"], org["Id"], root_id)
|
||||||
|
)
|
||||||
|
root["Name"].should.equal("Root")
|
||||||
|
root["PolicyTypes"].should.equal(
|
||||||
|
[{"Type": "SERVICE_CONTROL_POLICY", "Status": "ENABLED"}]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_disable_policy_type_errors():
|
||||||
|
# given
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
client.create_organization(FeatureSet="ALL")
|
||||||
|
root_id = client.list_roots()["Roots"][0]["Id"]
|
||||||
|
|
||||||
|
# not existing root
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.disable_policy_type(
|
||||||
|
RootId="r-0000", PolicyType="AISERVICES_OPT_OUT_POLICY"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("DisablePolicyType")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("RootNotFoundException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"You specified a root that doesn't exist."
|
||||||
|
)
|
||||||
|
|
||||||
|
# disable not enabled policy
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.disable_policy_type(
|
||||||
|
RootId=root_id, PolicyType="AISERVICES_OPT_OUT_POLICY"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("DisablePolicyType")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("PolicyTypeNotEnabledException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"This operation can be performed only for enabled policy types."
|
||||||
|
)
|
||||||
|
|
||||||
|
# invalid policy type
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.disable_policy_type(RootId=root_id, PolicyType="MOTO")
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("DisablePolicyType")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
|
ex.response["Error"]["Message"].should.equal("You specified an invalid value.")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_aiservices_opt_out_policy():
|
||||||
|
# given
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
org = client.create_organization(FeatureSet="ALL")["Organization"]
|
||||||
|
root_id = client.list_roots()["Roots"][0]["Id"]
|
||||||
|
client.enable_policy_type(RootId=root_id, PolicyType="AISERVICES_OPT_OUT_POLICY")
|
||||||
|
ai_policy = {
|
||||||
|
"services": {
|
||||||
|
"@@operators_allowed_for_child_policies": ["@@none"],
|
||||||
|
"default": {
|
||||||
|
"@@operators_allowed_for_child_policies": ["@@none"],
|
||||||
|
"opt_out_policy": {
|
||||||
|
"@@operators_allowed_for_child_policies": ["@@none"],
|
||||||
|
"@@assign": "optOut",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# when
|
||||||
|
response = client.create_policy(
|
||||||
|
Content=json.dumps(ai_policy),
|
||||||
|
Description="Opt out of all AI services",
|
||||||
|
Name="ai-opt-out",
|
||||||
|
Type="AISERVICES_OPT_OUT_POLICY",
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
summary = response["Policy"]["PolicySummary"]
|
||||||
|
policy_id = summary["Id"]
|
||||||
|
summary["Id"].should.match(utils.POLICY_ID_REGEX)
|
||||||
|
summary["Arn"].should.equal(
|
||||||
|
utils.AI_POLICY_ARN_FORMAT.format(
|
||||||
|
org["MasterAccountId"], org["Id"], summary["Id"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
summary["Name"].should.equal("ai-opt-out")
|
||||||
|
summary["Description"].should.equal("Opt out of all AI services")
|
||||||
|
summary["Type"].should.equal("AISERVICES_OPT_OUT_POLICY")
|
||||||
|
summary["AwsManaged"].should_not.be.ok
|
||||||
|
json.loads(response["Policy"]["Content"]).should.equal(ai_policy)
|
||||||
|
|
||||||
|
# when
|
||||||
|
client.attach_policy(PolicyId=policy_id, TargetId=root_id)
|
||||||
|
|
||||||
|
# then
|
||||||
|
response = client.list_policies_for_target(
|
||||||
|
TargetId=root_id, Filter="AISERVICES_OPT_OUT_POLICY"
|
||||||
|
)
|
||||||
|
response["Policies"].should.have.length_of(1)
|
||||||
|
response["Policies"][0]["Id"].should.equal(policy_id)
|
||||||
|
Loading…
Reference in New Issue
Block a user