require "test_helper" class GcpSecretManagerAdapterTest < SecretAdapterTestCase test "fetch" do stub_gcloud_version stub_authenticated stub_mypassword json = JSON.parse(shellunescape(run_command("fetch", "mypassword"))) expected_json = { "default/mypassword"=>"secret123" } assert_equal expected_json, json end test "fetch unauthenticated" do stub_ticks.with("gcloud --version 2> /dev/null") stub_mypassword stub_unauthenticated error = assert_raises RuntimeError do JSON.parse(shellunescape(run_command("fetch", "mypassword"))) end assert_match(/could not login to gcloud/, error.message) end test "fetch with from" do stub_gcloud_version stub_authenticated stub_items(0, project: "other-project") stub_items(1, project: "other-project") stub_items(2, project: "other-project") json = JSON.parse(shellunescape(run_command("fetch", "--from", "other-project", "item1", "item2", "item3"))) expected_json = { "other-project/item1"=>"secret1", "other-project/item2"=>"secret2", "other-project/item3"=>"secret3" } assert_equal expected_json, json end test "fetch with multiple projects" do stub_gcloud_version stub_authenticated stub_items(0, project: "some-project") stub_items(1, project: "project-confidence") stub_items(2, project: "manhattan-project") json = JSON.parse(shellunescape(run_command("fetch", "some-project/item1", "project-confidence/item2", "manhattan-project/item3"))) expected_json = { "some-project/item1"=>"secret1", "project-confidence/item2"=>"secret2", "manhattan-project/item3"=>"secret3" } assert_equal expected_json, json end test "fetch with specific version" do stub_gcloud_version stub_authenticated stub_items(0, project: "some-project", version: "123") json = JSON.parse(shellunescape(run_command("fetch", "some-project/item1/123"))) expected_json = { "some-project/item1"=>"secret1" } assert_equal expected_json, json end test "fetch with non-default account" do stub_gcloud_version stub_authenticated stub_items(0, project: "some-project", version: "123", account: "email@example.com") json = JSON.parse(shellunescape(run_command("fetch", "some-project/item1/123", account: "email@example.com"))) expected_json = { "some-project/item1"=>"secret1" } assert_equal expected_json, json end test "fetch with service account impersonation" do stub_gcloud_version stub_authenticated stub_items(0, project: "some-project", version: "123", impersonate_service_account: "service-user@example.com") json = JSON.parse(shellunescape(run_command("fetch", "some-project/item1/123", account: "default|service-user@example.com"))) expected_json = { "some-project/item1"=>"secret1" } assert_equal expected_json, json end test "fetch with delegation chain and specific user" do stub_gcloud_version stub_authenticated stub_items(0, project: "some-project", version: "123", account: "user@example.com", impersonate_service_account: "service-user@example.com,service-user2@example.com") json = JSON.parse(shellunescape(run_command("fetch", "some-project/item1/123", account: "user@example.com|service-user@example.com,service-user2@example.com"))) expected_json = { "some-project/item1"=>"secret1" } assert_equal expected_json, json end test "fetch with non-default account and service account impersonation" do stub_gcloud_version stub_authenticated stub_items(0, project: "some-project", version: "123", account: "email@example.com", impersonate_service_account: "service-user@example.com") json = JSON.parse(shellunescape(run_command("fetch", "some-project/item1/123", account: "email@example.com|service-user@example.com"))) expected_json = { "some-project/item1"=>"secret1" } assert_equal expected_json, json end test "fetch without CLI installed" do stub_gcloud_version(succeed: false) error = assert_raises RuntimeError do JSON.parse(shellunescape(run_command("fetch", "item1"))) end assert_equal "gcloud CLI is not installed", error.message end private def run_command(*command, account: "default") stdouted do Kamal::Cli::Secrets.start \ [ *command, "-c", "test/fixtures/deploy_with_accessories.yml", "--adapter", "gcp_secret_manager", "--account", account ] end end def stub_gcloud_version(succeed: true) stub_ticks_with("gcloud --version 2> /dev/null", succeed: succeed) end def stub_authenticated stub_ticks .with("gcloud auth list --format=json") .returns(<<~JSON) [ { "account": "email@example.com", "status": "ACTIVE" } ] JSON end def stub_unauthenticated stub_ticks .with("gcloud auth list --format=json") .returns("[]") stub_ticks .with("gcloud auth login") .returns(<<~JSON) { "expired": false, "valid": true } JSON end def stub_mypassword stub_ticks .with("gcloud secrets versions access latest --secret=mypassword --format=json") .returns(<<~JSON) { "name": "projects/000000000/secrets/mypassword/versions/1", "payload": { "data": "c2VjcmV0MTIz", "dataCrc32c": "2522602764" } } JSON end def stub_items(n, project: nil, account: nil, version: "latest", impersonate_service_account: nil) payloads = [ { data: "c2VjcmV0MQ==", checksum: 1846998209 }, { data: "c2VjcmV0Mg==", checksum: 2101741365 }, { data: "c2VjcmV0Mw==", checksum: 2402124854 } ] stub_ticks .with("gcloud secrets versions access #{version} " \ "--secret=item#{n + 1}" \ "#{" --project=#{project}" if project}" \ "#{" --account=#{account}" if account}" \ "#{" --impersonate-service-account=#{impersonate_service_account}" if impersonate_service_account} " \ "--format=json") .returns(<<~JSON) { "name": "projects/000000001/secrets/item1/versions/1", "payload": { "data": "#{payloads[n][:data]}", "dataCrc32c": "#{payloads[n][:checksum]}" } } JSON end end