diff --git a/.rubocop_todo/gitlab/namespaced_class.yml b/.rubocop_todo/gitlab/namespaced_class.yml index 54bb5d459e071689666c563c5d2c4c98138fdf26..4ce1b36a93abf3d5ae114057a35bd43909dfb399 100644 --- a/.rubocop_todo/gitlab/namespaced_class.yml +++ b/.rubocop_todo/gitlab/namespaced_class.yml @@ -1131,7 +1131,9 @@ Gitlab/NamespacedClass: - 'lib/gitlab/empty_search_results.rb' - 'lib/gitlab/encrypted_command_base.rb' - 'lib/gitlab/encrypted_configuration.rb' + - 'lib/gitlab/encrypted_incoming_email_command.rb' - 'lib/gitlab/encrypted_ldap_command.rb' + - 'lib/gitlab/encrypted_service_desk_email_command.rb' - 'lib/gitlab/encrypted_smtp_command.rb' - 'lib/gitlab/environment_logger.rb' - 'lib/gitlab/exceptions_app.rb' diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 7342fe4821a0f6d8cc497adc7dfbe8ec828b35f0..1b18f977e4f2594e3a9fdf1bdc9c5cdb48807f4d 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -271,6 +271,10 @@ production: &base # Default is '.gitlab_mailroom_secret' relative to Rails.root (i.e. root of the GitLab app). # secret_file: /home/git/gitlab/.gitlab_mailroom_secret + # File location to read encrypted incoming email secrets from + # encrypted_secret_file: /mnt/gitlab/smtp.yaml.enc + # Default: shared/encrypted_settings/incoming_email.yaml.enc + ## Consolidated object store config ## This will only take effect if the object_store sections are not defined ## within the types (e.g. artifacts, lfs, etc.). diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index ea768b20990361663362ae2de18c38ce0a490dd3..0afe09fa0a5b042c3a934b84d24883168f3b5fcc 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -280,12 +280,14 @@ Settings['incoming_email'] ||= Settingslogic.new({}) Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled'].nil? Settings.incoming_email['inbox_method'] ||= 'imap' +Settings.incoming_email['encrypted_secret_file'] = Settings.absolute(Settings.incoming_email['encrypted_secret_file'] || File.join(Settings.encrypted_settings['path'], "incoming_email.yaml.enc")) # # Service desk email # Settings['service_desk_email'] ||= Settingslogic.new({}) Settings.service_desk_email['enabled'] = false if Settings.service_desk_email['enabled'].nil? +Settings.service_desk_email['encrypted_secret_file'] = Settings.absolute(Settings.service_desk_email['encrypted_secret_file'] || File.join(Settings.encrypted_settings['path'], "service_desk_email.yaml.enc")) # # Build Artifacts diff --git a/config/metrics/settings/20230125220526_incoming_email_encrypted_secrets_enabled.yml b/config/metrics/settings/20230125220526_incoming_email_encrypted_secrets_enabled.yml new file mode 100644 index 0000000000000000000000000000000000000000..ae73bcbafea5d899727a098866e9544e4ddcbf0b --- /dev/null +++ b/config/metrics/settings/20230125220526_incoming_email_encrypted_secrets_enabled.yml @@ -0,0 +1,23 @@ +--- +key_path: settings.incoming_email_encrypted_secrets_enabled +description: Are encrypted incoming email secrets configured? +product_section: enablement +product_stage: enablement +product_group: distribution +product_category: build +value_type: boolean +status: active +milestone: "15.9" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108279 +time_frame: none +data_source: system +data_category: optional +instrumentation_class: IncomingEmailEncryptedSecretsEnabledMetric +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/settings/20230125221700_service_desk_email_encrypted_secrets_enabled.yml b/config/metrics/settings/20230125221700_service_desk_email_encrypted_secrets_enabled.yml new file mode 100644 index 0000000000000000000000000000000000000000..227bc86adfa2f9ce8ef5b39ff5f29eee150c6fb1 --- /dev/null +++ b/config/metrics/settings/20230125221700_service_desk_email_encrypted_secrets_enabled.yml @@ -0,0 +1,23 @@ +--- +key_path: settings.service_desk_email_encrypted_secrets_enabled +description: Are service desk email secrets configured? +product_section: enablement +product_stage: enablement +product_group: distribution +product_category: build +value_type: boolean +status: active +milestone: "15.9" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108279 +time_frame: none +data_source: system +data_category: optional +instrumentation_class: ServiceDeskEmailEncryptedSecretsEnabledMetric +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/doc/administration/encrypted_configuration.md b/doc/administration/encrypted_configuration.md index 648f6d7018ef30cf1a09ea641e59aa3ca41f5ea9..1ddf2951f70b34d06e3ed9bbd7a8357c74a2d0c5 100644 --- a/doc/administration/encrypted_configuration.md +++ b/doc/administration/encrypted_configuration.md @@ -11,7 +11,9 @@ type: reference GitLab can read settings for certain features from encrypted settings files. The supported features are: +- [Incoming email `user` and `password`](incoming_email.md#use-encrypted-credentials). - [LDAP `bind_dn` and `password`](auth/ldap/index.md#use-encrypted-credentials). +- [Service Desk email `user` and `password`](../user/project/service_desk.md#use-encrypted-credentials). - [SMTP `user_name` and `password`](raketasks/smtp.md#secrets). To enable the encrypted configuration settings, a new base key must be generated for diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md index 86c80c06f16731c4e88ebbce7ac8a1f07b0faacc..ea051e2067df80c13ae79857ba855a39ab7794ab 100644 --- a/doc/administration/incoming_email.md +++ b/doc/administration/incoming_email.md @@ -867,3 +867,131 @@ gitlab_rails['incoming_email_inbox_options'] = { ``` The Microsoft Graph API is not yet supported in source installations. See [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/326169) for more details. + +### Use encrypted credentials + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108279) in GitLab 15.9. + +Instead of having the incoming email credentials stored in plaintext in the configuration files, you can optionally +use an encrypted file for the incoming email credentials. + +Prerequisites: + +- To use encrypted credentials, you must first enable the + [encrypted configuration](encrypted_configuration.md). + +The supported configuration items for the encrypted file are: + +- `user` +- `password` + +::Tabs + +:::TabTitle Linux package (Omnibus) + +1. If initially your incoming email configuration in `/etc/gitlab/gitlab.rb` looked like: + + ```ruby + gitlab_rails['incoming_email_email'] = "incoming-email@mail.example.com" + gitlab_rails['incoming_email_password'] = "examplepassword" + ``` + +1. Edit the encrypted secret: + + ```shell + sudo gitlab-rake gitlab:incoming_email:secret:edit EDITOR=vim + ``` + +1. Enter the unencrypted contents of the incoming email secret: + + ```yaml + user: 'incoming-email@mail.example.com' + password: 'examplepassword' + ``` + +1. Edit `/etc/gitlab/gitlab.rb` and remove the `incoming_email` settings for `email` and `password`. +1. Save the file and reconfigure GitLab: + + ```shell + sudo gitlab-ctl reconfigure + ``` + +:::TabTitle Helm chart (Kubernetes) + +Use a Kubernetes secret to store the incoming email password. For more information, +read about [Helm IMAP secrets](https://docs.gitlab.com/charts/installation/secrets.html#imap-password-for-incoming-emails). + +:::TabTitle Docker + +1. If initially your incoming email configuration in `docker-compose.yml` looked like: + + ```yaml + version: "3.6" + services: + gitlab: + image: 'gitlab/gitlab-ee:latest' + restart: always + hostname: 'gitlab.example.com' + environment: + GITLAB_OMNIBUS_CONFIG: | + gitlab_rails['incoming_email_email'] = "incoming-email@mail.example.com" + gitlab_rails['incoming_email_password'] = "examplepassword" + ``` + +1. Get inside the container, and edit the encrypted secret: + + ```shell + sudo docker exec -t bash + gitlab-rake gitlab:incoming_email:secret:edit EDITOR=editor + ``` + +1. Enter the unencrypted contents of the incoming email secret: + + ```yaml + user: 'incoming-email@mail.example.com' + password: 'examplepassword' + ``` + +1. Edit `docker-compose.yml` and remove the `incoming_email` settings for `email` and `password`. +1. Save the file and restart GitLab: + + ```shell + docker compose up -d + ``` + +:::TabTitle Self-compiled (source) + +1. If initially your incoming email configuration in `/home/git/gitlab/config/gitlab.yml` looked like: + + ```yaml + production: + incoming_email: + user: 'incoming-email@mail.example.com' + password: 'examplepassword' + ``` + +1. Edit the encrypted secret: + + ```shell + bundle exec rake gitlab:incoming_email:secret:edit EDITOR=vim RAILS_ENVIRONMENT=production + ``` + +1. Enter the unencrypted contents of the incoming email secret: + + ```yaml + user: 'incoming-email@mail.example.com' + password: 'examplepassword' + ``` + +1. Edit `/home/git/gitlab/config/gitlab.yml` and remove the `incoming_email:` settings for `user` and `password`. +1. Save the file and restart GitLab and Mailroom + + ```shell + # For systems running systemd + sudo systemctl restart gitlab.target + + # For systems running SysV init + sudo service gitlab restart + ``` + +::EndTabs diff --git a/doc/administration/raketasks/incoming_email.md b/doc/administration/raketasks/incoming_email.md new file mode 100644 index 0000000000000000000000000000000000000000..6b9c27ed144023b03d513dbb03919bba9a821a8b --- /dev/null +++ b/doc/administration/raketasks/incoming_email.md @@ -0,0 +1,149 @@ +--- +stage: Systems +group: Distribution +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Incoming email Rake tasks **(FREE SELF)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108279) in GitLab 15.9. + +The following are Incoming email-related Rake tasks. + +## Secrets + +GitLab can use [Incoming email](../incoming_email.md) secrets read from an encrypted file instead of storing them in plaintext in the file system. The following Rake tasks are provided for updating the contents of the encrypted file. + +### Show secret + +Show the contents of the current Incoming email secrets. + +::Tabs + +:::TabTitle Linux package (Omnibus) + +```shell +sudo gitlab-rake gitlab:incoming_email:secret:show +``` + +:::TabTitle Helm chart (Kubernetes) + +Use a Kubernetes secret to store the incoming email password. For more information, +read about [Helm IMAP secrets](https://docs.gitlab.com/charts/installation/secrets.html#imap-password-for-incoming-emails). + +:::TabTitle Docker + +```shell +sudo docker exec -t gitlab:incoming_email:secret:show +``` + +:::TabTitle Self-compiled (source) + +```shell +bundle exec rake gitlab:incoming_email:secret:show RAILS_ENV=production +``` + +::EndTabs + +#### Example output + +```plaintext +password: 'examplepassword' +user: 'incoming-email@mail.example.com' +``` + +### Edit secret + +Opens the secret contents in your editor, and writes the resulting content to the encrypted secret file when you exit. + +::Tabs + +:::TabTitle Linux package (Omnibus) + +```shell +sudo gitlab-rake gitlab:incoming_email:secret:edit EDITOR=vim +``` + +:::TabTitle Helm chart (Kubernetes) + +Use a Kubernetes secret to store the incoming email password. For more information, +read about [Helm IMAP secrets](https://docs.gitlab.com/charts/installation/secrets.html#imap-password-for-incoming-emails). + +:::TabTitle Docker + +```shell +sudo docker exec -t gitlab:incoming_email:secret:edit EDITOR=editor +``` + +:::TabTitle Self-compiled (source) + +```shell +bundle exec rake gitlab:incoming_email:secret:edit RAILS_ENV=production EDITOR=vim +``` + +::EndTabs + +### Write raw secret + +Write new secret content by providing it on `STDIN`. + +::Tabs + +:::TabTitle Linux package (Omnibus) + +```shell +echo -e "password: 'examplepassword'" | sudo gitlab-rake gitlab:incoming_email:secret:write +``` + +:::TabTitle Helm chart (Kubernetes) + +Use a Kubernetes secret to store the incoming email password. For more information, +read about [Helm IMAP secrets](https://docs.gitlab.com/charts/installation/secrets.html#imap-password-for-incoming-emails). + +:::TabTitle Docker + +```shell +sudo docker exec -t /bin/bash +echo -e "password: 'examplepassword'" | gitlab-rake gitlab:incoming_email:secret:write +``` + +:::TabTitle Self-compiled (source) + +```shell +echo -e "password: 'examplepassword'" | bundle exec rake gitlab:incoming_email:secret:write RAILS_ENV=production +``` + +::EndTabs + +### Secrets examples + +**Editor example** + +The write task can be used in cases where the edit command does not work with your editor: + +```shell +# Write the existing secret to a plaintext file +sudo gitlab-rake gitlab:incoming_email:secret:show > incoming_email.yaml +# Edit the incoming_email file in your editor +... +# Re-encrypt the file +cat incoming_email.yaml | sudo gitlab-rake gitlab:incoming_email:secret:write +# Remove the plaintext file +rm incoming_email.yaml +``` + +**KMS integration example** + +It can also be used as a receiving application for content encrypted with a KMS: + +```shell +gcloud kms decrypt --key my-key --keyring my-test-kms --plaintext-file=- --ciphertext-file=my-file --location=us-west1 | sudo gitlab-rake gitlab:incoming_email:secret:write +``` + +**Google Cloud secret integration example** + +It can also be used as a receiving application for secrets out of Google Cloud: + +```shell +gcloud secrets versions access latest --secret="my-test-secret" > $1 | sudo gitlab-rake gitlab:incoming_email:secret:write +``` diff --git a/doc/administration/raketasks/service_desk_email.md b/doc/administration/raketasks/service_desk_email.md new file mode 100644 index 0000000000000000000000000000000000000000..10de379b1cddb7e614598150259bb087562b9c02 --- /dev/null +++ b/doc/administration/raketasks/service_desk_email.md @@ -0,0 +1,149 @@ +--- +stage: Systems +group: Distribution +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Service Desk email Rake tasks **(FREE SELF)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108279) in GitLab 15.9. + +The following are Service Desk email-related Rake tasks. + +## Secrets + +GitLab can use [Service Desk email](../../user/project/service_desk.md#configuring-a-custom-mailbox) secrets read from an encrypted file instead of storing them in plaintext in the file system. The following Rake tasks are provided for updating the contents of the encrypted file. + +### Show secret + +Show the contents of the current Service Desk email secrets. + +::Tabs + +:::TabTitle Linux package (Omnibus) + +```shell +sudo gitlab-rake gitlab:service_desk_email:secret:show +``` + +:::TabTitle Helm chart (Kubernetes) + +Use a Kubernetes secret to store the Service Desk email password. For more information, +read about [Helm IMAP secrets](https://docs.gitlab.com/charts/installation/secrets.html#imap-password-for-service-desk-emails). + +:::TabTitle Docker + +```shell +sudo docker exec -t gitlab:service_desk_email:secret:show +``` + +:::TabTitle Self-compiled (source) + +```shell +bundle exec rake gitlab:service_desk_email:secret:show RAILS_ENV=production +``` + +::EndTabs + +#### Example output + +```plaintext +password: 'examplepassword' +user: 'service-desk-email@mail.example.com' +``` + +### Edit secret + +Opens the secret contents in your editor, and writes the resulting content to the encrypted secret file when you exit. + +::Tabs + +:::TabTitle Linux package (Omnibus) + +```shell +sudo gitlab-rake gitlab:service_desk_email:secret:edit EDITOR=vim +``` + +:::TabTitle Helm chart (Kubernetes) + +Use a Kubernetes secret to store the Service Desk email password. For more information, +read about [Helm IMAP secrets](https://docs.gitlab.com/charts/installation/secrets.html#imap-password-for-service-desk-emails). + +:::TabTitle Docker + +```shell +sudo docker exec -t gitlab:service_desk_email:secret:edit EDITOR=editor +``` + +:::TabTitle Self-compiled (source) + +```shell +bundle exec rake gitlab:service_desk_email:secret:edit RAILS_ENV=production EDITOR=vim +``` + +::EndTabs + +### Write raw secret + +Write new secret content by providing it on `STDIN`. + +::Tabs + +:::TabTitle Linux package (Omnibus) + +```shell +echo -e "password: 'examplepassword'" | sudo gitlab-rake gitlab:service_desk_email:secret:write +``` + +:::TabTitle Helm chart (Kubernetes) + +Use a Kubernetes secret to store the Service Desk email password. For more information, +read about [Helm IMAP secrets](https://docs.gitlab.com/charts/installation/secrets.html#imap-password-for-service-desk-emails). + +:::TabTitle Docker + +```shell +sudo docker exec -t /bin/bash +echo -e "password: 'examplepassword'" | gitlab-rake gitlab:service_desk_email:secret:write +``` + +:::TabTitle Self-compiled (source) + +```shell +echo -e "password: 'examplepassword'" | bundle exec rake gitlab:service_desk_email:secret:write RAILS_ENV=production +``` + +::EndTabs + +### Secrets examples + +**Editor example** + +The write task can be used in cases where the edit command does not work with your editor: + +```shell +# Write the existing secret to a plaintext file +sudo gitlab-rake gitlab:service_desk_email:secret:show > service_desk_email.yaml +# Edit the service_desk_email file in your editor +... +# Re-encrypt the file +cat service_desk_email.yaml | sudo gitlab-rake gitlab:service_desk_email:secret:write +# Remove the plaintext file +rm service_desk_email.yaml +``` + +**KMS integration example** + +It can also be used as a receiving application for content encrypted with a KMS: + +```shell +gcloud kms decrypt --key my-key --keyring my-test-kms --plaintext-file=- --ciphertext-file=my-file --location=us-west1 | sudo gitlab-rake gitlab:service_desk_email:secret:write +``` + +**Google Cloud secret integration example** + +It can also be used as a receiving application for secrets out of Google Cloud: + +```shell +gcloud secrets versions access latest --secret="my-test-secret" > $1 | sudo gitlab-rake gitlab:service_desk_email:secret:write +``` diff --git a/doc/raketasks/index.md b/doc/raketasks/index.md index 1b8eccc7ae4203db0574d1fba21c37587cab0846..b5a778d6b7417dbf249afaaf5570da9bea19ee1e 100644 --- a/doc/raketasks/index.md +++ b/doc/raketasks/index.md @@ -29,6 +29,7 @@ The following Rake tasks are available for use with GitLab: | [Geo maintenance](../administration/raketasks/geo.md) | [Geo](../administration/geo/index.md)-related maintenance. | | [GitHub import](../administration/raketasks/github_import.md) | Retrieve and import repositories from GitHub. | | [Import large project exports](../development/import_project.md#importing-via-a-rake-task) | Import large GitLab [project exports](../user/project/settings/import_export.md). | +| [Incoming email](../administration/raketasks/incoming_email.md) | Incoming email-related tasks. | | [Integrity checks](../administration/raketasks/check.md) | Check the integrity of repositories, files, LDAP, and more. | | [LDAP maintenance](../administration/raketasks/ldap.md) | [LDAP](../administration/auth/ldap/index.md)-related tasks. | | [List repositories](list_repos.md) | List all GitLab-managed Git repositories on disk. | @@ -37,6 +38,7 @@ The following Rake tasks are available for use with GitLab: | [Project import/export](../administration/raketasks/project_import_export.md) | Prepare for [project exports and imports](../user/project/settings/import_export.md). | | [Sample Prometheus data](generate_sample_prometheus_data.md) | Generate sample Prometheus data. | | [Sidekiq job migration](../administration/sidekiq/sidekiq_job_migration.md) | Migrate Sidekiq jobs scheduled for future dates to a new queue. | +| [Service Desk email](../administration/raketasks/service_desk_email.md) | Service Desk email-related tasks. | | [SMTP maintenance](../administration/raketasks/smtp.md) | SMTP-related tasks. | | [SPDX license list import](spdx.md) | Import a local copy of the [SPDX license list](https://spdx.org/licenses/) for matching [License Compliance policies](../user/compliance/license_compliance/index.md). | | [Repository storage](../administration/raketasks/storage.md) | List and migrate existing projects and attachments from legacy storage to hashed storage. | diff --git a/doc/user/project/service_desk.md b/doc/user/project/service_desk.md index cbc64fe2368b251fe72f94c39d5e654a0b1b631f..732450f54433d8ea70f058c2bea18071e339ac41 100644 --- a/doc/user/project/service_desk.md +++ b/doc/user/project/service_desk.md @@ -260,6 +260,134 @@ service_desk_email: The configuration options are the same as for configuring [incoming email](../../administration/incoming_email.md#set-it-up). +##### Use encrypted credentials + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108279) in GitLab 15.9. + +Instead of having the Service Desk email credentials stored in plaintext in the configuration files, you can optionally +use an encrypted file for the Incoming email credentials. + +Prerequisites: + +- To use encrypted credentials, you must first enable the + [encrypted configuration](../../administration/encrypted_configuration.md). + +The supported configuration items for the encrypted file are: + +- `user` +- `password` + +::Tabs + +:::TabTitle Linux package (Omnibus) + +1. If initially your Service Desk configuration in `/etc/gitlab/gitlab.rb` looked like: + + ```ruby + gitlab_rails['service_desk_email_email'] = "service-desk-email@mail.example.com" + gitlab_rails['service_desk_email_password'] = "examplepassword" + ``` + +1. Edit the encrypted secret: + + ```shell + sudo gitlab-rake gitlab:service_desk_email:secret:edit EDITOR=vim + ``` + +1. Enter the unencrypted contents of the Service Desk email secret: + + ```yaml + user: 'service-desk-email@mail.example.com' + password: 'examplepassword' + ``` + +1. Edit `/etc/gitlab/gitlab.rb` and remove the `service_desk` settings for `email` and `password`. +1. Save the file and reconfigure GitLab: + + ```shell + sudo gitlab-ctl reconfigure + ``` + +:::TabTitle Helm chart (Kubernetes) + +Use a Kubernetes secret to store the Service Desk email password. For more information, +read about [Helm IMAP secrets](https://docs.gitlab.com/charts/installation/secrets.html#imap-password-for-service-desk-emails). + +:::TabTitle Docker + +1. If initially your Service Desk configuration in `docker-compose.yml` looked like: + + ```yaml + version: "3.6" + services: + gitlab: + image: 'gitlab/gitlab-ee:latest' + restart: always + hostname: 'gitlab.example.com' + environment: + GITLAB_OMNIBUS_CONFIG: | + gitlab_rails['service_desk_email_email'] = "service-desk-email@mail.example.com" + gitlab_rails['service_desk_email_password'] = "examplepassword" + ``` + +1. Get inside the container, and edit the encrypted secret: + + ```shell + sudo docker exec -t bash + gitlab-rake gitlab:service_desk_email:secret:edit EDITOR=editor + ``` + +1. Enter the unencrypted contents of the Service Desk secret: + + ```yaml + user: 'service-desk-email@mail.example.com' + password: 'examplepassword' + ``` + +1. Edit `docker-compose.yml` and remove the `service_desk` settings for `email` and `password`. +1. Save the file and restart GitLab: + + ```shell + docker compose up -d + ``` + +:::TabTitle Self-compiled (source) + +1. If initially your Service Desk configuration in `/home/git/gitlab/config/gitlab.yml` looked like: + + ```yaml + production: + service_desk_email: + user: 'service-desk-email@mail.example.com' + password: 'examplepassword' + ``` + +1. Edit the encrypted secret: + + ```shell + bundle exec rake gitlab:service_desk_email:secret:edit EDITOR=vim RAILS_ENVIRONMENT=production + ``` + +1. Enter the unencrypted contents of the Service Desk secret: + + ```yaml + user: 'service-desk-email@mail.example.com' + password: 'examplepassword' + ``` + +1. Edit `/home/git/gitlab/config/gitlab.yml` and remove the `service_desk_email:` settings for `user` and `password`. +1. Save the file and restart GitLab and Mailroom + + ```shell + # For systems running systemd + sudo systemctl restart gitlab.target + + # For systems running SysV init + sudo service gitlab restart + ``` + +::EndTabs + ##### Microsoft Graph > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214900) in GitLab 13.11. diff --git a/lib/gitlab/email/common.rb b/lib/gitlab/email/common.rb index afee8d9cd3d838043243ba6a32b9ebce2aecc156..01316995c4d437d1fbc0ff24c17e00865048715e 100644 --- a/lib/gitlab/email/common.rb +++ b/lib/gitlab/email/common.rb @@ -54,6 +54,10 @@ def scan_fallback_references(references) # It's looking for each <...> references.scan(/(?!<)[^<>]+(?=>)/) end + + def encrypted_secrets + Settings.encrypted(config.encrypted_secret_file) + end end end end diff --git a/lib/gitlab/encrypted_incoming_email_command.rb b/lib/gitlab/encrypted_incoming_email_command.rb new file mode 100644 index 0000000000000000000000000000000000000000..a18382439d68758b213048674c997f5467b1cc12 --- /dev/null +++ b/lib/gitlab/encrypted_incoming_email_command.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# rubocop:disable Rails/Output +module Gitlab + class EncryptedIncomingEmailCommand < EncryptedCommandBase + DISPLAY_NAME = "INCOMING_EMAIL" + EDIT_COMMAND_NAME = "gitlab:incoming_email:secret:edit" + + class << self + def encrypted_secrets + Gitlab::IncomingEmail.encrypted_secrets + end + + def encrypted_file_template + <<~YAML + # password: '123' + # user: 'gitlab-incoming@gmail.com' + YAML + end + end + end +end +# rubocop:enable Rails/Output diff --git a/lib/gitlab/encrypted_service_desk_email_command.rb b/lib/gitlab/encrypted_service_desk_email_command.rb new file mode 100644 index 0000000000000000000000000000000000000000..ece6da7c1b3d94045d8ba28cec620112478d1eed --- /dev/null +++ b/lib/gitlab/encrypted_service_desk_email_command.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# rubocop:disable Rails/Output +module Gitlab + class EncryptedServiceDeskEmailCommand < EncryptedCommandBase + DISPLAY_NAME = "SERVICE_DESK_EMAIL" + EDIT_COMMAND_NAME = "gitlab:service_desk_email:secret:edit" + + class << self + def encrypted_secrets + Gitlab::ServiceDeskEmail.encrypted_secrets + end + + def encrypted_file_template + <<~YAML + # password: '123' + # user: 'gitlab-incoming@gmail.com' + YAML + end + end + end +end +# rubocop:enable Rails/Output diff --git a/lib/gitlab/mail_room.rb b/lib/gitlab/mail_room.rb index f7cd28df5c9b5f3ef3a437ef76608927eca990ff..bad2e265f73a4954e89df31400c8b674e8fbc9de 100644 --- a/lib/gitlab/mail_room.rb +++ b/lib/gitlab/mail_room.rb @@ -3,6 +3,9 @@ require 'yaml' require 'json' require 'pathname' +require 'active_support' +require "active_support/core_ext/module/delegation" +require_relative 'encrypted_configuration' unless defined?(Gitlab::EncryptedConfiguration) require_relative 'redis/queues' unless defined?(Gitlab::Redis::Queues) # This service is run independently of the main Rails process, @@ -42,6 +45,20 @@ module MailRoom } }.freeze + # Default path strings (this is a data duplication + # with Settings which is not pulled in - see the service + # comment at the top of this file) + DEFAULT_PATHS = { + shared_path: 'shared', + encrypted_settings_path: 'encrypted_settings', + incoming_email: { + encrypted_secret_filename: 'incoming_email.yaml.enc' + }, + service_desk_email: { + encrypted_secret_filename: 'service_desk_email.yaml.enc' + } + }.freeze + class << self def enabled_configs @enabled_configs ||= configs.select { |_key, config| enabled?(config) } @@ -74,6 +91,12 @@ def fetch_config(config_key) config[:log_path] = File.expand_path(config[:log_path], RAILS_ROOT_DIR) + # override password/user from any encrypted secrets + if secrets = decrypted_secrets(config_key) + config[:password] = secrets[:password] if secrets[:password] + config[:user] = secrets[:user] if secrets[:user] + end + config end @@ -108,6 +131,58 @@ def config_file def load_yaml @yaml ||= YAML.load_file(config_file)[rails_env].deep_symbolize_keys end + + def application_secrets_file + ENV['MAIL_ROOM_GITLAB_SECRETS_FILE'] || File.expand_path('../../config/secrets.yml', __dir__) + end + + def application_secrets + @application_secrets ||= {}.tap do |application_secrets| + # Uses Rails::Secret.parse + # from: https://github.com/rails/rails/blob/v6.1.6.1/railties/lib/rails/secrets.rb#L24 + erb_processed_yaml = ERB.new(File.read(application_secrets_file)).result + yaml_secrets = + YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(erb_processed_yaml) : YAML.safe_load(erb_processed_yaml) + application_secrets.merge!(yaml_secrets["shared"].deep_symbolize_keys) if yaml_secrets["shared"] + application_secrets.merge!(yaml_secrets[rails_env].deep_symbolize_keys) if yaml_secrets[rails_env] + end + end + + def default_encrypted_secret_filename(config_key) + DEFAULT_PATHS[config_key][:encrypted_secret_filename] + end + + def encrypted_secret_file(config_key) + config = merged_configs(config_key) + return config[:encrypted_secret_file] if config[:encrypted_secret_file] + + config_yaml = load_yaml + # Path handling for shared.path / encrypted_settings.path is a duplicate + # of the logic in config/initializers/1_settings.rb + shared_path = File.expand_path(config_yaml.dig(:shared, :path) || + DEFAULT_PATHS[:shared_path], RAILS_ROOT_DIR) + encrypted_settings_path = + File.expand_path(config_yaml.dig(:encrypted_settings, :path) || + File.join(shared_path, DEFAULT_PATHS[:encrypted_settings_path]), + RAILS_ROOT_DIR) + File.join(encrypted_settings_path, default_encrypted_secret_filename(config_key)) + end + + def encrypted_configuration_settings(config_key) + { + content_path: encrypted_secret_file(config_key), + base_key: application_secrets[:encrypted_settings_key_base], + previous_keys: application_secrets[:rotated_encrypted_settings_key_base] || [] + } + end + + def decrypted_secrets(config_key) + settings = encrypted_configuration_settings(config_key) + return if settings[:base_key].nil? + + encrypted = Gitlab::EncryptedConfiguration.new(**settings) + encrypted.active? ? encrypted.config : nil + end end end end diff --git a/lib/gitlab/usage/metrics/instrumentations/incoming_email_encrypted_secrets_enabled_metric.rb b/lib/gitlab/usage/metrics/instrumentations/incoming_email_encrypted_secrets_enabled_metric.rb new file mode 100644 index 0000000000000000000000000000000000000000..ab9c6f87023c028b91e4892bf503e7bd275e27ce --- /dev/null +++ b/lib/gitlab/usage/metrics/instrumentations/incoming_email_encrypted_secrets_enabled_metric.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + module Instrumentations + class IncomingEmailEncryptedSecretsEnabledMetric < GenericMetric + value do + Gitlab::IncomingEmail.encrypted_secrets.active? + end + end + end + end + end +end diff --git a/lib/gitlab/usage/metrics/instrumentations/service_desk_email_encrypted_secrets_enabled_metric.rb b/lib/gitlab/usage/metrics/instrumentations/service_desk_email_encrypted_secrets_enabled_metric.rb new file mode 100644 index 0000000000000000000000000000000000000000..4332043de8a0c510ee8dfd72ec6381ddc70e711a --- /dev/null +++ b/lib/gitlab/usage/metrics/instrumentations/service_desk_email_encrypted_secrets_enabled_metric.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + module Instrumentations + class ServiceDeskEmailEncryptedSecretsEnabledMetric < GenericMetric + value do + Gitlab::ServiceDeskEmail.encrypted_secrets.active? + end + end + end + end + end +end diff --git a/lib/tasks/gitlab/incoming_email.rake b/lib/tasks/gitlab/incoming_email.rake new file mode 100644 index 0000000000000000000000000000000000000000..eaceeb91ad00b3b79cd12f58b7bc32338c5eb9a1 --- /dev/null +++ b/lib/tasks/gitlab/incoming_email.rake @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +namespace :gitlab do + namespace :incoming_email do + namespace :secret do + desc 'GitLab | Incoming Email | Secret | Write Incoming Email secrets' + task write: [:environment] do + content = $stdin.tty? ? $stdin.gets : $stdin.read + Gitlab::EncryptedIncomingEmailCommand.write(content) + end + + desc 'GitLab | Incoming Email | Secret | Edit Incoming Email secrets' + task edit: [:environment] do + Gitlab::EncryptedIncomingEmailCommand.edit + end + + desc 'GitLab | Incoming Email | Secret | Show Incoming Email secrets' + task show: [:environment] do + Gitlab::EncryptedIncomingEmailCommand.show + end + end + end +end diff --git a/lib/tasks/gitlab/service_desk_email.rake b/lib/tasks/gitlab/service_desk_email.rake new file mode 100644 index 0000000000000000000000000000000000000000..39c9d40bf5cf0d8973c3759cb7755d839e91ec28 --- /dev/null +++ b/lib/tasks/gitlab/service_desk_email.rake @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +namespace :gitlab do + namespace :service_desk_email do + namespace :secret do + desc 'GitLab | Service Desk Email | Secret | Write Service Desk Email secrets' + task write: [:environment] do + content = $stdin.tty? ? $stdin.gets : $stdin.read + Gitlab::EncryptedServiceDeskEmailCommand.write(content) + end + + desc 'GitLab | Service Desk Email | Secret | Edit Service Desk Email secrets' + task edit: [:environment] do + Gitlab::EncryptedServiceDeskEmailCommand.edit + end + + desc 'GitLab | Service Desk Email | Secret | Show Service Desk Email secrets' + task show: [:environment] do + Gitlab::EncryptedServiceDeskEmailCommand.show + end + end + end +end diff --git a/spec/fixtures/mail_room/encrypted_secrets/incoming_email.yaml.enc b/spec/fixtures/mail_room/encrypted_secrets/incoming_email.yaml.enc new file mode 100644 index 0000000000000000000000000000000000000000..c11722e68478e4dd95e5e2d99e84aed460651286 --- /dev/null +++ b/spec/fixtures/mail_room/encrypted_secrets/incoming_email.yaml.enc @@ -0,0 +1 @@ +v7dkZ/3zpOBsA6XhOq99ai5S4czZlkdLUmNcH8AYOw56b7PgEMmtLHu39Fcyd4ZvERJNMIqyLuCwooqvJKfUeFRi9HpY0Q==--GldddWssSV3R/Ood--1XlQ63E4XCVqcfZVAd+NGQ== \ No newline at end of file diff --git a/spec/fixtures/mail_room/encrypted_secrets/service_desk_email.yaml.enc b/spec/fixtures/mail_room/encrypted_secrets/service_desk_email.yaml.enc new file mode 100644 index 0000000000000000000000000000000000000000..bad1f2bbce11dac7975b056d877f332c9b386144 --- /dev/null +++ b/spec/fixtures/mail_room/encrypted_secrets/service_desk_email.yaml.enc @@ -0,0 +1 @@ +nWE1RNp/i+RzboK4SqbQBI52cFP14G3hduqIOdus2Ffgul8n0vL/bKHMHaiCttq2hzGnzw5zcMGUWxJu3RkAhR0jnLAgbpBrYsxKTH72cqkJfw==--5FtWCIPwB8DldWkY--FLjDkVAsCzDM9VY1x7yifg== \ No newline at end of file diff --git a/spec/fixtures/mail_room/secrets.yml.erb b/spec/fixtures/mail_room/secrets.yml.erb new file mode 100644 index 0000000000000000000000000000000000000000..fce7ff3180e1b9976f6cc4a10d7263c481b2a03f --- /dev/null +++ b/spec/fixtures/mail_room/secrets.yml.erb @@ -0,0 +1,11 @@ +--- +production: + an_unread_key: 'this key will not be in the secrets' +shared: + a_shared_key: 'this key is shared' + an_overriden_shared_key: 'this key is overwritten by merge' +test: + an_environment_specific_key: 'test environment value' + an_overriden_shared_key: 'the merge overwrote this key' + erb_env_key: <%= ENV['KEY'] %> + encrypted_settings_key_base: <%= '0123456789abcdef' * 8 %> diff --git a/spec/lib/gitlab/mail_room/mail_room_spec.rb b/spec/lib/gitlab/mail_room/mail_room_spec.rb index 0c2c9b8900560e4a12dac1a8e512cbb29ba533c5..7259b5e2484cc784e9ad575be7ef4f1e8b257e61 100644 --- a/spec/lib/gitlab/mail_room/mail_room_spec.rb +++ b/spec/lib/gitlab/mail_room/mail_room_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::MailRoom do +RSpec.describe Gitlab::MailRoom, feature_category: :build do let(:default_port) { 143 } let(:log_path) { Rails.root.join('log', 'mail_room_json.log').to_s } @@ -328,4 +328,123 @@ end end end + + describe 'mailroom encrypted configuration' do + context "when parsing secrets.yml" do + let(:application_secrets_file) { Rails.root.join('spec/fixtures/mail_room/secrets.yml.erb').to_s } + let(:encrypted_settings_key_base) { '0123456789abcdef' * 8 } + + before do + allow(described_class).to receive(:application_secrets_file).and_return(application_secrets_file) + stub_env('KEY', 'an environment variable value') + described_class.instance_variable_set(:@application_secrets, nil) + end + + after do + described_class.instance_variable_set(:@application_secrets, nil) + end + + it 'reads in the secrets.yml file as erb and merges shared and test environments' do + application_secrets = described_class.send(:application_secrets) + + expect(application_secrets).to match(a_hash_including( + a_shared_key: 'this key is shared', + an_overriden_shared_key: 'the merge overwrote this key', + an_environment_specific_key: 'test environment value', + erb_env_key: 'an environment variable value', + encrypted_settings_key_base: encrypted_settings_key_base + )) + + expect(application_secrets[:an_unread_key]).to be_nil + end + end + + context "when parsing gitlab.yml" do + let(:plain_configs) { configs } + let(:shared_path_config) do + { shared: { path: '/this/is/my/shared_path' } }.merge(configs) + end + + let(:encrypted_settings_config) do + { + shared: { path: '/this/is/my/shared_path' }, + encrypted_settings: { path: '/this/is/my_custom_encrypted_path' } + }.merge(configs) + end + + let(:encrypted_file_config) do + configs.deep_merge({ + incoming_email: { encrypted_secret_file: '/custom_incoming_secret.yaml.enc' }, + service_desk_email: { encrypted_secret_file: '/custom_service_desk_secret.yaml.enc' } + }) + end + + it 'returns default encrypted_secret_file path' do + allow(described_class).to receive(:load_yaml).and_return(plain_configs) + + expect(described_class.send(:encrypted_secret_file, :incoming_email)) + .to end_with('shared/encrypted_settings/incoming_email.yaml.enc') + + expect(described_class.send(:encrypted_secret_file, :service_desk_email)) + .to end_with('shared/encrypted_settings/service_desk_email.yaml.enc') + end + + it 'returns encrypted_secret_file relative to custom shared path' do + allow(described_class).to receive(:load_yaml).and_return(shared_path_config) + + expect(described_class.send(:encrypted_secret_file, :incoming_email)) + .to eq('/this/is/my/shared_path/encrypted_settings/incoming_email.yaml.enc') + + expect(described_class.send(:encrypted_secret_file, :service_desk_email)) + .to eq('/this/is/my/shared_path/encrypted_settings/service_desk_email.yaml.enc') + end + + it 'returns custom encrypted_secret_file' do + allow(described_class).to receive(:load_yaml).and_return(encrypted_file_config) + + expect(described_class.send(:encrypted_secret_file, :incoming_email)) + .to eq('/custom_incoming_secret.yaml.enc') + + expect(described_class.send(:encrypted_secret_file, :service_desk_email)) + .to eq('/custom_service_desk_secret.yaml.enc') + end + end + + context 'when using encrypted secrets' do + let(:mail_room_template) { ERB.new(File.read(Rails.root.join("./config/mail_room.yml"))).result } + let(:mail_room_yml) { YAML.safe_load(mail_room_template, permitted_classes: [Symbol]) } + let(:application_secrets) { { encrypted_settings_key_base: '0123456789abcdef' * 8 } } # gitleaks:allow + let(:configs) do + { + encrypted_settings: { path: 'spec/fixtures/mail_room/encrypted_secrets' } + }.merge({ + incoming_email: incoming_email_config, + service_desk_email: service_desk_email_config + }) + end + + before do + allow(described_class).to receive(:application_secrets).and_return(application_secrets) + end + + it 'renders the encrypted secrets into the configuration correctly' do + expect(mail_room_yml[:mailboxes]).to be_an(Array) + expect(mail_room_yml[:mailboxes].length).to eq(2) + + expect(mail_room_yml[:mailboxes][0]).to match( + a_hash_including( + password: 'abc123', + email: 'incoming-test-account@gitlab.com' + ) + ) + + expect(mail_room_yml[:mailboxes][1]).to match( + a_hash_including( + password: '123abc', + email: 'service-desk-test-account@gitlab.example.com' + ) + ) + end + end + end end diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/incoming_email_encrypted_secrets_enabled_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/incoming_email_encrypted_secrets_enabled_metric_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..ed35b2c8cdea05349c58dddc9974a828a4342b90 --- /dev/null +++ b/spec/lib/gitlab/usage/metrics/instrumentations/incoming_email_encrypted_secrets_enabled_metric_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Usage::Metrics::Instrumentations::IncomingEmailEncryptedSecretsEnabledMetric, +feature_category: :service_ping do + it_behaves_like 'a correct instrumented metric value', { time_frame: 'none', data_source: 'ruby' } do + let(:expected_value) { ::Gitlab::IncomingEmail.encrypted_secrets.active? } + end +end diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/service_desk_email_encrypted_secrets_enabled_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/service_desk_email_encrypted_secrets_enabled_metric_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..d602eae3159de318a619b149428fb0f82fc54ee6 --- /dev/null +++ b/spec/lib/gitlab/usage/metrics/instrumentations/service_desk_email_encrypted_secrets_enabled_metric_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Usage::Metrics::Instrumentations::ServiceDeskEmailEncryptedSecretsEnabledMetric, +feature_category: :service_ping do + it_behaves_like 'a correct instrumented metric value', { time_frame: 'none', data_source: 'ruby' } do + let(:expected_value) { ::Gitlab::ServiceDeskEmail.encrypted_secrets.active? } + end +end diff --git a/spec/tasks/gitlab/incoming_email_rake_spec.rb b/spec/tasks/gitlab/incoming_email_rake_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..3e1cc663ddbd021b0b26ebaaace789b7c6068a1d --- /dev/null +++ b/spec/tasks/gitlab/incoming_email_rake_spec.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +require 'rake_helper' + +RSpec.describe 'gitlab:incoming_email:secret rake tasks', :silence_stdout, feature_category: :build do + let(:encrypted_secret_file_dir) { Pathname.new(Dir.mktmpdir) } + let(:encrypted_secret_file) { encrypted_secret_file_dir.join('incoming_email.yaml.enc') } + + before do + Rake.application.rake_require 'tasks/gitlab/incoming_email' + stub_env('EDITOR', 'cat') + stub_warn_user_is_not_gitlab + allow(Gitlab.config.incoming_email).to receive(:encrypted_secret_file).and_return(encrypted_secret_file) + allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64)) + end + + after do + FileUtils.rm_rf(Rails.root.join('tmp/tests/incoming_email_enc')) + end + + describe ':show' do + it 'displays error when file does not exist' do + expect { run_rake_task('gitlab:incoming_email:secret:show') }.to \ + output(/File .* does not exist. Use `gitlab-rake gitlab:incoming_email:secret:edit` to change that./).to_stdout + end + + it 'displays error when key does not exist' do + Settings.encrypted(encrypted_secret_file).write('somevalue') + allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil) + expect { run_rake_task('gitlab:incoming_email:secret:show') }.to \ + output(/Missing encryption key encrypted_settings_key_base./).to_stderr + end + + it 'displays error when key is changed' do + Settings.encrypted(encrypted_secret_file).write('somevalue') + allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64)) + expect { run_rake_task('gitlab:incoming_email:secret:show') }.to \ + output(/Couldn't decrypt .* Perhaps you passed the wrong key?/).to_stderr + end + + it 'outputs the unencrypted content when present' do + encrypted = Settings.encrypted(encrypted_secret_file) + encrypted.write('somevalue') + expect { run_rake_task('gitlab:incoming_email:secret:show') }.to output(/somevalue/).to_stdout + end + end + + describe 'edit' do + it 'creates encrypted file' do + expect { run_rake_task('gitlab:incoming_email:secret:edit') }.to output(/File encrypted and saved./).to_stdout + expect(File.exist?(encrypted_secret_file)).to be true + value = Settings.encrypted(encrypted_secret_file) + expect(value.read).to match(/password: '123'/) + end + + it 'displays error when key does not exist' do + allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil) + expect { run_rake_task('gitlab:incoming_email:secret:edit') }.to \ + output(/Missing encryption key encrypted_settings_key_base./).to_stderr + end + + it 'displays error when key is changed' do + Settings.encrypted(encrypted_secret_file).write('somevalue') + allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64)) + expect { run_rake_task('gitlab:incoming_email:secret:edit') }.to \ + output(/Couldn't decrypt .* Perhaps you passed the wrong key?/).to_stderr + end + + it 'displays error when write directory does not exist' do + FileUtils.rm_rf(encrypted_secret_file_dir) + expect { run_rake_task('gitlab:incoming_email:secret:edit') }.to \ + output(/Directory .* does not exist./).to_stderr + end + + it 'shows a warning when content is invalid' do + Settings.encrypted(encrypted_secret_file).write('somevalue') + expect { run_rake_task('gitlab:incoming_email:secret:edit') }.to \ + output(/WARNING: Content was not a valid INCOMING_EMAIL secret yml file/).to_stdout + value = Settings.encrypted(encrypted_secret_file) + expect(value.read).to match(/somevalue/) + end + + it 'displays error when $EDITOR is not set' do + stub_env('EDITOR', nil) + expect { run_rake_task('gitlab:incoming_email:secret:edit') }.to \ + output(/No \$EDITOR specified to open file. Please provide one when running the command/).to_stderr + end + end + + describe 'write' do + before do + allow($stdin).to receive(:tty?).and_return(false) + allow($stdin).to receive(:read).and_return('username: foo') + end + + it 'creates encrypted file from stdin' do + expect { run_rake_task('gitlab:incoming_email:secret:write') }.to output(/File encrypted and saved./).to_stdout + expect(File.exist?(encrypted_secret_file)).to be true + value = Settings.encrypted(encrypted_secret_file) + expect(value.read).to match(/username: foo/) + end + + it 'displays error when key does not exist' do + allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil) + expect { run_rake_task('gitlab:incoming_email:secret:write') }.to \ + output(/Missing encryption key encrypted_settings_key_base./).to_stderr + end + + it 'displays error when write directory does not exist' do + FileUtils.rm_rf(encrypted_secret_file_dir) + expect { run_rake_task('gitlab:incoming_email:secret:write') }.to output(/Directory .* does not exist./).to_stderr + end + + it 'shows a warning when content is invalid' do + Settings.encrypted(encrypted_secret_file).write('somevalue') + expect { run_rake_task('gitlab:incoming_email:secret:edit') }.to \ + output(/WARNING: Content was not a valid INCOMING_EMAIL secret yml file/).to_stdout + value = Settings.encrypted(encrypted_secret_file) + expect(value.read).to match(/somevalue/) + end + end +end diff --git a/spec/tasks/gitlab/service_desk_email_rake_spec.rb b/spec/tasks/gitlab/service_desk_email_rake_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..6a1a7473f4a27299b3fe57c0e3ec36373fd2c09d --- /dev/null +++ b/spec/tasks/gitlab/service_desk_email_rake_spec.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +require 'rake_helper' + +RSpec.describe 'gitlab:service_desk_email:secret rake tasks', :silence_stdout, feature_category: :build do + let(:encrypted_secret_file_dir) { Pathname.new(Dir.mktmpdir) } + let(:encrypted_secret_file) { encrypted_secret_file_dir.join('service_desk_email.yaml.enc') } + + before do + Rake.application.rake_require 'tasks/gitlab/service_desk_email' + stub_env('EDITOR', 'cat') + stub_warn_user_is_not_gitlab + FileUtils.mkdir_p('tmp/tests/service_desk_email_enc/') + allow(Gitlab.config.service_desk_email).to receive(:encrypted_secret_file).and_return(encrypted_secret_file) + allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64)) + end + + after do + FileUtils.rm_rf(Rails.root.join('tmp/tests/service_desk_email_enc')) + end + + describe ':show' do + it 'displays error when file does not exist' do + expect { run_rake_task('gitlab:service_desk_email:secret:show') }.to \ + output(/File .* does not exist. Use `gitlab-rake gitlab:service_desk_email:secret:edit` to change that./) \ + .to_stdout + end + + it 'displays error when key does not exist' do + Settings.encrypted(encrypted_secret_file).write('somevalue') + allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil) + expect { run_rake_task('gitlab:service_desk_email:secret:show') }.to \ + output(/Missing encryption key encrypted_settings_key_base./).to_stderr + end + + it 'displays error when key is changed' do + Settings.encrypted(encrypted_secret_file).write('somevalue') + allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64)) + expect { run_rake_task('gitlab:service_desk_email:secret:show') }.to \ + output(/Couldn't decrypt .* Perhaps you passed the wrong key?/).to_stderr + end + + it 'outputs the unencrypted content when present' do + encrypted = Settings.encrypted(encrypted_secret_file) + encrypted.write('somevalue') + expect { run_rake_task('gitlab:service_desk_email:secret:show') }.to output(/somevalue/).to_stdout + end + end + + describe 'edit' do + it 'creates encrypted file' do + expect { run_rake_task('gitlab:service_desk_email:secret:edit') }.to \ + output(/File encrypted and saved./).to_stdout + expect(File.exist?(encrypted_secret_file)).to be true + value = Settings.encrypted(encrypted_secret_file) + expect(value.read).to match(/password: '123'/) + end + + it 'displays error when key does not exist' do + allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil) + expect { run_rake_task('gitlab:service_desk_email:secret:edit') }.to \ + output(/Missing encryption key encrypted_settings_key_base./).to_stderr + end + + it 'displays error when key is changed' do + Settings.encrypted(encrypted_secret_file).write('somevalue') + allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(SecureRandom.hex(64)) + expect { run_rake_task('gitlab:service_desk_email:secret:edit') }.to \ + output(/Couldn't decrypt .* Perhaps you passed the wrong key?/).to_stderr + end + + it 'displays error when write directory does not exist' do + FileUtils.rm_rf(encrypted_secret_file_dir) + expect { run_rake_task('gitlab:service_desk_email:secret:edit') }.to \ + output(/Directory .* does not exist./).to_stderr + end + + it 'shows a warning when content is invalid' do + Settings.encrypted(encrypted_secret_file).write('somevalue') + expect { run_rake_task('gitlab:service_desk_email:secret:edit') }.to \ + output(/WARNING: Content was not a valid SERVICE_DESK_EMAIL secret yml file/).to_stdout + value = Settings.encrypted(encrypted_secret_file) + expect(value.read).to match(/somevalue/) + end + + it 'displays error when $EDITOR is not set' do + stub_env('EDITOR', nil) + expect { run_rake_task('gitlab:service_desk_email:secret:edit') }.to \ + output(/No \$EDITOR specified to open file. Please provide one when running the command/).to_stderr + end + end + + describe 'write' do + before do + allow($stdin).to receive(:tty?).and_return(false) + allow($stdin).to receive(:read).and_return('username: foo') + end + + it 'creates encrypted file from stdin' do + expect { run_rake_task('gitlab:service_desk_email:secret:write') }.to \ + output(/File encrypted and saved./).to_stdout + expect(File.exist?(encrypted_secret_file)).to be true + value = Settings.encrypted(encrypted_secret_file) + expect(value.read).to match(/username: foo/) + end + + it 'displays error when key does not exist' do + allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil) + expect { run_rake_task('gitlab:service_desk_email:secret:write') }.to \ + output(/Missing encryption key encrypted_settings_key_base./).to_stderr + end + + it 'displays error when write directory does not exist' do + FileUtils.rm_rf(encrypted_secret_file_dir) + expect { run_rake_task('gitlab:service_desk_email:secret:write') }.to \ + output(/Directory .* does not exist./).to_stderr + end + + it 'shows a warning when content is invalid' do + Settings.encrypted(encrypted_secret_file).write('somevalue') + expect { run_rake_task('gitlab:service_desk_email:secret:edit') }.to \ + output(/WARNING: Content was not a valid SERVICE_DESK_EMAIL secret yml file/).to_stdout + value = Settings.encrypted(encrypted_secret_file) + expect(value.read).to match(/somevalue/) + end + end +end