From b45e276df53571f6f61de23adabcdcd5ec98fb7c Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Sun, 2 Apr 2023 12:07:58 +1000 Subject: [PATCH] use TLS --- cert.pem | 30 ++++++++++ key.pem | 52 ++++++++++++++++ main.go | 51 ++++++++++++++-- utils/utils.go | 157 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 286 insertions(+), 4 deletions(-) create mode 100644 cert.pem create mode 100644 key.pem diff --git a/cert.pem b/cert.pem new file mode 100644 index 0000000..25298bd --- /dev/null +++ b/cert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFJzCCAw+gAwIBAgIQIVjBMPostxK1MlusUI+2PzANBgkqhkiG9w0BAQsFADAP +MQ0wCwYDVQQKEwREVE1TMB4XDTIzMDQwMjAxNDYxOVoXDTI0MDQwMTAxNDYxOVow +DzENMAsGA1UEChMERFRNUzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ALux7Yv5ckCP26S4DVxAqLp/ggCKOhqUxRqp5+59zTk1QcqbTeO/L3pC/4tNRRze +y6vEIeOcUKGt7DVDOFNc7n96WFxLdjIowatuetYestPriSGgqWUSABLUUC7gP5rU +wjf5MHoiq9JKJYv158goCOnNZerEMz5ApchlpSnLfeFfmEsYlcwLfTDL03wsUAug +HGjD0XqKR4CxaL1C8QUbkteY4j+OODMUnvzFXMRSajbN92mA5ROKibN4/cOyb6z7 +66Jabdbcyj3obB6En1W8jCb1cofI/Dh8ZqtkSHRTTeosTHgm9XuLMwHPkQ2wOZHU +kudC4JvZsAy3aCl+14q3eaRUEPamd9Yg4mfB7VmrezDhGPRRX6kNGk4l2H8Pp/Fw +78UVhWNb/fPKsbXq+Zo7AH3oQrOa9Oc8+7/o+9wJEQu/tLj9bYSqUx9wIQfG0Lpy +J6CDFqGaxdcU9r4SFT45x411cBovcX8I888KE9E01W/kBBcaT0zjwTG+QfmDCyD1 +6Yc5hvy2CKU6t0X/+BPCsyoVNp1/kva5JOGL38F7K0wlwoKMpT1jO4Ulsgejohmx +lW5Eoldhoc9NBS6ZBZ+m/rh34CWcFdNi+KzvGmL5KCGbzDXo1zyJLW+n968m5c16 +DWsCRNX0UJC1841mCwb+W4TYs/2/19wZPFx0tLTx9C5PAgMBAAGjfzB9MA4GA1Ud +DwEB/wQEAwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBTolBa+8La6LG3PK8Jh1bfRJY2yMjAmBgNVHREEHzAdgg9uYXRo +bWJwMTQubG9jYWyHBAoAAYGHBAoAAXswDQYJKoZIhvcNAQELBQADggIBALdb55i4 +vEpltZ41CThTHN2+wLFAMrQPtRH0LeLaiikcn+BSt0TkOFIPfaAie0iYmYnEw1eb +0jw9Hu1avc7GIVaTiOFr9M7A/9VVltWqE/Ic5GH81XjjQJErjHAyULR/XO6CaPcj +ekMnDrTrfRdsie9DJHxXWLUowZwkinVDpCMeZFw6lLVYLpdx+nl+ouyEZSKGtDAh +mqSzqD0fw8aCwcbumfz7jY9US0LfPWUhjEyB200IsPU6fe9YtEyForH9wiRN1Y+p +q5gtPCYBBhopskLAnpA/hjYfpeN917yVHcWNDw8YaC4QKP+AmBC1gPyGnpTpBbzH +XT/IcMi3C7xvvKuTtacGRS5RoHOtBGDO/0PaW+IFrR9R89N8JhYgPA+fysPJ+fJn +YOTWW+n2Oc6QULv7pqCqfkQQ+TeQZHl9mZaHYzIvXGS2Fpo282EKCztzxrJJYZ9a +us7GL75UCNyNQDNii/AaSr3gA8CbKEaa3uDccv4+gxY137seF6qWakqLBm3Q4j6M ++nRb5++DVyPz8CpE9i2HkId+wwT3U6PiE7uFcJK+wpvpJmrqzcm3tHVBFe0Hp8YF +ykGZsMdaAkhIH1VtYggi26AIwe9vhtzb48LYLABYJ2blYXHsf/Md5MPhG8HFM9LR +iCNA98wWcCweUk1NpB6eYqTMbI9Qj4cYZ1MS +-----END CERTIFICATE----- diff --git a/key.pem b/key.pem new file mode 100644 index 0000000..5a64f24 --- /dev/null +++ b/key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQC7se2L+XJAj9uk +uA1cQKi6f4IAijoalMUaqefufc05NUHKm03jvy96Qv+LTUUc3surxCHjnFChrew1 +QzhTXO5/elhcS3YyKMGrbnrWHrLT64khoKllEgAS1FAu4D+a1MI3+TB6IqvSSiWL +9efIKAjpzWXqxDM+QKXIZaUpy33hX5hLGJXMC30wy9N8LFALoBxow9F6ikeAsWi9 +QvEFG5LXmOI/jjgzFJ78xVzEUmo2zfdpgOUTiomzeP3Dsm+s++uiWm3W3Mo96Gwe +hJ9VvIwm9XKHyPw4fGarZEh0U03qLEx4JvV7izMBz5ENsDmR1JLnQuCb2bAMt2gp +fteKt3mkVBD2pnfWIOJnwe1Zq3sw4Rj0UV+pDRpOJdh/D6fxcO/FFYVjW/3zyrG1 +6vmaOwB96EKzmvTnPPu/6PvcCRELv7S4/W2EqlMfcCEHxtC6cieggxahmsXXFPa+ +EhU+OceNdXAaL3F/CPPPChPRNNVv5AQXGk9M48ExvkH5gwsg9emHOYb8tgilOrdF +//gTwrMqFTadf5L2uSThi9/BeytMJcKCjKU9YzuFJbIHo6IZsZVuRKJXYaHPTQUu +mQWfpv64d+AlnBXTYvis7xpi+Sghm8w16Nc8iS1vp/evJuXNeg1rAkTV9FCQtfON +ZgsG/luE2LP9v9fcGTxcdLS08fQuTwIDAQABAoICADl3G1SMtcdAhC3lT+nbvg0D +duj1aR60R7iHJoRlLc0DGjaO4AUHhuC23Ihoyu0w5Uicjweh1yn5FuWjw/356OdM +rPrs+64Vvl3fQGqIEZZCecS67MIOBcm+CSKg87hSxXffqlDqhpKmsuIsEp+8aiYz +Ba4gDdRUdK1rOWsXa13yJ61uRIapSdYDwac15ff5OFl3Ao6zAXtMVdYERcibjZTG +sRZmU5yIBrk7L9DHrs9CtiouFXSrKMezFnaxDfH3W0hkyVZMWPslStz5G9NjG8Hd +ygf6qAYfSEvXMzuyOZulnm/29ZeyoZY/V5jKSIX2gFefqSbuOjFageCafiFxVSNM +o8e4q5Vr4/ovwjI57HCX2Mih43QI0tXJSMHgNa9LEcgjrmmvkYd2ZrUU+F4HylLp +e9rloxR018i7ycqyZTcocqTN14xALoHYyEZXMGa8DO+W8ZFFKv6/2UZP9WtbzCi5 +d+onuU8tJ8BwHsvcUlBqrDTTKgfHRXmL8c5VY9d2dLekbsTgqrp7QQSgznP0c6zC +uZz/NiKLuRJ0huC2hENvj+qNILMM8iGXwFPnLyN0fTqf58mNHQcC7mExlGWD3iCP +g8b0JOob3RTLHu6WyNSl5cDRD/VDHRv9AU6IfeWv0AIpAsLYII4q+C20a/1XOEK0 +nW7GRuMHOuOHpj5OIYsZAoIBAQDDjbDeRxFmV8dewhLd1c7UKp31395Yd8JG1GJk +KOGeejOmJsvDFfkipRHeqSZVHEvP5eaWAGAMAhe1OO+nru00R0RUDHvsPWH6/eos +4JwuGWRAGC5VVyqjDkbLUczMSmdsyOM3CCQYRjkU/D5yui0s9fgRqNJpNEh8Kw4k +UWkezcxVt+ZwUU7UCKf6497gdpnM4wpFh3eth+SrQIn3nR8op8E+Va1MQ8z/ZLNf +BLDgHb51hCwKnHMsnir4m808XS0jNxVDI6IjXZvo5+O9zk2Lf4v5HKs2Tpesp1p4 +Nah8D9q2fppLxf7IPF2b5FeFdPPqA2gxJcuMG1DIpuvie/59AoIBAQD1tmQ0kgNK +XQbKbobp1XnhOmxRrIy73uYzNygnK6CYqYFkQzUqWcjssCkpeXvyVtW9FAIwznsf +apnaszynm3zFTYST/cJBS/nqpEgOSU0lzy/zhN8Y0zmEn+FsqON18PA5Ghjtugqe +htsmB+St5Q2M5+CnD+cm7YIjadvuAGb8UppuJPx7VSIXYaWC5w6ihnIQscmk5V02 +J7JCBC9RYFkCAxYpkCGrS8qMhkMdMMMqifSdNsZRsXiNmQNlwT4BgVlY6t3MlA6C +glG+5K+3x48bdUPe2iQSS3fyXcV7r8w40vAPsgZ+trX2+U45Tq+8zoEm01ijnHKj +LUi6Qcotq727AoIBAQCGU9w8s6C2JnHjG3kOPQI4J5YeAUughZNESDvWBWcGD3dP +GLM7LOrX63/c7WBRcoB5LLk6LMPNO9rBN30YOEbkdFfxTCoL2xZk7OphLWDbIcIF +bRbWtGurWfEzVNtTMYDd7xh1AviYXBR139edw1mo2sUujEMM1Uc9U+4A8JM+/6ru +7sYyPQfjCpco9kJw+ccoQIUbwbRp1UE0ib68AogWyzUo9n12N9SnUqsMCBLfwE7G +NqZyG4naCkQGRi+y2WZaHEpUtjN4C6M/V5rPFNmbi1/BHPpW8aGqjcLNdD5qChH4 +7fpbNoSPUzgSD8Y2/GFYTELUD9xEP7c8Pb0m9HmZAoIBAQCZ+gNPglS3jBZOUvcO +n+1xu/fXMsc6kBM38w+hhFBueoKQPyGY+YzU7gvNZsjKc6obg+dfiHVVmaryhzGy +YtjMl6hGSVYrLx5c8TgKY9sJn42QECryVqOLvdk5C5kG9sbDMdx/VP+EmkPI0Fd4 +tbpJG6IHbwrOtTXBVXwx6GShKG6IKXtjGlVzy4g5wOvF43Sli5QuVmGUJ0oo3pB8 +cWE1xpNj1mOTAbrbVuPF7iHPjYYR4xpiZVGDgmjDolZmARXph70Yj4uMM3hgpNSM +2WyPq2utwouqP0raQfMYcHENBvrTCx/Q1lSvgUmI1guAyPPcTcMVPFwGb8FMnx/A +gQgzAoIBAQClJSL1X2YUaWo6Goe2Kcwfr0sPVF3JA30ywT/R+tubfUy5UrleGRjm +h5rFFOjB9+WGwvRS6tk8Wu8wPt6tMjBm06nHgb0I0RG2MZarOdhDkjJCt1qXwyhr +o1O1xWEOcgr3w+UHFHfHt/daEz0OvzjmdISLeJzv47tE9ZwAggSyXy0SBL4nv1wX +E3fYoYp/QX5riMY+In4ucNIf/6CVAGgUxu8sD7m2Y4cJQGN2ZVbxd5IuUtSrRYEU +fsKgDt9hmWhYEk5C6egVL4UrM070eV8Rssu2PdNgUF/7pylBnMYWcMHltfD3M+oR +YOtHX+6Qh2feqdNVUqFtlMCuk5iZUel9 +-----END PRIVATE KEY----- diff --git a/main.go b/main.go index c95b232..8cfbcb3 100644 --- a/main.go +++ b/main.go @@ -4,9 +4,13 @@ import ( "ccsecrets/controllers" "ccsecrets/middlewares" "ccsecrets/models" + "ccsecrets/utils" "context" + "crypto/tls" + "fmt" "log" "net/http" + "os" "os/signal" "syscall" "time" @@ -26,12 +30,51 @@ func main() { router := gin.Default() router.GET("/", func(c *gin.Context) { //time.Sleep(10 * time.Second) - c.String(http.StatusOK, "Welcome Gin Server") + c.String(http.StatusOK, "Hello World.") }) + // Set some options for TLS + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, + PreferServerCipherSuites: true, + InsecureSkipVerify: true, + CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + }, + } + + // Determine bind IP + bindIP := os.Getenv("BIND_IP") + if bindIP == "" { + bindIP = utils.GetOutboundIP().String() + } + // Determine bind port + bindPort := os.Getenv("BIND_PORT") + if bindPort == "" { + bindIP = "8443" + } + bindAddress := fmt.Sprint(bindIP, ":", bindPort) + fmt.Printf("Will listen on address 'https://%s'\n", bindAddress) + + // Generate certificate if required + tlsCertFilename := utils.GetFilePath(os.Getenv("TLS_CERT_FILE")) + tlsKeyFilename := utils.GetFilePath(os.Getenv("TLS_KEY_FILE")) + + if !(utils.FileExists(tlsCertFilename) && utils.FileExists(tlsKeyFilename)) { + fmt.Printf("Specified TLS certificate (%s) or private key (%s) do not exist.\n", tlsCertFilename, tlsKeyFilename) + utils.GenerateCerts(tlsCertFilename, tlsKeyFilename) + } + srv := &http.Server{ - Addr: ":8080", - Handler: router, + Addr: bindAddress, + Handler: router, + TLSConfig: tlsConfig, } // Register our routes @@ -52,7 +95,7 @@ func main() { // Initializing the server in a goroutine so that // it won't block the graceful shutdown handling below go func() { - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + if err := srv.ListenAndServeTLS(tlsCertFilename, tlsKeyFilename); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } }() diff --git a/utils/utils.go b/utils/utils.go index 5ec2844..bf1b479 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,12 +1,22 @@ package utils import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" "fmt" "log" + "math/big" + "net" "os" "path/filepath" + "time" ) +const rsaBits = 4096 + func GetFilePath(path string) string { // Check for empty filename if len(path) == 0 { @@ -27,3 +37,150 @@ func GetFilePath(path string) string { } return path } + +// Get preferred outbound ip of this machine +// @see https://stackoverflow.com/questions/23558425/how-do-i-get-the-local-ip-address-in-go +func GetOutboundIP() net.IP { + conn, err := net.Dial("udp", "8.8.8.8:80") + if err != nil { + log.Fatal(err) + } + defer conn.Close() + + localAddr := conn.LocalAddr().(*net.UDPAddr) + + return localAddr.IP +} + +// Check if a file exists from https://stackoverflow.com/questions/12518876/how-to-check-if-a-file-exists-in-go +func FileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} + +func GenerateCerts(tlsCert string, tlsKey string) { + // @see https://shaneutt.com/blog/golang-ca-and-signed-cert-go/ + // @see https://golang.org/src/crypto/tls/generate_cert.go + validFrom := "" + validFor := 365 * 24 * time.Hour + isCA := true + + // Get the hostname + hostname, err := os.Hostname() + if err != nil { + panic(err) + } + + // Check that the directory exists + relativePath := filepath.Dir(tlsCert) + fmt.Printf("GenerateCerts relative path for file creation is '%s'\n", relativePath) + _, err = os.Stat(relativePath) + if os.IsNotExist(err) { + log.Printf("Certificate path does not exist, creating %s before generating certificate\n", relativePath) + os.MkdirAll(relativePath, os.ModePerm) + } + + // Generate a private key + priv, err := rsa.GenerateKey(rand.Reader, rsaBits) + if err != nil { + log.Fatalf("Failed to generate private key: %v", err) + } + + var notBefore time.Time + if len(validFrom) == 0 { + notBefore = time.Now() + } else { + notBefore, err = time.Parse("Jan 2 15:04:05 2006", validFrom) + if err != nil { + log.Fatalf("Failed to parse creation date: %v", err) + } + } + + 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: %v", err) + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"DTMS"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + template.DNSNames = append(template.DNSNames, hostname) + + // Add in all the non-local IPs + ifaces, err := net.Interfaces() + + if err != nil { + fmt.Printf("Error enumerating interfaces: %v\n", err) + } + + for _, i := range ifaces { + addrs, err := i.Addrs() + if err != nil { + fmt.Printf("Oops: %v\n", err) + } + + for _, address := range addrs { + // check the address type and if it is not a loopback then add it to the list + if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + template.IPAddresses = append(template.IPAddresses, ipnet.IP) + } + } + } + } + + if isCA { + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + log.Fatalf("Failed to create certificate: %v", err) + } + + certOut, err := os.Create(tlsCert) + if err != nil { + log.Fatalf("Failed to open %s for writing: %v", tlsCert, err) + } + if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + log.Fatalf("Failed to write data to %s: %v", tlsCert, err) + } + if err := certOut.Close(); err != nil { + log.Fatalf("Error closing %s: %v", tlsCert, err) + } + log.Printf("wrote %s\n", tlsCert) + + keyOut, err := os.OpenFile(tlsKey, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Fatalf("Failed to open %s for writing: %v", tlsKey, err) + return + } + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + log.Fatalf("Unable to marshal private key: %v", err) + } + if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { + log.Fatalf("Failed to write data to %s: %v", tlsKey, err) + } + if err := keyOut.Close(); err != nil { + log.Fatalf("Error closing %s: %v", tlsKey, err) + } + log.Printf("wrote %s\n", tlsKey) +}