diff --git a/playbooks/bridge.yaml b/playbooks/bridge.yaml
index 50614f3247..a7d2d462fd 100644
--- a/playbooks/bridge.yaml
+++ b/playbooks/bridge.yaml
@@ -7,3 +7,12 @@
     - root-keys
     - ansible-cron
     - cloud-launcher-cron
+  tasks:
+    - name: Allow Zuul to trigger Ansible
+      authorized_key:
+        state: present
+        user: root
+        key: "{{ item }}"
+      loop:
+        - "https://zuul.openstack.org/api/project-ssh-key/openstack-infra/system-config.pub"
+        - "https://zuul.openstack.org/api/project-ssh-key/openstack-infra/project-config.pub"
diff --git a/playbooks/host_vars/bridge.openstack.org.yaml b/playbooks/host_vars/bridge.openstack.org.yaml
index 71b79291b7..c089e30c6e 100644
--- a/playbooks/host_vars/bridge.openstack.org.yaml
+++ b/playbooks/host_vars/bridge.openstack.org.yaml
@@ -1 +1,2 @@
 ansible_python_interpreter: python3
+bastion_key_exclusive: false
diff --git a/playbooks/roles/base-server/README.rst b/playbooks/roles/base-server/README.rst
index de244c3a06..e61f5c0572 100644
--- a/playbooks/roles/base-server/README.rst
+++ b/playbooks/roles/base-server/README.rst
@@ -2,4 +2,8 @@ Basic common server configuration
 
 **Role Variables**
 
-* None
+.. zuul:rolevar:: bastion_key_exclusive
+   :default: True
+
+   Whether the bastion ssh key is the only key allowed to ssh in as
+   root.
diff --git a/playbooks/roles/base-server/defaults/main.yaml b/playbooks/roles/base-server/defaults/main.yaml
index 0da2e7296e..84abd845ae 100644
--- a/playbooks/roles/base-server/defaults/main.yaml
+++ b/playbooks/roles/base-server/defaults/main.yaml
@@ -1,6 +1,7 @@
 bastion_ipv4: 23.253.245.198,23.253.234.219
 bastion_ipv6: 2001:4800:7818:101:3c21:a454:23ed:4072,2001:4800:7817:103:be76:4eff:fe04:5a1d
 bastion_public_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSLlN41ftgxkNeUi/kATYPwMPjJdMaSbgokSb9PSkRPZE7GeNai60BCfhu+ky8h5eMe70Bpwb7mQ7GAtHGXPNU1SRBPhMuVN9EYrQbt5KSiwuiTXtQHsWyYrSKtB+XGbl2PhpMQ/TPVtFoL5usxu/MYaakVkCEbt5IbPYNg88/NKPixicJuhi0qsd+l1X1zoc1+Fn87PlwMoIgfLIktwaL8hw9mzqr+pPcDIjCFQQWnjqJVEObOcMstBT20XwKj/ymiH+6p123nnlIHilACJzXhmIZIZO+EGkNF7KyXpcBSfv9efPI+VCE2TOv/scJFdEHtDFkl2kdUBYPC0wQ92rp puppet-remote-2014-09-15
+bastion_key_exclusive: true
 base_packages:
   - at
   - git
diff --git a/playbooks/roles/base-server/tasks/main.yaml b/playbooks/roles/base-server/tasks/main.yaml
index 167805bfc0..c4f4f7c330 100644
--- a/playbooks/roles/base-server/tasks/main.yaml
+++ b/playbooks/roles/base-server/tasks/main.yaml
@@ -33,7 +33,7 @@
   authorized_key:
     state: present
     user: root
-    exclusive: yes
+    exclusive: "{{ bastion_key_exclusive }}"
     key: "{{ bastion_public_key }}"
     key_options: |
       from="{{ bastion_ipv4 }},{{ bastion_ipv6 }},localhost"
diff --git a/testinfra/test_bridge.py b/testinfra/test_bridge.py
index 8964c4fd86..b46eff7833 100644
--- a/testinfra/test_bridge.py
+++ b/testinfra/test_bridge.py
@@ -49,3 +49,12 @@ def test_cloud_launcher_cron(host):
     with host.sudo():
         crontab = host.check_output('crontab -l')
         assert 'run_cloud_launcher.sh' in crontab
+
+
+def test_authorized_keys(host):
+    authorized_keys = host.file('/root/.ssh/authorized_keys')
+    assert authorized_keys.exists
+
+    content = authorized_keys.content.decode('utf8')
+    lines = content.split('\n')
+    assert len(lines) >= 3