org-formation / org-formation-cli

Better than landingzones!
MIT License
1.4k stars 130 forks source link

Feature request: managing alternate contacts on member accounts #440

Closed sshvetsov closed 1 year ago

sshvetsov commented 1 year ago

Subject of the issue

On Sep. 30 2021 AWS made it possible to programmatically update Alternate Contacts for accounts in AWS Organization. Today one can update contact information for Security, Operations and Billing contacts using CLI or Organization Console (no CloudFormation support yet). This is a very useful feature for organizations with a large number of AWS accounts and it would be amazing if Organization Formation supported alternate contact information management via the Organization template.

Expected behaviour

Today AWS accounts API supports three (3) different types of alternate contacts: BILLING, OPERATIONS, SECURITY. The format of the Alternate Contact is the same, except for the contact type.

According to PutAlternateContact API reference, all properties in the alternate contact are required:

AlternateContactType Specifies which alternate contact you want to create or update.

Type: String

Valid Values: BILLING | OPERATIONS | SECURITY

Required: Yes

EmailAddress Specifies an email address for the alternate contact.

Type: String

Length Constraints: Minimum length of 1. Maximum length of 64.

Pattern: ^[\s][\w+=.#!&-]+@[\w.-]+.[\w]+[\s]$

Required: Yes

Name Specifies a name for the alternate contact.

Type: String

Length Constraints: Minimum length of 1. Maximum length of 64.

Required: Yes

PhoneNumber Specifies a phone number for the alternate contact.

Type: String

Length Constraints: Minimum length of 1. Maximum length of 25.

Pattern: ^[\s0-9()+-]+$

Required: Yes

Title Specifies a title for the alternate contact.

Type: String

Length Constraints: Minimum length of 1. Maximum length of 50.

Required: Yes

I'm not sure of the best way to represent alternate contacts in a declarative manner (list of contacts or a dict/map), so here are examples of what an organization template could look like for both..

List of Alternate Contacts:

SampleAccount:
  Type: OC::ORG::Account
  Properties:
    AccountName: sample-account
    RootEmail: aws-root+sample-account@example.com
    AlternateContacts:
      - AlternateContactType: OPERATIONS
        Name: Operations
        Title: Team
        EmailAddress: operations@example.com"
        PhoneNumber: '+18000000000'
      - AlternateContactType: BILLING
        Name: Finance
        Title: Team
        EmailAddress: finance@example.com"
        PhoneNumber: '+18000000001'
      - AlternateContactType: SECURITY
        Name: Security
        Title: Team
        EmailAddress: security@example.com"
        PhoneNumber: '+18000000002'

Dict/Map of Alternate Contacts:

SampleAccount:
  Type: OC::ORG::Account
  Properties:
    AccountName: sample-account
    RootEmail: aws-root+sample-account@example.com
    OperationsAlternateContact:
      Name: Operations
      Title: Team
      EmailAddress: operations@example.com"
      PhoneNumber: '+18000000000'
    BillingAlternateContact:
      Name: Finance
      Title: Team
      EmailAddress: finance@example.com"
      PhoneNumber: '+18000000001'
    SecurityAlternateContact:
      Name: Security
      Title: Team
      EmailAddress: security@example.com"
      PhoneNumber: '+18000000002'

If desired, a separate resource type OC::ORG::AlternateContact can be created and referenced to make organization template more dry.

OlafConijn commented 1 year ago

hi! thanks for raising this. This is already supported through a "community" cloudformation type. see example here and installation instructions here

probably needs to be fixed in documentation. documentation probably needs a bit of an overhaul in general šŸ˜¬

craighurley commented 1 year ago

I use the community types :)

tasks-register-types.yaml:

Parameters:
  <<: !Include ./parameters.yaml

  catalogBucket:
    Type: String
    Default: community-resource-provider-catalog

...

CommunityAlternateContactsRP:
  Type: register-type
  SchemaHandlerPackage: !Sub "s3://${catalogBucket}/community-account-alternatecontact-0.1.0.zip"
  ResourceType: 'Community::Account::AlternateContact'
  MaxConcurrentTasks: !Ref MaxConcurrentTasks
  OrganizationBinding:
    Region: us-east-1
    IncludeMasterAccount: true
    Account: '*'

contacts.yaml:

AWSTemplateFormatVersion: '2010-09-09'
Organization: !Include ../organization.yaml

Resources:
  BillingContact:
    Type: 'Community::Account::AlternateContact'
    Properties:
      AlternateContactType: BILLING
      Title: CFO
      Name: First Last
      PhoneNumber: +123 456789
      EmailAddress: aws+billing@example.com

  OperationsContact:
    Type: 'Community::Account::AlternateContact'
    Properties:
      AlternateContactType: OPERATIONS
      Title: COO
      Name: First Last
      PhoneNumber: +123 456789
      EmailAddress: aws+operations@example.com

  SecurityContact:
    Type: 'Community::Account::AlternateContact'
    Properties:
      AlternateContactType: SECURITY
      Title: CISO
      Name: First Last
      PhoneNumber: +123 456789
      EmailAddress: aws+security@example.com

Finally, my main tasks.yaml file looks something like this:

Parameters:
  <<: !Include ./parameters.yaml

OrganizationUpdate:
  Type: update-organization
  Template: ./organization.yaml

...

# Register custom cloudformation types
RegisterTypes:
  Type: include
  Path: tasks-register-types.yaml
  MaxConcurrentTasks: !Ref MaxConcurrentTasks

# Set contacts
Contacts:
  Type: include
  DependsOn:
    - RegisterTypes
  Path: tasks-contacts.yaml
  MaxConcurrentTasks: !Ref MaxConcurrentTasks

...
sshvetsov commented 1 year ago

Thanks for the tip @OlafConijn and for an excellent example @craighurley. I've successfully configured alternate contacts for accounts using OFN and the custom Cloudformation resource type Community::Account::AlternateContact. I'm going to close this feature request as completed.