Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion apis/cluster/mysql/v1alpha1/grant_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,24 @@ func (gp *GrantPrivileges) ToStringSlice() []string {
}

// GrantParameters define the desired state of a MySQL grant instance.
// +kubebuilder:validation:XValidation:rule="has(self.memberOf) != has(self.privileges)",message="specify either memberOf or privileges"
// +kubebuilder:validation:XValidation:rule="!has(self.memberOf) || !(has(self.database) || has(self.databaseRef) || has(self.databaseSelector) || has(self.table))",message="memberOf cannot be combined with database or table targeting"
// +kubebuilder:validation:XValidation:rule="!has(self.memberOf) || has(self.user) || has(self.userRef) || has(self.userSelector)",message="memberOf requires a user, userRef, or userSelector"
type GrantParameters struct {
// Privileges to be granted.
// See https://mariadb.com/kb/en/grant/#database-privileges for available privileges.
Privileges GrantPrivileges `json:"privileges"`
// Cannot be combined with MemberOf.
// +optional
Privileges GrantPrivileges `json:"privileges,omitempty"`

// MemberOf is an existing role to grant to the user.
// Use name@host to specify a MySQL role host; the default host is %.
// Cannot be combined with privileges, database, databaseRef, databaseSelector, or table.
// This Grant does not configure default roles or session activation.
// Deleting the Grant revokes the membership without deleting the user or role.
// +kubebuilder:validation:MinLength=1
// +optional
MemberOf *string `json:"memberOf,omitempty"`

// User this grant is for.
// +optional
Expand Down Expand Up @@ -116,6 +130,7 @@ type GrantObservation struct {
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:printcolumn:name="ROLE",type="string",JSONPath=".spec.forProvider.user"
// +kubebuilder:printcolumn:name="DATABASE",type="string",JSONPath=".spec.forProvider.database"
// +kubebuilder:printcolumn:name="MEMBER OF",type="string",JSONPath=".spec.forProvider.memberOf"
// +kubebuilder:printcolumn:name="PRIVILEGES",type="string",JSONPath=".spec.forProvider.privileges"
// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,sql}
type Grant struct {
Expand Down
5 changes: 5 additions & 0 deletions apis/cluster/mysql/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion apis/namespaced/mysql/v1alpha1/grant_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,24 @@ func (gp *GrantPrivileges) ToStringSlice() []string {
}

// GrantParameters define the desired state of a MySQL grant instance.
// +kubebuilder:validation:XValidation:rule="has(self.memberOf) != has(self.privileges)",message="specify either memberOf or privileges"
// +kubebuilder:validation:XValidation:rule="!has(self.memberOf) || !(has(self.database) || has(self.databaseRef) || has(self.databaseSelector) || has(self.table))",message="memberOf cannot be combined with database or table targeting"
// +kubebuilder:validation:XValidation:rule="!has(self.memberOf) || has(self.user) || has(self.userRef) || has(self.userSelector)",message="memberOf requires a user, userRef, or userSelector"
type GrantParameters struct {
// Privileges to be granted.
// See https://mariadb.com/kb/en/grant/#database-privileges for available privileges.
Privileges GrantPrivileges `json:"privileges"`
// Cannot be combined with MemberOf.
// +optional
Privileges GrantPrivileges `json:"privileges,omitempty"`

// MemberOf is an existing role to grant to the user.
// Use name@host to specify a MySQL role host; the default host is %.
// Cannot be combined with privileges, database, databaseRef, databaseSelector, or table.
// This Grant does not configure default roles or session activation.
// Deleting the Grant revokes the membership without deleting the user or role.
// +kubebuilder:validation:MinLength=1
// +optional
MemberOf *string `json:"memberOf,omitempty"`

// User this grant is for.
// +optional
Expand Down Expand Up @@ -117,6 +131,7 @@ type GrantObservation struct {
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:printcolumn:name="ROLE",type="string",JSONPath=".spec.forProvider.user"
// +kubebuilder:printcolumn:name="DATABASE",type="string",JSONPath=".spec.forProvider.database"
// +kubebuilder:printcolumn:name="MEMBER OF",type="string",JSONPath=".spec.forProvider.memberOf"
// +kubebuilder:printcolumn:name="PRIVILEGES",type="string",JSONPath=".spec.forProvider.privileges"
// +kubebuilder:resource:scope=Namespaced,categories={crossplane,managed,sql}
type Grant struct {
Expand Down
5 changes: 5 additions & 0 deletions apis/namespaced/mysql/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions cluster/local/integration_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,64 @@ test_create_grant() {
echo_step_completed
}

mariadb_exec() {
"${KUBECTL}" exec mariadb-0 -- bash -c \
'mariadb -uroot -p"${MARIADB_ROOT_PASSWORD}" -N -e "$1"' -- "$1"
}

test_role_membership() {
echo_step "test MySQL Grant role membership"
mariadb_exec "CREATE ROLE reporting_reader; CREATE ROLE unrelated_reader;
GRANT unrelated_reader TO 'example-user'@'%' WITH ADMIN OPTION;"
local provider_user="root"
if [[ "${TLS}" == "true" ]]; then
provider_user="test"
fi
mariadb_exec "GRANT reporting_reader TO '${provider_user}'@'%' WITH ADMIN OPTION;"

local grants_before
grants_before=$(mariadb_exec "SHOW GRANTS FOR 'example-user'@'%';" | sort)
local membership_query="SELECT COUNT(*) FROM mysql.roles_mapping
WHERE \`Role\` = 'reporting_reader' AND \`User\` = 'example-user' AND \`Host\` = '%';"

"${KUBECTL}" apply -f ${projectdir}/examples/${API_TYPE}/mysql/grant_member_of.yaml
"${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/${API_TYPE}/mysql/grant_member_of.yaml
[[ $(mariadb_exec "${membership_query}") == "1" ]]

echo_info "check that membership and privileges cannot be combined"
local admission_error
if admission_error=$("${KUBECTL}" patch --dry-run=server --type merge \
-f ${projectdir}/examples/${API_TYPE}/mysql/grant_member_of.yaml \
-p '{"spec":{"forProvider":{"privileges":["SELECT"]}}}' 2>&1); then
echo_error "expected memberOf and privileges to be rejected"
fi
if [[ "${admission_error}" != *"specify either memberOf or privileges"* ]]; then
echo_error "unexpected admission error: ${admission_error}"
fi
echo_step_completed

echo_info "check if reconciliation restores a revoked membership"
mariadb_exec "REVOKE reporting_reader FROM 'example-user'@'%';"
"${KUBECTL}" annotate -f ${projectdir}/examples/${API_TYPE}/mysql/grant_member_of.yaml reconcile=role-membership --overwrite
for _ in $(seq 1 30); do
if [[ $(mariadb_exec "${membership_query}") == "1" ]]; then
break
fi
sleep 2
done
[[ $(mariadb_exec "${membership_query}") == "1" ]]

echo_info "check if deletion removes only the managed membership"
"${KUBECTL}" delete --timeout 2m -f ${projectdir}/examples/${API_TYPE}/mysql/grant_member_of.yaml
[[ $(mariadb_exec "${membership_query}") == "0" ]]
local grants_after
grants_after=$(mariadb_exec "SHOW GRANTS FOR 'example-user'@'%';" | sort)
[[ "${grants_before}" == "${grants_after}" ]]

mariadb_exec "DROP ROLE reporting_reader; DROP ROLE unrelated_reader;"
echo_step_completed
}

test_all() {
test_create_database
test_database_charset
Expand All @@ -393,6 +451,7 @@ test_all() {
test_create_user
test_update_user_password
test_create_grant
test_role_membership
}

cleanup_test_resources() {
Expand Down
19 changes: 19 additions & 0 deletions examples/cluster/mysql/grant_member_of.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Grant an existing role to a user.
# Create reporting_reader before applying this example.
# Activate the role separately, for example with SET ROLE or a default role.
# The provider needs WITH ADMIN OPTION for the role (or equivalent admin rights)
# and SELECT on mysql.roles_mapping (MariaDB) or mysql.role_edges (MySQL).
# Deleting this Grant removes the role from this user without deleting either account.
# To change roles without keeping the old role assigned, delete and recreate the Grant.
apiVersion: mysql.sql.crossplane.io/v1alpha1
kind: Grant
metadata:
name: example-grant-member-of
spec:
forProvider:
userRef:
name: example-user
# MySQL roles may use role@host; the default host is %. Use bare roles on MariaDB.
# For Aurora S3 exports, use AWS_SELECT_S3_ACCESS instead.
# Set up IAM/S3 access separately.
memberOf: reporting_reader
23 changes: 23 additions & 0 deletions examples/namespaced/mysql/grant_member_of.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Grant an existing role to a user.
# Create reporting_reader before applying this example.
# Activate the role separately, for example with SET ROLE or a default role.
# The provider needs WITH ADMIN OPTION for the role (or equivalent admin rights)
# and SELECT on mysql.roles_mapping (MariaDB) or mysql.role_edges (MySQL).
# Deleting this Grant removes the role from this user without deleting either account.
# To change roles without keeping the old role assigned, delete and recreate the Grant.
apiVersion: mysql.sql.m.crossplane.io/v1alpha1
kind: Grant
metadata:
name: example-grant-member-of
namespace: default
spec:
forProvider:
userRef:
name: example-user
# MySQL roles may use role@host; the default host is %. Use bare roles on MariaDB.
# For Aurora S3 exports, use AWS_SELECT_S3_ACCESS instead.
# Set up IAM/S3 access separately.
memberOf: reporting_reader
providerConfigRef:
kind: ProviderConfig
name: default
24 changes: 22 additions & 2 deletions package/crds/mysql.sql.crossplane.io_grants.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ spec:
- jsonPath: .spec.forProvider.database
name: DATABASE
type: string
- jsonPath: .spec.forProvider.memberOf
name: MEMBER OF
type: string
- jsonPath: .spec.forProvider.privileges
name: PRIVILEGES
type: string
Expand Down Expand Up @@ -164,10 +167,20 @@ spec:
type: string
type: object
type: object
memberOf:
description: |-
MemberOf is an existing role to grant to the user.
Use name@host to specify a MySQL role host; the default host is %.
Cannot be combined with privileges, database, databaseRef, databaseSelector, or table.
This Grant does not configure default roles or session activation.
Deleting the Grant revokes the membership without deleting the user or role.
minLength: 1
type: string
privileges:
description: |-
Privileges to be granted.
See https://mariadb.com/kb/en/grant/#database-privileges for available privileges.
Cannot be combined with MemberOf.
items:
description: GrantPrivilege represents a privilege to be granted
pattern: ^([A-Z0-9_ ]+|[A-Z_ ]+ [(]`[^`]+`(, `[^`]+`)*[)])$
Expand Down Expand Up @@ -256,9 +269,16 @@ spec:
type: string
type: object
type: object
required:
- privileges
type: object
x-kubernetes-validations:
- message: specify either memberOf or privileges
rule: has(self.memberOf) != has(self.privileges)
- message: memberOf cannot be combined with database or table targeting
rule: '!has(self.memberOf) || !(has(self.database) || has(self.databaseRef)
|| has(self.databaseSelector) || has(self.table))'
- message: memberOf requires a user, userRef, or userSelector
rule: '!has(self.memberOf) || has(self.user) || has(self.userRef)
|| has(self.userSelector)'
managementPolicies:
default:
- '*'
Expand Down
24 changes: 22 additions & 2 deletions package/crds/mysql.sql.m.crossplane.io_grants.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ spec:
- jsonPath: .spec.forProvider.database
name: DATABASE
type: string
- jsonPath: .spec.forProvider.memberOf
name: MEMBER OF
type: string
- jsonPath: .spec.forProvider.privileges
name: PRIVILEGES
type: string
Expand Down Expand Up @@ -156,10 +159,20 @@ spec:
type: string
type: object
type: object
memberOf:
description: |-
MemberOf is an existing role to grant to the user.
Use name@host to specify a MySQL role host; the default host is %.
Cannot be combined with privileges, database, databaseRef, databaseSelector, or table.
This Grant does not configure default roles or session activation.
Deleting the Grant revokes the membership without deleting the user or role.
minLength: 1
type: string
privileges:
description: |-
Privileges to be granted.
See https://mariadb.com/kb/en/grant/#database-privileges for available privileges.
Cannot be combined with MemberOf.
items:
description: GrantPrivilege represents a privilege to be granted
pattern: ^([A-Z0-9_ ]+|[A-Z_ ]+ [(]`[^`]+`(, `[^`]+`)*[)])$
Expand Down Expand Up @@ -254,9 +267,16 @@ spec:
type: string
type: object
type: object
required:
- privileges
type: object
x-kubernetes-validations:
- message: specify either memberOf or privileges
rule: has(self.memberOf) != has(self.privileges)
- message: memberOf cannot be combined with database or table targeting
rule: '!has(self.memberOf) || !(has(self.database) || has(self.databaseRef)
|| has(self.databaseSelector) || has(self.table))'
- message: memberOf requires a user, userRef, or userSelector
rule: '!has(self.memberOf) || has(self.user) || has(self.userRef)
|| has(self.userSelector)'
managementPolicies:
default:
- '*'
Expand Down
Loading