Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jetstream: support generating TLS cert #23

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/jetstream/api/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ type PortalConfig struct {
TLSCertKey string `configName:"CONSOLE_PROXY_CERT_KEY"`
TLSCertPath string `configName:"CONSOLE_PROXY_CERT_PATH"`
TLSCertKeyPath string `configName:"CONSOLE_PROXY_CERT_KEY_PATH"`
TLSCertGenerate bool `configName:"CONSOLE_PROXY_CERT_GENERATE"`
CFClient string `configName:"CF_CLIENT"`
CFClientSecret string `configName:"CF_CLIENT_SECRET"`
AllowedOrigins []string `configName:"ALLOWED_ORIGINS"`
Expand Down
49 changes: 48 additions & 1 deletion src/jetstream/main.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
package main

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"database/sql"
"encoding/gob"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"io"
"io/ioutil"
"math/big"
"net"
"net/http"
"os"
Expand Down Expand Up @@ -655,7 +663,15 @@ func detectTLSCert(pc api.PortalConfig) (string, string, error) {
return pc.TLSCertPath, pc.TLSCertKeyPath, nil
}

err := os.WriteFile(certFilename, []byte(pc.TLSCert), 0600)
// Check if we should generate self-signed certificates
if pc.TLSCertGenerate {
log.Info("Generating TLS certificates")
if err := generateTLSCert(&pc); err != nil {
return "", "", err
}
}

err := ioutil.WriteFile(certFilename, []byte(pc.TLSCert), 0600)
if err != nil {
return "", "", err
}
Expand All @@ -667,6 +683,37 @@ func detectTLSCert(pc api.PortalConfig) (string, string, error) {
return certFilename, certKeyFilename, nil
}

// generateTLSCert generates a self-signed TLS certificate for localhost. The
// resulting certificate is stored in the PortalConfig as TLSCert and TLSCertKey.
func generateTLSCert(pc *api.PortalConfig) error {
priv, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return fmt.Errorf("could not generate TLS certificate private key: %w", err)
}
template := x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()),
Subject: pkix.Name{CommonName: "localhost"},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 100),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.ParseIP("::1")},
DNSNames: []string{"localhost"},
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return fmt.Errorf("could not generate TLS certificate: %w", err)
}
out := &bytes.Buffer{}
pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
pc.TLSCert = out.String()
out.Reset()
pem.Encode(out, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
pc.TLSCertKey = out.String()
return nil
}

func newPortalProxy(pc api.PortalConfig, dcp *sql.DB, ss HttpSessionStore, sessionStoreOptions *sessions.Options, env *env.VarSet) *portalProxy {
log.Debug("newPortalProxy")

Expand Down
54 changes: 53 additions & 1 deletion src/jetstream/main_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package main

import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha512"
"crypto/x509"
"encoding/pem"
"testing"

"github.com/govau/cf-common/env"

"github.com/cloudfoundry-incubator/stratos/src/jetstream/datastore"
"github.com/cloudfoundry-incubator/stratos/src/jetstream/api"
"github.com/cloudfoundry-incubator/stratos/src/jetstream/datastore"
)

// DO NOT DELETE - this is necessary for thr HTTP Client used during unit tests
Expand Down Expand Up @@ -155,3 +161,49 @@ func TestLoadDatabaseConfigWithInvalidSSLMode(t *testing.T) {
t.Errorf("Unexpected success - should not be able to load database configs with an invalid SSL Mode specified.")
}
}

func TestGenerateTLSCert(t *testing.T) {
pc := api.PortalConfig{}
err := generateTLSCert(&pc)
if err != nil {
t.Errorf("Error generating TLS certificate: %v", err)
return
}
certBlock, rest := pem.Decode([]byte(pc.TLSCert))
if len(rest) > 0 {
t.Errorf("Extra bytes after certificate: %s", rest)
}
if certBlock.Type != "CERTIFICATE" {
t.Errorf("Invalid cerificate block type %s", certBlock.Type)
}
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
t.Errorf("Error parsing certificate: %v", err)
return
}
if err := cert.VerifyHostname("localhost"); err != nil {
t.Errorf("Certificate does not work for localhost: %v", err)
}
privBlock, rest := pem.Decode([]byte(pc.TLSCertKey))
if len(rest) > 0 {
t.Errorf("Extra bytes after private key: %s", rest)
}
if privBlock.Type != "RSA PRIVATE KEY" {
t.Errorf("Invalid private key block type %s", privBlock.Type)
}
privKey, err := x509.ParsePKCS1PrivateKey(privBlock.Bytes)
if err != nil {
t.Logf("Could not parse private key: %s", err)
t.FailNow()
}
data := []byte("sample data")
hash := sha512.Sum512(data)
sig, err := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA512, hash[:])
if err != nil {
t.Logf("Could not sign data: %s", err)
t.FailNow()
}
if err = cert.CheckSignature(x509.SHA512WithRSA, data, sig); err != nil {
t.Errorf("Certifcate public key does not match private key")
}
}
Loading