Add configuration settings for Argon2 password KDF (#217)

* Bump generated proto code

* Add argon2 conf sql and proto files

* Add generated code for argon2 conf

* Add Resources for Argon2 configuration

* Add get and set Configuration to repository

* Add current config reference to AuthMethod

* Fix authmethod_test

* Fix TestAccount_New

* Add WithConfiguration to options

* Add sql views for current configuration

* Fix SetTableName

* Remove duplicate testAccounts function

* Make tests clearer

* Fix comment

* Rename test variable

* Use NewPrivateId function for private Ids

* Refactor: rename public_id to private_id for password_conf and password_credential

* Remove bad comment

* Check if embedded argon2 configuration is nil

* Add checks around the WithConfiguration option

* Use spaces not tabs in proto file

* Add doc to explain choice of oplog ticket

* Verify oplog in test
pull/229/head
Michael Gaffney 6 years ago committed by GitHub
parent c3c07f1556
commit c163d790a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -79,6 +79,7 @@ protobuild:
@protoc-go-inject-tag -input=./internal/authtoken/store/authtoken.pb.go
@protoc-go-inject-tag -input=./internal/iam/store/auth_account.pb.go
@protoc-go-inject-tag -input=./internal/auth/password/store/password.pb.go
@protoc-go-inject-tag -input=./internal/auth/password/store/argon2.pb.go
@rm -R ${TMP_DIR}
protolint:

@ -0,0 +1,102 @@
package password
import (
"strings"
"github.com/hashicorp/watchtower/internal/auth/password/store"
"github.com/hashicorp/watchtower/internal/oplog"
"google.golang.org/protobuf/proto"
)
// Argon2Configuration is a configuration for using the argon2id key
// derivation function. It is owned by an AuthMethod.
//
// Iterations, Memory, and Threads are the cost parameters. The cost
// parameters should be increased as memory latency and CPU parallelism
// increases.
//
// For a detailed specification of Argon2 see:
// https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
type Argon2Configuration struct {
*store.Argon2Configuration
tableName string
}
// NewArgon2Configuration creates a new in memory Argon2Configuration with
// reasonable default settings.
func NewArgon2Configuration() *Argon2Configuration {
return &Argon2Configuration{
Argon2Configuration: &store.Argon2Configuration{
Iterations: 3,
Memory: 64 * 1024,
Threads: 1,
SaltLength: 32,
KeyLength: 32,
},
}
}
func (c *Argon2Configuration) validate() error {
switch {
case c == nil, c.Argon2Configuration == nil:
return ErrInvalidConfiguration
case c.Iterations == 0, c.Memory == 0, c.Threads == 0, c.SaltLength == 0, c.KeyLength == 0:
return ErrInvalidConfiguration
default:
return nil
}
}
// AuthMethodId returns the Id of the AuthMethod which owns c.
func (c *Argon2Configuration) AuthMethodId() string {
if c != nil && c.Argon2Configuration != nil {
return c.PasswordMethodId
}
return ""
}
func (c *Argon2Configuration) clone() *Argon2Configuration {
cp := proto.Clone(c.Argon2Configuration)
return &Argon2Configuration{
Argon2Configuration: cp.(*store.Argon2Configuration),
}
}
// TableName returns the table name.
func (c *Argon2Configuration) TableName() string {
if c != nil && c.tableName != "" {
return c.tableName
}
return "auth_password_argon2_conf"
}
// SetTableName sets the table name.
func (c *Argon2Configuration) SetTableName(n string) {
c.tableName = n
}
func (c *Argon2Configuration) oplog(op oplog.OpType) oplog.Metadata {
metadata := oplog.Metadata{
"resource-public-id": []string{c.PrivateId},
"resource-type": []string{"password argon2 conf"},
"op-type": []string{op.String()},
}
if c.PasswordMethodId != "" {
metadata["password-method-id"] = []string{c.PasswordMethodId}
}
return metadata
}
func (c *Argon2Configuration) whereDup() (string, []interface{}) {
var where []string
var args []interface{}
where, args = append(where, "password_method_id = ?"), append(args, c.PasswordMethodId)
where, args = append(where, "iterations = ?"), append(args, c.Iterations)
where, args = append(where, "memory = ?"), append(args, c.Memory)
where, args = append(where, "threads = ?"), append(args, c.Threads)
where, args = append(where, "key_length = ?"), append(args, c.KeyLength)
where, args = append(where, "salt_length = ?"), append(args, c.SaltLength)
return strings.Join(where, " and "), args
}

@ -0,0 +1,270 @@
package password
import (
"context"
"errors"
"testing"
"github.com/hashicorp/watchtower/internal/auth/password/store"
"github.com/hashicorp/watchtower/internal/db"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestArgon2Configuration_New(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
authMethods := testAuthMethods(t, conn, 1)
authMethod := authMethods[0]
authMethodId := authMethod.GetPublicId()
ctx := context.Background()
// There should already be a configuration when an authMethod is created.
t.Run("default-configuration", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
var confs []*Argon2Configuration
err := rw.SearchWhere(ctx, &confs, "password_method_id = ?", []interface{}{authMethodId})
require.NoError(err)
require.Equal(1, len(confs))
got := confs[0]
want := &Argon2Configuration{
Argon2Configuration: &store.Argon2Configuration{
PrivateId: got.GetPrivateId(),
CreateTime: got.GetCreateTime(),
PasswordMethodId: authMethodId,
Iterations: 3,
Memory: 64 * 1024,
Threads: 1,
SaltLength: 32,
KeyLength: 32,
},
}
assert.Equal(want, got)
})
t.Run("no-duplicate-configurations", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got := NewArgon2Configuration()
require.NotNil(got)
var err error
got.PrivateId, err = newArgon2ConfigurationId()
require.NoError(err)
got.PasswordMethodId = authMethodId
err = rw.Create(ctx, got)
assert.Error(err)
})
t.Run("multiple-configurations", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
var confs []*Argon2Configuration
err := rw.SearchWhere(ctx, &confs, "password_method_id = ?", []interface{}{authMethodId})
require.NoError(err)
assert.Equal(1, len(confs))
c1 := NewArgon2Configuration()
require.NotNil(c1)
c1.PrivateId, err = newArgon2ConfigurationId()
require.NoError(err)
c1.PasswordMethodId = authMethodId
c1.Iterations = c1.Iterations + 1
c1.Threads = c1.Threads + 1
err = rw.Create(ctx, c1)
assert.NoError(err)
c2 := NewArgon2Configuration()
require.NotNil(c2)
c2.PrivateId, err = newArgon2ConfigurationId()
require.NoError(err)
c2.PasswordMethodId = authMethodId
c2.Memory = 32 * 1024
c2.SaltLength = 16
c2.KeyLength = 16
err = rw.Create(ctx, c2)
assert.NoError(err)
confs = nil
err = rw.SearchWhere(ctx, &confs, "password_method_id = ?", []interface{}{authMethodId})
require.NoError(err)
assert.Equal(3, len(confs))
})
}
func TestArgon2Configuration_Readonly(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
changeIterations := func() func(*Argon2Configuration) (*Argon2Configuration, []string) {
return func(c *Argon2Configuration) (*Argon2Configuration, []string) {
c.Iterations = c.Iterations + 1
return c, []string{"Iterations"}
}
}
changeThreads := func() func(*Argon2Configuration) (*Argon2Configuration, []string) {
return func(c *Argon2Configuration) (*Argon2Configuration, []string) {
c.Threads = c.Threads + 1
return c, []string{"Threads"}
}
}
changeMemory := func() func(*Argon2Configuration) (*Argon2Configuration, []string) {
return func(c *Argon2Configuration) (*Argon2Configuration, []string) {
c.Memory = c.Memory + 1
return c, []string{"Memory"}
}
}
authMethods := testAuthMethods(t, conn, 1)
authMethod := authMethods[0]
authMethodId := authMethod.GetPublicId()
var tests = []struct {
name string
chgFn func(*Argon2Configuration) (*Argon2Configuration, []string)
}{
{
name: "iterations",
chgFn: changeIterations(),
},
{
name: "threads",
chgFn: changeThreads(),
},
{
name: "Memory",
chgFn: changeMemory(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
var confs []*Argon2Configuration
err := rw.SearchWhere(context.Background(), &confs, "password_method_id = ?", []interface{}{authMethodId})
require.NoError(err)
assert.Greater(len(confs), 0)
orig := confs[0]
changed, masks := tt.chgFn(orig)
require.NotEmpty(changed.GetPrivateId())
count, err := rw.Update(context.Background(), changed, masks, nil)
assert.Error(err)
assert.Equal(0, count)
})
}
}
func TestArgon2Configuration_Validate(t *testing.T) {
var tests = []struct {
name string
in *Argon2Configuration
want error
}{
{
name: "nil-configuration",
in: nil,
want: ErrInvalidConfiguration,
},
{
name: "nil-embedded-config",
in: &Argon2Configuration{},
want: ErrInvalidConfiguration,
},
{
name: "valid-default",
in: NewArgon2Configuration(),
},
{
name: "valid-changes",
in: &Argon2Configuration{
Argon2Configuration: &store.Argon2Configuration{
Iterations: 3 * 2,
Memory: 32 * 1024,
Threads: 10,
SaltLength: 16,
KeyLength: 16,
},
},
},
{
name: "invalid-iterations",
in: &Argon2Configuration{
Argon2Configuration: &store.Argon2Configuration{
Iterations: 0,
Memory: 1,
Threads: 1,
SaltLength: 1,
KeyLength: 1,
},
},
want: ErrInvalidConfiguration,
},
{
name: "invalid-memory",
in: &Argon2Configuration{
Argon2Configuration: &store.Argon2Configuration{
Iterations: 1,
Memory: 0,
Threads: 1,
SaltLength: 1,
KeyLength: 1,
},
},
want: ErrInvalidConfiguration,
},
{
name: "invalid-threads",
in: &Argon2Configuration{
Argon2Configuration: &store.Argon2Configuration{
Iterations: 1,
Memory: 1,
Threads: 0,
SaltLength: 1,
KeyLength: 1,
},
},
want: ErrInvalidConfiguration,
},
{
name: "invalid-salt-length",
in: &Argon2Configuration{
Argon2Configuration: &store.Argon2Configuration{
Iterations: 1,
Memory: 1,
Threads: 1,
SaltLength: 0,
KeyLength: 1,
},
},
want: ErrInvalidConfiguration,
},
{
name: "invalid-key-length",
in: &Argon2Configuration{
Argon2Configuration: &store.Argon2Configuration{
Iterations: 1,
Memory: 1,
Threads: 1,
SaltLength: 1,
KeyLength: 0,
},
},
want: ErrInvalidConfiguration,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got := tt.in.validate()
if tt.want == nil {
assert.NoErrorf(got, "valid argon2 configuration: %+v", tt.in)
return
}
require.Error(got)
assert.Truef(errors.Is(got, tt.want), "want err: %q got: %q", tt.want, got)
})
}
}

