diff --git a/README-automountkey.md b/README-automountkey.md
new file mode 100644
index 0000000000000000000000000000000000000000..cb49b5b59dbe1b611c4cc0cacf7e1f94de2e65b4
--- /dev/null
+++ b/README-automountkey.md
@@ -0,0 +1,112 @@
+Automountkey module
+=====================
+
+Description
+-----------
+
+The automountkey module allows management of keys within an automount map.
+
+It is desgined to follow the IPA api as closely as possible while ensuring ease of use.
+
+
+Features
+--------
+* Automount key management
+
+Supported FreeIPA Versions
+--------------------------
+
+FreeIPA versions 4.4.0 and up are supported by the ipaautomountkey module.
+
+Requirements
+------------
+**Controller**
+* Ansible version: 2.8+
+
+**Node**
+* Supported FreeIPA version (see above)
+
+
+Usage
+=====
+
+Example inventory file
+
+```ini
+[ipaserver]
+ipaserver.test.local
+```
+
+
+Example playbook to ensure presence of an automount key:
+
+```yaml
+---
+- name: Playbook to manage automount key
+  hosts: ipaserver
+
+  tasks:
+  - name: ensure automount key TestKey is present
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      location: TestLocation
+      mapname: TestMap
+      key: TestKey
+      info: 192.168.122.1:/exports
+      state: present
+```
+
+Example playbook to rename an automount map:
+
+```yaml
+---
+- name: Playbook to add an automount map
+  hosts: ipaserver
+
+  tasks:
+  - name: ensure aumount key TestKey is renamed to NewKeyName
+    ipaautomountkey:
+      ipaadmin_password: password01
+      automountlocationcn: TestLocation
+      automountmapname: TestMap
+      automountkey: TestKey
+      newname: NewKeyName
+      state: renamed
+```
+
+Example playbook to ensure an automount key is absent:
+
+```yaml
+---
+- name: Playbook to manage an automount key
+  hosts: ipaserver
+
+  tasks:
+  - name: ensure automount key TestKey is absent
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      location: TestLocation
+      mapname: TestMap
+      key: TestKey
+      state: absent
+```
+
+
+Variables
+=========
+
+Variable | Description | Required
+-------- | ----------- | --------
+`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
+`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`location` \| `automountlocationcn` \| `automountlocation` | Location name. | yes
+`mapname` \|  `map` \| `automountmapname` \| `automountmap` | Map the key belongs to | yes
+`key` \| `name` \| `automountkey` | Automount key to manage | yes
+`rename` \| `new_name` \| `newautomountkey` | the name to change the key to if state is `renamed` | yes when state is `renamed`
+`info` \| `information` \| `automountinformation` | Mount information for the key | yes when state is `present`
+`state` | The state to ensure. It can be one of `present`, `absent` or `renamed`, default: `present`. | no
+
+Authors
+=======
+
+Chris Procter
diff --git a/playbooks/automount/.automount-map-present.yml.swp b/playbooks/automount/.automount-map-present.yml.swp
new file mode 100644
index 0000000000000000000000000000000000000000..1a9bbc91a11e1d02cb72378a07472de6debf2cc1
Binary files /dev/null and b/playbooks/automount/.automount-map-present.yml.swp differ
diff --git a/playbooks/automount/automountkey-present.yml b/playbooks/automount/automountkey-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..50f51bc359f73ac50b5d1226f253641ad652c8d2
--- /dev/null
+++ b/playbooks/automount/automountkey-present.yml
@@ -0,0 +1,13 @@
+---
+- name: Playbook to manage an automout key
+  hosts: ipaserver
+
+  tasks:
+  - name: Ensure autmount key is present
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      location: TestLocation
+      mapname: TestMap
+      key: TestKey
+      info: 192.168.122.1:/exports
+      state: present
diff --git a/playbooks/automount/automountkey-renamed.yml b/playbooks/automount/automountkey-renamed.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ff60d1170334d995c5992121c85dc2d9ccbbdfe1
--- /dev/null
+++ b/playbooks/automount/automountkey-renamed.yml
@@ -0,0 +1,13 @@
+---
+- name: Playbook to manage an automount key
+  hosts: ipaserver
+
+  tasks:
+  - name: Ensure aumount key TestKey is renamed to NewKeyName
+    ipaautomountkey:
+      ipaadmin_password: password01
+      automountlocationcn: TestLocation
+      automountmapname: TestMap
+      automountkey: TestKey
+      newname: NewKeyName
+      state: renamed
diff --git a/playbooks/automount/automoutkey-absent.yml b/playbooks/automount/automoutkey-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..99eadbe3881873581a00ab1fb62c27eb8fd33881
--- /dev/null
+++ b/playbooks/automount/automoutkey-absent.yml
@@ -0,0 +1,12 @@
+---
+- name: Playbook to manage an automount key
+  hosts: ipaserver
+
+  tasks:
+  - name: Ensure autmount key is present
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      location: TestLocation
+      mapname: TestMap
+      key: TestKey
+      state: absent
diff --git a/plugins/modules/ipaautomountkey.py b/plugins/modules/ipaautomountkey.py
new file mode 100644
index 0000000000000000000000000000000000000000..8eac689635d63b5ac017f2094118b84e7ef18312
--- /dev/null
+++ b/plugins/modules/ipaautomountkey.py
@@ -0,0 +1,235 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Chris Procter <cprocter@redhat.com>
+#
+# Copyright (C) 2021 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import (absolute_import, division, print_function)
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {
+    "metadata_version": "1.0",
+    "supported_by": "community",
+    "status": ["preview"],
+}
+
+
+DOCUMENTATION = '''
+---
+module: ipaautomountkey
+author: chris procter
+short_description: Manage FreeIPA autommount map
+description:
+- Add, delete, and modify an IPA automount map
+options:
+  ipaadmin_principal:
+    description: The admin principal
+    default: admin
+  ipaadmin_password:
+    description: The admin password
+    required: False
+  location:
+    description: automount location map is in
+    required: True
+    choices: ["automountlocationcn", "automountlocation"]
+  mapname:
+    description: automount map to be managed
+    choices: ["map", "automountmapname", "automountmap"]
+    required: True
+  key:
+    description: automount key to be managed
+    required: True
+    choices: ["name", "automountkey"]
+  newkey:
+    description: key to change to if state is 'renamed'
+    required: True
+    choices: ["newname", "newautomountkey"]
+  info:
+    description: Mount information for the key
+    required: True
+    choices: ["information", "automountinformation"]
+  state:
+    description: State to ensure
+    required: False
+    default: present
+    choices: ["present", "absent", "renamed"]
+'''
+
+EXAMPLES = '''
+  - name: create key TestKey
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      locationcn: TestLocation
+      mapname: TestMap
+      key: TestKey
+      info: 192.168.122.1:/exports
+      state: present
+
+  - name: ensure key TestKey is absent
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      location: TestLocation
+      mapname: TestMap
+      key: TestKey
+      state: absent
+'''
+
+RETURN = '''
+'''
+
+from ansible.module_utils.ansible_freeipa_module import (
+    IPAAnsibleModule, ipalib_errors
+)
+
+
+class AutomountKey(IPAAnsibleModule):
+
+    def __init__(self, *args, **kwargs):
+        # pylint: disable=super-with-arguments
+        super(AutomountKey, self).__init__(*args, **kwargs)
+        self.commands = []
+
+    def get_key(self, location, mapname, key):
+        try:
+            args = {
+                "automountmapautomountmapname": mapname,
+                "automountkey": key,
+                "all": True,
+            }
+            resp = self.ipa_command("automountkey_show", location, args)
+        except ipalib_errors.NotFound:
+            return None
+        else:
+            return resp.get("result")
+
+    def check_ipa_params(self):
+        invalid = []
+        state = self.params_get("state")
+        if state == "present":
+            invalid = ["rename"]
+            if not self.params_get("info"):
+                self.fail_json(msg="Value required for argument 'info'")
+
+        if state == "rename":
+            invalid = ["info"]
+            if not self.params_get("rename"):
+                self.fail_json(msg="Value required for argument 'renamed'")
+
+        if state == "absent":
+            invalid = ["info", "rename"]
+
+        self.params_fail_used_invalid(invalid, state)
+
+    @staticmethod
+    def get_args(mapname, key, info, rename):
+        _args = {}
+        if mapname:
+            _args["automountmapautomountmapname"] = mapname
+        if key:
+            _args["automountkey"] = key
+        if info:
+            _args["automountinformation"] = info
+        if rename:
+            _args["rename"] = rename
+        return _args
+
+    def define_ipa_commands(self):
+        state = self.params_get("state")
+        location = self.params_get("location")
+        mapname = self.params_get("mapname")
+        key = self.params_get("key")
+        info = self.params_get("info")
+        rename = self.params_get("rename")
+
+        args = self.get_args(mapname, key, info, rename)
+
+        res_find = self.get_key(location, mapname, key)
+
+        if state == "present":
+            if res_find is None:
+                # does not exist and is wanted
+                self.commands.append([location, "automountkey_add", args])
+            else:
+                # exists and is wanted, check for changes
+                if info not in res_find.get("automountinformation"):
+                    self.commands.append([location, "automountkey_mod", args])
+
+        if state == "renamed":
+            if res_find is None:
+                self.fail_json(
+                    msg=(
+                        "Cannot rename inexistent key: '%s', '%s', '%s'"
+                        % (location, mapname, key)
+                    )
+                )
+            self.commands.append([location, "automountkey_mod", args])
+
+        if state == "absent":
+            # if key exists and self.ipa_params.state == "absent":
+            if res_find is not None:
+                self.commands.append([location, "automountkey_del", args])
+
+
+def main():
+    ipa_module = AutomountKey(
+        argument_spec=dict(
+            state=dict(
+                type='str',
+                choices=['present', 'absent', 'renamed'],
+                required=None,
+                default='present',
+            ),
+            location=dict(
+                type="str",
+                aliases=["automountlocationcn", "automountlocation"],
+                required=True,
+            ),
+            rename=dict(
+                type="str",
+                aliases=["new_name", "newautomountkey"],
+                required=False,
+            ),
+            mapname=dict(
+                type="str",
+                aliases=["map", "automountmapname", "automountmap"],
+                required=True,
+            ),
+            key=dict(
+                type="str",
+                aliases=["name", "automountkey"],
+                required=True,
+            ),
+            info=dict(
+                type="str",
+                aliases=["information", "automountinformation"],
+                required=False,
+            ),
+        ),
+    )
+    ipaapi_context = ipa_module.params_get("ipaapi_context")
+    with ipa_module.ipa_connect(context=ipaapi_context):
+        ipa_module.check_ipa_params()
+        ipa_module.define_ipa_commands()
+        changed = ipa_module.execute_ipa_commands(ipa_module.commands)
+    ipa_module.exit_json(changed=changed)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/automount/test_automountkey.yml b/tests/automount/test_automountkey.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6ab6bebab83b0002eaa8a2f74ace4efecb3bc7a3
--- /dev/null
+++ b/tests/automount/test_automountkey.yml
@@ -0,0 +1,154 @@
+---
+- name: Test automountmap
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: ensure test location TestLocation is present
+    ipaautomountlocation:
+      ipaadmin_password: SomeADMINpassword
+      name: TestLocation
+
+  - name: ensure test map TestMap is present
+    ipaautomountmap:
+      ipaadmin_password: SomeADMINpassword
+      name: TestMap
+      location: TestLocation
+
+  - name: ensure key NewKeyName is absent
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      location: TestLocation
+      map: TestMap
+      key: NewKeyName
+      state: absent
+
+  - name: ensure key TestKey is absent
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      location: TestLocation
+      map: TestMap
+      key: NewKeyName
+      state: absent
+
+  - block:
+    ### test the key creation, and modification
+    - name: ensure key TestKey is present
+      ipaautomountkey:
+        ipaadmin_password: SomeADMINpassword
+        location: TestLocation
+        map: TestMap
+        key: TestKey
+        info: 192.168.122.1:/exports
+        state: present
+      register: result
+      failed_when: result.failed or not result.changed
+
+    - name: ensure key TestKey is present again
+      ipaautomountkey:
+        ipaadmin_password: SomeADMINpassword
+        location: TestLocation
+        map: TestMap
+        key: TestKey
+        info: 192.168.122.1:/exports
+        state: present
+      register: result
+      failed_when: result.failed or result.changed
+
+    ## modify the key
+    - name: ensure key TestKey information has been updated
+      ipaautomountkey:
+        ipaadmin_password: SomeADMINpassword
+        location: TestLocation
+        map: TestMap
+        key: TestKey
+        info: 192.168.122.1:/nfsshare
+        state: present
+      register: result
+      failed_when: result.failed or not result.changed
+
+    - name: ensure key TestKey information has been updated again
+      ipaautomountkey:
+        ipaadmin_password: SomeADMINpassword
+        location: TestLocation
+        map: TestMap
+        key: TestKey
+        info: 192.168.122.1:/nfsshare
+        state: present
+      register: result
+      failed_when: result.failed or result.changed
+
+    ## modify the name
+    - name: ensure key TestKey has been renamed to NewKeyName
+      ipaautomountkey:
+        ipaadmin_password: SomeADMINpassword
+        location: TestLocation
+        map: TestMap
+        key: TestKey
+        new_name: NewKeyName
+        state: renamed
+      register: result
+      failed_when: result.failed or not result.changed
+
+    - name: ensure key TestKey is absent
+      ipaautomountkey:
+        ipaadmin_password: SomeADMINpassword
+        location: TestLocation
+        map: TestMap
+        key: TestKey
+        state: absent
+      register: result
+      failed_when: result.failed or result.changed
+
+    - name: ensure key NewKeyName is present
+      ipaautomountkey:
+        ipaadmin_password: SomeADMINpassword
+        location: TestLocation
+        map: TestMap
+        key: NewKeyName
+        info: 192.168.122.1:/nfsshare
+        state: present
+      register: result
+      failed_when: result.failed or result.changed
+
+    - name: ensure failure when state is renamed and newname is not set
+      ipaautomountkey:
+        ipaadmin_password: SomeADMINpassword
+        location: TestLocation
+        map: TestMap
+        key: TestKey
+        state: renamed
+      register: result
+      failed_when: not result.failed
+
+    ### cleanup after the tests
+    always:
+    - name: ensure key NewKeyName is absent
+      ipaautomountkey:
+        ipaadmin_password: SomeADMINpassword
+        location: TestLocation
+        map: TestMap
+        key: NewKeyName
+        state: absent
+
+    - name: ensure key TestKey is absent
+      ipaautomountkey:
+        ipaadmin_password: SomeADMINpassword
+        location: TestLocation
+        map: TestMap
+        key: NewKeyName
+        state: absent
+
+    - name: ensure map TestMap is absent
+      ipaautomountmap:
+        ipaadmin_password: SomeADMINpassword
+        name: TestMap
+        location: TestLocation
+        state: absent
+
+    - name: ensure location TestLocation is absent
+      ipaautomountlocation:
+        ipaadmin_password: SomeADMINpassword
+        name: TestLocation
+        state: absent
diff --git a/tests/automount/test_automountkey_client_context.yml b/tests/automount/test_automountkey_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e6d611b249d7731d688b9426fbc43858b761e4fb
--- /dev/null
+++ b/tests/automount/test_automountkey_client_context.yml
@@ -0,0 +1,41 @@
+---
+- name: Test automountkey
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      location: NoLocation
+      map: NoMap
+      key: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test automountlocation using client context, in client host.
+  import_playbook: test_automountkey.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test automountlocation using client context, in server host.
+  import_playbook: test_automountkey.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
+  vars:
+    ipa_context: client