diff --git a/bootstrap/api/endpoint_test.go b/bootstrap/api/endpoint_test.go index 0129494eb0..8d6f568c0d 100644 --- a/bootstrap/api/endpoint_test.go +++ b/bootstrap/api/endpoint_test.go @@ -29,6 +29,7 @@ import ( "github.com/absmach/magistrala/pkg/errors" mgsdk "github.com/absmach/magistrala/pkg/sdk/go" sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" + "github.com/absmach/magistrala/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -180,8 +181,8 @@ func newService() (bootstrap.Service, *authmocks.AuthClient, *sdkmocks.SDK) { things := mocks.NewConfigsRepository() auth := new(authmocks.AuthClient) sdk := new(sdkmocks.SDK) - - return bootstrap.New(auth, things, sdk, encKey), auth, sdk + idp := uuid.NewMock() + return bootstrap.New(auth, things, sdk, encKey, idp), auth, sdk } func newBootstrapServer(svc bootstrap.Service) *httptest.Server { diff --git a/bootstrap/api/transport.go b/bootstrap/api/transport.go index ff3eee6a65..740275c8f3 100644 --- a/bootstrap/api/transport.go +++ b/bootstrap/api/transport.go @@ -23,11 +23,12 @@ import ( ) const ( - contentType = "application/json" - offsetKey = "offset" - limitKey = "limit" - defOffset = 0 - defLimit = 10 + contentType = "application/json" + byteContentType = "application/octet-stream" + offsetKey = "offset" + limitKey = "limit" + defOffset = 0 + defLimit = 10 ) var ( @@ -259,7 +260,7 @@ func encodeResponse(_ context.Context, w http.ResponseWriter, response interface } func encodeSecureRes(_ context.Context, w http.ResponseWriter, response interface{}) error { - w.Header().Set("Content-Type", contentType) + w.Header().Set("Content-Type", byteContentType) w.WriteHeader(http.StatusOK) if b, ok := response.([]byte); ok { if _, err := w.Write(b); err != nil { diff --git a/bootstrap/events/producer/streams_test.go b/bootstrap/events/producer/streams_test.go index ea2abc0670..9f7f058b2f 100644 --- a/bootstrap/events/producer/streams_test.go +++ b/bootstrap/events/producer/streams_test.go @@ -23,6 +23,7 @@ import ( "github.com/absmach/magistrala/pkg/events/store" mgsdk "github.com/absmach/magistrala/pkg/sdk/go" sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" + "github.com/absmach/magistrala/pkg/uuid" "github.com/go-redis/redis/v8" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -81,8 +82,8 @@ func newService(t *testing.T, url string) (bootstrap.Service, *authmocks.AuthCli things := mocks.NewConfigsRepository() auth := new(authmocks.AuthClient) sdk := new(sdkmocks.SDK) - - svc := bootstrap.New(auth, things, sdk, encKey) + idp := uuid.NewMock() + svc := bootstrap.New(auth, things, sdk, encKey, idp) publisher, err := store.NewPublisher(context.Background(), url, streamID) require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) svc = producer.NewEventStoreMiddleware(svc, publisher) diff --git a/bootstrap/service.go b/bootstrap/service.go index d0d0399a2c..5730054ccf 100644 --- a/bootstrap/service.go +++ b/bootstrap/service.go @@ -104,19 +104,21 @@ type ConfigReader interface { } type bootstrapService struct { - auth magistrala.AuthServiceClient - configs ConfigRepository - sdk mgsdk.SDK - encKey []byte + auth magistrala.AuthServiceClient + configs ConfigRepository + sdk mgsdk.SDK + encKey []byte + idProvider magistrala.IDProvider } // New returns new Bootstrap service. -func New(auth magistrala.AuthServiceClient, configs ConfigRepository, sdk mgsdk.SDK, encKey []byte) Service { +func New(auth magistrala.AuthServiceClient, configs ConfigRepository, sdk mgsdk.SDK, encKey []byte, idp magistrala.IDProvider) Service { return &bootstrapService{ - configs: configs, - sdk: sdk, - auth: auth, - encKey: encKey, + configs: configs, + sdk: sdk, + auth: auth, + encKey: encKey, + idProvider: idp, } } @@ -152,8 +154,10 @@ func (bs bootstrapService) Add(ctx context.Context, token string, cfg Config) (C saved, err := bs.configs.Save(ctx, cfg, toConnect) if err != nil { + // If id is empty, then a new thing has been created function - bs.thing(id, token) + // So, on bootstrap config save error , delete the newly created thing. if id == "" { - if _, errT := bs.sdk.DisableThing(cfg.ThingID, token); errT != nil { + if errT := bs.sdk.DeleteThing(cfg.ThingID, token); errT != nil { err = errors.Wrap(err, errT) } } @@ -381,29 +385,24 @@ func (bs bootstrapService) identify(ctx context.Context, token string) (string, // Method thing retrieves Magistrala Thing creating one if an empty ID is passed. func (bs bootstrapService) thing(id, token string) (mgsdk.Thing, error) { - var thing mgsdk.Thing - var err error - var sdkErr errors.SDKError - - thing.ID = id + // If Thing ID is not provided, then create new thing. if id == "" { - thing, sdkErr = bs.sdk.CreateThing(mgsdk.Thing{}, token) + id, err := bs.idProvider.ID() if err != nil { + return mgsdk.Thing{}, errors.Wrap(errCreateThing, err) + } + thing, sdkErr := bs.sdk.CreateThing(mgsdk.Thing{ID: id, Name: "Bootstrapped Thing " + id}, token) + if sdkErr != nil { return mgsdk.Thing{}, errors.Wrap(errCreateThing, errors.New(sdkErr.Err().Msg())) } + return thing, nil } - thing, sdkErr = bs.sdk.Thing(thing.ID, token) + // If Thing ID is provided, then retrieve thing + thing, sdkErr := bs.sdk.Thing(id, token) if sdkErr != nil { - err = errors.New(sdkErr.Error()) - if id != "" { - if _, sdkErr2 := bs.sdk.DisableThing(thing.ID, token); sdkErr2 != nil { - err = errors.Wrap(errors.New(sdkErr.Msg()), errors.New(sdkErr2.Msg())) - } - } - return mgsdk.Thing{}, errors.Wrap(ErrThings, err) + return mgsdk.Thing{}, errors.Wrap(ErrThings, sdkErr) } - return thing, nil } diff --git a/bootstrap/service_test.go b/bootstrap/service_test.go index fb6f65c010..6848b32100 100644 --- a/bootstrap/service_test.go +++ b/bootstrap/service_test.go @@ -23,6 +23,7 @@ import ( svcerr "github.com/absmach/magistrala/pkg/errors/service" mgsdk "github.com/absmach/magistrala/pkg/sdk/go" sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks" + "github.com/absmach/magistrala/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -60,8 +61,9 @@ func newService() (bootstrap.Service, *authmocks.AuthClient, *sdkmocks.SDK) { things := mocks.NewConfigsRepository() auth := new(authmocks.AuthClient) sdk := new(sdkmocks.SDK) + idp := uuid.NewMock() - return bootstrap.New(auth, things, sdk, encKey), auth, sdk + return bootstrap.New(auth, things, sdk, encKey, idp), auth, sdk } func enc(in []byte) ([]byte, error) { diff --git a/cli/bootstrap.go b/cli/bootstrap.go index 9130fd070a..0c1d9c03d8 100644 --- a/cli/bootstrap.go +++ b/cli/bootstrap.go @@ -146,7 +146,7 @@ var cmdBootstrap = []cobra.Command{ }, }, { - Use: "bootstrap [ | secure ]", + Use: "bootstrap [ | secure ]", Short: "Bootstrap config", Long: `Returns Config to the Thing with provided external ID using external key. secure - Retrieves a configuration with given external ID and encrypted external key.`, @@ -156,7 +156,7 @@ var cmdBootstrap = []cobra.Command{ return } if args[0] == "secure" { - c, err := sdk.BootstrapSecure(args[1], args[2]) + c, err := sdk.BootstrapSecure(args[1], args[2], args[3]) if err != nil { logError(err) return diff --git a/cmd/bootstrap/main.go b/cmd/bootstrap/main.go index 2262dd4db3..78200f43cc 100644 --- a/cmd/bootstrap/main.go +++ b/cmd/bootstrap/main.go @@ -178,8 +178,9 @@ func newService(ctx context.Context, authClient magistrala.AuthServiceClient, db } sdk := mgsdk.NewSDK(config) + idp := uuid.New() - svc := bootstrap.New(authClient, repoConfig, sdk, []byte(cfg.EncKey)) + svc := bootstrap.New(authClient, repoConfig, sdk, []byte(cfg.EncKey), idp) publisher, err := store.NewPublisher(ctx, cfg.ESURL, streamID) if err != nil { diff --git a/pkg/sdk/go/bootstrap.go b/pkg/sdk/go/bootstrap.go index 3278d3a61e..5501070924 100644 --- a/pkg/sdk/go/bootstrap.go +++ b/pkg/sdk/go/bootstrap.go @@ -4,8 +4,13 @@ package sdk import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/hex" "encoding/json" "fmt" + "io" "net/http" "strings" @@ -233,18 +238,60 @@ func (sdk mgSDK) Bootstrap(externalID, externalKey string) (BootstrapConfig, err return bc, nil } -func (sdk mgSDK) BootstrapSecure(externalID, externalKey string) (BootstrapConfig, errors.SDKError) { +func (sdk mgSDK) BootstrapSecure(externalID, externalKey, cryptoKey string) (BootstrapConfig, errors.SDKError) { url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, bootstrapEndpoint, secureEndpoint, externalID) - _, body, err := sdk.processRequest(http.MethodGet, url, ThingPrefix+externalKey, nil, nil, http.StatusOK) + encExtKey, err := bootstrapEncrypt([]byte(externalKey), cryptoKey) if err != nil { - return BootstrapConfig{}, err + return BootstrapConfig{}, errors.NewSDKError(err) + } + + _, body, sdkErr := sdk.processRequest(http.MethodGet, url, ThingPrefix+encExtKey, nil, nil, http.StatusOK) + if sdkErr != nil { + return BootstrapConfig{}, sdkErr } + decBody, decErr := bootstrapDecrypt(body, cryptoKey) + if decErr != nil { + return BootstrapConfig{}, errors.NewSDKError(decErr) + } var bc BootstrapConfig - if err := json.Unmarshal(body, &bc); err != nil { + if err := json.Unmarshal(decBody, &bc); err != nil { return BootstrapConfig{}, errors.NewSDKError(err) } return bc, nil } + +func bootstrapEncrypt(in []byte, cryptoKey string) (string, error) { + block, err := aes.NewCipher([]byte(cryptoKey)) + if err != nil { + return "", err + } + ciphertext := make([]byte, aes.BlockSize+len(in)) + iv := ciphertext[:aes.BlockSize] + + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return "", err + } + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(ciphertext[aes.BlockSize:], in) + return hex.EncodeToString(ciphertext), nil +} + +func bootstrapDecrypt(in []byte, cryptoKey string) ([]byte, error) { + ciphertext := in + + block, err := aes.NewCipher([]byte(cryptoKey)) + if err != nil { + return nil, err + } + if len(ciphertext) < aes.BlockSize { + return nil, err + } + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + stream := cipher.NewCFBDecrypter(block, iv) + stream.XORKeyStream(ciphertext, ciphertext) + return ciphertext, nil +} diff --git a/pkg/sdk/go/sdk.go b/pkg/sdk/go/sdk.go index 98e3e6aab5..1bb8ff3033 100644 --- a/pkg/sdk/go/sdk.go +++ b/pkg/sdk/go/sdk.go @@ -916,9 +916,9 @@ type SDK interface { // BootstrapSecure retrieves a configuration with given external ID and encrypted external key. // // example: - // bootstrap, _ := sdk.BootstrapSecure("externalID", "externalKey") + // bootstrap, _ := sdk.BootstrapSecure("externalID", "externalKey", "cryptoKey") // fmt.Println(bootstrap) - BootstrapSecure(externalID, externalKey string) (BootstrapConfig, errors.SDKError) + BootstrapSecure(externalID, externalKey, cryptoKey string) (BootstrapConfig, errors.SDKError) // Bootstraps retrieves a list of managed configs. // diff --git a/pkg/sdk/mocks/sdk.go b/pkg/sdk/mocks/sdk.go index e8eb18f56b..eadcb80409 100644 --- a/pkg/sdk/mocks/sdk.go +++ b/pkg/sdk/mocks/sdk.go @@ -176,9 +176,9 @@ func (_m *SDK) Bootstrap(externalID string, externalKey string) (sdk.BootstrapCo return r0, r1 } -// BootstrapSecure provides a mock function with given fields: externalID, externalKey -func (_m *SDK) BootstrapSecure(externalID string, externalKey string) (sdk.BootstrapConfig, errors.SDKError) { - ret := _m.Called(externalID, externalKey) +// BootstrapSecure provides a mock function with given fields: externalID, externalKey, cryptoKey +func (_m *SDK) BootstrapSecure(externalID string, externalKey string, cryptoKey string) (sdk.BootstrapConfig, errors.SDKError) { + ret := _m.Called(externalID, externalKey, cryptoKey) if len(ret) == 0 { panic("no return value specified for BootstrapSecure") @@ -186,17 +186,17 @@ func (_m *SDK) BootstrapSecure(externalID string, externalKey string) (sdk.Boots var r0 sdk.BootstrapConfig var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(string, string) (sdk.BootstrapConfig, errors.SDKError)); ok { - return rf(externalID, externalKey) + if rf, ok := ret.Get(0).(func(string, string, string) (sdk.BootstrapConfig, errors.SDKError)); ok { + return rf(externalID, externalKey, cryptoKey) } - if rf, ok := ret.Get(0).(func(string, string) sdk.BootstrapConfig); ok { - r0 = rf(externalID, externalKey) + if rf, ok := ret.Get(0).(func(string, string, string) sdk.BootstrapConfig); ok { + r0 = rf(externalID, externalKey, cryptoKey) } else { r0 = ret.Get(0).(sdk.BootstrapConfig) } - if rf, ok := ret.Get(1).(func(string, string) errors.SDKError); ok { - r1 = rf(externalID, externalKey) + if rf, ok := ret.Get(1).(func(string, string, string) errors.SDKError); ok { + r1 = rf(externalID, externalKey, cryptoKey) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError)