FIX: Permissions To s3 And MediaConvert]
Build, Push and Deploy CMS / build-push-deploy (push) Successful in 2m2s
Build, Push and Deploy CMS / build-push-deploy (push) Successful in 2m2s
This commit is contained in:
+5
-4
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"thamanyah/cms/v2/internal/handlers"
|
"thamanyah/cms/v2/internal/handlers"
|
||||||
"thamanyah/cms/v2/internal/services"
|
"thamanyah/cms/v2/internal/services"
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ func main() {
|
|||||||
|
|
||||||
concreteS3Client := &services.S3Concrete{
|
concreteS3Client := &services.S3Concrete{
|
||||||
S3Client: s3.NewFromConfig(config),
|
S3Client: s3.NewFromConfig(config),
|
||||||
Bucket: "",
|
Bucket: os.Getenv("S3_BUCKET"),
|
||||||
}
|
}
|
||||||
|
|
||||||
concreteS3Client.AssertSuccessfulConnection(context.Background())
|
concreteS3Client.AssertSuccessfulConnection(context.Background())
|
||||||
@@ -30,9 +31,9 @@ func main() {
|
|||||||
|
|
||||||
services.MediaConvertClient = &services.MediaConvertConcrete{
|
services.MediaConvertClient = &services.MediaConvertConcrete{
|
||||||
MediaConvertClient: mediaconvert.NewFromConfig(config),
|
MediaConvertClient: mediaconvert.NewFromConfig(config),
|
||||||
Role: "test",
|
Role: os.Getenv("MEDIACONVERT_ROLE_ARN"),
|
||||||
InputBucket: "",
|
InputBucket: os.Getenv("MEDIACONVERT_INPUT_BUCKET"),
|
||||||
OutputBucket: "",
|
OutputBucket: os.Getenv("MEDIACONVERT_OUTPUT_BUCKET"),
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|||||||
+191
-11
@@ -80,17 +80,29 @@ func newServiceDatabase(ctx *pulumi.Context, provider *postgresql.Provider, serv
|
|||||||
return password, nil
|
return password, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// envVar is a name/value pair for a container's extra environment variables,
|
||||||
|
// where the value is only known after other resources are provisioned.
|
||||||
|
type envVar struct {
|
||||||
|
Name string
|
||||||
|
Value pulumi.StringOutput
|
||||||
|
}
|
||||||
|
|
||||||
// deployFargateService gives a service its own ECR repo, log group, task
|
// deployFargateService gives a service its own ECR repo, log group, task
|
||||||
// definition, ECS service, and its own ALB — so it gets its own DNS name
|
// definition, ECS service, and its own ALB — so it gets its own DNS name
|
||||||
// rather than sharing one load balancer with the other service on a
|
// rather than sharing one load balancer with the other service on a
|
||||||
// different port. The service's DB password is stored in Secrets Manager and
|
// different port. The service's DB password is stored in Secrets Manager and
|
||||||
// injected into the container as a secret rather than a plaintext env var.
|
// injected into the container as a secret rather than a plaintext env var.
|
||||||
|
// taskRole is optional (nil means no TaskRoleArn is set, i.e. the container
|
||||||
|
// gets no AWS identity of its own beyond execRole's pull/logs permissions);
|
||||||
|
// extraEnv is appended to the container's environment on top of the DB_* vars.
|
||||||
func deployFargateService(
|
func deployFargateService(
|
||||||
ctx *pulumi.Context,
|
ctx *pulumi.Context,
|
||||||
name string,
|
name string,
|
||||||
containerPort int,
|
containerPort int,
|
||||||
cluster *ecs.Cluster,
|
cluster *ecs.Cluster,
|
||||||
execRole *iam.Role,
|
execRole *iam.Role,
|
||||||
|
taskRole *iam.Role,
|
||||||
|
extraEnv []envVar,
|
||||||
vpcID string,
|
vpcID string,
|
||||||
subnetIDs []string,
|
subnetIDs []string,
|
||||||
albSecurityGroup *ec2.SecurityGroup,
|
albSecurityGroup *ec2.SecurityGroup,
|
||||||
@@ -194,7 +206,12 @@ func deployFargateService(
|
|||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
containerDefinitions := pulumi.All(repo.RepositoryUrl, logGroup.Name, secret.Arn, dbAddress, dbPort).ApplyT(
|
baseArgs := []any{repo.RepositoryUrl, logGroup.Name, secret.Arn, dbAddress, dbPort}
|
||||||
|
for _, ev := range extraEnv {
|
||||||
|
baseArgs = append(baseArgs, ev.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerDefinitions := pulumi.All(baseArgs...).ApplyT(
|
||||||
func(args []any) (string, error) {
|
func(args []any) (string, error) {
|
||||||
image := args[0].(string) + ":latest"
|
image := args[0].(string) + ":latest"
|
||||||
logGroupName := args[1].(string)
|
logGroupName := args[1].(string)
|
||||||
@@ -202,6 +219,16 @@ func deployFargateService(
|
|||||||
dbHost := args[3].(string)
|
dbHost := args[3].(string)
|
||||||
dbPort := args[4].(int)
|
dbPort := args[4].(int)
|
||||||
|
|
||||||
|
environment := []map[string]string{
|
||||||
|
{"name": "DB_HOST", "value": dbHost},
|
||||||
|
{"name": "DB_PORT", "value": fmt.Sprintf("%d", dbPort)},
|
||||||
|
{"name": "DB_NAME", "value": name},
|
||||||
|
{"name": "DB_USER", "value": name},
|
||||||
|
}
|
||||||
|
for i, ev := range extraEnv {
|
||||||
|
environment = append(environment, map[string]string{"name": ev.Name, "value": args[5+i].(string)})
|
||||||
|
}
|
||||||
|
|
||||||
def := []map[string]any{
|
def := []map[string]any{
|
||||||
{
|
{
|
||||||
"name": name,
|
"name": name,
|
||||||
@@ -209,12 +236,7 @@ func deployFargateService(
|
|||||||
"portMappings": []map[string]any{
|
"portMappings": []map[string]any{
|
||||||
{"containerPort": containerPort, "protocol": "tcp"},
|
{"containerPort": containerPort, "protocol": "tcp"},
|
||||||
},
|
},
|
||||||
"environment": []map[string]string{
|
"environment": environment,
|
||||||
{"name": "DB_HOST", "value": dbHost},
|
|
||||||
{"name": "DB_PORT", "value": fmt.Sprintf("%d", dbPort)},
|
|
||||||
{"name": "DB_NAME", "value": name},
|
|
||||||
{"name": "DB_USER", "value": name},
|
|
||||||
},
|
|
||||||
"secrets": []map[string]string{
|
"secrets": []map[string]string{
|
||||||
{"name": "DB_PASSWORD", "valueFrom": secretArn},
|
{"name": "DB_PASSWORD", "valueFrom": secretArn},
|
||||||
},
|
},
|
||||||
@@ -233,7 +255,7 @@ func deployFargateService(
|
|||||||
},
|
},
|
||||||
).(pulumi.StringOutput)
|
).(pulumi.StringOutput)
|
||||||
|
|
||||||
taskDefinition, err := ecs.NewTaskDefinition(ctx, name+"-task", &ecs.TaskDefinitionArgs{
|
taskDefinitionArgs := &ecs.TaskDefinitionArgs{
|
||||||
Family: pulumi.String(name),
|
Family: pulumi.String(name),
|
||||||
Cpu: pulumi.String("256"),
|
Cpu: pulumi.String("256"),
|
||||||
Memory: pulumi.String("512"),
|
Memory: pulumi.String("512"),
|
||||||
@@ -241,7 +263,12 @@ func deployFargateService(
|
|||||||
RequiresCompatibilities: pulumi.StringArray{pulumi.String("FARGATE")},
|
RequiresCompatibilities: pulumi.StringArray{pulumi.String("FARGATE")},
|
||||||
ExecutionRoleArn: execRole.Arn,
|
ExecutionRoleArn: execRole.Arn,
|
||||||
ContainerDefinitions: containerDefinitions,
|
ContainerDefinitions: containerDefinitions,
|
||||||
})
|
}
|
||||||
|
if taskRole != nil {
|
||||||
|
taskDefinitionArgs.TaskRoleArn = taskRole.Arn
|
||||||
|
}
|
||||||
|
|
||||||
|
taskDefinition, err := ecs.NewTaskDefinition(ctx, name+"-task", taskDefinitionArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -579,15 +606,168 @@ func main() {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Private bucket for raw video uploads (pre-transcode). Kept separate
|
||||||
|
// from encoded-bucket, which is fronted by CloudFront/OAC for public
|
||||||
|
// delivery of finished renditions — raw source video must not be
|
||||||
|
// reachable through that CDN.
|
||||||
|
rawUploadsBucket, err := s3.NewBucket(ctx, "raw-uploads-bucket", nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s3.NewBucketPublicAccessBlock(ctx, "raw-uploads-bucket-public-access-block", &s3.BucketPublicAccessBlockArgs{
|
||||||
|
Bucket: rawUploadsBucket.ID(),
|
||||||
|
BlockPublicAcls: pulumi.Bool(true),
|
||||||
|
BlockPublicPolicy: pulumi.Bool(true),
|
||||||
|
IgnorePublicAcls: pulumi.Bool(true),
|
||||||
|
RestrictPublicBuckets: pulumi.Bool(true),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Role MediaConvert itself assumes (passed as CreateJobInput.Role by
|
||||||
|
// the cms service) to read the raw upload and write the encoded
|
||||||
|
// output — distinct from the ECS task role below.
|
||||||
|
mediaConvertAssumePolicy, err := json.Marshal(map[string]any{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": []map[string]any{
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "sts:AssumeRole",
|
||||||
|
"Principal": map[string]string{"Service": "mediaconvert.amazonaws.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaConvertRole, err := iam.NewRole(ctx, "mediaconvert-service-role", &iam.RoleArgs{
|
||||||
|
AssumeRolePolicy: pulumi.String(mediaConvertAssumePolicy),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaConvertS3Policy := pulumi.All(rawUploadsBucket.Arn, bucket.Arn).ApplyT(
|
||||||
|
func(args []any) (string, error) {
|
||||||
|
rawUploadsArn := args[0].(string)
|
||||||
|
encodedBucketArn := args[1].(string)
|
||||||
|
|
||||||
|
doc := map[string]any{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": []map[string]any{
|
||||||
|
{
|
||||||
|
"Sid": "ReadRawUploads",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "s3:GetObject",
|
||||||
|
"Resource": rawUploadsArn + "/*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Sid": "WriteEncodedOutput",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "s3:PutObject",
|
||||||
|
"Resource": encodedBucketArn + "/*",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(doc)
|
||||||
|
return string(b), err
|
||||||
|
},
|
||||||
|
).(pulumi.StringOutput)
|
||||||
|
|
||||||
|
_, err = iam.NewRolePolicy(ctx, "mediaconvert-s3-access", &iam.RolePolicyArgs{
|
||||||
|
Role: mediaConvertRole.ID(),
|
||||||
|
Policy: mediaConvertS3Policy,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// cms's own ECS task role (distinct from the shared execRole, which
|
||||||
|
// is only for the ECS agent's pull/logs/secrets access): lets the
|
||||||
|
// running container call S3 and MediaConvert directly. discovery
|
||||||
|
// gets none of this since it never touches either API.
|
||||||
|
cmsTaskRole, err := iam.NewRole(ctx, "cms-task-role", &iam.RoleArgs{
|
||||||
|
AssumeRolePolicy: pulumi.String(execRoleAssumePolicy),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmsTaskPolicy := pulumi.All(rawUploadsBucket.Arn, mediaConvertRole.Arn).ApplyT(
|
||||||
|
func(args []any) (string, error) {
|
||||||
|
rawUploadsArn := args[0].(string)
|
||||||
|
mediaConvertRoleArn := args[1].(string)
|
||||||
|
|
||||||
|
doc := map[string]any{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": []map[string]any{
|
||||||
|
{
|
||||||
|
"Sid": "ListRawUploadsBucket",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "s3:ListBucket",
|
||||||
|
"Resource": rawUploadsArn,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Sid": "RawUploadsObjectAccess",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": []string{
|
||||||
|
"s3:PutObject",
|
||||||
|
"s3:GetObject",
|
||||||
|
},
|
||||||
|
"Resource": rawUploadsArn + "/*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Sid": "CreateTranscodeJob",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "mediaconvert:CreateJob",
|
||||||
|
"Resource": "*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Sid": "PassMediaConvertRole",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "iam:PassRole",
|
||||||
|
"Resource": mediaConvertRoleArn,
|
||||||
|
"Condition": map[string]any{
|
||||||
|
"StringEquals": map[string]string{
|
||||||
|
"iam:PassedToService": "mediaconvert.amazonaws.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(doc)
|
||||||
|
return string(b), err
|
||||||
|
},
|
||||||
|
).(pulumi.StringOutput)
|
||||||
|
|
||||||
|
_, err = iam.NewRolePolicy(ctx, "cms-task-role-policy", &iam.RolePolicyArgs{
|
||||||
|
Role: cmsTaskRole.ID(),
|
||||||
|
Policy: cmsTaskPolicy,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmsExtraEnv := []envVar{
|
||||||
|
{Name: "AWS_REGION", Value: pulumi.String(awsRegion).ToStringOutput()},
|
||||||
|
{Name: "S3_BUCKET", Value: rawUploadsBucket.ID().ToStringOutput()},
|
||||||
|
{Name: "MEDIACONVERT_INPUT_BUCKET", Value: rawUploadsBucket.ID().ToStringOutput()},
|
||||||
|
{Name: "MEDIACONVERT_OUTPUT_BUCKET", Value: bucket.ID().ToStringOutput()},
|
||||||
|
{Name: "MEDIACONVERT_ROLE_ARN", Value: mediaConvertRole.Arn},
|
||||||
|
}
|
||||||
|
|
||||||
cmsRepo, cmsService, cmsAlb, err := deployFargateService(ctx, "cms", 8081,
|
cmsRepo, cmsService, cmsAlb, err := deployFargateService(ctx, "cms", 8081,
|
||||||
cluster, execRole, vpc.Id, subnets.Ids, albSecurityGroup, serviceSecurityGroup,
|
cluster, execRole, cmsTaskRole, cmsExtraEnv, vpc.Id, subnets.Ids, albSecurityGroup, serviceSecurityGroup,
|
||||||
db.Address, db.Port, cmsPassword)
|
db.Address, db.Port, cmsPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
discoveryRepo, discoveryService, discoveryAlb, err := deployFargateService(ctx, "discovery", 8080,
|
discoveryRepo, discoveryService, discoveryAlb, err := deployFargateService(ctx, "discovery", 8080,
|
||||||
cluster, execRole, vpc.Id, subnets.Ids, albSecurityGroup, serviceSecurityGroup,
|
cluster, execRole, nil, nil, vpc.Id, subnets.Ids, albSecurityGroup, serviceSecurityGroup,
|
||||||
db.Address, db.Port, discoveryPassword)
|
db.Address, db.Port, discoveryPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user