-
Notifications
You must be signed in to change notification settings - Fork 13.9k
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
feat: add snowflake keypair authentication #21322
Conversation
@mebegu @villebro @dpgaspar |
Awesome @xiayanzheng , I'll review within the next 24 hours 👍 |
Codecov Report
@@ Coverage Diff @@
## master #21322 +/- ##
===========================================
- Coverage 66.56% 55.17% -11.40%
===========================================
Files 1790 1790
Lines 68393 68454 +61
Branches 7279 7287 +8
===========================================
- Hits 45526 37768 -7758
- Misses 20990 28809 +7819
Partials 1877 1877
Flags with carried forward coverage won't be shown. Click here to find out more.
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
…5: error: Name "logger" is not defined
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for rewriting this functionality. Left a few comments - In addition, please add documentation for this as requested in the previous PR so others don't need to read the code to understand how this works.
auth_method = encrypted_extra.pop("auth_method", None) | ||
auth_params = encrypted_extra.pop("auth_params", {}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any reason to .pop()
here, let's just .get()
with open(auth_params['privatekey_path'], "rb") as key: | ||
p_key = serialization.load_pem_private_key( | ||
key.read(), | ||
password=auth_params['privatekey_pass'].encode(), | ||
backend=default_backend() | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also support passing the privatekey
directly to avoid having to place the key inside the Superset instance/pod. This way it would be possible to either pass privatekey_path
or privatekey
.
requirements/testing.txt
Outdated
snowflake-connector-python==2.7.9 | ||
# via snowflake | ||
snowflake-sqlalchemy==1.2.4 | ||
# via snowflake |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we need these, as there are no integration tests added here that require having the snowflake drivers present.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the previous pr if snowflake drivers are not included, the GitHub actions will raise snowflake not found error even no integration tests added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because the previous PR did imports from the snowflake-sqlalchemy
package (this one no longer does)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I forgot that, thanks for reminding me. In this case, snowflake drivers should be removed from testing requirements.
return | ||
connect_args = params.setdefault("connect_args", {}) | ||
if auth_method == "keypair": | ||
with open(auth_params['privatekey_path'], "rb") as key: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There appears to be some linting issues here (we should always use double quotes)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good. Would be nice to have tests and add some docs also.
@villebro it feels like all these different excrypted_extra Dict
keys can become hard to manage on the future, do you think adding some TypedDict
s would improve it?
encryption_algorithm=serialization.NoEncryption()) | ||
connect_args["private_key"] = pkb | ||
except json.JSONDecodeError as ex: | ||
logger.error(ex, exc_info=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use logger.exception(ex). Also wonder if there is value on this try
/except
block, since load_perm_private_key
can raise and p_key.private_bytes
can also raise. We could just let it fail
if not database.encrypted_extra: | ||
return | ||
try: | ||
encrypted_extra = json.loads(database.encrypted_extra) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we can move the except
block to here, since only json.loads
will raise JSONDecodeError
. More readable and less nesting
if not auth_method: | ||
return | ||
connect_args = params.setdefault("connect_args", {}) | ||
if auth_method == "keypair": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should add an else
and support ALLOWED_EXTRA_AUTHENTICATIONS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added in 89cf4d3
@dpgaspar not a bad idea. Maybe we can do a standardization pass for 3.0 that makes these configs as uniform as possible across all specs? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments, but we're getting closer 👍
docs/docs/databases/snowflake.mdx
Outdated
***Please note that you need to split your private key content line by line before store it to privatekey_body*** | ||
|
||
``` | ||
{ | ||
"auth_method": "keypair", | ||
"auth_params": { | ||
"privatekey_body":[ | ||
"-----BEGIN ENCRYPTED PRIVATE KEY-----", | ||
"Key Body", | ||
"Key Body", | ||
"Key Body", | ||
"Key Body", | ||
"-----END ENCRYPTED PRIVATE KEY-----" | ||
], | ||
"privatekey_pass":"Your Private Key Password" | ||
} | ||
} | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While JSON isn't ideal for storing multiline strings, maybe we should just store it with the newlines replaced by a \n
as follows:
{
"auth_method": "keypair",
"auth_params": {
"privatekey_body": "-----BEGIN ENCRYPTED PRIVATE KEY-----\n...\n-----END ENCRYPTED PRIVATE KEY-----",
"privatekey_pass":"Your Private Key Password"
}
}
Later when we consolidate this type of logic across all db engine specs we can then move the private key to a separate column in the database model and have a dedicated field in the UI that accepts the string with newlines. WDYT @xiayanzheng @dpgaspar ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your advice, I'm ok with both formats, and this change just pushed to this PR.
requirements/testing.txt
Outdated
@@ -136,7 +136,6 @@ websocket-client==1.2.0 | |||
# via docker | |||
wrapt==1.12.1 | |||
# via astroid | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add the removed newline to avoid introducing unnecessary changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed in 78f625a
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. It would be great to add some unit tests, but I'm ok adding this as-is and following up with unit tests later if/when bugs are found.
Thanks for the review and approval, I'm starting to spin up the test environment and write tests, after the test code is ready I will create a new PR. |
Hi @villebro, do we need to wait for someone to merge this pr, or do I need to do something to merge it? |
I think it's been ready for review long enough to be merged. If we need to refine this let's open a follow up PR. Thanks for all the work @xiayanzheng , really awesome feature! |
SUMMARY
We want add snowflake in superset, but our snowflake is using SSO authentication so we can't add it with built-in snowflake connector, we found we can connect it with snowflake keypair authentication. so I modified connector file to support keypair authentication.
BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
BEFORE:
I can't add snowflake with keypair authentication
AFTER:
【Update @ 2022/09/04】
Now we can add private key path and private key password in "SECURE EXTRA" with flowing format, like below.
in
TESTING INSTRUCTIONS
ADDITIONAL INFORMATION