From b24302d344be80c4b898f53293a7d88af918c951 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Thu, 21 Sep 2023 19:32:58 +0530 Subject: [PATCH] Move certs & helper code from /internal/examples to /internal (#201) Fixes https://github.com/open-telemetry/opamp-go/issues/199 --- internal/certs.go | 164 ++++++++++++++++++ .../{examples => }/certs/certs/ca.cert.pem | 0 internal/{examples => }/certs/clear.sh | 0 internal/{examples => }/certs/client.conf | 0 internal/{examples => }/certs/generate.sh | 0 internal/{examples => }/certs/index.txt | 0 internal/{examples => }/certs/index.txt.attr | 0 internal/{examples => }/certs/index.txt.old | 0 internal/{examples => }/certs/openssl.conf | 0 .../{examples => }/certs/private/ca.key.pem | 0 internal/{examples => }/certs/serial | 0 internal/{examples => }/certs/serial.old | 0 internal/{examples => }/certs/server.conf | 0 .../certs/server_certs/server.cert.pem | 0 .../certs/server_certs/server.csr | 0 .../certs/server_certs/server.key.pem | 0 internal/{examples => }/certs/server_ext.conf | 0 internal/examples/agent/agent/agent.go | 37 ++-- internal/examples/server/opampsrv/opampsrv.go | 49 +----- internal/examples/server/uisrv/ui.go | 94 +--------- 20 files changed, 183 insertions(+), 161 deletions(-) create mode 100644 internal/certs.go rename internal/{examples => }/certs/certs/ca.cert.pem (100%) rename internal/{examples => }/certs/clear.sh (100%) rename internal/{examples => }/certs/client.conf (100%) rename internal/{examples => }/certs/generate.sh (100%) rename internal/{examples => }/certs/index.txt (100%) rename internal/{examples => }/certs/index.txt.attr (100%) rename internal/{examples => }/certs/index.txt.old (100%) rename internal/{examples => }/certs/openssl.conf (100%) rename internal/{examples => }/certs/private/ca.key.pem (100%) rename internal/{examples => }/certs/serial (100%) rename internal/{examples => }/certs/serial.old (100%) rename internal/{examples => }/certs/server.conf (100%) rename internal/{examples => }/certs/server_certs/server.cert.pem (100%) rename internal/{examples => }/certs/server_certs/server.csr (100%) rename internal/{examples => }/certs/server_certs/server.key.pem (100%) rename internal/{examples => }/certs/server_ext.conf (100%) diff --git a/internal/certs.go b/internal/certs.go new file mode 100644 index 00000000..7b86e078 --- /dev/null +++ b/internal/certs.go @@ -0,0 +1,164 @@ +package internal + +// This file contains helper functions to read and +// create TLS configs/certificates. Currently used in +// the example client and server and +// in the tests. Not intended for any other use. + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "math/big" + "net" + "os" + "time" + + "github.com/open-telemetry/opamp-go/protobufs" +) + +func CreateClientTLSConfig(clientCert *tls.Certificate, caCertPath string) (*tls.Config, error) { + // Read the CA's public key. This is the CA that signs the server's certificate. + caCertBytes, err := os.ReadFile(caCertPath) + if err != nil { + return nil, err + } + + // Create a certificate pool and make our CA trusted. + caCertPool := x509.NewCertPool() + if ok := caCertPool.AppendCertsFromPEM(caCertBytes); !ok { + return nil, errors.New("cannot append ca.cert.pem") + } + + cfg := &tls.Config{ + RootCAs: caCertPool, + } + if clientCert != nil { + // If there is a client-side certificate use it for connection too. + cfg.Certificates = []tls.Certificate{*clientCert} + } + return cfg, nil +} + +func CreateServerTLSConfig(caCertPath, serverCertPath, serverKeyPath string) (*tls.Config, error) { + // Read the CA's public key. This is the CA that signs the server's certificate. + caCertBytes, err := os.ReadFile(caCertPath) + if err != nil { + return nil, err + } + + // Create a certificate pool and make our CA trusted. + caCertPool := x509.NewCertPool() + if ok := caCertPool.AppendCertsFromPEM(caCertBytes); !ok { + return nil, errors.New("cannot append ca.cert.pem") + } + + // Load server's certificate. + cert, err := tls.LoadX509KeyPair( + serverCertPath, + serverKeyPath, + ) + if err != nil { + return nil, fmt.Errorf("tls.LoadX509KeyPair failed: %v", err) + } + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + // TODO: verify client cert manually, and allow TOFU option. See manual + // verification example: https://dev.to/living_syn/validating-client-certificate-sans-in-go-i5p + // Instead, we use VerifyClientCertIfGiven which will automatically verify the provided certificate + // is signed by our CA (so TOFU with self-generated client certificate will not work). + ClientAuth: tls.VerifyClientCertIfGiven, + // Allow insecure connections for demo purposes. + InsecureSkipVerify: true, + ClientCAs: caCertPool, + } + tlsConfig.BuildNameToCertificate() + return tlsConfig, nil +} + +func CreateTLSCert(caCertPath, caKeyPath string) (*protobufs.TLSCertificate, error) { + + // Load CA Cert. + caCertBytes, err := ioutil.ReadFile(caCertPath) + if err != nil { + return nil, fmt.Errorf("cannot read CA cert: %v", err) + } + + caKeyBytes, err := ioutil.ReadFile(caKeyPath) + if err != nil { + return nil, fmt.Errorf("cannot read CA key: %v", err) + } + + caCertPB, _ := pem.Decode(caCertBytes) + caKeyPB, _ := pem.Decode(caKeyBytes) + caCert, err := x509.ParseCertificate(caCertPB.Bytes) + if err != nil { + return nil, fmt.Errorf("cannot parse CA cert: %v", err) + } + + caPrivKey, err := x509.ParsePKCS1PrivateKey(caKeyPB.Bytes) + if err != nil { + return nil, fmt.Errorf("cannot parse CA key: %v", err) + } + + // Generate a private key for new client cert. + certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + err := fmt.Errorf("cannot generate private key: %v", err) + return nil, err + } + + // Prepare certificate template. + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "OpAMP Example Client", + Organization: []string{"OpAMP Example"}, + Country: []string{"CA"}, + Province: []string{"ON"}, + Locality: []string{"City"}, + StreetAddress: []string{""}, + PostalCode: []string{""}, + }, + IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 1000), + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature, + } + + // Create the client cert. Sign it using CA cert. + certBytes, err := x509.CreateCertificate(rand.Reader, template, caCert, &certPrivKey.PublicKey, caPrivKey) + if err != nil { + err := fmt.Errorf("cannot create certificate: %v", err) + return nil, err + } + + publicKeyPEM := new(bytes.Buffer) + pem.Encode(publicKeyPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + + privateKeyPEM := new(bytes.Buffer) + pem.Encode(privateKeyPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), + }) + + // We have a client certificate with a public and private key. + certificate := &protobufs.TLSCertificate{ + PublicKey: publicKeyPEM.Bytes(), + PrivateKey: privateKeyPEM.Bytes(), + CaPublicKey: caCertBytes, + } + + return certificate, nil +} diff --git a/internal/examples/certs/certs/ca.cert.pem b/internal/certs/certs/ca.cert.pem similarity index 100% rename from internal/examples/certs/certs/ca.cert.pem rename to internal/certs/certs/ca.cert.pem diff --git a/internal/examples/certs/clear.sh b/internal/certs/clear.sh similarity index 100% rename from internal/examples/certs/clear.sh rename to internal/certs/clear.sh diff --git a/internal/examples/certs/client.conf b/internal/certs/client.conf similarity index 100% rename from internal/examples/certs/client.conf rename to internal/certs/client.conf diff --git a/internal/examples/certs/generate.sh b/internal/certs/generate.sh similarity index 100% rename from internal/examples/certs/generate.sh rename to internal/certs/generate.sh diff --git a/internal/examples/certs/index.txt b/internal/certs/index.txt similarity index 100% rename from internal/examples/certs/index.txt rename to internal/certs/index.txt diff --git a/internal/examples/certs/index.txt.attr b/internal/certs/index.txt.attr similarity index 100% rename from internal/examples/certs/index.txt.attr rename to internal/certs/index.txt.attr diff --git a/internal/examples/certs/index.txt.old b/internal/certs/index.txt.old similarity index 100% rename from internal/examples/certs/index.txt.old rename to internal/certs/index.txt.old diff --git a/internal/examples/certs/openssl.conf b/internal/certs/openssl.conf similarity index 100% rename from internal/examples/certs/openssl.conf rename to internal/certs/openssl.conf diff --git a/internal/examples/certs/private/ca.key.pem b/internal/certs/private/ca.key.pem similarity index 100% rename from internal/examples/certs/private/ca.key.pem rename to internal/certs/private/ca.key.pem diff --git a/internal/examples/certs/serial b/internal/certs/serial similarity index 100% rename from internal/examples/certs/serial rename to internal/certs/serial diff --git a/internal/examples/certs/serial.old b/internal/certs/serial.old similarity index 100% rename from internal/examples/certs/serial.old rename to internal/certs/serial.old diff --git a/internal/examples/certs/server.conf b/internal/certs/server.conf similarity index 100% rename from internal/examples/certs/server.conf rename to internal/certs/server.conf diff --git a/internal/examples/certs/server_certs/server.cert.pem b/internal/certs/server_certs/server.cert.pem similarity index 100% rename from internal/examples/certs/server_certs/server.cert.pem rename to internal/certs/server_certs/server.cert.pem diff --git a/internal/examples/certs/server_certs/server.csr b/internal/certs/server_certs/server.csr similarity index 100% rename from internal/examples/certs/server_certs/server.csr rename to internal/certs/server_certs/server.csr diff --git a/internal/examples/certs/server_certs/server.key.pem b/internal/certs/server_certs/server.key.pem similarity index 100% rename from internal/examples/certs/server_certs/server.key.pem rename to internal/certs/server_certs/server.key.pem diff --git a/internal/examples/certs/server_ext.conf b/internal/certs/server_ext.conf similarity index 100% rename from internal/examples/certs/server_ext.conf rename to internal/certs/server_ext.conf diff --git a/internal/examples/agent/agent/agent.go b/internal/examples/agent/agent/agent.go index c82d96cd..5e0a496c 100644 --- a/internal/examples/agent/agent/agent.go +++ b/internal/examples/agent/agent/agent.go @@ -6,7 +6,6 @@ import ( "crypto/x509" "encoding/pem" "fmt" - "log" "math/rand" "os" "runtime" @@ -20,6 +19,7 @@ import ( "github.com/open-telemetry/opamp-go/client" "github.com/open-telemetry/opamp-go/client/types" + "github.com/open-telemetry/opamp-go/internal" "github.com/open-telemetry/opamp-go/protobufs" ) @@ -89,9 +89,17 @@ func NewAgent(logger types.Logger, agentType string, agentVersion string) *Agent func (agent *Agent) connect() error { agent.opampClient = client.NewWebSocket(agent.logger) + tlsConfig, err := internal.CreateClientTLSConfig( + agent.opampClientCert, + "../../certs/certs/ca.cert.pem", + ) + if err != nil { + return err + } + settings := types.StartSettings{ OpAMPServerURL: "wss://127.0.0.1:4320/v1/opamp", - TLSConfig: createClientTLSConfig(agent.opampClientCert), + TLSConfig: tlsConfig, InstanceUid: agent.instanceId.String(), Callbacks: types.CallbacksStruct{ OnConnectFunc: func() { @@ -120,7 +128,7 @@ func (agent *Agent) connect() error { protobufs.AgentCapabilities_AgentCapabilities_AcceptsOpAMPConnectionSettings, } - err := agent.opampClient.SetAgentDescription(agent.agentDescription) + err = agent.opampClient.SetAgentDescription(agent.agentDescription) if err != nil { return err } @@ -137,29 +145,6 @@ func (agent *Agent) connect() error { return nil } -func createClientTLSConfig(clientCert *tls.Certificate) *tls.Config { - // Read the CA's public key. This is the CA that signs the server's certificate. - caCertBytes, err := os.ReadFile("../certs/certs/ca.cert.pem") - if err != nil { - log.Fatalln(err) - } - - // Create a certificate pool and make our CA trusted. - caCertPool := x509.NewCertPool() - if ok := caCertPool.AppendCertsFromPEM(caCertBytes); !ok { - log.Fatalln("Cannot append ca.cert.pem") - } - - cfg := &tls.Config{ - RootCAs: caCertPool, - } - if clientCert != nil { - // If there is a client-side certificate use it for connection too. - cfg.Certificates = []tls.Certificate{*clientCert} - } - return cfg -} - func (agent *Agent) disconnect() { agent.logger.Debugf("Disconnecting from server...") agent.opampClient.Stop(context.Background()) diff --git a/internal/examples/server/opampsrv/opampsrv.go b/internal/examples/server/opampsrv/opampsrv.go index 908b10ae..4beb7818 100644 --- a/internal/examples/server/opampsrv/opampsrv.go +++ b/internal/examples/server/opampsrv/opampsrv.go @@ -2,15 +2,10 @@ package opampsrv import ( "context" - "crypto/tls" - "crypto/x509" - "errors" - "fmt" "log" "net/http" - "os" - "path" + "github.com/open-telemetry/opamp-go/internal" "github.com/open-telemetry/opamp-go/internal/examples/server/data" "github.com/open-telemetry/opamp-go/protobufs" "github.com/open-telemetry/opamp-go/server" @@ -59,7 +54,11 @@ func (srv *Server) Start() { }, ListenEndpoint: "127.0.0.1:4320", } - tlsConfig, err := createServerTLSConfig("../certs") + tlsConfig, err := internal.CreateServerTLSConfig( + "../../certs/certs/ca.cert.pem", + "../../certs/server_certs/server.cert.pem", + "../../certs/server_certs/server.key.pem", + ) if err != nil { srv.logger.Debugf("Could not load TLS config, working without TLS: %v", err.Error()) } @@ -68,42 +67,6 @@ func (srv *Server) Start() { srv.opampSrv.Start(settings) } -func createServerTLSConfig(certsDir string) (*tls.Config, error) { - // Read the CA's public key. This is the CA that signs the server's certificate. - caCertBytes, err := os.ReadFile(path.Join(certsDir, "certs/ca.cert.pem")) - if err != nil { - return nil, err - } - - // Create a certificate pool and make our CA trusted. - caCertPool := x509.NewCertPool() - if ok := caCertPool.AppendCertsFromPEM(caCertBytes); !ok { - return nil, errors.New("cannot append ca.cert.pem") - } - - // Load server's certificate. - cert, err := tls.LoadX509KeyPair( - path.Join(certsDir, "server_certs/server.cert.pem"), - path.Join(certsDir, "server_certs/server.key.pem"), - ) - if err != nil { - return nil, fmt.Errorf("tls.LoadX509KeyPair failed: %v", err) - } - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, - // TODO: verify client cert manually, and allow TOFU option. See manual - // verification example: https://dev.to/living_syn/validating-client-certificate-sans-in-go-i5p - // Instead, we use VerifyClientCertIfGiven which will automatically verify the provided certificate - // is signed by our CA (so TOFU with self-generated client certificate will not work). - ClientAuth: tls.VerifyClientCertIfGiven, - // Allow insecure connections for demo purposes. - InsecureSkipVerify: true, - ClientCAs: caCertPool, - } - tlsConfig.BuildNameToCertificate() - return tlsConfig, nil -} - func (srv *Server) Stop() { srv.opampSrv.Stop(context.Background()) } diff --git a/internal/examples/server/uisrv/ui.go b/internal/examples/server/uisrv/ui.go index ec29628c..3e18f481 100644 --- a/internal/examples/server/uisrv/ui.go +++ b/internal/examples/server/uisrv/ui.go @@ -1,23 +1,14 @@ package uisrv import ( - "bytes" "context" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "io/ioutil" "log" - "math/big" - "net" "net/http" "path" "text/template" "time" + "github.com/open-telemetry/opamp-go/internal" "github.com/open-telemetry/opamp-go/internal/examples/server/data" "github.com/open-telemetry/opamp-go/protobufs" ) @@ -130,7 +121,7 @@ func rotateInstanceClientCert(w http.ResponseWriter, r *http.Request) { } // Create a new certificate for the agent. - certificate, err := createTLSCert() + certificate, err := internal.CreateTLSCert("../../certs/certs/ca.cert.pem", "../../certs/certs/ca.key.pem") if err != nil { w.WriteHeader(http.StatusInternalServerError) logger.Println(err) @@ -162,84 +153,3 @@ func rotateInstanceClientCert(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/agent?instanceid="+string(instanceId), http.StatusSeeOther) } - -func createTLSCert() (*protobufs.TLSCertificate, error) { - certsDir := "../certs" - - // Load CA Cert. - caCertBytes, err := ioutil.ReadFile(path.Join(certsDir, "certs/ca.cert.pem")) - if err != nil { - logger.Fatalf("Cannot read CA cert: %v", err) - } - - caKeyBytes, err := ioutil.ReadFile(path.Join(certsDir, "private/ca.key.pem")) - if err != nil { - logger.Fatalf("Cannot read CA key: %v", err) - } - - caCertPB, _ := pem.Decode(caCertBytes) - caKeyPB, _ := pem.Decode(caKeyBytes) - caCert, err := x509.ParseCertificate(caCertPB.Bytes) - if err != nil { - logger.Fatalf("Cannot parse CA cert: %v", err) - } - - caPrivKey, err := x509.ParsePKCS1PrivateKey(caKeyPB.Bytes) - if err != nil { - logger.Fatalf("Cannot parse CA key: %v", err) - } - - // Generate a private key for new client cert. - certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) - if err != nil { - err := fmt.Errorf("cannot generate private key: %v", err) - return nil, err - } - - // Prepare certificate template. - template := &x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - CommonName: "OpAMP Example Client", - Organization: []string{"OpAMP Example"}, - Country: []string{"CA"}, - Province: []string{"ON"}, - Locality: []string{"City"}, - StreetAddress: []string{""}, - PostalCode: []string{""}, - }, - IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}, - NotBefore: time.Now(), - NotAfter: time.Now().Add(time.Hour * 1000), - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, - KeyUsage: x509.KeyUsageDigitalSignature, - } - - // Create the client cert. Sign it using CA cert. - certBytes, err := x509.CreateCertificate(rand.Reader, template, caCert, &certPrivKey.PublicKey, caPrivKey) - if err != nil { - err := fmt.Errorf("cannot create certificate: %v", err) - return nil, err - } - - publicKeyPEM := new(bytes.Buffer) - pem.Encode(publicKeyPEM, &pem.Block{ - Type: "CERTIFICATE", - Bytes: certBytes, - }) - - privateKeyPEM := new(bytes.Buffer) - pem.Encode(privateKeyPEM, &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), - }) - - // We have a client certificate with a public and private key. - certificate := &protobufs.TLSCertificate{ - PublicKey: publicKeyPEM.Bytes(), - PrivateKey: privateKeyPEM.Bytes(), - CaPublicKey: caCertBytes, - } - - return certificate, nil -}