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

Add server TLS settings test case #202

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
160 changes: 160 additions & 0 deletions internal/certs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package internal

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"math/big"
"net"
"os"
"path"
"time"

"github.com/open-telemetry/opamp-go/protobufs"
)

func CreateClientTLSConfig(clientCert *tls.Certificate, 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
}

Check warning on line 28 in internal/certs.go

View check run for this annotation

Codecov / codecov/patch

internal/certs.go#L23-L28

Added lines #L23 - L28 were not covered by tests

// 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")
}

Check warning on line 34 in internal/certs.go

View check run for this annotation

Codecov / codecov/patch

internal/certs.go#L31-L34

Added lines #L31 - L34 were not covered by tests

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

Check warning on line 43 in internal/certs.go

View check run for this annotation

Codecov / codecov/patch

internal/certs.go#L36-L43

Added lines #L36 - L43 were not covered by tests
}

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
}

Check warning on line 51 in internal/certs.go

View check run for this annotation

Codecov / codecov/patch

internal/certs.go#L50-L51

Added lines #L50 - L51 were not covered by tests

// 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")
}

Check warning on line 57 in internal/certs.go

View check run for this annotation

Codecov / codecov/patch

internal/certs.go#L56-L57

Added lines #L56 - L57 were not covered by tests

// 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)
}

Check warning on line 66 in internal/certs.go

View check run for this annotation

Codecov / codecov/patch

internal/certs.go#L65-L66

Added lines #L65 - L66 were not covered by tests
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(certsDir string) (*protobufs.TLSCertificate, error) {

// Load CA Cert.
caCertBytes, err := ioutil.ReadFile(path.Join(certsDir, "certs/ca.cert.pem"))
if err != nil {
return nil, fmt.Errorf("cannot read CA cert: %v", err)
}

Check warning on line 88 in internal/certs.go

View check run for this annotation

Codecov / codecov/patch

internal/certs.go#L82-L88

Added lines #L82 - L88 were not covered by tests

caKeyBytes, err := ioutil.ReadFile(path.Join(certsDir, "private/ca.key.pem"))
if err != nil {
return nil, fmt.Errorf("cannot read CA key: %v", err)
}

Check warning on line 93 in internal/certs.go

View check run for this annotation

Codecov / codecov/patch

internal/certs.go#L90-L93

Added lines #L90 - L93 were not covered by tests

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)
}

Check warning on line 100 in internal/certs.go

View check run for this annotation

Codecov / codecov/patch

internal/certs.go#L95-L100

Added lines #L95 - L100 were not covered by tests

caPrivKey, err := x509.ParsePKCS1PrivateKey(caKeyPB.Bytes)
if err != nil {
return nil, fmt.Errorf("cannot parse CA key: %v", err)
}

Check warning on line 105 in internal/certs.go

View check run for this annotation

Codecov / codecov/patch

internal/certs.go#L102-L105

Added lines #L102 - L105 were not covered by tests

// 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
}

Check warning on line 112 in internal/certs.go

View check run for this annotation

Codecov / codecov/patch

internal/certs.go#L108-L112

Added lines #L108 - L112 were not covered by tests

// 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
}

Check warning on line 138 in internal/certs.go

View check run for this annotation

Codecov / codecov/patch

internal/certs.go#L115-L138

Added lines #L115 - L138 were not covered by tests

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

Check warning on line 159 in internal/certs.go

View check run for this annotation

Codecov / codecov/patch

internal/certs.go#L140-L159

Added lines #L140 - L159 were not covered by tests
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
34 changes: 8 additions & 26 deletions internal/examples/agent/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
"log"
"math/rand"
"os"
"runtime"
Expand All @@ -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"
)

Expand Down Expand Up @@ -89,9 +89,14 @@ 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")
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() {
Expand Down Expand Up @@ -120,7 +125,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
}
Expand All @@ -137,29 +142,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())
Expand Down
4 changes: 2 additions & 2 deletions internal/examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ module github.com/open-telemetry/opamp-go/internal/examples
go 1.17

require (
github.com/cenkalti/backoff/v4 v4.1.2
github.com/knadh/koanf v1.3.3
github.com/oklog/ulid/v2 v2.0.2
github.com/open-telemetry/opamp-go v0.1.0
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/stretchr/testify v1.7.0
go.opentelemetry.io/otel v1.3.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.26.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.26.0
Expand All @@ -17,7 +19,6 @@ require (
)

require (
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-logr/logr v1.2.1 // indirect
Expand All @@ -30,7 +31,6 @@ require (
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.7.0 // indirect
github.com/tklauser/go-sysconf v0.3.9 // indirect
github.com/tklauser/numcpus v0.3.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
Expand Down
45 changes: 2 additions & 43 deletions internal/examples/server/opampsrv/opampsrv.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -59,7 +54,7 @@ func (srv *Server) Start() {
},
ListenEndpoint: "127.0.0.1:4320",
}
tlsConfig, err := createServerTLSConfig("../certs")
tlsConfig, err := internal.CreateServerTLSConfig("../../certs")
if err != nil {
srv.logger.Debugf("Could not load TLS config, working without TLS: %v", err.Error())
}
Expand All @@ -68,42 +63,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())
}
Expand Down
Loading
Loading