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
- 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)
- 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
- Check to make sure there are no statuses for your control
control.project_control_compliance_statuses #=> should return []
- 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"}
- 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:...