diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2569155..73c0378 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -123,7 +123,7 @@ jobs: services: mysql: - image: mysql:5.7 + image: mysql:8.0 env: MYSQL_DATABASE: ${{ env.MYSQL_DATABASE }} MYSQL_ROOT_PASSWORD: ${{ env.MYSQL_PASSWORD }} @@ -145,18 +145,8 @@ jobs: run: | bundle exec rake download - - name: Initialize RDBMS metastore - run: | - mysql -h ${{ env.MYSQL_HOSTNAME }} -P${{ job.services.mysql.ports[3306] }} -u ${{ env.MYSQL_USERNAME }} -p${{ env.MYSQL_PASSWORD }} -e "CREATE TABLE ${{ env.MYSQL_DATABASE }}.encryption_key ( - id VARCHAR(255) NOT NULL, - created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - key_record TEXT NOT NULL, - PRIMARY KEY (id, created), - INDEX (created) - );" - - name: Set up Go - uses: actions/setup-go@v6.2.0 + uses: actions/setup-go@v6.3.0 with: go-version: 1.24 diff --git a/.rubocop.yml b/.rubocop.yml index d8f5abc..30de9ab 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -95,6 +95,9 @@ Style/Documentation: Style/DocumentationMethod: Enabled: false # YARD comments are optional +Style/EmptyClassDefinition: + Enabled: false + # Additional cops for code quality Lint/UnusedMethodArgument: Enabled: true diff --git a/CHANGELOG.md b/CHANGELOG.md index e9990be..d7144b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## [Unreleased] +## [0.8.0] - 2026-03-04 + +- Upgrade to use asherah-cobhan v0.5.0 +- Expose disable_zero_copy config option to disable zero-copy FFI input buffers + ## [0.7.0] - 2025-08-15 - Fix memory leak risks in buffer management diff --git a/README.md b/README.md index 029991c..757edfb 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,22 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run For tests requiring secrets (AWS KMS, database credentials), copy `.env.secrets.example` to `.env.secrets` and fill in the required values. The `.env.secrets` file is already in `.gitignore` to prevent accidental commits. +### Cross-Language Tests + +Cross-language tests verify that data encrypted with the Go implementation can be decrypted with the Ruby implementation and vice versa. + +**Prerequisites:** +- MySQL running locally +- Go 1.24+ installed + +**Running the tests:** + +```bash +TEST_DB_PASSWORD=pass bin/cross-language-test.sh +``` + +See `bin/cross-language-test.sh` for available environment variables and their defaults. + To install this gem onto your local machine, run `rake install`. To release a new version, update the version number in `version.rb`, create and push a version tag: diff --git a/bin/cross-language-test.sh b/bin/cross-language-test.sh index 8d60f96..bb4d43c 100755 --- a/bin/cross-language-test.sh +++ b/bin/cross-language-test.sh @@ -6,6 +6,43 @@ ROOT_DIR=$(pwd) ASHERAH_GO_DIR=$(pwd)/tmp/asherah ASHERAH_GO_TEST_DIR=$(pwd)/tmp/asherah/tests/cross-language/go +# Set database environment variables +export TEST_DB_NAME=${TEST_DB_NAME:-testdb} +export TEST_DB_USER=${TEST_DB_USER:-root} +export TEST_DB_PASSWORD=${TEST_DB_PASSWORD:-} +export TEST_DB_HOSTNAME=${TEST_DB_HOSTNAME:-localhost} +export TEST_DB_PORT=${TEST_DB_PORT:-3306} + +# Set Asherah environment variables +export ASHERAH_SERVICE_NAME=${ASHERAH_SERVICE_NAME:-service} +export ASHERAH_PRODUCT_NAME=${ASHERAH_PRODUCT_NAME:-product} +export ASHERAH_KMS_MODE=${ASHERAH_KMS_MODE:-static} + +# Initialize database and table +echo "Initializing database..." +MYSQL_CMD="mysql -h $TEST_DB_HOSTNAME -P$TEST_DB_PORT -u $TEST_DB_USER" +if [ -n "$TEST_DB_PASSWORD" ]; then + MYSQL_CMD="$MYSQL_CMD -p$TEST_DB_PASSWORD" +fi + +# Create database if it doesn't exist +$MYSQL_CMD -e "CREATE DATABASE IF NOT EXISTS $TEST_DB_NAME;" 2>/dev/null || { + echo "Warning: Could not create database. It may already exist or you may not have permissions." +} + +# Create encryption_key table if it doesn't exist +$MYSQL_CMD $TEST_DB_NAME <<'SQL' 2>/dev/null || echo "Warning: Could not create table. It may already exist or you may not have permissions." +CREATE TABLE IF NOT EXISTS encryption_key ( + id VARCHAR(255) NOT NULL, + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + key_record TEXT NOT NULL, + PRIMARY KEY (id, created), + INDEX (created) +); +SQL + +echo "Database initialization complete." + # Clean tmp dir rm -rf $ASHERAH_GO_DIR @@ -19,11 +56,10 @@ cd $ASHERAH_GO_TEST_DIR go build ./... go mod edit -replace github.com/godaddy/asherah/go/appencryption=../../../go/appencryption go mod tidy -go install github.com/cucumber/godog/cmd/godog@latest # Encrypt with Go cd $ASHERAH_GO_TEST_DIR -godog run "$ROOT_DIR/features/encrypt.feature" +go run github.com/cucumber/godog/cmd/godog@latest run "$ROOT_DIR/features/encrypt.feature" # Encrypt with Ruby cd $ROOT_DIR @@ -35,4 +71,4 @@ bundle exec cucumber "$ROOT_DIR/features/decrypt.feature" # Decrypt all with Go cd $ASHERAH_GO_TEST_DIR -godog run "$ROOT_DIR/features/decrypt.feature" +go run github.com/cucumber/godog/cmd/godog@latest run "$ROOT_DIR/features/decrypt.feature" diff --git a/ext/asherah/checksums.yml b/ext/asherah/checksums.yml index bef7a89..6fe4edb 100644 --- a/ext/asherah/checksums.yml +++ b/ext/asherah/checksums.yml @@ -1,5 +1,5 @@ -version: v0.4.35 -libasherah-arm64.so: fad23a38e68e126374075adf197f0f431720aea9852deebe5f62d9240c935a66 -libasherah-x64.so: 8c52fc000df2c02fb2d1430afc3cd68e997f47f04b60d61481f8c4b201958ef8 -libasherah-arm64.dylib: 315bc41c85177a2b0c97f32e0af8e2694f393928678cbe648fdd8c16b8fe062a -libasherah-x64.dylib: 848d3635713373e0482223087f454ff8464e36e7695e0ed19f830737288adaa9 +version: v0.5.0 +libasherah-arm64.so: 8271298c357808d7e6daa4ca81ded8f39c1947a55043abe3b32359e0f5840a6c +libasherah-x64.so: 645c0da7d1330db511c6724f08154cfae3959610bd709d60eded1c1420d2fce8 +libasherah-arm64.dylib: 909097bf62207e6927a0184e41859ccf42a62afd711cdadf69b8c5672939468b +libasherah-x64.dylib: e53ee66b7dd16ce587d5062e9eed8835f272653b6a91b4b5c5c1efd2ca97483e diff --git a/features/support/env.rb b/features/support/env.rb index 5736a14..38a7cf6 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -10,7 +10,7 @@ DB_USER = ENV.fetch('TEST_DB_USER') DB_PASS = ENV.fetch('TEST_DB_PASSWORD') DB_PORT = ENV.fetch('TEST_DB_PORT') -DB_HOST = 'localhost' +DB_HOST = ENV.fetch('TEST_DB_HOSTNAME', 'localhost') CONNECTION_STRING = "#{DB_USER}:#{DB_PASS}@tcp(#{DB_HOST}:#{DB_PORT})/#{DB_NAME}?tls=skip-verify" TMP_DIR = '/tmp/' FILE_NAME = 'ruby_encrypted' diff --git a/lib/asherah/config.rb b/lib/asherah/config.rb index 6e586a4..54168bb 100644 --- a/lib/asherah/config.rb +++ b/lib/asherah/config.rb @@ -21,6 +21,7 @@ module Asherah # @attr [Integer] expire_after, The amount of time in seconds a key is considered valid # @attr [Integer] check_interval, The amount of time in seconds before cached keys are considered stale # @attr [Boolean] enable_session_caching, Enable shared session caching + # @attr [Boolean] disable_zero_copy, Disable zero-copy FFI input buffers to prevent use-after-free from caller runtime # @attr [Boolean] verbose, Enable verbose logging output class Config MAPPING = { @@ -40,6 +41,7 @@ class Config session_cache_max_size: :SessionCacheMaxSize, session_cache_duration: :SessionCacheDuration, enable_session_caching: :EnableSessionCaching, + disable_zero_copy: :DisableZeroCopy, expire_after: :ExpireAfter, check_interval: :CheckInterval, verbose: :Verbose diff --git a/lib/asherah/version.rb b/lib/asherah/version.rb index e7685c6..a9c8bff 100644 --- a/lib/asherah/version.rb +++ b/lib/asherah/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Asherah - VERSION = '0.7.0' + VERSION = '0.8.0' end diff --git a/spec/config_spec.rb b/spec/config_spec.rb index 20a9015..6434256 100644 --- a/spec/config_spec.rb +++ b/spec/config_spec.rb @@ -163,4 +163,94 @@ end end end + + describe '#disable_zero_copy' do + it 'accepts disable_zero_copy as true' do + expect { + Asherah.configure do |config| + base_config.call(config) + config.disable_zero_copy = true + end + }.not_to raise_error + Asherah.shutdown + end + + it 'accepts disable_zero_copy as false' do + expect { + Asherah.configure do |config| + base_config.call(config) + config.disable_zero_copy = false + end + }.not_to raise_error + Asherah.shutdown + end + end + + describe '#to_json' do + it 'correctly maps all configuration options to Go JSON format' do + config = Asherah::Config.new + config.service_name = 'test-service' + config.product_id = 'test-product' + config.kms = 'aws' + config.metastore = 'dynamodb' + config.connection_string = 'mysql://localhost:3306/test' + config.replica_read_consistency = 'eventual' + config.sql_metastore_db_type = 'postgres' + config.dynamo_db_endpoint = 'http://localhost:8000' + config.dynamo_db_region = 'us-west-2' + config.dynamo_db_table_name = 'test-table' + config.enable_region_suffix = true + config.region_map = { 'us-west-2' => 'arn' } + config.preferred_region = 'us-west-2' + config.session_cache_max_size = 500 + config.session_cache_duration = 3600 + config.enable_session_caching = true + config.disable_zero_copy = true + config.expire_after = 7200 + config.check_interval = 1800 + config.verbose = true + + json_output = JSON.parse(config.to_json) + + expect(json_output).to eq( + 'ServiceName' => 'test-service', + 'ProductID' => 'test-product', + 'KMS' => 'aws', + 'Metastore' => 'dynamodb', + 'ConnectionString' => 'mysql://localhost:3306/test', + 'ReplicaReadConsistency' => 'eventual', + 'SQLMetastoreDBType' => 'postgres', + 'DynamoDBEndpoint' => 'http://localhost:8000', + 'DynamoDBRegion' => 'us-west-2', + 'DynamoDBTableName' => 'test-table', + 'EnableRegionSuffix' => true, + 'RegionMap' => { 'us-west-2' => 'arn' }, + 'PreferredRegion' => 'us-west-2', + 'SessionCacheMaxSize' => 500, + 'SessionCacheDuration' => 3600, + 'EnableSessionCaching' => true, + 'DisableZeroCopy' => true, + 'ExpireAfter' => 7200, + 'CheckInterval' => 1800, + 'Verbose' => true + ) + end + + it 'excludes nil values from JSON output' do + config = Asherah::Config.new + config.service_name = 'test-service' + config.product_id = 'test-product' + config.kms = 'test-debug-static' + config.metastore = 'test-debug-memory' + + json_output = JSON.parse(config.to_json) + + expect(json_output).to eq( + 'ServiceName' => 'test-service', + 'ProductID' => 'test-product', + 'KMS' => 'test-debug-static', + 'Metastore' => 'test-debug-memory' + ) + end + end end