Automatically Share AWS Service Catalog Portfolios To Different Accounts In Your Organization

May 18, 2023
/
Santosh Peddada
/
No items found.

When you leverage AWS Service Catalog you can fully create and manage a catalog of approved IT services on AWS. By integrating AWS Service Catalog with AWS Organizations, you can further simplify how you share portfolios and copy products within an organization. Using the two AWS services in sync, you can connect an existing organization in AWS Organizations when you want to share a portfolio, and they are then allowed to share that portfolio with any named trusted 'organizational units' (OU) in their organization's outlined 'tree structure'. This process improves best practice organizational sharing capabilities by removing the need to manually reference or share portfolio IDs.

With AWS Service Catalog, you can make your products available to internal users as well as users who are not a part of your AWS account, for example, to users who are linked with other organizations or to additional AWS accounts in your organization by sharing your portfolios directly to their AWS accounts.

By sharing a portfolio, you also enable the AWS Service Catalog administrator of other AWS accounts to import your portfolio into their accounts and distribute your products to the end-users connected with that account. Imported portfolios, however, aren't independent copies of the original. All imported portfolios will stay synced to any changes to products and limitations according to the original shared portfolio. Any recipient administrators, those who you share a portfolio with, do not have the authority to change any products or portfolio limitations, but they can configure their organizational AWS Identity and Access Management (IAM) access for their end-users.

Recipient administrators can share products to end users who belong to their organization's AWS account by doing the following:

  • Add IAM users, groups, and roles to imported portfolios.
  • Copy products from an imported portfolio to a local portfolio, this would then become a separate portfolio (no longer synced with the original) that the recipient administrator can create and connect to their AWS account. They then have full control of the IAM configuration for their local portfolio. However, any limitations applied to the products in the original shared portfolio get carried across to the local portfolio. Recipient administrators can add in any additional limitations to their local portfolio, but they cannot remove any imported ones.

The article explains about sharing portfolios to different accounts using a Lambda function that is triggered by putting a JSON file with the customer account IDs, Portfolio IDs and Organization Unit ID information into an S3 bucket. We use cross-account roles and IAM users to make portfolios available to launch in our customer accounts.

Creating Cross-Account Roles and IAM Users in the Destination Account

Go to the IAM page your AWS Management Console, select ‘Create a role’, then select ‘Another AWS account’, update the master account ID at the Account ID field and click on ‘Next permissions’.

Create the IAM policy according to the code shown below (follow the link here for RAW code from Gist Github) and add to the appropriate IAM role. Give the role a name, such as "Servicecatalogcrossaccount."

{
   "Version": "2012-10-17",
   "Statement": [
       {
           "Sid": "VisualEditor0",
           "Effect": "Allow",
           "Action": [
               "servicecatalog:AssociatePrincipalWithPortfolio",
               "servicecatalog:CreatePortfolioShare",
               "iam:GetUser",
               "servicecatalog:AcceptPortfolioShare"
           ],
           "Resource": "*"
       }
   ]
}

Create an IAM user or group with the name "CatalogUser" with ‘AWSServiceCatalogEndUserFullAccess’ to launch the products shared with the child account.

In this way, we should now have a cross-account role and a user/group with Servicecatalogcrossaccount and CatalogUser respectively for all the (child)accounts under an organization.

Adding Child Accounts to the Organization Unit in a Master Account

Go to AWS organizations click on ‘Add accounts’ and ‘Invite account’ to add an existing account as a child account to a master account. The invited child accounts will then get an email to confirm the invitation.

Go to ‘Organize accounts’ on the organization's page, tick the appropriate child account and click on ‘Move’ to move to it to your organizational unit.

#moveaccount

Setting the IAM Role in Master Account to Attach to Lambda Function to Share Portfolio and Assume Cross-account Role Permissions

For the initial setup, create an IAM role for the Lambda function.

#AWSLambdaoptions

Select Lambda and click on ‘Next permissions’ and then click on ‘Create policy’ under the ‘Attach permissions policies’. We now get redirected to the policy creation page. We have to attach a couple of policies, one each for ‘Assume role of child account’ and another one for attaching permissions to your organization and Service Catalog. Click here for the Gist Github RAW code.

Click on ‘JSON’ and paste the below code and name the policy exactly as "Assumerole-policy."

{
  "Sid": "VisualEditor1",
    "Effect": "Allow",
    "Action": "sts:AssumeRole",
    "Resource": [
        "arn:aws:iam::<customeraccountID>:role/Servicecatalogcrossaccount"
     ]
 }

