Security Checklist
for a Go Developer

Elena Grahovac

GopherCon Russia 2019

Video (Russian, original): https://youtu.be/7g90BhAzWtk
Video (English, translated): https://youtu.be/I3O9WOixg28

The role of Security in DevOps

Stage

Examples of Security Input

Define Way of Working

General Requirements, Policies

Design

Threat Modelling, Design Guidelines

Code

Coding Rules, Codereview, Tools

Test

Testing Plan, Toolkits

Deploy

Configuration, Checklists

Monitor

Incident Management, Scanning, Pen Test

We know that in a world of DevOps it’s critically important to involve security as much as possible. We also know that DevOps is a culture of trust and personal responsibility.

Lead TechOps Automation Engineer @N26

DevOps Enthusiast

hello@grahovac.pro

webdeva

rumyantseva

I’m not a Security Engineer, I’m a Software Developer but I believe that it’s my personal responsibility to make my applications reliable and secure. Which practices, tools and techniques might help me to support requested level of security in my applications?

Security Onion Model

Data

Code

Container

PaaS

Cloud

Dependencies

Let’s consider a cloud service as an onion. These are the layers we can include in such model.

Security Onion Model

Data

Code

Container

Dependencies

As you know, usually, PaaS and Cloud are covered by infrastructure engineers but developers are involved in four top layers of this onion.

Data

Let’s start from Data

Data checklist

  • Passwords & sensitive data
  • Random data generation
  • Secrets
  • User data

Passwords

-- PostgreSQL

UPDATE credentials

SET password = crypt('new-password', gen_salt('bf'));

If we talk about data and security, passwords is probably the first thing which comes to mind.

We know that when we talk about password encryption we can rely on the storage like in this example we rely on the database.

math/rand

package main

import (

"fmt"

"math/rand"

)

func main() {

rand.Seed(1)

b := make([]byte, 4)

_, err := rand.Read(b)

if err != nil {

fmt.Println("error: ", err)

return

}

fmt.Println(b)

}

But what if we need to generate a random password or key or some other sequence?

math/rand + seed

package main

import (

"fmt"

"math/rand"

"time"

)

func main() {

rand.Seed(time.Now().Unix())

b := make([]byte, 4)

_, err := rand.Read(b)

if err != nil {

fmt.Println("error: ", err)

return

}

fmt.Println(b)

}

We can seed rand with timestamp but is it good enough? In fact, it’s still deterministic

crypto/rand

package main

import (

"crypto/rand"

"fmt"

)

func main() {

b := make([]byte, 4)

_, err := rand.Read(b)

if err != nil {

fmt.Println("error: ", err)

return

}

fmt.Println(b)

}

Luckily, we have the crypto/rand library which offers a non-deterministic way to generate random sequences.

Secrets

Our applications interact with a lot of sensitive data internally: a database password, or an API key are as much important as user data. As we can see from this example, some people don’t really care about their secrets.

Secrets

...Other people forget to clean their git history

Secrets: Kubernetes case study

  • How the data of secrets are stored?
  • Who can access and modify secrets?
  • Do you use secrets as ENV parameters?

So, I mustn’t store my secrets in source code.

FROM busybox

ENV HELLO world

RUN mkdir /www && touch /www/index.html

EXPOSE 8000

CMD httpd -p 8000 -h /www -f & wait

# How to try:

# docker build -t hello .

# docker run hello

# docker exec $(docker ps -q -l) cat /proc/1/environ

Secrets: Solutions

Data

Code

Next layer: code

Code checklist

  • Input validation & sanitizing
  • Error & panic handling
  • Diagnostics handlers visibility
  • Static code analysis
  • Fuzzy testing

Validate & sanitize input

  • SQL injections
  • XSS attacks
  • Unexpected data

It’s pretty straightforward that we should validate and sanitize data from users to prevent SQL injections, XSS attacks or just don’t allow any other data break the application. What developers sometimes forget is that it might make sense to also validate import from “trusted” sources.

Additional examples. XSS

As we mostly use Go for backend, potential XSS attacks are not that often. Anyway, if you build a webpage with Go, don’t forget to choose the right solution (e.g. here we compare text/template and html/template libraries).

Additional examples. SQL injection

Diagnostics handlers

package pprofed

import (

"net/http"

_ "net/http/pprof" // add pprof handlers

)

func serve(addr string) {

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

// provide some business logic

})

http.ListenAndServe(addr, nil)

}

Don’t forget to hide your diagnostics handlers!

Diagnostics handlers

r.HandleFunc("/diag/pprof", pprof.Index)

r.HandleFunc("/diag/cmdline", pprof.Cmdline)

r.HandleFunc("/diag/profile", pprof.Profile)

r.HandleFunc("/diag/symbol", pprof.Symbol)

r.HandleFunc("/diag/trace", pprof.Trace)

r.Handle("/diag/goroutine", pprof.Handler("goroutine"))

r.Handle("/diag/heap", pprof.Handler("heap"))

r.Handle("/diag/threadcreate", pprof.Handler("threadcreate"))

r.Handle("/diag/block", pprof.Handler("block"))

http.ListenAndServe(addr, r)

It’s not really straightforward but actually you can redefine endpoints

Static code analysis: gosec

Results:

[examples/random/math-seed-time/main.go:13] - G404: Use of weak random number

