Add a REST controller for external API access

What does this MR do and why?

This MR creates an REST API access point for an external service to update the status of a control given a project identifier, the ID for a control within that project, and authentication headers that match the control's.

References

Issue: Add REST API to update status of external requirement controls

This API endpoint is intended to be used by external control sources, so they can update the status of a control back to GitLab. They will authorize by signing the request with shared token defined when setting up the requirement control.

Design Doc: (Compliance Frameworks)[https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/compliance-frameworks/#application-programmer-interfaces-apis]

Screenshots or screen recordings

This changeset doesn't introduce and UI changes

How to set up and validate locally

  1. Go here and requirement that doesn't have a control for a new or existing framework. Add a project to the framework. I'm using "Gitlab Smoke Tests" (ID 1)
  2. In a rails console, create an external control for that requirement
requirement = ComplianceManagement::ComplianceFramework::ComplianceRequirement.last
control = requirement.compliance_requirements_controls.create!(name: :default_branch_protected, control_type: :external, external_url: "https://www.example.com", secret_token: "tacocat", namespace_id: requirement.namespace_id)
project_id = 1 # use the ID of the Project that you added to the framework
  1. Check to make sure there are no statuses for your control
control.project_control_compliance_statuses #=> should return []
  1. Run something like:
require 'openssl'
require 'net/http'
require 'uri'
require 'json'

timestamp = Time.now.to_i
nonce = SecureRandom.hex(16)
path = "/api/v4/projects/#{project_id}/control_statuses/#{control.id}"
data = "status=pass"

sign_payload = "#{timestamp}#{nonce}#{path}#{data}"

secret = "tacocat"
signature = OpenSSL::HMAC.hexdigest('SHA256', secret, sign_payload)

uri = URI("http://127.0.0.1:3000#{path}?status=pass")
request = Net::HTTP::Patch.new(uri)

request['X-Gitlab-Timestamp'] = timestamp.to_s
request['X-Gitlab-Nonce'] = nonce
request['X-Gitlab-Hmac-Sha256'] = signature
request['content-type'] = 'application/json'

http = Net::HTTP.new(uri.host, uri.port)
response = http.request(request)

puts response.body # => {"status":"pass"}
  1. Check to make sure the response matches, and verify that a status was added (or updated)
control.reload.project_control_compliance_statuses # => [#<ComplianceManagement::ComplianceFramework::ProjectControlComplianceStatus:...
Edited by Dakota Dux

Merge request reports

Loading