Then attach the below policy name as “IAM_policy_for_lamda”(arbitrary).  Click here for the Gist Github RAW code.    

{
   "Version": "2012-10-17",
   "Statement": [
       {
           "Sid": "VisualEditor0",
           "Effect": "Allow",
           "Action": "s3:GetObject",
           "Resource": "arn:aws:s3:::servicecatalogclientinfo/client.json"
       },
{
   "Sid": "VisualEditor1",
           "Effect": "Allow",
           "Action": [
               "iam:GetRole",
               "iam:GetPolicyVersion",
               "iam:ListRoleTags",
               "iam:GetPolicy",
               "iam:TagRole",
               "iam:DeletePolicyVersion",
               "iam:ListPolicyVersions",
               "iam:UpdateRole",
               "iam:CreatePolicyVersion",
               "iam:GetRolePolicy"
           ],
           "Resource": [
               "arn:aws:iam::*:policy/*",
               "arn:aws:iam::*:user/*",
               "arn:aws:iam::*:role/*"
           ]
},
{
   "Sid": "VisualEditor2",
           "Effect": "Allow",
           "Action": "logs:PutLogEvents",
           "Resource": "arn:aws:logs:*:*:log-group:*:log-stream:*"
       },
       {
           "Sid": "VisualEditor3",
           "Effect": "Allow",
           "Action": "logs:CreateLogStream",
           "Resource": "arn:aws:logs:*:*:log-group:*"
       },
       {
           "Sid": "VisualEditor4",
           "Effect": "Allow",
           "Action": "logs:CreateLogGroup",
           "Resource": "*"
},
       {
           "Sid": "VisualEditor6",
           "Effect": "Allow",
           "Action": [
               "organizations:ListRoots",
               "organizations:UntagResource",
               "organizations:DescribeAccount",
               "servicecatalog:CreatePortfolioShare",
               "organizations:CreateAccount",
               "organizations:DeleteOrganization",
               "organizations:DescribePolicy",
               "organizations:ListChildren",
               "organizations:TagResource",
               "organizations:EnableAWSServiceAccess",
               "organizations:ListCreateAccountStatus",
               "organizations:DescribeOrganization",
               "organizations:CreateGovCloudAccount",
               "organizations:EnableAllFeatures",
               "organizations:DescribeOrganizationalUnit",
               "organizations:DescribeHandshake",
               "organizations:CreatePolicy",
               "organizations:DescribeCreateAccountStatus",
               "organizations:CreateOrganization",
               "organizations:ListPoliciesForTarget",
               "organizations:ListTagsForResource",
               "organizations:ListTargetsForPolicy",
               "organizations:DisableAWSServiceAccess",
               "organizations:ListAWSServiceAccessForOrganization",
               "organizations:ListPolicies",
               "organizations:ListHandshakesForOrganization",
               "organizations:ListAccountsForParent",
               "organizations:LeaveOrganization",
               "organizations:ListHandshakesForAccount",
               "organizations:ListAccounts",
               "organizations:ListParents",
               "organizations:ListOrganizationalUnitsForParent"
           ],
           "Resource": "*"
       }    ]
}

A Lambda Function for Service Catalog Portfolio Sharing With Child Accounts

As per the above attached IAM policy, our new Lambda function will get all the appropriate access to do the required activity.

Create a Lambda function, select python 3.7 under run time, select ‘crossaccountLambda’ IAM role and under choose an existing role under permissions as the Lambda function should have access to the Service Catalog to share portfolios to the appropriate child accounts under the organizational unit.

Add an S3 trigger to the Lambda function as the Lambda function should be triggered by uploading the JSON file to the bucket.

Next, select S3 as the trigger, set up the event type as PUT, type the suffix as .json and update the bucket name.

#AWSLambdatrigger
#Triggerconfiguration

Update the code in the function with the below code and the environmental variables as shown below also. Click here for the Gist Github RAW code.

import json
import boto3
import os
import time
def get_account_id(context):
 return context.invoked_function_arn.split(':')[3]
