Skip to content

Commit c24b502

Browse files
committed
1 parent 87cb7b3 commit c24b502

File tree

8 files changed

+136
-39
lines changed

8 files changed

+136
-39
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
.work
99
_output
1010
__debug_bin
11+
deploy

apis/mssql/v1alpha1/user_types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ import (
2222
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
2323
)
2424

25+
// UserTypes
26+
const (
27+
UserTypeLocal = "Local"
28+
UserTypeAD = "AD"
29+
)
30+
2531
// A UserSpec defines the desired state of a Database.
2632
type UserSpec struct {
2733
xpv1.ResourceSpec `json:",inline"`
@@ -56,6 +62,7 @@ type UserParameters struct {
5662
LoginDatabaseRef *xpv1.Reference `json:"loginDatabaseRef,omitempty"`
5763
// DatabaseSelector allows you to use selector constraints to select a Database to be used to create the user LOGIN in (normally master).
5864
LoginDatabaseSelector *xpv1.Selector `json:"loginDatabaseSelector,omitempty"`
65+
Type *string `json:"type,omitempty"`
5966
}
6067

6168
// A UserObservation represents the observed state of a MSSQL user.

cmd/provider/main.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
_ "github.com/go-sql-driver/mysql"
2424
_ "github.com/lib/pq"
2525
_ "github.com/microsoft/go-mssqldb"
26+
_ "github.com/microsoft/go-mssqldb/azuread"
2627

2728
"gopkg.in/alecthomas/kingpin.v2"
2829
ctrl "sigs.k8s.io/controller-runtime"
@@ -34,6 +35,9 @@ import (
3435

3536
"github.com/crossplane-contrib/provider-sql/apis"
3637
"github.com/crossplane-contrib/provider-sql/pkg/controller"
38+
39+
azlog "github.com/Azure/azure-sdk-for-go/sdk/azcore/log"
40+
azidentity "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
3741
)
3842

3943
func main() {
@@ -53,6 +57,14 @@ func main() {
5357
// *very* verbose even at info level, so we only provide it a real
5458
// logger when we're running in debug mode.
5559
ctrl.SetLogger(zl)
60+
61+
// print log output to stdout
62+
azlog.SetListener(func(event azlog.Event, s string) {
63+
log.Debug(s) //fmt.Println(s)
64+
})
65+
66+
// include only azidentity credential logs
67+
azlog.SetEvents(azidentity.EventAuthentication)
5668
}
5769

5870
log.Debug("Starting", "sync-period", syncPeriod.String())

go.mod

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ go 1.21
55
toolchain go1.21.11
66

77
require (
8+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1
9+
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1
810
github.com/DATA-DOG/go-sqlmock v1.5.0
911
github.com/crossplane/crossplane-runtime v1.16.0
1012
github.com/crossplane/crossplane-tools v0.0.0-20240522174801-1ad3d4c87f21
@@ -23,6 +25,8 @@ require (
2325

2426
require (
2527
dario.cat/mergo v1.0.0 // indirect
28+
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 // indirect
29+
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect
2630
github.com/alecthomas/kingpin/v2 v2.4.0 // indirect
2731
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
2832
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
@@ -43,6 +47,7 @@ require (
4347
github.com/go-openapi/swag v0.22.3 // indirect
4448
github.com/gobuffalo/flect v1.0.2 // indirect
4549
github.com/gogo/protobuf v1.3.2 // indirect
50+
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
4651
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
4752
github.com/golang-sql/sqlexp v0.1.0 // indirect
4853
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
@@ -54,13 +59,15 @@ require (
5459
github.com/inconshreveable/mousetrap v1.1.0 // indirect
5560
github.com/josharian/intern v1.0.0 // indirect
5661
github.com/json-iterator/go v1.1.12 // indirect
62+
github.com/kylelemons/godebug v1.1.0 // indirect
5763
github.com/mailru/easyjson v0.7.7 // indirect
5864
github.com/mattn/go-colorable v0.1.13 // indirect
5965
github.com/mattn/go-isatty v0.0.20 // indirect
6066
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
6167
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
6268
github.com/modern-go/reflect2 v1.0.2 // indirect
6369
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
70+
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
6471
github.com/prometheus/client_golang v1.18.0 // indirect
6572
github.com/prometheus/client_model v0.5.0 // indirect
6673
github.com/prometheus/common v0.45.0 // indirect

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ github.com/dave/jennifer v1.7.0/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3
3737
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3838
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3939
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
40+
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
41+
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
4042
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
4143
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
4244
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
@@ -219,6 +221,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
219221
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
220222
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
221223
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
224+
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
222225
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
223226
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
224227
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

package/crds/mssql.sql.crossplane.io_users.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,8 @@ spec:
253253
- name
254254
- namespace
255255
type: object
256+
type:
257+
type: string
256258
type: object
257259
managementPolicies:
258260
default:

pkg/clients/mssql/mssql.go

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,26 @@ import (
3232
)
3333

3434
const (
35-
driverName = "sqlserver"
35+
schema = "sqlserver"
36+
stdDriverName = "sqlserver"
37+
azDriverName = "azuresql"
3638

3739
errNotSupported = "%s not supported by MSSQL client"
40+
fedauth = "fedauth"
3841
)
3942

4043
type mssqlDB struct {
4144
dsn string
4245
endpoint string
4346
port string
47+
driver string
4448
}
4549

4650
// New returns a new mssql database client.
4751
func New(creds map[string][]byte, database string) xsql.DB {
4852
endpoint := string(creds[xpv1.ResourceCredentialsSecretEndpointKey])
4953
port := string(creds[xpv1.ResourceCredentialsSecretPortKey])
50-
54+
driver := stdDriverName
5155
host := endpoint
5256
if port != "" {
5357
host = fmt.Sprintf("%s:%s", endpoint, port)
@@ -57,16 +61,39 @@ func New(creds map[string][]byte, database string) xsql.DB {
5761
if database != "" {
5862
query.Add("database", database)
5963
}
60-
u := &url.URL{
61-
Scheme: driverName,
62-
User: url.UserPassword(string(creds[xpv1.ResourceCredentialsSecretUserKey]), string(creds[xpv1.ResourceCredentialsSecretPasswordKey])),
63-
Host: host,
64-
RawQuery: query.Encode(),
64+
var u *url.URL
65+
if val, ok := creds[fedauth]; ok {
66+
authType := string(val)
67+
query.Add(fedauth, authType)
68+
// var user *url.Userinfo
69+
if authType == "ActiveDirectoryServicePrincipal" || authType == "ActiveDirectoryApplication" || authType == "ActiveDirectoryPassword" {
70+
query.Add("password", string(creds[xpv1.ResourceCredentialsSecretPasswordKey]))
71+
}
72+
if val, ok := creds[xpv1.ResourceCredentialsSecretUserKey]; ok {
73+
// user = url.User(string(val[:]))
74+
query.Add("user id", string(val))
75+
}
76+
u = &url.URL{
77+
Scheme: schema,
78+
// User: user,
79+
Host: host,
80+
RawQuery: query.Encode(),
81+
}
82+
driver = azDriverName
83+
} else {
84+
85+
u = &url.URL{
86+
Scheme: schema,
87+
User: url.UserPassword(string(creds[xpv1.ResourceCredentialsSecretUserKey]), string(creds[xpv1.ResourceCredentialsSecretPasswordKey])),
88+
Host: host,
89+
RawQuery: query.Encode(),
90+
}
6591
}
6692
return mssqlDB{
6793
dsn: u.String(),
6894
endpoint: endpoint,
6995
port: port,
96+
driver: driver,
7097
}
7198
}
7299

@@ -77,7 +104,7 @@ func (c mssqlDB) ExecTx(_ context.Context, _ []xsql.Query) error {
77104

78105
// Exec the supplied query.
79106
func (c mssqlDB) Exec(ctx context.Context, q xsql.Query) error {
80-
d, err := sql.Open(driverName, c.dsn)
107+
d, err := sql.Open(c.driver, c.dsn)
81108
if err != nil {
82109
return err
83110
}
@@ -89,7 +116,7 @@ func (c mssqlDB) Exec(ctx context.Context, q xsql.Query) error {
89116

90117
// Query the supplied query.
91118
func (c mssqlDB) Query(ctx context.Context, q xsql.Query) (*sql.Rows, error) {
92-
d, err := sql.Open(driverName, c.dsn)
119+
d, err := sql.Open(c.driver, c.dsn)
93120
if err != nil {
94121
return nil, err
95122
}
@@ -100,7 +127,7 @@ func (c mssqlDB) Query(ctx context.Context, q xsql.Query) (*sql.Rows, error) {
100127

101128
// Scan the results of the supplied query into the supplied destination.
102129
func (c mssqlDB) Scan(ctx context.Context, q xsql.Query, dest ...interface{}) error {
103-
db, err := sql.Open(driverName, c.dsn)
130+
db, err := sql.Open(c.driver, c.dsn)
104131
if err != nil {
105132
return err
106133
}

pkg/controller/mssql/user/reconciler.go

Lines changed: 67 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,21 @@ func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex
145145
}
146146

147147
var name string
148-
149-
query := "SELECT name FROM sys.database_principals WHERE type = 'S' AND name = @p1"
148+
var query string
149+
var userType string
150+
if cr.Spec.ForProvider.Type == nil {
151+
userType = v1alpha1.UserTypeLocal
152+
} else {
153+
userType = *cr.Spec.ForProvider.Type
154+
}
155+
switch userType {
156+
case v1alpha1.UserTypeAD:
157+
query = "SELECT name FROM sys.database_principals WHERE type IN ('E','X') AND name = @p1"
158+
case v1alpha1.UserTypeLocal:
159+
query = "SELECT name FROM sys.database_principals WHERE type = 'S' AND name = @p1"
160+
default:
161+
return managed.ExternalObservation{}, errors.Errorf("Type '%s' is not valid", *cr.Spec.ForProvider.Type)
162+
}
150163
err := c.userDB.Scan(ctx, xsql.Query{
151164
String: query, Parameters: []interface{}{
152165
meta.GetExternalName(cr),
@@ -177,30 +190,44 @@ func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.Ext
177190
if !ok {
178191
return managed.ExternalCreation{}, errors.New(errNotUser)
179192
}
180-
181-
pw, _, err := c.getPassword(ctx, cr)
182-
if err != nil {
183-
return managed.ExternalCreation{}, err
193+
var loginQuery string
194+
var pw string
195+
userType := v1alpha1.UserTypeLocal
196+
if cr.Spec.ForProvider.Type != nil {
197+
userType = *cr.Spec.ForProvider.Type
184198
}
185-
if pw == "" {
186-
pw, err = password.Generate()
199+
switch userType {
200+
case v1alpha1.UserTypeAD:
201+
loginQuery = fmt.Sprintf("CREATE USER %s FROM EXTERNAL PROVIDER", mssql.QuoteIdentifier(meta.GetExternalName(cr)))
202+
case v1alpha1.UserTypeLocal:
203+
var err error
204+
pw, _, err = c.getPassword(ctx, cr)
187205
if err != nil {
188206
return managed.ExternalCreation{}, err
189207
}
208+
if pw == "" {
209+
pw, err = password.Generate()
210+
if err != nil {
211+
return managed.ExternalCreation{}, err
212+
}
213+
}
214+
loginQuery = fmt.Sprintf("CREATE LOGIN %s WITH PASSWORD=%s", mssql.QuoteIdentifier(meta.GetExternalName(cr)), mssql.QuoteValue(pw))
215+
default:
216+
return managed.ExternalCreation{}, errors.Errorf("Type '%s' is not valid", *cr.Spec.ForProvider.Type)
190217
}
191-
192-
loginQuery := fmt.Sprintf("CREATE LOGIN %s WITH PASSWORD=%s", mssql.QuoteIdentifier(meta.GetExternalName(cr)), mssql.QuoteValue(pw))
193218
if err := c.loginDB.Exec(ctx, xsql.Query{
194219
String: loginQuery,
195220
}); err != nil {
196221
return managed.ExternalCreation{}, errors.Wrapf(err, errCreateLogin, meta.GetExternalName(cr))
197222
}
223+
if userType != v1alpha1.UserTypeAD {
198224

199-
userQuery := fmt.Sprintf("CREATE USER %s FOR LOGIN %s", mssql.QuoteIdentifier(meta.GetExternalName(cr)), mssql.QuoteIdentifier(meta.GetExternalName(cr)))
200-
if err := c.userDB.Exec(ctx, xsql.Query{
201-
String: userQuery,
202-
}); err != nil {
203-
return managed.ExternalCreation{}, errors.Wrapf(err, errCreateUser, meta.GetExternalName(cr))
225+
userQuery := fmt.Sprintf("CREATE USER %s FOR LOGIN %s", mssql.QuoteIdentifier(meta.GetExternalName(cr)), mssql.QuoteIdentifier(meta.GetExternalName(cr)))
226+
if err := c.userDB.Exec(ctx, xsql.Query{
227+
String: userQuery,
228+
}); err != nil {
229+
return managed.ExternalCreation{}, errors.Wrapf(err, errCreateUser, meta.GetExternalName(cr))
230+
}
204231
}
205232

206233
return managed.ExternalCreation{
@@ -213,23 +240,34 @@ func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.Ext
213240
if !ok {
214241
return managed.ExternalUpdate{}, errors.New(errNotUser)
215242
}
243+
if t := cr.Spec.ForProvider.Type; t == nil || *t == v1alpha1.UserTypeLocal {
216244

217-
pw, changed, err := c.getPassword(ctx, cr)
218-
if err != nil {
219-
return managed.ExternalUpdate{}, err
220-
}
221-
222-
if changed {
223-
query := fmt.Sprintf("ALTER LOGIN %s WITH PASSWORD=%s", mssql.QuoteIdentifier(meta.GetExternalName(cr)), mssql.QuoteValue(pw))
224-
if err := c.loginDB.Exec(ctx, xsql.Query{
225-
String: query,
226-
}); err != nil {
227-
return managed.ExternalUpdate{}, errors.Wrap(err, errUpdateUser)
245+
pw, changed, err := c.getPassword(ctx, cr)
246+
if err != nil {
247+
return managed.ExternalUpdate{}, err
228248
}
229249

230-
return managed.ExternalUpdate{
231-
ConnectionDetails: c.userDB.GetConnectionDetails(meta.GetExternalName(cr), pw),
232-
}, nil
250+
if changed {
251+
query := fmt.Sprintf("ALTER LOGIN %s WITH PASSWORD=%s", mssql.QuoteIdentifier(meta.GetExternalName(cr)), mssql.QuoteValue(pw))
252+
if err := c.userDB.Exec(ctx, xsql.Query{
253+
String: query,
254+
}); err != nil {
255+
return managed.ExternalUpdate{}, errors.Wrap(err, errUpdateUser)
256+
}
257+
258+
if changed {
259+
query := fmt.Sprintf("ALTER USER %s WITH PASSWORD=%s", mssql.QuoteIdentifier(meta.GetExternalName(cr)), mssql.QuoteValue(pw))
260+
if err := c.userDB.Exec(ctx, xsql.Query{
261+
String: query,
262+
}); err != nil {
263+
return managed.ExternalUpdate{}, errors.Wrap(err, errUpdateUser)
264+
}
265+
266+
return managed.ExternalUpdate{
267+
ConnectionDetails: c.userDB.GetConnectionDetails(meta.GetExternalName(cr), pw),
268+
}, nil
269+
}
270+
}
233271
}
234272
return managed.ExternalUpdate{}, nil
235273
}

0 commit comments

Comments
 (0)