diff --git a/internal/pkg/deploy/cloudformation/stack/transformers.go b/internal/pkg/deploy/cloudformation/stack/transformers.go index 88fb9267082..8f6878e5ffa 100644 --- a/internal/pkg/deploy/cloudformation/stack/transformers.go +++ b/internal/pkg/deploy/cloudformation/stack/transformers.go @@ -537,6 +537,7 @@ func convertStorageOpts(wlName *string, in manifest.Storage) *template.StorageOp } return &template.StorageOpts{ Ephemeral: convertEphemeral(in.Ephemeral), + ReadonlyRootFS: in.ReadonlyRootFS, Volumes: convertVolumes(in.Volumes), MountPoints: convertMountPoints(in.Volumes), EFSPerms: convertEFSPermissions(in.Volumes), diff --git a/internal/pkg/manifest/storage.go b/internal/pkg/manifest/storage.go index a83c79f415a..f0db4f4baf0 100644 --- a/internal/pkg/manifest/storage.go +++ b/internal/pkg/manifest/storage.go @@ -17,13 +17,14 @@ var ( // Storage represents the options for external and native storage. type Storage struct { - Ephemeral *int `yaml:"ephemeral"` - Volumes map[string]*Volume `yaml:"volumes"` // NOTE: keep the pointers because `mergo` doesn't automatically deep merge map's value unless it's a pointer type. + Ephemeral *int `yaml:"ephemeral"` + ReadonlyRootFS *bool `yaml:"readonly_fs"` + Volumes map[string]*Volume `yaml:"volumes"` // NOTE: keep the pointers because `mergo` doesn't automatically deep merge map's value unless it's a pointer type. } // IsEmpty returns empty if the struct has all zero members. func (s *Storage) IsEmpty() bool { - return s.Ephemeral == nil && s.Volumes == nil + return s.Ephemeral == nil && s.Volumes == nil && s.ReadonlyRootFS == nil } func (s *Storage) requiredEnvFeatures() []string { diff --git a/internal/pkg/manifest/storage_test.go b/internal/pkg/manifest/storage_test.go index 848a7183609..4d49d698345 100644 --- a/internal/pkg/manifest/storage_test.go +++ b/internal/pkg/manifest/storage_test.go @@ -257,6 +257,11 @@ func TestStorage_IsEmpty(t *testing.T) { in: Storage{}, wanted: true, }, + "non empty storage with ReadOnlyFS": { + in: Storage{ + ReadonlyRootFS: aws.Bool(true), + }, + }, "non empty storage": { in: Storage{ Volumes: map[string]*Volume{ diff --git a/internal/pkg/manifest/testdata/backend-svc-customhealthcheck.yml b/internal/pkg/manifest/testdata/backend-svc-customhealthcheck.yml index 82f3dc93561..d6d74c13ff5 100644 --- a/internal/pkg/manifest/testdata/backend-svc-customhealthcheck.yml +++ b/internal/pkg/manifest/testdata/backend-svc-customhealthcheck.yml @@ -26,6 +26,9 @@ memory: 512 # Amount of memory in MiB used by the task. count: 1 # Number of tasks that should be running in your service. exec: true # Enable running commands in your container. +storage: + readonly_fs: true # Limit to read-only access to mounted root filesystems by default. + # Optional fields for more advanced use-cases. # #variables: # Pass environment variables as key value pairs. diff --git a/internal/pkg/manifest/testdata/backend-svc-nohealthcheck.yml b/internal/pkg/manifest/testdata/backend-svc-nohealthcheck.yml index d8854755d4c..e23f81f8337 100644 --- a/internal/pkg/manifest/testdata/backend-svc-nohealthcheck.yml +++ b/internal/pkg/manifest/testdata/backend-svc-nohealthcheck.yml @@ -18,6 +18,9 @@ memory: 512 # Amount of memory in MiB used by the task. count: 1 # Number of tasks that should be running in your service. exec: true # Enable running commands in your container. +storage: + readonly_fs: true # Limit to read-only access to mounted root filesystems by default. + # Optional fields for more advanced use-cases. # #variables: # Pass environment variables as key value pairs. diff --git a/internal/pkg/manifest/testdata/lb-svc.yml b/internal/pkg/manifest/testdata/lb-svc.yml index acf26414154..bf8ea1bce0d 100644 --- a/internal/pkg/manifest/testdata/lb-svc.yml +++ b/internal/pkg/manifest/testdata/lb-svc.yml @@ -26,6 +26,9 @@ memory: 512 # Amount of memory in MiB used by the task. count: 1 # Number of tasks that should be running in your service. exec: true # Enable running commands in your container. +storage: + readonly_fs: true # Limit to read-only access to mounted root filesystems by default. + # Optional fields for more advanced use-cases. # #variables: # Pass environment variables as key value pairs. @@ -39,4 +42,4 @@ exec: true # Enable running commands in your container. # test: # count: 2 # Number of tasks to run for the "test" environment. # deployment: # The deployment strategy for the "test" environment. -# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments. +# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments. \ No newline at end of file diff --git a/internal/pkg/manifest/testdata/worker-svc-nosubscribe.yml b/internal/pkg/manifest/testdata/worker-svc-nosubscribe.yml index 2bf3924104b..47ab8e26710 100644 --- a/internal/pkg/manifest/testdata/worker-svc-nosubscribe.yml +++ b/internal/pkg/manifest/testdata/worker-svc-nosubscribe.yml @@ -16,6 +16,10 @@ memory: 512 # Amount of memory in MiB used by the task. count: 1 # Number of tasks that should be running in your service. exec: true # Enable running commands in your container. +storage: + readonly_fs: true # Limit to read-only access to mounted root filesystems by default. + + # You can register to topics from other services. # The events can be be received from an SQS queue via the env var $COPILOT_QUEUE_URI. # subscribe: @@ -36,4 +40,4 @@ exec: true # Enable running commands in your container. # test: # count: 2 # Number of tasks to run for the "test" environment. # deployment: # The deployment strategy for the "test" environment. -# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments. +# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments. \ No newline at end of file diff --git a/internal/pkg/manifest/testdata/worker-svc-subscribe.yml b/internal/pkg/manifest/testdata/worker-svc-subscribe.yml index 457dbabf944..45532afecd8 100644 --- a/internal/pkg/manifest/testdata/worker-svc-subscribe.yml +++ b/internal/pkg/manifest/testdata/worker-svc-subscribe.yml @@ -16,6 +16,10 @@ memory: 512 # Amount of memory in MiB used by the task. count: 1 # Number of tasks that should be running in your service. exec: true # Enable running commands in your container. +storage: + readonly_fs: true # Limit to read-only access to mounted root filesystems by default. + + # The events can be be received from an SQS queue via the env var $COPILOT_QUEUE_URI. subscribe: topics: @@ -37,4 +41,4 @@ subscribe: # test: # count: 2 # Number of tasks to run for the "test" environment. # deployment: # The deployment strategy for the "test" environment. -# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments. +# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments. \ No newline at end of file diff --git a/internal/pkg/manifest/testdata/worker-svc-with-default-fifo-queue.yml b/internal/pkg/manifest/testdata/worker-svc-with-default-fifo-queue.yml index 00e1da5e041..ad30eadcb99 100644 --- a/internal/pkg/manifest/testdata/worker-svc-with-default-fifo-queue.yml +++ b/internal/pkg/manifest/testdata/worker-svc-with-default-fifo-queue.yml @@ -16,6 +16,10 @@ memory: 512 # Amount of memory in MiB used by the task. count: 1 # Number of tasks that should be running in your service. exec: true # Enable running commands in your container. +storage: + readonly_fs: true # Limit to read-only access to mounted root filesystems by default. + + # The events can be be received from an SQS queue via the env var $COPILOT_QUEUE_URI. subscribe: topics: @@ -37,4 +41,4 @@ subscribe: # test: # count: 2 # Number of tasks to run for the "test" environment. # deployment: # The deployment strategy for the "test" environment. -# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments. +# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments. \ No newline at end of file diff --git a/internal/pkg/manifest/validate.go b/internal/pkg/manifest/validate.go index bd4a442ee8e..716efe366bd 100644 --- a/internal/pkg/manifest/validate.go +++ b/internal/pkg/manifest/validate.go @@ -162,6 +162,7 @@ func (l LoadBalancedWebServiceConfig) validate() error { if l.TaskConfig.IsWindows() { if err = validateWindows(validateWindowsOpts{ efsVolumes: l.Storage.Volumes, + readOnlyFS: l.Storage.ReadonlyRootFS, }); err != nil { return fmt.Errorf("validate Windows: %w", err) } @@ -262,6 +263,7 @@ func (b BackendServiceConfig) validate() error { if b.TaskConfig.IsWindows() { if err = validateWindows(validateWindowsOpts{ efsVolumes: b.Storage.Volumes, + readOnlyFS: b.Storage.ReadonlyRootFS, }); err != nil { return fmt.Errorf("validate Windows: %w", err) } @@ -374,6 +376,7 @@ func (w WorkerServiceConfig) validate() error { if w.TaskConfig.IsWindows() { if err = validateWindows(validateWindowsOpts{ efsVolumes: w.Storage.Volumes, + readOnlyFS: w.Storage.ReadonlyRootFS, }); err != nil { return fmt.Errorf(`validate Windows: %w`, err) } @@ -449,6 +452,7 @@ func (s ScheduledJobConfig) validate() error { if s.TaskConfig.IsWindows() { if err = validateWindows(validateWindowsOpts{ efsVolumes: s.Storage.Volumes, + readOnlyFS: s.Storage.ReadonlyRootFS, }); err != nil { return fmt.Errorf(`validate Windows: %w`, err) } @@ -1624,6 +1628,7 @@ type validateTargetContainerOpts struct { } type validateWindowsOpts struct { + readOnlyFS *bool efsVolumes map[string]*Volume } @@ -1772,6 +1777,9 @@ func isValidSubSvcName(name string) bool { } func validateWindows(opts validateWindowsOpts) error { + if aws.BoolValue(opts.readOnlyFS) { + return fmt.Errorf(`%q can not be set to 'true' when deploying a Windows container`, "readonly_fs") + } for _, volume := range opts.efsVolumes { if !volume.EmptyVolume() { return errors.New(`'EFS' is not supported when deploying a Windows container`) diff --git a/internal/pkg/manifest/validate_test.go b/internal/pkg/manifest/validate_test.go index d1d0515f7d3..f8aaeb82e94 100644 --- a/internal/pkg/manifest/validate_test.go +++ b/internal/pkg/manifest/validate_test.go @@ -3269,10 +3269,22 @@ func TestValidateWindows(t *testing.T) { }, wantedError: errors.New(`'EFS' is not supported when deploying a Windows container`), }, - "should return nil efs not specified": { + "should return nil when no fields are specified": { in: validateWindowsOpts{}, wantedError: nil, }, + "error if readonlyfs is true": { + in: validateWindowsOpts{ + readOnlyFS: aws.Bool(true), + }, + wantedError: fmt.Errorf(`%q can not be set to 'true' when deploying a Windows container`, "readonly_fs"), + }, + "should return nil if readonly_fs is false": { + in: validateWindowsOpts{ + readOnlyFS: aws.Bool(false), + }, + wantedError: nil, + }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { diff --git a/internal/pkg/template/templates/workloads/partials/cf/workload-container.yml b/internal/pkg/template/templates/workloads/partials/cf/workload-container.yml index e451cb152ad..595c2ef2c91 100644 --- a/internal/pkg/template/templates/workloads/partials/cf/workload-container.yml +++ b/internal/pkg/template/templates/workloads/partials/cf/workload-container.yml @@ -60,6 +60,9 @@ StartPeriod: {{.HealthCheck.StartPeriod}} Timeout: {{.HealthCheck.Timeout}} {{- end}} +{{- if and .Storage .Storage.ReadonlyRootFS}} + ReadonlyRootFilesystem: {{.Storage.ReadonlyRootFS}} +{{- end}} {{- if .CredentialsParameter}} RepositoryCredentials: CredentialsParameter: {{.CredentialsParameter}} diff --git a/internal/pkg/template/templates/workloads/services/backend/manifest.yml b/internal/pkg/template/templates/workloads/services/backend/manifest.yml index 1e644fdb871..6b4be00d4d2 100644 --- a/internal/pkg/template/templates/workloads/services/backend/manifest.yml +++ b/internal/pkg/template/templates/workloads/services/backend/manifest.yml @@ -44,6 +44,11 @@ count: {{.Count.Value}} # Number of tasks that should be running in your s exec: true # Enable running commands in your container. {{- end}} +storage: +{{- if not .TaskConfig.IsWindows}} + readonly_fs: true # Limit to read-only access to mounted root filesystems by default. +{{- end}} + # Optional fields for more advanced use-cases. # #variables: # Pass environment variables as key value pairs. diff --git a/internal/pkg/template/templates/workloads/services/lb-web/manifest.yml b/internal/pkg/template/templates/workloads/services/lb-web/manifest.yml index 7f0c4f4b7fe..8667f3574bb 100644 --- a/internal/pkg/template/templates/workloads/services/lb-web/manifest.yml +++ b/internal/pkg/template/templates/workloads/services/lb-web/manifest.yml @@ -39,6 +39,11 @@ count: {{.Count.Value}} # Number of tasks that should be running in your s exec: true # Enable running commands in your container. {{- end}} +storage: +{{- if not .TaskConfig.IsWindows}} + readonly_fs: true # Limit to read-only access to mounted root filesystems by default. +{{- end}} + # Optional fields for more advanced use-cases. # #variables: # Pass environment variables as key value pairs. @@ -52,4 +57,4 @@ exec: true # Enable running commands in your container. # test: # count: 2 # Number of tasks to run for the "test" environment. # deployment: # The deployment strategy for the "test" environment. -# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments. +# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments. \ No newline at end of file diff --git a/internal/pkg/template/templates/workloads/services/worker/manifest.yml b/internal/pkg/template/templates/workloads/services/worker/manifest.yml index a2f36c7a064..7c478504886 100644 --- a/internal/pkg/template/templates/workloads/services/worker/manifest.yml +++ b/internal/pkg/template/templates/workloads/services/worker/manifest.yml @@ -34,6 +34,12 @@ count: {{.Count.Value}} # Number of tasks that should be running in your s {{- if not .TaskConfig.IsWindows }} exec: true # Enable running commands in your container. {{- end}} + +storage: +{{- if not .TaskConfig.IsWindows}} + readonly_fs: true # Limit to read-only access to mounted root filesystems by default. +{{- end}} + {{if .Subscribe}}{{- if .Subscribe.Topics}} # The events can be be received from an SQS queue via the env var $COPILOT_QUEUE_URI. subscribe: @@ -71,4 +77,4 @@ subscribe: # test: # count: 2 # Number of tasks to run for the "test" environment. # deployment: # The deployment strategy for the "test" environment. -# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments. +# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments. \ No newline at end of file diff --git a/internal/pkg/template/workload.go b/internal/pkg/template/workload.go index b10baf5933a..9d33608caaf 100644 --- a/internal/pkg/template/workload.go +++ b/internal/pkg/template/workload.go @@ -145,6 +145,7 @@ type SidecarStorageOpts struct { // StorageOpts holds data structures for rendering Volumes and Mount Points type StorageOpts struct { Ephemeral *int + ReadonlyRootFS *bool Volumes []*Volume MountPoints []*MountPoint EFSPerms []*EFSPermission