Skip to content

Commit

Permalink
Issue 47 - Enabled SSL authentication and resolved comments
Browse files Browse the repository at this point in the history
Signed-off-by: Anukriti Jain <[email protected]>
  • Loading branch information
Anukriti2512 committed Jan 12, 2021
1 parent d9f84de commit 8dd2d23
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 109 deletions.
114 changes: 57 additions & 57 deletions core/storage/couchStorage.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package storage

import (
_ "github.com/go-kivik/couchdb/v3" // The CouchDB Driver
"github.com/go-kivik/couchdb/v3" // The CouchDB Driver
"github.com/go-kivik/couchdb/v3/chttp"
kivik "github.com/go-kivik/kivik/v3"
"strings"

"bytes"
"context"
//"crypto/tls"
//"crypto/x509"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"io/ioutil"
//"net/http"
//"os"
"bytes"
"net/http"
"os"
"time"

"github.com/open-horizon/edge-sync-service/common"
Expand Down Expand Up @@ -96,66 +97,59 @@ func (store *CouchStorage) Init() common.SyncServiceError {
"Password": common.Configuration.CouchPassword,
}

store.dsn = createDSN(store.loginInfo["ipAddress"], store.loginInfo["Username"], store.loginInfo["Password"])
store.dsn = createDSN(store.loginInfo["ipAddress"])
var client *kivik.Client
var err error

// if common.Configuration.CouchUseSSL {
// tlsConfig := &tls.Config{}
// if common.Configuration.CouchCACertificate != "" {
// var caFile string
// if strings.HasPrefix(common.Configuration.CouchCACertificate, "/") {
// caFile = common.Configuration.CouchCACertificate
// } else {
// caFile = common.Configuration.PersistenceRootPath + common.Configuration.CouchCACertificate
// }
// serverCaCert, err := ioutil.ReadFile(caFile)
// if err != nil {
// if _, ok := err.(*os.PathError); ok {
// serverCaCert = []byte(common.Configuration.CouchCACertificate)
// err = nil
// } else {
// message := fmt.Sprintf("Failed to find couch SSL CA file. Error: %s.", err)
// return &Error{message}
// }
// }

// caCertPool := x509.NewCertPool()
// caCertPool.AppendCertsFromPEM(serverCaCert)
// tlsConfig.RootCAs = caCertPool
// }

// // Please avoid using this if possible! Makes using TLS pointless
// if common.Configuration.CouchAllowInvalidCertificates {
// tlsConfig.InsecureSkipVerify = true
// }

// setXport := couchdb.SetTransport(&http.Transport{TLSClientConfig: tlsConfig})
// client, err = kivik.New("couch", store.dsn)
// if err != nil {
// message := fmt.Sprintf("Failed to connect. Error: %s.", err)
// return &Error{message}
// }

// err = client.Authenticate(context.TODO(), setXport)
// if err != nil {
// message := fmt.Sprintf("Authentication Failed. Error: %s.", err)
// return &Error{message}
// }
//}

// basicAuth := couchdb.BasicAuth(store.loginInfo["Username"], store.loginInfo["Password"])
// err = client.Authenticate(context.TODO(), basicAuth)
// if err != nil {
// return err
// }

client, err = kivik.New("couch", store.dsn)
if client == nil || err != nil {
message := fmt.Sprintf("Failed to connect to couch. Error: %s.", err)
return &Error{message}
}

if common.Configuration.CouchUseSSL {
tlsConfig := &tls.Config{}
if common.Configuration.CouchCACertificate != "" {
var caFile string
if strings.HasPrefix(common.Configuration.CouchCACertificate, "/") {
caFile = common.Configuration.CouchCACertificate
} else {
caFile = common.Configuration.PersistenceRootPath + common.Configuration.CouchCACertificate
}
serverCaCert, err := ioutil.ReadFile(caFile)
if err != nil {
if _, ok := err.(*os.PathError); ok {
serverCaCert = []byte(common.Configuration.CouchCACertificate)
err = nil
} else {
message := fmt.Sprintf("Failed to find Couch SSL CA file. Error: %s.", err)
return &Error{message}
}
}

caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(serverCaCert)
tlsConfig.RootCAs = caCertPool
}

// Please avoid using this if possible! Makes using TLS pointless
if common.Configuration.CouchAllowInvalidCertificates {
tlsConfig.InsecureSkipVerify = true
}

setXport := couchdb.SetTransport(&http.Transport{TLSClientConfig: tlsConfig})
err = client.Authenticate(context.TODO(), setXport)
if err != nil {
message := fmt.Sprintf("Authentication Failed. Error: %s.", err)
return &Error{message}
}
}

err = client.Authenticate(context.TODO(), &chttp.BasicAuth{Username: store.loginInfo["Username"], Password: store.loginInfo["Password"]})
if err != nil {
return err
}

available, err := client.Ping(context.TODO())
if !available || err != nil {
return err
Expand Down Expand Up @@ -271,6 +265,9 @@ func (store *CouchStorage) StoreObject(metaData common.MetaData, data []byte, st
RemainingConsumers: metaData.ExpectedConsumers,
RemainingReceivers: metaData.ExpectedConsumers, Destinations: dests}

// In case of existing object, check if it had attachment and add to newObject if present
// This is done only in case of metaOnly update
// otherwise updated attachment will be added in the next block
if existingObject != nil {
newObject.Rev = existingObject.Rev
if metaData.MetaOnly && data == nil {
Expand All @@ -286,6 +283,7 @@ func (store *CouchStorage) StoreObject(metaData common.MetaData, data []byte, st
}
}

// Add attachment to newObject for NoData=false
if !metaData.NoData && data != nil {
content := ioutil.NopCloser(bytes.NewReader(data))
defer content.Close()
Expand All @@ -295,6 +293,8 @@ func (store *CouchStorage) StoreObject(metaData common.MetaData, data []byte, st
newObject.Attachments = attachments
}

// Cases where attachment needs to be removed are handled implicitly
// by not adding the existing attachment to newObject
if err := store.upsertObject(id, newObject); err != nil {
return nil, &Error{fmt.Sprintf("Failed to store an object. Error: %s.", err)}
}
Expand Down
52 changes: 44 additions & 8 deletions core/storage/couchStorageHelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import (
"github.com/open-horizon/edge-utilities/logger/trace"
)

// Note:
// To update/delete an object in CouchDB, the first step is always to GET() the object
// This is because the current 'rev' i.e 'revision' is needed to perform any modifications
// Due to this - there are no direct methods to delete/update objects based on queries

func (store *CouchStorage) checkObjects() {
if !store.connected {
return
Expand Down Expand Up @@ -62,7 +67,7 @@ func (store *CouchStorage) checkObjects() {
query = map[string]interface{}{"selector": map[string]interface{}{"_id": id, "last-update": object.LastUpdate}}
}

if err := store.deleteAllCouchObjects(query); err != nil {
if err = store.deleteAllCouchObjects(query); err != nil {
if err != notFound || object.LastUpdate.IsZero() {
log.Error("Error in CouchStorage.checkObjects: failed to remove expired objects. Error: %s\n", err)
}
Expand All @@ -81,6 +86,7 @@ func (store *CouchStorage) getOne(id string, result interface{}) common.SyncServ
db := store.client.DB(context.TODO(), store.loginInfo["dbName"])

row := db.Get(context.TODO(), id)
// Other Runtime errors are returned after the row.ScanDoc() call
if kivik.StatusCode(row.Err) == http.StatusNotFound {
return notFound
}
Expand All @@ -94,8 +100,11 @@ func (store *CouchStorage) addAttachment(id string, dataReader io.Reader) (int64

db := store.client.DB(context.TODO(), store.loginInfo["dbName"])
row := db.Get(context.TODO(), id)
if kivik.StatusCode(row.Err) == http.StatusNotFound {
return 0, notFound
if row.Err != nil {
if kivik.StatusCode(row.Err) == http.StatusNotFound {
return 0, notFound
}
return 0, row.Err
}

content := ioutil.NopCloser(dataReader)
Expand All @@ -119,16 +128,20 @@ func (store *CouchStorage) removeAttachment(id string) common.SyncServiceError {

db := store.client.DB(context.TODO(), store.loginInfo["dbName"])
row := db.Get(context.TODO(), id)
if kivik.StatusCode(row.Err) == http.StatusNotFound {
return notFound
if row.Err != nil {
if kivik.StatusCode(row.Err) == http.StatusNotFound {
return notFound
}
return row.Err
}

if _, err := db.DeleteAttachment(context.TODO(), id, row.Rev, id); err != nil {
return err
}
return nil
}

// In case of updating existing object, Rev is needs to be set in object
// This has been set in the passed object by calling function
func (store *CouchStorage) upsertObject(id string, object interface{}) common.SyncServiceError {

db := store.client.DB(context.TODO(), store.loginInfo["dbName"])
Expand All @@ -143,6 +156,10 @@ func (store *CouchStorage) getInstanceID() int64 {
return time.Now().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
}

// Storage interface has many types of objects - couchObject, couchDestinationObject, couchACLObject, etc.
// To make this a generic function for all object types, address of relevant type's slice is passed in result interface
// Reflection is needed to be able to access and modify the underlying slice i.e
// append each object to it after iterating over the rows returned
func (store *CouchStorage) findAll(query interface{}, result interface{}) common.SyncServiceError {

db := store.client.DB(context.TODO(), store.loginInfo["dbName"])
Expand Down Expand Up @@ -219,8 +236,11 @@ func (store *CouchStorage) deleteObject(id string) common.SyncServiceError {

db := store.client.DB(context.TODO(), store.loginInfo["dbName"])
row := db.Get(context.TODO(), id)
if kivik.StatusCode(row.Err) == http.StatusNotFound {
return notFound
if row.Err != nil {
if kivik.StatusCode(row.Err) == http.StatusNotFound {
return notFound
}
return row.Err
}

if _, err := db.Delete(context.TODO(), id, row.Rev); err != nil {
Expand Down Expand Up @@ -580,3 +600,19 @@ func (store *CouchStorage) retrieveObjOrDestTypeForGivenACLUserHelper(aclType st
}
return result, nil
}

func createDSN(ipAddress string) string {
var strBuilder strings.Builder
if common.Configuration.CouchUseSSL {
strBuilder.WriteString("https://")
} else {
strBuilder.WriteString("http://")
}
strBuilder.WriteString(ipAddress)
if common.Configuration.CouchUseSSL {
strBuilder.WriteString(":6984/")
} else {
strBuilder.WriteString(":5984/")
}
return strBuilder.String()
}
39 changes: 9 additions & 30 deletions core/storage/couchStorage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import (
"testing"
)

func TestCouchStorageConnection(t *testing.T) {
store, err := setUpStorage(common.Couch)
if err != nil {
t.Errorf(err.Error())
return
}
defer store.Stop()
}

func TestCouchStorageObject(t *testing.T) {
testStorageObjects(common.Couch, t)
}
Expand Down Expand Up @@ -162,33 +171,3 @@ func TestCouchStorageDestinations(t *testing.T) {
func TestCouchStorageObjectData(t *testing.T) {
testStorageObjectData(common.Couch, t)
}

// func TestCouchCount(t *testing.T) {
// store, err := setUpStorage(common.Couch)
// if err != nil {
// t.Errorf(err.Error())
// return
// }
// defer store.Stop()

// tests := []struct {
// dest common.Destination
// }{
// {common.Destination{DestOrgID: "myorg123", DestID: "1", DestType: "device", Communication: common.MQTTProtocol}},
// {common.Destination{DestOrgID: "myorg123", DestID: "1", DestType: "device2", Communication: common.MQTTProtocol}},
// {common.Destination{DestOrgID: "myorg123", DestID: "2", DestType: "device2", Communication: common.MQTTProtocol}},
// {common.Destination{DestOrgID: "myorg2", DestID: "1", DestType: "device", Communication: common.HTTPProtocol}},
// }

// for _, test := range tests {
// if err := store.StoreDestination(test.dest); err != nil {
// t.Errorf("StoreDestination failed. Error: %s\n", err.Error())
// }
// }

// if count, err := store.GetNumberOfDestinations(); err != nil {
// t.Errorf("count failed. Error: %s\n", err.Error())
// } else if count != uint32(len(tests)) {
// t.Errorf("returned incorrect count %d \n", count)
// }
// }
13 changes: 0 additions & 13 deletions core/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -551,16 +551,3 @@ func DeleteStoredData(store Storage, metaData common.MetaData) common.SyncServic

return store.DeleteStoredData(metaData.DestOrgID, metaData.ObjectType, metaData.ObjectID)
}

// Only for CouchDB
func createDSN(ipAddress, username, password string) string {
var strBuilder strings.Builder
strBuilder.WriteString("http://")
strBuilder.WriteString(username)
strBuilder.WriteByte(':')
strBuilder.WriteString(password)
strBuilder.WriteByte('@')
strBuilder.WriteString(ipAddress)
strBuilder.WriteByte('/')
return strBuilder.String()
}
5 changes: 4 additions & 1 deletion core/storage/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1874,9 +1874,12 @@ func setUpStorage(storageType string) (Storage, error) {
store = &Cache{Store: &MongoStorage{}}
case common.Couch:
common.Configuration.CouchDbName = "d_test_db"
common.Configuration.CouchAddress = "127.0.0.1:5984"
common.Configuration.CouchAddress = "127.0.0.1"
common.Configuration.CouchUsername = os.Getenv("COUCH_USERNAME")
common.Configuration.CouchPassword = os.Getenv("COUCH_PASSWORD")
common.Configuration.CouchUseSSL = true
common.Configuration.CouchAllowInvalidCertificates = true
common.Configuration.CouchCACertificate = os.Getenv("CouchCACertificate")
store = &Cache{Store: &CouchStorage{}}
}
if err := store.Init(); err != nil {
Expand Down

0 comments on commit 8dd2d23

Please sign in to comment.