...
1 package secure
2
3 import (
4 "fmt"
5 "net"
6 "net/http"
7 "strings"
8
9 "github.com/gin-gonic/gin"
10 )
11
12 type (
13
14
15 policy struct {
16
17 config Config
18 fixedHeaders []header
19 }
20
21 header struct {
22 key string
23 value []string
24 }
25 )
26
27
28 func newPolicy(config Config) *policy {
29 policy := &policy{}
30 policy.loadConfig(config)
31 return policy
32 }
33
34 func (p *policy) loadConfig(config Config) {
35 p.config = config
36 p.fixedHeaders = make([]header, 0, 5)
37
38
39 if len(config.CustomFrameOptionsValue) > 0 {
40 p.addHeader("X-Frame-Options", config.CustomFrameOptionsValue)
41 } else if config.FrameDeny {
42 p.addHeader("X-Frame-Options", "DENY")
43 }
44
45
46 if config.ContentTypeNosniff {
47 p.addHeader("X-Content-Type-Options", "nosniff")
48 }
49
50
51 if config.BrowserXssFilter {
52 p.addHeader("X-Xss-Protection", "1; mode=block")
53 }
54
55
56 if len(config.ContentSecurityPolicy) > 0 {
57 p.addHeader("Content-Security-Policy", config.ContentSecurityPolicy)
58 }
59
60 if len(config.ReferrerPolicy) > 0 {
61 p.addHeader("Referrer-Policy", config.ReferrerPolicy)
62 }
63
64
65 if config.STSSeconds != 0 {
66 stsSub := ""
67 if config.STSIncludeSubdomains {
68 stsSub = "; includeSubdomains"
69 }
70
71
72
73 p.addHeader(
74 "Strict-Transport-Security",
75 fmt.Sprintf("max-age=%d%s", config.STSSeconds, stsSub))
76 }
77
78
79 if config.IENoOpen {
80 p.addHeader("X-Download-Options", "noopen")
81 }
82
83
84 if len(config.FeaturePolicy) > 0 {
85 p.addHeader("Feature-Policy", config.FeaturePolicy)
86 }
87 }
88
89 func (p *policy) addHeader(key string, value string) {
90 p.fixedHeaders = append(p.fixedHeaders, header{
91 key: key,
92 value: []string{value},
93 })
94 }
95
96 func (p *policy) applyToContext(c *gin.Context) bool {
97 if !p.config.IsDevelopment {
98 p.writeSecureHeaders(c)
99
100 if !p.checkAllowHosts(c) {
101 return false
102 }
103 if !p.checkSSL(c) {
104 return false
105 }
106 }
107 return true
108 }
109
110 func (p *policy) writeSecureHeaders(c *gin.Context) {
111 header := c.Writer.Header()
112 for _, pair := range p.fixedHeaders {
113 header[pair.key] = pair.value
114 }
115 }
116
117 func (p *policy) checkAllowHosts(c *gin.Context) bool {
118 if len(p.config.AllowedHosts) == 0 {
119 return true
120 }
121
122 host := c.Request.Host
123 if len(host) == 0 {
124 host = c.Request.URL.Host
125 }
126
127 for _, allowedHost := range p.config.AllowedHosts {
128 if strings.EqualFold(allowedHost, host) {
129 return true
130 }
131 }
132
133 if p.config.BadHostHandler != nil {
134 p.config.BadHostHandler(c)
135 } else {
136 c.AbortWithStatus(403)
137 }
138
139 return false
140 }
141
142
143 func isIPV4(host string) bool {
144 if index := strings.IndexByte(host, ':'); index != -1 {
145 host = host[:index]
146 }
147 return net.ParseIP(host) != nil
148 }
149
150 func (p *policy) isSSLRequest(req *http.Request) bool {
151 if strings.EqualFold(req.URL.Scheme, "https") || req.TLS != nil {
152 return true
153 }
154
155 for h, v := range p.config.SSLProxyHeaders {
156 hv, ok := req.Header[h]
157
158 if !ok {
159 continue
160 }
161
162 if strings.EqualFold(hv[0], v) {
163 return true
164 }
165 }
166
167 if p.config.DontRedirectIPV4Hostnames && isIPV4(req.Host) {
168 return true
169 }
170
171 return false
172 }
173
174 func (p *policy) checkSSL(c *gin.Context) bool {
175 if !p.config.SSLRedirect {
176 return true
177 }
178
179 req := c.Request
180 isSSLRequest := p.isSSLRequest(req)
181 if isSSLRequest {
182 return true
183 }
184
185
186
187 url := req.URL
188 url.Scheme = "https"
189 url.Host = req.Host
190
191 if len(p.config.SSLHost) > 0 {
192 url.Host = p.config.SSLHost
193 }
194
195 status := http.StatusMovedPermanently
196 if p.config.SSLTemporaryRedirect {
197 status = http.StatusTemporaryRedirect
198 }
199 c.Redirect(status, url.String())
200 c.Abort()
201 return false
202 }
203
View as plain text