snowflake-connector-python
snowflake-connector-python copied to clipboard
SNOW-676645: Add connector support for keypair authentication using hardware security modules or cloud key managers
What is the current behavior?
Current Snowflake keypair authentication requires that the user has the private key in their possession as a file. However, Hardware Security Modules (e.g. Smartcards, Yubikeys, …) and Cloud Key Managers do not expose the private key to the user. Instead, they provide cryptographic operations as API functionality. I can, for example, ask the HSM to sign a certain string using the private key.
What is the desired behavior?
I would like to be able to use any cryptographic device or service capable of RSA-signatures for keypair authentication. More specifically, I want an interface against which I can implement my custom signing flow.
How would this improve snowflake-connector-python?
It would provide more options for secure logins. In our case, we would use Azure Key Vault to store keys and allow services with Managed Identity to read those keys. This lets our service accounts log in without exposing passwords or private keys at any phase.
References, Other Background
As a proof of concept, I have implemented a version of keypair authentication that abstracts signing and public key operations into a separate class. An instance of this class can be provided to the Snowflake connector to sign the token.
Here's an example. I will provide a full pull request soon.
class AzureKeyVaultManager(KeyManager):
def __init__(self, kvclient, keyname):
self._kvclient = kvclient
self._keyname = keyname
def public_key(self):
pubkey = self._kvclient.get_key(self._keyname)
e = int.from_bytes(pubkey.key.e, 'big')
n = int.from_bytes(pubkey.key.n, 'big')
rsakey = RSAPublicNumbers(e, n)
return rsakey.public_key()
def sign(self, message):
cc = self._kvclient.get_cryptography_client(self._keyname)
hash = sha256(message).digest()
result = cc.sign(SignatureAlgorithm.rs256, hash)
return result.signature
ctx = snowflake.connector.connect(
user='[email protected]',
key_manager=AzureKeyVaultManager(kc, 'testkey'),
authenticator='KMS',
account='foo.west-europe.azure',
warehouse='compute_wh',
database='misc',
)
Please note that this approach would not add new dependencies into the connector. The user of the connector library is responsible for providing the class, customized to their particular usecase.