@ -27,9 +27,17 @@ func testAuthMethods(t *testing.T, conn *gorm.DB, count int) []*AuthMethod {
require.NotEmpty(id)
cat.PublicId = id
conf := NewArgon2Configuration()
require.NotNil(conf)
conf.PrivateId, err = newArgon2ConfigurationId()
require.NoError(err)
conf.PasswordMethodId = cat.PublicId
cat.PasswordConfId = conf.PrivateId
ctx := context.Background()
_, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, iw db.Writer) error {
require.NoError(iw.Create(ctx, conf))
return iw.Create(ctx, cat)
},
)
@ -123,14 +131,20 @@ func TestAuthMethod_New(t *testing.T) {
tt.want.PublicId = id
got.PublicId = id
conn.LogMode(true)
conf := NewArgon2Configuration()
require.NotNil(conf)
conf.PrivateId, err = newArgon2ConfigurationId()
require.NoError(err)
conf.PasswordMethodId = got.PublicId
got.PasswordConfId = conf.PrivateId
ctx := context.Background()
_, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, iw db.Writer) error {
require.NoError(iw.Create(ctx, conf))
return iw.Create(ctx, got)
},
)
conn.LogMode(false)
assert.NoError(err2)
})
}

@ -0,0 +1,13 @@
package password
import "errors"
var (
// ErrUnsupportedConfiguration results from attempting to perform an
// operation that sets a password configuration to an unsupported type.
ErrUnsupportedConfiguration = errors.New("unsupported configuration")
// ErrInvalidConfiguration results from attempting to perform an
// operation that sets a password configuration with invalid settings.
ErrInvalidConfiguration = errors.New("invalid configuration")
)

@ -17,10 +17,13 @@ type options struct {
withName string
withDescription string
withLimit int
withConfig Configuration
}
func getDefaultOptions() options {
return options{}
return options{
withConfig: NewArgon2Configuration(),
}
}
// WithDescription provides an optional description.
@ -45,3 +48,10 @@ func WithLimit(l int) Option {
o.withLimit = l
}
}
// WithConfiguration provides an optional configuration.
func WithConfiguration(config Configuration) Option {
return func(o *options) {
o.withConfig = config
}
}

@ -4,6 +4,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_GetOpts(t *testing.T) {
@ -26,4 +27,15 @@ func Test_GetOpts(t *testing.T) {
testOpts.withLimit = 5
assert.Equal(t, opts, testOpts)
})
t.Run("WithConfiguration", func(t *testing.T) {
conf := NewArgon2Configuration()
conf.KeyLength = conf.KeyLength * 2
opts := getOpts(WithConfiguration(conf))
testOpts := getDefaultOptions()
c, ok := testOpts.withConfig.(*Argon2Configuration)
require.True(t, ok, "need an Argon2Configuration")
c.KeyLength = c.KeyLength * 2
testOpts.withConfig = c
assert.Equal(t, opts, testOpts)
})
}

