From 543495ddb72782ca6a81e9560444350d340cc3d0 Mon Sep 17 00:00:00 2001 From: filipecosta90 Date: Mon, 11 May 2020 00:42:01 +0100 Subject: [PATCH] [add] Added an example on how to use an SSL Connection with RediSearch --- .circleci/config.yml | 48 +++++- Makefile | 18 ++- .../redisearch_auth.go} | 12 +- .../redisearch_quickstart.go} | 0 .../redisearch_tls_client.go | 115 ++++++++++++++ redisearch/example_client_test.go | 144 +++++++++++++++++- redisearch/package.go | 53 ------- redisearch/redisearch_test.go | 42 ----- 8 files changed, 325 insertions(+), 107 deletions(-) rename examples/{auth/main.go => redisearch_auth/redisearch_auth.go} (92%) rename examples/{quickstart/main.go => redisearch_quickstart/redisearch_quickstart.go} (100%) create mode 100644 examples/redisearch_tls_client/redisearch_tls_client.go delete mode 100644 redisearch/package.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 878c9b2..6c7d02f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,6 +3,46 @@ # Check https://circleci.com/docs/2.0/language-go/ for more details version: 2 jobs: + build-tls: + machine: + enabled: true + image: ubuntu-1604:202004-01 + steps: + - checkout + - run: + name: Setting GOPATH + command: | + go version + go env -w GOPATH=$HOME/go + - run: + name: Generate a root CA and a server certificate using redis helpers + command: | + git clone git://github.com/antirez/redis.git --branch 6.0.1 + cd redis + ./utils/gen-test-certs.sh + cd .. + - run: + name: Copy RediSearch + command: | + docker run --rm --entrypoint cat redislabs/redisearch:edge /usr/lib/redis/modules/redisearch.so > redisearch.so + chmod 755 redisearch.so + - run: + name: Run RedisAI with tls support + command: | + docker run -d -v $(pwd)/redisearch.so:/data/redisearch.so \ + -v $(pwd)/redis/tests/tls/:/data \ + -p 6379:6379 redis redis-server --tls-port 6379 --port 0 \ + --tls-cert-file /data/redis.crt \ + --tls-key-file /data/redis.key \ + --tls-ca-cert-file /data/ca.crt \ + --tls-auth-clients no --loadmodule /data/redisearch.so + - run: + name: Run Examples + command: | + make examples TLS_CERT=redis/tests/tls/redis.crt \ + TLS_KEY=redis/tests/tls/redis.key \ + TLS_CACERT=redis/tests/tls/ca.crt + build: # test with redisearch:edge docker: - image: circleci/golang:1.9 @@ -23,14 +63,15 @@ jobs: working_directory: /go/src/github.com/RediSearch/redisearch-go steps: - checkout - - run: go get -v -t -d ./... - - run: go test -v ./... -race #no need for codecov on nightly + - run: make get + - run: make test workflows: version: 2 commit: jobs: - build + - build-tls nightly: triggers: - schedule: @@ -40,4 +81,5 @@ workflows: only: - master jobs: - - build_nightly \ No newline at end of file + - build_nightly + - build-tls \ No newline at end of file diff --git a/Makefile b/Makefile index 435f7fb..75e975a 100644 --- a/Makefile +++ b/Makefile @@ -8,11 +8,27 @@ GOGET=$(GOCMD) get GOMOD=$(GOCMD) mod .PHONY: all test coverage -all: test coverage +all: test coverage examples get: $(GOGET) -t -v ./... +TLS_CERT ?= redis.crt +TLS_KEY ?= redis.key +TLS_CACERT ?= ca.crt +REDISEARCH_TEST_HOST ?= 127.0.0.1:6379 + +examples: get + @echo " " + @echo "Building the examples..." + $(GOBUILD) ./examples/redisearch_quickstart/. + $(GOBUILD) ./examples/redisearch_auth/. + $(GOBUILD) ./examples/redisearch_tls_client/. + ./redisearch_tls_client --tls-cert-file $(TLS_CERT) \ + --tls-key-file $(TLS_KEY) \ + --tls-ca-cert-file $(TLS_CACERT) \ + --host $(REDISEARCH_TEST_HOST) + test: get $(GOTEST) -race -covermode=atomic ./... diff --git a/examples/auth/main.go b/examples/redisearch_auth/redisearch_auth.go similarity index 92% rename from examples/auth/main.go rename to examples/redisearch_auth/redisearch_auth.go index e1b7ad1..67f4639 100644 --- a/examples/auth/main.go +++ b/examples/redisearch_auth/redisearch_auth.go @@ -1,11 +1,11 @@ package main import ( -"fmt" -redisearch "github.com/RediSearch/redisearch-go/redisearch" -"github.com/gomodule/redigo/redis" -"log" -"time" + "fmt" + "github.com/RediSearch/redisearch-go/redisearch" + "github.com/gomodule/redigo/redis" + "log" + "time" ) // exemplifies the NewClientFromPool function @@ -49,4 +49,4 @@ func main() { fmt.Println(docs[0].Id, docs[0].Properties["title"], total, err) // Output: doc1 Hello world 1 -} \ No newline at end of file +} diff --git a/examples/quickstart/main.go b/examples/redisearch_quickstart/redisearch_quickstart.go similarity index 100% rename from examples/quickstart/main.go rename to examples/redisearch_quickstart/redisearch_quickstart.go diff --git a/examples/redisearch_tls_client/redisearch_tls_client.go b/examples/redisearch_tls_client/redisearch_tls_client.go new file mode 100644 index 0000000..908cad0 --- /dev/null +++ b/examples/redisearch_tls_client/redisearch_tls_client.go @@ -0,0 +1,115 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "flag" + "fmt" + "github.com/RediSearch/redisearch-go/redisearch" + "github.com/gomodule/redigo/redis" + "io/ioutil" + "log" + "os" + "time" +) + +var ( + tlsCertFile = flag.String("tls-cert-file", "redis.crt", "A a X.509 certificate to use for authenticating the server to connected clients, masters or cluster peers. The file should be PEM formatted.") + tlsKeyFile = flag.String("tls-key-file", "redis.key", "A a X.509 privat ekey to use for authenticating the server to connected clients, masters or cluster peers. The file should be PEM formatted.") + tlsCaCertFile = flag.String("tls-ca-cert-file", "ca.crt", "A PEM encoded CA's certificate file.") + host = flag.String("host", "127.0.0.1:6379", "Redis host.") + password = flag.String("password", "", "Redis password.") +) + +func exists(filename string) (exists bool) { + exists = false + info, err := os.Stat(filename) + if os.IsNotExist(err) || info.IsDir() { + return + } + exists = true + return +} + +/* + * Example of how to establish an SSL connection from your app to the RedisAI Server + */ +func main() { + flag.Parse() + // Quickly check if the files exist + if !exists(*tlsCertFile) || !exists(*tlsKeyFile) || !exists(*tlsCaCertFile) { + fmt.Println("Some of the required files does not exist. Leaving example...") + return + } + + // Load client cert + cert, err := tls.LoadX509KeyPair(*tlsCertFile, *tlsKeyFile) + if err != nil { + log.Fatal(err) + } + + // Load CA cert + caCert, err := ioutil.ReadFile(*tlsCaCertFile) + if err != nil { + log.Fatal(err) + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + + clientTLSConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + } + + // InsecureSkipVerify controls whether a client verifies the + // server's certificate chain and host name. + // If InsecureSkipVerify is true, TLS accepts any certificate + // presented by the server and any host name in that certificate. + // In this mode, TLS is susceptible to man-in-the-middle attacks. + // This should be used only for testing. + clientTLSConfig.InsecureSkipVerify = true + + pool := &redis.Pool{Dial: func() (redis.Conn, error) { + return redis.Dial("tcp", *host, + redis.DialPassword(*password), + redis.DialTLSConfig(clientTLSConfig), + redis.DialUseTLS(true), + redis.DialTLSSkipVerify(true), + ) + }} + + c := redisearch.NewClientFromPool(pool, "search-client-1") + + // Create a schema + sc := redisearch.NewSchema(redisearch.DefaultOptions). + AddField(redisearch.NewTextField("body")). + AddField(redisearch.NewTextFieldOptions("title", redisearch.TextFieldOptions{Weight: 5.0, Sortable: true})). + AddField(redisearch.NewNumericField("date")) + + // Drop an existing index. If the index does not exist an error is returned + c.Drop() + + // Create the index with the given schema + if err := c.CreateIndex(sc); err != nil { + log.Fatal(err) + } + + // Create a document with an id and given score + doc := redisearch.NewDocument("doc1", 1.0) + doc.Set("title", "Hello world"). + Set("body", "foo bar"). + Set("date", time.Now().Unix()) + + // Index the document. The API accepts multiple documents at a time + if err := c.Index([]redisearch.Document{doc}...); err != nil { + log.Fatal(err) + } + + // Searching with limit and sorting + docs, total, err := c.Search(redisearch.NewQuery("hello world"). + Limit(0, 2). + SetReturnFields("title")) + + fmt.Println(docs[0].Id, docs[0].Properties["title"], total, err) + // Output: doc1 Hello world 1 +} diff --git a/redisearch/example_client_test.go b/redisearch/example_client_test.go index 27bc67d..5201282 100644 --- a/redisearch/example_client_test.go +++ b/redisearch/example_client_test.go @@ -1,15 +1,19 @@ package redisearch_test import ( + "crypto/tls" + "crypto/x509" "fmt" - redisearch "github.com/RediSearch/redisearch-go/redisearch" + "github.com/RediSearch/redisearch-go/redisearch" "github.com/gomodule/redigo/redis" + "io/ioutil" "log" + "os" "time" ) // exemplifies the NewClient function -func ExampleClient() { +func ExampleNewClient() { // Create a client. By default a client is schemaless // unless a schema is provided when creating the index c := redisearch.NewClient("localhost:6379", "myIndex") @@ -90,3 +94,139 @@ func ExampleNewClientFromPool() { fmt.Println(docs[0].Id, docs[0].Properties["title"], total, err) // Output: doc1 Hello world 1 } + +//Example of how to establish an SSL connection from your app to the RedisAI Server +func ExampleNewClientFromPool_ssl() { + // Consider the following helper methods that provide us with the connection details (host and password) + // and the paths for: + // tls_cert - A a X.509 certificate to use for authenticating the server to connected clients, masters or cluster peers. The file should be PEM formatted + // tls_key - A a X.509 private key to use for authenticating the server to connected clients, masters or cluster peers. The file should be PEM formatted + // tls_cacert - A PEM encoded CA's certificate file + host, password := getConnectionDetails() + tlsready, tls_cert, tls_key, tls_cacert := getTLSdetails() + + // Skip if we dont have all files to properly connect + if tlsready == false { + return + } + + // Load client cert + cert, err := tls.LoadX509KeyPair(tls_cert, tls_key) + if err != nil { + log.Fatal(err) + } + + // Load CA cert + caCert, err := ioutil.ReadFile(tls_cacert) + if err != nil { + log.Fatal(err) + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + + clientTLSConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + } + + // InsecureSkipVerify controls whether a client verifies the + // server's certificate chain and host name. + // If InsecureSkipVerify is true, TLS accepts any certificate + // presented by the server and any host name in that certificate. + // In this mode, TLS is susceptible to man-in-the-middle attacks. + // This should be used only for testing. + clientTLSConfig.InsecureSkipVerify = true + + pool := &redis.Pool{Dial: func() (redis.Conn, error) { + return redis.Dial("tcp", host, + redis.DialPassword(password), + redis.DialTLSConfig(clientTLSConfig), + redis.DialUseTLS(true), + redis.DialTLSSkipVerify(true), + ) + }} + + c := redisearch.NewClientFromPool(pool, "search-client-1") + + // Create a schema + sc := redisearch.NewSchema(redisearch.DefaultOptions). + AddField(redisearch.NewTextField("body")). + AddField(redisearch.NewTextFieldOptions("title", redisearch.TextFieldOptions{Weight: 5.0, Sortable: true})). + AddField(redisearch.NewNumericField("date")) + + // Drop an existing index. If the index does not exist an error is returned + c.Drop() + + // Create the index with the given schema + if err := c.CreateIndex(sc); err != nil { + log.Fatal(err) + } + + // Create a document with an id and given score + doc := redisearch.NewDocument("doc1", 1.0) + doc.Set("title", "Hello world"). + Set("body", "foo bar"). + Set("date", time.Now().Unix()) + + // Index the document. The API accepts multiple documents at a time + if err := c.Index([]redisearch.Document{doc}...); err != nil { + log.Fatal(err) + } + + // Searching with limit and sorting + docs, total, err := c.Search(redisearch.NewQuery("hello world"). + Limit(0, 2). + SetReturnFields("title")) + + fmt.Println(docs[0].Id, docs[0].Properties["title"], total, err) +} + +func getConnectionDetails() (host string, password string) { + value, exists := os.LookupEnv("REDISEARCH_TEST_HOST") + host = "localhost:6379" + password = "" + valuePassword, existsPassword := os.LookupEnv("REDISEARCH_TEST_PASSWORD") + if exists && value != "" { + host = value + } + if existsPassword && valuePassword != "" { + password = valuePassword + } + return +} + +func getTLSdetails() (tlsready bool, tls_cert string, tls_key string, tls_cacert string) { + tlsready = false + value, exists := os.LookupEnv("TLS_CERT") + if exists && value != "" { + info, err := os.Stat(value) + if os.IsNotExist(err) || info.IsDir() { + return + } + tls_cert = value + } else { + return + } + value, exists = os.LookupEnv("TLS_KEY") + if exists && value != "" { + info, err := os.Stat(value) + if os.IsNotExist(err) || info.IsDir() { + return + } + tls_key = value + } else { + return + } + value, exists = os.LookupEnv("TLS_CACERT") + if exists && value != "" { + info, err := os.Stat(value) + if os.IsNotExist(err) || info.IsDir() { + return + } + tls_cacert = value + } else { + return + } + tlsready = true + return +} diff --git a/redisearch/package.go b/redisearch/package.go deleted file mode 100644 index 96efcf9..0000000 --- a/redisearch/package.go +++ /dev/null @@ -1,53 +0,0 @@ -// Package redisearch provides a Go client for the RediSearch search engine. -// -// For the full documentation of RediSearch, see [http://redisearch.io](http://redisearch.io) -// -// Example Usage -// -//```go -// import ( -// "github.com/RediSearch/redisearch-go/redisearch" -// "log" -// "fmt" -// ) -// -// func ExampleClient() { -// // Create a client. By default a client is schemaless -// // unless a schema is provided when creating the index -// c := createClient("myIndex") -// -// // Create a schema -// sc := redisearch.NewSchema(redisearch.DefaultOptions). -// AddField(redisearch.NewTextField("body")). -// AddField(redisearch.NewTextFieldOptions("title", redisearch.TextFieldOptions{Weight: 5.0, Sortable: true})). -// AddField(redisearch.NewNumericField("date")) -// -// // Drop an existing index. If the index does not exist an error is returned -// c.Drop() -// -// // Create the index with the given schema -// if err := c.CreateIndex(sc); err != nil { -// log.Fatal(err) -// } -// -// // Create a document with an id and given score -// doc := redisearch.NewDocument("doc1", 1.0) -// doc.Set("title", "Hello world"). -// Set("body", "foo bar"). -// Set("date", time.Now().Unix()) -// -// // Index the document. The API accepts multiple documents at a time -// if err := c.IndexOptions(redisearch.DefaultIndexingOptions, doc); err != nil { -// log.Fatal(err) -// } -// -// // Searching with limit and sorting -// docs, total, err := c.Search(redisearch.NewQuery("hello world"). -// Limit(0, 2). -// SetReturnFields("title")) -// -// fmt.Println(docs[0].Id, docs[0].Properties["title"], total, err) -// // Output: doc1 Hello world 1 -// } -//``` -package redisearch diff --git a/redisearch/redisearch_test.go b/redisearch/redisearch_test.go index 23bd406..760d00b 100644 --- a/redisearch/redisearch_test.go +++ b/redisearch/redisearch_test.go @@ -468,45 +468,3 @@ func TestFilter(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 0, total) } - -func ExampleClient() { - - // Create a client. By default a client is schemaless - // unless a schema is provided when creating the index - c := createClient("myIndex") - - // Create a schema - sc := NewSchema(DefaultOptions). - AddField(NewTextField("body")). - AddField(NewTextFieldOptions("title", TextFieldOptions{Weight: 5.0, Sortable: true})). - AddField(NewNumericField("date")). - AddField(NewGeoFieldOptions("location", GeoFieldOptions{})) - - // Drop an existing index. If the index does not exist an error is returned - c.Drop() - - // Create the index with the given schema - if err := c.CreateIndex(sc); err != nil { - log.Fatal(err) - } - - // Create a document with an id and given score - doc := NewDocument("doc1", 1.0) - doc.Set("title", "Hello world"). - Set("body", "foo bar"). - Set("date", time.Now().Unix()). - Set("location", "13.361389,38.115556") - - // Index the document. The API accepts multiple documents at a time - if err := c.IndexOptions(DefaultIndexingOptions, doc); err != nil { - log.Fatal(err) - } - - // Searching with limit and sorting - docs, total, err := c.Search(NewQuery("hello world"). - Limit(0, 2). - SetReturnFields("title")) - - fmt.Println(docs[0].Id, docs[0].Properties["title"], total, err) - // Output: doc1 Hello world 1 -}