Skip to content

Commit

Permalink
Merge branch 'main' into cf-push-example-manifest
Browse files Browse the repository at this point in the history
  • Loading branch information
FelisiaM authored Sep 10, 2024
2 parents f7ee9ec + f0d0091 commit 602e3a4
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 4 deletions.
26 changes: 25 additions & 1 deletion cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ const (
apiPasswordProp = "api.password"
apiPortProp = "api.port"
apiHostProp = "api.host"
tlsCertCaBundleProp = "api.certCaBundle"
tlsKeyProp = "api.tlsKey"
encryptionPasswords = "db.encryption.passwords"
encryptionEnabled = "db.encryption.enabled"
)
Expand Down Expand Up @@ -84,6 +86,8 @@ func init() {
_ = viper.BindEnv(apiHostProp, "CSB_LISTENER_HOST")
_ = viper.BindEnv(encryptionPasswords, "ENCRYPTION_PASSWORDS")
_ = viper.BindEnv(encryptionEnabled, "ENCRYPTION_ENABLED")
_ = viper.BindEnv(tlsCertCaBundleProp, "TLS_CERT_CHAIN")
_ = viper.BindEnv(tlsKeyProp, "TLS_PRIVATE_KEY")
}

func serve() {
Expand Down Expand Up @@ -210,7 +214,27 @@ func startServer(registry pakBroker.BrokerRegistry, db *sql.DB, brokerapi http.H
port := viper.GetString(apiPortProp)
host := viper.GetString(apiHostProp)
logger.Info("Serving", lager.Data{"port": port})
_ = http.ListenAndServe(fmt.Sprintf("%s:%s", host, port), router)

tlsCertCaBundleFilePath := viper.GetString(tlsCertCaBundleProp)
tlsKeyFilePath := viper.GetString(tlsKeyProp)

logger.Info("tlsCertCaBundle", lager.Data{"tlsCertCaBundle": tlsCertCaBundleFilePath})
logger.Info("tlsKey", lager.Data{"tlsKey": tlsKeyFilePath})

httpServer := &http.Server{
Addr: fmt.Sprintf("%s:%s", host, port),
Handler: router,
}
var err error
if tlsCertCaBundleFilePath != "" && tlsKeyFilePath != "" {
err = httpServer.ListenAndServeTLS(tlsCertCaBundleFilePath, tlsKeyFilePath)
} else {
err = httpServer.ListenAndServe()
}
// when the server is receiving a signal, we probably do not want to panic.
if err != http.ErrServerClosed {
logger.Fatal("Failed to start broker", err)
}
}

func labelName(label string) string {
Expand Down
2 changes: 2 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Broker service configuration values:
| <tt>SECURITY_USER_NAME</tt> <b>*</b> | api.user | string | <p>Broker authentication username</p>|
| <tt>SECURITY_USER_PASSWORD</tt> <b>*</b> | api.password | string | <p>Broker authentication password</p>|
| <tt>PORT</tt> | api.port | string | <p>Port to bind broker to</p>|
| <tt>TLS_CERT_CHAIN</tt> | api.certCaBundle | string | <p>File path to a pem encoded certificate chain</p>|
| <tt>TLS_PRIVATE_KEY</tt> | api.tlsKey | string | <p>File path to a pem encoded private key</p>|

## Feature flags Configuration

Expand Down
2 changes: 1 addition & 1 deletion integrationtest/import_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ var _ = Describe("Import State", func() {
By("importing state into the vacant service instance")
req := must(http.NewRequest(http.MethodPatch, fmt.Sprintf("http://localhost:%d/import_state/%s", broker.Port, instance.GUID), bytes.NewReader(stateToImport)))
req.SetBasicAuth(broker.Username, broker.Password)
importResponse := must(http.DefaultClient.Do(req))
importResponse := must(broker.Client.Do(req))
Expect(importResponse).To(HaveHTTPStatus(http.StatusOK))

By("checking that the state was imported into the database")
Expand Down
1 change: 1 addition & 0 deletions integrationtest/maintenance_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var _ = Describe("Maintenance Info", func() {
broker = must(testdrive.StartBroker(
csb, brokerpak, database,
testdrive.WithOutputs(GinkgoWriter, GinkgoWriter),
testdrive.WithTLSConfig(),
testdrive.WithEnv("TERRAFORM_UPGRADES_ENABLED=true"),
))
})
Expand Down
64 changes: 64 additions & 0 deletions integrationtest/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package integrationtest_test

import (
"fmt"
"net/http"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/cloudfoundry/cloud-service-broker/v2/integrationtest/packer"
"github.com/cloudfoundry/cloud-service-broker/v2/internal/testdrive"
)

var _ = Describe("Starting Server", func() {

const userProvidedPlan = `[{"name": "user-plan-unique","id":"8b52a460-b246-11eb-a8f5-d349948e2481"}]`

var brokerpak string

BeforeEach(func() {
brokerpak = must(packer.BuildBrokerpak(csb, fixtures("service-catalog")))

DeferCleanup(func() {
cleanup(brokerpak)
})
})

When("TLS data is provided", func() {
When("Valid data exists", func() {
It("Should accept HTTPS requests", func() {
broker, err := testdrive.StartBroker(csb, brokerpak, database, testdrive.WithTLSConfig(), testdrive.WithEnv(fmt.Sprintf("GSB_SERVICE_ALPHA_SERVICE_PLANS=%s", userProvidedPlan)), testdrive.WithOutputs(GinkgoWriter, GinkgoWriter))
Expect(err).NotTo(HaveOccurred())

_, err = broker.Client.Get(fmt.Sprintf("https://localhost:%d", broker.Port))
Expect(err).NotTo(HaveOccurred())
})
})

When("Invalid data exists", func() {
It("Should fail to start", func() {
_, err := testdrive.StartBroker(csb, brokerpak, database, testdrive.WithInvalidTLSConfig(), testdrive.WithEnv(fmt.Sprintf("GSB_SERVICE_ALPHA_SERVICE_PLANS=%s", userProvidedPlan)), testdrive.WithOutputs(GinkgoWriter, GinkgoWriter))
Expect(err).To(HaveOccurred())
})
})
})

When("No TLS data is provided", func() {
It("Should return an error for HTTPS requests", func() {
broker, err := testdrive.StartBroker(csb, brokerpak, database, testdrive.WithEnv(fmt.Sprintf("GSB_SERVICE_ALPHA_SERVICE_PLANS=%s", userProvidedPlan)), testdrive.WithOutputs(GinkgoWriter, GinkgoWriter))
Expect(err).NotTo(HaveOccurred())

_, err = http.Get(fmt.Sprintf("https://localhost:%d", broker.Port))
Expect(err).To(HaveOccurred())
})

It("Should succeed for HTTP requests", func() {
broker, err := testdrive.StartBroker(csb, brokerpak, database, testdrive.WithEnv(fmt.Sprintf("GSB_SERVICE_ALPHA_SERVICE_PLANS=%s", userProvidedPlan)), testdrive.WithOutputs(GinkgoWriter, GinkgoWriter))
Expect(err).NotTo(HaveOccurred())

_, err = http.Get(fmt.Sprintf("http://localhost:%d", broker.Port))
Expect(err).NotTo(HaveOccurred())
})
})
})
124 changes: 123 additions & 1 deletion internal/testdrive/broker_start.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,25 @@ package testdrive

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io"
"math/big"
"net"
"net/http"
"os"
"os/exec"
"strings"
"time"

//lint:ignore ST1001 we do not care because this is a test helper
. "github.com/onsi/gomega"

"github.com/cloudfoundry/cloud-service-broker/v2/pkg/client"
"github.com/cloudfoundry/cloud-service-broker/v2/utils/freeport"
"github.com/google/uuid"
Expand Down Expand Up @@ -82,8 +93,24 @@ func StartBroker(csbPath, bpk, db string, opts ...StartBrokerOption) (*Broker, e
}

start := time.Now()

scheme := "http"

for _, envVar := range cmd.Env {
if strings.HasPrefix(envVar, "TLS_") {

ignoreSelfSignedCerts := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
broker.Client.BaseURL.Scheme = "https"
broker.Client.Transport = ignoreSelfSignedCerts
scheme = "https"
break
}
}

for {
response, err := http.Head(fmt.Sprintf("http://localhost:%d", port))
response, err := broker.Client.Head(fmt.Sprintf("%s://localhost:%d", scheme, port))
switch {
case err == nil && response.StatusCode == http.StatusOK:
return &broker, nil
Expand All @@ -99,6 +126,101 @@ func StartBroker(csbPath, bpk, db string, opts ...StartBrokerOption) (*Broker, e
}
}

func createCAKeyPair(msg string) (*x509.Certificate, *rsa.PrivateKey) {
ca := &x509.Certificate{
SerialNumber: big.NewInt(2019),
Subject: pkix.Name{
Country: []string{msg},
},
IsCA: true,
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}

caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
Expect(err).NotTo(HaveOccurred())

return ca, caPrivKey
}

func createKeyPairSignedByCA(ca *x509.Certificate, caPrivKey *rsa.PrivateKey) ([]byte, []byte) {
cert := &x509.Certificate{
SerialNumber: big.NewInt(1658),
Subject: pkix.Name{
Country: []string{"GB"},
},
DNSNames: []string{"localhost"},
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}

certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
Expect(err).NotTo(HaveOccurred())

certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey)
Expect(err).NotTo(HaveOccurred())

return encodeKeyPair(certBytes, x509.MarshalPKCS1PrivateKey(certPrivKey))
}

func encodeKeyPair(caBytes, caPrivKeyBytes []byte) ([]byte, []byte) {
caPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
})

caPrivKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: caPrivKeyBytes,
})

return caPEM, caPrivKeyPEM
}

