Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Operator to Add/delete User for PostgreSQL database #1097

Merged
merged 32 commits into from
Jun 3, 2020

Conversation

buhongw7583c
Copy link
Contributor

@buhongw7583c buhongw7583c commented May 21, 2020

Closes #1056

What this PR does / why we need it:
Support the function to add users with specific roles to Postgresql database.
--Be able to add a user with a predefined roles and delete the user.
-- Able to delete a user under the postgresql database.

Special notes for your reviewer:
The Azure Database for PostgreSQL server is created with the 3 default roles defined.

--azure_pg_admin
--azure_superuser
--your server admin user

The server admin user account can be used to create additional users and grant those users into the azure_pg_admin role.
Also, the server admin account can be used to create less privileged users and roles that have access to individual databases and schemas but this function is not included in this PR.

To verify the PR:
--Use the config/samples/azure_v1alpha1_postgresql_everything.yaml to config pgresql server, database and firewallrule. Ensure at least your local client has the access to azure.
-- Apply the sample file config/samples/azure_v1alpha1_postgresqluser.yaml to add a user with a predefined role.
Use any tool to connect the postgresqlServer (e.g. pgadmin). The default server admin password could be derived from kubectl get secret -o yaml. By default the secret name is the pgresqlserver name.
image

-- Delete the newly added user by kubect delete postgresqluser <metatdata name> . Note the username is the metadata name applied when adding the pgresql user but not the actual pgresql user name.
From the pgadmin tool, the user has been deleted.

image

How does this PR make you feel:
gif

If applicable:

  • this PR contains documentation
  • this PR contains tests

@buhongw7583c buhongw7583c changed the title WIP-- Support Operator to Add User for PostgreSQL database Support Operator to Add/delete User for PostgreSQL database May 26, 2020
@melonrush13
Copy link
Contributor

Hi Hong! I think we are missing a few files for adding a new operator. Kubebuilder generates these files for us so we don't have to manually create them!

Here is our documentation for creating a new operator with kubebuilder.

Here are the files we need to declare the PostgreSQLUser in from KubeBuilder:

  • PROJECT
  • Config/crd/kustomization.yaml
  • Config/crd/patches/webhook_in_postgresqluser
  • Config/rbac/postgresqluser_editor_role.yaml
  • Conifg/rbac/postgresqluser_viewer_role.yaml

In step 12 of the documentation I mentioned above, we update the helm chart to include the new operator (PostgreSqlUser). We also need these two files as well

  • charts/azure-service-operator-0.1.0.tgz
  • charts/index.yaml

@melonrush13
Copy link
Contributor

We also need to add the following inside the controller files:

  • new file for postgresql user test (i.e. controller/postgresqluser_controller_test.go)
  • declaration of postgresqluser in controllers/suite_test.go (declare like you declared in main.go)

main.go Outdated Show resolved Hide resolved
Copy link
Contributor

@melonrush13 melonrush13 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Hong!

I tested the Happy Path and it worked! Successfully created a user on top of the database, as well as deleting successfully! I tested this out by commenting out our optional fields of adminsecret, adminsecretkeyvault and username.

I am requesting changes so we can address the missing files from KubeBuilder, add tests in the controller, and address the following edge case I ran into.

If we create a PostgreSqlUser with a improperly connected IP address, we run into the following zapperr in the reconciler. It continues to loop with the following message.

"}, "reason": "FailedReconcile", "message": "Failed to reconcile resource: 1 error occurred:\n\t* pq: no pg_hba.conf entry for host \"73.181.19.189\", user \"yc5jvKen\", database \"postgresqldatabase-sample-m\", SSL on\n\n"}
2020-05-27T16:38:44.958-0600	ERROR	controller-runtime.controller	Reconciler error	{"controller": "postgresqluser", "request": "default/psqluser-sample-meltwo", "error": "1 error occurred:\n\t* pq: no pg_hba.conf entry for host \"73.181.19.189\", user \"yc5jvKen\", database \"postgresqldatabase-sample-m\", SSL on\n\n"}
github.com/go-logr/zapr.(*zapLogger).Error

I tested this out in AzureSqlUser to see if we would have the same behavior, and it does not have this zaperr in the reconcile. Here are the two status messages for comparison:

PostgreSqlUser:
psqluser-sample-meltwo pq: no pg_hba.conf entry for host "73.181.19.189", user "yc5jvKen", database "postgresqldatabase-sample-m", SSL on