@ -0,0 +1,20 @@
package password
import (
"fmt"
"github.com/hashicorp/watchtower/internal/db"
)
// Prefixes for private ids for types in the password package.
const (
argon2ConfigurationPrefix = "arg2conf"
)
func newArgon2ConfigurationId() (string, error) {
id, err := db.NewPrivateId(argon2ConfigurationPrefix)
if err != nil {
return "", fmt.Errorf("new password argon2 configuration id: %w", err)
}
return id, err
}

@ -0,0 +1,17 @@
package password
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_PrivateIds(t *testing.T) {
t.Run("argon2Config", func(t *testing.T) {
id, err := newArgon2ConfigurationId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, argon2ConfigurationPrefix+"_"))
})
}

@ -13,6 +13,9 @@ import (
// contain a valid ScopeId. m must not contain a PublicId. The PublicId is
// generated and assigned by this method.
//
// WithConfiguration is the only valid option. All other options are
// ignored.
//
// Both m.Name and m.Description are optional. If m.Name is set, it must be
// unique within m.ScopeId.
func (r *Repository) CreateAuthMethod(ctx context.Context, m *AuthMethod, opt ...Option) (*AuthMethod, error) {
@ -36,9 +39,29 @@ func (r *Repository) CreateAuthMethod(ctx context.Context, m *AuthMethod, opt ..
}
m.PublicId = id
opts := getOpts(opt...)
c, ok := opts.withConfig.(*Argon2Configuration)
if !ok {
return nil, fmt.Errorf("create: password auth method: unknown configuration: %w", ErrUnsupportedConfiguration)
}
if err := c.validate(); err != nil {
return nil, fmt.Errorf("create: password auth method: %w", err)
}
c.PrivateId, err = newArgon2ConfigurationId()
if err != nil {
return nil, fmt.Errorf("create: password auth method: %w", err)
}
m.PasswordConfId, c.PasswordMethodId = c.PrivateId, m.PublicId
var newAuthMethod *AuthMethod
var newArgon2Conf *Argon2Configuration
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
newArgon2Conf = c.clone()
if err := w.Create(ctx, newArgon2Conf, db.WithOplog(r.wrapper, c.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil {
return err
}
newAuthMethod = m.clone()
return w.Create(ctx, newAuthMethod, db.WithOplog(r.wrapper, m.oplog(oplog.OpType_OP_TYPE_CREATE)))
},

@ -96,6 +96,30 @@ func TestRepository_CreateAuthMethod(t *testing.T) {
},
},
},
{
name: "invalid-with-config-nil-embedded-config",
in: &AuthMethod{
AuthMethod: &store.AuthMethod{
ScopeId: org.PublicId,
},
},
opts: []Option{
WithConfiguration(&Argon2Configuration{}),
},
wantIsErr: ErrInvalidConfiguration,
},
{
name: "invalid-with-config-unknown-config-type",
in: &AuthMethod{
AuthMethod: &store.AuthMethod{
ScopeId: org.PublicId,
},
},
opts: []Option{
WithConfiguration(tconf(0)),
},
wantIsErr: ErrUnsupportedConfiguration,
},
}
for _, tt := range tests {

@ -0,0 +1,134 @@
package password
import (
"context"
"fmt"
"github.com/hashicorp/watchtower/internal/auth/password/store"
"github.com/hashicorp/watchtower/internal/db"
"github.com/hashicorp/watchtower/internal/oplog"
)
// A Configuration is an interface holding one of the configuration types
// for a specific key derivation function. Argon2Configuration is currently
// the only configuration type.
type Configuration interface {
AuthMethodId() string
validate() error
}
// GetConfiguration returns the current configuration for authMethodId.
func (r *Repository) GetConfiguration(ctx context.Context, authMethodId string) (Configuration, error) {
if authMethodId == "" {
return nil, fmt.Errorf("get password configuration: no auth method id: %w", db.ErrInvalidParameter)
}
cc, err := r.currentConfig(ctx, authMethodId)
if err != nil {
return nil, fmt.Errorf("get password configuration: %w", err)
}
return cc.argon2(), nil
}
// SetConfiguration sets the configuration for c.AuthMethodId to c and
// returns a new Configuration. c is not changed. c must contain a valid
// AuthMethodId. c.PrivateId is ignored.
//
// If c contains new settings for c.AuthMethodId, SetConfiguration inserts
// c into the repository and updates AuthMethod to use the new
// configuration. If c contains settings equal to the current configuration
// for c.AuthMethodId, SetConfiguration ignores c. If c contains settings
// equal to a previous configuration for c.AuthMethodId, SetConfiguration
// updates AuthMethod to use the previous configuration.
func (r *Repository) SetConfiguration(ctx context.Context, c Configuration) (Configuration, error) {
if c == nil {
return nil, fmt.Errorf("set password configuration: %w", db.ErrNilParameter)
}
if c.AuthMethodId() == "" {
return nil, fmt.Errorf("set password configuration: no auth method id: %w", db.ErrInvalidParameter)
}
if err := c.validate(); err != nil {
return nil, fmt.Errorf("set password configuration: %w", err)
}
switch v := c.(type) {
case *Argon2Configuration:
out, err := r.setArgon2Conf(ctx, v)
if err != nil {
return nil, fmt.Errorf("set password configuration: %w", err)
}
return out, nil
default:
return nil, fmt.Errorf("set password configuration: %w", ErrUnsupportedConfiguration)
}
}
func (r *Repository) setArgon2Conf(ctx context.Context, c *Argon2Configuration) (*Argon2Configuration, error) {
c = c.clone()
id, err := newArgon2ConfigurationId()
if err != nil {
return nil, err
}
c.PrivateId = id
a := &AuthMethod{
AuthMethod: &store.AuthMethod{
PublicId: c.PasswordMethodId,
},
}
newArgon2Conf := &Argon2Configuration{Argon2Configuration: &store.Argon2Configuration{}}
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(rr db.Reader, w db.Writer) error {
where, args := c.whereDup()
if err := rr.LookupWhere(ctx, newArgon2Conf, where, args...); err != nil {
if err != db.ErrRecordNotFound {
return err
}
newArgon2Conf = c.clone()
if err := w.Create(ctx, newArgon2Conf, db.WithOplog(r.wrapper, c.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil {
return err
}
}
a.PasswordConfId = newArgon2Conf.PrivateId
rowsUpdated, err := w.Update(ctx, a, []string{"PasswordConfId"}, nil, db.WithOplog(r.wrapper, a.oplog(oplog.OpType_OP_TYPE_UPDATE)))
if err == nil && rowsUpdated > 1 {
return db.ErrMultipleRecords
}
return err
},
)
if err != nil {
return nil, err
}
return newArgon2Conf, nil
}
type currentConfig struct {
ConfType string
MinUserNameLength int
MinPasswordLength int
*Argon2Configuration
}
func (c *currentConfig) TableName() string {
return "auth_password_current_conf"
}
func (r *Repository) currentConfig(ctx context.Context, authMethodId string) (*currentConfig, error) {
var cc currentConfig
if err := r.reader.LookupWhere(ctx, &cc, "password_method_id = ?", authMethodId); err != nil {
return nil, err
}
return &cc, nil
}
func (c *currentConfig) argon2() *Argon2Configuration {
if c.ConfType != "argon2" {
return nil
}
return c.Argon2Configuration
}

@ -0,0 +1,309 @@
package password
import (
"context"
"errors"
"testing"
"time"
"github.com/hashicorp/watchtower/internal/auth/password/store"
"github.com/hashicorp/watchtower/internal/db"
"github.com/hashicorp/watchtower/internal/oplog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRepository_GetSetConfiguration(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := NewRepository(rw, rw, wrapper)
assert.NoError(t, err)
require.NotNil(t, repo)
authMethods := testAuthMethods(t, conn, 1)
authMethod := authMethods[0]
authMethodId := authMethod.GetPublicId()
ctx := context.Background()
// The order of these tests are important. Some tests have a dependency
// on prior tests.
var original string // original configuration ID
t.Run("has-default-configuration", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := repo.GetConfiguration(ctx, authMethodId)
assert.NoError(err)
require.NotNil(got)
conf, ok := got.(*Argon2Configuration)
require.True(ok, "want *Argon2Configuration")
require.NotEmpty(conf.PrivateId, "default configuration PrivateId")
original = conf.PrivateId
want := NewArgon2Configuration()
want.PrivateId = original
want.CreateTime = conf.CreateTime
want.PasswordMethodId = authMethodId
require.Equal(want, got)
})
t.Run("change-configuration", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
require.NotEmpty(original, "Original ID")
current, err := repo.GetConfiguration(ctx, authMethodId)
assert.NoError(err)
require.NotNil(current)
currentConf, ok := current.(*Argon2Configuration)
require.True(ok, "want *Argon2Configuration")
assert.Equal(original, currentConf.PrivateId)
newConf := NewArgon2Configuration()
assert.Empty(newConf.PrivateId)
newConf.PasswordMethodId = currentConf.PasswordMethodId
newConf.Memory = currentConf.Memory * 2
updated, err := repo.SetConfiguration(ctx, newConf)
assert.NoError(err)
require.NotNil(updated)
assert.NotSame(newConf, updated)
updatedConf, ok := updated.(*Argon2Configuration)
require.True(ok, "want *Argon2Configuration")
assert.NotSame(newConf, updatedConf)
assert.NotEmpty(updatedConf.PrivateId, "updatedConf.PrivateId")
assert.NotEqual(original, updatedConf.PrivateId)
current2, err := repo.GetConfiguration(ctx, authMethodId)
assert.NoError(err)
require.NotNil(current2)
current2Conf, ok := current2.(*Argon2Configuration)
require.True(ok, "want *Argon2Configuration")
assert.Equal(updatedConf.PrivateId, current2Conf.PrivateId)
assert.Equal(newConf.Memory, current2Conf.Memory, "changed setting")
})
t.Run("change-to-old-configuration", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
require.NotEmpty(original, "Original ID")
newConf := NewArgon2Configuration()
newConf.PasswordMethodId = authMethodId
assert.Empty(newConf.PrivateId)
updated, err := repo.SetConfiguration(ctx, newConf)
assert.NoError(err)
require.NotNil(updated)
assert.NotSame(newConf, updated)
updatedConf, ok := updated.(*Argon2Configuration)
require.True(ok, "want *Argon2Configuration")
assert.NotSame(newConf, updatedConf)
assert.NotEmpty(updatedConf.PrivateId, "updatedConf.PrivateId")
assert.Equal(original, updatedConf.PrivateId)
current, err := repo.GetConfiguration(ctx, authMethodId)
assert.NoError(err)
require.NotNil(current)
currentConf, ok := current.(*Argon2Configuration)
require.True(ok, "want *Argon2Configuration")
assert.Equal(updatedConf.PrivateId, currentConf.PrivateId)
})
}
func TestRepository_GetConfiguration(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := NewRepository(rw, rw, wrapper)
assert.NoError(t, err)
require.NotNil(t, repo)
authMethods := testAuthMethods(t, conn, 1)
authMethod := authMethods[0]
authMethodId := authMethod.GetPublicId()
ctx := context.Background()
var tests = []struct {
name string
authMethodId string
want *Argon2Configuration
wantIsErr error
}{
{
name: "invalid-no-authMethodId",
authMethodId: "",
wantIsErr: db.ErrInvalidParameter,
},
{
name: "invalid-authMethodId",
authMethodId: "abcdefghijk",
wantIsErr: db.ErrRecordNotFound,
},
{
name: "valid-authMethodId",
authMethodId: authMethodId,
want: NewArgon2Configuration(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := repo.GetConfiguration(ctx, tt.authMethodId)
if tt.wantIsErr != nil {
assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err)
assert.Nil(got, "returned configuration")
return
}
require.NoError(err)
gotConf, ok := got.(*Argon2Configuration)
require.True(ok, "want *Argon2Configuration")
tt.want.PasswordMethodId = tt.authMethodId
assert.Equal(tt.authMethodId, gotConf.AuthMethodId(), "authMethodId")
assert.Equal(tt.want.PasswordMethodId, gotConf.PasswordMethodId)
assert.Equal(tt.want.Iterations, gotConf.Iterations)
assert.Equal(tt.want.Memory, gotConf.Memory)
assert.Equal(tt.want.Threads, gotConf.Threads)
assert.Equal(tt.want.SaltLength, gotConf.SaltLength)
assert.Equal(tt.want.KeyLength, gotConf.KeyLength)
})
}
}
type tconf int
func (t tconf) AuthMethodId() string { return "abcdefghijk" }
func (t tconf) validate() error { return nil }
var _ Configuration = tconf(0)
func TestRepository_SetConfiguration(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := NewRepository(rw, rw, wrapper)
assert.NoError(t, err)
require.NotNil(t, repo)
authMethods := testAuthMethods(t, conn, 1)
authMethod := authMethods[0]
authMethodId := authMethod.GetPublicId()
var tests = []struct {
name string
in Configuration
want *Argon2Configuration
wantUnknownErr bool
wantIsErr error
}{
{
name: "invalid-nil-config",
wantIsErr: db.ErrNilParameter,
},
{
name: "nil-embedded-config",
in: &Argon2Configuration{},
wantIsErr: db.ErrInvalidParameter,
},
{
name: "invalid-no-authMethodId",
in: NewArgon2Configuration(),
wantIsErr: db.ErrInvalidParameter,
},
{
name: "unknown-configuration-type",
in: tconf(0),
wantIsErr: ErrUnsupportedConfiguration,
},
{
name: "invalid-unknown-authMethodId",
in: &Argon2Configuration{
Argon2Configuration: &store.Argon2Configuration{
PasswordMethodId: "abcdefghijk",
Iterations: 3 * 2,
Memory: 64 * 1024,
Threads: 1,
SaltLength: 32,
KeyLength: 32,
},
},
wantUnknownErr: true,
},
{
name: "invalid-config-setting",
in: &Argon2Configuration{
Argon2Configuration: &store.Argon2Configuration{
PasswordMethodId: authMethodId,
Iterations: 0,
Memory: 64 * 1024,
Threads: 1,
SaltLength: 32,
KeyLength: 32,
},
},
wantIsErr: ErrInvalidConfiguration,
},
{
name: "valid",
in: &Argon2Configuration{
Argon2Configuration: &store.Argon2Configuration{
PasswordMethodId: authMethodId,
Iterations: 3 * 2,
Memory: 64 * 1024,
Threads: 1,
SaltLength: 32,
KeyLength: 32,
},
},
want: &Argon2Configuration{
Argon2Configuration: &store.Argon2Configuration{
PasswordMethodId: authMethodId,
Iterations: 3 * 2,
Memory: 64 * 1024,
Threads: 1,
SaltLength: 32,
KeyLength: 32,
},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := repo.SetConfiguration(context.Background(), tt.in)
if tt.wantIsErr != nil {
assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err)
assert.Nil(got, "returned configuration")
return
}
if tt.wantUnknownErr {
assert.Error(err)
return
}
require.NoError(err)
assert.NotSame(tt.in, got)
gotConf, ok := got.(*Argon2Configuration)
require.True(ok, "want *Argon2Configuration")
assert.Equal(tt.want.PasswordMethodId, gotConf.PasswordMethodId)
assert.Equal(tt.want.Iterations, gotConf.Iterations)
assert.Equal(tt.want.Memory, gotConf.Memory)
assert.Equal(tt.want.Threads, gotConf.Threads)
assert.Equal(tt.want.SaltLength, gotConf.SaltLength)
assert.Equal(tt.want.KeyLength, gotConf.KeyLength)
assert.NoError(db.TestVerifyOplog(t, rw, gotConf.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)))
})
}
}

