Merge branch 'main' into irenarindos-env-file-controllerconfig
This commit is contained in:
12 files changed
+505
-173
No files matched your search
@@ -8,6 +8,8 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
|
||||
* config: The `description` field for controllers now supports being set
|
||||
from environment variables or a file on disk
|
||||
([PR](https://github.com/hashicorp/boundary/pull/1766))
|
||||
* config: Add support for reading worker tags off of environment variables
|
||||
as well as files. ([PR](https://github.com/hashicorp/boundary/pull/1758))
|
||||
* config: Add support for go-sockaddr templates to Worker and Controller
|
||||
addresses. ([PR](https://github.com/hashicorp/boundary/pull/1731))
|
||||
* host: Plugin-based host catalogs will now schedule updates for all
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -157,9 +158,10 @@ type Worker struct {
|
||||
|
||||
// We use a raw interface for parsing so that people can use JSON-like
|
||||
// syntax that maps directly to the filter input or possibly more familiar
|
||||
// key=value syntax. This is trued up in the Parse function below.
|
||||
TagsRaw interface{} `hcl:"tags"`
|
||||
// key=value syntax, as well as accepting a string denoting an env or file
|
||||
// pointer. This is trued up in the Parse function below.
|
||||
Tags map[string][]string `hcl:"-"`
|
||||
TagsRaw interface{} `hcl:"tags"`
|
||||
|
||||
// StatusGracePeriod represents the period of time (as a duration) that the
|
||||
// worker will wait before disconnecting connections if it cannot make a
|
||||
@@ -344,12 +346,60 @@ func Parse(d string) (*Config, error) {
|
||||
if !strutil.Printable(result.Worker.Name) {
|
||||
return nil, errors.New("Worker name contains non-printable characters")
|
||||
}
|
||||
|
||||
if result.Worker.TagsRaw != nil {
|
||||
switch t := result.Worker.TagsRaw.(type) {
|
||||
// We allow `tags` to be a simple string containing a URL with schema.
|
||||
// See: https://github.com/hashicorp/go-secure-stdlib/blob/main/parseutil/parsepath.go
|
||||
case string:
|
||||
rawTags, err := parseutil.ParsePath(t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing worker tags: %w", err)
|
||||
}
|
||||
|
||||
var temp []map[string]interface{}
|
||||
err = hcl.Decode(&temp, rawTags)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error decoding raw worker tags: %w", err)
|
||||
}
|
||||
|
||||
if err := mapstructure.WeakDecode(temp, &result.Worker.Tags); err != nil {
|
||||
return nil, fmt.Errorf("Error decoding the worker's tags: %w", err)
|
||||
}
|
||||
|
||||
// HCL allows multiple labeled blocks with the same name, turning it
|
||||
// into a slice of maps, hence the slice here. This format is the
|
||||
// one that ends up matching the JSON that we use in the expression.
|
||||
case []map[string]interface{}:
|
||||
for _, m := range t {
|
||||
for k, v := range m {
|
||||
// We allow the user to pass in only the keys in HCL, and
|
||||
// then set the values to point to a URL with schema.
|
||||
valStr, ok := v.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
parsed, err := parseutil.ParsePath(valStr)
|
||||
if err != nil && !errors.Is(err, parseutil.ErrNotAUrl) {
|
||||
return nil, fmt.Errorf("Error parsing worker tag values: %w", err)
|
||||
}
|
||||
if valStr == parsed {
|
||||
// Nothing was found, ignore.
|
||||
// WeakDecode will still parse it though as we
|
||||
// don't know if this could be a valid tag.
|
||||
continue
|
||||
}
|
||||
|
||||
var tags []string
|
||||
err = json.Unmarshal([]byte(parsed), &tags)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error unmarshalling env var/file contents: %w", err)
|
||||
}
|
||||
m[k] = tags
|
||||
}
|
||||
}
|
||||
|
||||
if err := mapstructure.WeakDecode(t, &result.Worker.Tags); err != nil {
|
||||
return nil, fmt.Errorf("Error decoding the worker's %q section: %w", "tags", err)
|
||||
}
|
||||
@@ -381,6 +431,7 @@ func Parse(d string) (*Config, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range result.Worker.Tags {
|
||||
if k != strings.ToLower(k) {
|
||||
return nil, fmt.Errorf("Tag key %q is not all lower-case letters", k)
|
||||
|
||||
@@ -337,6 +337,292 @@ func TestParsingName(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkerTags(t *testing.T) {
|
||||
defaultStateFn := func(t *testing.T, tags string) {
|
||||
t.Setenv("BOUNDARY_WORKER_TAGS", tags)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
stateFn func(t *testing.T, tags string)
|
||||
actualTags string
|
||||
expWorkerTags map[string][]string
|
||||
expErr bool
|
||||
expErrStr string
|
||||
}{
|
||||
{
|
||||
name: "tags in HCL",
|
||||
in: `
|
||||
worker {
|
||||
tags {
|
||||
type = ["dev", "local"]
|
||||
typetwo = "devtwo"
|
||||
}
|
||||
}`,
|
||||
expWorkerTags: map[string][]string{
|
||||
"type": {"dev", "local"},
|
||||
"typetwo": {"devtwo"},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "tags in HCL key=value",
|
||||
in: `
|
||||
worker {
|
||||
tags = ["type=dev", "type=local", "typetwo=devtwo"]
|
||||
}
|
||||
`,
|
||||
expWorkerTags: map[string][]string{
|
||||
"type": {"dev", "local"},
|
||||
"typetwo": {"devtwo"},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "no tags",
|
||||
in: `
|
||||
worker {
|
||||
name = "testworker"
|
||||
}
|
||||
`,
|
||||
expWorkerTags: nil,
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty tags",
|
||||
in: `
|
||||
worker {
|
||||
name = "testworker"
|
||||
tags = {}
|
||||
}
|
||||
`,
|
||||
expWorkerTags: map[string][]string{},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty tags 2",
|
||||
in: `
|
||||
worker {
|
||||
name = "testworker"
|
||||
tags = []
|
||||
}
|
||||
`,
|
||||
expWorkerTags: map[string][]string{},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty str",
|
||||
in: `
|
||||
worker {
|
||||
tags = ""
|
||||
}`,
|
||||
expWorkerTags: map[string][]string{},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty env var",
|
||||
in: `
|
||||
worker {
|
||||
tags = "env://BOUNDARY_WORKER_TAGS"
|
||||
}`,
|
||||
expWorkerTags: map[string][]string{},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "not a url - entire tags block",
|
||||
in: `
|
||||
worker {
|
||||
tags = "\x00"
|
||||
}`,
|
||||
expWorkerTags: map[string][]string{},
|
||||
expErr: true,
|
||||
expErrStr: `Error parsing worker tags: error parsing url ("parse \"\\x00\": net/url: invalid control character in URL"): not a url`,
|
||||
},
|
||||
{
|
||||
name: "not a url - key's value set to string",
|
||||
in: `
|
||||
worker {
|
||||
tags {
|
||||
type = "\x00"
|
||||
}
|
||||
}
|
||||
`,
|
||||
expWorkerTags: map[string][]string{
|
||||
"type": {"\x00"},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "one tag key",
|
||||
in: `
|
||||
worker {
|
||||
tags = "env://BOUNDARY_WORKER_TAGS"
|
||||
}`,
|
||||
stateFn: defaultStateFn,
|
||||
actualTags: `type = ["dev", "local"]`,
|
||||
expWorkerTags: map[string][]string{
|
||||
"type": {"dev", "local"},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "multiple tag keys",
|
||||
in: `
|
||||
worker {
|
||||
tags = "env://BOUNDARY_WORKER_TAGS"
|
||||
}`,
|
||||
stateFn: defaultStateFn,
|
||||
actualTags: `
|
||||
type = ["dev", "local"]
|
||||
typetwo = ["devtwo", "localtwo"]
|
||||
`,
|
||||
expWorkerTags: map[string][]string{
|
||||
"type": {"dev", "local"},
|
||||
"typetwo": {"devtwo", "localtwo"},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "json tags - entire tags block",
|
||||
in: `
|
||||
worker {
|
||||
tags = "env://BOUNDARY_WORKER_TAGS"
|
||||
}`,
|
||||
stateFn: defaultStateFn,
|
||||
actualTags: `
|
||||
{
|
||||
"type": ["dev", "local"],
|
||||
"typetwo": ["devtwo", "localtwo"]
|
||||
}
|
||||
`,
|
||||
expWorkerTags: map[string][]string{
|
||||
"type": {"dev", "local"},
|
||||
"typetwo": {"devtwo", "localtwo"},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "json tags - keys specified in the HCL file, values point to env/file",
|
||||
in: `
|
||||
worker {
|
||||
tags = {
|
||||
type = "env://BOUNDARY_WORKER_TAGS"
|
||||
typetwo = "env://BOUNDARY_WORKER_TAGS_TWO"
|
||||
}
|
||||
}`,
|
||||
stateFn: func(t *testing.T, tags string) {
|
||||
defaultStateFn(t, tags)
|
||||
t.Setenv("BOUNDARY_WORKER_TAGS_TWO", `["devtwo", "localtwo"]`)
|
||||
},
|
||||
actualTags: `["dev","local"]`,
|
||||
expWorkerTags: map[string][]string{
|
||||
"type": {"dev", "local"},
|
||||
"typetwo": {"devtwo", "localtwo"},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "json tags - mix n' match",
|
||||
in: `
|
||||
worker {
|
||||
name = "web-prod-us-east-1"
|
||||
tags {
|
||||
type = "env://BOUNDARY_WORKER_TYPE_TAGS"
|
||||
typetwo = "file://type_two_tags.json"
|
||||
typethree = ["devthree", "localthree"]
|
||||
}
|
||||
}
|
||||
`,
|
||||
stateFn: func(t *testing.T, tags string) {
|
||||
workerTypeTags := `["dev", "local"]`
|
||||
t.Setenv("BOUNDARY_WORKER_TYPE_TAGS", workerTypeTags)
|
||||
|
||||
filepath := "./type_two_tags.json"
|
||||
err := os.WriteFile(filepath, []byte(`["devtwo", "localtwo"]`), 0o666)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
err := os.Remove(filepath)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
},
|
||||
expWorkerTags: map[string][]string{
|
||||
"type": {"dev", "local"},
|
||||
"typetwo": {"devtwo", "localtwo"},
|
||||
"typethree": {"devthree", "localthree"},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "bad json tags",
|
||||
in: `
|
||||
worker {
|
||||
tags = {
|
||||
type = "env://BOUNDARY_WORKER_TAGS"
|
||||
typetwo = "env://BOUNDARY_WORKER_TAGS"
|
||||
}
|
||||
}`,
|
||||
stateFn: defaultStateFn,
|
||||
actualTags: `
|
||||
{
|
||||
"type": ["dev", "local"],
|
||||
"typetwo": ["devtwo", "localtwo"]
|
||||
}
|
||||
`,
|
||||
expWorkerTags: nil,
|
||||
expErr: true,
|
||||
expErrStr: "Error unmarshalling env var/file contents: json: cannot unmarshal object into Go value of type []string",
|
||||
},
|
||||
{
|
||||
name: "no clean mapping to internal structures",
|
||||
in: `
|
||||
worker {
|
||||
tags = "env://BOUNDARY_WORKER_TAGS"
|
||||
}`,
|
||||
stateFn: defaultStateFn,
|
||||
actualTags: `
|
||||
worker {
|
||||
tags {
|
||||
type = "indeed"
|
||||
}
|
||||
}
|
||||
`,
|
||||
expErr: true,
|
||||
expErrStr: "Error decoding the worker's tags: 1 error(s) decoding:\n\n* '[0][worker][0]' expected type 'string', got unconvertible type 'map[string]interface {}', value: 'map[tags:[map[type:indeed]]]'",
|
||||
},
|
||||
{
|
||||
name: "not HCL",
|
||||
in: `worker {
|
||||
tags = "env://BOUNDARY_WORKER_TAGS"
|
||||
}`,
|
||||
stateFn: defaultStateFn,
|
||||
actualTags: `not_hcl`,
|
||||
expErr: true,
|
||||
expErrStr: "Error decoding raw worker tags: At 1:9: key 'not_hcl' expected start of object ('{') or assignment ('=')",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.stateFn != nil {
|
||||
tt.stateFn(t, tt.actualTags)
|
||||
}
|
||||
|
||||
c, err := Parse(tt.in)
|
||||
if tt.expErr {
|
||||
require.EqualError(t, err, tt.expErrStr)
|
||||
require.Nil(t, c)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, c)
|
||||
require.NotNil(t, c.Worker)
|
||||
require.Equal(t, tt.expWorkerTags, c.Worker.Tags)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestController_EventingConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -2,12 +2,9 @@ package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/host/plugin/store"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
"github.com/hashicorp/go-kms-wrapping/structwrapping"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -89,42 +86,3 @@ func (c *HostCatalogSecret) decrypt(ctx context.Context, cipher wrapping.Wrapper
|
||||
c.CtSecret = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(ICU-2835): this is necessary as update logic requires a static
|
||||
// public_id or private_id field. Once post-lookup updates can
|
||||
// support alternate primary key fields, we can port this to native
|
||||
// Update and upserts w/OnConflict.
|
||||
func (c *HostCatalogSecret) upsertQuery() (query string, queryValues []interface{}) {
|
||||
query = upsertHostCatalogSecretQuery
|
||||
queryValues = []interface{}{
|
||||
sql.Named("catalog_id", c.CatalogId),
|
||||
sql.Named("secret", c.CtSecret),
|
||||
sql.Named("key_id", c.KeyId),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *HostCatalogSecret) deleteQuery() (query string, queryValues []interface{}) {
|
||||
query = deleteHostCatalogSecretQuery
|
||||
queryValues = []interface{}{
|
||||
sql.Named("catalog_id", c.CatalogId),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *HostCatalogSecret) oplogMessage(opType db.OpType) *oplog.Message {
|
||||
msg := oplog.Message{
|
||||
Message: c.clone(),
|
||||
TypeName: c.TableName(),
|
||||
}
|
||||
switch opType {
|
||||
case db.CreateOp:
|
||||
msg.OpType = oplog.OpType_OP_TYPE_CREATE
|
||||
case db.UpdateOp:
|
||||
msg.OpType = oplog.OpType_OP_TYPE_UPDATE
|
||||
msg.FieldMaskPaths = []string{"secret", "key_id"}
|
||||
case db.DeleteOp:
|
||||
msg.OpType = oplog.OpType_OP_TYPE_DELETE
|
||||
}
|
||||
return &msg
|
||||
}
|
||||
@@ -118,57 +118,82 @@ func TestHostCatalogSecret_New(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostCatalogSecret_Custom_Queries(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
func TestHostCatalogSecret_Create_Upsert_Update_Delete(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kkms := kms.TestKms(t, conn, wrapper)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
plg := host.TestPlugin(t, conn, "test")
|
||||
cat := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId())
|
||||
ctx := context.Background()
|
||||
|
||||
secret, err := newHostCatalogSecret(ctx, cat.GetPublicId(), mustStruct(map[string]interface{}{
|
||||
"foo": "bar",
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, secret)
|
||||
require.Empty(t, secret.CtSecret)
|
||||
|
||||
databaseWrapper, err := kkms.GetWrapper(ctx, prj.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, databaseWrapper)
|
||||
require.NoError(t, secret.encrypt(ctx, databaseWrapper))
|
||||
|
||||
hcs, err := newHostCatalogSecret(ctx, cat.GetPublicId(),
|
||||
&structpb.Struct{Fields: map[string]*structpb.Value{"foo": structpb.NewStringValue("bar")}})
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, hcs.encrypt(ctx, databaseWrapper))
|
||||
q, v := hcs.upsertQuery()
|
||||
_, err = rw.Exec(ctx, q, v)
|
||||
assert.NoError(t, err)
|
||||
// Create
|
||||
w := db.New(conn)
|
||||
require.NoError(t, w.Create(ctx, secret))
|
||||
|
||||
found, err := newHostCatalogSecret(ctx, cat.GetPublicId(), nil)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, rw.LookupWhere(ctx, found, "catalog_id=?", found.GetCatalogId()))
|
||||
// Upsert
|
||||
newStructUpsert := mustMarshal(map[string]interface{}{
|
||||
"baz": "qux",
|
||||
})
|
||||
newSecretUpsert := secret.clone()
|
||||
newSecretUpsert.Secret = newStructUpsert
|
||||
require.NoError(t, newSecretUpsert.encrypt(ctx, databaseWrapper))
|
||||
require.NoError(t, w.Create(ctx, newSecretUpsert, db.WithOnConflict(&db.OnConflict{
|
||||
Target: db.Columns{"catalog_id"},
|
||||
Action: db.SetColumns([]string{"secret", "key_id"}),
|
||||
})))
|
||||
found := &HostCatalogSecret{
|
||||
HostCatalogSecret: &store.HostCatalogSecret{
|
||||
CatalogId: cat.GetPublicId(),
|
||||
},
|
||||
}
|
||||
require.NoError(t, w.LookupById(ctx, found))
|
||||
assert.Empty(t, cmp.Diff(newSecretUpsert.HostCatalogSecret, found.HostCatalogSecret, protocmp.Transform()))
|
||||
require.NoError(t, found.decrypt(ctx, databaseWrapper))
|
||||
require.NoError(t, hcs.decrypt(ctx, databaseWrapper))
|
||||
// update the created/updated time from the original
|
||||
hcs.CreateTime, hcs.UpdateTime = found.CreateTime, found.UpdateTime
|
||||
assert.Empty(t, cmp.Diff(hcs, found, protocmp.Transform()))
|
||||
assert.Empty(t, cmp.Diff(newStructUpsert, found.Secret, protocmp.Transform()))
|
||||
|
||||
// Update the secret and see the value updated.
|
||||
updated, err := newHostCatalogSecret(ctx, cat.GetPublicId(),
|
||||
&structpb.Struct{Fields: map[string]*structpb.Value{"updated": structpb.NewStringValue("value")}})
|
||||
// Update
|
||||
newStructUpdate := mustMarshal(map[string]interface{}{
|
||||
"one": "two",
|
||||
})
|
||||
newSecretUpdate := newSecretUpsert.clone()
|
||||
newSecretUpdate.Secret = newStructUpdate
|
||||
require.NoError(t, newSecretUpdate.encrypt(ctx, databaseWrapper))
|
||||
rowsUpdated, err := w.Update(ctx, newSecretUpdate, []string{"CtSecret"}, []string{})
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, updated.encrypt(ctx, databaseWrapper))
|
||||
q, v = updated.upsertQuery()
|
||||
_, err = rw.Exec(ctx, q, v)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, rw.LookupWhere(ctx, found, "catalog_id=?", found.GetCatalogId()))
|
||||
require.Equal(t, 1, rowsUpdated)
|
||||
found = &HostCatalogSecret{
|
||||
HostCatalogSecret: &store.HostCatalogSecret{
|
||||
CatalogId: cat.GetPublicId(),
|
||||
},
|
||||
}
|
||||
require.NoError(t, w.LookupById(ctx, found))
|
||||
assert.Empty(t, cmp.Diff(newSecretUpdate.HostCatalogSecret, found.HostCatalogSecret, protocmp.Transform()))
|
||||
require.NoError(t, found.decrypt(ctx, databaseWrapper))
|
||||
require.NoError(t, updated.decrypt(ctx, databaseWrapper))
|
||||
// set the created time to the first and update time from the newly found
|
||||
updated.CreateTime, updated.UpdateTime = hcs.CreateTime, found.UpdateTime
|
||||
assert.Empty(t, cmp.Diff(updated, found, protocmp.Transform()))
|
||||
assert.Empty(t, cmp.Diff(newStructUpdate, found.Secret, protocmp.Transform()))
|
||||
|
||||
// Try to delete this secret.
|
||||
q, v = updated.deleteQuery()
|
||||
_, err = rw.Exec(ctx, q, v)
|
||||
assert.NoError(t, err)
|
||||
err = rw.LookupWhere(ctx, found, "catalog_id=?", found.GetCatalogId())
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.IsNotFoundError(err))
|
||||
// Delete
|
||||
rowsDeleted, err := w.Delete(ctx, found)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, rowsDeleted)
|
||||
err = w.LookupById(ctx, &HostCatalogSecret{
|
||||
HostCatalogSecret: &store.HostCatalogSecret{
|
||||
CatalogId: cat.GetPublicId(),
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.True(t, errors.IsNotFoundError(err))
|
||||
}
|
||||
@@ -142,15 +142,11 @@ func (r *Repository) CreateCatalog(ctx context.Context, c *HostCatalog, _ ...Opt
|
||||
}
|
||||
if hcSecret != nil {
|
||||
newSecret := hcSecret.clone()
|
||||
q, v := newSecret.upsertQuery()
|
||||
rows, err := w.Exec(ctx, q, v)
|
||||
if err != nil {
|
||||
var sOplogMsg oplog.Message
|
||||
if err := w.Create(ctx, newSecret, db.NewOplogMsg(&sOplogMsg)); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if rows > 1 {
|
||||
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 catalog secret would have been created")
|
||||
}
|
||||
msgs = append(msgs, newSecret.oplogMessage(db.CreateOp))
|
||||
msgs = append(msgs, &sOplogMsg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,16 +396,16 @@ func (r *Repository) UpdateCatalog(ctx context.Context, c *HostCatalog, version
|
||||
// We didn't set/encrypt the persisted data because there was
|
||||
// none returned. Just delete the entry.
|
||||
deletedSecret := hcSecret.clone()
|
||||
q, v := deletedSecret.deleteQuery()
|
||||
secretsUpdated, err := w.Exec(ctx, q, v)
|
||||
var sOplogMsg oplog.Message
|
||||
secretsDeleted, err := w.Delete(ctx, deletedSecret, db.NewOplogMsg(&sOplogMsg))
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if secretsUpdated != 1 {
|
||||
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 catalog secret to be deleted, got %d", secretsUpdated))
|
||||
if secretsDeleted != 1 {
|
||||
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 catalog secret to be deleted, got %d", secretsDeleted))
|
||||
}
|
||||
updatedPersisted = true
|
||||
msgs = append(msgs, deletedSecret.oplogMessage(db.DeleteOp))
|
||||
msgs = append(msgs, &sOplogMsg)
|
||||
} else {
|
||||
hcSecret, err := newHostCatalogSecret(ctx, currentCatalog.GetPublicId(), plgResp.GetPersisted().GetSecrets())
|
||||
if err != nil {
|
||||
@@ -421,16 +417,20 @@ func (r *Repository) UpdateCatalog(ctx context.Context, c *HostCatalog, version
|
||||
|
||||
// Update the secrets.
|
||||
updatedSecret := hcSecret.clone()
|
||||
q, v := updatedSecret.upsertQuery()
|
||||
secretsUpdated, err := w.Exec(ctx, q, v)
|
||||
if err != nil {
|
||||
var sOplogMsg oplog.Message
|
||||
if err := w.Create(
|
||||
ctx,
|
||||
updatedSecret,
|
||||
db.WithOnConflict(&db.OnConflict{
|
||||
Target: db.Columns{"catalog_id"},
|
||||
Action: db.SetColumns([]string{"secret", "key_id"}),
|
||||
}),
|
||||
db.NewOplogMsg(&sOplogMsg),
|
||||
); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if secretsUpdated != 1 {
|
||||
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 catalog secret to be updated, got %d", secretsUpdated))
|
||||
}
|
||||
updatedPersisted = true
|
||||
msgs = append(msgs, updatedSecret.oplogMessage(db.UpdateOp))
|
||||
msgs = append(msgs, &sOplogMsg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1119,10 +1119,8 @@ func TestRepository_UpdateCatalog(t *testing.T) {
|
||||
cSecret, err := newHostCatalogSecret(ctx, cat.GetPublicId(), cat.Secrets)
|
||||
require.NoError(err)
|
||||
require.NoError(cSecret.encrypt(ctx, scopeWrapper))
|
||||
cSecretQ, cSecretV := cSecret.upsertQuery()
|
||||
secretsUpdated, err := dbRW.Exec(ctx, cSecretQ, cSecretV)
|
||||
err = dbRW.Create(ctx, cSecret)
|
||||
require.NoError(err)
|
||||
require.Equal(1, secretsUpdated)
|
||||
|
||||
// Set some (default) attributes on our test catalog and update SecretsHmac at the same time
|
||||
cat.Attributes = mustMarshal(map[string]interface{}{
|
||||
|
||||
@@ -424,10 +424,8 @@ func TestRepository_UpdateSet(t *testing.T) {
|
||||
scopeWrapper, err := dbKmsCache.GetWrapper(ctx, testCatalog.GetScopeId(), kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, testCatalogSecret.encrypt(ctx, scopeWrapper))
|
||||
testCatalogSecretQ, testCatalogSecretV := testCatalogSecret.upsertQuery()
|
||||
secretsUpdated, err := dbRW.Exec(ctx, testCatalogSecretQ, testCatalogSecretV)
|
||||
err = dbRW.Create(ctx, testCatalogSecret)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, secretsUpdated)
|
||||
|
||||
// Create a test duplicate set. We don't use this set, it just
|
||||
// exists to ensure that we can test for conflicts when setting
|
||||
|
||||
@@ -53,6 +53,53 @@ worker {
|
||||
|
||||
In this format, it is not possible to have an equal sign be a part of the key.
|
||||
|
||||
It is also possible to set the entire `tags` block or the keys' values within
|
||||
to point to an environment variable or filepath in the system, through the
|
||||
`env://` and `file://` URLs:
|
||||
|
||||
```hcl
|
||||
worker {
|
||||
name = "web-prod-us-east-1"
|
||||
tags = "env://BOUNDARY_ALL_WORKER_TAGS"
|
||||
}
|
||||
```
|
||||
|
||||
```hcl
|
||||
worker {
|
||||
name = "web-prod-us-east-1"
|
||||
tags {
|
||||
type = "env://BOUNDARY_WORKER_TYPE_TAGS"
|
||||
region = "file://config/worker/region_tags"
|
||||
usage = ["admin"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that the syntax within the environment variable / file changes
|
||||
slightly depending on how the configuration file is set:
|
||||
|
||||
For setting the entire `tags` block, both the keys and values need
|
||||
to be specified, in JSON or HCL format:
|
||||
|
||||
```json
|
||||
{
|
||||
"region": ["us-east-1"],
|
||||
"type": ["prod", "webservers"]
|
||||
}
|
||||
```
|
||||
|
||||
```hcl
|
||||
region = ["us-east-1"]
|
||||
type = ["prod", "webservers"]
|
||||
```
|
||||
|
||||
For setting the keys' values within the `tags` block, only a JSON
|
||||
array with the tags intended for the particular key is required:
|
||||
|
||||
```json
|
||||
["prod", "webservers"]
|
||||
```
|
||||
|
||||
# Target Worker Filtering
|
||||
|
||||
Once workers have tags, it is possible to use these tags to control which
|
||||
|
||||
@@ -39,7 +39,8 @@ worker {
|
||||
- `tags` - A map of key-value pairs where values are an array of strings. Most
|
||||
commonly used for [filtering](/docs/concepts/filtering) targets a worker can
|
||||
proxy via [worker tags](/docs/concepts/filtering/worker-tags). On `SIGHUP`, the
|
||||
tags set here will be re-parsed and new values used..
|
||||
tags set here will be re-parsed and new values used. It can also be a string
|
||||
referring to a file on disk (file://) or an env var (env://).
|
||||
|
||||
## KMS Configuration
|
||||
|
||||
|
||||
Generated
+31
-65
@@ -21,7 +21,7 @@
|
||||
"@hashicorp/react-content": "^8.0.2",
|
||||
"@hashicorp/react-docs-page": "^14.4.2",
|
||||
"@hashicorp/react-enterprise-alert": "^6.0.1",
|
||||
"@hashicorp/react-hashi-stack-menu": "^2.0.7",
|
||||
"@hashicorp/react-hashi-stack-menu": "^2.1.2",
|
||||
"@hashicorp/react-head": "^3.1.2",
|
||||
"@hashicorp/react-hero": "^8.0.2",
|
||||
"@hashicorp/react-image": "^4.0.3",
|
||||
@@ -1342,12 +1342,13 @@
|
||||
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
|
||||
},
|
||||
"node_modules/@hashicorp/react-hashi-stack-menu": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@hashicorp/react-hashi-stack-menu/-/react-hashi-stack-menu-2.0.7.tgz",
|
||||
"integrity": "sha512-3b9VxAzSPbb5YeiKbMiygrointTb56VsMBp27mXPXS2dqrZ4pj1o5AJCYwescv7g7KbwwL+YC9hlLlhRxjErUw==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@hashicorp/react-hashi-stack-menu/-/react-hashi-stack-menu-2.1.2.tgz",
|
||||
"integrity": "sha512-3K6Y2FNSLq6TsMceX3a1pdmxaVx8lRvxCgjxcKL1P6DpmOk0AU5/eL0CvKP8GzXSYstL36xXxIhWFfiaAaF/yQ==",
|
||||
"dependencies": {
|
||||
"@hashicorp/react-inline-svg": "^1.0.2",
|
||||
"slugify": "1.3.4"
|
||||
"@hashicorp/react-inline-svg": "^6.0.1",
|
||||
"@hashicorp/react-link-wrap": "^3.0.3",
|
||||
"slugify": "1.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@hashicorp/mktg-global-styles": ">=3.x",
|
||||
@@ -1355,20 +1356,12 @@
|
||||
"react": ">=16.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@hashicorp/react-hashi-stack-menu/node_modules/@hashicorp/react-inline-svg": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@hashicorp/react-inline-svg/-/react-inline-svg-1.0.2.tgz",
|
||||
"integrity": "sha512-AAFnBslSTgnEr++dTbMn3sybAqvn7myIj88ijGigF6u11eSRiV64zqEcyYLQKWTV6dF4AvYoxiYC6GSOgiM0Yw==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@hashicorp/react-hashi-stack-menu/node_modules/slugify": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.3.4.tgz",
|
||||
"integrity": "sha512-KP0ZYk5hJNBS8/eIjGkFDCzGQIoZ1mnfQRYS5WM3273z+fxGWXeN0fkwf2ebEweydv9tioZIHGZKoF21U07/nw==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.0.tgz",
|
||||
"integrity": "sha512-FkMq+MQc5hzYgM86nLuHI98Acwi3p4wX+a5BO9Hhw4JdK4L7WueIiZ4tXEobImPqBz2sVcV0+Mu3GRB30IGang==",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@hashicorp/react-head": {
|
||||
@@ -2347,13 +2340,9 @@
|
||||
"version": "11.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.2.tgz",
|
||||
"integrity": "sha512-hZuwOlGOwBZADA8EyDYyjx3+4JGIGjSHDHWrmpI7g5rFmQNltjlbaefAbiU5Kk7j3BUSDwt30quJRFv3nyJQ0w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"cpu": ["arm64"],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"os": ["darwin"],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
@@ -2362,13 +2351,9 @@
|
||||
"version": "11.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.2.tgz",
|
||||
"integrity": "sha512-PGOp0E1GisU+EJJlsmJVGE+aPYD0Uh7zqgsrpD3F/Y3766Ptfbe1lEPPWnRDl+OzSSrSrX1lkyM/Jlmh5OwNvA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"cpu": ["x64"],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"os": ["darwin"],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
@@ -2377,13 +2362,9 @@
|
||||
"version": "11.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.2.tgz",
|
||||
"integrity": "sha512-YcDHTJjn/8RqvyJVB6pvEKXihDcdrOwga3GfMv/QtVeLphTouY4BIcEUfrG5+26Nf37MP1ywN3RRl1TxpurAsQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"cpu": ["x64"],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"os": ["linux"],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
@@ -2392,13 +2373,9 @@
|
||||
"version": "11.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.2.tgz",
|
||||
"integrity": "sha512-e/pIKVdB+tGQYa1cW3sAeHm8gzEri/HYLZHT4WZojrUxgWXqx8pk7S7Xs47uBcFTqBDRvK3EcQpPLf3XdVsDdg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"cpu": ["x64"],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"os": ["win32"],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
@@ -5711,9 +5688,7 @@
|
||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
|
||||
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
|
||||
"devOptional": true,
|
||||
"engines": [
|
||||
"node >= 0.8"
|
||||
],
|
||||
"engines": ["node >= 0.8"],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
@@ -8821,9 +8796,7 @@
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"os": ["darwin"],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
@@ -19529,9 +19502,7 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"os": ["darwin"],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
@@ -21413,24 +21384,19 @@
|
||||
}
|
||||
},
|
||||
"@hashicorp/react-hashi-stack-menu": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@hashicorp/react-hashi-stack-menu/-/react-hashi-stack-menu-2.0.7.tgz",
|
||||
"integrity": "sha512-3b9VxAzSPbb5YeiKbMiygrointTb56VsMBp27mXPXS2dqrZ4pj1o5AJCYwescv7g7KbwwL+YC9hlLlhRxjErUw==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@hashicorp/react-hashi-stack-menu/-/react-hashi-stack-menu-2.1.2.tgz",
|
||||
"integrity": "sha512-3K6Y2FNSLq6TsMceX3a1pdmxaVx8lRvxCgjxcKL1P6DpmOk0AU5/eL0CvKP8GzXSYstL36xXxIhWFfiaAaF/yQ==",
|
||||
"requires": {
|
||||
"@hashicorp/react-inline-svg": "^1.0.2",
|
||||
"slugify": "1.3.4"
|
||||
"@hashicorp/react-inline-svg": "^6.0.1",
|
||||
"@hashicorp/react-link-wrap": "^3.0.3",
|
||||
"slugify": "1.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hashicorp/react-inline-svg": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@hashicorp/react-inline-svg/-/react-inline-svg-1.0.2.tgz",
|
||||
"integrity": "sha512-AAFnBslSTgnEr++dTbMn3sybAqvn7myIj88ijGigF6u11eSRiV64zqEcyYLQKWTV6dF4AvYoxiYC6GSOgiM0Yw==",
|
||||
"requires": {}
|
||||
},
|
||||
"slugify": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.3.4.tgz",
|
||||
"integrity": "sha512-KP0ZYk5hJNBS8/eIjGkFDCzGQIoZ1mnfQRYS5WM3273z+fxGWXeN0fkwf2ebEweydv9tioZIHGZKoF21U07/nw=="
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.0.tgz",
|
||||
"integrity": "sha512-FkMq+MQc5hzYgM86nLuHI98Acwi3p4wX+a5BO9Hhw4JdK4L7WueIiZ4tXEobImPqBz2sVcV0+Mu3GRB30IGang=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"@hashicorp/react-content": "^8.0.2",
|
||||
"@hashicorp/react-docs-page": "^14.4.2",
|
||||
"@hashicorp/react-enterprise-alert": "^6.0.1",
|
||||
"@hashicorp/react-hashi-stack-menu": "^2.0.7",
|
||||
"@hashicorp/react-hashi-stack-menu": "^2.1.2",
|
||||
"@hashicorp/react-head": "^3.1.2",
|
||||
"@hashicorp/react-hero": "^8.0.2",
|
||||
"@hashicorp/react-image": "^4.0.3",
|
||||
|
||||
Reference in new issue
Block a user