AzureSqlUser
sqluser-sample Login error: mssql: Cannot open server 'sqlservermeltest' requested by the login. Client with IP address '73.181.19.189' is not allowed to access the server. To enable access, use the Windows Azure Management Portal or run sp_set_firewall_rule on the master database to create a firewall rule for this IP address or address range. It may take up to five minutes for this change to take effect.

We should try to handle this err in the reconciler somehow

Let me know if this makes sense or not! Thanks!

@buhongw7583c
Copy link
Contributor Author

Hi Hong! I think we are missing a few files for adding a new operator. Kubebuilder generates these files for us so we don't have to manually create them!

Here is our documentation for creating a new operator with kubebuilder.

Here are the files we need to declare the PostgreSQLUser in from KubeBuilder:

  • PROJECT
  • Config/crd/kustomization.yaml
  • Config/crd/patches/webhook_in_postgresqluser
  • Config/rbac/postgresqluser_editor_role.yaml
  • Conifg/rbac/postgresqluser_viewer_role.yaml

In step 12 of the documentation I mentioned above, we update the helm chart to include the new operator (PostgreSqlUser). We also need these two files as well

  • charts/azure-service-operator-0.1.0.tgz
  • charts/index.yaml

@melonrush13 Mel, thanks so much for the great comments! I do missed several important files and the document mentioned is definitely a great help!

All the comments and suggestions have been addressed and just one left:
for the postgresqluser_editor_role.yaml and postgresqluser_viewer_role.yaml suggested to add under rbac folder, i checked the folder and not all the controllers have corresponding **_editor_role.yaml or **_viewer_role.yaml. Are they generated by commands or manually added? How to decide which verbs to support? Very much appreciate your careful checking on my PR and bring so many valuable comments!

@buhongw7583c
Copy link
Contributor Author

Hi Hong!

I tested the Happy Path and it worked! Successfully created a user on top of the database, as well as deleting successfully! I tested this out by commenting out our optional fields of adminsecret, adminsecretkeyvault and username.

I am requesting changes so we can address the missing files from KubeBuilder, add tests in the controller, and address the following edge case I ran into.

If we create a PostgreSqlUser with a improperly connected IP address, we run into the following zapperr in the reconciler. It continues to loop with the following message.

"}, "reason": "FailedReconcile", "message": "Failed to reconcile resource: 1 error occurred:\n\t* pq: no pg_hba.conf entry for host \"73.181.19.189\", user \"yc5jvKen\", database \"postgresqldatabase-sample-m\", SSL on\n\n"}
2020-05-27T16:38:44.958-0600	ERROR	controller-runtime.controller	Reconciler error	{"controller": "postgresqluser", "request": "default/psqluser-sample-meltwo", "error": "1 error occurred:\n\t* pq: no pg_hba.conf entry for host \"73.181.19.189\", user \"yc5jvKen\", database \"postgresqldatabase-sample-m\", SSL on\n\n"}
github.com/go-logr/zapr.(*zapLogger).Error

I tested this out in AzureSqlUser to see if we would have the same behavior, and it does not have this zaperr in the reconcile. Here are the two status messages for comparison:

PostgreSqlUser:
psqluser-sample-meltwo pq: no pg_hba.conf entry for host "73.181.19.189", user "yc5jvKen", database "postgresqldatabase-sample-m", SSL on

AzureSqlUser
sqluser-sample Login error: mssql: Cannot open server 'sqlservermeltest' requested by the login. Client with IP address '73.181.19.189' is not allowed to access the server. To enable access, use the Windows Azure Management Portal or run sp_set_firewall_rule on the master database to create a firewall rule for this IP address or address range. It may take up to five minutes for this change to take effect.

We should try to handle this err in the reconciler somehow

Let me know if this makes sense or not! Thanks!

@melonrush13 Mel, sure it makes sense! I did encounter the similar issue during my test but somehow it slipped from my mind and forgot to add handlings. Now the error could be caught up and wont produce the zapperr. But the error message returned from the sdk was quite confusing and indeed it indicate that your client IP address is not permitted to access, therefore I added more information in the returned message.

@melonrush13
Copy link
Contributor

Hi Hong! I think we are missing a few files for adding a new operator. Kubebuilder generates these files for us so we don't have to manually create them!
Here is our documentation for creating a new operator with kubebuilder.
Here are the files we need to declare the PostgreSQLUser in from KubeBuilder:

  • PROJECT
  • Config/crd/kustomization.yaml
  • Config/crd/patches/webhook_in_postgresqluser
  • Config/rbac/postgresqluser_editor_role.yaml
  • Conifg/rbac/postgresqluser_viewer_role.yaml

