Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync DO API keys between clouds #16205

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
60fb07f
Sync onprem DO API keys with CARTO clouds
amiedes Mar 2, 2021
5cea5f1
Destroy & regenerate remote DO API keys
amiedes Mar 2, 2021
382fb30
Test hack
amiedes Mar 5, 2021
c0768cd
Remove uneeded attribute
amiedes Mar 5, 2021
273d240
Avoid name collisions in factories
amiedes Mar 5, 2021
6e9777f
Avoid more name collisions
amiedes Mar 5, 2021
040ca63
Try better visibility
amiedes Mar 5, 2021
56098cc
Fix user password in factory
amiedes Mar 8, 2021
6a60e94
Use FactoryGirl sequences for generating unique usernames
amiedes Mar 8, 2021
9ed36fc
Remove accidentally commited code
amiedes Mar 8, 2021
eec8ce1
Try Faker again
amiedes Mar 8, 2021
13a9f39
Carto::RemoteDoApiKey specs
amiedes Mar 8, 2021
c0e5c3e
Fix some specs
amiedes Mar 8, 2021
88575d0
Remove confusing abstraction
amiedes Mar 9, 2021
4feee1a
2 failures remaining
amiedes Mar 9, 2021
88b0a70
Fix spec
amiedes Mar 9, 2021
0090942
Command specs
amiedes Mar 9, 2021
b144e76
WIP
amiedes Mar 9, 2021
4f3da6a
Don't use orgs helper
amiedes Mar 9, 2021
103f8e5
Fix ldap config issues
amiedes Mar 9, 2021
60e3683
Fix specs
amiedes Mar 9, 2021
2051e3e
Clean specs
amiedes Mar 9, 2021
62bdb3a
Try to fix spec again
amiedes Mar 9, 2021
0e2796b
Fix specs
amiedes Mar 9, 2021
305de8e
Merge branch 'master' into feature/ch132964/sync-api-keys-both-in-on-…
amiedes Mar 9, 2021
6cd437d
Update NEWS.md
amiedes Mar 9, 2021
c57531c
Clarifying comment
amiedes Mar 9, 2021
f428951
Remove accidentally commited changes
amiedes Mar 9, 2021
29df9dc
Merge branch 'master' into feature/ch132964/sync-api-keys-both-in-on-…
amiedes Mar 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ sudo make install
- Cleanup after [#16189](https://github.com/CartoDB/cartodb/pull/16189). See [#16200](https://github.com/CartoDB/cartodb/pull/16200)
- Split configuration for Message Broker & Central login redirection [#16150](https://github.com/CartoDB/cartodb/pull/16150)
- Remove Data Library gallery page (now redirected to Spatial Data Catalog) [#16133](https://github.com/CartoDB/cartodb/pull/16133)
- Sync DO API keys betwenn onpremises & CARTO-managed clouds [#16205](https://github.com/CartoDB/cartodb/pull/16205)

### Bug fixes / enhancements

Expand Down
12 changes: 12 additions & 0 deletions app/commands/remote_do_api_key_commands/create.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module RemoteDoApiKeyCommands
class Create < ::CartoCommand

private

def run_command
api_key = Carto::RemoteDoApiKey.new(params)
api_key.save!
end

end
end
12 changes: 12 additions & 0 deletions app/commands/remote_do_api_key_commands/destroy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module RemoteDoApiKeyCommands
class Destroy < ::CartoCommand

private

def run_command
api_key = Carto::RemoteDoApiKey.new(params)
api_key.destroy!
end

end
end
11 changes: 11 additions & 0 deletions app/helpers/message_broker_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module MessageBrokerHelper

def message_broker
Carto::Common::MessageBroker.new(logger: Rails.logger)
end

def cartodb_central_topic
message_broker.get_topic(:cartodb_central)
end

end
38 changes: 36 additions & 2 deletions app/models/carto/api_key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ module Carto
class ApiKey < ActiveRecord::Base

include Carto::AuthTokenGenerator
include ::MessageBrokerHelper

REDIS_KEY_PREFIX = 'api_keys:'.freeze

TYPE_REGULAR = 'regular'.freeze
TYPE_MASTER = 'master'.freeze
Expand Down Expand Up @@ -65,15 +68,19 @@ class ApiKey < ActiveRecord::Base
validate :valid_default_public_key, if: :default_public?

after_create :setup_db_role, if: ->(k) { k.needs_setup? && !k.skip_role_setup }
after_create :create_remote_do_api_key, if: ->(api_key) { api_key.master? }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just an opinion: I like more the syntax we use in Central

after_create(:create_remote_do_api_key, if: :master?)

(I understand you do need a block or a new function predicate for the check below (regenerate callback))
(as an opinion and likes and dislikes you're entitled to completely ignore it)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion 👍 . I agree with you and will try to remember this for the next time, but for (lazyness? 😅 ) I'm going to deploy it like it's now to avoid having to wait for the build again and potentially having to do a retry


after_save { remove_from_redis(redis_key(token_was)) if token_changed? }
after_save { invalidate_cache if token_changed? }
after_save :add_to_redis, if: :valid_user?
after_save :save_cdb_conf_info, unless: :skip_cdb_conf_info?
after_save :regenerate_remote_do_api_key, if: ->(k) { k.master? && k.token_changed? }

after_destroy :reassign_owner, :drop_db_role, if: ->(k) { k.needs_setup? && !k.skip_role_setup }
after_destroy :remove_from_redis
after_destroy :invalidate_cache
after_destroy :remove_cdb_conf_info, unless: :skip_cdb_conf_info?
after_destroy :destroy_remote_do_api_key, if: ->(api_key) { api_key.master? }

scope :master, -> { where(type: TYPE_MASTER) }
scope :default_public, -> { where(type: TYPE_DEFAULT_PUBLIC) }
Expand Down Expand Up @@ -150,6 +157,35 @@ def self.new_from_hash(api_key_hash)
)
end

def create_remote_do_api_key
cartodb_central_topic.publish(
:create_remote_do_api_key,
{ type: type, token: token, user_id: user_id, username: user.username }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to myself: sending both user_id and username, important to relay remote creation messages

)
end

def regenerate_remote_do_api_key
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, but we need a specific message here: there's no guarantee on the order of the messages, and the likelihood of the "create" to overtake the "destroy" is pretty high in this case.

I think it is safer to have a specific message/command :regenerate_remote_do_api_key almost for the same price.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After our chat and for the record: it is OK as worst case scenario is having an old api key in redis for a short while, as old and new have different redis keys.

OTOH that triggered the need for this: https://github.com/CartoDB/data-observatory/pull/1010

original_token, saved_token = token_change

# Order of execution is not guaranteed, but since the token is different there's no danger
# of race conditions
cartodb_central_topic.publish(
:destroy_remote_do_api_key,
{ token: original_token, user_id: user_id, username: user.username }
)
cartodb_central_topic.publish(
:create_remote_do_api_key,
{ type: type, token: saved_token, user_id: user_id, username: user.username }
)
end

def destroy_remote_do_api_key
cartodb_central_topic.publish(
:destroy_remote_do_api_key,
{ token: token, user_id: user_id, username: user.username }
)
end

def revoke_permissions(table, revoked_permissions)
Carto::TableAndFriends.apply(db_connection, table.database_schema, table.name) do |s, t, qualified_name|
query = %{
Expand Down Expand Up @@ -379,8 +415,6 @@ def setup_table_permissions

PASSWORD_LENGTH = 40

REDIS_KEY_PREFIX = 'api_keys:'.freeze

def raise_unprocessable_entity_error(error)
raise Carto::UnprocesableEntityError.new(/PG::Error: ERROR: (.+)/ =~ error.message && $1 || 'Unexpected error')
end
Expand Down
34 changes: 34 additions & 0 deletions app/models/carto/remote_do_api_key.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module Carto
class RemoteDoApiKey

attr_accessor :token, :type, :username
attr_reader :redis_client

def initialize(attributes = {})
attributes = attributes.with_indifferent_access
@token = attributes[:token]
@username = attributes[:username]
@type = attributes[:type]
@redis_client = $users_metadata
end

def save!
redis_client.hmset(redis_key, redis_hash_as_array)
end

def destroy!
redis_client.del(redis_key)
end

private

def redis_key
"#{Carto::ApiKey::REDIS_KEY_PREFIX}#{username}:#{token}"
end

def redis_hash_as_array
['user', username, 'type', type]
end

end
end
12 changes: 6 additions & 6 deletions app/models/concerns/cartodb_central_synchronizable.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module CartodbCentralSynchronizable

include ::MessageBrokerHelper

def user?
is_a?(::User) || is_a?(Carto::User)
end
Expand Down Expand Up @@ -59,12 +61,10 @@ def update_in_central
cartodb_central_client.update_user(username, allowed_attributes_to_central(:update))
end
elsif organization?
Carto::Common::MessageBroker.new(logger: Rails.logger)
.get_topic(:cartodb_central)
.publish(
:update_organization,
{ organization: allowed_attributes_to_central(:update) }
)
cartodb_central_topic.publish(
:update_organization,
{ organization: allowed_attributes_to_central(:update) }
)
end

true
Expand Down
8 changes: 3 additions & 5 deletions lib/cartodb/central.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
require_relative '../carto/http/client'
require './app/helpers/message_broker_helper'

module Cartodb
class Central

include ::MessageBrokerHelper

def self.message_broker_sync_enabled?
Cartodb.get_config(:message_broker, 'project_id').present? &&
Cartodb.get_config(:message_broker, 'central_subscription_name').present?
Expand Down Expand Up @@ -199,10 +202,5 @@ def delete_oauth_app(username, app_id)
send_request("api/users/#{username}/oauth_apps/#{app_id}", nil, :delete, [204])
end

private

def cartodb_central_topic
Carto::Common::MessageBroker.new(logger: Rails.logger).get_topic(:cartodb_central)
end
end
end
14 changes: 14 additions & 0 deletions lib/tasks/central_updates_subscriber.rake
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,20 @@ namespace :message_broker do
).run
end

subscription.register_callback(:create_do_api_key) do |message|
RemoteDoApiKeyCommands::Create.new(
message.payload,
{ logger: logger, request_id: message.request_id }
).run
end

subscription.register_callback(:destroy_do_api_key) do |message|
RemoteDoApiKeyCommands::Destroy.new(
message.payload,
{ logger: logger, request_id: message.request_id }
).run
end

subscription.register_callback(:set_do_gcloud_settings) do |message|
GcloudUserSettingsCommands::Set.new(
message.payload,
Expand Down
4 changes: 2 additions & 2 deletions script/ci/cloudbuild-tests-pr-pg12.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ steps:
- -cx
- |
docker pull gcr.io/cartodb-on-gcp-main-artifacts/builder:${_BRANCH_TAG}
if [ $? -ne 0 ]
if [ $? -ne 0 ]
then
docker pull gcr.io/cartodb-on-gcp-main-artifacts/builder:latest
docker tag gcr.io/cartodb-on-gcp-main-artifacts/builder:latest gcr.io/cartodb-on-gcp-main-artifacts/builder:${_BRANCH_TAG}
Expand Down Expand Up @@ -133,7 +133,7 @@ steps:
fi


substitutions:
substitutions:
_BRANCH_TAG: ${BRANCH_NAME//\//-}
options:
machineType: 'N1_HIGHCPU_32'
Expand Down
18 changes: 18 additions & 0 deletions spec/commands/remote_do_api_key_commands/create_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require 'spec_helper'

describe RemoteDoApiKeyCommands::Create do
let(:command) { described_class.new(params) }
let(:params) do
{
token: '1234-abcd-5678',
username: 'perico',
type: Carto::ApiKey::TYPE_MASTER
}
end

describe '#run' do
it 'runs OK' do
command.run
end
end
end
11 changes: 11 additions & 0 deletions spec/commands/remote_do_api_key_commands/destroy_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require 'spec_helper'

describe RemoteDoApiKeyCommands::Destroy do
let(:command) { described_class.new(token: '1234-abcd-5678', username: 'perico') }

describe '#run' do
it 'runs OK' do
command.run
end
end
end
16 changes: 8 additions & 8 deletions spec/factories/users.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
factory :user, class: ::User do
to_create(&:save)

username { unique_name('user') }
email { unique_email }
password { email.split('@').first }
password_confirmation { email.split('@').first }
sequence(:username) { |i| "#{Faker::Internet.username(separators: [])}#{i}" }
email { "#{username}@example.com" }
password { "#{username}123" }
password_confirmation { password }
table_quota 5
quota_in_bytes 5000000
id { Carto::UUIDHelper.random_uuid }
Expand Down Expand Up @@ -78,8 +78,8 @@
end

factory :carto_user, class: Carto::User do
username { unique_name('user') }
email { unique_email }
username { Faker::Internet.username(separators: ['-']) }
email { Faker::Internet.safe_email }

password { email.split('@').first }
password_confirmation { email.split('@').first }
Expand Down Expand Up @@ -131,8 +131,8 @@

# Light user: database creation etc is skipped
factory :carto_user_light, class: Carto::User do
username { unique_name('user') }
email { unique_email }
username { Faker::Internet.username(separators: ['-']) }
email { Faker::Internet.safe_email }

password { email.split('@').first }
password_confirmation { email.split('@').first }
Expand Down
40 changes: 40 additions & 0 deletions spec/models/carto/remote_do_api_key_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require 'spec_helper_min'

describe Carto::RemoteDoApiKey do
let(:username) { 'random-user' }
let(:type) { Carto::ApiKey::TYPE_MASTER }
let(:token) { '1234-abcd-5678' }
let(:redis_key) { 'api_keys:random-user:1234-abcd-5678' }
let(:api_key) { described_class.new(token: token, username: username, type: type) }

after { clean_redis_databases }

describe '#initialize' do
it 'correctly initializes the record' do
expect(api_key.token).to eq(token)
expect(api_key.username).to eq(username)
expect(api_key.type).to eq(type)
expect(api_key.redis_client).to be_present
end
end

describe '#save!' do
it 'saves the API key to redis' do
api_key.save!

expect($users_metadata.hgetall(redis_key)).to(
eq('user' => 'random-user', 'type' => 'master')
)
end
end

describe '#destroy!' do
before { api_key.save! }

it 'destroys the API key from redis' do
api_key.destroy!

expect($users_metadata.hgetall(redis_key)).to eq({})
end
end
end
Loading