Skip to content
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

Support secure channels through SSL/TLS #33

Merged
merged 1 commit into from
Aug 28, 2018

Conversation

mnito
Copy link
Contributor

@mnito mnito commented Aug 6, 2018

grpclib does not currently work with secure gRPC channels through HTTPS which we needed for our use case at Slyce -- this pull request uses hyper-h2's SSL context setup to create a secure channel if a secure keyword argument is provided when instantiating the channel.

Copy link
Owner

@vmagamedov vmagamedov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

grpclib/https.py Outdated

# We want to negotiate using NPN and ALPN. ALPN is mandatory, but NPN may
# be absent, so allow that. This setup allows for negotiation of HTTP/1.1.
ctx.set_alpn_protocols(["h2", "http/1.1"])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to use ALPN/NPN to negotiate about protocols, in gRPC only h2 is used with prior knowledge: https://python-hyper.org/projects/h2/en/stable/negotiating-http2.html#prior-knowledge

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a significant performance hit or disadvantage to negotiating? Our server requires negotiating and it seems friendlier to set these by default than requiring passing a custom context in situations where negotiating is required.

grpclib/https.py Outdated
# RFC 7540 Section 9.2: Implementations of HTTP/2 MUST use TLS version 1.2
# or higher. Disable TLS 1.1 and lower.
ctx.options |= (
ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create_default_context already disables OP_NO_SSLv2, OP_NO_SSLv3 and OP_NO_COMPRESSION: https://github.com/python/cpython/blob/3.5/Lib/ssl.py#L438

@@ -379,11 +380,13 @@ class Channel:
"""
_protocol = None

def __init__(self, host='127.0.0.1', port=50051, *, loop, codec=None):
def __init__(self, host='127.0.0.1', port=50051, *, loop, codec=None,
secure=False):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be more flexible to have ssl: Union[bool, SSLContext]=False argument instead of secure: bool=False, so users will be able to provide any SSLContext they want for their case. If ssl==True, we create default context.

grpclib/https.py Outdated
@@ -0,0 +1,66 @@
import ssl
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grpclib/https.py Outdated

# RFC 7540 Section 9.2.1: A deployment of HTTP/2 over TLS 1.2 MUST disable
# compression.
ctx.options |= ssl.OP_NO_COMPRESSION
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above

grpclib/https.py Outdated
"""


def get_http2_ssl_context():
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that it is too early to create additional module just for this function, which will be used only in one place. You can place it in utils.py or in client.py or directly in the Channel class as private method or in Channel.__init__ method in-place. And I think that there is no need to copy-paste whole function as is with full copyright notice, I think that this code is provided as documentation for reference and it is OK to use it's parts just with a link, pointing to the hyper-h2 project.

@vmagamedov
Copy link
Owner

And please ensure that :scheme -> https is used for requests over secure connections.

@vmagamedov
Copy link
Owner

@mnito, will you have a time to finish this PR? Or maybe I can do this for you?

@mnito
Copy link
Contributor Author

mnito commented Aug 27, 2018

Hey @vmagamedov, sorry I haven't gotten to this -- I have some time right now to make the requested changes.

@mnito
Copy link
Contributor Author

mnito commented Aug 27, 2018

Hey @vmagamedov I made the requested changes and tested with both SSL-enabled and non-SSL-enabled servers. I left the protocol negotiation stuff in because we never had any problems using grpcio with our server and communication did not work without it. If the cost outweighs the benefits of leaving it in, feel free to take it out and we can always pass in a custom SSL context -- but I figured it would be a friendlier default.

@mnito
Copy link
Contributor Author

mnito commented Aug 27, 2018

Made a mistake in handling the potential SSL import error -- updated.

Copy link
Owner

@vmagamedov vmagamedov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm almost ready to merge, will wait for your feedback about advertised protocols list.

ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
ctx.options |= (ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1)
ctx.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20')
ctx.set_alpn_protocols(['h2', 'http/1.1'])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm totally Ok with this, I don't worry about possible overhead, as this happens only once for a long-living connection. The only concern is that I don't think that we should advertise http/1.1 protocol support here. Can you remove it from the list and test with your server?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah you're probably right -- let me just do a quick test with our server

@@ -389,16 +395,39 @@ def __init__(self, host='127.0.0.1', port=50051, *, loop, codec=None):
header_encoding='utf-8')
self._authority = '{}:{}'.format(self._host, self._port)

if ssl and isinstance(ssl, bool):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just if ssl is True:?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doh - yeah

@mnito
Copy link
Contributor Author

mnito commented Aug 28, 2018

Should be good to go.

@vmagamedov
Copy link
Owner

Everything looks great, can you rebase your changes and resolve merge conflicts?

@mnito
Copy link
Contributor Author

mnito commented Aug 28, 2018

Sure, do you want me to squash as well?

@vmagamedov
Copy link
Owner

Yes! It would be great.

@mnito mnito force-pushed the feature/secure_channel branch from d0a23b0 to cdc7e22 Compare August 28, 2018 17:28
@mnito mnito force-pushed the feature/secure_channel branch from cdc7e22 to 079c70a Compare August 28, 2018 17:38
@mnito
Copy link
Contributor Author

mnito commented Aug 28, 2018

I rebased! Thanks for all your input.

@vmagamedov vmagamedov merged commit 5f425c6 into vmagamedov:master Aug 28, 2018
@vmagamedov
Copy link
Owner

Thank you for your work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants