Repository: dbt-labs/dbt-core

Issue:
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



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.

Resolving patch:
diff --git a/plugins/snowflake/dbt/adapters/snowflake/connections.py b/plugins/snowflake/dbt/adapters/snowflake/connections.py
--- 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(


Test patch:
diff --git a/test/unit/test_snowflake_adapter.py b/test/unit/test_snowflake_adapter.py
--- 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')
+        ])