2026-09-16 21:29:43 +00:00
# HCP plan/apply roles for syslog-server-prod (PLAT-78 / PLAT-144).
# Copy of seahaven-org-baseline/examples/hcptf-workspace-iam/hcp_iam.tf.example
# with the syslog-server EC2 service set. Create, do not import.
#
# Live seahaven-hcptf-iam-management DenySelfMutation blocks DetachRolePolicy
# and PutRolePolicy on hcptf-* (including this role). First-apply sequence:
# 1. seahaven-org-baseline scripts/create-hcptf-bootstrap-roles.sh
# --account prod --allow-workspace syslog-server-prod
# 2. Point this workspace's TFC_AWS_* at hcptf-bootstrap /
# hcptf-bootstrap-plan (workspace vars, never a project set).
# 3. One Manual apply (create roles + scoped inline + boundary + stack).
# 4. Point TFC_AWS_* back at hcptf-syslog-server / hcptf-syslog-server-plan.
# 5. Re-run the script without --allow-workspace to pin trust back to
# iam-bootstrap-prod only.
# Later apply-role IAM edits use the same window. Do not add StringLike
# on bootstrap trust. CreatePolicy stays on hcptf-bootstrap only; boundary
# document changes after seal also need that window.
data " aws_iam_policy_document " " hcptf_apply_trust " {
statement {
sid = " HcpApply "
effect = " Allow "
actions = [ " sts:AssumeRoleWithWebIdentity " ]
principals {
type = " Federated "
identifiers = [ " arn:aws:iam:: ${ local . account_id } :oidc-provider/app.terraform.io " ]
}
condition {
test = " StringEquals "
variable = " app.terraform.io:aud "
values = [ " aws.workload.identity " ]
}
condition {
test = " StringEquals "
variable = " app.terraform.io:sub "
values = [
" organization:seahaven:project: ${ local . hcp_project } :workspace: ${ local . hcp_workspace } :run_phase:apply " ,
]
}
}
}
data " aws_iam_policy_document " " hcptf_plan_trust " {
statement {
sid = " HcpPlan "
effect = " Allow "
actions = [ " sts:AssumeRoleWithWebIdentity " ]
principals {
type = " Federated "
identifiers = [ " arn:aws:iam:: ${ local . account_id } :oidc-provider/app.terraform.io " ]
}
condition {
test = " StringEquals "
variable = " app.terraform.io:aud "
values = [ " aws.workload.identity " ]
}
condition {
test = " StringEquals "
variable = " app.terraform.io:sub "
values = [
" organization:seahaven:project: ${ local . hcp_project } :workspace: ${ local . hcp_workspace } :run_phase:plan " ,
]
}
}
}
data " aws_iam_policy_document " " hcptf_scoped_iam " {
statement {
sid = " DenyCreatePolicy "
effect = " Deny "
actions = [
" iam:CreatePolicy " ,
" iam:CreatePolicyVersion " ,
" iam:DeletePolicy " ,
" iam:DeletePolicyVersion " ,
" iam:SetDefaultPolicyVersion " ,
]
resources = [ " * " ]
}
statement {
sid = " CreateExecRoleWithBoundary "
effect = " Allow "
actions = [ " iam:CreateRole " ]
resources = [
" arn:aws:iam:: ${ local . account_id } :role/tf-managed/ ${ local . stack_prefix } * " ,
]
condition {
test = " StringLike "
variable = " iam:PermissionsBoundary "
values = [
" arn:aws:iam:: ${ local . account_id } :policy/tf-managed/ ${ local . stack_prefix } * " ,
" arn:aws:iam:: ${ local . account_id } :policy/seahaven-lambda-execution-boundary " ,
" arn:aws:iam:: ${ local . account_id } :policy/seahaven-lambda-execution-boundary- ${ local . stack_name } " ,
]
}
}
statement {
sid = " MutateExecRoleWithBoundary "
effect = " Allow "
actions = [
" iam:AttachRolePolicy " ,
" iam:PutRolePolicy " ,
" iam:PutRolePermissionsBoundary " ,
]
resources = [
" arn:aws:iam:: ${ local . account_id } :role/tf-managed/ ${ local . stack_prefix } * " ,
]
condition {
test = " StringLike "
variable = " iam:PermissionsBoundary "
values = [
" arn:aws:iam:: ${ local . account_id } :policy/tf-managed/ ${ local . stack_prefix } * " ,
" arn:aws:iam:: ${ local . account_id } :policy/seahaven-lambda-execution-boundary " ,
" arn:aws:iam:: ${ local . account_id } :policy/seahaven-lambda-execution-boundary- ${ local . stack_name } " ,
]
}
}
statement {
sid = " WriteExecRoles "
effect = " Allow "
actions = [
" iam:DeleteRole " ,
" iam:DeleteRolePolicy " ,
" iam:DetachRolePolicy " ,
" iam:TagRole " ,
" iam:UntagRole " ,
" iam:UpdateAssumeRolePolicy " ,
" iam:UpdateRole " ,
" iam:UpdateRoleDescription " ,
]
resources = [
" arn:aws:iam:: ${ local . account_id } :role/tf-managed/ ${ local . stack_prefix } * " ,
]
}
statement {
sid = " PassExecRolesToEc2 "
effect = " Allow "
actions = [ " iam:PassRole " ]
resources = [ " arn:aws:iam:: ${ local . account_id } :role/tf-managed/ ${ local . instance_role_name } " ]
condition {
test = " StringEquals "
variable = " iam:PassedToService "
values = [ " ec2.amazonaws.com " ]
}
}
statement {
sid = " InstanceProfiles "
effect = " Allow "
actions = [
" iam:AddRoleToInstanceProfile " ,
" iam:CreateInstanceProfile " ,
" iam:DeleteInstanceProfile " ,
" iam:GetInstanceProfile " ,
" iam:ListInstanceProfileTags " ,
" iam:RemoveRoleFromInstanceProfile " ,
" iam:TagInstanceProfile " ,
" iam:UntagInstanceProfile " ,
]
resources = [
" arn:aws:iam:: ${ local . account_id } :instance-profile/tf-managed/ ${ local . instance_profile_name } " ,
]
}
statement {
sid = " IamReadOnly "
effect = " Allow "
actions = [
" iam:GetPolicy " ,
" iam:GetPolicyVersion " ,
" iam:GetRole " ,
" iam:GetRolePolicy " ,
" iam:GetInstanceProfile " ,
" iam:ListAttachedRolePolicies " ,
" iam:ListInstanceProfiles " ,
" iam:ListInstanceProfilesForRole " ,
" iam:ListPolicies " ,
" iam:ListPolicyVersions " ,
" iam:ListRolePolicies " ,
" iam:ListRoleTags " ,
" iam:ListRoles " ,
]
resources = [ " * " ]
}
statement {
sid = " DenySelfMutation "
effect = " Deny "
actions = [
" iam:AttachRolePolicy " ,
" iam:DeleteRole " ,
" iam:DeleteRolePolicy " ,
" iam:DeleteRolePermissionsBoundary " ,
" iam:DetachRolePolicy " ,
" iam:PutRolePolicy " ,
" iam:PutRolePermissionsBoundary " ,
" iam:UpdateAssumeRolePolicy " ,
" iam:UpdateRole " ,
" iam:UpdateRoleDescription " ,
]
resources = [
" arn:aws:iam:: ${ local . account_id } :role/hcptf-* " ,
" arn:aws:iam:: ${ local . account_id } :role/github-cfn-execution-role " ,
" arn:aws:iam:: ${ local . account_id } :role/githubdeploy-* " ,
" arn:aws:iam:: ${ local . account_id } :role/cdk-hnb659fds-* " ,
" arn:aws:iam:: ${ local . account_id } :role/OrganizationAccountAccessRole " ,
" arn:aws:iam:: ${ local . account_id } :role/seahaven-* " ,
]
}
statement {
sid = " DenyBoundaryTampering "
effect = " Deny "
actions = [
" iam:DeleteRolePermissionsBoundary " ,
" iam:DeleteUserPermissionsBoundary " ,
]
resources = [
" arn:aws:iam:: ${ local . account_id } :role/* " ,
" arn:aws:iam:: ${ local . account_id } :user/* " ,
]
}
statement {
sid = " DenyBoundaryPolicyEdit "
effect = " Deny "
actions = [
" iam:CreatePolicyVersion " ,
" iam:DeletePolicy " ,
" iam:DeletePolicyVersion " ,
" iam:SetDefaultPolicyVersion " ,
]
resources = [ " arn:aws:iam:: ${ local . account_id } :policy/seahaven-* " ]
}
}
data " aws_iam_policy_document " " hcptf_apply_services " {
# checkov:skip=CKV_AWS_111: EC2 VPC/instance lifecycle and describe APIs require Resource=*. Log group, alarm, and SNS writes are ARN-prefixed.
statement {
sid = " CloudWatchLogs "
effect = " Allow "
actions = [
" logs:CreateLogGroup " ,
" logs:DeleteLogGroup " ,
" logs:PutRetentionPolicy " ,
" logs:DeleteRetentionPolicy " ,
" logs:TagResource " ,
" logs:UntagResource " ,
" logs:ListTagsForResource " ,
" logs:AssociateKmsKey " ,
" logs:DisassociateKmsKey " ,
]
resources = [
" arn:aws:logs: ${ var . aws_region } : ${ local . account_id } :log-group: ${ local . log_group_name } " ,
" arn:aws:logs: ${ var . aws_region } : ${ local . account_id } :log-group: ${ local . log_group_name } :* " ,
]
}
statement {
sid = " CloudWatchLogsDescribe "
effect = " Allow "
actions = [ " logs:DescribeLogGroups " ]
resources = [ " * " ]
}
statement {
sid = " CloudWatchAlarms "
effect = " Allow "
actions = [
" cloudwatch:PutMetricAlarm " ,
" cloudwatch:DeleteAlarms " ,
" cloudwatch:DescribeAlarms " ,
" cloudwatch:TagResource " ,
" cloudwatch:UntagResource " ,
" cloudwatch:ListTagsForResource " ,
]
resources = [
" arn:aws:cloudwatch: ${ var . aws_region } : ${ local . account_id } :alarm:Syslog-* " ,
" arn:aws:cloudwatch: ${ var . aws_region } : ${ local . account_id } :alarm:EC2-StatusCheck*syslog* " ,
]
}
statement {
sid = " CloudWatchDescribeAlarms "
effect = " Allow "
actions = [ " cloudwatch:DescribeAlarms " ]
resources = [ " * " ]
}
statement {
sid = " SnsPublishSiteAlerts "
effect = " Allow "
actions = [
" sns:Publish " ,
" sns:GetTopicAttributes " ,
" sns:ListTagsForResource " ,
]
resources = [ local . site_alerts_arn ]
}
statement {
sid = " ManageTfManagedBoundary "
effect = " Allow "
actions = [
" iam:GetPolicy " ,
" iam:GetPolicyVersion " ,
" iam:ListPolicyVersions " ,
" iam:ListPolicyTags " ,
" iam:TagPolicy " ,
" iam:UntagPolicy " ,
]
resources = [
" arn:aws:iam:: ${ local . account_id } :policy/tf-managed/ ${ local . stack_prefix } * " ,
]
}
statement {
sid = " Ec2VpcManagement "
effect = " Allow "
actions = [
" ec2:AllocateAddress " ,
" ec2:AssociateAddress " ,
" ec2:AssociateRouteTable " ,
" ec2:AttachInternetGateway " ,
" ec2:AuthorizeSecurityGroupEgress " ,
" ec2:AuthorizeSecurityGroupIngress " ,
" ec2:CreateInternetGateway " ,
" ec2:CreateRoute " ,
" ec2:CreateRouteTable " ,
" ec2:CreateSecurityGroup " ,
" ec2:CreateSubnet " ,
" ec2:CreateTags " ,
" ec2:CreateVpc " ,
" ec2:DeleteInternetGateway " ,
" ec2:DeleteRoute " ,
" ec2:DeleteRouteTable " ,
" ec2:DeleteSecurityGroup " ,
" ec2:DeleteSubnet " ,
" ec2:DeleteTags " ,
" ec2:DeleteVpc " ,
" ec2:DescribeAccountAttributes " ,
" ec2:DescribeAddresses " ,
" ec2:DescribeAddressesAttribute " ,
" ec2:DescribeAvailabilityZones " ,
" ec2:DescribeInternetGateways " ,
" ec2:DescribeNetworkInterfaces " ,
" ec2:DescribeRouteTables " ,
" ec2:DescribeSecurityGroupRules " ,
" ec2:DescribeSecurityGroups " ,
" ec2:DescribeSubnets " ,
" ec2:DescribeTags " ,
" ec2:DescribeVpcAttribute " ,
" ec2:DescribeVpcs " ,
" ec2:DescribePrefixLists " ,
" ec2:DetachInternetGateway " ,
" ec2:DisassociateAddress " ,
" ec2:DisassociateRouteTable " ,
" ec2:ModifySecurityGroupRules " ,
" ec2:ModifySubnetAttribute " ,
" ec2:ModifyVpcAttribute " ,
" ec2:ReleaseAddress " ,
" ec2:RevokeSecurityGroupEgress " ,
" ec2:RevokeSecurityGroupIngress " ,
" ec2:UpdateSecurityGroupRuleDescriptionsEgress " ,
" ec2:UpdateSecurityGroupRuleDescriptionsIngress " ,
]
resources = [ " * " ]
}
statement {
sid = " Ec2InstanceManagement "
effect = " Allow "
actions = [
" ec2:AssociateIamInstanceProfile " ,
" ec2:AttachVolume " ,
" ec2:CreateVolume " ,
" ec2:DeleteVolume " ,
" ec2:DescribeIamInstanceProfileAssociations " ,
" ec2:DescribeImages " ,
" ec2:DescribeInstanceAttribute " ,
" ec2:DescribeInstanceCreditSpecifications " ,
2026-09-16 21:48:35 +00:00
" ec2:DescribeInstanceRecoveryAttribute " ,
2026-09-16 21:29:43 +00:00
" ec2:DescribeInstanceStatus " ,
" ec2:DescribeInstanceTypes " ,
" ec2:DescribeInstances " ,
" ec2:DescribeVolumes " ,
" ec2:DescribeVolumeAttribute " ,
" ec2:DescribeVolumeStatus " ,
" ec2:DetachVolume " ,
" ec2:DisassociateIamInstanceProfile " ,
" ec2:GetConsoleOutput " ,
" ec2:ModifyInstanceAttribute " ,
" ec2:ModifyVolume " ,
" ec2:MonitorInstances " ,
" ec2:RebootInstances " ,
2026-09-16 21:48:35 +00:00
" ec2:RecoverInstances " ,
2026-09-16 21:29:43 +00:00
" ec2:ReplaceIamInstanceProfileAssociation " ,
" ec2:RunInstances " ,
" ec2:StartInstances " ,
" ec2:StopInstances " ,
" ec2:TerminateInstances " ,
" ec2:UnmonitorInstances " ,
]
resources = [ " * " ]
}
2026-09-16 21:48:35 +00:00
statement {
sid = " CloudWatchEc2RecoverServiceLinkedRole "
effect = " Allow "
actions = [
" iam:CreateServiceLinkedRole " ,
]
resources = [
" arn:aws:iam:: ${ local . account_id } :role/aws-service-role/events.amazonaws.com/AWSServiceRoleForCloudWatchEvents " ,
]
condition {
test = " StringEquals "
variable = " iam:AWSServiceName "
values = [ " events.amazonaws.com " ]
}
}
2026-09-16 21:29:43 +00:00
}
data " aws_iam_policy_document " " hcptf_plan_refresh " {
statement {
sid = " RefreshIamRoles "
effect = " Allow "
actions = [
" iam:GetRole " ,
" iam:GetRolePolicy " ,
" iam:GetInstanceProfile " ,
" iam:ListRolePolicies " ,
" iam:ListAttachedRolePolicies " ,
" iam:ListInstanceProfilesForRole " ,
" iam:ListRoleTags " ,
]
resources = [
" arn:aws:iam:: ${ local . account_id } :role/tf-managed/ ${ local . stack_prefix } * " ,
" arn:aws:iam:: ${ local . account_id } :instance-profile/tf-managed/ ${ local . stack_prefix } * " ,
" arn:aws:iam:: ${ local . account_id } :role/ ${ local . apply_role } " ,
" arn:aws:iam:: ${ local . account_id } :role/ ${ local . plan_role } " ,
]
}
statement {
sid = " RefreshManagedPolicies "
effect = " Allow "
actions = [
" iam:GetPolicy " ,
" iam:GetPolicyVersion " ,
]
resources = [ " * " ]
}
statement {
sid = " RefreshLogs "
effect = " Allow "
actions = [
" logs:DescribeLogGroups " ,
" logs:ListTagsForResource " ,
]
resources = [ " * " ]
}
statement {
sid = " RefreshAlarms "
effect = " Allow "
actions = [
" cloudwatch:DescribeAlarms " ,
" cloudwatch:ListTagsForResource " ,
]
resources = [ " * " ]
}
statement {
sid = " RefreshSns "
effect = " Allow "
actions = [
" sns:GetTopicAttributes " ,
" sns:ListTagsForResource " ,
]
resources = [ local . site_alerts_arn ]
}
statement {
sid = " RefreshEc2 "
effect = " Allow "
actions = [
" ec2:DescribeAccountAttributes " ,
" ec2:DescribeAddresses " ,
" ec2:DescribeAddressesAttribute " ,
" ec2:DescribeAvailabilityZones " ,
" ec2:DescribeIamInstanceProfileAssociations " ,
" ec2:DescribeImages " ,
" ec2:DescribeInstanceAttribute " ,
" ec2:DescribeInstanceCreditSpecifications " ,
2026-09-16 21:48:35 +00:00
" ec2:DescribeInstanceRecoveryAttribute " ,
2026-09-16 21:29:43 +00:00
" ec2:DescribeInstanceStatus " ,
" ec2:DescribeInstanceTypes " ,
" ec2:DescribeInstances " ,
" ec2:DescribeInternetGateways " ,
" ec2:DescribeNetworkInterfaces " ,
" ec2:DescribePrefixLists " ,
" ec2:DescribeRouteTables " ,
" ec2:DescribeSecurityGroupRules " ,
" ec2:DescribeSecurityGroups " ,
" ec2:DescribeSubnets " ,
" ec2:DescribeTags " ,
" ec2:DescribeVolumeAttribute " ,
" ec2:DescribeVolumeStatus " ,
" ec2:DescribeVolumes " ,
" ec2:DescribeVpcAttribute " ,
" ec2:DescribeVpcs " ,
" ec2:GetConsoleOutput " ,
]
resources = [ " * " ]
}
}
resource " aws_iam_role " " hcptf_apply " {
name = local . apply_role
assume_role_policy = data . aws_iam_policy_document . hcptf_apply_trust . json
max_session_duration = 3600
tags = {
Owner = " adam@seahavenind.com "
ManagedBy = " terraform "
}
}
resource " aws_iam_role " " hcptf_plan " {
name = local . plan_role
assume_role_policy = data . aws_iam_policy_document . hcptf_plan_trust . json
max_session_duration = 3600
tags = {
Owner = " adam@seahavenind.com "
ManagedBy = " terraform "
}
}
resource " aws_iam_role_policy " " hcptf_scoped_iam " {
name = " scoped-iam-management "
role = aws_iam_role . hcptf_apply . id
policy = data . aws_iam_policy_document . hcptf_scoped_iam . json
}
resource " aws_iam_role_policy " " hcptf_apply_services " {
# checkov:skip=CKV_AWS_111: EC2 VPC/instance lifecycle and describe APIs require Resource=*. Log group, alarm, and SNS writes are ARN-prefixed.
name = " syslog-server-services "
role = aws_iam_role . hcptf_apply . id
policy = data . aws_iam_policy_document . hcptf_apply_services . json
}
resource " aws_iam_role_policy " " hcptf_plan_refresh " {
# checkov:skip=CKV_AWS_107: ViewOnlyAccess plus this sidecar is the org HCP plan-role pattern (PLAT-144). Sidecar Get* is named (GetRole, GetPolicy, GetInstanceProfile, GetConsoleOutput, GetTopicAttributes) and scoped to this stack. It does not add iam:CreateAccessKey, secretsmanager:GetSecretValue, or ssm:GetParameter on *.
name = " syslog-server-plan-refresh "
role = aws_iam_role . hcptf_plan . id
policy = data . aws_iam_policy_document . hcptf_plan_refresh . json
}
resource " aws_iam_role_policy_attachment " " hcptf_plan_view_only " {
role = aws_iam_role . hcptf_plan . name
policy_arn = " arn:aws:iam::aws:policy/job-function/ViewOnlyAccess "
}
resource " aws_iam_role_policy_attachments_exclusive " " hcptf_apply " {
role_name = aws_iam_role . hcptf_apply . name
policy_arns = [ ]
}
resource " aws_iam_role_policy_attachments_exclusive " " hcptf_plan " {
role_name = aws_iam_role . hcptf_plan . name
policy_arns = [
" arn:aws:iam::aws:policy/job-function/ViewOnlyAccess " ,
]
}