From ce023f499e161b3b981f43fdf5e57733fd54fbee Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Mon, 22 Jul 2024 10:50:14 -0400 Subject: [PATCH] wip: feat: support secrets for password Signed-off-by: Brooks Townsend --- README.md | 25 ++++++++--- config.go | 37 +++++----------- go.mod | 6 +-- go.sum | 6 --- main.go | 125 ++++++++++++------------------------------------------ run.sh | 8 +--- wadm.yaml | 14 ++---- 7 files changed, 64 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index 81eb371..aaec29e 100644 --- a/README.md +++ b/README.md @@ -18,19 +18,30 @@ wash build ## Run -Prerequisites: +### Prerequisites - [wash 0.29](https://wasmcloud.com/docs/installation) or later - A built couchbase capability provider, see [#build](#build) - Setup Couchbase server with the required configuration for testing using docker-compose.yaml in the repo. - ``` - docker-compose up -d - ``` - Alternatively, you can use [Quick Install](https://docs.couchbase.com/server/current/getting-started/do-a-quick-install.html) guide with a bucket named **test** created. - + +```bash +docker-compose up -d +``` + +Alternatively, you can use [Quick Install](https://docs.couchbase.com/server/current/getting-started/do-a-quick-install.html) guide with a bucket named **test** created. + +### Running ```shell wash up -d +# TODO: provide encryption key setup +secret-nats-kv run & +# Put the password in the NATS KV secrets backend +provider_key=$(wash inspect ./build/wasmcloud-provider-couchbase.par.gz -o json | jq -r '.service') +secrets-nats-kv put password --string password +secrets-nats-kv add-mapping $provider_key --secret password +# Create the secret reference, instructing wasmCloud how to pull the secret +wash config put SECRET_couchbase_password backend=nats-kv key=password wash app deploy ./wadm.yaml ``` @@ -42,7 +53,7 @@ curl localhost:8080/couchbase ## Test -To test the WIT bindings, download [`wit-bindgen`][wit-bindgen] and run the following: +To test the WIT bindings, download [wit-bindgen](https://github.com/bytecodealliance/wit-bindgen) and run the following: ```console wit-deps && wit-bindgen rust --out-dir /tmp/wit wit/ diff --git a/config.go b/config.go index 69cce93..1bd0f48 100644 --- a/config.go +++ b/config.go @@ -7,10 +7,10 @@ import ( ) type CouchbaseConnectionArgs struct { - Username string - Password string - BucketName string - Host string + Username string + Password string + BucketName string + // Host string ConnectionString string ScopeName string CollectionName string @@ -20,39 +20,24 @@ type CouchbaseConnectionArgs struct { func validateCouchbaseConfig(config map[string]string, secrets map[string]provider.SecretValue) (CouchbaseConnectionArgs, error) { connectionArgs := CouchbaseConnectionArgs{} if username, ok := config["username"]; !ok || username == "" { - return connectionArgs, errors.New("username is required") + return connectionArgs, errors.New("username config is required") } else { connectionArgs.Username = username } - if bucketName, ok := config["bucket_name"]; !ok || bucketName == "" { - return connectionArgs, errors.New("bucket_name is required") + if bucketName, ok := config["bucketName"]; !ok || bucketName == "" { + return connectionArgs, errors.New("bucketName config is required") } else { connectionArgs.BucketName = bucketName } - if host, ok := config["host"]; !ok || host == "" { - return connectionArgs, errors.New("host is required") - } else { - connectionArgs.Host = host - } - if connectionString, ok := config["connection_string"]; !ok || connectionString == "" { - return connectionArgs, errors.New("connection_string is required") + if connectionString, ok := config["connectionString"]; !ok || connectionString == "" { + return connectionArgs, errors.New("connectionString config is required") } else { connectionArgs.ConnectionString = connectionString } - if scopeName, ok := config["scope_name"]; !ok || scopeName == "" { - return connectionArgs, errors.New("scope_name is required") - } else { - connectionArgs.ScopeName = scopeName - } - if collectionName, ok := config["collection_name"]; !ok || collectionName == "" { - return connectionArgs, errors.New("collection_name is required") - } else { - connectionArgs.CollectionName = collectionName - } - password := secrets["password"].StringValue() + password := secrets["password"].String.Reveal() if password == "" { - return connectionArgs, errors.New("password is required") + return connectionArgs, errors.New("password secret is required") } else { connectionArgs.Password = password } diff --git a/go.mod b/go.mod index ccfe484..c274460 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,6 @@ require ( require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect @@ -31,12 +30,11 @@ require ( github.com/nats-io/nats.go v1.36.0 // indirect github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nuid v1.0.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 go.opentelemetry.io/otel v1.28.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/sdk v1.28.0 - go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.24.0 // indirect @@ -47,3 +45,5 @@ require ( google.golang.org/grpc v1.64.1 // indirect google.golang.org/protobuf v1.34.2 // indirect ) + +replace github.com/wasmCloud/provider-sdk-go => ../../wasmCloud/provider-sdk-go diff --git a/go.sum b/go.sum index 2808e2c..0fd47f3 100644 --- a/go.sum +++ b/go.sum @@ -25,8 +25,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -87,14 +85,10 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/wasmCloud/provider-sdk-go v0.0.0-20240626135506-00b1fb3dec9e h1:UIK1VDU1hwvGTd4S9pokw/IoY0ChIhYldWB44JnWuD8= -github.com/wasmCloud/provider-sdk-go v0.0.0-20240626135506-00b1fb3dec9e/go.mod h1:DZRTpRkX3YluIs+FKcx157XbQa1q4rFRDpxbamZZBc0= github.com/wrpc/wrpc/go v0.0.0-20240619071643-b830439e40d6 h1:P2X0Tsw8o45TmBZbST5vRguBjK80e7QJ9TX/fh/8l2U= github.com/wrpc/wrpc/go v0.0.0-20240619071643-b830439e40d6/go.mod h1:xbr40Iv8kVVnQHnHzmdbtaxOvyRZLb7y3c90vrGGcxo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= diff --git a/main.go b/main.go index 0e6e49f..ff59fc4 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ package main import ( "context" "errors" + "fmt" "log" "os" "os/signal" @@ -14,14 +15,6 @@ import ( server "github.com/couchbase-examples/wasmcloud-provider-couchbase/bindings" "github.com/couchbase/gocb/v2" "github.com/wasmCloud/provider-sdk-go" - "go.opentelemetry.io/otel" - - "go.opentelemetry.io/otel/exporters/otlp/otlptrace" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" - "go.opentelemetry.io/otel/propagation" - "go.opentelemetry.io/otel/sdk/resource" - "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.4.0" ) func main() { @@ -72,6 +65,8 @@ func run() error { // Store the provider for use in the handlers providerHandler.WasmcloudProvider = p + fmt.Println("host data: %v", providerHandler.WasmcloudProvider.HostData()) + // Setup two channels to await RPC and control interface operations providerCh := make(chan error, 1) signalCh := make(chan os.Signal, 1) @@ -104,32 +99,43 @@ func run() error { return nil } +// Provider handler functions func handleNewTargetLink(handler *Handler, link provider.InterfaceLinkDefinition) error { handler.Logger.Info("Handling new target link", "link", link) handler.linkedFrom[link.SourceID] = link.TargetConfig - err := ValidateCouchbaseConfig(link.TargetConfig) + couchbaseConnectionArgs, err := validateCouchbaseConfig(link.TargetConfig, link.TargetSecrets) if err != nil { handler.Logger.Error("Invalid couchbase target config", "error", err) return err } - handler.updateCouchbaseCluster(handler, link.SourceID, link.TargetConfig) + handler.updateCouchbaseCluster(handler, link.SourceID, couchbaseConnectionArgs) return nil } -func ValidateCouchbaseConfig(config map[string]string) error { - if config["username"] == "" { - return errors.New("username is required") - } - if config["password"] == "" { - return errors.New("password is required") +func (h *Handler) updateCouchbaseCluster(handler *Handler, sourceId string, connectionArgs CouchbaseConnectionArgs) { + // Connect to the cluster + cluster, err := gocb.Connect(connectionArgs.ConnectionString, gocb.ClusterOptions{ + Username: connectionArgs.Username, + Password: connectionArgs.Password, + }) + if err != nil { + handler.Logger.Error("unable to connect to couchbase cluster", "error", err) + return } - if config["bucket"] == "" { - return errors.New("bucket is required") + var collection *gocb.Collection + if connectionArgs.CollectionName != "" && connectionArgs.ScopeName != "" { + collection = cluster.Bucket(connectionArgs.BucketName).Scope(connectionArgs.ScopeName).Collection(connectionArgs.CollectionName) + } else { + collection = cluster.Bucket(connectionArgs.BucketName).DefaultCollection() } - if config["host"] == "" { - return errors.New("host is required") + + bucket := cluster.Bucket(connectionArgs.BucketName) + if err = bucket.WaitUntilReady(5*time.Second, nil); err != nil { + handler.Logger.Error("unable to connect to couchbase bucket", "error", err) } - return nil + + // Store the connection + handler.clusterConnections[sourceId] = collection } func handleDelTargetLink(handler *Handler, link provider.InterfaceLinkDefinition) error { @@ -139,7 +145,7 @@ func handleDelTargetLink(handler *Handler, link provider.InterfaceLinkDefinition } func handleHealthCheck(handler *Handler) string { - handler.Logger.Info("Handling health check") + handler.Logger.Debug("Handling health check") return "provider healthy" } @@ -148,78 +154,3 @@ func handleShutdown(handler *Handler) error { // clear(handler.linkedFrom) return nil } - -func setupOTelSDK(ctx context.Context) (shutdown func(context.Context) error, err error) { - var shutdownFuncs []func(context.Context) error - - // shutdown calls cleanup functions registered via shutdownFuncs. - // The errors from the calls are joined. - // Each registered cleanup will be invoked once. - shutdown = func(ctx context.Context) error { - var err error - for _, fn := range shutdownFuncs { - err = errors.Join(err, fn(ctx)) - } - shutdownFuncs = nil - return err - } - - // handleErr calls shutdown for cleanup and makes sure that all errors are returned. - handleErr := func(inErr error) { - err = errors.Join(inErr, shutdown(ctx)) - } - - // Set up propagator. - prop := newPropagator() - otel.SetTextMapPropagator(prop) - - // Set up trace provider. - tracerProvider, err := newTraceProvider() - if err != nil { - handleErr(err) - return - } - shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) - otel.SetTracerProvider(tracerProvider) - traceProvider := otel.GetTracerProvider() - tracer = traceProvider.Tracer(TRACER_NAME) - - return -} - -func newExporter(ctx context.Context) (*otlptrace.Exporter, error) { - - exporter, err := otlptrace.New( - context.Background(), - otlptracehttp.NewClient( - otlptracehttp.WithEndpoint("localhost:4318"), - - otlptracehttp.WithInsecure(), - ), - ) - return exporter, err -} - -func newTraceProvider() (*trace.TracerProvider, error) { - traceExporter, err := newExporter(context.Background()) - if err != nil { - return nil, err - } - - traceProvider := trace.NewTracerProvider( - trace.WithBatcher(traceExporter, - // Default is 5s. Set to 1s for demonstrative purposes. - trace.WithBatchTimeout(time.Second)), - trace.WithResource(resource.NewWithAttributes( - semconv.SchemaURL, - semconv.ServiceNameKey.String("couchbase-provider"), - ))) - return traceProvider, nil -} - -func newPropagator() propagation.TextMapPropagator { - return propagation.NewCompositeTextMapPropagator( - propagation.TraceContext{}, - propagation.Baggage{}, - ) -} diff --git a/run.sh b/run.sh index c585283..aa6fd14 100755 --- a/run.sh +++ b/run.sh @@ -5,12 +5,6 @@ host_data=' "lattice_rpc_url": "0.0.0.0:4222", "lattice_rpc_prefix": "default", "provider_key": "couchbase", - "link_name": "default", - "config": { - "username": "Administrator", - "password": "password", - "bucketName": "test", - "connectionString": "localhost" - } + "link_name": "default" }' echo $host_data | base64 | go run ./ diff --git a/wadm.yaml b/wadm.yaml index cf37ade..a2b5083 100644 --- a/wadm.yaml +++ b/wadm.yaml @@ -14,7 +14,7 @@ spec: traits: - type: spreadscaler properties: - replicas: 1 + replicas: 100 # Link the component to the provider on wasi-keyvalue - type: link properties: @@ -23,30 +23,22 @@ spec: package: keyvalue interfaces: [atomics, store] target_config: + - name: SECRET_couchbase_password - name: provider-config properties: username: 'Administrator' - password: 'password' bucketName: 'test' connectionString: 'localhost' - scopeName: 'test' - name: couchbase type: capability properties: image: file://./build/wasmcloud-provider-couchbase.par.gz id: couchbase - config: - - name: provider-config - properties: - username: 'Administrator' - password: 'password' - bucketName: 'test' - connectionString: 'localhost' # Add a capability provider that enables HTTP access - name: httpserver type: capability properties: - image: ghcr.io/wasmcloud/http-server:0.20.0 + image: ghcr.io/wasmcloud/http-server:0.21.0 traits: # Link the httpserver to the component, and configure the HTTP server # to listen on port 8080 for incoming requests