Serving Static Files with Custom Headers using Golang
Tags: go, security, owasp
Published: 2021-06-11 06:08:18

## Basic Golang Static File Server

One of the common uses for Golang is to create servers to serve content, be it an API, or serve some files. Develop APIs long enough and soon you'll find yourself needing to serve static content such as HTML, JS, CSS.

Let's use the following as an example:

index.html: 
```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Basic HTML Document</title>
  </head>
  <link
    href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css"
    rel="stylesheet"
    integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x"
    crossorigin="anonymous"
  />
  <link rel="stylesheet" href="style.css">
  <body>
    <h1><TITLE></TITLE></h1>
    <script src="script.js"></script>
    <div class="blue">This is Blue</div>
    <button class="btn btn-primary m-5">Bootstrap Button</button>
  </body>
</html>
```

script.js:
```js
document.write('this comes from a script')
```

style.css:
```css
.blue {
  background-color: blue;
  color: white;
}
```

The first step is to put the following files inside a folder we conveniently named `static`. Next we create a `main.go` at the root directory, and the folder structure of your project will look like this:

```sh
.
├── main.go
└── static
    ├── index.html
    ├── script.js
    └── style.css
```

A simple version of a file serving server written in Go takes advantage of the `http.FileServer` method.

main.go:
```go
package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	fs := http.FileServer(http.Dir("./static"))
	http.Handle("/", fs)

	fmt.Printf("Server started at port 3000")
	err := http.ListenAndServe(":3000", nil)
	if err == nil {
		log.Fatal(err)
	}
}
```

Running the above with `go run main.go` and browsing http://localhost:3000 will show you the following page:

</br>
![Example Static Site](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7dufp9sj5krfspuds81q.png)
<figcaption>Example Static Site</figcaption>
 
</br>
Now if that is all you need, you would do well to stop here and be on your merry way. However, often times in production you may want to add certain headers to the docume
 nt request response for security purposes. 

## X-Frame-Options Header Ramblings

An example of such a header is the `X-Frame-Options` header. A part of Clickjacking attacks is that the attacker may embed the main site in an `<iframe>` tag, thus allowing the attacker's site to snoop on the user's input while impersonating a different site.


</br>
![Clickjacking Example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztnuc95ewk8io7ixyquh.png)
<figcaption>Clickjacking Example</figcaption>

</br>
What the `X-Frame-Options` header does is that it tells the browser that it only allows the domain of say `legitsite.com` to put the response inside an `<iframe>` tag, so the illegitimate `leg1tsite.com` would not be able to put the content of `legitsite.com` inside an `<iframe>` because the browser would not agree to render it.

</br>
![Clickjacking Prevention](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tnvp431diwyitiufm7w0.png)
<figcaption>Clickjacking Prevention</figcaption>

</br>
Of course this is not the only security header required to secure a website, but for more information on common web vulnerabilities do check out [owasp.org](owasp.org)

## Implementing headers using `http.FileServer`

So the idea of implementing headers is by creating a wrapper over the `fs` variable, serving as a middleware. Of course, while we are applying the `X-Frame-Options` header in the wrapper, one can do all sorts of preprocessing in the wrapper, allowing you to solve more problems than just adding headers. `main.go` then is changed to the following:

main.go:
```go
package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	fs := http.FileServer(http.Dir("./static"))
	http.Handle("/", addHeaders(fs))

	fmt.Printf("Server started at port 3000")
	err := http.ListenAndServe(":3000", nil)
	if err == nil {
		log.Fatal(err)
	}
}

func addHeaders(fs http.Handler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("X-Frame-Options", "DENY")
		fs.ServeHTTP(w, r)
	}
}
```

With this change, you are done! All document requests to localhost will have the `X-Frame-Options` header, as well as any headers you wish to add.

</br>
![Response With Added Headers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9fq08tc6vzgnvfuakj5v.png)
<figcaption>Response With Added Headers</figcaption>

</br>