diff --git a/ansible/overcloud-provision.yml b/ansible/overcloud-provision.yml
index e3a8590a8f2d4c77f30f3f9c6f6206d11a18e833..d1536de30e698676094190d8bddd6e6b8e6c530e 100644
--- a/ansible/overcloud-provision.yml
+++ b/ansible/overcloud-provision.yml
@@ -143,7 +143,6 @@
         bash -c '
         export OS_CLOUD=bifrost &&
         export BIFROST_INVENTORY_SOURCE=ironic &&
-        export OS_BAREMETAL_API_VERSION=1.34 &&
         ansible-playbook -vvvv
         /bifrost/playbooks/deploy-dynamic.yaml
         --inventory /etc/bifrost/inventory/
diff --git a/ansible/roles/ironic-inspector-rules/README.md b/ansible/roles/ironic-inspector-rules/README.md
index 7a3bdb1d1bf0f6da54cc61c0d55a911f0f7109b6..bd5462e98b6bd39631c662636e5093ff5142a0e4 100644
--- a/ansible/roles/ironic-inspector-rules/README.md
+++ b/ansible/roles/ironic-inspector-rules/README.md
@@ -26,8 +26,7 @@ compatible with the `auth` argument of `os_*` Ansible modules.
 
 `ironic_inspector_cacert` is an optional path to a CA certificate.
 
-`ironic_inspector_url` is the URL of Ironic Inspector API endpoint,
-required if no authentication is used.
+`ironic_inspector_cloud` is the name of a cloud in ``clouds.yaml``.
 
 `ironic_inspector_rules` is a list of introspection rules which should
 exist. See the Inspector rules API for details of parameters available
diff --git a/ansible/roles/ironic-inspector-rules/defaults/main.yml b/ansible/roles/ironic-inspector-rules/defaults/main.yml
index 64545b517e7c0e47205ed3e8f2154fb54972407e..ee38abae1d1f6bd623594fa32806896802f2ec58 100644
--- a/ansible/roles/ironic-inspector-rules/defaults/main.yml
+++ b/ansible/roles/ironic-inspector-rules/defaults/main.yml
@@ -14,12 +14,12 @@ ironic_inspector_auth: {}
 # CA certificate path.
 ironic_inspector_cacert:
 
+# Name of cloud in clouds.yaml.
+ironic_inspector_cloud:
+
 # Interface (public, internal, admin).
 ironic_inspector_interface:
 
-# URL of Ironic Inspector API endpoint.
-ironic_inspector_url:
-
 # List of rules which should exist. See the Inspector rules API for details of
 # parameters available for rules.
 ironic_inspector_rules: []
diff --git a/ansible/roles/ironic-inspector-rules/library/os_ironic_inspector_rule.py b/ansible/roles/ironic-inspector-rules/library/os_ironic_inspector_rule.py
index e71e19cebe2cf2b0dad4e7c61c2fdaaea52a3c26..56c26e0fc359e86131288ca4cfb0c3ab80ab13a8 100644
--- a/ansible/roles/ironic-inspector-rules/library/os_ironic_inspector_rule.py
+++ b/ansible/roles/ironic-inspector-rules/library/os_ironic_inspector_rule.py
@@ -21,10 +21,6 @@ from ansible.module_utils.openstack import *
 
 # Store a list of import errors to report to the user.
 IMPORT_ERRORS = []
-try:
-    import ironic_inspector_client
-except Exception as e:
-    IMPORT_ERRORS.append(e)
 try:
     import openstack
 except Exception as e:
