Skip to content

Commit

Permalink
Beholder CSA Authentication (#877)
Browse files Browse the repository at this point in the history
* Rename getAttributes to getMap

* Fix getMap

* Add Authenticator to Beholder

* Use Authenticator in Beholder

* Add Authenticator to Beholder global

* Use Authenticator Headers in LOOP

* Add authenticator to HTTP client

* Fix config test

* Add pub key getter to authenticator

* Set CSA pub key on Otel resource

* Add noop value to authenticator

* Move auth tests to beholder package, unexport new auth

* Simplify auth header approach

* Remove duplicate test

* Use ed25519 keys instead of signer

* Remove pub key from args

---------

Co-authored-by: nanchano <nicolas.anchano@smartcontract.com>
Co-authored-by: Pavel <177363085+pkcll@users.noreply.github.com>
Co-authored-by: Geert G <117188496+cll-gg@users.noreply.github.com>
  • Loading branch information
4 people authored Nov 8, 2024
1 parent 4ae4553 commit 914b88b
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 1 deletion.
27 changes: 27 additions & 0 deletions pkg/beholder/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package beholder

import (
"crypto/ed25519"
"fmt"
)

// authHeaderKey is the name of the header that the node authenticator will use to send the auth token
var authHeaderKey = "X-Beholder-Node-Auth-Token"

// authHeaderVersion is the version of the auth header format
var authHeaderVersion = "1"

// BuildAuthHeaders creates the auth header value to be included on requests.
// The current format for the header is:
//
// <version>:<public_key_hex>:<signature_hex>
//
// where the byte value of <public_key_hex> is what's being signed
func BuildAuthHeaders(privKey ed25519.PrivateKey) map[string]string {
pubKey := privKey.Public().(ed25519.PublicKey)
messageBytes := pubKey
signature := ed25519.Sign(privKey, messageBytes)
headerValue := fmt.Sprintf("%s:%x:%x", authHeaderVersion, messageBytes, signature)

return map[string]string{authHeaderKey: headerValue}
}
22 changes: 22 additions & 0 deletions pkg/beholder/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package beholder

import (
"crypto/ed25519"
"encoding/hex"
"testing"

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

func TestBuildAuthHeaders(t *testing.T) {
csaPrivKeyHex := "1ac84741fa51c633845fa65c06f37a700303619135630a01f2d22fb98eb1c54ecab39509e63cfaa81c70e2c907391f96803aacb00db5619a5ace5588b4b08159"
csaPrivKeyBytes, err := hex.DecodeString(csaPrivKeyHex)
assert.NoError(t, err)
csaPrivKey := ed25519.PrivateKey(csaPrivKeyBytes)

expectedHeaders := map[string]string{
"X-Beholder-Node-Auth-Token": "1:cab39509e63cfaa81c70e2c907391f96803aacb00db5619a5ace5588b4b08159:4403178e299e9acc5b48ae97de617d3975c5d431b794cfab1d23eda01c194119b2360f5f74cfb3e4f706237ab57a0ba88ffd3f8addbc1e5197b3d3e13a1fc409",
}

assert.Equal(t, expectedHeaders, BuildAuthHeaders(csaPrivKey))
}
18 changes: 18 additions & 0 deletions pkg/beholder/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func newGRPCClient(cfg Config, otlploggrpcNew otlploggrpcFactory) (*Client, erro
opts := []otlploggrpc.Option{
otlploggrpc.WithTLSCredentials(creds),
otlploggrpc.WithEndpoint(cfg.OtelExporterGRPCEndpoint),
otlploggrpc.WithHeaders(cfg.AuthHeaders),
}
if cfg.LogRetryConfig != nil {
// NOTE: By default, the retry is enabled in the OTel SDK
Expand Down Expand Up @@ -242,6 +243,21 @@ func newOtelResource(cfg Config) (resource *sdkresource.Resource, err error) {
if err != nil {
return nil, err
}

// Add csa public key resource attribute
csaPublicKeyHex := "not-configured"
if len(cfg.AuthPublicKeyHex) > 0 {
csaPublicKeyHex = cfg.AuthPublicKeyHex
}
csaPublicKeyAttr := attribute.String("csa_public_key", csaPublicKeyHex)
resource, err = sdkresource.Merge(
sdkresource.NewSchemaless(csaPublicKeyAttr),
resource,
)
if err != nil {
return nil, err
}

// Add custom resource attributes
resource, err = sdkresource.Merge(
sdkresource.NewSchemaless(cfg.ResourceAttributes...),
Expand Down Expand Up @@ -282,6 +298,7 @@ func newTracerProvider(config Config, resource *sdkresource.Resource, creds cred
exporterOpts := []otlptracegrpc.Option{
otlptracegrpc.WithTLSCredentials(creds),
otlptracegrpc.WithEndpoint(config.OtelExporterGRPCEndpoint),
otlptracegrpc.WithHeaders(config.AuthHeaders),
}
if config.TraceRetryConfig != nil {
// NOTE: By default, the retry is enabled in the OTel SDK
Expand Down Expand Up @@ -318,6 +335,7 @@ func newMeterProvider(config Config, resource *sdkresource.Resource, creds crede
opts := []otlpmetricgrpc.Option{
otlpmetricgrpc.WithTLSCredentials(creds),
otlpmetricgrpc.WithEndpoint(config.OtelExporterGRPCEndpoint),
otlpmetricgrpc.WithHeaders(config.AuthHeaders),
}
if config.MetricRetryConfig != nil {
// NOTE: By default, the retry is enabled in the OTel SDK
Expand Down
4 changes: 4 additions & 0 deletions pkg/beholder/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ type Config struct {
LogBatchProcessor bool
// Retry config for shared log exporter, used by Emitter and Logger
LogRetryConfig *RetryConfig

// Auth
AuthPublicKeyHex string
AuthHeaders map[string]string
}

type RetryConfig struct {
Expand Down
2 changes: 1 addition & 1 deletion pkg/beholder/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ func ExampleConfig() {
}
fmt.Printf("%+v\n", *config.LogRetryConfig)
// Output:
// {InsecureConnection:true CACertFile: OtelExporterGRPCEndpoint:localhost:4317 OtelExporterHTTPEndpoint:localhost:4318 ResourceAttributes:[{Key:package_name Value:{vtype:4 numeric:0 stringly:beholder slice:<nil>}} {Key:sender Value:{vtype:4 numeric:0 stringly:beholderclient slice:<nil>}}] EmitterExportTimeout:1s EmitterBatchProcessor:true TraceSampleRatio:1 TraceBatchTimeout:1s TraceSpanExporter:<nil> TraceRetryConfig:<nil> MetricReaderInterval:1s MetricRetryConfig:<nil> LogExportTimeout:1s LogBatchProcessor:true LogRetryConfig:<nil>}
// {InsecureConnection:true CACertFile: OtelExporterGRPCEndpoint:localhost:4317 OtelExporterHTTPEndpoint:localhost:4318 ResourceAttributes:[{Key:package_name Value:{vtype:4 numeric:0 stringly:beholder slice:<nil>}} {Key:sender Value:{vtype:4 numeric:0 stringly:beholderclient slice:<nil>}}] EmitterExportTimeout:1s EmitterBatchProcessor:true TraceSampleRatio:1 TraceBatchTimeout:1s TraceSpanExporter:<nil> TraceRetryConfig:<nil> MetricReaderInterval:1s MetricRetryConfig:<nil> LogExportTimeout:1s LogBatchProcessor:true LogRetryConfig:<nil> AuthPublicKeyHex: AuthHeaders:map[]}
// {InitialInterval:5s MaxInterval:30s MaxElapsedTime:1m0s}
}
3 changes: 3 additions & 0 deletions pkg/beholder/httpclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func newHTTPClient(cfg Config, otlploghttpNew otlploghttpFactory) (*Client, erro
opts := []otlploghttp.Option{
tlsConfigOption,
otlploghttp.WithEndpoint(cfg.OtelExporterHTTPEndpoint),
otlploghttp.WithHeaders(cfg.AuthHeaders),
}
if cfg.LogRetryConfig != nil {
// NOTE: By default, the retry is enabled in the OTel SDK
Expand Down Expand Up @@ -164,6 +165,7 @@ func newHTTPTracerProvider(config Config, resource *sdkresource.Resource, tlsCon
exporterOpts := []otlptracehttp.Option{
tlsConfigOption,
otlptracehttp.WithEndpoint(config.OtelExporterHTTPEndpoint),
otlptracehttp.WithHeaders(config.AuthHeaders),
}
if config.TraceRetryConfig != nil {
// NOTE: By default, the retry is enabled in the OTel SDK
Expand Down Expand Up @@ -205,6 +207,7 @@ func newHTTPMeterProvider(config Config, resource *sdkresource.Resource, tlsConf
opts := []otlpmetrichttp.Option{
tlsConfigOption,
otlpmetrichttp.WithEndpoint(config.OtelExporterHTTPEndpoint),
otlpmetrichttp.WithHeaders(config.AuthHeaders),
}
if config.MetricRetryConfig != nil {
// NOTE: By default, the retry is enabled in the OTel SDK
Expand Down
11 changes: 11 additions & 0 deletions pkg/loop/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const (
envTelemetryCACertFile = "CL_TELEMETRY_CA_CERT_FILE"
envTelemetryAttribute = "CL_TELEMETRY_ATTRIBUTE_"
envTelemetryTraceSampleRatio = "CL_TELEMETRY_TRACE_SAMPLE_RATIO"
envTelemetryAuthHeader = "CL_TELEMETRY_AUTH_HEADER"
envTelemetryAuthPubKeyHex = "CL_TELEMETRY_AUTH_PUB_KEY_HEX"
)

// EnvConfig is the configuration between the application and the LOOP executable. The values
Expand All @@ -47,6 +49,8 @@ type EnvConfig struct {
TelemetryCACertFile string
TelemetryAttributes OtelAttributes
TelemetryTraceSampleRatio float64
TelemetryAuthHeaders map[string]string
TelemetryAuthPubKeyHex string
}

// AsCmdEnv returns a slice of environment variable key/value pairs for an exec.Cmd.
Expand Down Expand Up @@ -78,6 +82,11 @@ func (e *EnvConfig) AsCmdEnv() (env []string) {
add(envTelemetryAttribute+k, v)
}

for k, v := range e.TelemetryAuthHeaders {
add(envTelemetryAuthHeader+k, v)
}
add(envTelemetryAuthPubKeyHex, e.TelemetryAuthPubKeyHex)

return
}

Expand Down Expand Up @@ -124,6 +133,8 @@ func (e *EnvConfig) parse() error {
e.TelemetryCACertFile = os.Getenv(envTelemetryCACertFile)
e.TelemetryAttributes = getMap(envTelemetryAttribute)
e.TelemetryTraceSampleRatio = getFloat64OrZero(envTelemetryTraceSampleRatio)
e.TelemetryAuthHeaders = getMap(envTelemetryAuthHeader)
e.TelemetryAuthPubKeyHex = os.Getenv(envTelemetryAuthPubKeyHex)
}
return nil
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/loop/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ func TestEnvConfig_AsCmdEnv(t *testing.T) {
TelemetryCACertFile: "foo/bar",
TelemetryAttributes: OtelAttributes{"foo": "bar", "baz": "42"},
TelemetryTraceSampleRatio: 0.42,
TelemetryAuthHeaders: map[string]string{"header-key": "header-value"},
TelemetryAuthPubKeyHex: "pub-key-hex",
}
got := map[string]string{}
for _, kv := range envCfg.AsCmdEnv() {
Expand All @@ -152,6 +154,8 @@ func TestEnvConfig_AsCmdEnv(t *testing.T) {
assert.Equal(t, "0.42", got[envTelemetryTraceSampleRatio])
assert.Equal(t, "bar", got[envTelemetryAttribute+"foo"])
assert.Equal(t, "42", got[envTelemetryAttribute+"baz"])
assert.Equal(t, "header-value", got[envTelemetryAuthHeader+"header-key"])
assert.Equal(t, "pub-key-hex", got[envTelemetryAuthPubKeyHex])
}

func TestGetMap(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions pkg/loop/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,15 @@ func (s *Server) start() error {
if tracingConfig.Enabled {
attributes = tracingConfig.Attributes()
}

beholderCfg := beholder.Config{
InsecureConnection: envCfg.TelemetryInsecureConnection,
CACertFile: envCfg.TelemetryCACertFile,
OtelExporterGRPCEndpoint: envCfg.TelemetryEndpoint,
ResourceAttributes: append(attributes, envCfg.TelemetryAttributes.AsStringAttributes()...),
TraceSampleRatio: envCfg.TelemetryTraceSampleRatio,
AuthHeaders: envCfg.TelemetryAuthHeaders,
AuthPublicKeyHex: envCfg.TelemetryAuthPubKeyHex,
}

if tracingConfig.Enabled {
Expand Down

0 comments on commit 914b88b

Please sign in to comment.