diff --git a/app/graphql/types/access_levels/deploy_key_type.rb b/app/graphql/types/access_levels/deploy_key_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e4e90619891465adb22e3e76d8c9bc384a2b7adf
--- /dev/null
+++ b/app/graphql/types/access_levels/deploy_key_type.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Types
+  module AccessLevels
+    class DeployKeyType < BaseObject
+      graphql_name 'AccessLevelDeployKey'
+      description 'Representation of a GitLab deploy key.'
+
+      authorize :read_deploy_key
+
+      field :id,
+        type: GraphQL::Types::ID,
+        null: false,
+        description: 'ID of the deploy key.'
+
+      field :title,
+        type: GraphQL::Types::String,
+        null: false,
+        description: 'Title of the deploy key.'
+
+      field :expires_at,
+        type: Types::DateType,
+        null: true,
+        description: 'Expiration date of the deploy key.'
+
+      field :user,
+        type: Types::AccessLevels::UserType,
+        null: false,
+        description: 'User assigned to the deploy key.'
+    end
+  end
+end
diff --git a/ee/app/graphql/types/access_levels/user_type.rb b/app/graphql/types/access_levels/user_type.rb
similarity index 94%
rename from ee/app/graphql/types/access_levels/user_type.rb
rename to app/graphql/types/access_levels/user_type.rb
index 5c273bdc1c1cbe874fa051c21aae7c1647a489f8..82aba6732506c93c0305b4d2a415abb4dba7c4e9 100644
--- a/ee/app/graphql/types/access_levels/user_type.rb
+++ b/app/graphql/types/access_levels/user_type.rb
@@ -18,7 +18,7 @@ class UserType < BaseObject
       field :username,
         type: GraphQL::Types::String,
         null: false,
-        description: 'Username of the user. Unique within this instance of GitLab.'
+        description: 'Username of the user.'
 
       field :name,
         type: GraphQL::Types::String,
diff --git a/app/graphql/types/branch_protections/push_access_level_type.rb b/app/graphql/types/branch_protections/push_access_level_type.rb
index c5e21fad88db75e73889699dc0a4a3431c2ae63f..2a66f1a4ec59b6ab1675c946a5ace446b9f2a4d3 100644
--- a/app/graphql/types/branch_protections/push_access_level_type.rb
+++ b/app/graphql/types/branch_protections/push_access_level_type.rb
@@ -6,6 +6,11 @@ class PushAccessLevelType < BaseAccessLevelType # rubocop:disable Graphql/Author
       graphql_name 'PushAccessLevel'
       description 'Defines which user roles, users, or groups can push to a protected branch.'
       accepts ::ProtectedBranch::PushAccessLevel
+
+      field :deploy_key,
+        Types::AccessLevels::DeployKeyType,
+        null: true,
+        description: 'Deploy key assigned to the access level.'
     end
   end
 end
diff --git a/app/policies/deploy_key_policy.rb b/app/policies/deploy_key_policy.rb
index b117bb5792198f5560ddb48be817296408dd37f9..ccf1bda26bb6b277bd3e59c8106415337161866a 100644
--- a/app/policies/deploy_key_policy.rb
+++ b/app/policies/deploy_key_policy.rb
@@ -3,10 +3,14 @@
 class DeployKeyPolicy < BasePolicy
   with_options scope: :subject, score: 0
   condition(:private_deploy_key) { @subject.private? }
+  condition(:public_deploy_key) { @subject.public? }
   condition(:has_deploy_key) { @user.project_deploy_keys.any? { |pdk| pdk.id.eql?(@subject.id) } }
 
   rule { anonymous }.prevent_all
