Skip to content

Commit 8dd2d23

Browse files
committed
Issue 47 - Enabled SSL authentication and resolved comments
Signed-off-by: Anukriti Jain <[email protected]>
1 parent d9f84de commit 8dd2d23

File tree

5 files changed

+114
-109
lines changed

5 files changed

+114
-109
lines changed

core/storage/couchStorage.go

Lines changed: 57 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
package storage
22

33
import (
4-
_ "github.com/go-kivik/couchdb/v3" // The CouchDB Driver
4+
"github.com/go-kivik/couchdb/v3" // The CouchDB Driver
5+
"github.com/go-kivik/couchdb/v3/chttp"
56
kivik "github.com/go-kivik/kivik/v3"
67
"strings"
78

9+
"bytes"
810
"context"
9-
//"crypto/tls"
10-
//"crypto/x509"
11+
"crypto/tls"
12+
"crypto/x509"
1113
"fmt"
1214
"io"
1315
"io/ioutil"
14-
//"net/http"
15-
//"os"
16-
"bytes"
16+
"net/http"
17+
"os"
1718
"time"
1819

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

99-
store.dsn = createDSN(store.loginInfo["ipAddress"], store.loginInfo["Username"], store.loginInfo["Password"])
100+
store.dsn = createDSN(store.loginInfo["ipAddress"])
100101
var client *kivik.Client
101102
var err error
102103

103-
// if common.Configuration.CouchUseSSL {
104-
// tlsConfig := &tls.Config{}
105-
// if common.Configuration.CouchCACertificate != "" {
106-
// var caFile string
107-
// if strings.HasPrefix(common.Configuration.CouchCACertificate, "/") {
108-
// caFile = common.Configuration.CouchCACertificate
109-
// } else {
110-
// caFile = common.Configuration.PersistenceRootPath + common.Configuration.CouchCACertificate
111-
// }
112-
// serverCaCert, err := ioutil.ReadFile(caFile)
113-
// if err != nil {
114-
// if _, ok := err.(*os.PathError); ok {
115-
// serverCaCert = []byte(common.Configuration.CouchCACertificate)
116-
// err = nil
117-
// } else {
118-
// message := fmt.Sprintf("Failed to find couch SSL CA file. Error: %s.", err)
119-
// return &Error{message}
120-
// }
121-
// }
122-
123-
// caCertPool := x509.NewCertPool()
124-
// caCertPool.AppendCertsFromPEM(serverCaCert)
125-
// tlsConfig.RootCAs = caCertPool
126-
// }
127-
128-
// // Please avoid using this if possible! Makes using TLS pointless
129-
// if common.Configuration.CouchAllowInvalidCertificates {
130-
// tlsConfig.InsecureSkipVerify = true
131-
// }
132-
133-
// setXport := couchdb.SetTransport(&http.Transport{TLSClientConfig: tlsConfig})
134-
// client, err = kivik.New("couch", store.dsn)
135-
// if err != nil {
136-
// message := fmt.Sprintf("Failed to connect. Error: %s.", err)
137-
// return &Error{message}
138-
// }
139-
140-
// err = client.Authenticate(context.TODO(), setXport)
141-
// if err != nil {
142-
// message := fmt.Sprintf("Authentication Failed. Error: %s.", err)
143-
// return &Error{message}
144-
// }
145-
//}
146-
147-
// basicAuth := couchdb.BasicAuth(store.loginInfo["Username"], store.loginInfo["Password"])
148-
// err = client.Authenticate(context.TODO(), basicAuth)
149-
// if err != nil {
150-
// return err
151-
// }
152-
153104
client, err = kivik.New("couch", store.dsn)
154105
if client == nil || err != nil {
155106
message := fmt.Sprintf("Failed to connect to couch. Error: %s.", err)
156107
return &Error{message}
157108
}
158109

110+
if common.Configuration.CouchUseSSL {
111+
tlsConfig := &tls.Config{}
112+
if common.Configuration.CouchCACertificate != "" {
113+
var caFile string
114+
if strings.HasPrefix(common.Configuration.CouchCACertificate, "/") {
115+
caFile = common.Configuration.CouchCACertificate
116+
} else {
117+
caFile = common.Configuration.PersistenceRootPath + common.Configuration.CouchCACertificate
118+
}
119+
serverCaCert, err := ioutil.ReadFile(caFile)
120+
if err != nil {
121+
if _, ok := err.(*os.PathError); ok {
122+
serverCaCert = []byte(common.Configuration.CouchCACertificate)
123+
err = nil
124+
} else {
125+
message := fmt.Sprintf("Failed to find Couch SSL CA file. Error: %s.", err)
126+
return &Error{message}
127+
}
128+
}
129+
130+
caCertPool := x509.NewCertPool()
131+
caCertPool.AppendCertsFromPEM(serverCaCert)
132+
tlsConfig.RootCAs = caCertPool
133+
}
134+
135+
// Please avoid using this if possible! Makes using TLS pointless
136+
if common.Configuration.CouchAllowInvalidCertificates {
137+
tlsConfig.InsecureSkipVerify = true
138+
}
139+
140+
setXport := couchdb.SetTransport(&http.Transport{TLSClientConfig: tlsConfig})
141+
err = client.Authenticate(context.TODO(), setXport)
142+
if err != nil {
143+
message := fmt.Sprintf("Authentication Failed. Error: %s.", err)
144+
return &Error{message}
145+
}
146+
}
147+
148+
err = client.Authenticate(context.TODO(), &chttp.BasicAuth{Username: store.loginInfo["Username"], Password: store.loginInfo["Password"]})
149+
if err != nil {
150+
return err
151+
}
152+
159153
available, err := client.Ping(context.TODO())
160154
if !available || err != nil {
161155
return err
@@ -271,6 +265,9 @@ func (store *CouchStorage) StoreObject(metaData common.MetaData, data []byte, st
271265
RemainingConsumers: metaData.ExpectedConsumers,
272266
RemainingReceivers: metaData.ExpectedConsumers, Destinations: dests}
273267

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

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

296+
// Cases where attachment needs to be removed are handled implicitly
297+
// by not adding the existing attachment to newObject
298298
if err := store.upsertObject(id, newObject); err != nil {
299299
return nil, &Error{fmt.Sprintf("Failed to store an object. Error: %s.", err)}
300300
}

core/storage/couchStorageHelpers.go

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ import (
1818
"github.com/open-horizon/edge-utilities/logger/trace"
1919
)
2020

21+
// Note:
22+
// To update/delete an object in CouchDB, the first step is always to GET() the object
23+
// This is because the current 'rev' i.e 'revision' is needed to perform any modifications
24+
// Due to this - there are no direct methods to delete/update objects based on queries
25+
2126
func (store *CouchStorage) checkObjects() {
2227
if !store.connected {
2328
return
@@ -62,7 +67,7 @@ func (store *CouchStorage) checkObjects() {
6267
query = map[string]interface{}{"selector": map[string]interface{}{"_id": id, "last-update": object.LastUpdate}}
6368
}
6469

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

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

95101
db := store.client.DB(context.TODO(), store.loginInfo["dbName"])
96102
row := db.Get(context.TODO(), id)
97-
if kivik.StatusCode(row.Err) == http.StatusNotFound {
98-
return 0, notFound
103+
if row.Err != nil {
104+
if kivik.StatusCode(row.Err) == http.StatusNotFound {
105+
return 0, notFound
106+
}
107+
return 0, row.Err
99108
}
100109

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

120129
db := store.client.DB(context.TODO(), store.loginInfo["dbName"])
121130
row := db.Get(context.TODO(), id)
122-
if kivik.StatusCode(row.Err) == http.StatusNotFound {
123-
return notFound
131+
if row.Err != nil {
132+
if kivik.StatusCode(row.Err) == http.StatusNotFound {
133+
return notFound
134+
}
135+
return row.Err
124136
}
125-
126137
if _, err := db.DeleteAttachment(context.TODO(), id, row.Rev, id); err != nil {
127138
return err
128139
}
129140
return nil
130141
}
131142

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

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

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

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

220237
db := store.client.DB(context.TODO(), store.loginInfo["dbName"])
221238
row := db.Get(context.TODO(), id)
222-
if kivik.StatusCode(row.Err) == http.StatusNotFound {
223-
return notFound
239+
if row.Err != nil {
240+
if kivik.StatusCode(row.Err) == http.StatusNotFound {
241+
return notFound
242+
}
243+
return row.Err
224244
}
225245

226246
if _, err := db.Delete(context.TODO(), id, row.Rev); err != nil {
@@ -580,3 +600,19 @@ func (store *CouchStorage) retrieveObjOrDestTypeForGivenACLUserHelper(aclType st
580600
}
581601
return result, nil
582602
}
603+
604+
func createDSN(ipAddress string) string {
605+
var strBuilder strings.Builder
606+
if common.Configuration.CouchUseSSL {
607+
strBuilder.WriteString("https://")
608+
} else {
609+
strBuilder.WriteString("http://")
610+
}
611+
strBuilder.WriteString(ipAddress)
612+
if common.Configuration.CouchUseSSL {
613+
strBuilder.WriteString(":6984/")
614+
} else {
615+
strBuilder.WriteString(":5984/")
616+
}
617+
return strBuilder.String()
618+
}

core/storage/couchStorage_test.go

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ import (
55
"testing"
66
)
77

8+
func TestCouchStorageConnection(t *testing.T) {
9+
store, err := setUpStorage(common.Couch)
10+
if err != nil {
11+
t.Errorf(err.Error())
12+
return
13+
}
14+
defer store.Stop()
15+
}
16+
817
func TestCouchStorageObject(t *testing.T) {
918
testStorageObjects(common.Couch, t)
1019
}
@@ -162,33 +171,3 @@ func TestCouchStorageDestinations(t *testing.T) {
162171
func TestCouchStorageObjectData(t *testing.T) {
163172
testStorageObjectData(common.Couch, t)
164173
}
165-
166-
// func TestCouchCount(t *testing.T) {
167-
// store, err := setUpStorage(common.Couch)
168-
// if err != nil {
169-
// t.Errorf(err.Error())
170-
// return
171-
// }
172-
// defer store.Stop()
173-
174-
// tests := []struct {
175-
// dest common.Destination
176-
// }{
177-
// {common.Destination{DestOrgID: "myorg123", DestID: "1", DestType: "device", Communication: common.MQTTProtocol}},
178-
// {common.Destination{DestOrgID: "myorg123", DestID: "1", DestType: "device2", Communication: common.MQTTProtocol}},
179-
// {common.Destination{DestOrgID: "myorg123", DestID: "2", DestType: "device2", Communication: common.MQTTProtocol}},
180-
// {common.Destination{DestOrgID: "myorg2", DestID: "1", DestType: "device", Communication: common.HTTPProtocol}},
181-
// }
182-
183-
// for _, test := range tests {
184-
// if err := store.StoreDestination(test.dest); err != nil {
185-
// t.Errorf("StoreDestination failed. Error: %s\n", err.Error())
186-
// }
187-
// }
188-
189-
// if count, err := store.GetNumberOfDestinations(); err != nil {
190-
// t.Errorf("count failed. Error: %s\n", err.Error())
191-
// } else if count != uint32(len(tests)) {
192-
// t.Errorf("returned incorrect count %d \n", count)
193-
// }
194-
// }

core/storage/storage.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -551,16 +551,3 @@ func DeleteStoredData(store Storage, metaData common.MetaData) common.SyncServic
551551

552552
return store.DeleteStoredData(metaData.DestOrgID, metaData.ObjectType, metaData.ObjectID)
553553
}
554-
555-
// Only for CouchDB
556-
func createDSN(ipAddress, username, password string) string {
557-
var strBuilder strings.Builder
558-
strBuilder.WriteString("http://")
559-
strBuilder.WriteString(username)
560-
strBuilder.WriteByte(':')
561-
strBuilder.WriteString(password)
562-
strBuilder.WriteByte('@')
563-
strBuilder.WriteString(ipAddress)
564-
strBuilder.WriteByte('/')
565-
return strBuilder.String()
566-
}

core/storage/storage_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1874,9 +1874,12 @@ func setUpStorage(storageType string) (Storage, error) {
18741874
store = &Cache{Store: &MongoStorage{}}
18751875
case common.Couch:
18761876
common.Configuration.CouchDbName = "d_test_db"
1877-
common.Configuration.CouchAddress = "127.0.0.1:5984"
1877+
common.Configuration.CouchAddress = "127.0.0.1"
18781878
common.Configuration.CouchUsername = os.Getenv("COUCH_USERNAME")
18791879
common.Configuration.CouchPassword = os.Getenv("COUCH_PASSWORD")
1880+
common.Configuration.CouchUseSSL = true
1881+
common.Configuration.CouchAllowInvalidCertificates = true
1882+
common.Configuration.CouchCACertificate = os.Getenv("CouchCACertificate")
18801883
store = &Cache{Store: &CouchStorage{}}
18811884
}
18821885
if err := store.Init(); err != nil {

0 commit comments

Comments
 (0)