Skip to content

Commit

Permalink
SSOAdmin: Default Instance and provisioning of permission sets (#7727)
Browse files Browse the repository at this point in the history
  • Loading branch information
bblommers authored May 29, 2024
1 parent 1bd6b31 commit c637076
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 17 deletions.
12 changes: 6 additions & 6 deletions IMPLEMENTATION_COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7891,7 +7891,7 @@

## sso-admin
<details>
<summary>27% implemented</summary>
<summary>34% implemented</summary>

- [X] attach_customer_managed_policy_reference_to_permission_set
- [X] attach_managed_policy_to_permission_set
Expand Down Expand Up @@ -7936,7 +7936,7 @@
- [ ] list_account_assignment_deletion_status
- [X] list_account_assignments
- [X] list_account_assignments_for_principal
- [ ] list_accounts_for_provisioned_permission_set
- [X] list_accounts_for_provisioned_permission_set
- [ ] list_application_access_scopes
- [ ] list_application_assignments
- [ ] list_application_assignments_for_principal
Expand All @@ -7945,14 +7945,14 @@
- [ ] list_application_providers
- [ ] list_applications
- [X] list_customer_managed_policy_references_in_permission_set
- [ ] list_instances
- [X] list_instances
- [X] list_managed_policies_in_permission_set
- [ ] list_permission_set_provisioning_status
- [X] list_permission_sets
- [ ] list_permission_sets_provisioned_to_account
- [X] list_permission_sets_provisioned_to_account
- [ ] list_tags_for_resource
- [ ] list_trusted_token_issuers
- [ ] provision_permission_set
- [X] provision_permission_set
- [ ] put_application_access_scope
- [ ] put_application_assignment_configuration
- [ ] put_application_authentication_method
Expand All @@ -7962,7 +7962,7 @@
- [ ] tag_resource
- [ ] untag_resource
- [ ] update_application
- [ ] update_instance
- [X] update_instance
- [ ] update_instance_access_control_attribute_configuration
- [X] update_permission_set
- [ ] update_trusted_token_issuer
Expand Down
22 changes: 17 additions & 5 deletions docs/docs/services/sso-admin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ sso-admin
- [ ] list_account_assignment_deletion_status
- [X] list_account_assignments
- [X] list_account_assignments_for_principal
- [ ] list_accounts_for_provisioned_permission_set
- [X] list_accounts_for_provisioned_permission_set

The following parameters are not yet implemented: MaxResults, NextToken, ProvisioningStatus


- [ ] list_application_access_scopes
- [ ] list_application_assignments
- [ ] list_application_assignments_for_principal
Expand All @@ -68,14 +72,22 @@ sso-admin
- [ ] list_application_providers
- [ ] list_applications
- [X] list_customer_managed_policy_references_in_permission_set
- [ ] list_instances
- [X] list_instances
- [X] list_managed_policies_in_permission_set
- [ ] list_permission_set_provisioning_status
- [X] list_permission_sets
- [ ] list_permission_sets_provisioned_to_account
- [X] list_permission_sets_provisioned_to_account

The following parameters are not yet implemented: AccountId, ProvisioningStatus, MaxResults, NextToken


- [ ] list_tags_for_resource
- [ ] list_trusted_token_issuers
- [ ] provision_permission_set
- [X] provision_permission_set

The TargetType/TargetId parameters are currently ignored - PermissionSets are simply provisioned to the caller's account


- [ ] put_application_access_scope
- [ ] put_application_assignment_configuration
- [ ] put_application_authentication_method
Expand All @@ -85,7 +97,7 @@ sso-admin
- [ ] tag_resource
- [ ] untag_resource
- [ ] update_application
- [ ] update_instance
- [X] update_instance
- [ ] update_instance_access_control_attribute_configuration
- [X] update_permission_set
- [ ] update_trusted_token_issuer
Expand Down
84 changes: 78 additions & 6 deletions moto/ssoadmin/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from moto.iam.aws_managed_policies import aws_managed_policies_data
from moto.moto_api._internal import mock_random as random
from moto.utilities.paginator import paginate
from moto.utilities.utils import get_partition

from .exceptions import (
ConflictException,
Expand Down Expand Up @@ -94,12 +95,7 @@ def to_json(self, include_creation_date: bool = False) -> Dict[str, Any]:

@staticmethod
def generate_id(instance_arn: str) -> str:
chars = list(range(10)) + ["a", "b", "c", "d", "e", "f"]
return (
instance_arn
+ "/ps-"
+ "".join(str(random.choice(chars)) for _ in range(16))
)
return instance_arn + "/ps-" + random.get_random_string(length=16).lower()


class ManagedPolicy(BaseModel):
Expand All @@ -124,6 +120,30 @@ def __eq__(self, other: Any) -> bool:
return f"{self.path}{self.name}" == f"{other.path}{other.name}"


class Instance:
def __init__(self, account_id: str, region: str):
self.created_date = unix_time()
self.identity_store_id = (
f"d-{random.get_random_string(length=10, lower_case=True)}"
)
self.instance_arn = f"arn:{get_partition(region)}:sso:::instance/ssoins-{random.get_random_string(length=16, lower_case=True)}"
self.account_id = account_id
self.status = "ACTIVE"
self.name: Optional[str] = None

self.provisioned_permission_sets: List[PermissionSet] = []

def to_json(self) -> Dict[str, Any]:
return {
"CreatedDate": self.created_date,
"IdentityStoreId": self.identity_store_id,
"InstanceArn": self.instance_arn,
"Name": self.name,
"OwnerAccountId": self.account_id,
"Status": self.status,
}


class SSOAdminBackend(BaseBackend):
"""Implementation of SSOAdmin APIs."""

Expand All @@ -133,6 +153,9 @@ def __init__(self, region_name: str, account_id: str):
self.deleted_account_assignments: List[AccountAssignment] = list()
self.permission_sets: List[PermissionSet] = list()
self.aws_managed_policies: Optional[Dict[str, Any]] = None
self.instances: List[Instance] = []

self.instances.append(Instance(self.account_id, self.region_name))

def create_account_assignment(
self,
Expand Down Expand Up @@ -327,6 +350,13 @@ def delete_permission_set(
permission_set_arn,
)
self.permission_sets.remove(permission_set)

for instance in self.instances:
try:
instance.provisioned_permission_sets.remove(permission_set)
except ValueError:
pass

return permission_set.to_json(include_creation_date=True)

def _find_permission_set(
Expand Down Expand Up @@ -542,5 +572,47 @@ def describe_account_assignment_deletion_status(

raise ResourceNotFoundException

def list_instances(self) -> List[Instance]:
return self.instances

def update_instance(self, instance_arn: str, name: str) -> None:
for instance in self.instances:
if instance.instance_arn == instance_arn:
instance.name = name

def provision_permission_set(
self, instance_arn: str, permission_set_arn: str
) -> None:
"""
The TargetType/TargetId parameters are currently ignored - PermissionSets are simply provisioned to the caller's account
"""
permission_set = self._find_permission_set(instance_arn, permission_set_arn)
instance = [i for i in self.instances if i.instance_arn == instance_arn][0]
instance.provisioned_permission_sets.append(permission_set)

def list_permission_sets_provisioned_to_account(
self, instance_arn: str
) -> List[PermissionSet]:
"""
The following parameters are not yet implemented: AccountId, ProvisioningStatus, MaxResults, NextToken
"""
for instance in self.instances:
if instance.instance_arn == instance_arn:
return instance.provisioned_permission_sets
return []

def list_accounts_for_provisioned_permission_set(
self, instance_arn: str, permission_set_arn: str
) -> List[str]:
"""
The following parameters are not yet implemented: MaxResults, NextToken, ProvisioningStatus
"""
for instance in self.instances:
if instance.instance_arn == instance_arn:
for ps in instance.provisioned_permission_sets:
if ps.permission_set_arn == permission_set_arn:
return [self.account_id]
return []


ssoadmin_backends = BackendDict(SSOAdminBackend, "sso-admin")
57 changes: 57 additions & 0 deletions moto/ssoadmin/responses.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import json
from uuid import uuid4

from moto.core.responses import BaseResponse
from moto.core.utils import unix_time

from .models import SSOAdminBackend, ssoadmin_backends

Expand Down Expand Up @@ -325,3 +327,58 @@ def describe_account_assignment_deletion_status(self) -> str:
return json.dumps(
dict(AccountAssignmentDeletionStatus=account_assignment_deletion_status)
)

def list_instances(self) -> str:
instances = self.ssoadmin_backend.list_instances()

return json.dumps({"Instances": [i.to_json() for i in instances]})

def update_instance(self) -> str:
instance_arn = self._get_param("InstanceArn")
name = self._get_param("Name")

self.ssoadmin_backend.update_instance(instance_arn=instance_arn, name=name)

return "{}"

def provision_permission_set(self) -> str:
instance_arn = self._get_param("InstanceArn")
permission_set_arn = self._get_param("PermissionSetArn")

self.ssoadmin_backend.provision_permission_set(
instance_arn=instance_arn,
permission_set_arn=permission_set_arn,
)
return json.dumps(
{
"PermissionSetProvisioningStatus": {
"AccountId": self.current_account,
"CreatedDate": unix_time(),
"PermissionSetArn": permission_set_arn,
"RequestId": str(uuid4()),
"Status": "SUCCEEDED",
}
}
)

def list_permission_sets_provisioned_to_account(self) -> str:
instance_arn = self._get_param("InstanceArn")

permission_sets = (
self.ssoadmin_backend.list_permission_sets_provisioned_to_account(
instance_arn
)
)
arns = [p.permission_set_arn for p in permission_sets]
return json.dumps({"PermissionSets": arns})

def list_accounts_for_provisioned_permission_set(self) -> str:
instance_arn = self._get_param("InstanceArn")
permission_set_arn = self._get_param("PermissionSetArn")

account_ids = (
self.ssoadmin_backend.list_accounts_for_provisioned_permission_set(
instance_arn=instance_arn, permission_set_arn=permission_set_arn
)
)
return json.dumps({"AccountIds": account_ids})
38 changes: 38 additions & 0 deletions tests/test_ssoadmin/test_ssoadmin_instances.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import boto3

from moto import mock_aws
from tests import DEFAULT_ACCOUNT_ID


@mock_aws
def test_list_instances():
ssoadmin = boto3.client("sso-admin", "us-east-1")

# We automatically create an instance on startup
# In AWS, this would involve some manual steps on the dashboard
instances = ssoadmin.list_instances()["Instances"]
assert len(instances) == 1

assert instances[0]["CreatedDate"]
assert instances[0]["IdentityStoreId"].startswith("d-")
assert instances[0]["InstanceArn"].startswith("arn:aws:sso:::instance/ssoins-")
assert instances[0]["OwnerAccountId"] == DEFAULT_ACCOUNT_ID
assert instances[0]["Status"] == "ACTIVE"

assert "Name" not in instances[0]


@mock_aws
def test_update_instance():
ssoadmin = boto3.client("sso-admin", "us-east-1")

# We automatically create an instance on startup
# In AWS, this would involve some manual steps on the dashboard
initial = ssoadmin.list_instances()["Instances"][0]

ssoadmin.update_instance(InstanceArn=initial["InstanceArn"], Name="instancename")

updated = ssoadmin.list_instances()["Instances"][0]
assert updated["Name"] == "instancename"
assert initial["IdentityStoreId"] == updated["IdentityStoreId"]
assert initial["InstanceArn"] == updated["InstanceArn"]
63 changes: 63 additions & 0 deletions tests/test_ssoadmin/test_ssoadmin_permission_sets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import boto3

from moto import mock_aws
from tests import DEFAULT_ACCOUNT_ID


@mock_aws
def test_provision_permission_set():
ssoadmin = boto3.client("sso-admin", "us-east-1")

instance_arn = ssoadmin.list_instances()["Instances"][0]["InstanceArn"]

p_set_arn = ssoadmin.create_permission_set(InstanceArn=instance_arn, Name="pset1")[
"PermissionSet"
]["PermissionSetArn"]

status = ssoadmin.provision_permission_set(
InstanceArn=instance_arn,
PermissionSetArn=p_set_arn,
TargetType="AWS_ACCOUNT",
)["PermissionSetProvisioningStatus"]

assert status["AccountId"] == DEFAULT_ACCOUNT_ID
assert status["CreatedDate"]
assert status["PermissionSetArn"] == p_set_arn
assert status["Status"] == "SUCCEEDED"


@mock_aws
def test_list_permission_sets_provisioned_to_account():
ssoadmin = boto3.client("sso-admin", "us-east-1")

instance_arn = ssoadmin.list_instances()["Instances"][0]["InstanceArn"]

p_set_arn = ssoadmin.create_permission_set(InstanceArn=instance_arn, Name="pset1")[
"PermissionSet"
]["PermissionSetArn"]

provisioned = ssoadmin.list_permission_sets_provisioned_to_account(
AccountId=DEFAULT_ACCOUNT_ID, InstanceArn=instance_arn
)["PermissionSets"]
assert len(provisioned) == 0

accounts = ssoadmin.list_accounts_for_provisioned_permission_set(
InstanceArn=instance_arn, PermissionSetArn=p_set_arn
)["AccountIds"]
assert accounts == []

ssoadmin.provision_permission_set(
InstanceArn=instance_arn,
PermissionSetArn=p_set_arn,
TargetType="AWS_ACCOUNT",
)

provisioned = ssoadmin.list_permission_sets_provisioned_to_account(
AccountId=DEFAULT_ACCOUNT_ID, InstanceArn=instance_arn
)["PermissionSets"]
assert provisioned == [p_set_arn]

accounts = ssoadmin.list_accounts_for_provisioned_permission_set(
InstanceArn=instance_arn, PermissionSetArn=p_set_arn
)["AccountIds"]
assert accounts == [DEFAULT_ACCOUNT_ID]

0 comments on commit c637076

Please sign in to comment.