AWS IAM Giveth And Taketh Away

Tailoring permissions to Manage Elemental Media Channels

Houston Haynes

6 minute read

Who, What, Where, When & Why

Elemental is a highly versatile media streaming platform, and it dovetails well to long-standing Amazon services. This sidebar is about how I configured a “viewer+” role for all resources in and around it. This would provide minimal permissions, from S3 to CloudFront, for behind-the-scenes visibility into how each channel is configured. And while the role is primarily considered a read resource I also included the ability to Start, Stop, edit Channel Schedules and Invalidate CloudFront caching, which became an interesting coda to this process.

The original intent was to set up a profile that could be used by a micro-service to expose the various elements in the “stack” within a custom admin panel. The developers used AWS Console initially, but I wanted to redirect everyone to a behind-the-scenes page to have a cleaner interface and also ‘smooth wall’ extraneous resources from view - to focus on the media delivery pipeline. So this set of policies would eventually land in production to provide a survey of AWS Elemental settings for a global “virtual cinema” delivery system run by support staff and used by developers for Level III support.

Separation of Concerns

One of the primitives I wanted to address from the outset was the principal of least privilege. Since I come from a regulated systems background I have fairly conservative reflexes when outlining roles and responsibilities. And also with major studio partners and their security requirements, I wanted to be double-sure that the proper constraints were in place to assure partners that only the right individuals had access to media resources.

Addition by Subtraction

The first iteration started with simply creating custom read permissions to the services (and in the case of S3, to specific buckets) and that didn’t work. The user could log into the console but none of the resources could be viewed even with view permissions in place. So I went to an older permissions model used ages ago when I first started with AWS, which is to provision default full read and write permission and then subtract write permissions out with separate DENY Policies… per below…