In step 12 of the documentation I mentioned above, we update the helm chart to include the new operator (PostgreSqlUser). We also need these two files as well

  • charts/azure-service-operator-0.1.0.tgz
  • charts/index.yaml

@melonrush13 Mel, thanks so much for the great comments! I do missed several important files and the document mentioned is definitely a great help!

All the comments and suggestions have been addressed and just one left:
for the postgresqluser_editor_role.yaml and postgresqluser_viewer_role.yaml suggested to add under rbac folder, i checked the folder and not all the controllers have corresponding **_editor_role.yaml or **_viewer_role.yaml. Are they generated by commands or manually added? How to decide which verbs to support? Very much appreciate your careful checking on my PR and bring so many valuable comments!

Hi Hong! So these two rbac files are generated by KubeBuilder when you do the
kubebuilder create api --group azure --version v1alpha1 --kind <AzureNewType> command.

Also, we began discussing this on Teams as Janani also noticed not every controller has these rbac files like you mentioned. We think this might be a difference in kubebuilder versions. Currently if you create a new operator with the latest version of kubebuilder, it will create these rbac files.

When you created the operator with Kubebuilder did it generate these files for you?

melonrush13
melonrush13 previously approved these changes Jun 1, 2020
Copy link
Contributor

@melonrush13 melonrush13 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Thanks for all your work on this one, Hong!

  • Was able to successfully create the user/delete the user
  • Create successfully with keyvault set from adminSecret/adminSecretKeyVault
  • Create successfully with keyVaultToStoreSecrets

Looks good to me. :) Passing on to an approved reviewer so we can get merged and another set of eyes!

@frodopwns @jananivMS

PROJECT Outdated Show resolved Hide resolved
@frodopwns frodopwns self-assigned this Jun 1, 2020
PROJECT Outdated Show resolved Hide resolved
this is superfluous
@melonrush13
Copy link
Contributor

Also one other thing, @buhongw7583c , we should update the docs here to account for this new operator. Feel free to address this in another PR if you like (same comment for MySql, too!)

//for US government <server>.postgres.database.usgovcloudapi.net
fullServerAddress := fmt.Sprintf("%s.postgres."+psqldbdnssuffix, server)

connString := fmt.Sprintf("host=%s user=%s password=%s port=%d dbname=%s sslmode=require connect_timeout=30", fullServerAddress, user, password, port, database)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should consider putting a conn string in the secret too

Copy link
Contributor

@frodopwns frodopwns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

few things I think we should look at before merge