@@ -78,29 +74,21 @@ os_ironic_inspector_rule:
 """
 
 
-def _build_client(module, cloud):
-    """Create and return an Ironic inspector client."""
-    # Ensure the requested API version is supported.
-    # API 1.14 is the latest API version available in Rocky.
-    api_version = (1, 14)
-    client = ironic_inspector_client.v1.ClientV1(
-        inspector_url=module.params['inspector_url'],
-        interface=module.params['interface'],
-        session=cloud.session, region_name=module.params['region_name'],
-        api_version=api_version)
-    return client
+def _get_client(module, cloud):
+    """Return an Ironic inspector client."""
+    return cloud.baremetal_introspection
 
 
 def _ensure_rule_present(module, client):
     """Ensure that an inspector rule is present."""
     if module.params['uuid']:
-        try:
-            rule = client.rules.get(module.params['uuid'])
-        except ironic_inspector_client.ClientError as e:
-            if e.response.status_code != 404:
+        response = client.get('/rules/{}'.format(module.params['uuid']))
+        if not response.ok:
+            if response.status_code != 404:
                 module.fail_json(msg="Failed retrieving Inspector rule %s: %s"
                                  % (module.params['uuid'], repr(e)))
         else:
+            rule = response.json()
             # Check whether the rule differs from the request.
             keys = ('conditions', 'actions', 'description')
             for key in keys:
@@ -121,8 +109,16 @@ def _ensure_rule_present(module, client):
             # Rule differs - delete it before recreating.
             _ensure_rule_absent(module, client)
 
-    client.rules.create(module.params['conditions'], module.params['actions'],
-                        module.params['uuid'], module.params['description'])
+    rule = {
+        "conditions": module.params['conditions'],
+        "actions": module.params['actions'],
+        "description": module.params['description'],
+        "uuid": module.params['uuid'],
+    }
+    response = client.post("/rules", json=rule)
+    if not response.ok:
+        module.fail_json(msg="Failed creating Inspector rule %s: %s"
+                         % (module.params['uuid'], response.text))
     return True
 
 
@@ -130,14 +126,13 @@ def _ensure_rule_absent(module, client):
     """Ensure that an inspector rule is absent."""
     if not module.params['uuid']:
         module.fail_json(msg="UUID is required to ensure rules are absent")
-    try:
-        client.rules.delete(module.params['uuid'])
-    except ironic_inspector_client.ClientError as e:
+    response = client.delete("/rules/{}".format(module.params['uuid']))
+    if not response.ok:
         # If the rule does not exist, no problem and no change.
-        if e.response.status_code == 404:
+        if response.status_code == 404:
             return False
         module.fail_json(msg="Failed retrieving Inspector rule %s: %s"
-                         % (module.params['uuid'], repr(e)))
+                         % (module.params['uuid'], response.text))
     return True
 
 
@@ -149,7 +144,6 @@ def main():
         uuid=dict(required=False),
         state=dict(required=False, default='present',
                    choices=['present', 'absent']),
-        inspector_url=dict(required=False),
     )
     module_kwargs = openstack_module_kwargs()
     module = AnsibleModule(argument_spec, **module_kwargs)
@@ -159,20 +153,9 @@ def main():
         module.fail_json(msg="Import errors: %s" %
                          ", ".join([repr(e) for e in IMPORT_ERRORS]))
 
-    if (module.params['auth_type'] in [None, 'None'] and
-            module.params['inspector_url'] is None):
-        module.fail_json(msg="Authentication appears disabled, please "
-                             "define an inspector_url parameter")
-
-    if (module.params['inspector_url'] and
-            module.params['auth_type'] in [None, 'None']):
-        module.params['auth'] = dict(
-            endpoint=module.params['inspector_url']
-        )
-
     sdk, cloud = openstack_cloud_from_module(module)
     try:
-        client = _build_client(module, cloud)
+        client = _get_client(module, cloud)
         if module.params["state"] == "present":
             changed = _ensure_rule_present(module, client)
         else:
diff --git a/ansible/roles/ironic-inspector-rules/tasks/main.yml b/ansible/roles/ironic-inspector-rules/tasks/main.yml
index 47103073abc6a5252c1cf2fd4901a7b18ecb6238..93fbe7fcbb7c2259ff3bb1ebc12bd72e46166461 100644
--- a/ansible/roles/ironic-inspector-rules/tasks/main.yml
+++ b/ansible/roles/ironic-inspector-rules/tasks/main.yml
@@ -1,14 +1,4 @@
 ---
-- name: Ensure required Python packages are installed
-  pip:
-    name: "{{ item.name }}"
-    version: "{{ item.version | default(omit) }}"
-    state: latest
-    virtualenv: "{{ ironic_inspector_venv }}"
-    extra_args: "{% if ironic_inspector_upper_constraints_file %}-c {{ ironic_inspector_upper_constraints_file }}{% endif %}"
-  with_items:
-    - name: python-ironic-inspector-client
-
 - name: Ensure introspection rules exist
   vars:
     ansible_python_interpreter: "{{ ironic_inspector_venv }}/bin/python"
@@ -16,11 +6,11 @@
     auth_type: "{{ ironic_inspector_auth_type }}"
     auth: "{{ ironic_inspector_auth }}"
     cacert: "{{ ironic_inspector_cacert | default(omit, true) }}"
+    cloud: "{{ ironic_inspector_cloud | default(omit, true) }}"
     interface: "{{ ironic_inspector_interface | default(omit, true) }}"
     conditions: "{{ item.conditions }}"
     actions: "{{ item.actions }}"
     description: "{{ item.description | default(omit) }}"
     uuid: "{{ item.uuid | default(item.description | to_uuid) | default(omit) }}"
     state: present
-    inspector_url: "{{ ironic_inspector_url }}"
   with_items: "{{ ironic_inspector_rules }}"
diff --git a/ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/bifrost.yml b/ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/bifrost.yml
index 7ec8bf87321979496c8adcb0243271a7a345d22d..5269328feaa743c80e485a8743a4f943e1760f7e 100644
--- a/ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/bifrost.yml
+++ b/ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/bifrost.yml
@@ -71,9 +71,6 @@ use_firewalld: "{{ kolla_bifrost_use_firewalld }}"
 # Firewalld zone used by Bifrost.
 firewalld_internal_zone: "{{ kolla_bifrost_firewalld_internal_zone }}"
 
-# Disable authentication for the Ironic and Inspector APIs.
-noauth_mode: true
-
 # Enable discovery of nodes in Ironic Inspector.
 enable_inspector_discovery: true
 
diff --git a/ansible/seed-credentials.yml b/ansible/seed-credentials.yml
new file mode 100644
index 0000000000000000000000000000000000000000..076086a5b56b553135f9843d430593d62368b39f
--- /dev/null
+++ b/ansible/seed-credentials.yml
@@ -0,0 +1,45 @@
+---
+# Copy the Bifrost clouds.yaml file and CA certificate (if one is in use) to
+# the host. This allows us to access the Ironic and Inspector APIs outside of
+# the Bifrost container.
+- name: Ensure credentials are available on the host
+  hosts: seed
+  tags:
+    - seed-credentials
+  vars:
+    openstack_config_dir: "{{ ansible_facts.env.HOME }}/.config/openstack"
+  tasks:
+    - name: Ensure OpenStack config directory exists
+      file:
+        path: "{{ openstack_config_dir }}"
+        state: directory
+        mode: 0700
+
+    - name: Get clouds.yaml from Bifrost container
+      command:
+        cmd: docker exec bifrost_deploy cat /root/.config/openstack/clouds.yaml
+      changed_when: false
+      register: clouds_yaml
+      no_log: true
+
+    - name: Write clouds.yaml
+      copy:
+        content: |
+          {%- set clouds = clouds_yaml.stdout | from_yaml -%}
+          {%- for cloud in clouds.clouds.keys() | list -%}
+          {%- if 'cacert' in clouds.clouds[cloud] -%}
+          {%- set _ = clouds.clouds[cloud].update({'cacert': openstack_config_dir ~ '/bifrost.crt'}) -%}
+          {%- endif -%}
+          {%- endfor -%}
+          {{ clouds | to_nice_yaml }}
+        dest: "{{ openstack_config_dir }}/clouds.yaml"
+        mode: 0600
+
+    - name: Copy CA certificate from Bifrost container
+      vars:
+        clouds: "{{ clouds_yaml.stdout | from_yaml }}"
+        cacerts: "{{ clouds.clouds.values() | selectattr('cacert', 'defined') | map(attribute='cacert') | list }}"
+      command:
+        cmd: docker cp bifrost_deploy:{{ cacerts[0] }} {{ openstack_config_dir }}/bifrost.crt
+      changed_when: false
+      when: cacerts | length > 0
diff --git a/ansible/seed-introspection-rules.yml b/ansible/seed-introspection-rules.yml
index 6c826150b807e1f1b1c4af0098f1555a5f2ff270..f59655af611476b7b729089d4c332efa242633c2 100644
--- a/ansible/seed-introspection-rules.yml
+++ b/ansible/seed-introspection-rules.yml
@@ -9,10 +9,7 @@
       os_openstacksdk_state: latest
       ironic_inspector_venv: "{{ virtualenv_path }}/openstacksdk"
       ironic_inspector_upper_constraints_file: "{{ pip_upper_constraints_file }}"
-      # No auth required for Bifrost.
-      ironic_inspector_auth_type: None
-      ironic_inspector_auth: {}
-      ironic_inspector_url: "http://localhost:5050"
+      ironic_inspector_cloud: bifrost
       ironic_inspector_rules: "{{ kolla_bifrost_inspector_rules }}"
       # These variables may be referenced in the introspection rules.
       inspector_rule_var_ipmi_username: "{{ kolla_bifrost_inspector_ipmi_username }}"
diff --git a/kayobe/cli/commands.py b/kayobe/cli/commands.py
index 9d5c8ab2df0fea37b737445cee187d6b0f006842..a0007d887773e4ae594ae6dc1baa43e5407255e6 100644
--- a/kayobe/cli/commands.py
+++ b/kayobe/cli/commands.py
@@ -704,6 +704,7 @@ class SeedServiceDeploy(KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin,
 
         self.run_kolla_ansible_seed(parsed_args, "deploy-bifrost")
         playbooks = _build_playbook_list(
+            "seed-credentials",
             "seed-introspection-rules",
             "dell-switch-bmp")
         self.run_kayobe_playbooks(parsed_args, playbooks)
@@ -738,6 +739,7 @@ class SeedServiceUpgrade(KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin,
         self.run_kayobe_playbooks(parsed_args, playbooks)
         self.run_kolla_ansible_seed(parsed_args, "upgrade-bifrost")
         playbooks = _build_playbook_list(
+            "seed-credentials",
             "seed-introspection-rules",
             "dell-switch-bmp")
         self.run_kayobe_playbooks(parsed_args, playbooks)
diff --git a/kayobe/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py
index 5986de76b5d29a13d6c3afd79c9809c0da0f11ab..76c43343d5212aa0da8d3d02d89b98148d1d32d4 100644
--- a/kayobe/tests/unit/cli/test_commands.py
+++ b/kayobe/tests/unit/cli/test_commands.py
@@ -881,6 +881,8 @@ class TestCase(unittest.TestCase):
             mock.call(
                 mock.ANY,
                 [
+                    utils.get_data_files_path(
+                        "ansible", "seed-credentials.yml"),
                     utils.get_data_files_path(
                         "ansible", "seed-introspection-rules.yml"),
                     utils.get_data_files_path(
@@ -936,6 +938,8 @@ class TestCase(unittest.TestCase):
             mock.call(
                 mock.ANY,
                 [
+                    utils.get_data_files_path(
+                        "ansible", "seed-credentials.yml"),
                     utils.get_data_files_path(
                         "ansible",
                         "seed-introspection-rules.yml"),
diff --git a/releasenotes/notes/seed-clouds-yaml-cbaf1961c5f8ceb0.yaml b/releasenotes/notes/seed-clouds-yaml-cbaf1961c5f8ceb0.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..024fdd43e0324d45e48b825e63761653945bbf8f
--- /dev/null
+++ b/releasenotes/notes/seed-clouds-yaml-cbaf1961c5f8ceb0.yaml
@@ -0,0 +1,9 @@
+---
+features:
+  - |
+    Adds support for copying the Bifrost ``clouds.yaml`` file and optionally a
+    TLS CA certificate from the Bifrost container to the seed host. This makes
+    it possible to enable authentication and TLS for Bifrost services.
+upgrade:
+  - |
+    Enables authentication by default in Bifrost.