generator (math/rand instead of crypto/rand) (Confidence: MEDIUM, Severity: HIGH)

> rand.Read(b)

[examples/sql-injection/main.go:14-17]

- G201: SQL string formatting (Confidence: HIGH, Severity: MEDIUM)

> fmt.Sprintf(

"INSERT INTO users (name) VALUES %s",

strings.Join(values, ","),

)

Summary:

Files: 10

Lines: 181

Issues: 11

Luckily, linters like gosec can help you to prevent some vulnerabilities

Static code analysis: other tools

Check out all of them :)

Fuzzy testing

  • My application works with a complex input
  • I validate it
  • I want to make sure that my validation is good enough
  • https://github.com/dvyukov/go-fuzz

Fuzzy testing can help you to check if your validation is good enough

Vulnerabilities in Go?

https://nvd.nist.gov/vuln/search/results?form_type=Basic&results_type=overview&query=golang&search_type=all

https://www.cvedetails.com/vendor/14185/Golang.html

Data

Code

Dependencies

Dependencies is one of the most favourite topics in the community

Dependencies: problem definition

  • Design & quality
  • Testing
  • Activity & maintenance
  • Licenses
  • Integrity & dependencies
  • Immutability & updates

Dependencies checklist

  • Description
  • Documentation
  • Go Report Card
  • Issues & pull requests
  • Code coverage
  • Other reports
  • Repeat the same for dependencies of this dependency

GOPROXY: pros

  • Availability
  • Independency
  • Immutability
  • Archives are faster than git repos
  • Additional opportunities

GOPROXY: cons

  • Workflows around module management
  • Current implementations

Security Onion Model

Data

Code

Container

Dependencies

Container

  • Multi-staging builds
  • Separate containers for testing
  • Security policies
  • OS-level dependencies
  • Runtime controls

Builds

  • Compile a binary on the CI/CD node & copy it
  • Multi-staging builds

Multi-staging builds: Stage 0

# Stage 0. Build the binary

FROM artifactory/golang:1.12

# add a non-privileged user

RUN useradd -u 10001 myapp

RUN mkdir -p /go/src/github.com/rumyantseva/myapp

ADD . /go/src/github.com/rumyantseva/myapp

WORKDIR /go/src/github.com/rumyantseva/myapp

# Build the binary with go build

RUN CGO_ENABLED=0 go build \

-o bin/myapp github.com/rumyantseva/myapp/cmd/myapp

# Stage 1. Run the binary

Multi-staging builds: Stage 1

# Stage 1. Run the binary

FROM scratch

ENV PORT 8080

# certificates to interact with other services

COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

# don't forget /etc/passwd from previous stage

COPY --from=0 /etc/passwd /etc/passwd

USER myapp

# and finally the binary

COPY --from=0 /go/src/github.com/rumyantseva/myapp/bin/myapp /myapp

EXPOSE $PORT

CMD ["myapp"]

Multi-staging builds: GOPROXY

# Initial stage: download modules

FROM artifactory/golang:1.12

ADD go.mod go.sum /m/

RUN cd /m && go mod download

While go.mod and go.sum are not changed, the stage will be cached

Base image for testing

FROM artifactory/golang:1.12

# Change here to update

ENV VERSION 1.12.3

ENV CHECKSUM c531688661b500d4c0c500fcf57f829388a4a9ba79697c2e134302aedef0cd46

# Make sure we have a fixed golangci-lint script with a chekcsum check

RUN echo "${CHECKSUM} golangci-lint-${VERSION}-linux-amd64.tar.gz" > CHECKSUM

# Download from Github the specified release and extract into the go/bin folder

RUN curl -L "https://github.com/golangci/golangci-lint/ releases/download/v${VERSION}/golangci-lint-${VERSION}-linux-amd64.tar.gz" \

-o golangci-lint-${VERSION}-linux-amd64.tar.gz \

&& shasum -a 256 -c CHECKSUM \

&& tar xvzf golangci-lint-${VERSION}-linux-amd64.tar.gz \

--strip-components=1 \

-C ./bin \

golangci-lint-${VERSION}-linux-amd64/golangci-lint

# Clean up

RUN rm -rf CHECKSUM "golangci-lint-${VERSION}-linux-amd64.tar.gz"

Run linters and tests

FROM artifactory/golang-linters

RUN mkdir -p /go/src/github.com/rumyantseva/myapp

ADD . /go/src/github.com/rumyantseva/myapp

WORKDIR /go/src/github.com/rumyantseva/myapp

# Run linters

RUN golangci-lint run \

--no-config --issues-exit-code=1 \

--deadline=10m --exclude-use-default=false \

./...

# Run tests

RUN go test -timeout=600s -v --race ./...

Operate containers

  • PodSecurityPolicies
  • Disable privileged containers
  • Consider MustRunAsNonRoot
  • Limit possible types of volumes

What we can learn?

  • Add security checks as early as possible
  • Automate as much as possible
  • Although, some steps have to be manual
  • Prepare a checklist to improve codereview experience
  • Contribute to the Go community

hello@grahovac.pro

webdeva

rumyantseva

These slides and additional examples:
https://security.grahovac.pro

References and additional reading

GopherCon Russia 2019 - Security Checklist for a Go Developer - Google Slides