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

remove a mysql user functionality #119

Open
Sylvain303 opened this issue May 12, 2016 · 18 comments
Open

remove a mysql user functionality #119

Sylvain303 opened this issue May 12, 2016 · 18 comments

Comments

@Sylvain303
Copy link

Add a functionality to remove a database user:

Example pillar:

mysql:
  # Manage users
  # you can get pillar for existing server using scripts/import_users.py script
  user:
    frank:
      absent: true   ## <===== Add this, the user will be removed.
      password: 'somepass'
      host: localhost
      databases:
        - database: foo
          grants: ['select', 'insert', 'update']
        - database: bar
          grants: ['all privileges']

Run:

salt 'db*' state.apply mysql.user
@gravyboat
Copy link
Contributor

This would be a nice thing to have! If you want to submit a PR that would be awesome!

@Sylvain303
Copy link
Author

I'm on it. You can direct people interested to here. I will link information with this ticket.
I'll do it on mariaDB on debian jessie, other may add they're own OS tricks. ;)

Sylvain303 added a commit to opensource-expert/mysql-formula that referenced this issue May 17, 2016
add a .absent in any user in the pillar
user is skipped in user.sls too

run
salt 'db*' state.apply mysql.remove-user
@Sylvain303
Copy link
Author

Sylvain303 commented May 17, 2016

In #55 there was the ability to define multiple users (mysql's users = row in mysql.user table)

That way:

mysql:
  # Manage users
  # you can get pillar for existing server using scripts/import_users.py script
  user:
    bob:
      password_hash: '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4'
      hosts: # <=============== uses hosts (with an S at the end, no example in pillar.example)
        - localhost
        - 10.0.0.1
      databases:
        - database: foo
          grants: ['all privileges']
          grant_option: True
        - database: bar
          grants: ['select', 'insert', 'update', 'delete']

See the jinja in user.sls

How to handle deletion of this multiples users?

@wwentland
Copy link
Contributor

wwentland commented May 17, 2016

I'd handle it exactly the same way as adding users, but introduce an absent: True pillar value that, if defined and true, causes mysql_user.absent to run in lieu of mysql_user.present.

@Sylvain303
Copy link
Author

@BABILEN I think I mis-explained my point or I don't understand your proposal, are you speaking about a user or a connexion from a host to be removed?

I commited a version in my branch.

I used this syntax I used in the pillar is: user.host_absent

mysql:
  # Manage users
  # you can get pillar for existing server using scripts/import_users.py script
  user:
    frank:
      # frank will be removed
      absent: True # <======== this will remove the couple frank@localhost
      password: 'somepass'
      host: localhost
      databases:
        - database: foo
          grants: ['select', 'insert', 'update']
        - database: bar
          grants: ['all privileges']
    sylvain:
      password: 'sylvainSecr3t'
      # mutiple hosts
      hosts:
        - localhost
        - 10.0.0.2
      host_absent:
        # removed from this one
        - 10.0.0.1 # <======== this will remove the couple sylvain@10.0.0.1
      databases:
        - database: foo
          grants: ['all privileges']
          grant_option: true
        - database: bar
          table: foobar
          grants: ['select', 'insert', 'update', 'delete']
    bob:
      password_hash: '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4'
      hosts:
        - localhost
        - prout
        - 10.0.0.2
      host_absent: # <======== this will remove the couple bob@10.0.0.1
        - 10.0.0.1
      databases:
        - database: foo
          grants: ['all privileges']
          grant_option: True
        - database: baz

I splited remove-user.sls from user.sls for readability and because you may try to remove user separately.

apply with:

salt '*' state.apply mysql.remove-user

Could you write what your pillar suggestion, would look like?

@wwentland
Copy link
Contributor

wwentland commented May 17, 2016