@ -0,0 +1,264 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.12.3
// source: controller/storage/auth/password/store/v1/argon2.proto
// Package store provides protobufs for storing types in the password package.
package store
import (
proto "github.com/golang/protobuf/proto"
timestamp "github.com/hashicorp/watchtower/internal/db/timestamp"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4
// Argon2Configuration is a configuration for using the argon2id key
// derivation function. It is owned by an AuthMethod.
//
// Iterations, Memory, and Threads are the cost parameters. The cost
// parameters should be increased as memory latency and CPU parallelism
// increases.
//
// For a detailed specification of Argon2 see:
// https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
type Argon2Configuration struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// @inject_tag: `gorm:"primary_key"`
PrivateId string `protobuf:"bytes,1,opt,name=private_id,json=privateId,proto3" json:"private_id,omitempty" gorm:"primary_key"`
// The create_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
CreateTime *timestamp.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
// @inject_tag: `gorm:"not_null"`
PasswordMethodId string `protobuf:"bytes,3,opt,name=password_method_id,json=passwordMethodId,proto3" json:"password_method_id,omitempty" gorm:"not_null"`
// Iterations is the time parameter in the Argon2 specification. It
// specifies the number of passes over the memory. Must be > 0.
// @inject_tag: `gorm:"default:null"`
Iterations uint32 `protobuf:"varint,4,opt,name=iterations,proto3" json:"iterations,omitempty" gorm:"default:null"`
// Memory is the memory parameter in the Argon2 specification. It
// specifies the size of the memory in KiB. For example Memory=32*1024
// sets the memory cost to ~32 MB. Must be > 0.
// @inject_tag: `gorm:"default:null"`
Memory uint32 `protobuf:"varint,5,opt,name=memory,proto3" json:"memory,omitempty" gorm:"default:null"`
// Threads is the threads parameter in the Argon2 specification. It can
// be adjusted to the number of available CPUs. Must be > 0.
// @inject_tag: `gorm:"default:null"`
Threads uint32 `protobuf:"varint,6,opt,name=threads,proto3" json:"threads,omitempty" gorm:"default:null"`
// SaltLength is in bytes. Must be >= 16.
// @inject_tag: `gorm:"default:null"`
SaltLength uint32 `protobuf:"varint,7,opt,name=salt_length,json=saltLength,proto3" json:"salt_length,omitempty" gorm:"default:null"`
// KeyLength is in bytes. Must be >= 16.
// @inject_tag: `gorm:"default:null"`
KeyLength uint32 `protobuf:"varint,8,opt,name=key_length,json=keyLength,proto3" json:"key_length,omitempty" gorm:"default:null"`
}
func (x *Argon2Configuration) Reset() {
*x = Argon2Configuration{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_auth_password_store_v1_argon2_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Argon2Configuration) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Argon2Configuration) ProtoMessage() {}
func (x *Argon2Configuration) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_auth_password_store_v1_argon2_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Argon2Configuration.ProtoReflect.Descriptor instead.
func (*Argon2Configuration) Descriptor() ([]byte, []int) {
return file_controller_storage_auth_password_store_v1_argon2_proto_rawDescGZIP(), []int{0}
}
func (x *Argon2Configuration) GetPrivateId() string {
if x != nil {
return x.PrivateId
}
return ""
}
func (x *Argon2Configuration) GetCreateTime() *timestamp.Timestamp {
if x != nil {
return x.CreateTime
}
return nil
}
func (x *Argon2Configuration) GetPasswordMethodId() string {
if x != nil {
return x.PasswordMethodId
}
return ""
}
func (x *Argon2Configuration) GetIterations() uint32 {
if x != nil {
return x.Iterations
}
return 0
}
func (x *Argon2Configuration) GetMemory() uint32 {
if x != nil {
return x.Memory
}
return 0
}
func (x *Argon2Configuration) GetThreads() uint32 {
if x != nil {
return x.Threads
}
return 0
}
func (x *Argon2Configuration) GetSaltLength() uint32 {
if x != nil {
return x.SaltLength
}
return 0
}
func (x *Argon2Configuration) GetKeyLength() uint32 {
if x != nil {
return x.KeyLength
}
return 0
}
var File_controller_storage_auth_password_store_v1_argon2_proto protoreflect.FileDescriptor
var file_controller_storage_auth_password_store_v1_argon2_proto_rawDesc = []byte{
0x0a, 0x36, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f,
0x72, 0x61, 0x67, 0x65, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f,
0x72, 0x64, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x72, 0x67, 0x6f,
0x6e, 0x32, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x29, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x61, 0x75, 0x74,
0x68, 0x2e, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65,
0x2e, 0x76, 0x31, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f,
0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc1, 0x02, 0x0a, 0x13, 0x41, 0x72, 0x67, 0x6f, 0x6e, 0x32, 0x43,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a,
0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x09, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63,
0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74,
0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e,
0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72,
0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x61, 0x73, 0x73,
0x77, 0x6f, 0x72, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x4d, 0x65,
0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x69, 0x74, 0x65, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79,
0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x18,
0x0a, 0x07, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x07, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x61, 0x6c, 0x74,
0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73,
0x61, 0x6c, 0x74, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x79,
0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x6b,
0x65, 0x79, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x42, 0x44, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70,
0x2f, 0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65,
0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f,
0x72, 0x64, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_controller_storage_auth_password_store_v1_argon2_proto_rawDescOnce sync.Once
file_controller_storage_auth_password_store_v1_argon2_proto_rawDescData = file_controller_storage_auth_password_store_v1_argon2_proto_rawDesc
)
func file_controller_storage_auth_password_store_v1_argon2_proto_rawDescGZIP() []byte {
file_controller_storage_auth_password_store_v1_argon2_proto_rawDescOnce.Do(func() {
file_controller_storage_auth_password_store_v1_argon2_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_auth_password_store_v1_argon2_proto_rawDescData)
})
return file_controller_storage_auth_password_store_v1_argon2_proto_rawDescData
}
var file_controller_storage_auth_password_store_v1_argon2_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_controller_storage_auth_password_store_v1_argon2_proto_goTypes = []interface{}{
(*Argon2Configuration)(nil), // 0: controller.storage.auth.password.store.v1.Argon2Configuration
(*timestamp.Timestamp)(nil), // 1: controller.storage.timestamp.v1.Timestamp
}
var file_controller_storage_auth_password_store_v1_argon2_proto_depIdxs = []int32{
1, // 0: controller.storage.auth.password.store.v1.Argon2Configuration.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_controller_storage_auth_password_store_v1_argon2_proto_init() }
func file_controller_storage_auth_password_store_v1_argon2_proto_init() {
if File_controller_storage_auth_password_store_v1_argon2_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_controller_storage_auth_password_store_v1_argon2_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Argon2Configuration); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_storage_auth_password_store_v1_argon2_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_controller_storage_auth_password_store_v1_argon2_proto_goTypes,
DependencyIndexes: file_controller_storage_auth_password_store_v1_argon2_proto_depIdxs,
MessageInfos: file_controller_storage_auth_password_store_v1_argon2_proto_msgTypes,
}.Build()
File_controller_storage_auth_password_store_v1_argon2_proto = out.File
file_controller_storage_auth_password_store_v1_argon2_proto_rawDesc = nil
file_controller_storage_auth_password_store_v1_argon2_proto_goTypes = nil
file_controller_storage_auth_password_store_v1_argon2_proto_depIdxs = nil
}

