Adding generation of cert and key, and dockerfile

This commit is contained in:
PierreZ 2015-07-19 15:11:03 +02:00
parent 38fe333888
commit 60df40beb9
5 changed files with 167 additions and 37 deletions

4
Dockerfile Normal file
View file

@ -0,0 +1,4 @@
FROM centurylink/ca-certs
COPY goStatic /
ENTRYPOINT ["/goStatic"]

View file

@ -1,4 +1,4 @@
default: static default: static
static: static:
docker run --rm -v $(pwd):/src centurylink/golang-builder docker run --rm -v $(shell pwd):/src centurylink/golang-builder

View file

@ -4,17 +4,42 @@ A really small static web server for Docker
### The goal ### The goal
My goal is to create to smallest docker container for my web static files. The advantage of Go is that you can generate a fully static binary, so that you don't need anything else. My goal is to create to smallest docker container for my web static files. The advantage of Go is that you can generate a fully static binary, so that you don't need anything else.
### Features
* Web server build for Docker
* HTTPS by default
* Can generate certificate on his onw
* Light container
* More security than official images (see below)
* Log enabled
### Why? ### Why?
Because the official Golang image is wayyyy to big. (around 1/2Gb as you can see below) Because the official Golang image is wayyyy to big (around 1/2Gb as you can see below) and could be unsecure.
[![](https://badge.imagelayers.io/golang:latest.svg)](https://imagelayers.io/?images=golang:latest 'Get your own badge on imagelayers.io') [![](https://badge.imagelayers.io/golang:latest.svg)](https://imagelayers.io/?images=golang:latest 'Get your own badge on imagelayers.io')
For me, the whole point of containers is to have a light container... For me, the whole point of containers is to have a light container...
Many links should provide you with additionnal info to see my point of view: Many links should provide you with additionnal info to see my point of view:
* [Over 30% of Official Images in Docker Hub Contain High Priority Security Vulnerabilities](http://www.banyanops.com/blog/analyzing-docker-hub/)
* [Create The Smallest Possible Docker Container](http://blog.xebia.com/2014/07/04/create-the-smallest-possible-docker-container/) * [Create The Smallest Possible Docker Container](http://blog.xebia.com/2014/07/04/create-the-smallest-possible-docker-container/)
* [Building Docker Images for Static Go Binaries](https://medium.com/@kelseyhightower/optimizing-docker-images-for-static-binaries-b5696e26eb07) * [Building Docker Images for Static Go Binaries](https://medium.com/@kelseyhightower/optimizing-docker-images-for-static-binaries-b5696e26eb07)
* [Small Docker Images For Go Apps](http://www.centurylinklabs.com/small-docker-images-for-go-apps/) * [Small Docker Images For Go Apps](http://www.centurylinklabs.com/small-docker-images-for-go-apps/)
### What are you using? ### How to use
```
// HTTPS server
docker run -d -p 443:8043 -v path/to/website:/srv/http pierrezemb/goStatic
// HTTP server
docker run -d -p 80:8043 -v path/to/website:/srv/http pierrezemb/goStatic --forceHTTP
```
I'm using [echo](http://echo.labstack.com/) as a micro web framework because he has great performance, and [golang-builder](https://github.com/CenturyLinkLabs/golang-builder) to generate the static binary. ### Wow, such container! What are you using?
I'm using [echo](http://echo.labstack.com/) as a micro web framework because he has great performance, and [golang-builder](https://github.com/CenturyLinkLabs/golang-builder) to generate the static binary (command line in the makefile)
I'm also using the centurylink/ca-certs image instead of the scratch image to avoid this error:
```
x509: failed to load system roots and no roots provided
```
The centurylink/ca-certs image is simply the scratch image with the most common root CA certificates pre-installed. The resulting image is only 258 kB which is still a good starting point for creating your own minimal images.

BIN
goStatic Executable file

Binary file not shown.

167
main.go
View file

@ -1,6 +1,8 @@
package main // import "github.com/PierreZ/goStatic" package main // import "github.com/PierreZ/goStatic"
import ( import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
@ -8,23 +10,35 @@ import (
"encoding/pem" "encoding/pem"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"math/big" "math/big"
"net"
"os" "os"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/labstack/echo" "github.com/labstack/echo"
mw "github.com/labstack/echo/middleware" mw "github.com/labstack/echo/middleware"
) )
func main() { var (
// Def & parsing of flags // Def & parsing of flags
portPtr := flag.Int("p", 8080, "The proxy listening port") portPtr = flag.Int("p", 8043, "The listening port")
pathPtr := flag.String("path for static file", "/var/www", "The path for the static files") pathPtr = flag.String("static", "/srv/http", "The path for the static files")
crtPtr := flag.String("crt", "/etc/ssl/server", "The path and the name whithout extension for your CRT/key for TLS. Example: /etc/ssl/server for /etc/ssl/server.{crt,key}") crtPtr = flag.String("crt", "/etc/ssl/server", "Folder for server.pem and key.pem")
HTTPPtr := flag.Bool("forceHTTP", false, "Forcing HTTP and not HTTPS") HTTPPtr = flag.Bool("forceHTTP", false, "Forcing HTTP and not HTTPS")
// var for cert
host = flag.String("host", "", "Comma-separated hostnames and IPs to generate a certificate for")
validFrom = flag.String("start-date", "", "Creation date formatted as Jan 1 15:04:05 2011")
validFor = flag.Duration("duration", 365*24*time.Hour, "Duration that certificate is valid for")
isCA = flag.Bool("ca", false, "whether this cert should be its own Certificate Authority")
rsaBits = flag.Int("rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set")
ecdsaCurve = flag.String("ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521")
)
func main() {
flag.Parse() flag.Parse()
@ -45,54 +59,141 @@ func main() {
// Start server with unsecure HTTP // Start server with unsecure HTTP
// or with awesome TLS // or with awesome TLS
if *HTTPPtr { if *HTTPPtr {
log.Println("Starting serving", *pathPtr, "on", *portPtr)
e.Run(port) e.Run(port)
} else { } else {
if *crtPtr != "/etc/ssl/server" { // Check for cert
_, err := os.Stat(*crtPtr + "cert.pem")
if err != nil {
log.Println("Cert not found, generating .pem and .crt file")
generateCert(*crtPtr) generateCert(*crtPtr)
} }
e.RunTLS(port, *pathPtr+".pem", *pathPtr+".crt") log.Println("Starting serving", *pathPtr, "on", *portPtr)
e.RunTLS(port, *crtPtr+"cert.pem", *crtPtr+"key.pem")
} }
} }
// Mostly based on https://www.socketloop.com/tutorials/golang-create-x509-certificate-private-and-public-keys // Based on https://golang.org/src/crypto/tls/generate_cert.go
func generateCert(path string) { func generateCert(path string) {
var priv interface{}
var err error
switch *ecdsaCurve {
case "":
priv, err = rsa.GenerateKey(rand.Reader, *rsaBits)
case "P224":
priv, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
case "P256":
priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
case "P384":
priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
case "P521":
priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
default:
fmt.Fprintf(os.Stderr, "Unrecognized elliptic curve: %q", *ecdsaCurve)
os.Exit(1)
}
if err != nil {
log.Fatalf("failed to generate private key: %s", err)
}
template := &x509.Certificate{ var notBefore time.Time
IsCA: true, if len(*validFrom) == 0 {
BasicConstraintsValid: true, notBefore = time.Now()
SubjectKeyId: []byte{1, 2, 3}, } else {
SerialNumber: big.NewInt(1234), notBefore, err = time.Parse("Jan 2 15:04:05 2006", *validFrom)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse creation date: %s\n", err)
os.Exit(1)
}
}
notAfter := notBefore.Add(*validFor)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
log.Fatalf("failed to generate serial number: %s", err)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{ Subject: pkix.Name{
Country: []string{"Earth"},
Organization: []string{"Hooli"}, Organization: []string{"Hooli"},
}, },
NotBefore: time.Now(), NotBefore: notBefore,
NotAfter: time.Now().AddDate(5, 5, 5), NotAfter: notAfter,
// see http://golang.org/pkg/crypto/x509/#KeyUsage
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
} }
// generate private key hosts := strings.Split(*host, ",")
privatekey, err := rsa.GenerateKey(rand.Reader, 4096) for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
}
if *isCA {
template.IsCA = true
template.KeyUsage |= x509.KeyUsageCertSign
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
if err != nil { if err != nil {
log.Println(err) log.Fatalf("Failed to create certificate: %s", err)
} }
// save cert certOut, err := os.Create(path + "cert.pem")
ioutil.WriteFile(path+".crt", cert, 0777) if err != nil {
fmt.Println("certificate saved to", path+".crt") log.Fatalf("failed to open cert.pem for writing: %s", err)
}
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
certOut.Close()
log.Print("written cert.pem\n")
// this will create plain text PEM file. keyOut, err := os.OpenFile(path+"key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
pemfile, _ := os.Create(path + ".pem") if err != nil {
var pemkey = &pem.Block{ log.Print("failed to open key.pem for writing:", err)
Type: "RSA PRIVATE KEY", return
Bytes: x509.MarshalPKCS1PrivateKey(privatekey)} }
pem.Encode(pemfile, pemkey) pem.Encode(keyOut, pemBlockForKey(priv))
pemfile.Close() keyOut.Close()
log.Print("written key.pem\n")
}
// Based on https://golang.org/src/crypto/tls/generate_cert.go
func publicKey(priv interface{}) interface{} {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &k.PublicKey
case *ecdsa.PrivateKey:
return &k.PublicKey
default:
return nil
}
}
// Based on https://golang.org/src/crypto/tls/generate_cert.go
func pemBlockForKey(priv interface{}) *pem.Block {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
case *ecdsa.PrivateKey:
b, err := x509.MarshalECPrivateKey(k)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err)
os.Exit(2)
}
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
default:
return nil
}
} }