diff --git a/app/validators/json_schemas/build_metadata_secrets.json b/app/validators/json_schemas/build_metadata_secrets.json
index 952f9cf5cda03abe2c3e68d009739613b810ce26..f9122ff811c4f0c0e035c7b724dbe44cbf41f1dc 100644
--- a/app/validators/json_schemas/build_metadata_secrets.json
+++ b/app/validators/json_schemas/build_metadata_secrets.json
@@ -39,6 +39,18 @@
           },
           "additionalProperties": false
         },
+        "^gitlab_secrets_manager$": {
+          "type": "object",
+          "required": [
+            "name"
+          ],
+          "properties": {
+            "name": {
+              "type": "string"
+            }
+          },
+          "additionalProperties": false
+        },
         "^gcp_secret_manager$": {
           "type": "object",
           "required": [
@@ -198,6 +210,11 @@
           "required": [
             "akeyless"
           ]
+        },
+        {
+          "required": [
+            "gitlab_secrets_manager"
+          ]
         }
       ],
       "additionalProperties": false
diff --git a/ee/app/models/ci/secrets/integration.rb b/ee/app/models/ci/secrets/integration.rb
index 52f92befc0a206f1eacdf8fd19436699a83bb328..c8ded1cb51872d390c1152d49b160458341375aa 100644
--- a/ee/app/models/ci/secrets/integration.rb
+++ b/ee/app/models/ci/secrets/integration.rb
@@ -3,19 +3,37 @@
 module Ci
   module Secrets
     class Integration
-      PROVIDERS = [
-        :azure_key_vault,
-        :akeyless,
-        :gcp_secret_manager,
-        :hashicorp_vault
-      ].freeze
-
-      def initialize(variables)
+      PROVIDER_TYPE_MAP = {
+        "azure_key_vault" => :azure_key_vault,
+        "akeyless" => :akeyless,
+        "gcp_secret_manager" => :gcp_secret_manager,
+        "vault" => :hashicorp_vault,
+        "gitlab_secrets_manager" => :gitlab_secrets_manager
+      }.freeze
+
+      def initialize(variables:, project:)
         @variables = variables
+        @project = project
       end
 
-      def secrets_provider?
-        PROVIDERS.any? { |provider| send(:"#{provider}?") } # rubocop:disable GitlabSecurity/PublicSend -- metaprogramming
+      def secrets_provider?(secrets)
+        candidates = PROVIDER_TYPE_MAP.values.select { |provider| send(:"#{provider}?") } # rubocop:disable GitlabSecurity/PublicSend -- metaprogramming
+
+        # No providers are enabled.
+        return false if candidates.empty?
+
+        # No secrets were provided; vacuously this means all provided secrets
+        # have a provider. This is deferred so global enablement logic can be
+        # checked independently of secrets value.
+        return true if secrets.nil? || secrets.empty?
+
+        # If none of the secrets lacks an enabled provider, we're good.
+        secrets.none? do |(_, secret_info)|
+          secret_info.any? do |(provider_key, _)|
+            PROVIDER_TYPE_MAP.has_key?(provider_key) &&
+              candidates.exclude?(PROVIDER_TYPE_MAP[provider_key])
+          end
+        end
       end
 
       def variable_value(key, default = nil)
@@ -51,6 +69,12 @@ def hashicorp_vault?
       def akeyless?
         variable_value('AKEYLESS_ACCESS_ID').present?
       end
+
+      def gitlab_secrets_manager?
+        # TODO: figure out context for whether GitLab Secrets Manager is
+        # globally enabled on this instance.
+        SecretsManagement::ProjectSecretsManager.find_by_project_id(@project.id)&.active?
+      end
     end
   end
 end
diff --git a/ee/app/models/ee/ci/build.rb b/ee/app/models/ee/ci/build.rb
index 71314b483a5221d51efe3d38b40c038e8f873159..99d4faf8c3bac9b2cffd4cb29cfa69461443d47f 100644
--- a/ee/app/models/ee/ci/build.rb
+++ b/ee/app/models/ee/ci/build.rb
@@ -186,7 +186,7 @@ def runner_required_feature_names
       end
 
       def secrets_integration
-        ::Ci::Secrets::Integration.new(variables)
+        ::Ci::Secrets::Integration.new(variables: variables, project: project)
       end
 
       def playable?
diff --git a/ee/app/models/secrets_management/project_secrets_manager.rb b/ee/app/models/secrets_management/project_secrets_manager.rb
index 924912bc899940797da88e91ec99dd870c6893b6..18f89ca20faf12ef3040387d207d8267b830c6b1 100644
--- a/ee/app/models/secrets_management/project_secrets_manager.rb
+++ b/ee/app/models/secrets_management/project_secrets_manager.rb
@@ -4,7 +4,8 @@ module SecretsManagement
   class ProjectSecretsManager < ApplicationRecord
     STATUSES = {
       provisioning: 0,
-      active: 1
+      active: 1,
+      disabled: 2
     }.freeze
 
     self.table_name = 'project_secrets_managers'
