Skip to content

Commit d97fe84

Browse files
authored
[management] fix race condition in the setup flow that enables creation of multiple owner users (#5754)
1 parent 81f45da commit d97fe84

2 files changed

Lines changed: 235 additions & 150 deletions

File tree

management/server/instance/manager.go

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,19 @@ type Manager interface {
6464
GetVersionInfo(ctx context.Context) (*VersionInfo, error)
6565
}
6666

67+
type instanceStore interface {
68+
GetAccountsCounter(ctx context.Context) (int64, error)
69+
}
70+
71+
type embeddedIdP interface {
72+
CreateUserWithPassword(ctx context.Context, email, password, name string) (*idp.UserData, error)
73+
GetAllAccounts(ctx context.Context) (map[string][]*idp.UserData, error)
74+
}
75+
6776
// DefaultManager is the default implementation of Manager.
6877
type DefaultManager struct {
69-
store store.Store
70-
embeddedIdpManager *idp.EmbeddedIdPManager
78+
store instanceStore
79+
embeddedIdpManager embeddedIdP
7180

7281
setupRequired bool
7382
setupMu sync.RWMutex
@@ -82,18 +91,18 @@ type DefaultManager struct {
8291
// NewManager creates a new instance manager.
8392
// If idpManager is not an EmbeddedIdPManager, setup-related operations will return appropriate defaults.
8493
func NewManager(ctx context.Context, store store.Store, idpManager idp.Manager) (Manager, error) {
85-
embeddedIdp, _ := idpManager.(*idp.EmbeddedIdPManager)
94+
embeddedIdp, ok := idpManager.(*idp.EmbeddedIdPManager)
8695

8796
m := &DefaultManager{
88-
store: store,
89-
embeddedIdpManager: embeddedIdp,
90-
setupRequired: false,
97+
store: store,
98+
setupRequired: false,
9199
httpClient: &http.Client{
92100
Timeout: httpTimeout,
93101
},
94102
}
95103

96-
if embeddedIdp != nil {
104+
if ok && embeddedIdp != nil {
105+
m.embeddedIdpManager = embeddedIdp
97106
err := m.loadSetupRequired(ctx)
98107
if err != nil {
99108
return nil, err
@@ -143,36 +152,61 @@ func (m *DefaultManager) IsSetupRequired(_ context.Context) (bool, error) {
143152
// CreateOwnerUser creates the initial owner user in the embedded IDP.
144153
func (m *DefaultManager) CreateOwnerUser(ctx context.Context, email, password, name string) (*idp.UserData, error) {
145154

146-
if err := m.validateSetupInfo(email, password, name); err != nil {
147-
return nil, err
148-
}
149-
150155
if m.embeddedIdpManager == nil {
151156
return nil, errors.New("embedded IDP is not enabled")
152157
}
153158

154-
m.setupMu.RLock()
155-
setupRequired := m.setupRequired
156-
m.setupMu.RUnlock()
159+
if err := m.validateSetupInfo(email, password, name); err != nil {
160+
return nil, err
161+
}
162+
163+
m.setupMu.Lock()
164+
defer m.setupMu.Unlock()
157165

158-
if !setupRequired {
166+
if !m.setupRequired {
159167
return nil, status.Errorf(status.PreconditionFailed, "setup already completed")
160168
}
161169

170+
if err := m.checkSetupRequiredFromDB(ctx); err != nil {
171+
var sErr *status.Error
172+
if errors.As(err, &sErr) && sErr.Type() == status.PreconditionFailed {
173+
m.setupRequired = false
174+
}
175+
return nil, err
176+
}
177+
162178
userData, err := m.embeddedIdpManager.CreateUserWithPassword(ctx, email, password, name)
163179
if err != nil {
164180
return nil, fmt.Errorf("failed to create user in embedded IdP: %w", err)
165181
}
166182

167-
m.setupMu.Lock()
168183
m.setupRequired = false
169-
m.setupMu.Unlock()
170184

171185
log.WithContext(ctx).Infof("created owner user %s in embedded IdP", email)
172186

173187
return userData, nil
174188
}
175189

190+
func (m *DefaultManager) checkSetupRequiredFromDB(ctx context.Context) error {
191+
numAccounts, err := m.store.GetAccountsCounter(ctx)
192+
if err != nil {
193+
return fmt.Errorf("failed to check accounts: %w", err)
194+
}
195+
if numAccounts > 0 {
196+
return status.Errorf(status.PreconditionFailed, "setup already completed")
197+
}
198+
199+
users, err := m.embeddedIdpManager.GetAllAccounts(ctx)
200+
if err != nil {
201+
return fmt.Errorf("failed to check IdP users: %w", err)
202+
}
203+
if len(users) > 0 {
204+
return status.Errorf(status.PreconditionFailed, "setup already completed")
205+
}
206+
207+
return nil
208+
}
209+
176210
func (m *DefaultManager) validateSetupInfo(email, password, name string) error {
177211
if email == "" {
178212
return status.Errorf(status.InvalidArgument, "email is required")
@@ -189,6 +223,9 @@ func (m *DefaultManager) validateSetupInfo(email, password, name string) error {
189223
if len(password) < 8 {
190224
return status.Errorf(status.InvalidArgument, "password must be at least 8 characters")
191225
}
226+
if len(password) > 72 {
227+
return status.Errorf(status.InvalidArgument, "password must be at most 72 characters")
228+
}
192229
return nil
193230
}
194231

0 commit comments

Comments
 (0)