My suggestion for introducing mysql:$USER:absent:[True|False] did not cover host_absent (shouldn't that be hosts_absent ?), but only the case you exemplified for user frank.

@Sylvain303
Copy link
Author

Sylvain303 commented May 19, 2016

@BABILEN, could you please use pillar example syntax. I'm messed up. This has not to be a valid syntax, just a proposal would suffice.

Yes, I'm facing two kind of user's removal.

User's removal, which I guess, using mysql_user.absent will not totally remove the user. As this query would do:

DELETE * FROM user WHERE user = 'frank';

For the name of hosts_absent with an S, of course why not, it was a prototype. Is there any convention about that? Also I feel that using a key hosts.absent would be really confusing.

I think you got the point, it is needed to separate hosts values to be associated to the user and those to be removed. Except if I wipe out all user privileges before recreating everything from pillar data (root would be kept, of course). Which is what I was used to do via other management script in bash, perl, whatever…

An alternative, would be to fetch the database actual couple user@host (mysql GRANT's syntax) for a user and remove what is not anymore in:

(pillar)

mysql:
  user:
    bob:
      hosts:
        - localhost
        - 10.0.0.2

EDIT:
I will try to have a merge with user.sls today and have a working version.

@wwentland
Copy link
Contributor

I was thinking of something like:

mysql:
  # Manage users
  # you can get pillar for existing server using scripts/import_users.py script
  user:
    frank:
      # frank will be removed
      absent: True # <======== this will remove the couple frank@localhost
      password: 'somepass'
      host: localhost
      databases:
        - database: foo
          grants: ['select', 'insert', 'update']
        - database: bar
          grants: ['all privileges']
    sylvain:
      password: 'sylvainSecr3t'
      # multiple hosts
      hosts:
        - localhost
        - 10.0.0.2
      hosts_absent:
        # removed from this one
        - 10.0.0.1

Where the presence of absent: True will branch to the mysql_user.absent for a single host (much like mysql_user.present is used now), while hosts_absent behaves exactly like hosts: with the difference of using mysql_user.absent again.

What is missing from mysql_user.absent that you want to be done?

@Sylvain303
Copy link
Author

OK. That what I was speaking about too. :)

What is missing from mysql_user.absent that you want to be done?

Remove all user 'bob' not regarding its host. Somewhat:

(fake syntax)

slat 'db*' mysql.user_remove bob '*'

Which would perform some equivalent to:

DELETE * FROM user WHERE user = 'bob';

Regardless of its host.

I looked at module code here mysql.user_remove, and tested queries. It wont work that way.

Why would I want to do that?

Because of removing all unwanted entries in the database. It would be better if accomplished in a single SQL connection for every users and followed with a FLUSH PRIVILEGES; once. But salt doesn't perform it that way, as far as I understand. ;) You can apply root user modifications that way too. ;)

For now, I will go with what I have. remove-user.sls and user.sls I will use your IRC suggestion and try to merge both file and logic. The result may be difficult to read ;)

@wwentland
Copy link
Contributor

I'm still not entirely sure what the problem with mysql_user.absent is, but I would recommend to file an issue on https://github.com/saltstack/salt/issues if you believe that there is an issue that should be addressed in that state.

@Sylvain303
Copy link
Author

mysql_user.absent, I'm not sure if there's an issue here. As far as I understand salt behavior:

You have to define an mysql_user.absent for every couple user @ host, you want to remove. In the state. It will call mysql.user_remove module, which will execute the query DROP USER (see my link above).

It is designed that way for me: mysql_user.absent ensure that a user is absent. A user is defined by a couple: user @ host in mysql. See mariadb's doc for DROP USER for example.

You cannot remove all bob, that way:

To reproduce:

bob.sql

CREATE USER bob@localhost;
CREATE USER bob@'192.168.1.1';
CREATE USER bob@'10.10.0.1';
CREATE USER bob@'%.mydomain.com';
SELECT user, host FROM user WHERE user = 'bob';
DROP USER bob;
SELECT user, host FROM user WHERE user = 'bob';
mysql --table -vv -f mysql < bob.sql

This query doesn't drop all bob, it try to drop boo@'%' which doesn't exists. And it will fail.

DROP USER bob;

Do you feel the difference? I don't know if we have to handle it from salt's point of view. I'll continue to try the formula. And simulate 300 mysql's users to see if it works. ;)

@wwentland
Copy link
Contributor

wwentland commented May 19, 2016

Sure, but that is intentional, isn't it?

Users are created with a specific host and you'd therefore have to pass the user@host tuple when you want to remove it. That's exactly how CREATE USER and DROP USER behave and this is simply mirrored by mysql_user.present and mysql_user.absent.

I mean the basic idea is that you used salt in the past to create a specific user and that you can remove exactly that user by running mysql_user.absent in lieu of mysql_user.present. This would be achieved by passing absent: True in your case.

Sylvain303 added a commit to opensource-expert/mysql-formula that referenced this issue May 19, 2016
@Sylvain303
Copy link
Author

OK, thanks @BABILEN. We agreed. ;)