@@ -16,25 +17,76 @@ class ProjectSecretsManager < ApplicationRecord
     state_machine :status, initial: :provisioning do
       state :provisioning, value: STATUSES[:provisioning]
       state :active, value: STATUSES[:active]
+      state :disabled, value: STATUSES[:disabled]
 
       event :activate do
         transition all - [:active] => :active
       end
+
+      event :disable do
+        transition active: :disabled
+      end
+    end
+
+    def self.server_url
+      # Allow setting an external secrets manager URL if necessary. This is
+      # useful for GitLab.Com's deployment.
+      return Gitlab.config.openbao.url if Gitlab.config.has_key?("openbao") && Gitlab.config.openbao.has_key?("url")
+
+      default_openbao_server_url
+    end
+
+    def self.default_openbao_server_url
+      "#{Gitlab.config.gitlab.protocol}://#{Gitlab.config.gitlab.host}:8200"
     end
+    private_class_method :default_openbao_server_url
 
     def ci_secrets_mount_path
       [
         namespace_path,
         "project_#{project.id}",
-        'ci'
+        'secrets',
+        'kv'
       ].compact.join('/')
     end
 
+    def ci_data_path(secret_key)
+      [
+        'explicit',
+        secret_key
+      ].compact.join('/')
+    end
+
+    def ci_full_path(secret_key)
+      [
+        ci_secrets_mount_path,
+        'data',
+        ci_data_path(secret_key)
+      ].compact.join('/')
+    end
+
+    def ci_auth_mount
+      [
+        namespace_path,
+        'pipeline_jwt'
+      ].compact.join('/')
+    end
+
+    def ci_auth_role
+      "project_#{project.id}"
+    end
+
+    def ci_auth_type
+      'jwt'
+    end
+
+    def ci_jwt(build)
+      Gitlab::Ci::JwtV2.for_build(build, aud: self.class.server_url)
+    end
+
     private
 
     def namespace_path
-      return unless project.namespace.type == "User"
-
       [
         project.namespace.type.downcase,
         project.namespace.id.to_s
diff --git a/ee/app/presenters/ee/ci/build_runner_presenter.rb b/ee/app/presenters/ee/ci/build_runner_presenter.rb
index d32b269736317e3a3174e01bfa1f422e4509d3ce..83c5bd7f6df9249a0c0ae42fa1f2e0e9e0abeb6f 100644
--- a/ee/app/presenters/ee/ci/build_runner_presenter.rb
+++ b/ee/app/presenters/ee/ci/build_runner_presenter.rb
@@ -15,6 +15,26 @@ def secrets_configuration
             secret['akeyless']['server'] = akeyless_server(secret)
           end
 
+          # For compatibility with the existing Vault integration in Runner,
+          # template gitlab_secrets_manager data into the vault field.
+          if secret.has_key?('gitlab_secrets_manager')
+            # GitLab Secrets Manager and Vault integrations have different
+            # structure; remove the old secret but save its data for later.
+            gtsm_secret = secret.delete('gitlab_secrets_manager')
+
+            psm = SecretsManagement::ProjectSecretsManager.find_by_project_id(project.id)
+
+            # Compute full path to secret in OpenBao for Vault runner
+            # compatibility.
+            secret['vault'] = {}
+            secret['vault']['path'] = psm.ci_data_path(gtsm_secret['name'])
+            secret['vault']['engine'] = { name: "kv-v2", path: psm.ci_secrets_mount_path }
+            secret['vault']['field'] = "value"
+
+            # Tell Runner about our server information.
+            secret['vault']['server'] = gitlab_secrets_manager_server(psm)
+          end
+
           secret
         end
       end
@@ -36,6 +56,20 @@ def vault_server(secret)
         }
       end
 
+      def gitlab_secrets_manager_server(psm)
+        @gitlab_secrets_manager_server ||= {
+          'url' => SecretsManagement::ProjectSecretsManager.server_url,
+          'auth' => {
+            'name' => psm.ci_auth_type,
+            'path' => psm.ci_auth_mount,
+            'data' => {
+              'jwt' => psm.ci_jwt(self),
+              'role' => psm.ci_auth_role
+            }.compact
+          }
+        }
+      end
+
       def vault_jwt(secret)
         if id_tokens?
           id_token_var(secret)
diff --git a/ee/app/services/ci/pipeline_creation/drop_secrets_provider_not_found_builds_service.rb b/ee/app/services/ci/pipeline_creation/drop_secrets_provider_not_found_builds_service.rb
index fbeb352b8e62145b43ffc1c8661aed0a15ec8345..d618b156dbb7249383b86663c0dc65751c74a061 100644
--- a/ee/app/services/ci/pipeline_creation/drop_secrets_provider_not_found_builds_service.rb
+++ b/ee/app/services/ci/pipeline_creation/drop_secrets_provider_not_found_builds_service.rb
@@ -12,7 +12,7 @@ def execute
 
         pipeline.builds.each do |build|
           next unless build.created?
-          next unless build.secrets? && !build.secrets_provider?
+          next unless build.secrets? && !build.secrets_provider?(build.secrets)
 
           build.drop!(:secrets_provider_not_found, skip_pipeline_processing: true)
         end