func WithInvalidTLSConfig() StartBrokerOption {
return func(cfg *startBrokerConfig) {
tlsConfig(cfg, false)
}
}

func WithTLSConfig() StartBrokerOption {
return func(cfg *startBrokerConfig) {
tlsConfig(cfg, true)
}
}

func tlsConfig(cfg *startBrokerConfig, valid bool) {
ca, caPrivKey := createCAKeyPair("US")

serverCert, serverPrivKey := createKeyPairSignedByCA(ca, caPrivKey)

certFileBuf, err := os.CreateTemp("", "")
Expect(err).NotTo(HaveOccurred())
defer certFileBuf.Close()

privKeyFileBuf, err := os.CreateTemp("", "")
Expect(err).NotTo(HaveOccurred())
defer privKeyFileBuf.Close()

if !valid {
// If the isValid parameter is false, the server private key is intentionally corrupted
// by modifying one of its bytes.
serverPrivKey[10] = 'a'
}

Expect(os.WriteFile(privKeyFileBuf.Name(), serverPrivKey, 0o644)).To(Succeed())

Expect(os.WriteFile(certFileBuf.Name(), serverCert, 0o644)).To(Succeed())

cfg.env = append(cfg.env, fmt.Sprintf("TLS_CERT_CHAIN=%s", certFileBuf.Name()))
cfg.env = append(cfg.env, fmt.Sprintf("TLS_PRIVATE_KEY=%s", privKeyFileBuf.Name()))
}

func WithEnv(extraEnv ...string) StartBrokerOption {
return func(cfg *startBrokerConfig) {
cfg.env = append(cfg.env, extraEnv...)
Expand Down
3 changes: 2 additions & 1 deletion pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func New(username, password, hostname string, port int) (*Client, error) {
}

type Client struct {
http.Client
BaseURL *url.URL
}

Expand Down Expand Up @@ -135,7 +136,7 @@ func (client *Client) makeRequest(method, path, requestID string, body any) *Bro
return &br
}

resp, err := http.DefaultClient.Do(req)
resp, err := client.Do(req)

br.UpdateResponse(resp)
br.UpdateError(err)
Expand Down

0 comments on commit 602e3a4

Please sign in to comment.