Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"Keboola\\StorageDriver\\": "packages/php-storage-driver-common/generated/Keboola/StorageDriver",
"Keboola\\StorageDriver\\Contract\\": "packages/php-storage-driver-common/contract/",
"Keboola\\StorageDriver\\Shared\\": "packages/php-storage-driver-common/Shared/",
"Keboola\\StorageDriver\\Snowflake\\": "packages/php-storage-driver-snowflake/src/",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

protoze to tady chybelo z initu repa

"Keboola\\TableBackendUtils\\": "packages/php-table-backend-utils/src/"
}
},
Expand All @@ -66,7 +67,8 @@
"Tests\\Keboola\\Db\\ImportExportCommon\\": "packages/php-db-import-export/tests/Common",
"Tests\\Keboola\\Db\\ImportExportFunctional\\": "packages/php-db-import-export/tests/functional/",
"Tests\\Keboola\\Db\\ImportExportUnit\\": "packages/php-db-import-export/tests/unit",
"Tests\\Keboola\\TableBackendUtils\\": "packages/php-table-backend-utils/tests"
"Tests\\Keboola\\TableBackendUtils\\": "packages/php-table-backend-utils/tests",
"Keboola\\StorageDriver\\Snowflake\\Tests\\Functional\\": "packages/php-storage-driver-snowflake/tests/Functional"
}
},
"replace": {
Expand Down
15 changes: 13 additions & 2 deletions packages/php-storage-driver-snowflake/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ Keboola high level storage backend driver for Snowflake.
### Snowflake

Prepare credentials for Snowflake access
Create RSA key pair for Snowflake user, you can use the following command to generate it:

```bash
openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out rsa_key.p8 -nocrypt
openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub
```

Then you can use the public key in the Snowflake user creation script below.

```snowflake
CREATE ROLE "KEBOOLA_CI_PHP_STORAGE_DRIVER_SNOWFLAKE";
Expand All @@ -19,8 +27,10 @@ GRANT ALL PRIVILEGES ON DATABASE "KEBOOLA_CI_PHP_STORAGE_DRIVER_SNOWFLAKE" TO RO
GRANT USAGE ON WAREHOUSE "DEV" TO ROLE "KEBOOLA_CI_PHP_STORAGE_DRIVER_SNOWFLAKE";

CREATE USER "KEBOOLA_CI_PHP_STORAGE_DRIVER_SNOWFLAKE"
PASSWORD = 'ewC@B3.6UyWVLxe*MZMdN7xYEnX6ZV_P'
DEFAULT_ROLE = "KEBOOLA_CI_PHP_STORAGE_DRIVER_SNOWFLAKE";
PASSWORD = ''
Copy link
Contributor Author

Choose a reason for hiding this comment

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

nechavam tady password a neresim TYPE, protoze to se odstrani az s https://github.com/keboola/storage-backend/pull/212/files

DEFAULT_ROLE = "KEBOOLA_CI_PHP_STORAGE_DRIVER_SNOWFLAKE"
RSA_PUBLIC_KEY = '<your_public_key>'
;

GRANT ROLE "KEBOOLA_CI_PHP_STORAGE_DRIVER_SNOWFLAKE" TO USER "KEBOOLA_CI_PHP_STORAGE_DRIVER_SNOWFLAKE";
```
Expand All @@ -32,6 +42,7 @@ SNOWFLAKE_HOST: keboolaconnectiondev.us-east-1.snowflakecomputing.com
SNOWFLAKE_PORT: 443
SNOWFLAKE_USER: KEBOOLA_CI_PHP_STORAGE_DRIVER_SNOWFLAKE
SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }}
SNOWFLAKE_PRIVATE_KEY: ${{ secrets.SNOWFLAKE_PRIVATE_KEY }} # note: it has to be full private key in PEM format, including the header and footer
SNOWFLAKE_DATABASE: KEBOOLA_CI_PHP_STORAGE_DRIVER_SNOWFLAKE
SNOWFLAKE_WAREHOUSE: DEV
```
48 changes: 43 additions & 5 deletions packages/php-storage-driver-snowflake/src/ConnectionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,30 @@

final class ConnectionFactory
{
/**
* Check if a string is a valid RSA private key
*/
private static function isValidRsaPrivateKey(string $key): bool
{
// Remove any whitespace and check if it looks like a PEM encoded key
$key = trim($key);
if (!str_contains($key, '-----BEGIN') || !str_contains($key, 'PRIVATE KEY-----')) {
return false;
}

// Try to get the private key details
$privateKey = openssl_pkey_get_private($key);
if ($privateKey === false) {
return false;
}

// Get the details to verify it's an RSA key
$details = openssl_pkey_get_details($privateKey);

// Check if it's an RSA key
return $details !== false && isset($details['key']) && $details['type'] === OPENSSL_KEYTYPE_RSA;
}

public static function createFromCredentials(GenericBackendCredentials $credentials): Connection
{
$meta = $credentials->getMeta();
Expand All @@ -22,15 +46,29 @@ public static function createFromCredentials(GenericBackendCredentials $credenti
throw new Exception('SnowflakeCredentialsMeta is required.');
}

// Check if the secret is a valid RSA private key
$isRsaKey = self::isValidRsaPrivateKey($credentials->getSecret());

$connectionParams = [
'port' => (string) $credentials->getPort(),
'warehouse' => $meta->getWarehouse(),
'database' => $meta->getDatabase(),
];

if ($isRsaKey) {
return SnowflakeConnectionFactory::getConnectionWithCert(
$credentials->getHost(),
$credentials->getPrincipal(),
$credentials->getSecret(),
$connectionParams,
);
}

return SnowflakeConnectionFactory::getConnection(
$credentials->getHost(),
$credentials->getPrincipal(),
$credentials->getSecret(),
[
'port' => (string) $credentials->getPort(),
'warehouse' => $meta->getWarehouse(),
'database' => $meta->getDatabase(),
],
$connectionParams,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ abstract class BaseCase extends TestCase

protected function getSnowflakeConnection(): Connection
{
$this->connection = SnowflakeConnectionFactory::getConnection(
$this->connection = SnowflakeConnectionFactory::getConnectionWithCert(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

defaultne se k tomu uz chovam jako ke keypairu. BC pro heslo je tady jen docasna, opet odstranena pak v #212

(string) getenv('SNOWFLAKE_HOST'),
(string) getenv('SNOWFLAKE_USER'),
(string) getenv('SNOWFLAKE_PASSWORD'),
(string) getenv('SNOWFLAKE_PRIVATE_KEY'),
[
'port' => (string) getenv('SNOWFLAKE_PORT'),
'warehouse' => (string) getenv('SNOWFLAKE_WAREHOUSE'),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace Keboola\StorageDriver\Snowflake\Tests\Functional;

use Google\Protobuf\Any;
use Keboola\StorageDriver\Credentials\GenericBackendCredentials;
use Keboola\StorageDriver\Credentials\GenericBackendCredentials\SnowflakeCredentialsMeta;
use Keboola\StorageDriver\Snowflake\ConnectionFactory;
use PHPUnit\Framework\TestCase;

class ConnectionFactoryTest extends TestCase
{
public function testCreateFromCredentialsWithPassword(): void
{
// Create credentials with a password
$credentials = new GenericBackendCredentials();
$credentials->setHost((string) getenv('SNOWFLAKE_HOST'));
$credentials->setPrincipal((string) getenv('SNOWFLAKE_USER'));
$credentials->setSecret((string) getenv('SNOWFLAKE_PASSWORD'));
$credentials->setPort((int) getenv('SNOWFLAKE_PORT'));

$meta = new Any();
$meta->pack(
(new SnowflakeCredentialsMeta())
->setWarehouse((string) getenv('SNOWFLAKE_WAREHOUSE'))
->setDatabase((string) getenv('SNOWFLAKE_DATABASE')),
);
$credentials->setMeta($meta);

// Create connection
$connection = ConnectionFactory::createFromCredentials($credentials);

// Test connection works
$result = $connection->executeQuery('SELECT 1 as TEST');
$this->assertEquals(1, $result->fetchOne());
}

public function testCreateFromCredentialsWithPrivateKey(): void
{
// Create credentials with a key
$credentials = new GenericBackendCredentials();
$credentials->setHost((string) getenv('SNOWFLAKE_HOST'));
$credentials->setPrincipal((string) getenv('SNOWFLAKE_USER'));
$credentials->setSecret((string) getenv('SNOWFLAKE_PRIVATE_KEY'));
$credentials->setPort((int) getenv('SNOWFLAKE_PORT'));

$meta = new Any();
$meta->pack(
(new SnowflakeCredentialsMeta())
->setWarehouse((string) getenv('SNOWFLAKE_WAREHOUSE'))
->setDatabase((string) getenv('SNOWFLAKE_DATABASE')),
);
$credentials->setMeta($meta);

// Create connection
$connection = ConnectionFactory::createFromCredentials($credentials);

// Test connection works
$result = $connection->executeQuery('SELECT 1 as TEST');
$this->assertEquals(1, $result->fetchOne());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Keboola\StorageDriver\Snowflake\Tests\Functional;

use Doctrine\DBAL\Connection;
use Keboola\TableBackendUtils\Connection\Snowflake\SnowflakeConnectionFactory;

class ConnectionTestWithPassword extends BaseCase
{
/**
* @doesNotPerformAssertions
*/
public function testQuery(): void
{
$connection = $this->getSnowflakeConnection();
$connection->executeQuery('SELECT 1');
}

protected function getSnowflakeConnection(): Connection
{
$this->connection = SnowflakeConnectionFactory::getConnection(
(string) getenv('SNOWFLAKE_HOST'),
(string) getenv('SNOWFLAKE_USER'),
(string) getenv('SNOWFLAKE_PASSWORD'),
[
'port' => (string) getenv('SNOWFLAKE_PORT'),
'warehouse' => (string) getenv('SNOWFLAKE_WAREHOUSE'),
'database' => (string) getenv('SNOWFLAKE_DATABASE'),
],
);

return $this->connection;
}
}