mirror of
https://github.com/Sea-Haven-Industries/file-share.git
synced 2026-09-30 06:33:17 +00:00
* feat(infra): add HCP Terraform for the prod file share (PLAT-77) The prod host will live on a subnet in the syslog VPC. The data volume stays unmanaged and is attached only after a snapshot copy. * fix(infra): pin FileBrowser version to a release tag (PLAT-77) The version is interpolated into the boot script. Reject anything that is not a vX.Y.Z tag. * fix(infra): keep the file share off the public internet (PLAT-77) The instance has no public IP. Office routes use the syslog VPN gateway and other egress uses a NAT gateway. DLM targets the tagged data volume, and replacement detaches stop the instance first.
529 lines
15 KiB
HCL
529 lines
15 KiB
HCL
# HCP plan/apply roles for file-share-prod (PLAT-77).
|
|
# Copy of the syslog-server EC2 shape, narrowed to this stack.
|
|
# 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 file-share-prod
|
|
# 2. Point this workspace's TFC_AWS_* at hcptf-bootstrap /
|
|
# hcptf-bootstrap-plan (workspace vars, never a project set).
|
|
# 3. One Manual apply. Bootstrap can create these roles and the boundary.
|
|
# Subnet, DLM, and the instance are created on the following apply
|
|
# after TFC_AWS_* points at hcptf-file-share. Tolerate that partial
|
|
# state.
|
|
# 4. Point TFC_AWS_* back at hcptf-file-share / hcptf-file-share-plan.
|
|
# 5. Re-run the create 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.
|
|
|
|
data "aws_iam_policy_document" "hcptf_apply_trust" {
|
|
statement {
|
|
sid = "HcpApply"
|
|
effect = "Allow"
|
|
actions = ["sts:AssumeRoleWithWebIdentity"]
|
|
|
|
principals {
|
|
type = "Federated"
|
|
identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.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::${data.aws_caller_identity.current.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::${data.aws_caller_identity.current.account_id}:role/tf-managed/${local.stack_prefix}*",
|
|
]
|
|
|
|
condition {
|
|
test = "StringLike"
|
|
variable = "iam:PermissionsBoundary"
|
|
values = [
|
|
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/tf-managed/${local.stack_prefix}*",
|
|
]
|
|
}
|
|
}
|
|
|
|
statement {
|
|
sid = "MutateExecRoleWithBoundary"
|
|
effect = "Allow"
|
|
actions = [
|
|
"iam:AttachRolePolicy",
|
|
"iam:PutRolePolicy",
|
|
"iam:PutRolePermissionsBoundary",
|
|
]
|
|
resources = [
|
|
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/tf-managed/${local.stack_prefix}*",
|
|
]
|
|
|
|
condition {
|
|
test = "StringLike"
|
|
variable = "iam:PermissionsBoundary"
|
|
values = [
|
|
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/tf-managed/${local.stack_prefix}*",
|
|
]
|
|
}
|
|
}
|
|
|
|
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::${data.aws_caller_identity.current.account_id}:role/tf-managed/${local.stack_prefix}*",
|
|
]
|
|
}
|
|
|
|
statement {
|
|
sid = "PassExecRoleToEc2"
|
|
effect = "Allow"
|
|
actions = ["iam:PassRole"]
|
|
resources = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/tf-managed/${local.instance_role_name}"]
|
|
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "iam:PassedToService"
|
|
values = ["ec2.amazonaws.com"]
|
|
}
|
|
}
|
|
|
|
statement {
|
|
sid = "PassDlmServiceRole"
|
|
effect = "Allow"
|
|
actions = ["iam:PassRole"]
|
|
resources = [local.dlm_service_role_arn]
|
|
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "iam:PassedToService"
|
|
values = ["dlm.amazonaws.com"]
|
|
}
|
|
}
|
|
|
|
statement {
|
|
sid = "CreateDlmServiceLinkedRole"
|
|
effect = "Allow"
|
|
actions = [
|
|
"iam:CreateServiceLinkedRole",
|
|
]
|
|
resources = [local.dlm_service_role_arn]
|
|
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "iam:AWSServiceName"
|
|
values = ["dlm.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::${data.aws_caller_identity.current.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::${data.aws_caller_identity.current.account_id}:role/hcptf-*",
|
|
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/github-cfn-execution-role",
|
|
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/githubdeploy-*",
|
|
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/cdk-hnb659fds-*",
|
|
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/OrganizationAccountAccessRole",
|
|
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/seahaven-*",
|
|
]
|
|
}
|
|
|
|
statement {
|
|
sid = "DenyBoundaryTampering"
|
|
effect = "Deny"
|
|
actions = [
|
|
"iam:DeleteRolePermissionsBoundary",
|
|
"iam:DeleteUserPermissionsBoundary",
|
|
]
|
|
resources = [
|
|
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/*",
|
|
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/*",
|
|
]
|
|
}
|
|
|
|
statement {
|
|
sid = "DenyBoundaryPolicyEdit"
|
|
effect = "Deny"
|
|
actions = [
|
|
"iam:CreatePolicyVersion",
|
|
"iam:DeletePolicy",
|
|
"iam:DeletePolicyVersion",
|
|
"iam:SetDefaultPolicyVersion",
|
|
]
|
|
resources = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/seahaven-*"]
|
|
}
|
|
|
|
statement {
|
|
sid = "ReadTfManagedBoundary"
|
|
effect = "Allow"
|
|
actions = [
|
|
"iam:GetPolicy",
|
|
"iam:GetPolicyVersion",
|
|
"iam:ListPolicyVersions",
|
|
"iam:ListPolicyTags",
|
|
"iam:TagPolicy",
|
|
"iam:UntagPolicy",
|
|
]
|
|
resources = [
|
|
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/tf-managed/${local.stack_prefix}*",
|
|
]
|
|
}
|
|
}
|
|
|
|
data "aws_iam_policy_document" "hcptf_apply_services" {
|
|
# checkov:skip=CKV_AWS_111: EC2 describe and RunInstances APIs require Resource=*. This role cannot CreateVolume or DeleteVolume.
|
|
statement {
|
|
sid = "Ec2Network"
|
|
effect = "Allow"
|
|
actions = [
|
|
"ec2:AllocateAddress",
|
|
"ec2:AssociateAddress",
|
|
"ec2:AssociateRouteTable",
|
|
"ec2:CreateNatGateway",
|
|
"ec2:CreateRoute",
|
|
"ec2:CreateRouteTable",
|
|
"ec2:AuthorizeSecurityGroupEgress",
|
|
"ec2:AuthorizeSecurityGroupIngress",
|
|
"ec2:CreateSecurityGroup",
|
|
"ec2:CreateSubnet",
|
|
"ec2:CreateTags",
|
|
"ec2:DeleteNatGateway",
|
|
"ec2:DeleteRoute",
|
|
"ec2:DeleteRouteTable",
|
|
"ec2:DeleteSecurityGroup",
|
|
"ec2:DeleteSubnet",
|
|
"ec2:DeleteTags",
|
|
"ec2:DescribeAccountAttributes",
|
|
"ec2:DescribeAddresses",
|
|
"ec2:DescribeAddressesAttribute",
|
|
"ec2:DescribeNatGateways",
|
|
"ec2:DescribeAvailabilityZones",
|
|
"ec2:DescribeImages",
|
|
"ec2:DescribeInstanceAttribute",
|
|
"ec2:DescribeInstanceCreditSpecifications",
|
|
"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:DescribeVpnGateways",
|
|
"ec2:DisassociateAddress",
|
|
"ec2:DisassociateRouteTable",
|
|
"ec2:ReleaseAddress",
|
|
"ec2:ModifySecurityGroupRules",
|
|
"ec2:ModifySubnetAttribute",
|
|
"ec2:RevokeSecurityGroupEgress",
|
|
"ec2:RevokeSecurityGroupIngress",
|
|
"ec2:UpdateSecurityGroupRuleDescriptionsEgress",
|
|
"ec2:UpdateSecurityGroupRuleDescriptionsIngress",
|
|
]
|
|
resources = ["*"]
|
|
}
|
|
|
|
statement {
|
|
sid = "Ec2Instance"
|
|
effect = "Allow"
|
|
actions = [
|
|
"ec2:AssociateIamInstanceProfile",
|
|
"ec2:AttachVolume",
|
|
"ec2:DescribeIamInstanceProfileAssociations",
|
|
"ec2:DetachVolume",
|
|
"ec2:DisassociateIamInstanceProfile",
|
|
"ec2:GetConsoleOutput",
|
|
"ec2:ModifyInstanceAttribute",
|
|
"ec2:MonitorInstances",
|
|
"ec2:ReplaceIamInstanceProfileAssociation",
|
|
"ec2:RunInstances",
|
|
"ec2:StartInstances",
|
|
"ec2:StopInstances",
|
|
"ec2:TerminateInstances",
|
|
"ec2:UnmonitorInstances",
|
|
]
|
|
resources = ["*"]
|
|
}
|
|
|
|
statement {
|
|
sid = "DlmPolicy"
|
|
effect = "Allow"
|
|
actions = [
|
|
"dlm:CreateLifecyclePolicy",
|
|
"dlm:DeleteLifecyclePolicy",
|
|
"dlm:GetLifecyclePolicy",
|
|
"dlm:ListTagsForResource",
|
|
"dlm:TagResource",
|
|
"dlm:UntagResource",
|
|
"dlm:UpdateLifecyclePolicy",
|
|
]
|
|
resources = ["*"]
|
|
}
|
|
}
|
|
|
|
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::${data.aws_caller_identity.current.account_id}:role/tf-managed/${local.stack_prefix}*",
|
|
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:instance-profile/tf-managed/${local.stack_prefix}*",
|
|
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.apply_role}",
|
|
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.plan_role}",
|
|
]
|
|
}
|
|
|
|
statement {
|
|
sid = "RefreshManagedPolicies"
|
|
effect = "Allow"
|
|
actions = [
|
|
"iam:GetPolicy",
|
|
"iam:GetPolicyVersion",
|
|
]
|
|
resources = [
|
|
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/tf-managed/${local.stack_prefix}*",
|
|
"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
|
|
"arn:aws:iam::aws:policy/job-function/ViewOnlyAccess",
|
|
]
|
|
}
|
|
|
|
statement {
|
|
sid = "RefreshEc2"
|
|
effect = "Allow"
|
|
actions = [
|
|
"ec2:DescribeAccountAttributes",
|
|
"ec2:DescribeAddresses",
|
|
"ec2:DescribeAddressesAttribute",
|
|
"ec2:DescribeAvailabilityZones",
|
|
"ec2:DescribeIamInstanceProfileAssociations",
|
|
"ec2:DescribeImages",
|
|
"ec2:DescribeInstanceAttribute",
|
|
"ec2:DescribeInstanceCreditSpecifications",
|
|
"ec2:DescribeInstanceStatus",
|
|
"ec2:DescribeInstanceTypes",
|
|
"ec2:DescribeInstances",
|
|
"ec2:DescribeInternetGateways",
|
|
"ec2:DescribeNatGateways",
|
|
"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:DescribeVpnGateways",
|
|
"ec2:GetConsoleOutput",
|
|
]
|
|
resources = ["*"]
|
|
}
|
|
|
|
statement {
|
|
sid = "RefreshDlm"
|
|
effect = "Allow"
|
|
actions = [
|
|
"dlm:GetLifecyclePolicy",
|
|
"dlm:ListTagsForResource",
|
|
]
|
|
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 describe and RunInstances APIs require Resource=*. This role cannot CreateVolume or DeleteVolume.
|
|
name = "file-share-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 HCP plan-role pattern. Actions are named. It does not add iam:CreateAccessKey, secretsmanager:GetSecretValue, or ssm:GetParameter on *.
|
|
name = "file-share-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",
|
|
]
|
|
}
|