Commit 8d13b44b authored by Mikolaj Wawrzyniak's avatar Mikolaj Wawrzyniak

Mask grafana token with encryption

To prevent personal secret grafana token
from leaking to other maitainers,
we should display encrypted version,
similary as grafana do in its UI.
To prevent similar leakege in the future, we should
move #token into private scope.
parent a2412cd9
......@@ -368,8 +368,8 @@ module ProjectsHelper
@project.grafana_integration&.grafana_url
end
def grafana_integration_token
@project.grafana_integration&.token
def grafana_integration_masked_token
@project.grafana_integration&.masked_token
end
def grafana_integration_enabled?
......
......@@ -8,11 +8,13 @@ class GrafanaIntegration < ApplicationRecord
algorithm: 'aes-256-gcm',
key: Settings.attr_encrypted_db_key_base_32
before_validation :check_token_changes
validates :grafana_url,
length: { maximum: 1024 },
addressable_url: { enforce_sanitization: true, ascii_only: true }
validates :token, :project, presence: true
validates :encrypted_token, :project, presence: true
validates :enabled, inclusion: { in: [true, false] }
......@@ -23,4 +25,28 @@ class GrafanaIntegration < ApplicationRecord
@client ||= ::Grafana::Client.new(api_url: grafana_url.chomp('/'), token: token)
end
def masked_token
mask(encrypted_token)
end
def masked_token_was
mask(encrypted_token_was)
end
private
def token
decrypt(:token, encrypted_token)
end
def check_token_changes
return unless [encrypted_token_was, masked_token_was].include?(token)
clear_attribute_changes [:token, :encrypted_token, :encrypted_token_iv]
end
def mask(token)
token&.squish&.gsub(/./, '*')
end
end
.js-grafana-integration{ data: { operations_settings_endpoint: project_settings_operations_path(@project),
grafana_integration: { url: grafana_integration_url, token: grafana_integration_token, enabled: grafana_integration_enabled?.to_s } } }
grafana_integration: { url: grafana_integration_url, token: grafana_integration_masked_token, enabled: grafana_integration_enabled?.to_s } } }
---
title: Prevent gafana integration token from being displayed as a plain text to other project maintainers, by only displaying a masked version of it.
merge_request:
author:
type: security
......@@ -935,14 +935,14 @@ describe ProjectsHelper do
helper.instance_variable_set(:@project, project)
end
subject { helper.grafana_integration_token }
subject { helper.grafana_integration_masked_token }
it { is_expected.to eq(nil) }
context 'grafana integration exists' do
let!(:grafana_integration) { create(:grafana_integration, project: project) }
it { is_expected.to eq(grafana_integration.token) }
it { is_expected.to eq(grafana_integration.masked_token) }
end
end
......
......@@ -9,7 +9,7 @@ describe GrafanaIntegration do
describe 'validations' do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:token) }
it { is_expected.to validate_presence_of(:encrypted_token) }
it 'disallows invalid urls for grafana_url' do
unsafe_url = %{https://replaceme.com/'><script>alert(document.cookie)</script>}
......@@ -66,4 +66,24 @@ describe GrafanaIntegration do
end
end
end
describe 'attribute encryption' do
subject(:grafana_integration) { create(:grafana_integration, token: 'super-secret') }
context 'token' do
it 'encrypts original value into encrypted_token attribute' do
expect(grafana_integration.encrypted_token).not_to be_nil
end
it 'locks access to raw value in private method', :aggregate_failures do
expect { grafana_integration.token }.to raise_error(NoMethodError, /private method .token. called/)
expect(grafana_integration.send(:token)).to eql('super-secret')
end
it 'prevents overriding token value with its encrypted or masked version', :aggregate_failures do
expect { grafana_integration.update(token: grafana_integration.encrypted_token) }.not_to change { grafana_integration.reload.send(:token) }
expect { grafana_integration.update(token: grafana_integration.masked_token) }.not_to change { grafana_integration.reload.send(:token) }
end
end
end
end
......@@ -210,7 +210,7 @@ describe Projects::Operations::UpdateService do
integration = project.reload.grafana_integration
expect(integration.grafana_url).to eq(expected_attrs[:grafana_url])
expect(integration.token).to eq(expected_attrs[:token])
expect(integration.send(:token)).to eq(expected_attrs[:token])
end
end
......@@ -226,7 +226,7 @@ describe Projects::Operations::UpdateService do
integration = project.reload.grafana_integration
expect(integration.grafana_url).to eq(expected_attrs[:grafana_url])
expect(integration.token).to eq(expected_attrs[:token])
expect(integration.send(:token)).to eq(expected_attrs[:token])
end
context 'with all grafana attributes blank in params' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment