mirror of https://github.com/grafana/grafana.git
SecretsManager: add secure value store (#106708)
* SecretsManager: add secure value model and sql templates Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com> Co-authored-by: Dana Axinte <53751979+dana-axinte@users.noreply.github.com> Co-authored-by: Leandro Deveikis <leandro.deveikis@gmail.com> Co-authored-by: PoorlyDefinedBehaviour <brunotj2015@hotmail.com> * SecretsManager: secure value rest layer to use store Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com> Co-authored-by: Dana Axinte <53751979+dana-axinte@users.noreply.github.com> Co-authored-by: Leandro Deveikis <leandro.deveikis@gmail.com> Co-authored-by: PoorlyDefinedBehaviour <brunotj2015@hotmail.com> * SecretsManager: temporary add actor prefix to decrypters * Remove list securevalue by namefor now --------- Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com> Co-authored-by: Leandro Deveikis <leandro.deveikis@gmail.com> Co-authored-by: PoorlyDefinedBehaviour <brunotj2015@hotmail.com>
This commit is contained in:
parent
ffc16ee072
commit
6097841e67
|
|
@ -31,7 +31,7 @@ type SecureValueMetadataStorage interface {
|
|||
Read(ctx context.Context, namespace xkube.Namespace, name string, opts ReadOpts) (*secretv0alpha1.SecureValue, error)
|
||||
Update(ctx context.Context, sv *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error)
|
||||
Delete(ctx context.Context, namespace xkube.Namespace, name string) error
|
||||
List(ctx context.Context, namespace xkube.Namespace) (*secretv0alpha1.SecureValueList, error)
|
||||
List(ctx context.Context, namespace xkube.Namespace) ([]secretv0alpha1.SecureValue, error)
|
||||
SetStatus(ctx context.Context, namespace xkube.Namespace, name string, status secretv0alpha1.SecureValueStatus) error
|
||||
SetExternalID(ctx context.Context, namespace xkube.Namespace, name string, externalID ExternalID) error
|
||||
ReadForDecrypt(ctx context.Context, namespace xkube.Namespace, name string) (*DecryptSecureValue, error)
|
||||
|
|
|
|||
|
|
@ -98,9 +98,9 @@ func (s *SecureValueRest) List(ctx context.Context, options *internalversion.Lis
|
|||
fieldSelector = fields.Everything()
|
||||
}
|
||||
|
||||
allowedSecureValues := make([]secretv0alpha1.SecureValue, 0, len(secureValueList.Items))
|
||||
allowedSecureValues := make([]secretv0alpha1.SecureValue, 0, len(secureValueList))
|
||||
|
||||
for _, secureValue := range secureValueList.Items {
|
||||
for _, secureValue := range secureValueList {
|
||||
// Filter by label
|
||||
if labelSelector.Matches(labels.Set(secureValue.Labels)) {
|
||||
// Filter by status.phase
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
INSERT INTO {{ .Ident "secret_secure_value" }} (
|
||||
{{ .Ident "guid" }},
|
||||
{{ .Ident "name" }},
|
||||
{{ .Ident "namespace" }},
|
||||
{{ .Ident "annotations" }},
|
||||
{{ .Ident "labels" }},
|
||||
{{ .Ident "created" }},
|
||||
{{ .Ident "created_by" }},
|
||||
{{ .Ident "updated" }},
|
||||
{{ .Ident "updated_by" }},
|
||||
{{ .Ident "status_phase" }},
|
||||
{{ if .Row.Message.Valid }}
|
||||
{{ .Ident "status_message" }},
|
||||
{{ end }}
|
||||
{{ .Ident "description" }},
|
||||
{{ if .Row.Keeper.Valid }}
|
||||
{{ .Ident "keeper" }},
|
||||
{{ end }}
|
||||
{{ if .Row.Decrypters.Valid }}
|
||||
{{ .Ident "decrypters" }},
|
||||
{{ end }}
|
||||
{{ if .Row.Ref.Valid }}
|
||||
{{ .Ident "ref" }},
|
||||
{{ end }}
|
||||
{{ .Ident "external_id" }}
|
||||
) VALUES (
|
||||
{{ .Arg .Row.GUID }},
|
||||
{{ .Arg .Row.Name }},
|
||||
{{ .Arg .Row.Namespace }},
|
||||
{{ .Arg .Row.Annotations }},
|
||||
{{ .Arg .Row.Labels }},
|
||||
{{ .Arg .Row.Created }},
|
||||
{{ .Arg .Row.CreatedBy }},
|
||||
{{ .Arg .Row.Updated }},
|
||||
{{ .Arg .Row.UpdatedBy }},
|
||||
{{ .Arg .Row.Phase }},
|
||||
{{ if .Row.Message.Valid }}
|
||||
{{ .Arg .Row.Message.String }},
|
||||
{{ end }}
|
||||
{{ .Arg .Row.Description }},
|
||||
{{ if .Row.Keeper.Valid }}
|
||||
{{ .Arg .Row.Keeper.String }},
|
||||
{{ end }}
|
||||
{{ if .Row.Decrypters.Valid }}
|
||||
{{ .Arg .Row.Decrypters.String }},
|
||||
{{ end }}
|
||||
{{ if .Row.Ref.Valid }}
|
||||
{{ .Arg .Row.Ref.String }},
|
||||
{{ end }}
|
||||
{{ .Arg .Row.ExternalID }}
|
||||
);
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
DELETE FROM {{ .Ident "secret_secure_value" }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }}
|
||||
;
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
SELECT
|
||||
{{ .Ident "guid" }},
|
||||
{{ .Ident "name" }},
|
||||
{{ .Ident "namespace" }},
|
||||
{{ .Ident "annotations" }},
|
||||
{{ .Ident "labels" }},
|
||||
{{ .Ident "created" }},
|
||||
{{ .Ident "created_by" }},
|
||||
{{ .Ident "updated" }},
|
||||
{{ .Ident "updated_by" }},
|
||||
{{ .Ident "status_phase" }},
|
||||
{{ .Ident "status_message" }},
|
||||
{{ .Ident "description" }},
|
||||
{{ .Ident "keeper" }},
|
||||
{{ .Ident "decrypters" }},
|
||||
{{ .Ident "ref" }},
|
||||
{{ .Ident "external_id" }}
|
||||
FROM
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Namespace }}
|
||||
ORDER BY {{ .Ident "updated" }} DESC
|
||||
;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{{/* this query is used to validate the keeper update or creation */}}
|
||||
|
||||
SELECT
|
||||
{{ .Ident "name" }},
|
||||
{{ .Ident "keeper" }}
|
||||
FROM
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} IN ({{ .ArgList .UsedSecureValues }})
|
||||
{{ .SelectFor "UPDATE" }}
|
||||
;
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
SELECT
|
||||
{{ .Ident "guid" }},
|
||||
{{ .Ident "name" }},
|
||||
{{ .Ident "namespace" }},
|
||||
{{ .Ident "annotations" }},
|
||||
{{ .Ident "labels" }},
|
||||
{{ .Ident "created" }},
|
||||
{{ .Ident "created_by" }},
|
||||
{{ .Ident "updated" }},
|
||||
{{ .Ident "updated_by" }},
|
||||
{{ .Ident "status_phase" }},
|
||||
{{ .Ident "status_message" }},
|
||||
{{ .Ident "description" }},
|
||||
{{ .Ident "keeper" }},
|
||||
{{ .Ident "decrypters" }},
|
||||
{{ .Ident "ref" }},
|
||||
{{ .Ident "external_id" }}
|
||||
FROM
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }}
|
||||
{{ if .IsForUpdate }}
|
||||
{{ .SelectFor "UPDATE" }}
|
||||
{{ end }}
|
||||
;
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
SELECT
|
||||
{{ .Ident "keeper" }},
|
||||
{{ .Ident "decrypters" }},
|
||||
{{ .Ident "ref" }},
|
||||
{{ .Ident "external_id" }}
|
||||
FROM
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }}
|
||||
;
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
UPDATE
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
SET
|
||||
{{ .Ident "guid" }} = {{ .Arg .Row.GUID }},
|
||||
{{ .Ident "name" }} = {{ .Arg .Row.Name }},
|
||||
{{ .Ident "namespace" }} = {{ .Arg .Row.Namespace }},
|
||||
{{ .Ident "annotations" }} = {{ .Arg .Row.Annotations }},
|
||||
{{ .Ident "labels" }} = {{ .Arg .Row.Labels }},
|
||||
{{ .Ident "created" }} = {{ .Arg .Row.Created }},
|
||||
{{ .Ident "created_by" }} = {{ .Arg .Row.CreatedBy }},
|
||||
{{ .Ident "updated" }} = {{ .Arg .Row.Updated }},
|
||||
{{ .Ident "updated_by" }} = {{ .Arg .Row.UpdatedBy }},
|
||||
{{ .Ident "status_phase" }} = {{ .Arg .Row.Phase }},
|
||||
{{ if .Row.Message.Valid }}
|
||||
{{ .Ident "status_message" }} = {{ .Arg .Row.Message.String }},
|
||||
{{ end }}
|
||||
{{ .Ident "description" }} = {{ .Arg .Row.Description }},
|
||||
{{ if .Row.Keeper.Valid }}
|
||||
{{ .Ident "keeper" }} = {{ .Arg .Row.Keeper.String }},
|
||||
{{ end }}
|
||||
{{ if .Row.Decrypters.Valid }}
|
||||
{{ .Ident "decrypters" }} = {{ .Arg .Row.Decrypters.String }},
|
||||
{{ end }}
|
||||
{{ if .Row.Ref.Valid }}
|
||||
{{ .Ident "ref" }} = {{ .Arg .Row.Ref.String }},
|
||||
{{ end }}
|
||||
{{ .Ident "external_id" }} = {{ .Arg .Row.ExternalID }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Row.Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Row.Name }}
|
||||
;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
UPDATE
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
SET
|
||||
{{ .Ident "external_id" }} = {{ .Arg .ExternalID }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }}
|
||||
;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
UPDATE
|
||||
{{ .Ident "secret_secure_value" }}
|
||||
SET
|
||||
{{ .Ident "status_phase" }} = {{ .Arg .Phase }},
|
||||
{{ .Ident "status_message" }} = {{ .Arg .Message }}
|
||||
WHERE {{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND
|
||||
{{ .Ident "name" }} = {{ .Arg .Name }}
|
||||
;
|
||||
|
|
@ -23,6 +23,15 @@ var (
|
|||
|
||||
sqlKeeperListByName = mustTemplate("keeper_listByName.sql")
|
||||
|
||||
sqlSecureValueRead = mustTemplate("secure_value_read.sql")
|
||||
sqlSecureValueList = mustTemplate("secure_value_list.sql")
|
||||
sqlSecureValueCreate = mustTemplate("secure_value_create.sql")
|
||||
sqlSecureValueDelete = mustTemplate("secure_value_delete.sql")
|
||||
sqlSecureValueUpdate = mustTemplate("secure_value_update.sql")
|
||||
sqlSecureValueUpdateExternalId = mustTemplate("secure_value_updateExternalId.sql")
|
||||
sqlSecureValueUpdateStatus = mustTemplate("secure_value_updateStatus.sql")
|
||||
sqlSecureValueReadForDecrypt = mustTemplate("secure_value_read_for_decrypt.sql")
|
||||
|
||||
sqlSecureValueOutboxAppend = mustTemplate("secure_value_outbox_append.sql")
|
||||
sqlSecureValueOutboxReceiveN = mustTemplate("secure_value_outbox_receiveN.sql")
|
||||
sqlSecureValueOutboxDelete = mustTemplate("secure_value_outbox_delete.sql")
|
||||
|
|
@ -110,6 +119,102 @@ func (r listByNameKeeper) Validate() error {
|
|||
return nil // TODO
|
||||
}
|
||||
|
||||
/******************************/
|
||||
/**-- Secure Value Queries --**/
|
||||
/******************************/
|
||||
|
||||
type readSecureValue struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
Name string
|
||||
IsForUpdate bool
|
||||
}
|
||||
|
||||
// Validate is only used if we use `dbutil` from `unifiedstorage`
|
||||
func (r readSecureValue) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
type listSecureValue struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// Validate is only used if we use `dbutil` from `unifiedstorage`
|
||||
func (r listSecureValue) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
type createSecureValue struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Row *secureValueDB
|
||||
}
|
||||
|
||||
// Validate is only used if we use `dbutil` from `unifiedstorage`
|
||||
func (r createSecureValue) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
// Delete
|
||||
type deleteSecureValue struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
Name string
|
||||
}
|
||||
|
||||
// Validate is only used if we use `dbutil` from `unifiedstorage`
|
||||
func (r deleteSecureValue) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
// Update externalId
|
||||
type updateExternalIdSecureValue struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
Name string
|
||||
ExternalID string
|
||||
}
|
||||
|
||||
// Validate is only used if we use `dbutil` from `unifiedstorage`
|
||||
func (r updateExternalIdSecureValue) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
// Update secure value
|
||||
type updateSecureValue struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
Name string
|
||||
Row *secureValueDB
|
||||
}
|
||||
|
||||
// Validate is only used if we use `dbutil` from `unifiedstorage`
|
||||
func (r updateSecureValue) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
// update status message
|
||||
type updateStatusSecureValue struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
Name string
|
||||
Phase string
|
||||
Message string
|
||||
}
|
||||
|
||||
// Validate is only used if we use `dbutil` from `unifiedstorage`
|
||||
func (r updateStatusSecureValue) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
type readSecureValueForDecrypt struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Namespace string
|
||||
Name string
|
||||
}
|
||||
|
||||
func (r readSecureValueForDecrypt) Validate() error { return nil }
|
||||
|
||||
/*************************************/
|
||||
/**-- Secure Value Outbox Queries --**/
|
||||
/*************************************/
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"text/template"
|
||||
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate/mocks"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func TestKeeperQueries(t *testing.T) {
|
||||
|
|
@ -108,6 +109,189 @@ func TestKeeperQueries(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestSecureValueQueries(t *testing.T) {
|
||||
mocks.CheckQuerySnapshots(t, mocks.TemplateTestSetup{
|
||||
RootDir: "testdata",
|
||||
Templates: map[*template.Template][]mocks.TemplateTestCase{
|
||||
sqlSecureValueRead: {
|
||||
{
|
||||
Name: "read",
|
||||
Data: &readSecureValue{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "read-for-update",
|
||||
Data: &readSecureValue{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
IsForUpdate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueList: {
|
||||
{
|
||||
Name: "list",
|
||||
Data: &listSecureValue{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Namespace: "ns",
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueCreate: {
|
||||
{
|
||||
Name: "create-null",
|
||||
Data: &createSecureValue{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Row: &secureValueDB{
|
||||
GUID: "abc",
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
Annotations: `{"x":"XXXX"}`,
|
||||
Labels: `{"a":"AAA", "b", "BBBB"}`,
|
||||
Created: 1234,
|
||||
CreatedBy: "user:ryan",
|
||||
Updated: 5678,
|
||||
UpdatedBy: "user:cameron",
|
||||
Phase: "creating",
|
||||
Message: toNullString(nil),
|
||||
Description: "description",
|
||||
Keeper: toNullString(nil),
|
||||
Decrypters: toNullString(nil),
|
||||
Ref: toNullString(nil),
|
||||
ExternalID: "extId",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "create-not-null",
|
||||
Data: &createSecureValue{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Row: &secureValueDB{
|
||||
GUID: "abc",
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
Annotations: `{"x":"XXXX"}`,
|
||||
Labels: `{"a":"AAA", "b", "BBBB"}`,
|
||||
Created: 1234,
|
||||
CreatedBy: "user:ryan",
|
||||
Updated: 5678,
|
||||
UpdatedBy: "user:cameron",
|
||||
Phase: "creating",
|
||||
Message: toNullString(ptr.To("message_test")),
|
||||
Description: "description",
|
||||
Keeper: toNullString(ptr.To("keeper_test")),
|
||||
Decrypters: toNullString(ptr.To("decrypters_test")),
|
||||
Ref: toNullString(ptr.To("ref_test")),
|
||||
ExternalID: "extId",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueDelete: {
|
||||
{
|
||||
Name: "delete",
|
||||
Data: &deleteSecureValue{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueUpdate: {
|
||||
{
|
||||
Name: "update-null",
|
||||
Data: &updateSecureValue{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
Row: &secureValueDB{
|
||||
GUID: "abc",
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
Annotations: `{"x":"XXXX"}`,
|
||||
Labels: `{"a":"AAA", "b", "BBBB"}`,
|
||||
Created: 1234,
|
||||
CreatedBy: "user:ryan",
|
||||
Updated: 5678,
|
||||
UpdatedBy: "user:cameron",
|
||||
Phase: "creating",
|
||||
Message: toNullString(nil),
|
||||
Description: "description",
|
||||
Keeper: toNullString(nil),
|
||||
Decrypters: toNullString(nil),
|
||||
Ref: toNullString(nil),
|
||||
ExternalID: "extId",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update-not-null",
|
||||
Data: &updateSecureValue{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
Row: &secureValueDB{
|
||||
GUID: "abc",
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
Annotations: `{"x":"XXXX"}`,
|
||||
Labels: `{"a":"AAA", "b", "BBBB"}`,
|
||||
Created: 1234,
|
||||
CreatedBy: "user:ryan",
|
||||
Updated: 5678,
|
||||
UpdatedBy: "user:cameron",
|
||||
Phase: "creating",
|
||||
Message: toNullString(ptr.To("message_test")),
|
||||
Description: "description",
|
||||
Keeper: toNullString(ptr.To("keeper_test")),
|
||||
Decrypters: toNullString(ptr.To("decrypters_test")),
|
||||
Ref: toNullString(ptr.To("ref_test")),
|
||||
ExternalID: "extId",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueUpdateExternalId: {
|
||||
{
|
||||
Name: "updateExternalId",
|
||||
Data: &updateExternalIdSecureValue{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
ExternalID: "extId",
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueUpdateStatus: {
|
||||
{
|
||||
Name: "updateStatus",
|
||||
Data: &updateStatusSecureValue{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
Phase: "Succeeded",
|
||||
Message: "message-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlSecureValueReadForDecrypt: {
|
||||
{
|
||||
Name: "read-for-decrypt",
|
||||
Data: &readSecureValueForDecrypt{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestSecureValueOutboxQueries(t *testing.T) {
|
||||
mocks.CheckQuerySnapshots(t, mocks.TemplateTestSetup{
|
||||
RootDir: "testdata",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,277 @@
|
|||
package metadata
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
"github.com/grafana/grafana/pkg/storage/secret/migrator"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
type secureValueDB struct {
|
||||
// Kubernetes Metadata
|
||||
GUID string
|
||||
Name string
|
||||
Namespace string
|
||||
Annotations string // map[string]string
|
||||
Labels string // map[string]string
|
||||
Created int64
|
||||
CreatedBy string
|
||||
Updated int64
|
||||
UpdatedBy string
|
||||
|
||||
// Kubernetes Status
|
||||
Phase string
|
||||
Message sql.NullString
|
||||
|
||||
// Spec
|
||||
Description string
|
||||
Keeper sql.NullString
|
||||
Decrypters sql.NullString
|
||||
Ref sql.NullString
|
||||
ExternalID string
|
||||
}
|
||||
|
||||
func (*secureValueDB) TableName() string {
|
||||
return migrator.TableNameSecureValue
|
||||
}
|
||||
|
||||
// toKubernetes maps a DB row into a Kubernetes resource (metadata + spec).
|
||||
func (sv *secureValueDB) toKubernetes() (*secretv0alpha1.SecureValue, error) {
|
||||
annotations := make(map[string]string, 0)
|
||||
if sv.Annotations != "" {
|
||||
if err := json.Unmarshal([]byte(sv.Annotations), &annotations); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal annotations: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
labels := make(map[string]string, 0)
|
||||
if sv.Labels != "" {
|
||||
if err := json.Unmarshal([]byte(sv.Labels), &labels); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal labels: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
decrypters := make([]string, 0)
|
||||
|
||||
if sv.Decrypters.Valid && sv.Decrypters.String != "" {
|
||||
if err := json.Unmarshal([]byte(sv.Decrypters.String), &decrypters); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal decrypters: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
resource := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Description: sv.Description,
|
||||
Decrypters: decrypters,
|
||||
},
|
||||
Status: secretv0alpha1.SecureValueStatus{
|
||||
Phase: secretv0alpha1.SecureValuePhase(sv.Phase),
|
||||
ExternalID: sv.ExternalID,
|
||||
},
|
||||
}
|
||||
|
||||
if sv.Keeper.Valid {
|
||||
resource.Spec.Keeper = &sv.Keeper.String
|
||||
}
|
||||
if sv.Ref.Valid {
|
||||
resource.Spec.Ref = &sv.Ref.String
|
||||
}
|
||||
if sv.Message.Valid {
|
||||
resource.Status.Message = sv.Message.String
|
||||
}
|
||||
resource.Status.Phase = secretv0alpha1.SecureValuePhase(sv.Phase)
|
||||
resource.Status.ExternalID = sv.ExternalID
|
||||
|
||||
// Set all meta fields here for consistency.
|
||||
meta, err := utils.MetaAccessor(resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get meta accessor: %w", err)
|
||||
}
|
||||
|
||||
updated := time.Unix(sv.Updated, 0).UTC()
|
||||
|
||||
meta.SetUID(types.UID(sv.GUID))
|
||||
meta.SetName(sv.Name)
|
||||
meta.SetNamespace(sv.Namespace)
|
||||
meta.SetAnnotations(annotations)
|
||||
meta.SetLabels(labels)
|
||||
meta.SetCreatedBy(sv.CreatedBy)
|
||||
meta.SetCreationTimestamp(metav1.NewTime(time.Unix(sv.Created, 0).UTC()))
|
||||
meta.SetUpdatedBy(sv.UpdatedBy)
|
||||
meta.SetUpdatedTimestamp(&updated)
|
||||
meta.SetResourceVersionInt64(sv.Updated)
|
||||
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
// toCreateRow maps a Kubernetes resource into a DB row for new resources being created/inserted.
|
||||
func toCreateRow(sv *secretv0alpha1.SecureValue, actorUID string) (*secureValueDB, error) {
|
||||
row, err := toRow(sv, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert SecureValue to secureValueDB: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now().UTC().Unix()
|
||||
|
||||
row.GUID = uuid.New().String()
|
||||
row.Created = now
|
||||
row.CreatedBy = actorUID
|
||||
row.Updated = now
|
||||
row.UpdatedBy = actorUID
|
||||
|
||||
return row, nil
|
||||
}
|
||||
|
||||
// toUpdateRow maps a Kubernetes resource into a DB row for existing resources being updated.
|
||||
func toUpdateRow(currentRow *secureValueDB, newSecureValue *secretv0alpha1.SecureValue, actorUID, externalID string) (*secureValueDB, error) {
|
||||
row, err := toRow(newSecureValue, externalID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now().UTC().Unix()
|
||||
|
||||
row.GUID = currentRow.GUID
|
||||
row.Created = currentRow.Created
|
||||
row.CreatedBy = currentRow.CreatedBy
|
||||
row.Updated = now
|
||||
row.UpdatedBy = actorUID
|
||||
|
||||
return row, nil
|
||||
}
|
||||
|
||||
// toRow maps a Kubernetes resource into a DB row.
|
||||
func toRow(sv *secretv0alpha1.SecureValue, externalID string) (*secureValueDB, error) {
|
||||
var annotations string
|
||||
if len(sv.Annotations) > 0 {
|
||||
cleanedAnnotations := xkube.CleanAnnotations(sv.Annotations)
|
||||
if len(cleanedAnnotations) > 0 {
|
||||
sv.Annotations = make(map[string]string) // Safety: reset to prohibit use of sv.Annotations further.
|
||||
|
||||
encodedAnnotations, err := json.Marshal(cleanedAnnotations)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode annotations: %w", err)
|
||||
}
|
||||
|
||||
annotations = string(encodedAnnotations)
|
||||
}
|
||||
}
|
||||
|
||||
var labels string
|
||||
if len(sv.Labels) > 0 {
|
||||
encodedLabels, err := json.Marshal(sv.Labels)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode labels: %w", err)
|
||||
}
|
||||
|
||||
labels = string(encodedLabels)
|
||||
}
|
||||
|
||||
var decrypters *string
|
||||
if len(sv.Spec.Decrypters) > 0 {
|
||||
encodedDecrypters, err := json.Marshal(sv.Spec.Decrypters)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode decrypters: %w", err)
|
||||
}
|
||||
|
||||
rawDecrypters := string(encodedDecrypters)
|
||||
decrypters = &rawDecrypters
|
||||
}
|
||||
|
||||
meta, err := utils.MetaAccessor(sv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get meta accessor: %w", err)
|
||||
}
|
||||
|
||||
if meta.GetFolder() != "" {
|
||||
return nil, fmt.Errorf("folders are not supported")
|
||||
}
|
||||
|
||||
updatedTimestamp, err := meta.GetResourceVersionInt64()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get resource version: %w", err)
|
||||
}
|
||||
|
||||
var statusMessage *string
|
||||
if sv.Status.Message != "" {
|
||||
statusMessage = &sv.Status.Message
|
||||
}
|
||||
|
||||
return &secureValueDB{
|
||||
GUID: string(sv.UID),
|
||||
Name: sv.Name,
|
||||
Namespace: sv.Namespace,
|
||||
Annotations: annotations,
|
||||
Labels: labels,
|
||||
Created: meta.GetCreationTimestamp().UnixMilli(),
|
||||
CreatedBy: meta.GetCreatedBy(),
|
||||
Updated: updatedTimestamp,
|
||||
UpdatedBy: meta.GetUpdatedBy(),
|
||||
|
||||
Phase: string(sv.Status.Phase),
|
||||
Message: toNullString(statusMessage),
|
||||
|
||||
Description: sv.Spec.Description,
|
||||
Keeper: toNullString(sv.Spec.Keeper),
|
||||
Decrypters: toNullString(decrypters),
|
||||
Ref: toNullString(sv.Spec.Ref),
|
||||
ExternalID: externalID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DTO for `secureValueForDecrypt` query result, only what we need.
|
||||
type secureValueForDecrypt struct {
|
||||
Keeper sql.NullString
|
||||
Decrypters sql.NullString
|
||||
Ref sql.NullString
|
||||
ExternalID string
|
||||
}
|
||||
|
||||
// to Decrypt maps a DB row into a DecryptSecureValue object needed for decryption.
|
||||
func (sv *secureValueForDecrypt) toDecrypt() (*contracts.DecryptSecureValue, error) {
|
||||
decrypters := make([]string, 0)
|
||||
if sv.Decrypters.Valid && sv.Decrypters.String != "" {
|
||||
if err := json.Unmarshal([]byte(sv.Decrypters.String), &decrypters); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal decrypters: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
decryptSecureValue := &contracts.DecryptSecureValue{
|
||||
Decrypters: decrypters,
|
||||
ExternalID: sv.ExternalID,
|
||||
}
|
||||
|
||||
if sv.Keeper.Valid && sv.Keeper.String != "" {
|
||||
decryptSecureValue.Keeper = &sv.Keeper.String
|
||||
}
|
||||
if sv.Ref.Valid && sv.Ref.String != "" {
|
||||
decryptSecureValue.Ref = sv.Ref.String
|
||||
}
|
||||
|
||||
return decryptSecureValue, nil
|
||||
}
|
||||
|
||||
// toNullString returns a sql.NullString struct given a *string
|
||||
// assumes that "" (empty string) is a valid string
|
||||
func toNullString(s *string) sql.NullString {
|
||||
if s == nil {
|
||||
return sql.NullString{
|
||||
String: "",
|
||||
Valid: false,
|
||||
}
|
||||
}
|
||||
|
||||
return sql.NullString{
|
||||
String: *s,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
|
@ -2,59 +2,418 @@ package metadata
|
|||
|
||||
import (
|
||||
"context"
|
||||
|
||||
claims "github.com/grafana/authlib/types"
|
||||
"fmt"
|
||||
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
||||
)
|
||||
|
||||
func ProvideSecureValueMetadataStorage(db db.DB, features featuremgmt.FeatureToggles, accessClient claims.AccessClient) (contracts.SecureValueMetadataStorage, error) {
|
||||
var _ contracts.SecureValueMetadataStorage = (*secureValueMetadataStorage)(nil)
|
||||
|
||||
func ProvideSecureValueMetadataStorage(db contracts.Database, features featuremgmt.FeatureToggles) (contracts.SecureValueMetadataStorage, error) {
|
||||
if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) ||
|
||||
!features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) {
|
||||
return &secureValueMetadataStorage{}, nil
|
||||
}
|
||||
|
||||
return &secureValueMetadataStorage{db: db, accessClient: accessClient}, nil
|
||||
return &secureValueMetadataStorage{
|
||||
db: db,
|
||||
dialect: sqltemplate.DialectForDriver(db.DriverName()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// secureValueMetadataStorage is the actual implementation of the secure value metadata storage.
|
||||
// secureValueMetadataStorage is the actual implementation of the secure value (metadata) storage.
|
||||
type secureValueMetadataStorage struct {
|
||||
db db.DB
|
||||
accessClient claims.AccessClient
|
||||
db contracts.Database
|
||||
dialect sqltemplate.Dialect
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) Create(ctx context.Context, sv *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error) {
|
||||
return nil, nil
|
||||
sv.Status.Phase = secretv0alpha1.SecureValuePhasePending
|
||||
sv.Status.Message = "Creating secure value"
|
||||
|
||||
row, err := toCreateRow(sv, actorUID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("to create row: %w", err)
|
||||
}
|
||||
|
||||
req := createSecureValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Row: row,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlSecureValueCreate, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute template %q: %w", sqlSecureValueCreate.Name(), err)
|
||||
}
|
||||
|
||||
err = s.db.Transaction(ctx, func(ctx context.Context) error {
|
||||
if row.Keeper.Valid {
|
||||
// Validate before inserting that the chosen `keeper` exists.
|
||||
|
||||
// -- This is a copy of KeeperMetadataStore.read, which is not public at the moment, and is not defined in contract.KeeperMetadataStorage
|
||||
req := &readKeeper{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: row.Namespace,
|
||||
Name: row.Keeper.String,
|
||||
IsForUpdate: true,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlKeeperRead, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlKeeperRead.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.QueryContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting row: %w", err)
|
||||
}
|
||||
defer func() { _ = res.Close() }()
|
||||
|
||||
if !res.Next() {
|
||||
return contracts.ErrKeeperNotFound
|
||||
}
|
||||
}
|
||||
|
||||
res, err := s.db.ExecContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
if sql.IsRowAlreadyExistsError(err) {
|
||||
return fmt.Errorf("namespace=%+v name=%+v %w", sv.Namespace, sv.Name, contracts.ErrSecureValueAlreadyExists)
|
||||
}
|
||||
return fmt.Errorf("inserting row: %w", err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting rows affected: %w", err)
|
||||
}
|
||||
|
||||
if rowsAffected != 1 {
|
||||
return fmt.Errorf("expected 1 row affected, got %d for %s on %s", rowsAffected, row.Name, row.Namespace)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("db failure: %w", err)
|
||||
}
|
||||
|
||||
createdSecureValue, err := row.toKubernetes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert to kubernetes object: %w", err)
|
||||
}
|
||||
|
||||
return createdSecureValue, nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.Namespace, name string, opts contracts.ReadOpts) (*secretv0alpha1.SecureValue, error) {
|
||||
return nil, nil
|
||||
secureValue, err := s.read(ctx, namespace, name, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secureValueKub, err := secureValue.toKubernetes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert to kubernetes object: %w", err)
|
||||
}
|
||||
|
||||
return secureValueKub, nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) Update(ctx context.Context, newSecureValue *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error) {
|
||||
return nil, nil
|
||||
var newRow *secureValueDB
|
||||
|
||||
err := s.db.Transaction(ctx, func(ctx context.Context) error {
|
||||
read, err := s.read(ctx, xkube.Namespace(newSecureValue.Namespace), newSecureValue.Name, contracts.ReadOpts{ForUpdate: true})
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading secure value: %w", err)
|
||||
}
|
||||
|
||||
// TODO: Confirm the ExternalID should come from the read model.
|
||||
var updateErr error
|
||||
newRow, updateErr = toUpdateRow(&read, newSecureValue, actorUID, read.ExternalID)
|
||||
if updateErr != nil {
|
||||
return fmt.Errorf("model to update row: %w", updateErr)
|
||||
}
|
||||
|
||||
if newRow.Keeper.Valid {
|
||||
// Validate before updating that the new `keeper` exists.
|
||||
|
||||
// -- This is a copy of KeeperMetadataStore.read, which is not public at the moment, and is not defined in contract.KeeperMetadataStorage
|
||||
req := &readKeeper{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: newRow.Namespace,
|
||||
Name: newRow.Keeper.String,
|
||||
IsForUpdate: true,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlKeeperRead, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlKeeperRead.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.QueryContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting row: %w", err)
|
||||
}
|
||||
defer func() { _ = res.Close() }()
|
||||
|
||||
if !res.Next() {
|
||||
return contracts.ErrKeeperNotFound
|
||||
}
|
||||
}
|
||||
|
||||
req := &updateSecureValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: newRow.Namespace,
|
||||
Name: newRow.Name,
|
||||
Row: newRow,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlSecureValueUpdate, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlSecureValueUpdate.Name(), err)
|
||||
}
|
||||
|
||||
result, err := s.db.ExecContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("updating row: %w", err)
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting rows affected: %w", err)
|
||||
}
|
||||
|
||||
if rowsAffected != 1 {
|
||||
return fmt.Errorf("expected 1 row affected, got %d for %s on %s", rowsAffected, newRow.Name, newRow.Namespace)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("db failure: %w", err)
|
||||
}
|
||||
|
||||
secureValue, err := newRow.toKubernetes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert to kubernetes object: %w", err)
|
||||
}
|
||||
|
||||
return secureValue, nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) Delete(ctx context.Context, namespace xkube.Namespace, name string) error {
|
||||
req := deleteSecureValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlSecureValueDelete, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlSecureValueDelete.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.ExecContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting secure value row: %w", err)
|
||||
}
|
||||
|
||||
if rowsAffected, err := res.RowsAffected(); err != nil || rowsAffected != 1 {
|
||||
return fmt.Errorf("deleting secure value rowsAffected=%d error=%w", rowsAffected, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) List(ctx context.Context, namespace xkube.Namespace) (*secretv0alpha1.SecureValueList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (s *secureValueMetadataStorage) List(ctx context.Context, namespace xkube.Namespace) ([]secretv0alpha1.SecureValue, error) {
|
||||
req := listSecureValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) SetStatus(ctx context.Context, namespace xkube.Namespace, name string, status secretv0alpha1.SecureValueStatus) error {
|
||||
return nil
|
||||
q, err := sqltemplate.Execute(sqlSecureValueList, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute template %q: %w", sqlSecureValueList.Name(), err)
|
||||
}
|
||||
|
||||
rows, err := s.db.QueryContext(ctx, q, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing secure values: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
secureValues := make([]secretv0alpha1.SecureValue, 0)
|
||||
for rows.Next() {
|
||||
row := secureValueDB{}
|
||||
|
||||
err = rows.Scan(&row.GUID,
|
||||
&row.Name, &row.Namespace, &row.Annotations,
|
||||
&row.Labels,
|
||||
&row.Created, &row.CreatedBy,
|
||||
&row.Updated, &row.UpdatedBy,
|
||||
&row.Phase, &row.Message,
|
||||
&row.Description, &row.Keeper, &row.Decrypters,
|
||||
&row.Ref, &row.ExternalID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading secure value row: %w", err)
|
||||
}
|
||||
|
||||
secureValue, err := row.toKubernetes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert to kubernetes object: %w", err)
|
||||
}
|
||||
|
||||
secureValues = append(secureValues, *secureValue)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("read rows error: %w", err)
|
||||
}
|
||||
|
||||
return secureValues, nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) SetExternalID(ctx context.Context, namespace xkube.Namespace, name string, externalID contracts.ExternalID) error {
|
||||
req := updateExternalIdSecureValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
ExternalID: externalID.String(),
|
||||
}
|
||||
|
||||
q, err := sqltemplate.Execute(sqlSecureValueUpdateExternalId, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlSecureValueUpdateExternalId.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.ExecContext(ctx, q, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting secure value external id: namespace=%+v name=%+v externalID=%+v %w", namespace, name, externalID, err)
|
||||
}
|
||||
|
||||
// validate modified cound
|
||||
modifiedCount, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting updated rows update external id secure value: %w", err)
|
||||
}
|
||||
if modifiedCount > 1 {
|
||||
return fmt.Errorf("secureValueMetadataStorage.SetExternalID: modified more than one secret, this is a bug, check the where condition: modifiedCount=%d", modifiedCount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) SetStatus(ctx context.Context, namespace xkube.Namespace, name string, status secretv0alpha1.SecureValueStatus) error {
|
||||
req := updateStatusSecureValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
Phase: string(status.Phase),
|
||||
Message: status.Message,
|
||||
}
|
||||
|
||||
q, err := sqltemplate.Execute(sqlSecureValueUpdateStatus, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template %q: %w", sqlSecureValueUpdateStatus.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.ExecContext(ctx, q, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting secure value status to Succeeded id: namespace=%+v name=%+v %w", namespace, name, err)
|
||||
}
|
||||
|
||||
// validate modified cound
|
||||
modifiedCount, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting updated rows update status secure value: %w", err)
|
||||
}
|
||||
if modifiedCount > 1 {
|
||||
return fmt.Errorf("secureValueMetadataStorage.SetExternalID: modified more than one secret, this is a bug, check the where condition: modifiedCount=%d", modifiedCount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) ReadForDecrypt(ctx context.Context, namespace xkube.Namespace, name string) (*contracts.DecryptSecureValue, error) {
|
||||
return nil, nil
|
||||
req := readSecureValueForDecrypt{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlSecureValueReadForDecrypt, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute template %q: %w", sqlSecureValueReadForDecrypt.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.QueryContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading row: %w", err)
|
||||
}
|
||||
defer func() { _ = res.Close() }()
|
||||
|
||||
var row secureValueForDecrypt
|
||||
if !res.Next() {
|
||||
return nil, contracts.ErrSecureValueNotFound
|
||||
}
|
||||
if err := res.Scan(
|
||||
&row.Keeper, &row.Decrypters,
|
||||
&row.Ref, &row.ExternalID); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan secure value row: %w", err)
|
||||
}
|
||||
|
||||
if err := res.Err(); err != nil {
|
||||
return nil, fmt.Errorf("read rows error: %w", err)
|
||||
}
|
||||
|
||||
secureValue, err := row.toDecrypt()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert to kubernetes object: %w", err)
|
||||
}
|
||||
|
||||
return secureValue, nil
|
||||
}
|
||||
|
||||
func (s *secureValueMetadataStorage) read(ctx context.Context, namespace xkube.Namespace, name string, opts contracts.ReadOpts) (secureValueDB, error) {
|
||||
req := readSecureValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
IsForUpdate: opts.ForUpdate,
|
||||
}
|
||||
|
||||
query, err := sqltemplate.Execute(sqlSecureValueRead, req)
|
||||
if err != nil {
|
||||
return secureValueDB{}, fmt.Errorf("execute template %q: %w", sqlSecureValueRead.Name(), err)
|
||||
}
|
||||
|
||||
res, err := s.db.QueryContext(ctx, query, req.GetArgs()...)
|
||||
if err != nil {
|
||||
return secureValueDB{}, fmt.Errorf("reading row: %w", err)
|
||||
}
|
||||
defer func() { _ = res.Close() }()
|
||||
|
||||
var secureValue secureValueDB
|
||||
if !res.Next() {
|
||||
return secureValueDB{}, contracts.ErrSecureValueNotFound
|
||||
}
|
||||
|
||||
if err := res.Scan(
|
||||
&secureValue.GUID, &secureValue.Name, &secureValue.Namespace,
|
||||
&secureValue.Annotations, &secureValue.Labels,
|
||||
&secureValue.Created, &secureValue.CreatedBy,
|
||||
&secureValue.Updated, &secureValue.UpdatedBy,
|
||||
&secureValue.Phase, &secureValue.Message,
|
||||
&secureValue.Description, &secureValue.Keeper, &secureValue.Decrypters, &secureValue.Ref, &secureValue.ExternalID); err != nil {
|
||||
return secureValueDB{}, fmt.Errorf("failed to scan secure value row: %w", err)
|
||||
}
|
||||
|
||||
if err := res.Err(); err != nil {
|
||||
return secureValueDB{}, fmt.Errorf("read rows error: %w", err)
|
||||
}
|
||||
return secureValue, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,141 @@
|
|||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/storage/secret/database"
|
||||
"github.com/grafana/grafana/pkg/storage/secret/migrator"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func createTestKeeper(t *testing.T, ctx context.Context, keeperStorage contracts.KeeperMetadataStorage, name, namespace string) string {
|
||||
t.Helper()
|
||||
|
||||
testKeeper := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
Description: "test keeper description",
|
||||
AWS: &secretv0alpha1.AWSKeeperConfig{},
|
||||
},
|
||||
}
|
||||
testKeeper.Name = name
|
||||
testKeeper.Namespace = namespace
|
||||
|
||||
// Create the keeper
|
||||
_, err := keeperStorage.Create(ctx, testKeeper, "testuser")
|
||||
require.NoError(t, err)
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
func Test_SecureValueMetadataStorage_CreateAndRead(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New()))
|
||||
db := database.ProvideDatabase(testDB)
|
||||
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform)
|
||||
|
||||
// Initialize the secure value storage
|
||||
secureValueStorage, err := ProvideSecureValueMetadataStorage(db, features)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Initialize the keeper storage
|
||||
keeperStorage, err := ProvideKeeperMetadataStorage(db, features)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("create and read a secure value", func(t *testing.T) {
|
||||
// First create a keeper
|
||||
keeperName := createTestKeeper(t, ctx, keeperStorage, "test-keeper", "default")
|
||||
|
||||
// Create a test secure value
|
||||
testSecureValue := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Description: "test description",
|
||||
Value: "test-value",
|
||||
Keeper: &keeperName,
|
||||
},
|
||||
}
|
||||
testSecureValue.Name = "sv-test"
|
||||
testSecureValue.Namespace = "default"
|
||||
|
||||
// Create the secure value
|
||||
createdSecureValue, err := secureValueStorage.Create(ctx, testSecureValue, "testuser")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, createdSecureValue)
|
||||
require.Equal(t, "sv-test", createdSecureValue.Name)
|
||||
require.Equal(t, "default", createdSecureValue.Namespace)
|
||||
require.Equal(t, "test description", createdSecureValue.Spec.Description)
|
||||
require.Equal(t, keeperName, *createdSecureValue.Spec.Keeper)
|
||||
require.Equal(t, secretv0alpha1.SecureValuePhasePending, createdSecureValue.Status.Phase)
|
||||
|
||||
// Read the secure value back
|
||||
readSecureValue, err := secureValueStorage.Read(ctx, xkube.Namespace("default"), "sv-test", contracts.ReadOpts{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, readSecureValue)
|
||||
require.Equal(t, "sv-test", readSecureValue.Name)
|
||||
require.Equal(t, "default", readSecureValue.Namespace)
|
||||
require.Equal(t, "test description", readSecureValue.Spec.Description)
|
||||
require.Equal(t, keeperName, *readSecureValue.Spec.Keeper)
|
||||
require.Equal(t, secretv0alpha1.SecureValuePhasePending, readSecureValue.Status.Phase)
|
||||
|
||||
// List secure values and verify our value is in the list
|
||||
secureValues, err := secureValueStorage.List(ctx, xkube.Namespace("default"))
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, secureValues)
|
||||
|
||||
// Find our secure value in the list
|
||||
var found bool
|
||||
for _, sv := range secureValues {
|
||||
if sv.Name == "sv-test" {
|
||||
found = true
|
||||
require.Equal(t, "default", sv.Namespace)
|
||||
require.Equal(t, "test description", sv.Spec.Description)
|
||||
require.Equal(t, keeperName, *sv.Spec.Keeper)
|
||||
require.Equal(t, secretv0alpha1.SecureValuePhasePending, sv.Status.Phase)
|
||||
break
|
||||
}
|
||||
}
|
||||
require.True(t, found, "secure value not found in list")
|
||||
})
|
||||
|
||||
t.Run("create, read, delete and verify secure value", func(t *testing.T) {
|
||||
// First create a keeper
|
||||
keeperName := createTestKeeper(t, ctx, keeperStorage, "test-keeper-2", "default")
|
||||
|
||||
// Create a test secure value
|
||||
testSecureValue := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Description: "test description 2",
|
||||
Value: "test-value-2",
|
||||
Keeper: &keeperName,
|
||||
},
|
||||
}
|
||||
testSecureValue.Name = "sv-test-2"
|
||||
testSecureValue.Namespace = "default"
|
||||
|
||||
// Create the secure value
|
||||
createdSecureValue, err := secureValueStorage.Create(ctx, testSecureValue, "testuser")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, createdSecureValue)
|
||||
|
||||
// Read the secure value to verify it exists
|
||||
readSecureValue, err := secureValueStorage.Read(ctx, xkube.Namespace("default"), "sv-test-2", contracts.ReadOpts{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, readSecureValue)
|
||||
require.Equal(t, "sv-test-2", readSecureValue.Name)
|
||||
|
||||
// Delete the secure value
|
||||
err = secureValueStorage.Delete(ctx, xkube.Namespace("default"), "sv-test-2")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Try to read the deleted secure value - should return error
|
||||
_, err = secureValueStorage.Read(ctx, xkube.Namespace("default"), "sv-test-2", contracts.ReadOpts{})
|
||||
require.Error(t, err)
|
||||
require.Equal(t, contracts.ErrSecureValueNotFound, err)
|
||||
})
|
||||
}
|
||||
35
pkg/storage/secret/metadata/testdata/mysql--secure_value_create-create-not-null.sql
vendored
Executable file
35
pkg/storage/secret/metadata/testdata/mysql--secure_value_create-create-not-null.sql
vendored
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
INSERT INTO `secret_secure_value` (
|
||||
`guid`,
|
||||
`name`,
|
||||
`namespace`,
|
||||
`annotations`,
|
||||
`labels`,
|
||||
`created`,
|
||||
`created_by`,
|
||||
`updated`,
|
||||
`updated_by`,
|
||||
`status_phase`,
|
||||
`status_message`,
|
||||
`description`,
|
||||
`keeper`,
|
||||
`decrypters`,
|
||||
`ref`,
|
||||
`external_id`
|
||||
) VALUES (
|
||||
'abc',
|
||||
'name',
|
||||
'ns',
|
||||
'{"x":"XXXX"}',
|
||||
'{"a":"AAA", "b", "BBBB"}',
|
||||
1234,
|
||||
'user:ryan',
|
||||
5678,
|
||||
'user:cameron',
|
||||
'creating',
|
||||
'message_test',
|
||||
'description',
|
||||
'keeper_test',
|
||||
'decrypters_test',
|
||||
'ref_test',
|
||||
'extId'
|
||||
);
|
||||
27
pkg/storage/secret/metadata/testdata/mysql--secure_value_create-create-null.sql
vendored
Executable file
27
pkg/storage/secret/metadata/testdata/mysql--secure_value_create-create-null.sql
vendored
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
INSERT INTO `secret_secure_value` (
|
||||
`guid`,
|
||||
`name`,
|
||||
`namespace`,
|
||||
`annotations`,
|
||||
`labels`,
|
||||
`created`,
|
||||
`created_by`,
|
||||
`updated`,
|
||||
`updated_by`,
|
||||
`status_phase`,
|
||||
`description`,
|
||||
`external_id`
|
||||
) VALUES (
|
||||
'abc',
|
||||
'name',
|
||||
'ns',
|
||||
'{"x":"XXXX"}',
|
||||
'{"a":"AAA", "b", "BBBB"}',
|
||||
1234,
|
||||
'user:ryan',
|
||||
5678,
|
||||
'user:cameron',
|
||||
'creating',
|
||||
'description',
|
||||
'extId'
|
||||
);
|
||||
4
pkg/storage/secret/metadata/testdata/mysql--secure_value_delete-delete.sql
vendored
Executable file
4
pkg/storage/secret/metadata/testdata/mysql--secure_value_delete-delete.sql
vendored
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
DELETE FROM `secret_secure_value`
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
;
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
SELECT
|
||||
`guid`,
|
||||
`name`,
|
||||
`namespace`,
|
||||
`annotations`,
|
||||
`labels`,
|
||||
`created`,
|
||||
`created_by`,
|
||||
`updated`,
|
||||
`updated_by`,
|
||||
`status_phase`,
|
||||
`status_message`,
|
||||
`description`,
|
||||
`keeper`,
|
||||
`decrypters`,
|
||||
`ref`,
|
||||
`external_id`
|
||||
FROM
|
||||
`secret_secure_value`
|
||||
WHERE `namespace` = 'ns'
|
||||
ORDER BY `updated` DESC
|
||||
;
|
||||
23
pkg/storage/secret/metadata/testdata/mysql--secure_value_read-read-for-update.sql
vendored
Executable file
23
pkg/storage/secret/metadata/testdata/mysql--secure_value_read-read-for-update.sql
vendored
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
SELECT
|
||||
`guid`,
|
||||
`name`,
|
||||
`namespace`,
|
||||
`annotations`,
|
||||
`labels`,
|
||||
`created`,
|
||||
`created_by`,
|
||||
`updated`,
|
||||
`updated_by`,
|
||||
`status_phase`,
|
||||
`status_message`,
|
||||
`description`,
|
||||
`keeper`,
|
||||
`decrypters`,
|
||||
`ref`,
|
||||
`external_id`
|
||||
FROM
|
||||
`secret_secure_value`
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
FOR UPDATE
|
||||
;
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
SELECT
|
||||
`guid`,
|
||||
`name`,
|
||||
`namespace`,
|
||||
`annotations`,
|
||||
`labels`,
|
||||
`created`,
|
||||
`created_by`,
|
||||
`updated`,
|
||||
`updated_by`,
|
||||
`status_phase`,
|
||||
`status_message`,
|
||||
`description`,
|
||||
`keeper`,
|
||||
`decrypters`,
|
||||
`ref`,
|
||||
`external_id`
|
||||
FROM
|
||||
`secret_secure_value`
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
;
|
||||
10
pkg/storage/secret/metadata/testdata/mysql--secure_value_read_for_decrypt-read-for-decrypt.sql
vendored
Executable file
10
pkg/storage/secret/metadata/testdata/mysql--secure_value_read_for_decrypt-read-for-decrypt.sql
vendored
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
SELECT
|
||||
`keeper`,
|
||||
`decrypters`,
|
||||
`ref`,
|
||||
`external_id`
|
||||
FROM
|
||||
`secret_secure_value`
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
;
|
||||
22
pkg/storage/secret/metadata/testdata/mysql--secure_value_update-update-not-null.sql
vendored
Executable file
22
pkg/storage/secret/metadata/testdata/mysql--secure_value_update-update-not-null.sql
vendored
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
UPDATE
|
||||
`secret_secure_value`
|
||||
SET
|
||||
`guid` = 'abc',
|
||||
`name` = 'name',
|
||||
`namespace` = 'ns',
|
||||
`annotations` = '{"x":"XXXX"}',
|
||||
`labels` = '{"a":"AAA", "b", "BBBB"}',
|
||||
`created` = 1234,
|
||||
`created_by` = 'user:ryan',
|
||||
`updated` = 5678,
|
||||
`updated_by` = 'user:cameron',
|
||||
`status_phase` = 'creating',
|
||||
`status_message` = 'message_test',
|
||||
`description` = 'description',
|
||||
`keeper` = 'keeper_test',
|
||||
`decrypters` = 'decrypters_test',
|
||||
`ref` = 'ref_test',
|
||||
`external_id` = 'extId'
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
;
|
||||
18
pkg/storage/secret/metadata/testdata/mysql--secure_value_update-update-null.sql
vendored
Executable file
18
pkg/storage/secret/metadata/testdata/mysql--secure_value_update-update-null.sql
vendored
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
UPDATE
|
||||
`secret_secure_value`
|
||||
SET
|
||||
`guid` = 'abc',
|
||||
`name` = 'name',
|
||||
`namespace` = 'ns',
|
||||
`annotations` = '{"x":"XXXX"}',
|
||||
`labels` = '{"a":"AAA", "b", "BBBB"}',
|
||||
`created` = 1234,
|
||||
`created_by` = 'user:ryan',
|
||||
`updated` = 5678,
|
||||
`updated_by` = 'user:cameron',
|
||||
`status_phase` = 'creating',
|
||||
`description` = 'description',
|
||||
`external_id` = 'extId'
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
;
|
||||
7
pkg/storage/secret/metadata/testdata/mysql--secure_value_updateExternalId-updateExternalId.sql
vendored
Executable file
7
pkg/storage/secret/metadata/testdata/mysql--secure_value_updateExternalId-updateExternalId.sql
vendored
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
UPDATE
|
||||
`secret_secure_value`
|
||||
SET
|
||||
`external_id` = 'extId'
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
;
|
||||
8
pkg/storage/secret/metadata/testdata/mysql--secure_value_updateStatus-updateStatus.sql
vendored
Executable file
8
pkg/storage/secret/metadata/testdata/mysql--secure_value_updateStatus-updateStatus.sql
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
UPDATE
|
||||
`secret_secure_value`
|
||||
SET
|
||||
`status_phase` = 'Succeeded',
|
||||
`status_message` = 'message-1'
|
||||
WHERE `namespace` = 'ns' AND
|
||||
`name` = 'name'
|
||||
;
|
||||
35
pkg/storage/secret/metadata/testdata/postgres--secure_value_create-create-not-null.sql
vendored
Executable file
35
pkg/storage/secret/metadata/testdata/postgres--secure_value_create-create-not-null.sql
vendored
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
INSERT INTO "secret_secure_value" (
|
||||
"guid",
|
||||
"name",
|
||||
"namespace",
|
||||
"annotations",
|
||||
"labels",
|
||||
"created",
|
||||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"status_message",
|
||||
"description",
|
||||
"keeper",
|
||||
"decrypters",
|
||||
"ref",
|
||||
"external_id"
|
||||
) VALUES (
|
||||
'abc',
|
||||
'name',
|
||||
'ns',
|
||||
'{"x":"XXXX"}',
|
||||
'{"a":"AAA", "b", "BBBB"}',
|
||||
1234,
|
||||
'user:ryan',
|
||||
5678,
|
||||
'user:cameron',
|
||||
'creating',
|
||||
'message_test',
|
||||
'description',
|
||||
'keeper_test',
|
||||
'decrypters_test',
|
||||
'ref_test',
|
||||
'extId'
|
||||
);
|
||||
27
pkg/storage/secret/metadata/testdata/postgres--secure_value_create-create-null.sql
vendored
Executable file
27
pkg/storage/secret/metadata/testdata/postgres--secure_value_create-create-null.sql
vendored
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
INSERT INTO "secret_secure_value" (
|
||||
"guid",
|
||||
"name",
|
||||
"namespace",
|
||||
"annotations",
|
||||
"labels",
|
||||
"created",
|
||||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"description",
|
||||
"external_id"
|
||||
) VALUES (
|
||||
'abc',
|
||||
'name',
|
||||
'ns',
|
||||
'{"x":"XXXX"}',
|
||||
'{"a":"AAA", "b", "BBBB"}',
|
||||
1234,
|
||||
'user:ryan',
|
||||
5678,
|
||||
'user:cameron',
|
||||
'creating',
|
||||
'description',
|
||||
'extId'
|
||||
);
|
||||
4
pkg/storage/secret/metadata/testdata/postgres--secure_value_delete-delete.sql
vendored
Executable file
4
pkg/storage/secret/metadata/testdata/postgres--secure_value_delete-delete.sql
vendored
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
DELETE FROM "secret_secure_value"
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
||||
22
pkg/storage/secret/metadata/testdata/postgres--secure_value_list-list.sql
vendored
Executable file
22
pkg/storage/secret/metadata/testdata/postgres--secure_value_list-list.sql
vendored
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
SELECT
|
||||
"guid",
|
||||
"name",
|
||||
"namespace",
|
||||
"annotations",
|
||||
"labels",
|
||||
"created",
|
||||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"status_message",
|
||||
"description",
|
||||
"keeper",
|
||||
"decrypters",
|
||||
"ref",
|
||||
"external_id"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE "namespace" = 'ns'
|
||||
ORDER BY "updated" DESC
|
||||
;
|
||||
23
pkg/storage/secret/metadata/testdata/postgres--secure_value_read-read-for-update.sql
vendored
Executable file
23
pkg/storage/secret/metadata/testdata/postgres--secure_value_read-read-for-update.sql
vendored
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
SELECT
|
||||
"guid",
|
||||
"name",
|
||||
"namespace",
|
||||
"annotations",
|
||||
"labels",
|
||||
"created",
|
||||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"status_message",
|
||||
"description",
|
||||
"keeper",
|
||||
"decrypters",
|
||||
"ref",
|
||||
"external_id"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
FOR UPDATE
|
||||
;
|
||||
22
pkg/storage/secret/metadata/testdata/postgres--secure_value_read-read.sql
vendored
Executable file
22
pkg/storage/secret/metadata/testdata/postgres--secure_value_read-read.sql
vendored
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
SELECT
|
||||
"guid",
|
||||
"name",
|
||||
"namespace",
|
||||
"annotations",
|
||||
"labels",
|
||||
"created",
|
||||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"status_message",
|
||||
"description",
|
||||
"keeper",
|
||||
"decrypters",
|
||||
"ref",
|
||||
"external_id"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
||||
10
pkg/storage/secret/metadata/testdata/postgres--secure_value_read_for_decrypt-read-for-decrypt.sql
vendored
Executable file
10
pkg/storage/secret/metadata/testdata/postgres--secure_value_read_for_decrypt-read-for-decrypt.sql
vendored
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
SELECT
|
||||
"keeper",
|
||||
"decrypters",
|
||||
"ref",
|
||||
"external_id"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
||||
22
pkg/storage/secret/metadata/testdata/postgres--secure_value_update-update-not-null.sql
vendored
Executable file
22
pkg/storage/secret/metadata/testdata/postgres--secure_value_update-update-not-null.sql
vendored
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
UPDATE
|
||||
"secret_secure_value"
|
||||
SET
|
||||
"guid" = 'abc',
|
||||
"name" = 'name',
|
||||
"namespace" = 'ns',
|
||||
"annotations" = '{"x":"XXXX"}',
|
||||
"labels" = '{"a":"AAA", "b", "BBBB"}',
|
||||
"created" = 1234,
|
||||
"created_by" = 'user:ryan',
|
||||
"updated" = 5678,
|
||||
"updated_by" = 'user:cameron',
|
||||
"status_phase" = 'creating',
|
||||
"status_message" = 'message_test',
|
||||
"description" = 'description',
|
||||
"keeper" = 'keeper_test',
|
||||
"decrypters" = 'decrypters_test',
|
||||
"ref" = 'ref_test',
|
||||
"external_id" = 'extId'
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
||||
18
pkg/storage/secret/metadata/testdata/postgres--secure_value_update-update-null.sql
vendored
Executable file
18
pkg/storage/secret/metadata/testdata/postgres--secure_value_update-update-null.sql
vendored
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
UPDATE
|
||||
"secret_secure_value"
|
||||
SET
|
||||
"guid" = 'abc',
|
||||
"name" = 'name',
|
||||
"namespace" = 'ns',
|
||||
"annotations" = '{"x":"XXXX"}',
|
||||
"labels" = '{"a":"AAA", "b", "BBBB"}',
|
||||
"created" = 1234,
|
||||
"created_by" = 'user:ryan',
|
||||
"updated" = 5678,
|
||||
"updated_by" = 'user:cameron',
|
||||
"status_phase" = 'creating',
|
||||
"description" = 'description',
|
||||
"external_id" = 'extId'
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
||||
7
pkg/storage/secret/metadata/testdata/postgres--secure_value_updateExternalId-updateExternalId.sql
vendored
Executable file
7
pkg/storage/secret/metadata/testdata/postgres--secure_value_updateExternalId-updateExternalId.sql
vendored
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
UPDATE
|
||||
"secret_secure_value"
|
||||
SET
|
||||
"external_id" = 'extId'
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
||||
8
pkg/storage/secret/metadata/testdata/postgres--secure_value_updateStatus-updateStatus.sql
vendored
Executable file
8
pkg/storage/secret/metadata/testdata/postgres--secure_value_updateStatus-updateStatus.sql
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
UPDATE
|
||||
"secret_secure_value"
|
||||
SET
|
||||
"status_phase" = 'Succeeded',
|
||||
"status_message" = 'message-1'
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
||||
35
pkg/storage/secret/metadata/testdata/sqlite--secure_value_create-create-not-null.sql
vendored
Executable file
35
pkg/storage/secret/metadata/testdata/sqlite--secure_value_create-create-not-null.sql
vendored
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
INSERT INTO "secret_secure_value" (
|
||||
"guid",
|
||||
"name",
|
||||
"namespace",
|
||||
"annotations",
|
||||
"labels",
|
||||
"created",
|
||||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"status_message",
|
||||
"description",
|
||||
"keeper",
|
||||
"decrypters",
|
||||
"ref",
|
||||
"external_id"
|
||||
) VALUES (
|
||||
'abc',
|
||||
'name',
|
||||
'ns',
|
||||
'{"x":"XXXX"}',
|
||||
'{"a":"AAA", "b", "BBBB"}',
|
||||
1234,
|
||||
'user:ryan',
|
||||
5678,
|
||||
'user:cameron',
|
||||
'creating',
|
||||
'message_test',
|
||||
'description',
|
||||
'keeper_test',
|
||||
'decrypters_test',
|
||||
'ref_test',
|
||||
'extId'
|
||||
);
|
||||
27
pkg/storage/secret/metadata/testdata/sqlite--secure_value_create-create-null.sql
vendored
Executable file
27
pkg/storage/secret/metadata/testdata/sqlite--secure_value_create-create-null.sql
vendored
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
INSERT INTO "secret_secure_value" (
|
||||
"guid",
|
||||
"name",
|
||||
"namespace",
|
||||
"annotations",
|
||||
"labels",
|
||||
"created",
|
||||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"description",
|
||||
"external_id"
|
||||
) VALUES (
|
||||
'abc',
|
||||
'name',
|
||||
'ns',
|
||||
'{"x":"XXXX"}',
|
||||
'{"a":"AAA", "b", "BBBB"}',
|
||||
1234,
|
||||
'user:ryan',
|
||||
5678,
|
||||
'user:cameron',
|
||||
'creating',
|
||||
'description',
|
||||
'extId'
|
||||
);
|
||||
4
pkg/storage/secret/metadata/testdata/sqlite--secure_value_delete-delete.sql
vendored
Executable file
4
pkg/storage/secret/metadata/testdata/sqlite--secure_value_delete-delete.sql
vendored
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
DELETE FROM "secret_secure_value"
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
SELECT
|
||||
"guid",
|
||||
"name",
|
||||
"namespace",
|
||||
"annotations",
|
||||
"labels",
|
||||
"created",
|
||||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"status_message",
|
||||
"description",
|
||||
"keeper",
|
||||
"decrypters",
|
||||
"ref",
|
||||
"external_id"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE "namespace" = 'ns'
|
||||
ORDER BY "updated" DESC
|
||||
;
|
||||
22
pkg/storage/secret/metadata/testdata/sqlite--secure_value_read-read-for-update.sql
vendored
Executable file
22
pkg/storage/secret/metadata/testdata/sqlite--secure_value_read-read-for-update.sql
vendored
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
SELECT
|
||||
"guid",
|
||||
"name",
|
||||
"namespace",
|
||||
"annotations",
|
||||
"labels",
|
||||
"created",
|
||||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"status_message",
|
||||
"description",
|
||||
"keeper",
|
||||
"decrypters",
|
||||
"ref",
|
||||
"external_id"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
SELECT
|
||||
"guid",
|
||||
"name",
|
||||
"namespace",
|
||||
"annotations",
|
||||
"labels",
|
||||
"created",
|
||||
"created_by",
|
||||
"updated",
|
||||
"updated_by",
|
||||
"status_phase",
|
||||
"status_message",
|
||||
"description",
|
||||
"keeper",
|
||||
"decrypters",
|
||||
"ref",
|
||||
"external_id"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
||||
10
pkg/storage/secret/metadata/testdata/sqlite--secure_value_read_for_decrypt-read-for-decrypt.sql
vendored
Executable file
10
pkg/storage/secret/metadata/testdata/sqlite--secure_value_read_for_decrypt-read-for-decrypt.sql
vendored
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
SELECT
|
||||
"keeper",
|
||||
"decrypters",
|
||||
"ref",
|
||||
"external_id"
|
||||
FROM
|
||||
"secret_secure_value"
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
||||
22
pkg/storage/secret/metadata/testdata/sqlite--secure_value_update-update-not-null.sql
vendored
Executable file
22
pkg/storage/secret/metadata/testdata/sqlite--secure_value_update-update-not-null.sql
vendored
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
UPDATE
|
||||
"secret_secure_value"
|
||||
SET
|
||||
"guid" = 'abc',
|
||||
"name" = 'name',
|
||||
"namespace" = 'ns',
|
||||
"annotations" = '{"x":"XXXX"}',
|
||||
"labels" = '{"a":"AAA", "b", "BBBB"}',
|
||||
"created" = 1234,
|
||||
"created_by" = 'user:ryan',
|
||||
"updated" = 5678,
|
||||
"updated_by" = 'user:cameron',
|
||||
"status_phase" = 'creating',
|
||||
"status_message" = 'message_test',
|
||||
"description" = 'description',
|
||||
"keeper" = 'keeper_test',
|
||||
"decrypters" = 'decrypters_test',
|
||||
"ref" = 'ref_test',
|
||||
"external_id" = 'extId'
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
||||
18
pkg/storage/secret/metadata/testdata/sqlite--secure_value_update-update-null.sql
vendored
Executable file
18
pkg/storage/secret/metadata/testdata/sqlite--secure_value_update-update-null.sql
vendored
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
UPDATE
|
||||
"secret_secure_value"
|
||||
SET
|
||||
"guid" = 'abc',
|
||||
"name" = 'name',
|
||||
"namespace" = 'ns',
|
||||
"annotations" = '{"x":"XXXX"}',
|
||||
"labels" = '{"a":"AAA", "b", "BBBB"}',
|
||||
"created" = 1234,
|
||||
"created_by" = 'user:ryan',
|
||||
"updated" = 5678,
|
||||
"updated_by" = 'user:cameron',
|
||||
"status_phase" = 'creating',
|
||||
"description" = 'description',
|
||||
"external_id" = 'extId'
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
||||
7
pkg/storage/secret/metadata/testdata/sqlite--secure_value_updateExternalId-updateExternalId.sql
vendored
Executable file
7
pkg/storage/secret/metadata/testdata/sqlite--secure_value_updateExternalId-updateExternalId.sql
vendored
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
UPDATE
|
||||
"secret_secure_value"
|
||||
SET
|
||||
"external_id" = 'extId'
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
||||
8
pkg/storage/secret/metadata/testdata/sqlite--secure_value_updateStatus-updateStatus.sql
vendored
Executable file
8
pkg/storage/secret/metadata/testdata/sqlite--secure_value_updateStatus-updateStatus.sql
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
UPDATE
|
||||
"secret_secure_value"
|
||||
SET
|
||||
"status_phase" = 'Succeeded',
|
||||
"status_message" = 'message-1'
|
||||
WHERE "namespace" = 'ns' AND
|
||||
"name" = 'name'
|
||||
;
|
||||
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
const (
|
||||
TableNameKeeper = "secret_keeper"
|
||||
TableNameSecureValue = "secret_secure_value"
|
||||
TableNameSecureValueOutbox = "secret_secure_value_outbox"
|
||||
TableNameEncryptedValue = "secret_encrypted_value"
|
||||
)
|
||||
|
|
@ -69,6 +70,36 @@ func (*SecretDB) AddMigration(mg *migrator.Migrator) {
|
|||
},
|
||||
})
|
||||
|
||||
tables = append(tables, migrator.Table{
|
||||
Name: TableNameSecureValue,
|
||||
Columns: []*migrator.Column{
|
||||
// Kubernetes Metadata
|
||||
{Name: "guid", Type: migrator.DB_NVarchar, Length: 36, IsPrimaryKey: true}, // Fixed size of a UUID.
|
||||
{Name: "name", Type: migrator.DB_NVarchar, Length: 253, Nullable: false}, // Limit enforced by K8s.
|
||||
{Name: "namespace", Type: migrator.DB_NVarchar, Length: 253, Nullable: false}, // Limit enforced by K8s.
|
||||
{Name: "annotations", Type: migrator.DB_Text, Nullable: true},
|
||||
{Name: "labels", Type: migrator.DB_Text, Nullable: true},
|
||||
{Name: "created", Type: migrator.DB_BigInt, Nullable: false},
|
||||
{Name: "created_by", Type: migrator.DB_Text, Nullable: false},
|
||||
{Name: "updated", Type: migrator.DB_BigInt, Nullable: false}, // Used as RV (ResourceVersion)
|
||||
{Name: "updated_by", Type: migrator.DB_Text, Nullable: false},
|
||||
|
||||
// Kubernetes Status
|
||||
{Name: "status_phase", Type: migrator.DB_Text, Nullable: false},
|
||||
{Name: "status_message", Type: migrator.DB_Text, Nullable: true},
|
||||
|
||||
// Spec
|
||||
{Name: "description", Type: migrator.DB_NVarchar, Length: 253, Nullable: false}, // Chosen arbitrarily, but should be enough.
|
||||
{Name: "keeper", Type: migrator.DB_NVarchar, Length: 253, Nullable: true}, // Keeper name, if not set, use default keeper.
|
||||
{Name: "decrypters", Type: migrator.DB_Text, Nullable: true},
|
||||
{Name: "ref", Type: migrator.DB_NVarchar, Length: 1024, Nullable: true}, // Reference to third-party storage secret path.Chosen arbitrarily, but should be enough.
|
||||
{Name: "external_id", Type: migrator.DB_Text, Nullable: false},
|
||||
},
|
||||
Indices: []*migrator.Index{
|
||||
{Cols: []string{"namespace", "name"}, Type: migrator.UniqueIndex},
|
||||
},
|
||||
})
|
||||
|
||||
tables = append(tables, migrator.Table{
|
||||
Name: TableNameEncryptedValue,
|
||||
Columns: []*migrator.Column{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
apiVersion: secret.grafana.app/v0alpha1
|
||||
kind: SecureValue
|
||||
metadata:
|
||||
annotations:
|
||||
xx: XXX
|
||||
yy: YYY
|
||||
labels:
|
||||
aa: AAA
|
||||
bb: BBB
|
||||
spec:
|
||||
description: This is a secret
|
||||
value: this is super duper secure
|
||||
decrypters:
|
||||
- actor_k6
|
||||
- actor_synthetic-monitoring
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
apiVersion: secret.grafana.app/v0alpha1
|
||||
kind: SecureValue
|
||||
metadata:
|
||||
annotations:
|
||||
xx: XXX
|
||||
yy: YYY
|
||||
labels:
|
||||
aa: AAA
|
||||
bb: BBB
|
||||
spec:
|
||||
description: XYZ value
|
||||
keeper: my-keeper-1
|
||||
value: super duper secure
|
||||
decrypters:
|
||||
- actor_k6
|
||||
- actor_synthetic-monitoring
|
||||
Loading…
Reference in New Issue