Skip to content

Commit e5b6d19

Browse files
authored
*: Support to configure grant option and SSL when creating users. (#575)
* feat(user): create user with grant add options 'WithGrantOption' fix: #557 * feat(user): create user with ssl options add options `TLSOptions` support REQUIRE NONE/SSL/X509 TODO: Issuer, Subject and Cipher. fix: #573 * feat(user): add printcolumn for mysql users
1 parent 0db4b35 commit e5b6d19

File tree

6 files changed

+172
-11
lines changed

6 files changed

+172
-11
lines changed

api/v1alpha1/mysqluser_types.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ type UserSpec struct {
5151
// Permissions is the list of roles that user has in the specified database.
5252
// +optional
5353
Permissions []UserPermission `json:"permissions,omitempty"`
54+
55+
// WithGrantOption is the flag to indicate whether the user has grant option.
56+
// +optional
57+
// +kubebuilder:default:=false
58+
WithGrantOption bool `json:"withGrantOption,omitempty"`
59+
60+
// TLSOptions contains the TLS parameter of the MySQL user.
61+
// Enabling SSL/TLS will encrypt the data being sent to and from the database.
62+
// https://dev.mysql.com/doc/refman/5.7/en/create-user.html
63+
// +kubebuilder:default:={type: "NONE"}
64+
TLSOptions TLSOptions `json:"tlsOptions,omitempty"`
5465
}
5566

5667
type UserOwner struct {
@@ -85,6 +96,15 @@ type UserPermission struct {
8596
Privileges []string `json:"privileges,omitempty"`
8697
}
8798

99+
type TLSOptions struct {
100+
// TLS options for the client certificates
101+
// +kubebuilder:default:=NONE
102+
// +kubebuilder:validation:Enum=NONE;SSL;X509;
103+
Type string `json:"type,omitempty"`
104+
105+
// TODO: support Issuer, Subject and Cipher.
106+
}
107+
88108
// UserStatus defines the observed state of MysqlUser.
89109
type UserStatus struct {
90110
// Conditions represents the MysqlUser resource conditions list.
@@ -122,6 +142,15 @@ type MySQLUserCondition struct {
122142
//+kubebuilder:object:root=true
123143
//+kubebuilder:subresource:status
124144
//+kubebuilder:subresource:finalizers
145+
// +kubebuilder:printcolumn:name="UserName",type="string",JSONPath=".spec.user",description="The name of the MySQL user"
146+
// +kubebuilder:printcolumn:name="SuperUser",type="boolean",JSONPath=".spec.withGrantOption",description="Whether the user can grant other users"
147+
// +kubebuilder:printcolumn:name="Hosts",type="string",JSONPath=".spec.hosts",description="The hosts that the user is allowed to connect from"
148+
// +kubebuilder:printcolumn:name="TLSType",type="string",JSONPath=".spec.tlsOptions.type",description="TLS type of user"
149+
// +kubebuilder:printcolumn:name="Cluster",type="string",JSONPath=".spec.userOwner.clusterName",description="The cluster of the user"
150+
// +kubebuilder:printcolumn:name="NameSpace",type="string",JSONPath=".spec.userOwner.nameSpace",description="The namespace of the user"
151+
// +kubebuilder:printcolumn:name="Available",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="The availability of the user"
152+
// +kubebuilder:printcolumn:name="SecretName",type="string",priority=1,JSONPath=".spec.secretSelector.secretName",description="The name of the secret object"
153+
// +kubebuilder:printcolumn:name="SecretKey",type="string",priority=1,JSONPath=".spec.secretSelector.secretKey",description="The key of the secret object"
125154
// MysqlUser is the Schema for the users API.
126155
type MysqlUser struct {
127156
metav1.TypeMeta `json:",inline"`

charts/mysql-operator/crds/mysql.radondb.com_mysqlusers.yaml

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,46 @@ spec:
1616
singular: mysqluser
1717
scope: Namespaced
1818
versions:
19-
- name: v1alpha1
19+
- additionalPrinterColumns:
20+
- description: The name of the MySQL user
21+
jsonPath: .spec.user
22+
name: UserName
23+
type: string
24+
- description: Whether the user can grant other users
25+
jsonPath: .spec.withGrantOption
26+
name: SuperUser
27+
type: boolean
28+
- description: The hosts that the user is allowed to connect from
29+
jsonPath: .spec.hosts
30+
name: Hosts
31+
type: string
32+
- description: TLS type of user
33+
jsonPath: .spec.tlsOptions.type
34+
name: TLSType
35+
type: string
36+
- description: The cluster of the user
37+
jsonPath: .spec.userOwner.clusterName
38+
name: Cluster
39+
type: string
40+
- description: The namespace of the user
41+
jsonPath: .spec.userOwner.nameSpace
42+
name: NameSpace
43+
type: string
44+
- description: The availability of the user
45+
jsonPath: .status.conditions[?(@.type=="Ready")].status
46+
name: Available
47+
type: string
48+
- description: The name of the secret object
49+
jsonPath: .spec.secretSelector.secretName
50+
name: SecretName
51+
priority: 1
52+
type: string
53+
- description: The key of the secret object
54+
jsonPath: .spec.secretSelector.secretKey
55+
name: SecretKey
56+
priority: 1
57+
type: string
58+
name: v1alpha1
2059
schema:
2160
openAPIV3Schema:
2261
description: MysqlUser is the Schema for the users API.
@@ -79,6 +118,22 @@ spec:
79118
description: SecretName is the name of secret object.
80119
type: string
81120
type: object
121+
tlsOptions:
122+
default:
123+
type: NONE
124+
description: TLSOptions contains the TLS parameter of the MySQL user.
125+
Enabling SSL/TLS will encrypt the data being sent to and from the
126+
database. https://dev.mysql.com/doc/refman/5.7/en/create-user.html
127+
properties:
128+
type:
129+
default: NONE
130+
description: TLS options for the client certificates
131+
enum:
132+
- NONE
133+
- SSL
134+
- X509
135+
type: string
136+
type: object
82137
user:
83138
description: Username is the name of user to be operated. This field
84139
should be immutable.
@@ -95,6 +150,11 @@ spec:
95150
description: NameSpace is the nameSpace of cluster.
96151
type: string
97152
type: object
153+
withGrantOption:
154+
default: false
155+
description: WithGrantOption is the flag to indicate whether the user
156+
has grant option.
157+
type: boolean
98158
type: object
99159
status:
100160
description: UserStatus defines the observed state of MysqlUser.

config/crd/bases/mysql.radondb.com_mysqlusers.yaml

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,46 @@ spec:
1616
singular: mysqluser
1717
scope: Namespaced
1818
versions:
19-
- name: v1alpha1
19+
- additionalPrinterColumns:
20+
- description: The name of the MySQL user
21+
jsonPath: .spec.user
22+
name: UserName
23+
type: string
24+
- description: Whether the user can grant other users
25+
jsonPath: .spec.withGrantOption
26+
name: SuperUser
27+
type: boolean
28+
- description: The hosts that the user is allowed to connect from
29+
jsonPath: .spec.hosts
30+
name: Hosts
31+
type: string
32+
- description: TLS type of user
33+
jsonPath: .spec.tlsOptions.type
34+
name: TLSType
35+
type: string
36+
- description: The cluster of the user
37+
jsonPath: .spec.userOwner.clusterName
38+
name: Cluster
39+
type: string
40+
- description: The namespace of the user
41+
jsonPath: .spec.userOwner.nameSpace
42+
name: NameSpace
43+
type: string
44+
- description: The availability of the user
45+
jsonPath: .status.conditions[?(@.type=="Ready")].status
46+
name: Available
47+
type: string
48+
- description: The name of the secret object
49+
jsonPath: .spec.secretSelector.secretName
50+
name: SecretName
51+
priority: 1
52+
type: string
53+
- description: The key of the secret object
54+
jsonPath: .spec.secretSelector.secretKey
55+
name: SecretKey
56+
priority: 1
57+
type: string
58+
name: v1alpha1
2059
schema:
2160
openAPIV3Schema:
2261
description: MysqlUser is the Schema for the users API.
@@ -79,6 +118,22 @@ spec:
79118
description: SecretName is the name of secret object.
80119
type: string
81120
type: object
121+
tlsOptions:
122+
default:
123+
type: NONE
124+
description: TLSOptions contains the TLS parameter of the MySQL user.
125+
Enabling SSL/TLS will encrypt the data being sent to and from the
126+
database. https://dev.mysql.com/doc/refman/5.7/en/create-user.html
127+
properties:
128+
type:
129+
default: NONE
130+
description: TLS options for the client certificates
131+
enum:
132+
- NONE
133+
- SSL
134+
- X509
135+
type: string
136+
type: object
82137
user:
83138
description: Username is the name of user to be operated. This field
84139
should be immutable.
@@ -95,6 +150,11 @@ spec:
95150
description: NameSpace is the nameSpace of cluster.
96151
type: string
97152
type: object
153+
withGrantOption:
154+
default: false
155+
description: WithGrantOption is the flag to indicate whether the user
156+
has grant option.
157+
type: boolean
98158
type: object
99159
status:
100160
description: UserStatus defines the observed state of MysqlUser.

config/samples/mysql_v1alpha1_mysqluser.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ metadata:
55
spec:
66
## User to operate.
77
user: sample_user
8+
withGrantOption: false
9+
tlsOptions:
10+
## NONE/SSL/X509
11+
type: NONE
812
hosts:
913
- "%"
1014
permissions:

controllers/mysqluser_controller.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,7 @@ func (r *MysqlUserReconciler) reconcileUserInDB(ctx context.Context, mysqlUser *
183183

184184
// Create/Update user in database.
185185
userLog.Info("creating mysql user", "key", mysqlUser.GetKey(), "username", mysqlUser.Spec.User, "cluster", mysqlUser.GetClusterKey())
186-
if err := internal.CreateUserIfNotExists(sqlRunner, mysqlUser.Spec.User, password, mysqlUser.Spec.Hosts,
187-
mysqlUser.Spec.Permissions); err != nil {
186+
if err := internal.CreateUserIfNotExists(sqlRunner, mysqlUser.Unwrap(), password); err != nil {
188187
return err
189188
}
190189

internal/sql_runner.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -334,22 +334,23 @@ func columnValue(scanArgs []interface{}, slaveCols []string, colName string) str
334334
}
335335

336336
// CreateUserIfNotExists creates a user if it doesn't already exist and it gives it the specified permissions.
337-
func CreateUserIfNotExists(
338-
sqlRunner SQLRunner, user, pass string, hosts []string, permissions []apiv1alpha1.UserPermission,
339-
) error {
337+
func CreateUserIfNotExists(sqlRunner SQLRunner, user *apiv1alpha1.MysqlUser, pass string) error {
338+
userName := user.Spec.User
339+
hosts := user.Spec.Hosts
340+
permissions := user.Spec.Permissions
340341

341342
// Throw error if there are no allowed hosts.
342343
if len(hosts) == 0 {
343344
return errors.New("no allowedHosts specified")
344345
}
345346

346347
queries := []Query{
347-
getCreateUserQuery(user, pass, hosts),
348+
getCreateUserQuery(userName, pass, hosts, user.Spec.TLSOptions),
348349
// todo: getAlterUserQuery.
349350
}
350351

351352
if len(permissions) > 0 {
352-
queries = append(queries, permissionsToQuery(permissions, user, hosts))
353+
queries = append(queries, permissionsToQuery(permissions, userName, hosts, user.Spec.WithGrantOption))
353354
}
354355

355356
query := BuildAtomicQuery(queries...)
@@ -361,12 +362,17 @@ func CreateUserIfNotExists(
361362
return nil
362363
}
363364

364-
func getCreateUserQuery(user, pwd string, allowedHosts []string) Query {
365+
func getCreateUserQuery(user, pwd string, allowedHosts []string, tlsOption apiv1alpha1.TLSOptions) Query {
365366
idsTmpl, idsArgs := getUsersIdentification(user, &pwd, allowedHosts)
367+
idsTmpl += getUserTLSRequire(tlsOption)
366368

367369
return NewQuery(fmt.Sprintf("CREATE USER IF NOT EXISTS%s", idsTmpl), idsArgs...)
368370
}
369371

372+
func getUserTLSRequire(tlsOption apiv1alpha1.TLSOptions) string {
373+
return fmt.Sprintf(" REQUIRE %s", tlsOption.Type)
374+
}
375+
370376
func getUsersIdentification(user string, pwd *string, allowedHosts []string) (ids string, args []interface{}) {
371377
for i, host := range allowedHosts {
372378
// Add comma if more than one allowed hosts are used.
@@ -397,7 +403,7 @@ func DropUser(sqlRunner SQLRunner, user, host string) error {
397403
return nil
398404
}
399405

400-
func permissionsToQuery(permissions []apiv1alpha1.UserPermission, user string, allowedHosts []string) Query {
406+
func permissionsToQuery(permissions []apiv1alpha1.UserPermission, user string, allowedHosts []string, withGrant bool) Query {
401407
permQueries := []Query{}
402408

403409
for _, perm := range permissions {
@@ -416,6 +422,9 @@ func permissionsToQuery(permissions []apiv1alpha1.UserPermission, user string, a
416422
idsTmpl, idsArgs := getUsersIdentification(user, nil, allowedHosts)
417423

418424
query := "GRANT " + strings.Join(escPerms, ", ") + " ON " + schemaTable + " TO" + idsTmpl
425+
if withGrant {
426+
query += " WITH GRANT OPTION"
427+
}
419428
args = append(args, idsArgs...)
420429

421430
permQueries = append(permQueries, NewQuery(query, args...))

0 commit comments

Comments
 (0)