Skip to content

Commit

Permalink
NOISSUE - Fix Bootstrap thing creation flow (#2083)
Browse files Browse the repository at this point in the history
Signed-off-by: Arvindh <arvindh91@gmail.com>
  • Loading branch information
arvindh123 authored Feb 19, 2024
1 parent 21c5813 commit 4c206ec
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 54 deletions.
5 changes: 3 additions & 2 deletions bootstrap/api/endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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 {
Expand Down
13 changes: 7 additions & 6 deletions bootstrap/api/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 3 additions & 2 deletions bootstrap/events/producer/streams_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
49 changes: 24 additions & 25 deletions bootstrap/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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
}

Expand Down
4 changes: 3 additions & 1 deletion bootstrap/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions cli/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ var cmdBootstrap = []cobra.Command{
},
},
{
Use: "bootstrap [<external_id> <external_key> | secure <external_id> <external_key> ]",
Use: "bootstrap [<external_id> <external_key> | secure <external_id> <external_key> <crypto_key> ]",
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.`,
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion cmd/bootstrap/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
55 changes: 51 additions & 4 deletions pkg/sdk/go/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@
package sdk

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"

Expand Down Expand Up @@ -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
}
4 changes: 2 additions & 2 deletions pkg/sdk/go/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down
18 changes: 9 additions & 9 deletions pkg/sdk/mocks/sdk.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4c206ec

Please sign in to comment.