diff --git a/ee/config/events/create_secrets_gitlab_secrets_manager.yml b/ee/config/events/create_secrets_gitlab_secrets_manager.yml
new file mode 100644
index 0000000000000000000000000000000000000000..285cdcf29fa851f08730a0fc45306e0c8c534d3f
--- /dev/null
+++ b/ee/config/events/create_secrets_gitlab_secrets_manager.yml
@@ -0,0 +1,15 @@
+---
+description: Pipeline created with secrets (GitLab Secrets Manager) defined
+internal_events: true
+action: create_secrets_gitlab_secrets_manager
+identifiers:
+- namespace
+- user
+product_group: pipeline_security
+milestone: '17.6'
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162963
+distributions:
+- ee
+tiers:
+- premium
+- ultimate
diff --git a/ee/config/metrics/counts_28d/20210216184251_i_ci_secrets_management_vault_build_created_monthly.yml b/ee/config/metrics/counts_28d/20210216184251_i_ci_secrets_management_vault_build_created_monthly.yml
index 8fc3695b4c8b9997a13d22ebbd97d97665e41a01..377a47748223b3ca94694c53fdc02a3c3fabc3ef 100644
--- a/ee/config/metrics/counts_28d/20210216184251_i_ci_secrets_management_vault_build_created_monthly.yml
+++ b/ee/config/metrics/counts_28d/20210216184251_i_ci_secrets_management_vault_build_created_monthly.yml
@@ -2,7 +2,7 @@
 data_category: optional
 key_path: redis_hll_counters.ci_secrets_management.i_ci_secrets_management_vault_build_created_monthly
 description: Monthly active users creating pipelines that that have the Vault JWT
-  with it.'
+  with it.
 product_group: environments
 value_type: number
 status: active
diff --git a/ee/config/metrics/counts_28d/count_total_create_secrets_azure_key_vault_monthly.yml b/ee/config/metrics/counts_28d/count_total_create_secrets_azure_key_vault_monthly.yml
index ac953b0b7a697dc4317d3cef4116c66d94f1c632..8dc23f49b857af292f14f28f224366e6b9d349f8 100644
--- a/ee/config/metrics/counts_28d/count_total_create_secrets_azure_key_vault_monthly.yml
+++ b/ee/config/metrics/counts_28d/count_total_create_secrets_azure_key_vault_monthly.yml
@@ -1,7 +1,7 @@
 ---
 data_category: optional
 key_path: redis_hll_counters.ci_secrets_management.i_ci_secrets_management_azure_key_vault_build_created_monthly
-description: Monthly active users creating pipelines that that have the Azure Key Vault secrets.'
+description: Monthly active users creating pipelines that that have the Azure Key Vault secrets.
 product_group: pipeline_security
 value_type: number
 status: active
diff --git a/ee/config/metrics/counts_28d/count_total_create_secrets_gitlab_secrets_manager_monthly.yml b/ee/config/metrics/counts_28d/count_total_create_secrets_gitlab_secrets_manager_monthly.yml
new file mode 100644
index 0000000000000000000000000000000000000000..23cb91808daf5672934440b33d734b30a640338f
--- /dev/null
+++ b/ee/config/metrics/counts_28d/count_total_create_secrets_gitlab_secrets_manager_monthly.yml
@@ -0,0 +1,21 @@
+---
+data_category: optional
+key_path: redis_hll_counters.ci_secrets_management.i_ci_secrets_management_gitlab_secrets_manager_build_created_monthly
+description: Monthly active users creating pipelines that that have the GitLab Secret Manager secrets.
+product_group: pipeline_security
+value_type: number
+status: active
+time_frame: 28d
+data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+  events:
+    - i_ci_secrets_management_gitlab_secrets_manager_build_created
+distribution:
+  - ee
+tier:
+  - premium
+  - ultimate
+performance_indicator_type: []
+milestone: "17.6"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162963
diff --git a/ee/config/metrics/counts_28d/count_total_create_secrets_google_cloud_monthly.yml b/ee/config/metrics/counts_28d/count_total_create_secrets_google_cloud_monthly.yml
index 9dc29b29e8e4095bbd1cc8b43c60c861b5e00866..c35535b427b6584644836373174ac1fe001bc272 100644
--- a/ee/config/metrics/counts_28d/count_total_create_secrets_google_cloud_monthly.yml
+++ b/ee/config/metrics/counts_28d/count_total_create_secrets_google_cloud_monthly.yml
@@ -1,7 +1,7 @@
 ---
 data_category: optional
 key_path: redis_hll_counters.ci_secrets_management.i_ci_secrets_management_gcp_secret_manager_build_created_monthly
-description: Monthly active users creating pipelines that that have the GCP Secret Manager secrets.'
+description: Monthly active users creating pipelines that that have the GCP Secret Manager secrets.
 product_group: pipeline_security
 value_type: number
 status: active
