mirror of
https://github.com/grafana/grafana.git
synced 2025-02-13 00:55:47 -06:00
SupportBundles: Add bundle encryption based on age (#62501)
* add bundle encryption based on age * undo changes to grafana-data * sort deps * test bundle creation and encryption * use whitespace separator * add support bundle config documentation * Update docs/sources/troubleshooting/support-bundles/index.md * Apply suggestions from code review Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * touch up docs * extract encrypt * Update docs/sources/troubleshooting/support-bundles/index.md Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * Update docs/sources/troubleshooting/support-bundles/index.md Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> --------- Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
This commit is contained in:
parent
966bcd3545
commit
af987ae636
@ -1377,6 +1377,14 @@ max_crawl_duration =
|
||||
# This setting should be expressed as a duration. Examples: 10s (seconds), 1m (minutes).
|
||||
scheduler_interval =
|
||||
|
||||
#################################### Support Bundles #####################################
|
||||
[support_bundles]
|
||||
# Enable support bundle creation (default: true)
|
||||
enabled = true
|
||||
# Only server admins can generate and view support bundles (default: true)
|
||||
server_admin_only = true
|
||||
# If set, bundles will be encrypted with the provided public keys separated by whitespace
|
||||
public_keys = ""
|
||||
|
||||
#################################### Storage ################################################
|
||||
|
||||
|
@ -1263,6 +1263,14 @@
|
||||
;grpc_host =
|
||||
;grpc_port =
|
||||
|
||||
[support_bundles]
|
||||
# Enable support bundle creation (default: true)
|
||||
#enabled = true
|
||||
# Only server admins can generate and view support bundles (default: true)
|
||||
#server_admin_only = true
|
||||
# If set, bundles will be encrypted with the provided public keys separated by whitespace
|
||||
#public_keys = ""
|
||||
|
||||
[enterprise]
|
||||
# Path to a valid Grafana Enterprise license.jwt file
|
||||
;license_path =
|
||||
|
@ -53,3 +53,71 @@ To generate a support bundle and send the support bundle to Grafana Labs via a s
|
||||
Grafana downloads the support bundle to an archive (tar.gz) file.
|
||||
|
||||
1. Attach the archive (tar.gz) file to a support ticket that you send to Grafana Labs Technical Support.
|
||||
|
||||
## Support bundle configuration
|
||||
|
||||
You can configure the following settings for support bundles:
|
||||
|
||||
```ini
|
||||
# Enable support bundle creation (default: true)
|
||||
enabled = true
|
||||
# Only server admins can generate and view support bundles. When set to false, organization admins can generate and view support bundles (default: true)
|
||||
server_admin_only = true
|
||||
# If set, bundles will be encrypted with the provided public keys separated by whitespace
|
||||
public_keys = ""
|
||||
```
|
||||
|
||||
## Encrypting a support bundle
|
||||
|
||||
Support bundles can be encrypted with [age](age-encryption.org) before they are sent to
|
||||
recipients. This is useful when you want to send a support bundle to Grafana through a
|
||||
channel that is not private.
|
||||
|
||||
### Generate a key pair
|
||||
|
||||
Ensure [age](https://github.com/FiloSottile/age#installation) is installed on your system.
|
||||
|
||||
```bash
|
||||
$ age-keygen -o key.txt
|
||||
Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
|
||||
```
|
||||
|
||||
### Support bundle encryption
|
||||
|
||||
Ensure [age](https://github.com/FiloSottile/age#installation) is installed on your system.
|
||||
|
||||
Add the public key to the `public_keys` setting in the `support_bundle` section of the Grafana configuration file.
|
||||
|
||||
```ini
|
||||
[support_bundle]
|
||||
public_keys = "age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"
|
||||
```
|
||||
|
||||
> Multiple public keys can be defined by separating them with whitespace.
|
||||
> All included public keys will be able to decrypt the support bundle.
|
||||
|
||||
Example:
|
||||
|
||||
```ini
|
||||
[support_bundle]
|
||||
public_keys = "age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p age1yu8vzu554pv3klw46yhdv4raz36k5w3vy30lpxn46923lqngudyqvxacer"
|
||||
```
|
||||
|
||||
When you restart Grafana, new support bundles will be encrypted with the provided
|
||||
public keys. The support bundle file extension is `tar.gz.age`.
|
||||
|
||||
#### Decrypt a support bundle
|
||||
|
||||
Ensure [age](https://github.com/FiloSottile/age#installation) is installed on your system.
|
||||
|
||||
Execute the following command to decrypt the support bundle:
|
||||
|
||||
```bash
|
||||
age --decrypt -i keyfile -o output.tar.gz downloaded.tar.gz.age
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
age --decrypt -i key.txt -o data.tar.gz af6684b4-d613-4b31-9fc3-7cb579199bea.tar.gz.age
|
||||
```
|
||||
|
3
go.mod
3
go.mod
@ -131,7 +131,7 @@ require (
|
||||
gopkg.in/square/go-jose.v2 v2.5.1
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
xorm.io/builder v0.3.6 // indirect
|
||||
xorm.io/builder v0.3.6
|
||||
xorm.io/core v0.7.3
|
||||
xorm.io/xorm v0.8.2
|
||||
)
|
||||
@ -348,6 +348,7 @@ require (
|
||||
require (
|
||||
cloud.google.com/go/compute v1.13.0 // indirect
|
||||
cloud.google.com/go/iam v0.8.0 // indirect
|
||||
filippo.io/age v1.1.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0 // indirect
|
||||
|
4
go.sum
4
go.sum
@ -101,6 +101,8 @@ contrib.go.opencensus.io/exporter/prometheus v0.3.0/go.mod h1:rpCPVQKhiyH8oomWgm
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.13.10/go.mod h1:I5htMbyta491eUxufwwZPQdcKvvgzMB4O9ni41YnIM8=
|
||||
contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/age v1.1.1 h1:pIpO7l151hCnQ4BdyBujnGP2YlUo0uj6sAVNHGBvXHg=
|
||||
filippo.io/age v1.1.1/go.mod h1:l03SrzDUrBkdBx8+IILdnn2KZysqQdbEBUQ4p3sqEQE=
|
||||
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
|
||||
github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d/go.mod h1:3cARGAK9CfW3HoxCy1a0G4TKrdiKke8ftOMEOHyySYs=
|
||||
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY=
|
||||
@ -1264,8 +1266,6 @@ github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58 h1:2
|
||||
github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58/go.mod h1:Vo2TKWfDVmNTELBUM+3lkrZvFtBws0qSZdXhQxRdJrE=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.94.0/go.mod h1:3VXz4nCv6wH5SfgB3mlW39s+c+LetqSCjFj7xxPC5+M=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.148.0 h1:M8v6L9agAFMlZMnak1yInII+aVF5FjZ1Qv4Q+GANyk4=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.148.0/go.mod h1:NMgO3t2gR5wyLx8bWZ9CTmpDk5Txp4wYFccFLHdYn3Q=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.149.1 h1:n4Mx8oUE+exa1DGdWSUmp2DuZUDhURQEzPG05HUGfnc=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.149.1/go.mod h1:NMgO3t2gR5wyLx8bWZ9CTmpDk5Txp4wYFccFLHdYn3Q=
|
||||
github.com/grafana/phlare/api v0.1.2 h1:1jrwd3KnsXMzj/tJih9likx5EvbY3pbvLbDqAAYem30=
|
||||
|
77
pkg/infra/kvstore/test_utils.go
Normal file
77
pkg/infra/kvstore/test_utils.go
Normal file
@ -0,0 +1,77 @@
|
||||
package kvstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// In memory kv store used for testing
|
||||
type FakeKVStore struct {
|
||||
store map[Key]string
|
||||
delError bool
|
||||
}
|
||||
|
||||
func NewFakeKVStore() *FakeKVStore {
|
||||
return &FakeKVStore{store: make(map[Key]string)}
|
||||
}
|
||||
|
||||
func (f *FakeKVStore) DeletionError(shouldErr bool) {
|
||||
f.delError = shouldErr
|
||||
}
|
||||
|
||||
func (f *FakeKVStore) Get(ctx context.Context, orgId int64, namespace string, key string) (string, bool, error) {
|
||||
value := f.store[buildKey(orgId, namespace, key)]
|
||||
found := value != ""
|
||||
return value, found, nil
|
||||
}
|
||||
|
||||
func (f *FakeKVStore) Set(ctx context.Context, orgId int64, namespace string, key string, value string) error {
|
||||
f.store[buildKey(orgId, namespace, key)] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FakeKVStore) Del(ctx context.Context, orgId int64, namespace string, key string) error {
|
||||
if f.delError {
|
||||
return errors.New("mocked del error")
|
||||
}
|
||||
delete(f.store, buildKey(orgId, namespace, key))
|
||||
return nil
|
||||
}
|
||||
|
||||
// List all keys with an optional filter. If default values are provided, filter is not applied.
|
||||
func (f *FakeKVStore) Keys(ctx context.Context, orgId int64, namespace string, keyPrefix string) ([]Key, error) {
|
||||
res := make([]Key, 0)
|
||||
for k := range f.store {
|
||||
if orgId == AllOrganizations && namespace == "" && keyPrefix == "" {
|
||||
res = append(res, k)
|
||||
} else if k.OrgId == orgId && k.Namespace == namespace && strings.HasPrefix(k.Key, keyPrefix) {
|
||||
res = append(res, k)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (f *FakeKVStore) GetAll(ctx context.Context, orgId int64, namespace string) (map[int64]map[string]string, error) {
|
||||
items := make(map[int64]map[string]string)
|
||||
for k := range f.store {
|
||||
orgId := k.OrgId
|
||||
namespace := k.Namespace
|
||||
|
||||
if _, ok := items[orgId]; !ok {
|
||||
items[orgId] = make(map[string]string)
|
||||
}
|
||||
|
||||
items[orgId][namespace] = f.store[k]
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func buildKey(orgId int64, namespace string, key string) Key {
|
||||
return Key{
|
||||
OrgId: orgId,
|
||||
Namespace: namespace,
|
||||
Key: key,
|
||||
}
|
||||
}
|
@ -99,6 +99,10 @@ func (s *Service) handleDownload(ctx *contextmodel.ReqContext) response.Response
|
||||
|
||||
ctx.Resp.Header().Set("Content-Type", "application/tar+gzip")
|
||||
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.tar.gz", uid))
|
||||
if len(s.encryptionPublicKeys) > 0 {
|
||||
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.tar.gz.age", uid))
|
||||
}
|
||||
|
||||
return response.CreateNormalResponse(ctx.Resp.Header(), bundle.TarBytes, http.StatusOK)
|
||||
}
|
||||
|
||||
|
@ -27,45 +27,48 @@ const (
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
cfg *setting.Cfg
|
||||
store bundleStore
|
||||
pluginStore plugins.Store
|
||||
pluginSettings pluginsettings.Service
|
||||
accessControl ac.AccessControl
|
||||
features *featuremgmt.FeatureManager
|
||||
bundleRegistry *bundleregistry.Service
|
||||
cfg *setting.Cfg
|
||||
features *featuremgmt.FeatureManager
|
||||
pluginSettings pluginsettings.Service
|
||||
pluginStore plugins.Store
|
||||
store bundleStore
|
||||
|
||||
log log.Logger
|
||||
log log.Logger
|
||||
encryptionPublicKeys []string
|
||||
|
||||
enabled bool
|
||||
serverAdminOnly bool
|
||||
}
|
||||
|
||||
func ProvideService(cfg *setting.Cfg,
|
||||
bundleRegistry *bundleregistry.Service,
|
||||
sql db.DB,
|
||||
kvStore kvstore.KVStore,
|
||||
func ProvideService(
|
||||
accessControl ac.AccessControl,
|
||||
accesscontrolService ac.Service,
|
||||
routeRegister routing.RouteRegister,
|
||||
settings setting.Provider,
|
||||
pluginStore plugins.Store,
|
||||
pluginSettings pluginsettings.Service,
|
||||
bundleRegistry *bundleregistry.Service,
|
||||
cfg *setting.Cfg,
|
||||
features *featuremgmt.FeatureManager,
|
||||
httpServer *grafanaApi.HTTPServer,
|
||||
kvStore kvstore.KVStore,
|
||||
pluginSettings pluginsettings.Service,
|
||||
pluginStore plugins.Store,
|
||||
routeRegister routing.RouteRegister,
|
||||
settings setting.Provider,
|
||||
sql db.DB,
|
||||
usageStats usagestats.Service) (*Service, error) {
|
||||
section := cfg.SectionWithEnvOverrides("support_bundles")
|
||||
s := &Service{
|
||||
cfg: cfg,
|
||||
store: newStore(kvStore),
|
||||
pluginStore: pluginStore,
|
||||
pluginSettings: pluginSettings,
|
||||
accessControl: accessControl,
|
||||
features: features,
|
||||
bundleRegistry: bundleRegistry,
|
||||
log: log.New("supportbundle.service"),
|
||||
enabled: section.Key("enabled").MustBool(true),
|
||||
serverAdminOnly: section.Key("server_admin_only").MustBool(true),
|
||||
accessControl: accessControl,
|
||||
bundleRegistry: bundleRegistry,
|
||||
cfg: cfg,
|
||||
enabled: section.Key("enabled").MustBool(true),
|
||||
encryptionPublicKeys: section.Key("public_keys").Strings(" "),
|
||||
features: features,
|
||||
log: log.New("supportbundle.service"),
|
||||
pluginSettings: pluginSettings,
|
||||
pluginStore: pluginStore,
|
||||
serverAdminOnly: section.Key("server_admin_only").MustBool(true),
|
||||
store: newStore(kvStore),
|
||||
}
|
||||
|
||||
usageStats.RegisterMetricsFunc(s.getUsageStats)
|
||||
|
@ -6,11 +6,14 @@ import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"filippo.io/age"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/supportbundles"
|
||||
)
|
||||
|
||||
@ -92,7 +95,43 @@ func (s *Service) bundle(ctx context.Context, collectors []string, uid string) (
|
||||
return nil, errCompress
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
final := buf
|
||||
if len(s.encryptionPublicKeys) > 0 {
|
||||
var err error
|
||||
final, err = encrypt(buf, s.encryptionPublicKeys...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return final.Bytes(), nil
|
||||
}
|
||||
|
||||
func encrypt(buf bytes.Buffer, publicKeys ...string) (bytes.Buffer, error) {
|
||||
final := bytes.Buffer{}
|
||||
recipients := make([]age.Recipient, 0, len(publicKeys))
|
||||
for _, key := range publicKeys {
|
||||
recipient, err := age.ParseX25519Recipient(key)
|
||||
if err != nil {
|
||||
return final, fmt.Errorf("unable to parse support bundle recipient public key: %w", err)
|
||||
}
|
||||
recipients = append(recipients, recipient)
|
||||
}
|
||||
|
||||
w, err := age.Encrypt(&final, recipients...)
|
||||
if err != nil {
|
||||
return final, fmt.Errorf("unable to open support bundle encryption header: %w", err)
|
||||
}
|
||||
|
||||
if _, err = w.Write(buf.Bytes()); err != nil {
|
||||
return final, fmt.Errorf("unable to write support bundle encryption: %w", err)
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
return final, fmt.Errorf("unable to close support bundle encryption: %w", err)
|
||||
}
|
||||
|
||||
return final, nil
|
||||
}
|
||||
|
||||
func compress(files map[string][]byte, buf io.Writer) error {
|
||||
|
@ -0,0 +1,162 @@
|
||||
package supportbundlesimpl
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"filippo.io/age"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/kvstore"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/supportbundles"
|
||||
"github.com/grafana/grafana/pkg/services/supportbundles/bundleregistry"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
testAgePublicKey = "age15xgfm9gg89nz92pqjat2hy9h7lpwwfwwgvchg67lqtzzhjtmg5vq9utnd4"
|
||||
testAgePrivateKey = "AGE-SECRET-KEY-1HGMLT8VSC95UXN2R5LUZECXT42WW7TSEYQKCWLX7PKH3YHS6HGCQ0XVEFD"
|
||||
testAgePublicKey2 = "age1q02sf508xetfa5ztzhuw0hxweyd50n27qndufaffvdth25knueds8w99c5"
|
||||
testAgePrivateKey2 = "AGE-SECRET-KEY-1DSW2P60F2ZRY4D4M57PEKTFVYCXXDYYZ0VZWG5RTUZCWHR3EJ9TQP92JXQ"
|
||||
)
|
||||
|
||||
func TestService_bundleCreate(t *testing.T) {
|
||||
s := &Service{
|
||||
log: log.New("test"),
|
||||
bundleRegistry: bundleregistry.ProvideService(),
|
||||
store: newStore(kvstore.NewFakeKVStore()),
|
||||
}
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
|
||||
collector := basicCollector(cfg)
|
||||
s.bundleRegistry.RegisterSupportItemCollector(collector)
|
||||
|
||||
createdBundle, err := s.store.Create(context.Background(), &user.SignedInUser{UserID: 1, Login: "bob"})
|
||||
require.NoError(t, err)
|
||||
|
||||
s.startBundleWork(context.Background(), []string{collector.UID}, createdBundle.UID)
|
||||
|
||||
bundle, err := s.get(context.Background(), createdBundle.UID)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, createdBundle.UID, bundle.UID)
|
||||
assert.Equal(t, supportbundles.StateComplete, bundle.State)
|
||||
assert.Equal(t, "bob", bundle.Creator)
|
||||
assert.NotZero(t, len(bundle.TarBytes))
|
||||
|
||||
confirmFilesInTar(t, bundle.TarBytes)
|
||||
}
|
||||
|
||||
func TestService_bundleEncryptDecrypt(t *testing.T) {
|
||||
s := &Service{
|
||||
log: log.New("test"),
|
||||
bundleRegistry: bundleregistry.ProvideService(),
|
||||
store: newStore(kvstore.NewFakeKVStore()),
|
||||
encryptionPublicKeys: []string{testAgePublicKey},
|
||||
}
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
|
||||
collector := basicCollector(cfg)
|
||||
s.bundleRegistry.RegisterSupportItemCollector(collector)
|
||||
|
||||
createdBundle, err := s.store.Create(context.Background(), &user.SignedInUser{UserID: 1, Login: "bob"})
|
||||
require.NoError(t, err)
|
||||
|
||||
s.startBundleWork(context.Background(), []string{collector.UID}, createdBundle.UID)
|
||||
|
||||
bundle, err := s.get(context.Background(), createdBundle.UID)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, createdBundle.UID, bundle.UID)
|
||||
assert.Equal(t, supportbundles.StateComplete, bundle.State)
|
||||
assert.Equal(t, "bob", bundle.Creator)
|
||||
assert.NotZero(t, len(bundle.TarBytes))
|
||||
|
||||
tarBytes := decryptTar(t, bundle.TarBytes, testAgePrivateKey)
|
||||
assert.NotZero(t, len(tarBytes))
|
||||
|
||||
confirmFilesInTar(t, tarBytes)
|
||||
}
|
||||
|
||||
func TestService_bundleEncryptDecryptMultipleRecipients(t *testing.T) {
|
||||
s := &Service{
|
||||
log: log.New("test"),
|
||||
bundleRegistry: bundleregistry.ProvideService(),
|
||||
store: newStore(kvstore.NewFakeKVStore()),
|
||||
encryptionPublicKeys: []string{testAgePublicKey, testAgePublicKey2},
|
||||
}
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
|
||||
collector := basicCollector(cfg)
|
||||
s.bundleRegistry.RegisterSupportItemCollector(collector)
|
||||
|
||||
createdBundle, err := s.store.Create(context.Background(), &user.SignedInUser{UserID: 1, Login: "bob"})
|
||||
require.NoError(t, err)
|
||||
|
||||
s.startBundleWork(context.Background(), []string{collector.UID}, createdBundle.UID)
|
||||
|
||||
bundle, err := s.get(context.Background(), createdBundle.UID)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, createdBundle.UID, bundle.UID)
|
||||
assert.Equal(t, supportbundles.StateComplete, bundle.State)
|
||||
assert.Equal(t, "bob", bundle.Creator)
|
||||
assert.NotZero(t, len(bundle.TarBytes))
|
||||
|
||||
tarBytes := decryptTar(t, bundle.TarBytes, testAgePrivateKey)
|
||||
assert.NotZero(t, len(tarBytes))
|
||||
|
||||
confirmFilesInTar(t, tarBytes)
|
||||
|
||||
tarBytes2 := decryptTar(t, bundle.TarBytes, testAgePrivateKey2)
|
||||
assert.NotZero(t, len(tarBytes2))
|
||||
|
||||
confirmFilesInTar(t, tarBytes2)
|
||||
}
|
||||
|
||||
func decryptTar(t *testing.T, tarBytes []byte, privateKey string) []byte {
|
||||
reader := bytes.NewReader(tarBytes)
|
||||
t.Helper()
|
||||
recipientPK, err := age.ParseX25519Identity(privateKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
tarBytesReader, err := age.Decrypt(reader, recipientPK)
|
||||
require.NoError(t, err)
|
||||
|
||||
newTarBytes, err := io.ReadAll(tarBytesReader)
|
||||
require.NoError(t, err)
|
||||
return newTarBytes
|
||||
}
|
||||
|
||||
// Check that the tarball contains the expected files
|
||||
func confirmFilesInTar(t *testing.T, tarBytes []byte) {
|
||||
t.Helper()
|
||||
r := bytes.NewReader(tarBytes)
|
||||
gzipReader, err := gzip.NewReader(r)
|
||||
require.NoError(t, err)
|
||||
|
||||
tr := tar.NewReader(gzipReader)
|
||||
files := []string{}
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
files = append(files, hdr.Name)
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, []string{"/bundle/basic.json"}, files)
|
||||
}
|
Loading…
Reference in New Issue
Block a user