You’ll see that the full ReadWrite is in place for all services except for CloudFront, which has a boilerplate ReadOnly permission. One wrinkle here is that they also needed the CreateInvalidation permission in case there was the need to clear the CloudFront cache at the restart of a channel.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Deny",
            "Action": [
                "cloudfront:CreatePublicKey",
                "cloudfront:DeleteFieldLevelEncryptionConfig",
                "cloudfront:UpdatePublicKey",
                "cloudfront:TagResource",
                "cloudfront:DeleteCloudFrontOriginAccessIdentity",
                "cloudfront:CreateStreamingDistributionWithTags",
                "cloudfront:CreateStreamingDistribution",
                "cloudfront:CreateFieldLevelEncryptionProfile",
                "cloudfront:CreateDistributionWithTags",
                "cloudfront:UpdateStreamingDistribution",
                "cloudfront:CreateDistribution",
                "cloudfront:DeleteFieldLevelEncryptionProfile",
                "cloudfront:UpdateFieldLevelEncryptionConfig",
                "cloudfront:CreateInvalidation",
                "cloudfront:DeletePublicKey",
                "cloudfront:CreateCloudFrontOriginAccessIdentity",
                "cloudfront:DeleteStreamingDistribution",
                "cloudfront:UpdateFieldLevelEncryptionProfile",
                "cloudfront:UpdateDistribution",
                "cloudfront:UpdateCloudFrontOriginAccessIdentity",
                "cloudfront:DeleteDistribution",
                "cloudfront:CreateFieldLevelEncryptionConfig",
                "cloudfront:UntagResource"
            ],
            "Resource": "*",
            "Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "true"
                }
            }
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Deny",
            "Action": [
                "medialive:StartMultiplex",
                "medialive:UpdateInput",
                "medialive:DeleteChannel",
                "medialive:UpdateChannel",
                "medialive:UpdateReservation",
                "medialive:UpdateMultiplex",
                "medialive:PurchaseOffering",
                "medialive:CreateMultiplex",
                "medialive:UpdateInputDevice",
                "medialive:CreateTags",
                "medialive:DeleteTags",
                "medialive:CreateInput",
                "medialive:UpdateChannelClass",
                "medialive:DeleteMultiplex",
                "medialive:UpdateInputSecurityGroup",
                "medialive:DeleteReservation",
                "medialive:StopMultiplex",
                "medialive:DeleteInput",
                "medialive:DeleteInputSecurityGroup",
                "medialive:CreateChannel",
                "medialive:CreateInputSecurityGroup"
            ],
            "Resource": "*",
            "Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "true"
                }
            }
        },
        {
            "Sid": "VisualEditor2",
            "Effect": "Deny",
            "Action": [
                "mediapackage-vod:DeleteAsset",
                "mediapackage-vod:CreatePackagingGroup",
                "mediapackage-vod:DeletePackagingConfiguration",
                "mediapackage-vod:CreateAsset",
                "mediapackage-vod:CreatePackagingConfiguration",
                "mediapackage-vod:DeletePackagingGroup"
            ],
            "Resource": "*",
            "Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "true"
                }
            }
        },
        {
            "Sid": "VisualEditor3",
            "Effect": "Deny",
            "Action": [
                "s3:CreateAccessPoint",
                "s3:PutAnalyticsConfiguration",
                "s3:PutAccelerateConfiguration",
                "s3:DeleteObjectVersion",
                "s3:ReplicateTags",
                "s3:RestoreObject",
                "s3:DeleteAccessPoint",
                "s3:CreateBucket",
                "s3:ReplicateObject",
                "s3:PutEncryptionConfiguration",
                "s3:DeleteBucketWebsite",
                "s3:AbortMultipartUpload",
                "s3:PutBucketTagging",
                "s3:PutLifecycleConfiguration",
                "s3:UpdateJobPriority",
                "s3:PutBucketAcl",
                "s3:PutObjectTagging",
                "s3:DeleteObject",
                "s3:DeleteBucket",
                "s3:PutBucketVersioning",
                "s3:PutObjectAcl",
                "s3:DeleteObjectTagging",
                "s3:PutBucketPublicAccessBlock",
                "s3:PutAccountPublicAccessBlock",
                "s3:PutMetricsConfiguration",
                "s3:PutReplicationConfiguration",
                "s3:PutObjectVersionTagging",
                "s3:PutObjectLegalHold",
                "s3:UpdateJobStatus",
                "s3:DeleteObjectVersionTagging",
                "s3:PutBucketCORS",
                "s3:DeleteBucketPolicy",
                "s3:BypassGovernanceRetention",
                "s3:PutInventoryConfiguration",
                "s3:PutObject",
                "s3:PutBucketNotification",
                "s3:ObjectOwnerOverrideToBucketOwner",
                "s3:PutBucketWebsite",
                "s3:PutBucketRequestPayment",
                "s3:PutObjectRetention",
                "s3:PutBucketLogging",
                "s3:PutObjectVersionAcl",
                "s3:PutBucketPolicy",
                "s3:DeleteAccessPointPolicy",
                "s3:PutBucketObjectLockConfiguration",
                "s3:CreateJob",
                "s3:PutAccessPointPolicy",
                "s3:ReplicateDelete"
            ],
            "Resource": "*",
            "Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "true"
                }
            }
        }
    ]
}

I also still put in DENY Write permissions for CloudFront in case a “real” user is mistakenly assigned into a group that gave more permissions to them than simply CreateInvalidation. Again, this is just a reflexive abundance of caution. There is three other “write” permission that we needed within MediaLive, which are:

  1. StopChannel,
  2. StartChannel, and
  3. BatchUpdateSchedule

These are “write” permissions, but all of these fall within the “good faith actor” purview of the role, allowing media managers to do general house-keeping of channels without direct access to (or ability to remove/delete/download) partner-provided media resources.

Multi-Factor Authentication

Along those lines MFA is used for all Groups in any AWS account I build. We also implemented CloudTrail to monitor which user was accessing various resources. Because of that, we also had to provision the ability for users to manage their password and MFA device pairing in the AWS Console.