-
-  rule { admin }.enable :update_deploy_key
-  rule { private_deploy_key & has_deploy_key }.enable :update_deploy_key
+  rule { public_deploy_key | admin | has_deploy_key }.policy do
+    enable :read_deploy_key
+  end
+  rule { admin | (private_deploy_key & has_deploy_key) }.policy do
+    enable :update_deploy_key
+  end
 end
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 941e077ffaa3dca7aaae0751f0632501116bb9bd..b554525ba63dc26ed8b7aed0c202fc5a0b9f62b7 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -12077,6 +12077,19 @@ Represents the access level of a relationship between a User and object that it
 | <a id="accesslevelintegervalue"></a>`integerValue` | [`Int`](#int) | Integer representation of access level. |
 | <a id="accesslevelstringvalue"></a>`stringValue` | [`AccessLevelEnum`](#accesslevelenum) | String representation of access level. |
 
+### `AccessLevelDeployKey`
+
+Representation of a GitLab deploy key.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="accessleveldeploykeyexpiresat"></a>`expiresAt` | [`Date`](#date) | Expiration date of the deploy key. |
+| <a id="accessleveldeploykeyid"></a>`id` | [`ID!`](#id) | ID of the deploy key. |
+| <a id="accessleveldeploykeytitle"></a>`title` | [`String!`](#string) | Title of the deploy key. |
+| <a id="accessleveldeploykeyuser"></a>`user` | [`AccessLevelUser!`](#accessleveluser) | User assigned to the deploy key. |
+
 ### `AccessLevelGroup`
 
 Representation of a GitLab group.
@@ -12103,7 +12116,7 @@ Representation of a GitLab user.
 | <a id="accessleveluserid"></a>`id` | [`ID!`](#id) | ID of the user. |
 | <a id="accesslevelusername"></a>`name` | [`String!`](#string) | Human-readable name of the user. Returns `****` if the user is a project bot and the requester does not have permission to view the project. |
 | <a id="accessleveluserpublicemail"></a>`publicEmail` | [`String`](#string) | User's public email. |
-| <a id="accessleveluserusername"></a>`username` | [`String!`](#string) | Username of the user. Unique within this instance of GitLab. |
+| <a id="accessleveluserusername"></a>`username` | [`String!`](#string) | Username of the user. |
 | <a id="accessleveluserwebpath"></a>`webPath` | [`String!`](#string) | Web path of the user. |
 | <a id="accessleveluserweburl"></a>`webUrl` | [`String!`](#string) | Web URL of the user. |
 
@@ -21910,6 +21923,7 @@ Defines which user roles, users, or groups can push to a protected branch.
 | ---- | ---- | ----------- |
 | <a id="pushaccesslevelaccesslevel"></a>`accessLevel` | [`Int!`](#int) | GitLab::Access level. |
 | <a id="pushaccesslevelaccessleveldescription"></a>`accessLevelDescription` | [`String!`](#string) | Human readable representation for this access level. |
+| <a id="pushaccessleveldeploykey"></a>`deployKey` | [`AccessLevelDeployKey`](#accessleveldeploykey) | Deploy key assigned to the access level. |
 | <a id="pushaccesslevelgroup"></a>`group` | [`AccessLevelGroup`](#accesslevelgroup) | Group associated with this access level. |
 | <a id="pushaccessleveluser"></a>`user` | [`AccessLevelUser`](#accessleveluser) | User associated with this access level. |
 
diff --git a/ee/spec/graphql/ee/types/branch_protections/push_access_level_type_spec.rb b/ee/spec/graphql/ee/types/branch_protections/push_access_level_type_spec.rb
index 671f1591b6a6a22addd8c933a96d6c87d139942d..4c6a2cce71b0603414c9455e6ba78871465c8904 100644
--- a/ee/spec/graphql/ee/types/branch_protections/push_access_level_type_spec.rb
+++ b/ee/spec/graphql/ee/types/branch_protections/push_access_level_type_spec.rb
@@ -5,7 +5,7 @@
 RSpec.describe GitlabSchema.types['PushAccessLevel'] do
   subject { described_class }
 
-  let(:fields) { %i[access_level access_level_description user group] }
+  let(:fields) { %i[access_level access_level_description user group deploy_key] }
 
   specify { is_expected.to have_graphql_fields(fields).only }
 end
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
index f6f06a994947b01e81484d28b79840adfeac4f89..4bd41c1faa1cfd9b6adbd140e904a49177e3a365 100644
--- a/spec/factories/keys.rb
+++ b/spec/factories/keys.rb
@@ -30,7 +30,15 @@
       key { SSHData::PrivateKey::RSA.generate(3072, unsafe_allow_small_key: true).public_key.openssh }
     end
 
-    factory :deploy_key, class: 'DeployKey'
+    factory :deploy_key, class: 'DeployKey' do
+      trait :private do
+        public { false }
+      end
+
+      trait :public do
+        public { true }
+      end
+    end
 
     factory :group_deploy_key, class: 'GroupDeployKey' do
       user
diff --git a/spec/graphql/types/access_levels/deploy_key_type_spec.rb b/spec/graphql/types/access_levels/deploy_key_type_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..02f58ec4c1508efa3af9d020a4ea815d1fa55acd
--- /dev/null
+++ b/spec/graphql/types/access_levels/deploy_key_type_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['AccessLevelDeployKey'], feature_category: :source_code_management do
+  subject { described_class }
+
+  let(:fields) { %i[id title expires_at user] }
+
+  specify { is_expected.to require_graphql_authorizations(:read_deploy_key) }
+
+  specify { is_expected.to have_graphql_fields(fields).at_least }
+end
diff --git a/ee/spec/graphql/types/access_levels/user_type_spec.rb b/spec/graphql/types/access_levels/user_type_spec.rb
similarity index 92%
rename from ee/spec/graphql/types/access_levels/user_type_spec.rb
rename to spec/graphql/types/access_levels/user_type_spec.rb
index 5ea35074d49e0bed7823c69c51d425497f09674f..7a34f70e166e4ad1af123ef98c07118c61bad353 100644
--- a/ee/spec/graphql/types/access_levels/user_type_spec.rb
+++ b/spec/graphql/types/access_levels/user_type_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe GitlabSchema.types['AccessLevelUser'] do
+RSpec.describe GitlabSchema.types['AccessLevelUser'], feature_category: :source_code_management do
   include GraphqlHelpers
 
   describe 'config' do
diff --git a/spec/graphql/types/branch_protections/push_access_level_type_spec.rb b/spec/graphql/types/branch_protections/push_access_level_type_spec.rb
index c78c0bda74c9094510926c2330bde4b971059795..ec5d42ac7203806003c572944011bf13143aa668 100644
--- a/spec/graphql/types/branch_protections/push_access_level_type_spec.rb
+++ b/spec/graphql/types/branch_protections/push_access_level_type_spec.rb
@@ -2,10 +2,10 @@
 
 require 'spec_helper'
 
-RSpec.describe GitlabSchema.types['PushAccessLevel'] do
+RSpec.describe GitlabSchema.types['PushAccessLevel'], feature_category: :source_code_management do
   subject { described_class }
 
-  let(:fields) { %i[access_level access_level_description] }
+  let(:fields) { %i[access_level access_level_description deploy_key] }
 
   specify { is_expected.to require_graphql_authorizations(:read_protected_branch) }
 
diff --git a/spec/policies/deploy_key_policy_spec.rb b/spec/policies/deploy_key_policy_spec.rb
index d84b80a8738bb0a9d950185aa714df3fe4668b27..754f36ce3b00d4a8575668143f0e841dcd27ed5e 100644
--- a/spec/policies/deploy_key_policy_spec.rb
+++ b/spec/policies/deploy_key_policy_spec.rb
@@ -2,69 +2,89 @@
 
 require 'spec_helper'
 
-RSpec.describe DeployKeyPolicy do
+RSpec.describe DeployKeyPolicy, feature_category: :groups_and_projects do
   subject { described_class.new(current_user, deploy_key) }
 
-  describe 'updating a deploy_key' do
-    context 'when a regular user' do
-      let(:current_user) { create(:user) }
+  let_it_be(:current_user, refind: true) { create(:user) }
+  let_it_be(:admin) { create(:user, :admin) }
 
-      context 'tries to update private deploy key attached to project' do
-        let(:deploy_key) { create(:deploy_key, public: false) }
-        let(:project) { create(:project_empty_repo) }
+  context 'when deploy key is public' do
+    let_it_be(:deploy_key) { create(:deploy_key, public: true) }
 
-        before do
-          project.add_maintainer(current_user)
-          project.deploy_keys << deploy_key
-        end
+    context 'and current_user is nil' do
+      let(:current_user) { nil }
 
-        it { is_expected.to be_allowed(:update_deploy_key) }
-      end
+      it { is_expected.to be_disallowed(:read_deploy_key) }
+
+      it { is_expected.to be_disallowed(:update_deploy_key) }
+    end
 
-      context 'tries to update private deploy key attached to other project' do
-        let(:deploy_key) { create(:deploy_key, public: false) }
-        let(:other_project) { create(:project_empty_repo) }
+    context 'and current_user is present' do
+      it { is_expected.to be_allowed(:read_deploy_key) }
 
-        before do
-          other_project.deploy_keys << deploy_key
-        end
+      it { is_expected.to be_disallowed(:update_deploy_key) }
+    end
 
-        it { is_expected.to be_disallowed(:update_deploy_key) }
+    context 'when current_user is admin' do
+      let(:current_user) { admin }
+
+      context 'when admin mode enabled', :enable_admin_mode do
+        it { is_expected.to be_allowed(:read_deploy_key) }
+
+        it { is_expected.to be_allowed(:update_deploy_key) }
       end
 
-      context 'tries to update public deploy key' do
-        let(:deploy_key) { create(:another_deploy_key, public: true) }
+      context 'when admin mode disabled' do
+        it { is_expected.to be_allowed(:read_deploy_key) }
 
         it { is_expected.to be_disallowed(:update_deploy_key) }
       end
     end
+  end
+
+  context 'when deploy key is private' do
+    let_it_be(:deploy_key) { create(:deploy_key, :private) }
+
+    context 'and current_user is nil' do
+      let(:current_user) { nil }
 
-    context 'when an admin user' do
-      let(:current_user) { create(:user, :admin) }
+      it { is_expected.to be_disallowed(:read_deploy_key) }
 
-      context 'tries to update private deploy key' do
-        let(:deploy_key) { create(:deploy_key, public: false) }
+      it { is_expected.to be_disallowed(:update_deploy_key) }
+    end
+
+    context 'when current_user is admin' do
+      let(:current_user) { admin }
 
-        context 'when admin mode enabled', :enable_admin_mode do
-          it { is_expected.to be_allowed(:update_deploy_key) }
-        end
+      context 'when admin mode enabled', :enable_admin_mode do
+        it { is_expected.to be_allowed(:read_deploy_key) }
 
-        context 'when admin mode disabled' do
-          it { is_expected.to be_disallowed(:update_deploy_key) }
-        end
+        it { is_expected.to be_allowed(:update_deploy_key) }
       end
 
-      context 'when an admin user tries to update public deploy key' do
-        let(:deploy_key) { create(:another_deploy_key, public: true) }
+      context 'when admin mode disabled' do
+        it { is_expected.to be_disallowed(:read_deploy_key) }
+
+        it { is_expected.to be_disallowed(:update_deploy_key) }
+      end
+    end
 
-        context 'when admin mode enabled', :enable_admin_mode do
-          it { is_expected.to be_allowed(:update_deploy_key) }
-        end
+    context 'when assigned to the project' do
+      let_it_be(:deploy_keys_project) { create(:deploy_keys_project, deploy_key: deploy_key) }
 
-        context 'when admin mode disabled' do
-          it { is_expected.to be_disallowed(:update_deploy_key) }
-        end
+      before_all do
+        deploy_keys_project.project.add_maintainer(current_user)
       end
+
+      it { is_expected.to be_allowed(:read_deploy_key) }
+
+      it { is_expected.to be_allowed(:update_deploy_key) }
+    end
+
+    context 'when assigned to another project' do
+      it { is_expected.to be_disallowed(:read_deploy_key) }
+
+      it { is_expected.to be_disallowed(:update_deploy_key) }
     end
   end
 end