ansible-collections / community.mongodb

MongoDB Ansible Collection
http://galaxy.ansible.com/community/mongodb
GNU General Public License v3.0
108 stars 72 forks source link

authenticate using x509 Membership Authentication #177

Closed fischerscode closed 4 years ago

fischerscode commented 4 years ago
SUMMARY

For setting up a mongodb, being able to authenticate using the x509 Membership Authentication would be quite useful, as it would allow secure authentication before any user is configured.

ISSUE TYPE
COMPONENT NAME

mongodb_common

ADDITIONAL INFORMATION

This feature could be used to configure a cluster from the first second with little preparation, in addition to that, the cluster could be set up without restarting any node, as authentication could be enabled at the first boot.

When implemented, the plugins could be used for doing something like this:

- name: create admin user
  command: |
    /usr/bin/mongo mongodb-host.test \
          --tls \
          --tlsCAFile /tmp/ca.crt \
          --tlsCertificateKeyFile=/tmp/tls.key \
          --authenticationMechanism MONGODB-X509 \
          --authenticationDatabase '$external' \
          --eval \
          '{{ lookup('template', 'templates/create-admin-user.js') }}'
  register: mongoout
  changed_when: "'{ \0ok\0 : 1 }' in mongoout.stdout"

Mongopy seems to support this. The mongodb doc for x509 membership authentication.

rhysmeister commented 4 years ago

All modules support the following ssl options...

ssl (False, bool, False)
Whether to use an SSL connection when connecting to the database.
ssl_cert_reqs (False, str, CERT_REQUIRED)
Specifies whether a certificate is required from the other side of the connection, and whether it will be validated if provided.
ssl_ca_certs (False, str, None)
The ssl_ca_certs option takes a path to a CA file.
ssl_crlfile (False, str, None)
The ssl_crlfile option takes a path to a CRL file.
ssl_certfile (False, str, None)
Present a client certificate using the ssl_certfile option.
ssl_keyfile (False, str, None)
Private key for the client certificate.
ssl_pem_passphrase (False, str, None)
Passphrase to decrypt encrypted private keys.

The documentation states these provide the same functionality as the tls options...

Starting in version 4.2, MongoDB provides net.tls settings (and corresponding command-line options) that corresponds to the net.ssl settings (and their corresponding command-line options). The net.tls settings provide identical functionality as the net.ssl options since MongoDB has always supported TLS 1.0 and later.

https://docs.mongodb.com/manual/tutorial/configure-ssl/#procedures-using-net-ssl-settings

Is there anything missing meaning your above approach won't work?

fischerscode commented 4 years ago

As far as I know, there is a difference between Membership Authentication and Client Authentication. I think, the provided ssl options only work for client authentication.

I am able to create a a user using this task through x509 Membership Authentication.

- name: create admin user
  command: |
    /usr/bin/mongo mongodb-host.test \
          --tls \
          --tlsCAFile /tmp/ca.crt \
          --tlsCertificateKeyFile=/tmp/tls.key \
          --authenticationMechanism MONGODB-X509 \
          --authenticationDatabase '$external' \
          --eval \
          '{{ lookup('template', 'templates/create-admin-user.js') }}'
  register: mongoout
  changed_when: "'{ \0ok\0 : 1 }' in mongoout.stdout"

and this fails for me with: fatal: [localhost]: FAILED! => {"changed": false, "msg": "Unable to add or update user: 'ServerSelectionTimeoutError' object has no attribute 'code'"}

- name: create admin user
  community.mongodb.mongodb_user:
    login_host: "mongodb-host.test"
    login_port: 27001
    login_database: "$external"
    database: "admin"
    name: "admin"
    password: "{{admin_password}}"
    roles:
      - dbAdminAnyDatabase
    ssl: true
    ssl_ca_certs: "/tmp/ca.crt"
    ssl_certfile: "/tmp/tls.key"
    state: present

But maybe I am just missing something.

rhysmeister commented 4 years ago

Well... you've found a bug here I need to fix.

fatal: [localhost]: FAILED! => {"changed": false, "msg": "Unable to add or update user: 'ServerSelectionTimeoutError' object has no attribute 'code'"}

ssl/tls are definitely the same thing as far as I can see in the documentation. We don't specify authenticationMechanism anywhere in the mongodb module so your example would be attempting with the default (SCRAM). It should be fairly simple to add this to the modules as follows...

- name: create admin user
  community.mongodb.mongodb_user:
    login_host: "mongodb-host.test"
    login_port: 27001
    login_database: "$external"
    database: "admin"
    name: "admin"
    password: "{{admin_password}}"
    roles:
      - dbAdminAnyDatabase
    ssl: true
    ssl_ca_certs: "/tmp/ca.crt"
    ssl_certfile: "/tmp/tls.key"
    auth_mechanism: "MONGODB-X509"
    state: present

I probably need to add the mechanisms from here...

https://api.mongodb.com/python/current/examples/authentication.html

and probably remove the auth database from the arguments when x.509 is used. Should be able to get this done in the next day or two.

Cheers,

Rhys