@ -50,6 +50,8 @@ type AuthMethod struct {
// The scope_id of the owning scope. Must be set.
// @inject_tag: `gorm:"not_null"`
ScopeId string `protobuf:"bytes,6,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"not_null"`
// @inject_tag: `gorm:"not_null"`
PasswordConfId string `protobuf:"bytes,7,opt,name=password_conf_id,json=passwordConfId,proto3" json:"password_conf_id,omitempty" gorm:"not_null"`
// @inject_tag: `gorm:"default:null"`
MinUserNameLength uint32 `protobuf:"varint,8,opt,name=min_user_name_length,json=minUserNameLength,proto3" json:"min_user_name_length,omitempty" gorm:"default:null"`
// @inject_tag: `gorm:"default:null"`
@ -130,6 +132,13 @@ func (x *AuthMethod) GetScopeId() string {
return ""
}
func (x *AuthMethod) GetPasswordConfId() string {
if x != nil {
return x.PasswordConfId
}
return ""
}
func (x *AuthMethod) GetMinUserNameLength() uint32 {
if x != nil {
return x.MinUserNameLength
@ -272,7 +281,7 @@ var file_controller_storage_auth_password_store_v1_password_proto_rawDesc = []by
0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf5, 0x02, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x4d,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9f, 0x03, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x4d,
0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d,
@ -289,40 +298,42 @@ var file_controller_storage_auth_password_store_v1_password_proto_rawDesc = []by
0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18,
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x2f, 0x0a,
0x14, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x6c,
0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6d, 0x69, 0x6e,
0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x2e,
0x0a, 0x13, 0x6d, 0x69, 0x6e, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x6c,
0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6d, 0x69, 0x6e,
0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0xd4,
0x02, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75,
0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70,
0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74,
0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67,
0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54,
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74,
0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74,
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d,
0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63,
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65,
0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65,
0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f,
0x64, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68,
0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72,
0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65,
0x72, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x44, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x77, 0x61,
0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
0x6c, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x2f,
0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x28, 0x0a,
0x10, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x69,
0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
0x64, 0x43, 0x6f, 0x6e, 0x66, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x6d, 0x69, 0x6e, 0x5f, 0x75,
0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18,
0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61,
0x6d, 0x65, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x6d, 0x69, 0x6e, 0x5f,
0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18,
0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6d, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,
0x72, 0x64, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0xd4, 0x02, 0x0a, 0x07, 0x41, 0x63, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49,
0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b,
0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
0x6e, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20,
0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e,
0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x07,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64,
0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x42,
0x44, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61,
0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77,
0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x75, 0x74, 0x68,
0x2f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b,
0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

@ -1660,7 +1660,7 @@ begin;
auth_method auth_password_method auth_password_conf
public_id (pk) public_id (pk,fk) public_id (pk,fk)
public_id (pk) public_id (pk,fk) private_id (pk,fk)
scope_id (fk) scope_id (fk) password_method_id (fk)
...
@ -1674,7 +1674,7 @@ begin;
auth_account auth_password_account auth_password_credential
public_id (pk) public_id (pk,fk2) public_id (pk)
public_id (pk) public_id (pk,fk2) private_id (pk)
scope_id (fk1,fk2) fk2 scope_id (fk1,fk2) fk2 password_method_id (fk1,fk2)
auth_method_id (fk1) auth_method_id (fk1,fk2) password_conf_id (fk1)
iam_user_id (fk2) ... password_account_id (fk2)
@ -1714,6 +1714,7 @@ begin;
create table auth_password_method (
public_id wt_public_id primary key,
scope_id wt_scope_id not null,
password_conf_id wt_private_id not null, -- FK to auth_password_conf added below
name text,
description text,
create_time wt_timestamp,
@ -1769,15 +1770,23 @@ begin;
for each row execute procedure insert_auth_account_subtype();
create table auth_password_conf (
public_id wt_public_id primary key,
private_id wt_private_id primary key,
password_method_id wt_public_id not null
references auth_password_method (public_id)
on delete cascade
on update cascade
deferrable initially deferred,
unique(password_method_id, public_id)
unique(password_method_id, private_id)
);
alter table auth_password_method
add constraint current_conf_fkey
foreign key (public_id, password_conf_id)
references auth_password_conf (password_method_id, private_id)
on delete cascade
on update cascade
deferrable initially deferred;
-- insert_auth_password_conf_subtype() is a trigger function for subtypes of
-- auth_password_conf
create or replace function
@ -1786,20 +1795,20 @@ begin;
as $$
begin
insert into auth_password_conf
(public_id, password_method_id)
(private_id, password_method_id)
values
(new.public_id, new.password_method_id);
(new.private_id, new.password_method_id);
return new;
end;
$$ language plpgsql;
create table auth_password_credential (
public_id wt_public_id primary key,
private_id wt_private_id primary key,
password_account_id wt_public_id not null unique,
password_conf_id wt_public_id not null,
password_conf_id wt_private_id not null,
password_method_id wt_public_id not null,
foreign key (password_method_id, password_conf_id)
references auth_password_conf (password_method_id, public_id)
references auth_password_conf (password_method_id, private_id)
on delete cascade
on update cascade,
foreign key (password_method_id, password_account_id)
@ -1823,16 +1832,16 @@ begin;
where auth_password_account.public_id = new.password_account_id;
insert into auth_password_credential
(public_id, password_account_id, password_conf_id, password_method_id)
(private_id, password_account_id, password_conf_id, password_method_id)
values
(new.public_id, new.password_account_id, new.password_conf_id, new.password_method_id);
(new.private_id, new.password_account_id, new.password_conf_id, new.password_method_id);
return new;
end;
$$ language plpgsql;
--
-- triggers for time columns
---
--
create trigger
update_time_column
@ -1870,13 +1879,151 @@ begin;
insert on auth_password_account
for each row execute procedure default_create_time();
insert into oplog_ticket (name, version)
-- The tickets for oplog are the subtypes not the base types because no updates
-- are done to any values in the base types.
insert into oplog_ticket
(name, version)
values
('auth_password_method', 1),
('auth_password_account', 1);
commit;
`),
},
"migrations/13_auth_password_argon.down.sql": {
name: "13_auth_password_argon.down.sql",
bytes: []byte(`
begin;
drop table auth_password_argon2_conf;
commit;
`),
},
"migrations/13_auth_password_argon.up.sql": {
name: "13_auth_password_argon.up.sql",
bytes: []byte(`
begin;
create table auth_password_argon2_conf (
private_id wt_private_id primary key
references auth_password_conf (private_id)
on delete cascade
on update cascade,
password_method_id wt_public_id not null,
create_time wt_timestamp,
iterations int not null default 3
check(iterations > 0),
memory int not null default 65536
check(memory > 0),
threads int not null default 1
check(threads > 0),
-- salt_length unit is bytes
salt_length int not null default 32
-- minimum of 16 bytes (128 bits)
check(salt_length >= 16),
-- key_length unit is bytes
key_length int not null default 32
-- minimum of 16 bytes (128 bits)
check(key_length >= 16),
unique(password_method_id, iterations, memory, threads, salt_length, key_length),
unique (password_method_id, private_id),
foreign key (password_method_id, private_id)
references auth_password_conf (password_method_id, private_id)
on delete cascade
on update cascade
deferrable initially deferred
);
create or replace function
read_only_auth_password_argon2_conf()
returns trigger
as $$
begin
raise exception 'auth_password_argon2_conf is read-only';
end;
$$ language plpgsql;
create trigger
read_only_auth_password_argon2_conf
before
update on auth_password_argon2_conf
for each row execute procedure read_only_auth_password_argon2_conf();
create trigger
insert_auth_password_conf_subtype
before insert on auth_password_argon2_conf
for each row execute procedure insert_auth_password_conf_subtype();
--
-- triggers for time columns
--
create trigger
immutable_create_time
before
update on auth_password_argon2_conf
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on auth_password_argon2_conf
for each row execute procedure default_create_time();
-- The tickets for oplog are the subtypes not the base types because no updates
-- are done to any values in the base types.
insert into oplog_ticket
(name, version)
values
('auth_password_argon2_conf', 1),
('auth_password_argon2_cred', 1);
commit;
`),
},
"migrations/14_auth_password_views.down.sql": {
name: "14_auth_password_views.down.sql",
bytes: []byte(`
begin;
drop view auth_password_current_conf;
drop view auth_password_conf_union;
commit;
`),
},
"migrations/14_auth_password_views.up.sql": {
name: "14_auth_password_views.up.sql",
bytes: []byte(`
begin;
-- auth_password_conf_union is a union of the configuration settings
-- of all supported key derivation functions.
-- It will be updated as new key derivation functions are supported.
create or replace view auth_password_conf_union as
-- Do not change the order of the columns when adding new configurations.
-- Union with new tables appending new columns as needed.
select c.password_method_id, c.private_id as password_conf_id, c.private_id,
'argon2' as conf_type,
c.iterations, c.memory, c.threads, c.salt_length, c.key_length
from auth_password_argon2_conf c;
-- auth_password_current_conf provides a view of the current password
-- configuration for each password auth method.
-- The view will be updated as new key derivation functions are supported
-- but the query to create the view should not need to be updated.
create or replace view auth_password_current_conf as
-- Rerun this query whenever auth_password_conf_union is updated.
select pm.min_user_name_length, pm.min_password_length, c.*
from auth_password_method pm
inner join auth_password_conf_union c
on pm.password_conf_id = c.password_conf_id;
commit;
`),
},
}

@ -5,7 +5,7 @@ begin;
auth_method auth_password_method auth_password_conf
public_id (pk) public_id (pk,fk) public_id (pk,fk)
public_id (pk) public_id (pk,fk) private_id (pk,fk)
scope_id (fk) scope_id (fk) password_method_id (fk)
...
@ -19,7 +19,7 @@ begin;
auth_account auth_password_account auth_password_credential
public_id (pk) public_id (pk,fk2) public_id (pk)
public_id (pk) public_id (pk,fk2) private_id (pk)
scope_id (fk1,fk2) fk2 scope_id (fk1,fk2) fk2 password_method_id (fk1,fk2)
auth_method_id (fk1) auth_method_id (fk1,fk2) password_conf_id (fk1)
iam_user_id (fk2) ... password_account_id (fk2)
@ -59,6 +59,7 @@ begin;
create table auth_password_method (
public_id wt_public_id primary key,
scope_id wt_scope_id not null,
password_conf_id wt_private_id not null, -- FK to auth_password_conf added below
name text,
description text,
create_time wt_timestamp,
@ -114,15 +115,23 @@ begin;
for each row execute procedure insert_auth_account_subtype();
create table auth_password_conf (
public_id wt_public_id primary key,
private_id wt_private_id primary key,
password_method_id wt_public_id not null
references auth_password_method (public_id)
on delete cascade
on update cascade
deferrable initially deferred,
unique(password_method_id, public_id)
unique(password_method_id, private_id)
);
alter table auth_password_method
add constraint current_conf_fkey
foreign key (public_id, password_conf_id)
references auth_password_conf (password_method_id, private_id)
on delete cascade
on update cascade
deferrable initially deferred;
-- insert_auth_password_conf_subtype() is a trigger function for subtypes of
-- auth_password_conf
create or replace function
@ -131,20 +140,20 @@ begin;
as $$
begin
insert into auth_password_conf
(public_id, password_method_id)
(private_id, password_method_id)
values
(new.public_id, new.password_method_id);
(new.private_id, new.password_method_id);
return new;
end;
$$ language plpgsql;
create table auth_password_credential (
public_id wt_public_id primary key,
private_id wt_private_id primary key,
password_account_id wt_public_id not null unique,
password_conf_id wt_public_id not null,
password_conf_id wt_private_id not null,
password_method_id wt_public_id not null,
foreign key (password_method_id, password_conf_id)
references auth_password_conf (password_method_id, public_id)
references auth_password_conf (password_method_id, private_id)
on delete cascade
on update cascade,
foreign key (password_method_id, password_account_id)
@ -168,16 +177,16 @@ begin;
where auth_password_account.public_id = new.password_account_id;
insert into auth_password_credential
(public_id, password_account_id, password_conf_id, password_method_id)
(private_id, password_account_id, password_conf_id, password_method_id)
values
(new.public_id, new.password_account_id, new.password_conf_id, new.password_method_id);
(new.private_id, new.password_account_id, new.password_conf_id, new.password_method_id);
return new;
end;
$$ language plpgsql;
--
-- triggers for time columns
---
--
create trigger
update_time_column
@ -215,7 +224,10 @@ begin;
insert on auth_password_account
for each row execute procedure default_create_time();
insert into oplog_ticket (name, version)
-- The tickets for oplog are the subtypes not the base types because no updates
-- are done to any values in the base types.
insert into oplog_ticket
(name, version)
values
('auth_password_method', 1),
('auth_password_account', 1);

@ -0,0 +1,5 @@
begin;
drop table auth_password_argon2_conf;
commit;

@ -0,0 +1,75 @@
begin;
create table auth_password_argon2_conf (
private_id wt_private_id primary key
references auth_password_conf (private_id)
on delete cascade
on update cascade,
password_method_id wt_public_id not null,
create_time wt_timestamp,
iterations int not null default 3
check(iterations > 0),
memory int not null default 65536
check(memory > 0),
threads int not null default 1
check(threads > 0),
-- salt_length unit is bytes
salt_length int not null default 32
-- minimum of 16 bytes (128 bits)
check(salt_length >= 16),
-- key_length unit is bytes
key_length int not null default 32
-- minimum of 16 bytes (128 bits)
check(key_length >= 16),
unique(password_method_id, iterations, memory, threads, salt_length, key_length),
unique (password_method_id, private_id),
foreign key (password_method_id, private_id)
references auth_password_conf (password_method_id, private_id)
on delete cascade
on update cascade
deferrable initially deferred
);
create or replace function
read_only_auth_password_argon2_conf()
returns trigger
as $$
begin
raise exception 'auth_password_argon2_conf is read-only';
end;
$$ language plpgsql;
create trigger
read_only_auth_password_argon2_conf
before
update on auth_password_argon2_conf
for each row execute procedure read_only_auth_password_argon2_conf();
create trigger
insert_auth_password_conf_subtype
before insert on auth_password_argon2_conf
for each row execute procedure insert_auth_password_conf_subtype();
--
-- triggers for time columns
--
create trigger
immutable_create_time
before
update on auth_password_argon2_conf
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on auth_password_argon2_conf
for each row execute procedure default_create_time();
-- The tickets for oplog are the subtypes not the base types because no updates
-- are done to any values in the base types.
insert into oplog_ticket
(name, version)
values
('auth_password_argon2_conf', 1),
('auth_password_argon2_cred', 1);
commit;

@ -0,0 +1,6 @@
begin;
drop view auth_password_current_conf;
drop view auth_password_conf_union;
commit;

@ -0,0 +1,25 @@
begin;
-- auth_password_conf_union is a union of the configuration settings
-- of all supported key derivation functions.
-- It will be updated as new key derivation functions are supported.
create or replace view auth_password_conf_union as
-- Do not change the order of the columns when adding new configurations.
-- Union with new tables appending new columns as needed.
select c.password_method_id, c.private_id as password_conf_id, c.private_id,
'argon2' as conf_type,
c.iterations, c.memory, c.threads, c.salt_length, c.key_length
from auth_password_argon2_conf c;
-- auth_password_current_conf provides a view of the current password
-- configuration for each password auth method.
-- The view will be updated as new key derivation functions are supported
-- but the query to create the view should not need to be updated.
create or replace view auth_password_current_conf as
-- Rerun this query whenever auth_password_conf_union is updated.
select pm.min_user_name_length, pm.min_password_length, c.*
from auth_password_method pm
inner join auth_password_conf_union c
on pm.password_conf_id = c.password_conf_id;
commit;

@ -0,0 +1,52 @@
syntax = "proto3";
// Package store provides protobufs for storing types in the password package.
package controller.storage.auth.password.store.v1;
option go_package = "github.com/hashicorp/watchtower/internal/auth/password/store;store";
import "controller/storage/timestamp/v1/timestamp.proto";
// Argon2Configuration is a configuration for using the argon2id key
// derivation function. It is owned by an AuthMethod.
//
// Iterations, Memory, and Threads are the cost parameters. The cost
// parameters should be increased as memory latency and CPU parallelism
// increases.
//
// For a detailed specification of Argon2 see:
// https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
message Argon2Configuration {
// @inject_tag: `gorm:"primary_key"`
string private_id = 1;
// The create_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp create_time = 2;
// @inject_tag: `gorm:"not_null"`
string password_method_id = 3;
// Iterations is the time parameter in the Argon2 specification. It
// specifies the number of passes over the memory. Must be > 0.
// @inject_tag: `gorm:"default:null"`
uint32 iterations = 4;
// Memory is the memory parameter in the Argon2 specification. It
// specifies the size of the memory in KiB. For example Memory=32*1024
// sets the memory cost to ~32 MB. Must be > 0.
// @inject_tag: `gorm:"default:null"`
uint32 memory = 5;
// Threads is the threads parameter in the Argon2 specification. It can
// be adjusted to the number of available CPUs. Must be > 0.
// @inject_tag: `gorm:"default:null"`
uint32 threads = 6;
// SaltLength is in bytes. Must be >= 16.
// @inject_tag: `gorm:"default:null"`
uint32 salt_length = 7;
// KeyLength is in bytes. Must be >= 16.
// @inject_tag: `gorm:"default:null"`
uint32 key_length = 8;
}

@ -30,6 +30,9 @@ message AuthMethod {
// @inject_tag: `gorm:"not_null"`
string scope_id = 6;
// @inject_tag: `gorm:"not_null"`
string password_conf_id = 7;
// @inject_tag: `gorm:"default:null"`
uint32 min_user_name_length = 8;

Loading…
Cancel
Save