Comment on lines 195 to 224
// delete all the custom formatted secrets if keyvault is in use
keyVaultEnabled := reflect.TypeOf(secretClient).Elem().Name() == "KeyvaultSecretClient"
if keyVaultEnabled {
customFormatNames := []string{
"adonet",
"adonet-urlonly",
"jdbc",
"jdbc-urlonly",
"odbc",
"odbc-urlonly",
"server",
"database",
"username",
"password",
}

for _, formatName := range customFormatNames {
key := types.NamespacedName{Namespace: secretKey.Namespace, Name: instance.Name + "-" + formatName}

err = secretClient.Delete(
ctx,
key,
)
if err != nil {
instance.Status.Message = "failed to delete secret, err: " + err.Error()
return false, err
}
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should remove the concept of custom secret formatting. It probably should not have been included in AzureSQLUser and is in need of some work (some is already done in a PR that is awaiting review)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only left this comment to confirm with you since the code change is big. if we remove the custom secret formating, so we would not allow user to use keyVaultSecretPrefix but we still allow customer to use keyvault to store the secret, right?

pkg/resourcemanager/psql/psqluser/psqluser.go Show resolved Hide resolved
Comment on lines 251 to 266
keyVaultEnabled := reflect.TypeOf(secretClient).Elem().Name() == "KeyvaultSecretClient"

if keyVaultEnabled {
// For a keyvault secret store, check for supplied namespace parameters
var dbUserCustomNamespace string
if instance.Spec.KeyVaultSecretPrefix != "" {
dbUserCustomNamespace = instance.Spec.KeyVaultSecretPrefix
} else {
dbUserCustomNamespace = "psqluser-" + instance.Spec.Server + "-" + instance.Spec.DbName
}

namespacedName = types.NamespacedName{Namespace: dbUserCustomNamespace, Name: instance.Name}
} else {
namespacedName = types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't going to work. The operator should have no concept of what type of secret store is being used. This is a problem in AzureSQL User too and that is why there is a PR out to refactor it.

I think we might be able to remove the prefix idea from this operator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same question: we would remove both the keyVaultToStoreSecrets and keyVaultSecretPrefix?

pkg/resourcemanager/psql/psqluser/psqluser_reconcile.go Outdated Show resolved Hide resolved
Comment on lines 152 to 245
// Preformatted special formats are only available through keyvault as they require separated secrets
keyVaultEnabled := reflect.TypeOf(sqlUserSecretClient).Elem().Name() == "KeyvaultSecretClient"
if keyVaultEnabled {
// Instantiate a map of all formats and flip the bool to true for any that have been requested in the spec.
// Formats that were not requested will be explicitly deleted.
requestedFormats := map[string]bool{
"adonet": false,
"adonet-urlonly": false,
"jdbc": false,
"jdbc-urlonly": false,
"odbc": false,
"odbc-urlonly": false,
"server": false,
"database": false,
"username": false,
"password": false,
}
for _, format := range instance.Spec.KeyVaultSecretFormats {
requestedFormats[format] = true
}

// Deleted items will be processed immediately but secrets that need to be added will be created in this array and persisted in one pass at the end
formattedSecrets := make(map[string][]byte)

for formatName, requested := range requestedFormats {
// Add the format to the output map if it has been requested otherwise call for its deletion from the secret store
if requested {
switch formatName {
case "adonet":
formattedSecrets["adonet"] = []byte(fmt.Sprintf(
"Server=tcp:%v,1433;Initial Catalog=%v;Persist Security Info=False;User ID=%v;Password=%v;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;",
string(DBSecret["fullyQualifiedServerName"]),
instance.Spec.DbName,
user,
string(DBSecret["password"]),
))

case "adonet-urlonly":
formattedSecrets["adonet-urlonly"] = []byte(fmt.Sprintf(
"Server=tcp:%v,1433;Initial Catalog=%v;Persist Security Info=False; MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout",
string(DBSecret["fullyQualifiedServerName"]),
instance.Spec.DbName,
))

case "jdbc":
formattedSecrets["jdbc"] = []byte(fmt.Sprintf(
"jdbc:sqlserver://%v:1433;database=%v;user=%v@%v;password=%v;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*."+config.Environment().SQLDatabaseDNSSuffix+";loginTimeout=30;",
string(DBSecret["fullyQualifiedServerName"]),
instance.Spec.DbName,
user,
instance.Spec.Server,
string(DBSecret["password"]),
))
case "jdbc-urlonly":
formattedSecrets["jdbc-urlonly"] = []byte(fmt.Sprintf(
"jdbc:sqlserver://%v:1433;database=%v;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*."+config.Environment().SQLDatabaseDNSSuffix+";loginTimeout=30;",
string(DBSecret["fullyQualifiedServerName"]),
instance.Spec.DbName,
))

case "odbc":
formattedSecrets["odbc"] = []byte(fmt.Sprintf(
"Server=tcp:%v,1433;Initial Catalog=%v;Persist Security Info=False;User ID=%v;Password=%v;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;",
string(DBSecret["fullyQualifiedServerName"]),
instance.Spec.DbName,
user,
string(DBSecret["password"]),
))
case "odbc-urlonly":
formattedSecrets["odbc-urlonly"] = []byte(fmt.Sprintf(
"Driver={ODBC Driver 13 for SQL Server};Server=tcp:%v,1433;Database=%v; Encrypt=yes;TrustServerCertificate=no;Connection Timeout=30;",
string(DBSecret["fullyQualifiedServerName"]),
instance.Spec.DbName,
))
case "server":
formattedSecrets["server"] = DBSecret["fullyQualifiedServerName"]

case "database":
formattedSecrets["database"] = []byte(instance.Spec.DbName)

case "username":
formattedSecrets["username"] = []byte(user)

case "password":
formattedSecrets["password"] = DBSecret["password"]
}
} else {
err = sqlUserSecretClient.Delete(
ctx,
types.NamespacedName{Namespace: key.Namespace, Name: instance.Name + "-" + formatName},
)
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to drop all this secret formatting stuff. There is a PR to refactor it in AzureSQL and I don't want to introduce this feature until its fixed and/or required by a user.

pkg/resourcemanager/psql/psqluser/psqluser_reconcile.go Outdated Show resolved Hide resolved
Copy link
Contributor

@frodopwns frodopwns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm once tests pass we should be able to merge this

@buhongw7583c buhongw7583c merged commit 217c62e into Azure:master Jun 3, 2020
@buhongw7583c buhongw7583c deleted the UserManagerPgreSQL branch June 3, 2020 03:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

As an operator, I can use the service operator to manage a user within the PostgreSQL database
3 participants