diff --git a/ee/config/metrics/counts_7d/20210216184249_i_ci_secrets_management_vault_build_created_weekly.yml b/ee/config/metrics/counts_7d/20210216184249_i_ci_secrets_management_vault_build_created_weekly.yml
index 4d043269bfa0c0e6fe908762a5d98f062becfbc3..27db5079eecd634c93122a1eb900d677d7510498 100644
--- a/ee/config/metrics/counts_7d/20210216184249_i_ci_secrets_management_vault_build_created_weekly.yml
+++ b/ee/config/metrics/counts_7d/20210216184249_i_ci_secrets_management_vault_build_created_weekly.yml
@@ -2,7 +2,7 @@
 data_category: optional
 key_path: redis_hll_counters.ci_secrets_management.i_ci_secrets_management_vault_build_created_weekly
 description: Weekly active users creating pipelines that that have the Vault JWT
-  with it.'
+  with it.
 product_group: environments
 value_type: number
 status: active
diff --git a/ee/config/metrics/counts_7d/count_total_create_secrets_azure_key_vault_weekly.yml b/ee/config/metrics/counts_7d/count_total_create_secrets_azure_key_vault_weekly.yml
index 842de44651bf38d7fc99584ad6080c5346673762..586575a9de7a7f181d1471bcdd01269add708d92 100644
--- a/ee/config/metrics/counts_7d/count_total_create_secrets_azure_key_vault_weekly.yml
+++ b/ee/config/metrics/counts_7d/count_total_create_secrets_azure_key_vault_weekly.yml
@@ -1,7 +1,7 @@
 ---
 data_category: optional
 key_path: redis_hll_counters.ci_secrets_management.i_ci_secrets_management_azure_key_vault_build_created_weekly
-description: Monthly active users creating pipelines that that have the Azure Key Vault secrets.'
+description: Monthly active users creating pipelines that that have the Azure Key Vault secrets.
 product_group: pipeline_security
 value_type: number
 status: active
diff --git a/ee/config/metrics/counts_7d/count_total_create_secrets_gitlab_secrets_manager_weekly.yml b/ee/config/metrics/counts_7d/count_total_create_secrets_gitlab_secrets_manager_weekly.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7c6fb72c09bf3aa352e6ce3c1020429daf6bc452
--- /dev/null
+++ b/ee/config/metrics/counts_7d/count_total_create_secrets_gitlab_secrets_manager_weekly.yml
@@ -0,0 +1,21 @@
+---
+data_category: optional
+key_path: redis_hll_counters.ci_secrets_management.i_ci_secrets_management_gitlab_secrets_manager_build_created_weekly
+description: Monthly active users creating pipelines that that have the GitLab Secrets Manager secrets.
+product_group: pipeline_security
+value_type: number
+status: active
+time_frame: 7d
+data_source: redis_hll
+instrumentation_class: RedisHLLMetric
+options:
+  events:
+    - i_ci_secrets_management_gitlab_secrets_manager_build_created
+distribution:
+  - ee
+tier:
+  - premium
+  - ultimate
+performance_indicator_type: []
+milestone: "17.6"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162963
diff --git a/ee/config/metrics/counts_7d/count_total_create_secrets_google_cloud_weekly.yml b/ee/config/metrics/counts_7d/count_total_create_secrets_google_cloud_weekly.yml
index 445c6185c8377b27eaf4eb8c327a2cc2ac8d6477..ff0848caefbc97d33a99585712ea6eecba4935c2 100644
--- a/ee/config/metrics/counts_7d/count_total_create_secrets_google_cloud_weekly.yml
+++ b/ee/config/metrics/counts_7d/count_total_create_secrets_google_cloud_weekly.yml
@@ -1,7 +1,7 @@
 ---
 data_category: optional
 key_path: redis_hll_counters.ci_secrets_management.i_ci_secrets_management_gcp_secret_manager_build_created_weekly
-description: Monthly active users creating pipelines that that have the GCP Secret Manager secrets.'
+description: Monthly active users creating pipelines that that have the GCP Secret Manager secrets.
 product_group: pipeline_security
 value_type: number
 status: active
diff --git a/ee/lib/gitlab/ci/config/entry/gitlab_secrets_manager/secret.rb b/ee/lib/gitlab/ci/config/entry/gitlab_secrets_manager/secret.rb
new file mode 100644
index 0000000000000000000000000000000000000000..32d224135b2ead93c7024ceb1cb6d44389e72d8f
--- /dev/null
+++ b/ee/lib/gitlab/ci/config/entry/gitlab_secrets_manager/secret.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Ci
+    class Config
+      module Entry
+        module GitlabSecretsManager
+          class Secret < ::Gitlab::Config::Entry::Node
+            include ::Gitlab::Config::Entry::Validatable
+            include ::Gitlab::Config::Entry::Attributable
+
+            ALLOWED_KEYS = %i[name].freeze
+
+            attributes ALLOWED_KEYS
+
+            validations do
+              validates :config, type: Hash
+              validates :name, presence: true, type: String
+            end
+
+            def value
+              {
+                name: name
+              }
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/ee/lib/gitlab/ci/config/entry/secret.rb b/ee/lib/gitlab/ci/config/entry/secret.rb
index ae5625c8b25e1a1639f8f608d651426d7fe81d20..bf91e5b33b9eadf0ee344442c9e686f09926ed6c 100644
--- a/ee/lib/gitlab/ci/config/entry/secret.rb
+++ b/ee/lib/gitlab/ci/config/entry/secret.rb
@@ -11,8 +11,8 @@ class Secret < ::Gitlab::Config::Entry::Node
           include ::Gitlab::Config::Entry::Configurable
           include ::Gitlab::Config::Entry::Attributable
 
