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

[ASCII-2582] Add certificate generation and retrieval for IPC communications #31838

Merged
7 changes: 6 additions & 1 deletion comp/api/authtoken/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@
package authtoken

import (
"crypto/tls"

misteriaud marked this conversation as resolved.
Show resolved Hide resolved
"go.uber.org/fx"

"github.com/DataDog/datadog-agent/pkg/util/fxutil"
"github.com/DataDog/datadog-agent/pkg/util/optional"
"go.uber.org/fx"
)

// team: agent-shared-components

// Component is the component type.
type Component interface {
Get() string
GetTLSClientConfig() *tls.Config
GetTLSServerConfig() *tls.Config
}

// NoneModule return a None optional type for authtoken.Component.
Expand Down
12 changes: 12 additions & 0 deletions comp/api/authtoken/createandfetchimpl/authtoken.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
package createandfetchimpl

import (
"crypto/tls"

"go.uber.org/fx"

"github.com/DataDog/datadog-agent/comp/api/authtoken"
Expand Down Expand Up @@ -49,3 +51,13 @@ func newAuthToken(deps dependencies) (authtoken.Component, error) {
func (at *authToken) Get() string {
return util.GetAuthToken()
}

// GetTLSServerConfig return a TLS configuration with the IPC certificate for http.Server
func (at *authToken) GetTLSClientConfig() *tls.Config {
return util.GetTLSClientConfig()
}

// GetTLSServerConfig return a TLS configuration with the IPC certificate for http.Client
func (at *authToken) GetTLSServerConfig() *tls.Config {
return util.GetTLSServerConfig()
}
6 changes: 4 additions & 2 deletions comp/api/authtoken/createandfetchimpl/authtoken_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,24 @@ import (
"path/filepath"
"testing"

"github.com/DataDog/datadog-agent/pkg/api/util"
"github.com/DataDog/datadog-agent/pkg/util/fxutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/fx"

"github.com/DataDog/datadog-agent/comp/core/config"
log "github.com/DataDog/datadog-agent/comp/core/log/def"
logmock "github.com/DataDog/datadog-agent/comp/core/log/mock"
"github.com/DataDog/datadog-agent/pkg/api/util"
"github.com/DataDog/datadog-agent/pkg/util/fxutil"
)

func TestGet(t *testing.T) {
dir := t.TempDir()
authPath := filepath.Join(dir, "auth_token")
ipcPath := filepath.Join(dir, "ipc_cert")
overrides := map[string]any{
"auth_token_file_path": authPath,
"ipc_cert_file_path": ipcPath,
}

comp, err := newAuthToken(
Expand Down
43 changes: 36 additions & 7 deletions comp/api/authtoken/fetchonlyimpl/authtoken.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
package fetchonlyimpl

import (
"crypto/tls"
"fmt"

"go.uber.org/fx"

"github.com/DataDog/datadog-agent/comp/api/authtoken"
Expand All @@ -26,9 +29,8 @@ func Module() fxutil.Module {
}

type authToken struct {
log log.Component
conf config.Component

log log.Component
conf config.Component
tokenLoaded bool
}

Expand All @@ -48,17 +50,44 @@ func newAuthToken(deps dependencies) authtoken.Component {
}
}

// Get returns the session token
func (at *authToken) Get() string {
func (at *authToken) setToken() error {
if !at.tokenLoaded {
// We try to load the auth_token until we succeed since it might be created at some point by another
// process.
if err := util.SetAuthToken(at.conf); err != nil {
at.log.Debugf("could not load auth_token: %s", err)
return ""
return fmt.Errorf("could not load auth_token: %s", err)
}
at.tokenLoaded = true
}
return nil
}

// Get returns the session token
func (at *authToken) Get() string {
if err := at.setToken(); err != nil {
at.log.Debugf("%s", err.Error())
return ""
}

return util.GetAuthToken()
}

// GetTLSClientConfig return a TLS configuration with the IPC certificate for http.Client
func (at *authToken) GetTLSClientConfig() *tls.Config {
if err := at.setToken(); err != nil {
at.log.Debugf("%s", err.Error())
return nil
}

return util.GetTLSClientConfig()
}

// GetTLSServerConfig return a TLS configuration with the IPC certificate for http.Server
func (at *authToken) GetTLSServerConfig() *tls.Config {
if err := at.setToken(); err != nil {
at.log.Debugf("%s", err.Error())
return nil
}

return util.GetTLSServerConfig()
}
14 changes: 13 additions & 1 deletion comp/api/authtoken/fetchonlyimpl/authtoken_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import (
"path/filepath"
"testing"

"github.com/DataDog/datadog-agent/pkg/util/fxutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/fx"

"github.com/DataDog/datadog-agent/pkg/api/security/cert"
"github.com/DataDog/datadog-agent/pkg/util/fxutil"

"github.com/DataDog/datadog-agent/comp/core/config"
log "github.com/DataDog/datadog-agent/comp/core/log/def"
logmock "github.com/DataDog/datadog-agent/comp/core/log/mock"
Expand All @@ -23,6 +25,7 @@ import (
func TestGet(t *testing.T) {
dir := t.TempDir()
authPath := filepath.Join(dir, "auth_token")
var cfg config.Component
overrides := map[string]any{
"auth_token_file_path": authPath,
}
Expand All @@ -32,6 +35,7 @@ func TestGet(t *testing.T) {
t,
fx.Provide(func() log.Component { return logmock.New(t) }),
config.MockModule(),
fx.Populate(&cfg),
fx.Replace(config.MockParams{Overrides: overrides}),
),
).(*authToken)
Expand All @@ -42,6 +46,14 @@ func TestGet(t *testing.T) {
err := os.WriteFile(authPath, []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), 0777)
require.NoError(t, err)

// Should be empty because the cert/key weren't generated yet
assert.Empty(t, comp.Get())
assert.False(t, comp.tokenLoaded)

// generating IPC cert/key files
_, _, err = cert.CreateOrFetchAgentIPCCert(cfg)
require.NoError(t, err)

assert.Equal(t, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", comp.Get())
assert.True(t, comp.tokenLoaded)

Expand Down
17 changes: 16 additions & 1 deletion comp/api/authtoken/fetchonlyimpl/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
package fetchonlyimpl

import (
"crypto/tls"

"go.uber.org/fx"

authtokeninterface "github.com/DataDog/datadog-agent/comp/api/authtoken"
"github.com/DataDog/datadog-agent/pkg/util/fxutil"
"go.uber.org/fx"
)

// MockModule defines the fx options for the mock component.
Expand All @@ -28,6 +31,18 @@ func (fc *MockFetchOnly) Get() string {
return "a string"
}

// GetTLSClientConfig is a mock of the fetchonly GetTLSClientConfig function
func (fc *MockFetchOnly) GetTLSClientConfig() *tls.Config {
return &tls.Config{
InsecureSkipVerify: true,
}
}

// GetTLSServerConfig is a mock of the fetchonly GetTLSServerConfig function
func (fc *MockFetchOnly) GetTLSServerConfig() *tls.Config {
return &tls.Config{}
}

// NewMock returns a new fetch only authtoken mock
func newMock() authtokeninterface.Component {
return &MockFetchOnly{}
Expand Down
75 changes: 75 additions & 0 deletions pkg/api/security/cert/cert_generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

// Package cert provide useful functions to generate certificates
package cert

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"time"
)

func certTemplate() (*x509.Certificate, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, fmt.Errorf("failed to generate serial number: %s", err)
}

notBefore := time.Now()
// 50 years duration
notAfter := notBefore.Add(50 * 365 * 24 * time.Hour)
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Datadog, Inc."},
},
NotBefore: notBefore,
NotAfter: notAfter,
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature | x509.KeyUsageCRLSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
IsCA: true,
IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
DNSNames: []string{"localhost"},
}

return &template, nil
}

func generateCertKeyPair() ([]byte, []byte, error) {
rootCertTmpl, err := certTemplate()
if err != nil {
return nil, nil, err
}

rootKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, fmt.Errorf("Unable to generate IPC private key: %v", err)
}

certDER, err := x509.CreateCertificate(rand.Reader, rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey)
if err != nil {
return nil, nil, err
}

certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
rawKey, err := x509.MarshalECPrivateKey(rootKey)
if err != nil {
return nil, nil, fmt.Errorf("Unable to marshall private key: %v", err)
}

keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: rawKey})

return certPEM, keyPEM, nil
}
71 changes: 71 additions & 0 deletions pkg/api/security/cert/cert_generator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

package cert

import (
"crypto/tls"
"crypto/x509"
"io"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCertCommunication(t *testing.T) {
certPEM, keyPEM, err := generateCertKeyPair()
assert.NoError(t, err)

// Load server certificate
serverCert, err := tls.X509KeyPair(certPEM, keyPEM)
assert.NoError(t, err)

// Create a certificate pool with the generated certificate
certPool := x509.NewCertPool()
ok := certPool.AppendCertsFromPEM(certPEM)
assert.True(t, ok)

// Create a TLS config for the server
serverTLSConfig := &tls.Config{
Certificates: []tls.Certificate{serverCert},
}

// Create a TLS config for the client
clientTLSConfig := &tls.Config{
RootCAs: certPool,
}

expectedResult := []byte("hello word")

// Create a HTTPS Server
s := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Write(expectedResult)
}))

s.TLS = serverTLSConfig
s.StartTLS()
t.Cleanup(func() {
s.Close()
})

// Create a HTTPS Client
client := http.Client{
Transport: &http.Transport{
TLSClientConfig: clientTLSConfig,
},
}

// Try to communicate together
resp, err := client.Get(s.URL)
require.NoError(t, err)
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, body, expectedResult)
}
Loading
Loading