def lambda_handler(event, context):
 #######################  ENVIRONMENT VARIABLES ################################
 s3_obj =boto3.client('s3')
 s3_clientobj = s3_obj.get_object(Bucket='servicecatalogclientinfo', Key='client.json')
 s3_clientdata = s3_clientobj['Body'].read().decode('utf-8')
 clientdata = json.loads(s3_clientdata)
 Destination_AccountIDs = clientdata['DestinationAccountIDs']
 ORGANIZATIONALUNIT_ID = clientdata['OU_ID']
 Port_ID = clientdata['PortID']
 ###############################################################################
 sts_connection = boto3.client('sts')
 servicecatalog = boto3.client('servicecatalog')
 iam = boto3.resource('iam')
 iamclient = boto3.client('iam')
 share_catalog_product = servicecatalog.create_portfolio_share(
     AcceptLanguage='en',
     PortfolioId=Port_ID,
     OrganizationNode={
       'Type': 'ORGANIZATIONAL_UNIT',
       'Value': ORGANIZATIONALUNIT_ID
     }
 )
 test=[]
 for accountID in Destination_AccountIDs:
   arn = 'arn:aws:iam::'+accountID+':role/Servicecatalogcrossaccount'
   policy = iam.Policy('arn:aws:iam::518164082586:policy/Assumerole-policy')
   version = policy.default_version
   policyJson = version.document
   test.append(arn)
 policyJson['Statement'][0]['Resource'].extend(test)  
 policyJson['Statement'][0]['Resource']=list(dict.fromkeys(policyJson['Statement'][0]['Resource']))
 print(policyJson)  

 policylatestversion = iamclient.create_policy_version(
   PolicyArn= 'arn:aws:iam::518164082586:policy/Assumerole-policy',
   PolicyDocument= json.dumps(policyJson),
   SetAsDefault= True
 )
 print(policylatestversion)
 delete_older_version = iamclient.delete_policy_version(
   PolicyArn= 'arn:aws:iam::518164082586:policy/Assumerole-policy',
   VersionId= version.version_id
 )  
 time.sleep(30)
 for accountID in Destination_AccountIDs:
   acct_b = sts_connection.assume_role(
     RoleArn="arn:aws:iam::"+accountID+":role/Servicecatalogcrossaccount",
     RoleSessionName="cross_acct_lambda"
   )
   ACCESS_KEY = acct_b['Credentials']['AccessKeyId']
   SECRET_KEY = acct_b['Credentials']['SecretAccessKey']
   SESSION_TOKEN = acct_b['Credentials']['SessionToken']
   dest_account=boto3.client('servicecatalog',region_name=get_account_id(context),aws_access_key_id=ACCESS_KEY,aws_secret_access_key=SECRET_KEY,aws_session_token=SESSION_TOKEN)
   add_IAM_user = dest_account.associate_principal_with_portfolio(
     AcceptLanguage='en',
     PortfolioId=Port_ID,
     PrincipalARN="arn:aws:iam::"+accountID+":user/CatalogUser",
     PrincipalType='IAM'
   )
 print("Portfolio shared with reference "+share_catalog_product['PortfolioShareToken'])

The Lambda function will be triggered by uploading the JSON file (shown below) to the S3 bucket. The resulting output will look like this.

{
  "OU_ID": "<*********************>",
  "PortID": "<*********************>",
 "DestinationAccountIDs": ["customeraccount1","customeraccount2","customeraccount3"]
}

In this way, we are not mentioning environment variables in Lambda console but keeping them in Json file in S3 bucket and triggering the Lambda function without touching the console.

Uploading the JSON file into the mentioned S3 bucket will trigger the Lambda function and share the portfolio to all the "DestinationaccountIDs" mentioned in the JSON file if the accounts are part of the organization unit of the master account.

After uploading the JSON file, we will also get the Lambda function output in your AWS CloudWatch log group.

Result

Once after Lambda function execution. Login to child account as catalog user and we should be seeing the products in the Service Catalog console which are available to launch.

#ServiceCataloglist
#ServiceCatalogprovisionedproductslist

If we onboard a new client, we then simply have to add the account in the organization to the master account, update the JSON file with the new account number, upload it to the S3 bucket, and thanks to our Lambda function, our Service Catalog products will automatically be shared and ready to launch in the new accounts.

Ibexlabs is an experienced DevOps & Managed Services provider and an AWS consulting partner. Our AWS Certified DevOps consultancy team evaluates your infrastructure and make recommendations based on your individual business or personal requirements. Contact us today and set up a free consultation to discuss a custom-built solution tailored just for you.

Santosh Peddada

Santosh Peddada is a Solution Architect with Ibexlabs. He has been in the IT industry for around 7 years, holding positions from Devops Engineer to Solution Architect. For the past two years, he has been an integral part of the design and development of AWS architecture for clients. He has served as the product owner for the Ibex Catalog, and provided solutions for a number of different industries.

Talk to an Ibexlabs Cloud Advisor