-          ALLOWED_KEYS = %i[vault file azure_key_vault gcp_secret_manager akeyless token].freeze
-          SUPPORTED_PROVIDERS = %i[vault azure_key_vault gcp_secret_manager akeyless].freeze
+          ALLOWED_KEYS = %i[vault file azure_key_vault gcp_secret_manager akeyless gitlab_secrets_manager token].freeze
+          SUPPORTED_PROVIDERS = %i[vault azure_key_vault gcp_secret_manager akeyless gitlab_secrets_manager].freeze
 
           attributes ALLOWED_KEYS
 
@@ -21,6 +21,8 @@ class Secret < ::Gitlab::Config::Entry::Node
           entry :azure_key_vault, Entry::AzureKeyVault::Secret, description: 'Azure Key Vault configuration'
           entry :gcp_secret_manager, Entry::GcpSecretManager::Secret, description: 'GCP Secrets Manager configuration'
           entry :akeyless, Entry::Akeyless::Secret, description: 'Akeyless Key Vault configuration'
+          entry :gitlab_secrets_manager, Entry::GitlabSecretsManager::Secret,
+            description: 'Gitlab Secrets Manager configuration'
 
           validations do
             validates :config, allowed_keys: ALLOWED_KEYS, only_one_of_keys: SUPPORTED_PROVIDERS
