Repository: dbt-labs/dbt-core
Pull request: removing the requirement to have a passphrase on a Snowflake key
resolves #1805 


### Description

This PR removes the requirement to have a passphrase when authenticating with a Snowflake key pair. Snowflake key pairs without passphrases are supported by Snowflake, but are not currently supported by dbt.


### Checklist
 - [x] I have signed the [CLA](https://docs.getdbt.com/docs/contributor-license-agreements)
 - [x] I have run this code in development and it appears to resolve the stated issue
 - [x] This PR includes tests, or tests are not required/relevant for this PR
 - [x] I have updated the `CHANGELOG.md` and added information about my change to the "dbt next" section.


Problem statement:
Allow no-passphrase private keys
### Describe the bug
The Snowflake connector (at least) requires a passphrase in the profile file to open a private key connection.

### Steps To Reproduce
Create a dbt target like the following:
```
    qa:
      type: snowflake
      account: my_account
      user: my_user
      role: ANALYST

      # Keypair config
      private_key_path: "path/to/my/no/passphrase/key"
      private_key_passphrase: None

      database: DB
      warehouse: WH
      schema: PUBLIC
      threads: 1
      client_session_keep_alive: False
```
Attempt to run against said DBT target. DBT will fail because no passphrase is provided. If, instead, a passphrase is provided, the connection will fail because the key is not encrypted.

### Expected behavior
Perhaps a warning in output that unencrypted keys are not the norm, requiring additional setting of override field in profile. If that's set, go ahead with the unencrypted key.

### System information
**Which database are you using dbt with?**
- [ ] postgres
- [ ] redshift
- [ ] bigquery
- [ x] snowflake
- [ ] other (specify: ____________)


**The output of `dbt --version`:**
```
0.14.2
```

**The operating system you're using:**
OSX
**The output of `python --version`:**
Python 3.7.3

Hints from issue discussion:
I know how to just make it work within the Snowflake connection, but don't know how to do more involved things like requiring a flag in the project or profile file. 
Thanks for the suggestion @kmbenitez! The operative code for private key Snowflake connections is around here:
https://github.com/fishtown-analytics/dbt/blob/31e085b7df015eac92faee4912fa481d1085c4b9/plugins/snowflake/dbt/adapters/snowflake/connections.py#L53-L65

If you have an unencrypted .pem file, would we skip the `serialization.load_pem_private_key` step and instead just supply raw bytes from the .pem file to `p_key.private_bytes()`?

If that's the case, I think we can remove the guard ` or self.private_key_passphrase is None` and instead do something like:
```
if  self.private_key_passphrase is None:
  logger.warn("The specified private key is not encrypted; this is not recommended")
  p_key = ... code to get raw bytes from the file....
else:
  p_key = serialization.load_pem_private_key( ... )
```

I don't think we'd need extra flags in the project or profile file in this case.

Commit message: updating changelog

Patch to review:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ef7ba64c17b..2577891fead 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
 - Add a dbt-{dbt_version} user agent field to the bigquery connector ([#2121](https://github.com/fishtown-analytics/dbt/issues/2121), [#2146](https://github.com/fishtown-analytics/dbt/pull/2146))
 - Add support for `generate_database_name` macro ([#1695](https://github.com/fishtown-analytics/dbt/issues/1695), [#2143](https://github.com/fishtown-analytics/dbt/pull/2143))
 - Expand the search path for schema.yml (and by extension, the default docs path) to include macro-paths and analysis-paths (in addition to source-paths, data-paths, and snapshot-paths) ([#2155](https://github.com/fishtown-analytics/dbt/issues/2155), [#2160](https://github.com/fishtown-analytics/dbt/pull/2160))
+- Remove the requirement to have a passphrase when using Snowflake key pair authentication ([#2164](https://github.com/fishtown-analytics/dbt/pull/2164)
 
 ### Fixes
 - Fix issue where dbt did not give an error in the presence of duplicate doc names ([#2054](https://github.com/fishtown-analytics/dbt/issues/2054), [#2080](https://github.com/fishtown-analytics/dbt/pull/2080))
diff --git a/plugins/snowflake/dbt/adapters/snowflake/connections.py b/plugins/snowflake/dbt/adapters/snowflake/connections.py
index 826dfb154ce..148be2ac091 100644
--- a/plugins/snowflake/dbt/adapters/snowflake/connections.py
+++ b/plugins/snowflake/dbt/adapters/snowflake/connections.py
@@ -129,13 +129,18 @@ def _get_access_token(self) -> str:
 
     def _get_private_key(self):
         """Get Snowflake private key by path or None."""
-        if not self.private_key_path or self.private_key_passphrase is None:
+        if not self.private_key_path:
             return None
 
+        if self.private_key_passphrase:
+            encoded_passphrase = self.private_key_passphrase.encode()
+        else:
+            encoded_passphrase = None
+
         with open(self.private_key_path, 'rb') as key:
             p_key = serialization.load_pem_private_key(
                 key.read(),
-                password=self.private_key_passphrase.encode(),
+                password=encoded_passphrase,
                 backend=default_backend())
 
         return p_key.private_bytes(
diff --git a/test/unit/test_snowflake_adapter.py b/test/unit/test_snowflake_adapter.py
index 19acc0a98d8..9f9e3e4ae23 100644
--- a/test/unit/test_snowflake_adapter.py
+++ b/test/unit/test_snowflake_adapter.py
@@ -355,3 +355,24 @@ def test_authenticator_private_key_authentication(self, mock_get_private_key):
                 warehouse='test_warehouse', private_key='test_key',
                 application='dbt')
         ])
+
+    @mock.patch('dbt.adapters.snowflake.SnowflakeCredentials._get_private_key', return_value='test_key')
+    def test_authenticator_private_key_authentication_no_passphrase(self, mock_get_private_key):
+        self.config.credentials = self.config.credentials.replace(
+            private_key_path='/tmp/test_key.p8',
+            private_key_passphrase=None,
+        )
+
+        self.adapter = SnowflakeAdapter(self.config)
+        conn = self.adapter.connections.set_connection_name(name='new_connection_with_new_config')
+
+        self.snowflake.assert_not_called()
+        conn.handle
+        self.snowflake.assert_has_calls([
+            mock.call(
+                account='test_account', autocommit=False,
+                client_session_keep_alive=False, database='test_database',
+                role=None, schema='public', user='test_user',
+                warehouse='test_warehouse', private_key='test_key',
+                application='dbt')
+        ])


Code review comments:
diff_hunk: @@ -9,6 +9,7 @@
 - Add a dbt-{dbt_version} user agent field to the bigquery connector ([#2121](https://github.com/fishtown-analytics/dbt/issues/2121), [#2146](https://github.com/fishtown-analytics/dbt/pull/2146))
 - Add support for `generate_database_name` macro ([#1695](https://github.com/fishtown-analytics/dbt/issues/1695), [#2143](https://github.com/fishtown-analytics/dbt/pull/2143))
 - Expand the search path for schema.yml (and by extension, the default docs path) to include macro-paths and analysis-paths (in addition to source-paths, data-paths, and snapshot-paths) ([#2155](https://github.com/fishtown-analytics/dbt/issues/2155), [#2160](https://github.com/fishtown-analytics/dbt/pull/2160))
+- Remove the requirement to have a passphrase when using Snowflake key pair authentication ([#2164](https://github.com/fishtown-analytics/dbt/pull/2164)
path: CHANGELOG.md
text: @user1:
```suggestion
- Remove the requirement to have a passphrase when using Snowflake key pair authentication ([#1804](https://github.com/fishtown-analytics/dbt/issues/1805), [#2164](https://github.com/fishtown-analytics/dbt/pull/2164))
```