Below is the “AllowMFAManagement” Policy which actually covers password and MFA management.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowViewAccountInfo",
            "Effect": "Allow",
            "Action": [
                "iam:GetAccountPasswordPolicy",
                "iam:GetAccountSummary",
                "iam:ListVirtualMFADevices"
            ],
            "Resource": "*"
        },
        {
            "Sid": "AllowManageOwnPasswords",
            "Effect": "Allow",
            "Action": [
                "iam:ChangePassword",
                "iam:GetUser"
            ],
            "Resource": "arn:aws:iam::*:user/${aws:username}"
        },
        {
            "Sid": "AllowManageOwnAccessKeys",
            "Effect": "Allow",
            "Action": [
                "iam:CreateAccessKey",
                "iam:DeleteAccessKey",
                "iam:ListAccessKeys",
                "iam:UpdateAccessKey"
            ],
            "Resource": "arn:aws:iam::*:user/${aws:username}"
        },
        {
            "Sid": "AllowManageOwnSigningCertificates",
            "Effect": "Allow",
            "Action": [
                "iam:DeleteSigningCertificate",
                "iam:ListSigningCertificates",
                "iam:UpdateSigningCertificate",
                "iam:UploadSigningCertificate"
            ],
            "Resource": "arn:aws:iam::*:user/${aws:username}"
        },
        {
            "Sid": "AllowManageOwnSSHPublicKeys",
            "Effect": "Allow",
            "Action": [
                "iam:DeleteSSHPublicKey",
                "iam:GetSSHPublicKey",
                "iam:ListSSHPublicKeys",
                "iam:UpdateSSHPublicKey",
                "iam:UploadSSHPublicKey"
            ],
            "Resource": "arn:aws:iam::*:user/${aws:username}"
        },
        {
            "Sid": "AllowManageOwnGitCredentials",
            "Effect": "Allow",
            "Action": [
                "iam:CreateServiceSpecificCredential",
                "iam:DeleteServiceSpecificCredential",
                "iam:ListServiceSpecificCredentials",
                "iam:ResetServiceSpecificCredential",
                "iam:UpdateServiceSpecificCredential"
            ],
            "Resource": "arn:aws:iam::*:user/${aws:username}"
        },
        {
            "Sid": "AllowManageOwnVirtualMFADevice",
            "Effect": "Allow",
            "Action": [
                "iam:CreateVirtualMFADevice",
                "iam:DeleteVirtualMFADevice"
            ],
            "Resource": "arn:aws:iam::*:mfa/${aws:username}"
        },
        {
            "Sid": "AllowManageOwnUserMFA",
            "Effect": "Allow",
            "Action": [
                "iam:DeactivateMFADevice",
                "iam:EnableMFADevice",
                "iam:ListMFADevices",
                "iam:ResyncMFADevice"
            ],
            "Resource": "arn:aws:iam::*:user/${aws:username}"
        },
        {
            "Sid": "DenyAllExceptListedIfNoMFA",
            "Effect": "Deny",
            "NotAction": [
                "iam:CreateVirtualMFADevice",
                "iam:EnableMFADevice",
                "iam:GetUser",
                "iam:ListMFADevices",
                "iam:ListVirtualMFADevices",
                "iam:ResyncMFADevice",
                "sts:GetSessionToken"
            ],
            "Resource": "*",
            "Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "false"
                }
            }
        }
    ]
}

If the user logs in before/without setting MFA then they won’t see any resources, and AWS Console has a prompt to that effect. Again, this is to satisfy partner security requirements for access to studio film assets, but I also believe that it’s a good general habit.

Next Steps: CloudTrail and Service Logging

As the astute viewer might have caught in one of the screen grabs, the DENY Policy is labeled as “staging” which means it’s designed for access to pre-production resources. While that policy is currently in place for all tiers, eventually there will be a separate “production” instance of this Group, using Tags to differentiate which resource is visible to which User/Group.

And while CloudTrail tracks individual users as they work within the AWS account, when a service account is used some visibility into individual user access is lost. That has put some work on the road map to enhance the logging within the buffering service that the assigned Group would use, in order to track which user performed an action through the micro-service. This is all part of a strategy to create a proper “walled garden” in which actors can operate and their actions can remain within agreed-to guidelines with media partners. As it stands here, this is good starting point for working well within the AWS IAM harness.

Key Value
BuildDateTime 2021-07-03 10:22:03 -0700
LastGitUpdate 2021-05-27 18:55:44 -0700
GitHash f696b0c
CommitComment updated ranks - fleshed out low code