I merged both creating and deleting users.
I'd have to purge my mysql database by hand, first, with all my test it messed up salt.

         ID: mysql_user_bob_10.0.0.2
    Function: mysql_user.present
        Name: bob
      Result: False
     Comment: Failed to create user bob@10.0.0.2 (MySQL Error 1396: Operation CREATE USER failed for 'bob'@'10.0.0.2')
     Started: 17:08:26.629363
    Duration: 6.647 ms
     Changes:   

hosts_absent plural is now used in the pillar

grants are not revoked yet and may denied user re-creation. Multiple user tuple deletion seems to work.

Next episode tomorrow.

Sylvain303 added a commit to opensource-expert/mysql-formula that referenced this issue May 20, 2016
as suggested user are removed one bye one associtated with tupple user @ host.
With salt.module.mysql.user_remove you cannot delete all couple in one single call.

Here's a prototype written in the state as a Jinja macro.
@Sylvain303
Copy link
Author

I added a prototype to destroy a mysql's user here as a Jinja macro. It completely removes the user from the server and all its grants privileges regardless of the combination user @ host.

Usage in Jinja:

# […] loop or user.absent in the pillar
{{ mysql_user_destroy('bob') }}

On IRC #salt I got proposed to develop a behavior which would simulate (note: DROP USER doesn't have a LIKE):

DROP USER LIKE 'bob%';

To be implemented in both the state.mysql_user and module.mysql, rather than writing salt states for that.

example.sls:

mysql_destroy_bob:
  mysql_user.absent:
    - name: "bob%"
    - use_like: True
    - drop_all_privileges: True

It seems it could be implemented locally in the formula by adding _module and _state folder with some python code to add the feature to the main code. As seen in postgres-formula

Jinja template for user.sls in the current formula is becoming unreadable (if not already ;-))…

@wwentland
Copy link
Contributor

wwentland commented May 20, 2016

It might also make sense to simply run a suitable query with https://docs.saltstack.com/en/latest/ref/states/all/salt.states.mysql_query.html

But then I'm not convinced that this is a feature many people need as I believe that users are created explicitly and should also be explicitly removed.

@Sylvain303
Copy link
Author

Playing with _modules/ I produced a prototype for destroying all mysql's users except root and debian-sys-maint

To install and run:

salt 'db*' saltutil.sync_all
salt 'db*' mysql.mysql_cleanup_users

Really destructive, be warned.
It uses managed file proposed in #120 to handle mysql's root credential.

@Sylvain303
Copy link
Author

So here's a new concept, as I feel that doing it in Jinja is just, so bad…
Especially if you rename the hosts list in the user, you just can't drop all old user@badhost entries.

mysql:
  user:
     user1:
        hosts:
          - web0.example.com
          - web2.example.com

Here is an execution module for managed users:

_modules/cleanup_users.py

I didn't yet code the state based on that module, it's more another prototype.

It is based on this formula pillar. I kept the function's name, but it is less destructive. It Only removes non managed users. Not all parameters are fully tested, and unittest are missing.

salt 'db*' mysql.cleanup_users

You can also do, the full destroy (only keeps, root and debian-sys-maint)

salt 'db*' mysql.remove_all_non_admin_user

But you can also check what would happen:

salt 'db*' mysql.list_user_to_keep
salt 'db*' mysql.list_user_to_drop
salt 'db*' mysql.list_user_managed

Sylvain303 added a commit to opensource-expert/mysql-formula that referenced this issue Jun 25, 2016
use in a state of mysql.cleanup_users for simple user management with
removal.

user.absent is supported too.
pillar.example is updated
@Sylvain303
Copy link
Author

Sylvain303 commented Jun 25, 2016

Here's the related state

{% if salt['pillar.get']('mysql:server:auto_remove_user_not_managed') %}
remove_user_not_managed:
  module.run:
    - name: mysql.cleanup_users
    - keep_extra: {{ salt['pillar.get']('mysql:server:keep_user_extra', []) }}
{% endif %}

How to use it:

mysql:
  server:
    root_user: 'root'
    root_password: "Prout@le-Mammouth"
    auto_remove_user_not_managed: True
    # list of couple user@host, can be user@%, 
    # note: % will become a regexp (.*) matching everything
    keep_user_extra: 
      - momo@foreignhost.name
      - admin@%

apply.

salt '*' saltutil.sync_all
salt 'db*' state.apply mysql.user

Enjoy!

Notes:

hosts_absent: discussed above is no more useful and has been dropped. Multiple host is handled automatically by managed users.

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

No branches or pull requests

3 participants