@@ -34,6 +36,7 @@ class Secret < ::Gitlab::Config::Entry::Node
           def value
             {
               vault: vault_value,
+              gitlab_secrets_manager: gitlab_secrets_manager_value,
               gcp_secret_manager: gcp_secret_manager_value,
               azure_key_vault: azure_key_vault_value,
               akeyless: akeyless_value,
diff --git a/ee/spec/lib/gitlab/ci/config/entry/gitlab_secrets_manager/secret_spec.rb b/ee/spec/lib/gitlab/ci/config/entry/gitlab_secrets_manager/secret_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..47dfb215791c8b1d3e3163e624c665136b87dfe6
--- /dev/null
+++ b/ee/spec/lib/gitlab/ci/config/entry/gitlab_secrets_manager/secret_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Entry::GitlabSecretsManager::Secret, feature_category: :secrets_management do
+  let(:entry) { described_class.new(config) }
+
+  before do
+    entry.compose!
+  end
+
+  describe 'validations' do
+    context 'when all config value is correct' do
+      let(:config) do
+        {
+          name: 'name'
+        }
+      end
+
+      it { expect(entry).to be_valid }
+    end
+
+    context 'when name is nil' do
+      let(:config) do
+        {
+          name: nil
+        }
+      end
+
+      it { expect(entry).not_to be_valid }
+
+      it 'reports error' do
+        expect(entry.errors)
+          .to include 'secret name can\'t be blank'
+      end
+    end
+
+    context 'when there is an unknown key present' do
+      let(:config) { { foo: :bar } }
+
+      it { expect(entry).not_to be_valid }
+
+      it 'reports error' do
+        expect(entry.errors)
+          .to include "secret name can't be blank"
+      end
+    end
+
+    context 'when config is not a hash' do
+      let(:config) { "" }
+
+      it { expect(entry).not_to be_valid }
+
+      it 'reports error' do
+        expect(entry.errors)
+          .to include 'secret config should be a hash'
+      end
+    end
+  end
+
+  describe '#value' do
+    context 'when config is valid' do
+      let(:config) do
+        {
+          name: 'name'
+        }
+      end
+
+      let(:result) do
+        {
+          name: "name"
+        }
+      end
+
+      it 'returns config' do
+        expect(entry.value).to eq(result)
+      end
+    end
+  end
+end
diff --git a/ee/spec/lib/gitlab/ci/config/entry/secret_spec.rb b/ee/spec/lib/gitlab/ci/config/entry/secret_spec.rb
index ae6e7ecb433d02d3d2e6aefacb2d7ba4bc04b238..e9035502c65700d2ddadc217deb9f57e03ba0208 100644
--- a/ee/spec/lib/gitlab/ci/config/entry/secret_spec.rb
+++ b/ee/spec/lib/gitlab/ci/config/entry/secret_spec.rb
@@ -299,6 +299,36 @@
           end
         end
       end
+
+      context 'for Gitlab Secrets Manager' do
+        context 'when config is valid' do
+          let(:config) do
+            {
+              gitlab_secrets_manager: {
+                name: 'name'
+              }
+            }
+          end
+
+          describe '#value' do
+            it 'returns secret configuration' do
+              expected_result = {
+                gitlab_secrets_manager: {
+                  name: 'name'
+                }
+              }
+
+              expect(entry.value).to eq(expected_result)
+            end
+          end
+
+          describe '#valid?' do
+            it 'is valid' do
+              expect(entry).to be_valid
+            end
+          end
+        end
+      end
     end
   end
 
@@ -319,7 +349,7 @@
         it 'reports error' do
           expect(entry.errors)
             .to include 'secret config must use exactly one of these keys: ' \
-            'vault, azure_key_vault, gcp_secret_manager, akeyless'
+            'vault, azure_key_vault, gcp_secret_manager, akeyless, gitlab_secrets_manager'
         end
       end
 
diff --git a/ee/spec/lib/gitlab/ci/yaml_processor_spec.rb b/ee/spec/lib/gitlab/ci/yaml_processor_spec.rb
index ed8c0326ce2b784aadc632b22cf738ab5ef50973..b90b5e2b9041e8c63c2ca2ff63b6b0918fd8bce1 100644
--- a/ee/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/ee/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -304,6 +304,32 @@
       end
     end
 
+    context 'on gitlab_secrets_manager' do
+      let(:secrets) do
+        {
+          DATABASE_PASSWORD: {
+            gitlab_secrets_manager: {
+              name: 'password'
+            }
+          }
+        }
+      end
+
+      let(:config) { { deploy_to_production: { stage: 'deploy', script: ['echo'], secrets: secrets } } }
+
+      it "returns secrets info" do
+        secrets = result.builds.first.fetch(:secrets)
+
+        expect(secrets).to eq({
+          DATABASE_PASSWORD: {
+            gitlab_secrets_manager: {
+              name: 'password'
+            }
+          }
+        })
+      end
+    end
+
     context 'on azure key vault' do
       let(:secrets) do
         {
diff --git a/ee/spec/models/ci/build_spec.rb b/ee/spec/models/ci/build_spec.rb
index 7ce242477c38798a9565b3ff3e1e7fcb9ba6fbc6..66232f18cd66c138c77deb889b34c09d9e352233 100644
--- a/ee/spec/models/ci/build_spec.rb
+++ b/ee/spec/models/ci/build_spec.rb
@@ -868,7 +868,7 @@
         stub_licensed_features(ci_secrets_management: false)
       end
 
-      (Gitlab::Ci::Config::Entry::Secret::SUPPORTED_PROVIDERS - [:akeyless]).each do |provider|
+      (Gitlab::Ci::Config::Entry::Secret::SUPPORTED_PROVIDERS - [:akeyless, :gitlab_secrets_manager]).each do |provider|
         context "when using #{provider}" do
           let(:valid_secret) { valid_secret_configs.fetch(provider) }
           let(:ci_build) { build(:ci_build, secrets: valid_secret, ci_stage: stage) }
@@ -884,7 +884,7 @@
       end
 
       context 'when there are secrets defined' do
-        (Gitlab::Ci::Config::Entry::Secret::SUPPORTED_PROVIDERS - [:akeyless]).each do |provider|
+        (Gitlab::Ci::Config::Entry::Secret::SUPPORTED_PROVIDERS - [:akeyless, :gitlab_secrets_manager]).each do |provider|
           context "when using #{provider}" do
             let(:valid_secret) { valid_secret_configs.fetch(provider) }
 
@@ -910,9 +910,10 @@
           let(:valid_secret) { valid_secret_configs.values.inject(:merge) }
 
           let(:ci_build) { build(:ci_build, secrets: valid_secret, user: user, ci_stage: stage) }
+          let(:supported_providers_with_tracking) { Gitlab::Ci::Config::Entry::Secret::SUPPORTED_PROVIDERS - [:akeyless, :gitlab_secrets_manager] }
 
           it 'tracks RedisHLL event with user_id on all providers' do
-            (Gitlab::Ci::Config::Entry::Secret::SUPPORTED_PROVIDERS - [:akeyless]).each do |provider|
+            supported_providers_with_tracking.each do |provider|
               expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
                 .with("i_ci_secrets_management_#{provider}_build_created", values: user.id)
             end
@@ -923,7 +924,7 @@
           it 'tracks Snowplow event with RedisHLL context on all providers' do
             ci_build.save!
 
-            (Gitlab::Ci::Config::Entry::Secret::SUPPORTED_PROVIDERS - [:akeyless]).each do |provider|
+            supported_providers_with_tracking.each do |provider|
               params = {
                 category: described_class.to_s,
                 action: "create_secrets_#{provider}",
diff --git a/ee/spec/models/ci/secrets/integration_spec.rb b/ee/spec/models/ci/secrets/integration_spec.rb
index 46a265d2599d8d3400124dc55e5c1203dc8be5e6..521105a171f61c2f0f4fa08dc3369a0e28ff27b0 100644
--- a/ee/spec/models/ci/secrets/integration_spec.rb
+++ b/ee/spec/models/ci/secrets/integration_spec.rb
@@ -7,7 +7,7 @@
   let_it_be_with_refind(:pipeline) { create(:ci_pipeline, project: project) }
   let(:job) { create(:ci_build, pipeline: pipeline) }
 
-  subject(:secrets_provider?) { job.secrets_provider? }
+  subject(:secrets_provider?) { job.secrets_provider?(nil) }
 
   describe '#secrets_provider?' do
     context 'when no secret CI variables are set' do
diff --git a/ee/spec/models/secrets_management/project_secrets_manager_spec.rb b/ee/spec/models/secrets_management/project_secrets_manager_spec.rb
index 58ec0d5188c38f23c816191094f4d9cdccf6fa01..b1b3b57a97263225ac05f1717e9b22c988152fc2 100644
--- a/ee/spec/models/secrets_management/project_secrets_manager_spec.rb
+++ b/ee/spec/models/secrets_management/project_secrets_manager_spec.rb
@@ -35,15 +35,59 @@
       let_it_be(:project) { create(:project) }
 
       it 'includes the namespace type and ID in the path' do
-        expect(path).to eq("user_#{project.namespace.id}/project_#{project.id}/ci")
+        expect(path).to eq("user_#{project.namespace.id}/project_#{project.id}/secrets/kv")
       end
     end
 
     context 'when the project belongs to a group namespace' do
       let_it_be(:project) { create(:project, :in_group) }
 
-      it 'does not include the namespace type and ID in the path' do
-        expect(path).to eq("project_#{project.id}/ci")
+      it 'includes the namespace type and ID in the path' do
+        expect(path).to eq("group_#{project.namespace.id}/project_#{project.id}/secrets/kv")
+      end
+    end
+  end
+
+  describe '#ci_data_path' do
+    let(:secrets_manager) { build(:project_secrets_manager, project: project) }
+
+    subject(:path) { secrets_manager.ci_data_path("DB_PASS") }
+
+    context 'when the project belongs to a user namespace' do
+      let_it_be(:project) { create(:project) }
+
+      it 'does not include any namespace information' do
+        expect(path).to eq("explicit/DB_PASS")
+      end
+    end
+
+    context 'when the project belongs to a group namespace' do
+      let_it_be(:project) { create(:project, :in_group) }
+
+      it 'does not include any namespace information' do
+        expect(path).to eq("explicit/DB_PASS")
+      end
+    end
+  end
+
+  describe '#ci_full_path' do
+    let(:secrets_manager) { build(:project_secrets_manager, project: project) }
+
+    subject(:path) { secrets_manager.ci_full_path("DB_PASS") }
+
+    context 'when the project belongs to a user namespace' do
+      let_it_be(:project) { create(:project) }
+
+      it 'does not include any namespace information' do
+        expect(path).to eq("user_#{project.namespace.id}/project_#{project.id}/secrets/kv/data/explicit/DB_PASS")
+      end
+    end
+
+    context 'when the project belongs to a group namespace' do
+      let_it_be(:project) { create(:project, :in_group) }
+
+      it 'does not include any namespace information' do
+        expect(path).to eq("group_#{project.namespace.id}/project_#{project.id}/secrets/kv/data/explicit/DB_PASS")
       end
     end
   end
diff --git a/ee/spec/presenters/ci/build_runner_presenter_spec.rb b/ee/spec/presenters/ci/build_runner_presenter_spec.rb
index 46fbc21fef1256e51180e280bffc457cc52e5f54..45fc0e86860e2b9f0a19562014e2d97e9af32cb6 100644
--- a/ee/spec/presenters/ci/build_runner_presenter_spec.rb
+++ b/ee/spec/presenters/ci/build_runner_presenter_spec.rb
@@ -6,6 +6,7 @@
 
   describe '#secrets_configuration' do
     let!(:ci_build) { create(:ci_build, secrets: secrets) }
+    let(:jwt_token) { "TESTING" }
 
     context 'build has no secrets' do
       let(:secrets) { {} }
@@ -30,22 +31,18 @@
           }
         end
 
+        before do
+          create(:ci_variable, project: ci_build.project, key: 'VAULT_SERVER_URL', value: 'https://vault.example.com')
+        end
+
         context 'Vault server URL' do
           let(:vault_server) { presenter.secrets_configuration.dig('DATABASE_PASSWORD', 'vault', 'server') }
 
           context 'VAULT_SERVER_URL CI variable is present' do
             it 'returns the URL' do
-              create(:ci_variable, project: ci_build.project, key: 'VAULT_SERVER_URL', value: 'https://vault.example.com')
-
               expect(vault_server.fetch('url')).to eq('https://vault.example.com')
             end
           end
-
-          context 'VAULT_SERVER_URL CI variable is not present' do
-            it 'returns nil' do
-              expect(vault_server.fetch('url')).to be_nil
-            end
-          end
         end
 
         context 'Vault auth role' do
@@ -332,6 +329,50 @@
         end
       end
 
+      context 'with Gitlab Secrets Manager' do
+        let(:secrets) do
+          {
+            DATABASE_PASSWORD: {
+              gitlab_secrets_manager: {
+                name: "password"
+              }
+            }
+          }
+        end
+
+        before do
+          create(:project_secrets_manager, project: ci_build.project)
+        end
+
+        context 'JWT token' do
+          before do
+            allow_any_instance_of(SecretsManagement::ProjectSecretsManager).to receive(:ci_jwt).and_return(jwt_token) # rubocop:disable RSpec/AnyInstanceOf -- It's not the next instance
+          end
+
+          let(:gitlab_secrets_manager_server) { presenter.secrets_configuration.dig('DATABASE_PASSWORD', 'vault', 'server') }
+
+          it 'uses the specified token variable' do
+            expect(gitlab_secrets_manager_server.fetch('auth')['data']['jwt']).to eq(jwt_token)
+          end
+        end
+
+        context 'JWT auth method and path' do
+          before do
+            rsa_key = OpenSSL::PKey::RSA.generate(3072).to_s
+            stub_application_setting(ci_jwt_signing_key: rsa_key)
+          end
+
+          let(:gitlab_secrets_manager_server) { presenter.secrets_configuration.dig('DATABASE_PASSWORD', 'vault', 'server') }
+
+          it 'uses the specified token variable' do
+            expect(gitlab_secrets_manager_server.fetch('auth')['name']).to eq("jwt")
+            expect(gitlab_secrets_manager_server.fetch('auth')['path']).to eq("#{ci_build.project.namespace.type.downcase}_#{ci_build.project.namespace.id}/pipeline_jwt")
+            expect(gitlab_secrets_manager_server.fetch('auth')['data']['role']).to eq("project_#{ci_build.project.id}")
+            expect(gitlab_secrets_manager_server.fetch('auth')['data']['jwt']).not_to be_empty
+          end
+        end
+      end
+
       context 'with akeyless secret manager' do
         let(:secrets) do
           {
diff --git a/ee/spec/services/ci/create_pipeline_service/secrets_spec.rb b/ee/spec/services/ci/create_pipeline_service/secrets_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ec0f5f97ea7217f14ebb5423c1960baa2bf9279b
--- /dev/null
+++ b/ee/spec/services/ci/create_pipeline_service/secrets_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::CreatePipelineService, feature_category: :secrets_management do # rubocop:disable RSpec/SpecFilePathFormat -- create_pipeline_service is split into components
+  let(:downstream_project) { create(:project, path: 'project', namespace: create(:namespace, path: 'some')) }
+
+  let_it_be(:project) { create(:project, :repository) }
+  let(:user) { project.first_owner }
+  let(:service) { described_class.new(project, user, { ref: 'refs/heads/master' }) }
+
+  let(:config) do
+    <<~YAML
+    test_openbao_direct:
+      secrets:
+        TEST_SECRET:
+          gitlab_secrets_manager:
+            name: foo
+      script:
+      - echo "testing Openbao in CI"
+      - cat $TEST_SECRET
+      - echo "done."
+    YAML
+  end
+
+  before do
+    downstream_project.add_developer(user)
+    stub_ci_pipeline_yaml_file(config)
+  end
+
+  it 'persists pipeline' do
+    pipeline = create_pipeline!
+    expect(pipeline).to be_persisted
+
+    job = pipeline.builds.find_by_name('test_openbao_direct')
+    expect(job).not_to be_failed
+  end
+
+  def create_pipeline!
+    service.execute(:push).payload
+  end
+end
diff --git a/lib/gitlab/ci/jwt_v2.rb b/lib/gitlab/ci/jwt_v2.rb
index 7892c4ad757f7c333fd031ce4c9b3bbdd4b4bc9d..5638a3c42a309d18743318f9c4dbfcb56e81b44c 100644
--- a/lib/gitlab/ci/jwt_v2.rb
+++ b/lib/gitlab/ci/jwt_v2.rb
@@ -8,7 +8,9 @@ class JwtV2 < Jwt
       GITLAB_HOSTED_RUNNER = 'gitlab-hosted'
       SELF_HOSTED_RUNNER = 'self-hosted'
 
-      def self.for_build(build, aud:, sub_components: [:project_path, :ref_type, :ref], target_audience: nil)
+      def self.for_build(
+        build, aud:, sub_components: [:project_path, :ref_type,
+          :ref], target_audience: nil)
         new(build, ttl: build.metadata_timeout, aud: aud, sub_components: sub_components,
           target_audience: target_audience).encoded
       end
diff --git a/lib/gitlab/usage_data_counters/hll_redis_legacy_events.yml b/lib/gitlab/usage_data_counters/hll_redis_legacy_events.yml
index f84348b05ed3105178c87bc514f40acca9abb58a..b95da54b559f338fc52ad7eab28091a26ebd4e91 100644
--- a/lib/gitlab/usage_data_counters/hll_redis_legacy_events.yml
+++ b/lib/gitlab/usage_data_counters/hll_redis_legacy_events.yml
@@ -42,6 +42,7 @@
 - i_ci_secrets_management_gcp_secret_manager_build_created
 - i_ci_secrets_management_id_tokens_build_created
 - i_ci_secrets_management_vault_build_created
+- i_ci_secrets_management_gitlab_secrets_manager_build_created
 - i_code_review_click_diff_view_setting
 - i_code_review_click_file_browser_setting
 - i_code_review_click_single_file_mode_setting