From bb2d86eb397840257340ed79433bc2abe6a1f3b5 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 16 Mar 2021 11:19:10 -0400 Subject: [PATCH] Add ability to generate pseudo-random IDs (#984) --- internal/db/id.go | 24 +++++++++++++++----- internal/db/id_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++ internal/db/option.go | 10 +++++++++ 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/internal/db/id.go b/internal/db/id.go index 9a58054c23..2c40e20f82 100644 --- a/internal/db/id.go +++ b/internal/db/id.go @@ -1,27 +1,39 @@ package db import ( + "bytes" "fmt" + "strings" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/vault/sdk/helper/base62" + "golang.org/x/crypto/blake2b" ) -func NewPrivateId(prefix string) (string, error) { - return newId(prefix) +func NewPrivateId(prefix string, opt ...Option) (string, error) { + return newId(prefix, opt...) } // NewPublicId creates a new public id with the prefix -func NewPublicId(prefix string) (string, error) { - return newId(prefix) +func NewPublicId(prefix string, opt ...Option) (string, error) { + return newId(prefix, opt...) } -func newId(prefix string) (string, error) { +func newId(prefix string, opt ...Option) (string, error) { const op = "db.newId" if prefix == "" { return "", errors.New(errors.InvalidParameter, op, "missing prefix") } - publicId, err := base62.Random(10) + var publicId string + var err error + opts := GetOpts(opt...) + if len(opts.withPrngValues) > 0 { + sum := blake2b.Sum256([]byte(strings.Join(opts.withPrngValues, "|"))) + reader := bytes.NewReader(sum[0:]) + publicId, err = base62.RandomWithReader(10, reader) + } else { + publicId, err = base62.Random(10) + } if err != nil { return "", errors.Wrap(err, op, errors.WithMsg("unable to generate id"), errors.WithCode(errors.Io)) } diff --git a/internal/db/id_test.go b/internal/db/id_test.go index e26a4c1593..776653afb8 100644 --- a/internal/db/id_test.go +++ b/internal/db/id_test.go @@ -93,3 +93,53 @@ func TestNewPrivateId(t *testing.T) { }) } } + +func TestPseudoRandomId(t *testing.T) { + type args struct { + prngValues []string + } + tests := []struct { + name string + args args + sameAsPrev bool + }{ + { + name: "valid first", + args: args{}, + }, + { + name: "valid second", + args: args{}, + }, + { + name: "first prng", + args: args{prngValues: []string{"foo", "bar"}}, + }, + { + name: "first prng verify", + args: args{prngValues: []string{"foo", "bar"}}, + sameAsPrev: true, + }, + { + name: "second prng", + args: args{prngValues: []string{"bar", "foo"}}, + }, + { + name: "second prng verify", + args: args{prngValues: []string{"bar", "foo"}}, + sameAsPrev: true, + }, + } + var prevTestValue string + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, err := NewPublicId("id", WithPrngValues(tt.args.prngValues)) + require.NoError(err) + if tt.sameAsPrev { + assert.Equal(prevTestValue, got) + } + prevTestValue = got + }) + } +} diff --git a/internal/db/option.go b/internal/db/option.go index b55af90c24..1547bab9ae 100644 --- a/internal/db/option.go +++ b/internal/db/option.go @@ -40,6 +40,9 @@ type Options struct { withWhereClause string withWhereClauseArgs []interface{} withOrder string + + // withPrngValues is used to switch the ID generation to a pseudo-random mode + withPrngValues []string } type oplogOpts struct { @@ -155,3 +158,10 @@ func WithOrder(withOrder string) Option { o.withOrder = withOrder } } + +// WithPrngValues provides an option to provide values to seed an PRNG when generating IDs +func WithPrngValues(withPrngValues []string) Option { + return func(o *Options) { + o.withPrngValues = withPrngValues + } +}