fischerscode commented 4 years ago

Nice, thank you! 👍

rhysmeister commented 4 years ago

I've added the auth_mechanism feature in the following pr...

https://github.com/ansible-collections/community.mongodb/pull/179

Assuming I'm correct in my assumptions this should work. I don't currently have the ability to easily test this. If you are able to do so that would be helpful.

fischerscode commented 4 years ago

Thank you 👍 I'll be happy to test it.

fischerscode commented 4 years ago

Unfortunately I have not been able to make it work. I have tried different combinations like this:

     community.mongodb.mongodb_user:
       login_host: "mongodb-host.test"
       login_port: 27001
       login_database: "$external"
       database: "admin"
       name: "admin"
       password: "test"
       roles:
        - dbAdminAnyDatabase
       ssl: true
       ssl_ca_certs: "/tmp/ca.crt"
       ssl_certfile: "/tmp/tls.key" #cert and key in one file
       state: present
       auth_mechanism: "MONGODB-X509"

I am still getting this this error: fatal: [localhost]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"}, "changed": false, "msg": "Unable to add or update user: 'ServerSelectionTimeoutError' object has no attribute 'code'"}

rhysmeister commented 4 years ago

OK. It's probably best I create some form of test setup in Docker to debug this.

fischerscode commented 4 years ago

I have uploaded such a test environment based on Kubernetes and the operator-sdk.

rhysmeister commented 4 years ago

@fischerscode Great stuff. Very useful. I'll start looking at this over the next week.

fischerscode commented 4 years ago

@rhysmeister Great! 👍 Have a nice weekend.

rhysmeister commented 4 years ago

Had some free time this afternoon so I decided to look at this.

Tested with the following command...

ansible localhost -m mongodb_user -a "login_host=localhost login_port=27017 login_database='$external' database='admin' password='secret' ssl=true ssl_ca_certs=/Users/rhyscampbell/Documents/git/mongodb-kub/tests/ansible-operator/ca.crt ssl_certfile=/Users/rhyscampbell/Documents/git/mongodb-kub/tests/ansible-operator/tls.key auth_mechanism=MONGODB-X509 name="test" state=present connection_options='tlsAllowInvalidHostnames=true'"

This is essentially the same as your example with the addition of the tlsAllowInvalidHostnames flag. Here's the log from the test MongoDB instance showing the member auth works...

2020-09-20T11:19:25.274+0000 W NETWORK [conn116] Client connecting with server's own TLS certificate 2020-09-20T11:19:25.286+0000 I NETWORK [conn116] received client metadata from 127.0.0.1:42388 conn116: { driver: { name: "PyMongo", version: "3.11.0" }, os: { type: "Darwin", name: "Darwin", architecture: "x86_64", version: "10.15.6" }, platform: "CPython 3.7.5.final.0" } 2020-09-20T11:19:25.299+0000 I NETWORK [listener] connection accepted from 127.0.0.1:42390 #117 (2 connections now open) 2020-09-20T11:19:25.308+0000 W NETWORK [conn117] Client connecting with server's own TLS certificate 2020-09-20T11:19:25.308+0000 I NETWORK [conn117] received client metadata from 127.0.0.1:42390 conn117: { driver: { name: "PyMongo", version: "3.11.0" }, os: { type: "Darwin", name: "Darwin", architecture: "x86_64", version: "10.15.6" }, platform: "CPython 3.7.5.final.0" } 2020-09-20T11:19:25.311+0000 I ACCESS [conn117] authenticate db: $external { authenticate: 1, mechanism: "MONGODB-X509", $db: "$external", $readPreference: { mode: "primary" } } 2020-09-20T11:19:25.312+0000 W ACCESS [conn117] Client isn't a mongod or mongos, but is connecting with a certificate with cluster membership 2020-09-20T11:19:25.312+0000 I ACCESS [conn117] Successfully authenticated as principal CN=mongodb-sample.default.svc.cluster.local,OU=organizationUnit,O=organizationName on $external from client 127.0.0.1:42390 2020-09-20T11:19:25.373+0000 I STORAGE [conn117] createCollection: admin.system.users with generated UUID: fb39dbc5-71c1-47e6-b2bf-8c354d13786a and options: {} 2020-09-20T11:19:25.387+0000 I INDEX [conn117] index build: done building index id on ns admin.system.users 2020-09-20T11:19:25.395+0000 I INDEX [conn117] index build: done building index user_1_db_1 on ns admin.system.users

This should work with all modules using pymongo, basically all apart from mongodb_shell, but I've only tested the mongodb_user module for now. If this is to be an officially supported authentication method then we should probably have a set of tests for this. Would you mind if I included your test env?

TODO

fischerscode commented 4 years ago

Thanks, it works! I do absolutely not mind if you add this test env. Should I create a PR?

rhysmeister commented 4 years ago

@fischerscode Yes, please do.

rhysmeister commented 4 years ago

Merged in https://github.com/ansible-collections/community.mongodb/pull/179

Will be available in latest tar archive.

fischerscode commented 4 years ago

@rhysmeister Thank you, it works perfectly in my current project.