-
Notifications
You must be signed in to change notification settings - Fork 167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SNOW-618478: Unable to authenticate with a private key by using spring datasource properties #1053
Comments
Hi @ZigZag59, I was able to reproduce this also using Hikari. The only workaround right now is to set your private key file and private key passphrase in your URL, for example: |
Hi @sfc-gh-wfateem my customer is also trying to create a Hikari connection pool that uses a private key instead of password. IS this the only workaround code snippet we can point them to ? |
we can create any other datasource using privatekey and set it as a datasource in Hikari. |
This should be addressed by Hikari now in PR 1895. |
which hikari cp version has this change ? And spring boot version. |
@NitinSharma1991 I'm reopening this issue because it looks like I erroneously perceived that changes were made in that PR, but I see now that it was closed and the Hikari team commented that they're not going to make any changes. We'll need to take a closer look. |
We currently use HikariCP + Snowflake JDBC in our project and faced this issue while attempting to inject Code Snippet: import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openssl.PEMParser
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
import java.io.StringReader
import java.security.{PrivateKey, Security}
import java.sql.{Connection, DriverManager, ResultSet, Statement}
import java.util.Properties
object TestJdbc {
// Private key as a string
private val PRIVATE_KEY_STRING: String =
"""-----BEGIN PRIVATE KEY-----
|-----END PRIVATE KEY-----
|""".stripMargin
private object PrivateKeyReader {
def getFromString(privateKeyString: String): PrivateKey = {
Security.addProvider(new BouncyCastleProvider())
val pemParser = new PEMParser(new StringReader(privateKeyString))
val pemObject = pemParser.readObject.asInstanceOf[PrivateKeyInfo]
pemParser.close()
val converter = new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME)
converter.getPrivateKey(pemObject)
}
}
def main(args: Array[String]): Unit = {
val url = "jdbc:snowflake://account-prod.snowflakecomputing.com"
val prop = new Properties()
prop.put("user", "service_user")
prop.put("privateKey", PrivateKeyReader.getFromString(PRIVATE_KEY_STRING))
prop.put("db", "SNOWFLAKE_SAMPLE_DATA")
prop.put("schema", "TPCH_SF1")
prop.put("warehouse", "COMPUTE_WH")
prop.put("role", "READ_ROLE")
val conn: Connection = DriverManager.getConnection(url, prop)
val stat: Statement = conn.createStatement()
val res: ResultSet = stat.executeQuery("select 1")
if (res.next()) {
println(res.getString(1))
}
conn.close()
}
} |
Thanks for the comment. Yes, you're right. The Map should have stored <String, String> key, value pairs. However, historically, the JDBC code used the Properties HashMap to store objects, and it requires a broader cleanup. I'm assuming you're using an encrypted key and you're just not sharing the full code used to decrypt the private key. If the original reason of using this Bouncy Castle code was because of similar problems as described in #1683 then JDBC driver version 3.16.0 introduces a JVM argument you can enable Are any of these options possible for you to try? |
@sfc-gh-wfateem I just opened a PR to resolve this issue. As further justification of the need, I'd add my blocking concern: I want to leverage this driver through Spark.read in a cluster. In order to use private_key_file, I'd have to make the file available to every executor in the cluster. If I instead use private_key_base64, I can read the bytes from disk once and allow Spark to pass the base64 encoded String to all the executors transparently. Happy to make adjustments to the PR as necessary, I'd really like to get this mainlined asap and I'm certain all the Hikari users represented here would appreciate it as well. |
+1 |
Hi @ets, That makes total sense. Thanks for the contribution! |
+1 |
3 similar comments
+1 |
+1 |
+1 |
@ets I just want to make sure I understand the expectations on how to use that new
The file
So a user should pass this to the JDBC driver:
Is my understanding correct? |
@sfc-gh-wfateem negative. The value of PRIVATE_KEY_BASE64 is just the base64 encoded byte content of the entire file. For example: We only use Base64 encoding because we can't pass around the byte[] for our purposes...we need a string. If you take a look at SessionUtilKeyPair in the PR you'll see two methods that illustrate this further: The former reads the bytes from the PRIVATE_KEY_FILE file and the latter Base64.decodes the bytes from the PRIVATE_KEY_BASE64 string ... after that they both call extractPrivateKeyFromBytes |
For reference and my own understanding:
|
Setting a DataSource like that should work too, but there's an easier approach. With the PR in place, you can just pass the new private_key_base64 property into the config using addDataSourceProperty
|
Hi @sfc-gh-wfateem , I'm using the v3.19.0 driver, and I cannot get this to work at all. I've gone through this thread and I'm also using the Hikari connection pool, and I've tried the private_key_base64 and it gives me this error:
If I decrypt the file with OpenSSL, and try the base64 without the corresponding property : private_key_pwd it has an error but still connects ( I assume it figures out that it's not encrypted and uses the file contents):
I also tried creating my own decryption method using the examples, and when I decrypt the original .p8 file, it says the algorithm is : 1.2.840.113549.1.1.1 and not 1.2.840.113549.1.5.13. I was getting this error in my decryption method but I overcame it like this: PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new PKCS8EncryptedPrivateKeyInfo ( pemObject.getContent ( ) );
String passphrase = pass; // Getting from dependency injection
JceOpenSSLPKCS8DecryptorProviderBuilder builder = new JceOpenSSLPKCS8DecryptorProviderBuilder ( );
builder.setProvider ( new BouncyCastleProvider ( ) );
InputDecryptorProvider pkcs8Prov = builder.build ( passphrase.toCharArray ( ) );
privateKeyInfo = encryptedPrivateKeyInfo.decryptPrivateKeyInfo ( pkcs8Prov ); I got the same error but adding this fixed it for me: builder.setProvider ( new BouncyCastleProvider ( ) ); But it looks like the decryption that the driver is doing may not be doing this step? I used BouncyCastle v1.78, which worked--is there a way to tell the driver to use the provided BouncyCastle instead of the |
@jase52476 I don't believe your issue is related to the new If you're still having issues with this then I would recommend you open a separate issue, so we can unpack what it is that's special about your scenario and see if it's reproducible on our end. |
@sfc-gh-wfateem I'm following the instructions provided for using key pair authentication and it's not working if we use the encrypted key file and passphrase: props.put("private_key_file", "/tmp/rsa_key.p8"); This is the driver/version I'm using. <dependency>
<groupId>net.snowflake</groupId>
<artifactId>snowflake-jdbc</artifactId>
<version>3.19.0</version>
</dependency> I finally got it to work. I'm using SpringBoot and HikariCP for the connection. It looks like my main issue was that I was using the HikariConfig.setJdbcUrl and HikariConfig.setDriverClassName. When I do this, I am unable to do the addDataSourceProperty ( "privateKey", pk ), because it came back with the issue of the non-String property issue that's mentioned earlier in this thread. I had to end up doing this: datasource.snowflake.hikari.dataSourceClassName=net.snowflake.client.jdbc.SnowflakeBasicDataSource
datasource.snowflake.hikari.dataSourceProperties[url]=jdbc:snowflake://[REDACTED].snowflakecomputing.com
datasource.snowflake.hikari.dataSourceProperties[user]=[REDACTED]
datasource.snowflake.hikari.dataSourceProperties[databaseName]=[REDACTED]
datasource.snowflake.hikari.dataSourceProperties[warehouse]=[REDACTED]
datasource.snowflake.hikari.dataSourceProperties[schema]=[REDACTED] Then, for the private key, I had to write my own extraction method with BouncyCastle (v1.78) and then add in the property manually: extractPrivateKey( resourceLoader.getResource ( getKeyFile () ), getPassphrase () )
.ifPresent ( pk -> getHikari ().addDataSourceProperty ( "privateKey", pk ) ); Regarding this error: This error happens in this class in the driver jar: net.snowflake.client.core.SessionUtilKeyPair.extractPrivateKeyWithBouncyCastleextractPrivateKeyWithBouncyCastle(byte[] privateKeyBytes, String privateKeyPwd) I'm not sure if it's possible to change it to this instead (following solution here) : // Current:
InputDecryptorProvider pkcs8Prov =
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(privateKeyPwd.toCharArray());
// Proposed
InputDecryptorProvider pkcs8Prov
= new JceOpenSSLPKCS8DecryptorProviderBuilder()
.setProvider ( new BouncyCastleProvider () ) // This line made the error go away for me.
.build ( passphrase.toCharArray ( ) ); |
@jase52476 For this particular issue, regarding how to use the new property with Hikari, the objective was for you to read the private key data and pass that as a base64 encoded string using the property You can't use the option I'm not clear on what your issue was when you attempted to use
Did you also add the following JVM argument when you tried passing those two options? If you have already tried that, and you want us to look into this error, then let's open a new issue and discuss that separately. In that new issue, just let us know how you generated the private key (i.e. the OpenSSL command or whatever else you were using) so we can try to reproduce your problem and take it from there. The reason is that you shouldn't really need to explicitly set the provider; the JDBC driver already adds BouncyCastleProvider as a security provider at startup unless it detects that you have already loaded the BouncyCastleFipsProvider. I suspect the issue has to do with the fact that we shade and relocate the Bouncy Castle classes. But again, that's a whole different topic and a separate issue entirely from the one in this issue. |
We are using the standard configuration to setup spring datasources. When we want to use a private key to authenticate, the key is interpreted as a string and not a private key.
net.snowflake.client.jdbc.SnowflakeSQLLoggedException: Invalid parameter value type: java.lang.String, expected type: java.security.PrivateKey.
The final case is to use Azure AppConfiguration/KeyVault to get properties.
The text was updated successfully, but these errors were encountered: