diff --git a/src/staticfile/finalize/data.go b/src/staticfile/finalize/data.go index 2c7e0a562..d68887947 100644 --- a/src/staticfile/finalize/data.go +++ b/src/staticfile/finalize/data.go @@ -114,7 +114,7 @@ http { {{end}} {{if .HSTS}} - add_header Strict-Transport-Security "max-age=31536000"; + add_header Strict-Transport-Security "max-age=31536000{{if .HSTSIncludeSubDomains}}; includeSubDomains{{end}}{{if .HSTSPreload}}; preload{{end}}"; {{end}} {{if ne .LocationInclude ""}} diff --git a/src/staticfile/finalize/finalize.go b/src/staticfile/finalize/finalize.go index df749efe9..78b5193c6 100644 --- a/src/staticfile/finalize/finalize.go +++ b/src/staticfile/finalize/finalize.go @@ -14,15 +14,17 @@ import ( ) type Staticfile struct { - RootDir string `yaml:"root"` - HostDotFiles bool `yaml:"host_dot_files"` - LocationInclude string `yaml:"location_include"` - DirectoryIndex bool `yaml:"directory"` - SSI bool `yaml:"ssi"` - PushState bool `yaml:"pushstate"` - HSTS bool `yaml:"http_strict_transport_security"` - ForceHTTPS bool `yaml:"force_https"` - BasicAuth bool + RootDir string `yaml:"root"` + HostDotFiles bool `yaml:"host_dot_files"` + LocationInclude string `yaml:"location_include"` + DirectoryIndex bool `yaml:"directory"` + SSI bool `yaml:"ssi"` + PushState bool `yaml:"pushstate"` + HSTS bool `yaml:"http_strict_transport_security"` + HSTSIncludeSubDomains bool `yaml:"http_strict_transport_security_include_subdomains"` + HSTSPreload bool `yaml:"http_strict_transport_security_preload"` + ForceHTTPS bool `yaml:"force_https"` + BasicAuth bool } type YAML interface { @@ -149,6 +151,16 @@ func (sf *Finalizer) LoadStaticfile() error { sf.Log.BeginStep("Enabling HSTS") conf.HSTS = true } + case "http_strict_transport_security_include_subdomains": + if isEnabled { + sf.Log.BeginStep("Enabling HSTS includeSubDomains") + conf.HSTSIncludeSubDomains = true + } + case "http_strict_transport_security_preload": + if isEnabled { + sf.Log.BeginStep("Enabling HSTS Preload") + conf.HSTSPreload = true + } case "force_https": if isEnabled { sf.Log.BeginStep("Enabling HTTPS redirect") @@ -157,6 +169,11 @@ func (sf *Finalizer) LoadStaticfile() error { } } + if !conf.HSTS && (conf.HSTSIncludeSubDomains || conf.HSTSPreload) { + sf.Log.Warning("http_strict_transport_security is not enabled while http_strict_transport_security_include_subdomains or http_strict_transport_security_preload have been enabled.") + sf.Log.Protip("http_strict_transport_security_include_subdomains and http_strict_transport_security_preload do nothing without http_strict_transport_security enabled.", "http://docs.cloudfoundry.org/buildpacks/staticfile/index.html#strict-security") + } + authFile := filepath.Join(sf.BuildDir, "Staticfile.auth") _, err = os.Stat(authFile) if err == nil { diff --git a/src/staticfile/finalize/finalize_test.go b/src/staticfile/finalize/finalize_test.go index 9f4fa0eb8..e0360ca89 100644 --- a/src/staticfile/finalize/finalize_test.go +++ b/src/staticfile/finalize/finalize_test.go @@ -135,6 +135,8 @@ var _ = Describe("Compile", func() { Expect(finalizer.Config.SSI).To(Equal(false)) Expect(finalizer.Config.PushState).To(Equal(false)) Expect(finalizer.Config.HSTS).To(Equal(false)) + Expect(finalizer.Config.HSTSIncludeSubDomains).To(Equal(false)) + Expect(finalizer.Config.HSTSPreload).To(Equal(false)) Expect(finalizer.Config.ForceHTTPS).To(Equal(false)) Expect(finalizer.Config.BasicAuth).To(Equal(false)) }) @@ -237,7 +239,7 @@ var _ = Describe("Compile", func() { (*hash)["http_strict_transport_security"] = "true" }) }) - It("sets pushstate", func() { + It("sets http_strict_transport_security", func() { Expect(finalizer.Config.HSTS).To(Equal(true)) }) It("Logs", func() { @@ -245,6 +247,36 @@ var _ = Describe("Compile", func() { }) }) + Context("and sets http_strict_transport_security_include_subdomains", func() { + BeforeEach(func() { + mockYaml.EXPECT().Load(filepath.Join(buildDir, "Staticfile"), gomock.Any()).Do(func(_ string, hash *map[string]string) { + (*hash)["http_strict_transport_security_include_subdomains"] = "true" + }) + }) + It("sets http_strict_transport_security_include_subdomains", func() { + Expect(finalizer.Config.HSTSIncludeSubDomains).To(Equal(true)) + Expect(finalizer.Config.HSTS).To(Equal(false)) + }) + It("Logs", func() { + Expect(buffer.String()).To(ContainSubstring("-----> Enabling HSTS includeSubDomains\n")) + }) + }) + + Context("and sets http_strict_transport_security_preload", func() { + BeforeEach(func() { + mockYaml.EXPECT().Load(filepath.Join(buildDir, "Staticfile"), gomock.Any()).Do(func(_ string, hash *map[string]string) { + (*hash)["http_strict_transport_security_preload"] = "true" + }) + }) + It("sets http_strict_transport_security_preload", func() { + Expect(finalizer.Config.HSTSPreload).To(Equal(true)) + Expect(finalizer.Config.HSTS).To(Equal(false)) + }) + It("Logs", func() { + Expect(buffer.String()).To(ContainSubstring("-----> Enabling HSTS Preload\n")) + }) + }) + Context("and sets force_https", func() { BeforeEach(func() { mockYaml.EXPECT().Load(filepath.Join(buildDir, "Staticfile"), gomock.Any()).Do(func(_ string, hash *map[string]string) { @@ -622,6 +654,31 @@ var _ = Describe("Compile", func() { }) }) + Context("http_strict_transport_security and http_strict_transport_security_include_subdomain is set in staticfile", func() { + BeforeEach(func() { + staticfile.HSTS = true + staticfile.HSTSIncludeSubDomains = true + }) + It("it adds the HSTS header", func() { + data, err = ioutil.ReadFile(filepath.Join(buildDir, "nginx", "conf", "nginx.conf")) + Expect(err).To(BeNil()) + Expect(string(data)).To(ContainSubstring(`add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";`)) + }) + }) + + Context("http_strict_transport_security, http_strict_transport_security_include_subdomain, and http_strict_transport_security_preload is set in staticfile", func() { + BeforeEach(func() { + staticfile.HSTS = true + staticfile.HSTSIncludeSubDomains = true + staticfile.HSTSPreload = true + }) + It("it adds the HSTS header", func() { + data, err = ioutil.ReadFile(filepath.Join(buildDir, "nginx", "conf", "nginx.conf")) + Expect(err).To(BeNil()) + Expect(string(data)).To(ContainSubstring(`add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";`)) + }) + }) + Context("http_strict_transport_security is NOT set in staticfile", func() { BeforeEach(func() { staticfile.HSTS = false @@ -633,6 +690,19 @@ var _ = Describe("Compile", func() { }) }) + Context("http_strict_transport_security is NOT set in staticfile, but http_strict_transport_security_preload or http_strict_transport_security_include_subdomain are set in staticfile", func() { + BeforeEach(func() { + staticfile.HSTS = false + staticfile.HSTSIncludeSubDomains = true + staticfile.HSTSPreload = true + }) + It("it does not add the HSTS header", func() { + data, err = ioutil.ReadFile(filepath.Join(buildDir, "nginx", "conf", "nginx.conf")) + Expect(err).To(BeNil()) + Expect(string(data)).NotTo(ContainSubstring(`add_header Strict-Transport-Security "max-age=31536000";`)) + }) + }) + Context("force_https is set in staticfile", func() { BeforeEach(func() { staticfile.ForceHTTPS = true