@@ -2,7 +2,9 @@ package connector
22
33import (
44 "context"
5+ "crypto/rand"
56 "fmt"
7+ "math/big"
68 "net/mail"
79
810 v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
@@ -129,12 +131,9 @@ func (d *userPrincipalSyncer) CreateAccount(
129131 formattedUsername = username
130132 }
131133 case mssqldb .LoginTypeSQL :
132- // For SQL auth, extract password
133- passwordVal := accountInfo .Profile .GetFields ()["password" ]
134- if passwordVal == nil || passwordVal .GetStringValue () == "" {
135- return nil , nil , nil , fmt .Errorf ("missing required password field for SQL Server authentication" )
136- }
137- password = passwordVal .GetStringValue ()
134+ // For SQL auth, generate a strong random password
135+ password = generateStrongPassword ()
136+ l .Debug ("generated random password for SQL Server authentication" )
138137 formattedUsername = username
139138 case mssqldb .LoginTypeAzureAD , mssqldb .LoginTypeEntraID :
140139 // For Azure AD or Entra ID, just use the username as is
@@ -183,13 +182,26 @@ func (d *userPrincipalSyncer) CreateAccount(
183182 return nil , nil , nil , fmt .Errorf ("failed to create resource for new user: %w" , err )
184183 }
185184
186- // Return success result with the new user resource
185+ // Prepare the response - for SQL auth, we need to return the generated password
187186 successResult := & v2.CreateAccountResponse_SuccessResult {
188187 Resource : resource ,
189188 IsCreateAccountResult : true ,
190189 }
191190
192- return successResult , nil , nil , nil
191+ var plaintextData []* v2.PlaintextData
192+ // If this is SQL authentication, return the generated password
193+ if loginType == mssqldb .LoginTypeSQL {
194+ plaintextData = []* v2.PlaintextData {
195+ {
196+ Name : "password" ,
197+ Description : "The generated password for SQL Server authentication" ,
198+ Schema : "text/plain" ,
199+ Bytes : []byte (password ),
200+ },
201+ }
202+ }
203+
204+ return successResult , plaintextData , nil , nil
193205}
194206
195207// CreateAccountCapabilityDetails returns the capability details for account creation.
@@ -198,12 +210,60 @@ func (d *userPrincipalSyncer) CreateAccountCapabilityDetails(
198210) (* v2.CredentialDetailsAccountProvisioning , annotations.Annotations , error ) {
199211 return & v2.CredentialDetailsAccountProvisioning {
200212 SupportedCredentialOptions : []v2.CapabilityDetailCredentialOption {
201- v2 .CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_NO_PASSWORD ,
213+ v2 .CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_NO_PASSWORD , // For Windows/Azure AD/Entra ID
214+ v2 .CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_RANDOM_PASSWORD , // For SQL Server auth
202215 },
203216 PreferredCredentialOption : v2 .CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_NO_PASSWORD ,
204217 }, nil , nil
205218}
206219
220+ // generateStrongPassword creates a secure random password for SQL Server.
221+ // The password meets SQL Server complexity requirements:
222+ // - At least 8 characters in length
223+ // - Contains uppercase, lowercase, numbers, and special characters.
224+ func generateStrongPassword () string {
225+ const (
226+ uppercaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
227+ lowercaseChars = "abcdefghijklmnopqrstuvwxyz"
228+ numberChars = "0123456789"
229+ specialChars = "!@#$%^&*()-_=+[]{}|;:,.<>?"
230+ passwordLength = 16
231+ )
232+
233+ // Ensure at least one character from each category
234+ password := make ([]byte , passwordLength )
235+
236+ // Add at least one character from each required group
237+ addRandomChar := func (charSet string , position int ) {
238+ maxVal := big .NewInt (int64 (len (charSet )))
239+ randomIndex , _ := rand .Int (rand .Reader , maxVal )
240+ password [position ] = charSet [randomIndex .Int64 ()]
241+ }
242+
243+ // Add one of each required character type
244+ addRandomChar (uppercaseChars , 0 )
245+ addRandomChar (lowercaseChars , 1 )
246+ addRandomChar (numberChars , 2 )
247+ addRandomChar (specialChars , 3 )
248+
249+ // Fill the rest with random characters from all sets
250+ allChars := uppercaseChars + lowercaseChars + numberChars + specialChars
251+ for i := 4 ; i < passwordLength ; i ++ {
252+ maxVal := big .NewInt (int64 (len (allChars )))
253+ randomIndex , _ := rand .Int (rand .Reader , maxVal )
254+ password [i ] = allChars [randomIndex .Int64 ()]
255+ }
256+
257+ // Shuffle the password to avoid predictable positions of character types
258+ for i := passwordLength - 1 ; i > 0 ; i -- {
259+ maxVal := big .NewInt (int64 (i + 1 ))
260+ j , _ := rand .Int (rand .Reader , maxVal )
261+ password [i ], password [j .Int64 ()] = password [j .Int64 ()], password [i ]
262+ }
263+
264+ return string (password )
265+ }
266+
207267func newUserPrincipalSyncer (ctx context.Context , c * mssqldb.Client ) * userPrincipalSyncer {
208268 return & userPrincipalSyncer {
209269 resourceType : resourceTypeUser ,
0 commit comments