diff --git a/saas/app/controllers/saas_admin_controller.rb b/saas/app/controllers/saas_admin_controller.rb new file mode 100644 index 000000000..f68ca492e --- /dev/null +++ b/saas/app/controllers/saas_admin_controller.rb @@ -0,0 +1,6 @@ +class SaasAdminController < ::AdminController + private + def find_current_auditor + Current.identity + end +end diff --git a/saas/app/models/saas_record.rb b/saas/app/models/saas_record.rb new file mode 100644 index 000000000..37a1d8980 --- /dev/null +++ b/saas/app/models/saas_record.rb @@ -0,0 +1,5 @@ +class SaasRecord < ActiveRecord::Base + self.abstract_class = true + + connects_to database: { writing: :saas, reading: :saas } +end diff --git a/saas/config/database.yml b/saas/config/database.yml index 3ef8f4b53..41be3a0e2 100644 --- a/saas/config/database.yml +++ b/saas/config/database.yml @@ -11,6 +11,8 @@ mysql_app_user = ENV[mysql_app_user_key] mysql_app_password = ENV[mysql_app_password_key] + + gem_path = Gem::Specification.find_by_name("fizzy-saas").gem_dir %> default: &default @@ -48,6 +50,12 @@ development: database: development_queue port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> migrations_paths: db/queue_migrate + saas: + <<: *default + database: fizzy_saas_development + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + migrations_paths: <%= File.join(gem_path, "db", "migrate") %> + schema_dump: <%= File.join(gem_path, "db", "saas_schema.rb") %> # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". @@ -62,6 +70,12 @@ test: database: fizzy_test port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> replica: true + saas: + <<: *default + database: fizzy_saas_test + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + migrations_paths: <%= File.join(gem_path, "db", "migrate") %> + schema_dump: <%= File.join(gem_path, "db", "saas_schema.rb") %> production: &production primary: @@ -98,6 +112,14 @@ production: &production username: <%= mysql_app_user %> password: <%= mysql_app_password %> migrations_paths: db/cache_migrate + saas: + <<: *default + database: fizzy_saas_production + host: <%= ENV["MYSQL_DATABASE_HOST"] %> + username: <%= mysql_app_user %> + password: <%= mysql_app_password %> + migrations_paths: <%= File.join(gem_path, "db", "migrate") %> + schema_dump: <%= File.join(gem_path, "db", "saas_schema.rb") %> beta: *production staging: *production diff --git a/saas/config/routes.rb b/saas/config/routes.rb index cb130af4f..7c7d4f6be 100644 --- a/saas/config/routes.rb +++ b/saas/config/routes.rb @@ -1,3 +1,7 @@ Fizzy::Saas::Engine.routes.draw do Queenbee.routes(self) + + namespace :admin do + mount Audits1984::Engine, at: "/console" + end end diff --git a/saas/db/migrate/20251202200249_create_console1984_tables.console1984.rb b/saas/db/migrate/20251202200249_create_console1984_tables.console1984.rb new file mode 100644 index 000000000..d03cc6248 --- /dev/null +++ b/saas/db/migrate/20251202200249_create_console1984_tables.console1984.rb @@ -0,0 +1,36 @@ +# This migration comes from console1984 (originally 20210517203931) +class CreateConsole1984Tables < ActiveRecord::Migration[7.0] + def change + create_table :console1984_sessions do |t| + t.text :reason + t.references :user, null: false, index: false + t.timestamps + + t.index :created_at + t.index [ :user_id, :created_at ] + end + + create_table :console1984_users do |t| + t.string :username, null: false + t.timestamps + + t.index [:username] + end + + create_table :console1984_commands do |t| + t.text :statements + t.references :sensitive_access + t.references :session, null: false, index: false + t.timestamps + + t.index [ :session_id, :created_at, :sensitive_access_id ], name: "on_session_and_sensitive_chronologically" + end + + create_table :console1984_sensitive_accesses do |t| + t.text :justification + t.references :session, null: false + + t.timestamps + end + end +end diff --git a/saas/db/migrate/20251202205753_create_auditing_tables.audits1984.rb b/saas/db/migrate/20251202205753_create_auditing_tables.audits1984.rb new file mode 100644 index 000000000..431da136b --- /dev/null +++ b/saas/db/migrate/20251202205753_create_auditing_tables.audits1984.rb @@ -0,0 +1,13 @@ +# This migration comes from audits1984 (originally 20210810092639) +class CreateAuditingTables < ActiveRecord::Migration[7.0] + def change + create_table :audits1984_audits do |t| + t.integer :status, default: 0, null: false + t.text :notes + t.references :session, null: false + t.uuid :auditor_id, null: false + + t.timestamps + end + end +end diff --git a/saas/db/saas_schema.rb b/saas/db/saas_schema.rb new file mode 100644 index 000000000..19d468927 --- /dev/null +++ b/saas/db/saas_schema.rb @@ -0,0 +1,57 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.2].define(version: 2025_12_02_205753) do + create_table "audits1984_audits", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.uuid "auditor_id", null: false + t.datetime "created_at", null: false + t.text "notes" + t.bigint "session_id", null: false + t.integer "status", default: 0, null: false + t.datetime "updated_at", null: false + t.index ["session_id"], name: "index_audits1984_audits_on_session_id" + end + + create_table "console1984_commands", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.datetime "created_at", null: false + t.bigint "sensitive_access_id" + t.bigint "session_id", null: false + t.text "statements" + t.datetime "updated_at", null: false + t.index ["sensitive_access_id"], name: "index_console1984_commands_on_sensitive_access_id" + t.index ["session_id", "created_at", "sensitive_access_id"], name: "on_session_and_sensitive_chronologically" + end + + create_table "console1984_sensitive_accesses", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.datetime "created_at", null: false + t.text "justification" + t.bigint "session_id", null: false + t.datetime "updated_at", null: false + t.index ["session_id"], name: "index_console1984_sensitive_accesses_on_session_id" + end + + create_table "console1984_sessions", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.datetime "created_at", null: false + t.text "reason" + t.datetime "updated_at", null: false + t.bigint "user_id", null: false + t.index ["created_at"], name: "index_console1984_sessions_on_created_at" + t.index ["user_id", "created_at"], name: "index_console1984_sessions_on_user_id_and_created_at" + end + + create_table "console1984_users", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "username", null: false + t.index ["username"], name: "index_console1984_users_on_username" + end +end diff --git a/saas/fizzy-saas.gemspec b/saas/fizzy-saas.gemspec index 7f993fbfd..64f253588 100644 --- a/saas/fizzy-saas.gemspec +++ b/saas/fizzy-saas.gemspec @@ -35,4 +35,6 @@ Gem::Specification.new do |spec| spec.add_dependency "yabeda-puma-plugin" spec.add_dependency "yabeda-rails", ">= 0.10" spec.add_dependency "prometheus-client-mmap", "~> 1.4.0" + spec.add_dependency "console1984" + spec.add_dependency "audits1984" end diff --git a/saas/lib/fizzy/saas/engine.rb b/saas/lib/fizzy/saas/engine.rb index 878bf7a3f..ede0cbfcf 100644 --- a/saas/lib/fizzy/saas/engine.rb +++ b/saas/lib/fizzy/saas/engine.rb @@ -1,5 +1,6 @@ require_relative "transaction_pinning" require_relative "signup" +require_relative "../../rails_ext/active_record_tasks_database_tasks.rb" module Fizzy module Saas @@ -41,7 +42,7 @@ module Fizzy # Load test mocks automatically in test environment initializer "fizzy_saas.test_mocks", after: :load_config_initializers do if Rails.env.test? - require "fizzy/saas/testing" + require_relative "testing" end end @@ -79,6 +80,22 @@ module Fizzy require_relative "metrics" end + config.before_initialize do + config.console1984.protected_environments = %i[ production beta staging ] + config.console1984.ask_for_username_if_empty = true + config.console1984.base_record_class = "::SaasRecord" + + config.audits1984.base_controller_class = "::SaasAdminController" + config.audits1984.auditor_class = "::Identity" + config.audits1984.auditor_name_attribute = :email_address + + if config.console1984.protected_environments.include?(Rails.env.to_sym) + config.active_record.encryption.primary_key = ENV.fetch("ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY") + config.active_record.encryption.deterministic_key = ENV.fetch("ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY") + config.active_record.encryption.key_derivation_salt = ENV.fetch("ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT") + end + end + config.to_prepare do ::Signup.prepend(Fizzy::Saas::Signup) diff --git a/saas/lib/rails_ext/active_record_tasks_database_tasks.rb b/saas/lib/rails_ext/active_record_tasks_database_tasks.rb new file mode 100644 index 000000000..bafa27464 --- /dev/null +++ b/saas/lib/rails_ext/active_record_tasks_database_tasks.rb @@ -0,0 +1,23 @@ +module ActiveRecordTasksDatabaseTasksExtension + extend ActiveSupport::Concern + + class_methods do + # proposed upstream in https://github.com/rails/rails/pull/56290 + def schema_dump_path(db_config, format = db_config.schema_format) + return ENV["SCHEMA"] if ENV["SCHEMA"] + + filename = db_config.schema_dump(format) + return unless filename + + if Pathname.new(filename).absolute? + filename + else + super + end + end + end +end + +ActiveSupport.on_load(:active_record) do + ActiveRecord::Tasks::DatabaseTasks.include(ActiveRecordTasksDatabaseTasksExtension) +end