mirror of
https://github.com/absmach/magistrala.git
synced 2026-06-22 20:00:22 +00:00
683809dc6b
Property Based Tests / api-test (push) Has been cancelled
Continuous Delivery / lint-and-build (push) Has been cancelled
Deploy GitHub Pages / swagger-ui (push) Has been cancelled
CI Pipeline / Lint Proto (push) Has been cancelled
Continuous Delivery / Build and Push Docker Images (push) Has been cancelled
CI Pipeline / lint-and-build (push) Has been cancelled
CI Pipeline / Test ${{ matrix.module }} (push) Has been cancelled
CI Pipeline / Upload Coverage (push) Has been cancelled
CI Pipeline / Detect Changes (push) Has been cancelled
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
533 lines
19 KiB
Go
533 lines
19 KiB
Go
// Copyright (c) Abstract Machines
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"encoding/hex"
|
|
|
|
"github.com/absmach/magistrala"
|
|
smqauthn "github.com/absmach/magistrala/pkg/authn"
|
|
"github.com/absmach/magistrala/pkg/errors"
|
|
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
|
|
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
|
mgsdk "github.com/absmach/magistrala/pkg/sdk"
|
|
)
|
|
|
|
var (
|
|
// ErrExternalKey indicates a non-existent bootstrap configuration for given external key.
|
|
ErrExternalKey = errors.NewAuthZError("failed to get bootstrap configuration for given external key")
|
|
|
|
// ErrExternalKeySecure indicates error in getting bootstrap configuration for given encrypted external key.
|
|
ErrExternalKeySecure = errors.NewAuthZError("failed to get bootstrap configuration for given encrypted external key")
|
|
|
|
// ErrBootstrap indicates error in getting bootstrap configuration.
|
|
ErrBootstrap = errors.New("failed to read bootstrap configuration")
|
|
|
|
// ErrAddBootstrap indicates error in adding bootstrap configuration.
|
|
ErrAddBootstrap = errors.NewServiceError("failed to add bootstrap configuration")
|
|
|
|
// ErrBootstrapStatus indicates an invalid bootstrap status.
|
|
ErrBootstrapStatus = errors.NewRequestError("invalid bootstrap status")
|
|
|
|
errRemoveBootstrap = errors.New("failed to remove bootstrap configuration")
|
|
errEnableConfig = errors.New("failed to enable bootstrap configuration")
|
|
errDisableConfig = errors.New("failed to disable bootstrap configuration")
|
|
errUpdateCert = errors.New("failed to update cert")
|
|
|
|
errCreateProfile = errors.New("failed to create profile")
|
|
errViewProfile = errors.New("failed to view profile")
|
|
errUpdateProfile = errors.New("failed to update profile")
|
|
errDeleteProfile = errors.New("failed to delete profile")
|
|
errListProfiles = errors.New("failed to list profiles")
|
|
errAssignProfile = errors.New("failed to assign profile to enrollment")
|
|
errBindResources = errors.New("failed to bind resources")
|
|
errListBindings = errors.New("failed to list bindings")
|
|
errRefreshBinding = errors.New("failed to refresh bindings")
|
|
errRenderBootstrap = errors.New("failed to render bootstrap configuration")
|
|
)
|
|
|
|
var _ Service = (*bootstrapService)(nil)
|
|
|
|
// Service specifies an API that must be fulfilled by the domain service
|
|
// implementation, and all of its decorators (e.g. logging & metrics).
|
|
type Service interface {
|
|
// Add adds new Client Config to the user identified by the provided token.
|
|
Add(ctx context.Context, session smqauthn.Session, token string, cfg Config) (Config, error)
|
|
|
|
// View returns Client Config with given ID belonging to the user identified by the given token.
|
|
View(ctx context.Context, session smqauthn.Session, id string) (Config, error)
|
|
|
|
// Update updates editable fields of the provided Config.
|
|
Update(ctx context.Context, session smqauthn.Session, cfg Config) error
|
|
|
|
// UpdateCert updates an existing Config certificate and token.
|
|
// A non-nil error is returned to indicate operation failure.
|
|
UpdateCert(ctx context.Context, session smqauthn.Session, id, clientCert, clientKey, caCert string) (Config, error)
|
|
|
|
// List returns subset of Configs with given search params that belong to the
|
|
// user identified by the given token.
|
|
List(ctx context.Context, session smqauthn.Session, filter Filter, offset, limit uint64) (ConfigsPage, error)
|
|
|
|
// Remove removes Config with specified token that belongs to the user identified by the given token.
|
|
Remove(ctx context.Context, session smqauthn.Session, id string) error
|
|
|
|
// Bootstrap returns Config to the Client with provided external ID using external key.
|
|
Bootstrap(ctx context.Context, externalKey, externalID string, secure bool) (Config, error)
|
|
|
|
// EnableConfig enables the Config so its device can successfully bootstrap.
|
|
EnableConfig(ctx context.Context, session smqauthn.Session, id string) (Config, error)
|
|
|
|
// DisableConfig disables the Config, preventing its device from bootstrapping.
|
|
DisableConfig(ctx context.Context, session smqauthn.Session, id string) (Config, error)
|
|
|
|
// CreateProfile persists a new device Profile.
|
|
CreateProfile(ctx context.Context, session smqauthn.Session, p Profile) (Profile, error)
|
|
|
|
// ViewProfile returns the Profile with the given ID.
|
|
ViewProfile(ctx context.Context, session smqauthn.Session, profileID string) (Profile, error)
|
|
|
|
// UpdateProfile updates editable fields of the given Profile and returns the updated Profile.
|
|
UpdateProfile(ctx context.Context, session smqauthn.Session, p Profile) (Profile, error)
|
|
|
|
// ListProfiles returns a page of Profiles belonging to the domain.
|
|
ListProfiles(ctx context.Context, session smqauthn.Session, offset, limit uint64, name string) (ProfilesPage, error)
|
|
|
|
// DeleteProfile removes the Profile with the given ID.
|
|
DeleteProfile(ctx context.Context, session smqauthn.Session, profileID string) error
|
|
|
|
// AssignProfile sets the ProfileID on an existing enrollment (Config).
|
|
AssignProfile(ctx context.Context, session smqauthn.Session, configID, profileID string) error
|
|
|
|
// BindResources resolves the requested bindings through their owning services,
|
|
// stores snapshots, and marks the enrollment renderable when all required slots
|
|
// are satisfied.
|
|
BindResources(ctx context.Context, session smqauthn.Session, token, configID string, bindings []BindingRequest) error
|
|
|
|
// ListBindings returns all stored binding snapshots for an enrollment.
|
|
ListBindings(ctx context.Context, session smqauthn.Session, configID string) ([]BindingSnapshot, error)
|
|
|
|
// RefreshBindings re-resolves all existing bindings for an enrollment and
|
|
// updates the stored snapshots.
|
|
RefreshBindings(ctx context.Context, session smqauthn.Session, token, configID string) error
|
|
}
|
|
|
|
// ConfigReader is used to parse Config into format which will be encoded
|
|
// as a JSON and consumed from the client side. The purpose of this interface
|
|
// is to provide convenient way to generate custom configuration response
|
|
// based on the specific Config which will be consumed by the client.
|
|
type ConfigReader interface {
|
|
ReadConfig(Config, bool) (any, error)
|
|
}
|
|
|
|
type bootstrapService struct {
|
|
configs ConfigRepository
|
|
profiles ProfileRepository
|
|
bindings BindingStore
|
|
resolver BindingResolver
|
|
renderer Renderer
|
|
hasher Hasher
|
|
sdk mgsdk.SDK
|
|
encKey []byte
|
|
idProvider magistrala.IDProvider
|
|
}
|
|
|
|
// New returns new Bootstrap service.
|
|
func New(
|
|
configs ConfigRepository,
|
|
profiles ProfileRepository,
|
|
bindings BindingStore,
|
|
resolver BindingResolver,
|
|
renderer Renderer,
|
|
sdk mgsdk.SDK,
|
|
hasher Hasher,
|
|
encKey []byte,
|
|
idp magistrala.IDProvider,
|
|
) Service {
|
|
return &bootstrapService{
|
|
configs: configs,
|
|
profiles: profiles,
|
|
bindings: bindings,
|
|
resolver: resolver,
|
|
renderer: renderer,
|
|
hasher: hasher,
|
|
sdk: sdk,
|
|
encKey: encKey,
|
|
idProvider: idp,
|
|
}
|
|
}
|
|
|
|
func (bs bootstrapService) Add(ctx context.Context, session smqauthn.Session, token string, cfg Config) (Config, error) {
|
|
id, err := bs.idProvider.ID()
|
|
if err != nil {
|
|
return Config{}, errors.Wrap(ErrAddBootstrap, err)
|
|
}
|
|
|
|
hashedKey, err := bs.hasher.Hash(cfg.ExternalKey)
|
|
if err != nil {
|
|
return Config{}, errors.Wrap(ErrAddBootstrap, err)
|
|
}
|
|
|
|
cfg.ID = id
|
|
cfg.DomainID = session.DomainID
|
|
cfg.Status = Active
|
|
cfg.ExternalKey = hashedKey
|
|
|
|
saved, err := bs.configs.Save(ctx, cfg)
|
|
if err != nil {
|
|
if errors.Contains(err, repoerr.ErrConflict) {
|
|
return Config{}, errors.Wrap(svcerr.ErrConflict, err)
|
|
}
|
|
return Config{}, errors.Wrap(ErrAddBootstrap, err)
|
|
}
|
|
|
|
cfg.ID = saved
|
|
return cfg, nil
|
|
}
|
|
|
|
func (bs bootstrapService) View(ctx context.Context, session smqauthn.Session, id string) (Config, error) {
|
|
cfg, err := bs.configs.RetrieveByID(ctx, session.DomainID, id)
|
|
if err != nil {
|
|
return Config{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func (bs bootstrapService) Update(ctx context.Context, session smqauthn.Session, cfg Config) error {
|
|
cfg.DomainID = session.DomainID
|
|
if err := bs.configs.Update(ctx, cfg); err != nil {
|
|
return errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (bs bootstrapService) UpdateCert(ctx context.Context, session smqauthn.Session, id, clientCert, clientKey, caCert string) (Config, error) {
|
|
cfg, err := bs.configs.UpdateCert(ctx, session.DomainID, id, clientCert, clientKey, caCert)
|
|
if err != nil {
|
|
return Config{}, errors.Wrap(errUpdateCert, err)
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func (bs bootstrapService) List(ctx context.Context, session smqauthn.Session, filter Filter, offset, limit uint64) (ConfigsPage, error) {
|
|
return bs.configs.RetrieveAll(ctx, session.DomainID, filter, offset, limit), nil
|
|
}
|
|
|
|
func (bs bootstrapService) Remove(ctx context.Context, session smqauthn.Session, id string) error {
|
|
if err := bs.configs.Remove(ctx, session.DomainID, id); err != nil {
|
|
return errors.Wrap(errRemoveBootstrap, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (bs bootstrapService) Bootstrap(ctx context.Context, externalKey, externalID string, secure bool) (Config, error) {
|
|
cfg, err := bs.configs.RetrieveByExternalID(ctx, externalID)
|
|
if err != nil {
|
|
return cfg, errors.Wrap(ErrBootstrap, err)
|
|
}
|
|
if secure {
|
|
dec, err := bs.dec(externalKey)
|
|
if err != nil {
|
|
return Config{}, errors.Wrap(ErrExternalKeySecure, err)
|
|
}
|
|
externalKey = dec
|
|
}
|
|
|
|
if err := bs.hasher.Compare(externalKey, cfg.ExternalKey); err != nil {
|
|
return Config{}, ErrExternalKey
|
|
}
|
|
if cfg.Status == DisabledStatus {
|
|
return Config{}, ErrBootstrap
|
|
}
|
|
|
|
cfg, err = bs.renderBootstrapConfig(ctx, cfg)
|
|
if err != nil {
|
|
return Config{}, errors.Wrap(ErrBootstrap, err)
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func (bs bootstrapService) renderBootstrapConfig(ctx context.Context, cfg Config) (Config, error) {
|
|
if cfg.ProfileID == "" {
|
|
return cfg, nil
|
|
}
|
|
if bs.profiles == nil || bs.bindings == nil || bs.renderer == nil {
|
|
return Config{}, errors.Wrap(errRenderBootstrap, errors.New("profile rendering support not configured"))
|
|
}
|
|
|
|
profile, err := bs.profiles.RetrieveByID(ctx, cfg.DomainID, cfg.ProfileID)
|
|
if err != nil {
|
|
return Config{}, errors.Wrap(errRenderBootstrap, err)
|
|
}
|
|
|
|
bindings, err := bs.bindings.Retrieve(ctx, cfg.ID)
|
|
if err != nil {
|
|
return Config{}, errors.Wrap(errRenderBootstrap, err)
|
|
}
|
|
if err := validateRequiredBindings(profile, bindings); err != nil {
|
|
return Config{}, errors.Wrap(errRenderBootstrap, err)
|
|
}
|
|
bindings, err = bs.decryptSecretSnapshots(bindings)
|
|
if err != nil {
|
|
return Config{}, errors.Wrap(errRenderBootstrap, err)
|
|
}
|
|
|
|
rendered, err := bs.renderer.Render(profile, cfg, bindings)
|
|
if err != nil {
|
|
return Config{}, errors.Wrap(errRenderBootstrap, err)
|
|
}
|
|
|
|
cfg.Content = string(rendered)
|
|
return cfg, nil
|
|
}
|
|
|
|
func (bs bootstrapService) EnableConfig(ctx context.Context, session smqauthn.Session, id string) (Config, error) {
|
|
cfg, err := bs.changeConfigStatus(ctx, session.DomainID, id, EnabledStatus)
|
|
if err != nil {
|
|
return Config{}, errors.Wrap(errEnableConfig, err)
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func (bs bootstrapService) DisableConfig(ctx context.Context, session smqauthn.Session, id string) (Config, error) {
|
|
cfg, err := bs.changeConfigStatus(ctx, session.DomainID, id, DisabledStatus)
|
|
if err != nil {
|
|
return Config{}, errors.Wrap(errDisableConfig, err)
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func (bs bootstrapService) changeConfigStatus(ctx context.Context, domainID, id string, status Status) (Config, error) {
|
|
cfg, err := bs.configs.RetrieveByID(ctx, domainID, id)
|
|
if err != nil {
|
|
return Config{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
|
}
|
|
if cfg.Status == status {
|
|
return cfg, nil
|
|
}
|
|
if err := bs.configs.ChangeStatus(ctx, domainID, id, status); err != nil {
|
|
return Config{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
|
}
|
|
cfg.Status = status
|
|
return cfg, nil
|
|
}
|
|
|
|
// --- Profile management ---
|
|
|
|
func (bs bootstrapService) CreateProfile(ctx context.Context, session smqauthn.Session, p Profile) (Profile, error) {
|
|
if bs.profiles == nil {
|
|
return Profile{}, errors.Wrap(errCreateProfile, errors.New("profile repository not configured"))
|
|
}
|
|
id, err := bs.idProvider.ID()
|
|
if err != nil {
|
|
return Profile{}, errors.Wrap(errCreateProfile, err)
|
|
}
|
|
p.ID = id
|
|
p.DomainID = session.DomainID
|
|
if p.ContentFormat == "" {
|
|
p.ContentFormat = ContentFormatJSON
|
|
}
|
|
p.Version = 1
|
|
if err := validateProfileBindingSlots(p); err != nil {
|
|
return Profile{}, errors.Wrap(errCreateProfile, err)
|
|
}
|
|
if err := validateProfileTemplate(p); err != nil {
|
|
return Profile{}, errors.Wrap(errCreateProfile, err)
|
|
}
|
|
saved, err := bs.profiles.Save(ctx, p)
|
|
if err != nil {
|
|
return Profile{}, errors.Wrap(errCreateProfile, err)
|
|
}
|
|
return saved, nil
|
|
}
|
|
|
|
func (bs bootstrapService) ViewProfile(ctx context.Context, session smqauthn.Session, profileID string) (Profile, error) {
|
|
if bs.profiles == nil {
|
|
return Profile{}, errors.Wrap(errViewProfile, errors.New("profile repository not configured"))
|
|
}
|
|
p, err := bs.profiles.RetrieveByID(ctx, session.DomainID, profileID)
|
|
if err != nil {
|
|
return Profile{}, errors.Wrap(errViewProfile, err)
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (bs bootstrapService) UpdateProfile(ctx context.Context, session smqauthn.Session, p Profile) (Profile, error) {
|
|
if bs.profiles == nil {
|
|
return Profile{}, errors.Wrap(errUpdateProfile, errors.New("profile repository not configured"))
|
|
}
|
|
p.DomainID = session.DomainID
|
|
if err := validateProfileBindingSlots(p); err != nil {
|
|
return Profile{}, errors.Wrap(errUpdateProfile, err)
|
|
}
|
|
if err := validateProfileTemplate(p); err != nil {
|
|
return Profile{}, errors.Wrap(errUpdateProfile, err)
|
|
}
|
|
updated, err := bs.profiles.Update(ctx, p)
|
|
if err != nil {
|
|
return Profile{}, errors.Wrap(errUpdateProfile, err)
|
|
}
|
|
return updated, nil
|
|
}
|
|
|
|
func (bs bootstrapService) ListProfiles(ctx context.Context, session smqauthn.Session, offset, limit uint64, name string) (ProfilesPage, error) {
|
|
if bs.profiles == nil {
|
|
return ProfilesPage{}, errors.Wrap(errListProfiles, errors.New("profile repository not configured"))
|
|
}
|
|
page, err := bs.profiles.RetrieveAll(ctx, session.DomainID, offset, limit, name)
|
|
if err != nil {
|
|
return ProfilesPage{}, errors.Wrap(errListProfiles, err)
|
|
}
|
|
return page, nil
|
|
}
|
|
|
|
func (bs bootstrapService) DeleteProfile(ctx context.Context, session smqauthn.Session, profileID string) error {
|
|
if bs.profiles == nil {
|
|
return errors.Wrap(errDeleteProfile, errors.New("profile repository not configured"))
|
|
}
|
|
if err := bs.profiles.Delete(ctx, session.DomainID, profileID); err != nil {
|
|
return errors.Wrap(errDeleteProfile, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// --- Enrollment-profile assignment ---
|
|
|
|
func (bs bootstrapService) AssignProfile(ctx context.Context, session smqauthn.Session, configID, profileID string) error {
|
|
if bs.profiles == nil {
|
|
return errors.Wrap(errAssignProfile, errors.New("profile repository not configured"))
|
|
}
|
|
// Validate profile exists in domain.
|
|
if _, err := bs.profiles.RetrieveByID(ctx, session.DomainID, profileID); err != nil {
|
|
return errors.Wrap(errAssignProfile, err)
|
|
}
|
|
if err := bs.configs.AssignProfile(ctx, session.DomainID, configID, profileID); err != nil {
|
|
return errors.Wrap(errAssignProfile, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// --- Binding management ---
|
|
|
|
func (bs bootstrapService) BindResources(ctx context.Context, session smqauthn.Session, token, configID string, requested []BindingRequest) error {
|
|
if bs.profiles == nil || bs.bindings == nil || bs.resolver == nil {
|
|
return errors.Wrap(errBindResources, errors.New("binding support not configured"))
|
|
}
|
|
cfg, err := bs.configs.RetrieveByID(ctx, session.DomainID, configID)
|
|
if err != nil {
|
|
return errors.Wrap(errBindResources, err)
|
|
}
|
|
profile, err := bs.profiles.RetrieveByID(ctx, session.DomainID, cfg.ProfileID)
|
|
if err != nil {
|
|
return errors.Wrap(errBindResources, err)
|
|
}
|
|
if err := validateRequestedBindings(profile, requested); err != nil {
|
|
return errors.Wrap(errBindResources, err)
|
|
}
|
|
snapshots, err := bs.resolver.Resolve(ctx, ResolveRequest{
|
|
Enrollment: cfg,
|
|
Token: token,
|
|
Requested: requested,
|
|
})
|
|
if err != nil {
|
|
return errors.Wrap(errBindResources, err)
|
|
}
|
|
existing, err := bs.bindings.Retrieve(ctx, configID)
|
|
if err != nil {
|
|
return errors.Wrap(errBindResources, err)
|
|
}
|
|
if err := validateRequiredBindings(profile, mergeBindingSnapshots(existing, snapshots)); err != nil {
|
|
return errors.Wrap(errBindResources, err)
|
|
}
|
|
snapshots, err = bs.encryptSecretSnapshots(snapshots)
|
|
if err != nil {
|
|
return errors.Wrap(errBindResources, err)
|
|
}
|
|
if err := bs.bindings.Save(ctx, configID, snapshots); err != nil {
|
|
return errors.Wrap(errBindResources, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (bs bootstrapService) ListBindings(ctx context.Context, session smqauthn.Session, configID string) ([]BindingSnapshot, error) {
|
|
if bs.bindings == nil {
|
|
return nil, errors.Wrap(errListBindings, errors.New("binding support not configured"))
|
|
}
|
|
if _, err := bs.configs.RetrieveByID(ctx, session.DomainID, configID); err != nil {
|
|
return nil, errors.Wrap(errListBindings, err)
|
|
}
|
|
snapshots, err := bs.bindings.Retrieve(ctx, configID)
|
|
if err != nil {
|
|
return nil, errors.Wrap(errListBindings, err)
|
|
}
|
|
return hideSecretSnapshots(snapshots), nil
|
|
}
|
|
|
|
func (bs bootstrapService) RefreshBindings(ctx context.Context, session smqauthn.Session, token, configID string) error {
|
|
if bs.profiles == nil || bs.bindings == nil || bs.resolver == nil {
|
|
return errors.Wrap(errRefreshBinding, errors.New("binding support not configured"))
|
|
}
|
|
cfg, err := bs.configs.RetrieveByID(ctx, session.DomainID, configID)
|
|
if err != nil {
|
|
return errors.Wrap(errRefreshBinding, err)
|
|
}
|
|
profile, err := bs.profiles.RetrieveByID(ctx, session.DomainID, cfg.ProfileID)
|
|
if err != nil {
|
|
return errors.Wrap(errRefreshBinding, err)
|
|
}
|
|
existing, err := bs.bindings.Retrieve(ctx, configID)
|
|
if err != nil {
|
|
return errors.Wrap(errRefreshBinding, err)
|
|
}
|
|
if len(existing) == 0 {
|
|
return nil
|
|
}
|
|
// Re-resolve every existing binding to refresh its snapshot.
|
|
requested := make([]BindingRequest, len(existing))
|
|
for i, b := range existing {
|
|
requested[i] = BindingRequest{Slot: b.Slot, Type: b.Type, ResourceID: b.ResourceID}
|
|
}
|
|
if err := validateRequestedBindings(profile, requested); err != nil {
|
|
return errors.Wrap(errRefreshBinding, err)
|
|
}
|
|
refreshed, err := bs.resolver.Resolve(ctx, ResolveRequest{
|
|
Enrollment: cfg,
|
|
Token: token,
|
|
Requested: requested,
|
|
})
|
|
if err != nil {
|
|
return errors.Wrap(errRefreshBinding, err)
|
|
}
|
|
if err := validateRequiredBindings(profile, refreshed); err != nil {
|
|
return errors.Wrap(errRefreshBinding, err)
|
|
}
|
|
refreshed, err = bs.encryptSecretSnapshots(refreshed)
|
|
if err != nil {
|
|
return errors.Wrap(errRefreshBinding, err)
|
|
}
|
|
return bs.bindings.Save(ctx, configID, refreshed)
|
|
}
|
|
|
|
func (bs bootstrapService) dec(in string) (string, error) {
|
|
ciphertext, err := hex.DecodeString(in)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
block, err := aes.NewCipher(bs.encKey)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(ciphertext) < aes.BlockSize {
|
|
return "", err
|
|
}
|
|
iv := ciphertext[:aes.BlockSize]
|
|
ciphertext = ciphertext[aes.BlockSize:]
|
|
stream := cipher.NewCFBDecrypter(block, iv)
|
|
stream.XORKeyStream(ciphertext, ciphertext)
|
|
return string(ciphertext), nil
|
|
}
|