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

refactor(azure-iot-device): Auth Revisions #555

Merged
merged 5 commits into from
May 27, 2020

Conversation

cartertinney
Copy link
Member

@cartertinney cartertinney commented Apr 29, 2020

  • Removed auth providers and refactored entire auth flow
  • Unified auth between provisioning and hub clients
  • Refactored pipeline to store configuration details on the root
  • Refactored translation tests to move to new infrastructure
  • Removed all code no longer necessary due to these changes

@cartertinney
Copy link
Member Author

./azp run Azure.azure-iot-sdk-python-horton-e2e

@cartertinney
Copy link
Member Author

./azp run

1 similar comment
@cartertinney
Copy link
Member Author

./azp run

@olivakar
Copy link
Collaborator

/azp run Azure.azure-iot-sdk-python-dps-e2e

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

ttl (int): Time to live for the token, in seconds
"""

_auth_rule_token_format = (
Copy link
Collaborator

@olivakar olivakar May 18, 2020

Choose a reason for hiding this comment

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

_auth_rule_token_format [](start = 4, length = 23)

why not keyname_token_format ? #ByDesign

Copy link
Member Author

@cartertinney cartertinney May 19, 2020

Choose a reason for hiding this comment

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

Because apparently for some reason the official name of of the skn field is not... shared key name as you might expect, and is actually.... for some reason... auth rule.

I don't get it either.


In reply to: 426946266 [](ancestors = 426946266)

Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering if this was the name in the azure portal at one point. Right now, the key names (iothubowner, etc) show up in the portal under "Shared Access Policy" as "Access policy name". We had this same thing happen with one of the DPS names -- I think ProvisioningHostName shows up in the portal with one name and in the SDK as another.


In reply to: 427500301 [](ancestors = 427500301,426946266)


@property
def expiry_time(self):
"Expiry Time is READ ONLY"
Copy link
Collaborator

@olivakar olivakar May 18, 2020

Choose a reason for hiding this comment

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

" [](start = 8, length = 1)

are read only doc strings with single quotes instead of 3 ? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

Nope, that's just a mistake.


In reply to: 426946876 [](ancestors = 426946876)

:type sastoken: :class:`azure.iot.device.common.auth.SasToken`
:param x509: X509 to be used for authentication. Mutually exclusive with sastoken.
:type x509: :class:`azure.iot.device.models.X509`
:param str server_verification_cert: The trusted certificate chain.
Copy link
Collaborator

@olivakar olivakar May 18, 2020

Choose a reason for hiding this comment

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

:param str server_verification_cert: The trusted certificate chain. [](start = 8, length = 67)

this has nothing to do with you PR...but i just wanted to mention should we update the doc string to say that it is the content of the cert chain...i have faced ppl with some confusion #Pending

Copy link
Member Author

Choose a reason for hiding this comment

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

At some point we'll change the functionality to take both the contents or filepath


In reply to: 426949443 [](ancestors = 426949443)

self._token_renewal_timer = None
if timer:
logger.debug("Cancelling SAS Token renewal timer")
timer.cancel()
Copy link
Collaborator

@olivakar olivakar May 19, 2020

Choose a reason for hiding this comment

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

timer.cancel() [](start = 12, length = 14)

can you also put timer = None? For reasons that I don't quite remember i see this pattern in all timers in our stack #ByDesign

Copy link
Member Author

Choose a reason for hiding this comment

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

This happens on line 303


In reply to: 426955668 [](ancestors = 426955668)

from . import pipeline

# TODO: different package?
from azure.iot.device.common.auth import connection_string as cs
Copy link
Collaborator

@olivakar olivakar May 19, 2020

Choose a reason for hiding this comment

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

cs [](start = 62, length = 2)

may be import as connection and token...just to make more meaningful in code. #ByDesign

Copy link
Member

Choose a reason for hiding this comment

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

I have mixed feelings about renaming to cs and st. Yes, it's shorter, but this is the only place we do this and I don't want it to get out of control.


In reply to: 426962229 [](ancestors = 426962229)

Copy link
Member Author

@cartertinney cartertinney May 19, 2020

Choose a reason for hiding this comment

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

So for context, the reason that we do this is that connection_string and sastoken are kind of abnormal modules (for us, not in general) in that there are associated values in the modules, such as the constants. Thus we need to import the whole module, not just the object in it.

The problem is that connection_string as a term easily gets overloaded, and I didn't want to have to worry about accidentally doing that, so I aliased the package to be something that is out of the way and not likely to be used in a variable name by accident.


In reply to: 427502200 [](ancestors = 427502200,426962229)

Copy link
Member

Choose a reason for hiding this comment

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

I'm OK with that, I've seen that problem before and never came up with a better answer. I wouldn't be offended if you left this as-is


In reply to: 427504005 [](ancestors = 427504005,427502200,426962229)

@@ -263,4 +220,4 @@ def _handle_pipeline_event(self, event):

else:
# all other messages get passed up
super(IoTHubMQTTTranslationStage, self)._handle_pipeline_event(event)
self.send_event_up(event)
Copy link
Collaborator

@olivakar olivakar May 19, 2020

Choose a reason for hiding this comment

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

self.send_event_up(event) [](start = 12, length = 25)

oh we missed this #Resolved

@@ -3,6 +3,9 @@
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
"""This module is for creating product identifiers or agent strings for IotHub as well as Provisioning."""

# TODO: Should this be renamed to user_agent.py?
Copy link
Member

@BertKleewein BertKleewein May 19, 2020

Choose a reason for hiding this comment

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

yes #Closed


def __call__(self, *args, **kwargs):
return self._get_method()(*args, **kwargs)

def __eq__(self, other):
return self._get_method() == other
# if inspect.isfunction(other):
Copy link
Member

@BertKleewein BertKleewein May 19, 2020

Choose a reason for hiding this comment

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

if inspect.isfunction(other): [](start = 8, length = 31)

I've been debating this in my head. I think you should leave the getattr change and revert the other 2 changes. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree, especially since the getattr is the only thing required to fix the issue.


In reply to: 427012869 [](ancestors = 427012869)

from .signing_mechanism import SymmetricKeySigningMechanism

# from .sastoken import SasToken
# from .connection_string import ConnectionString
Copy link
Member

@BertKleewein BertKleewein May 19, 2020

Choose a reason for hiding this comment

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

please remove #Closed

# Perhaps some kind of... Pipeline Daemon?
class SasTokenRenewalStage(PipelineStage):
# Amount of time, in seconds, prior to token expiration, when the renewal process will begin
DEFAULT_TOKEN_RENEWAL_MARGIN = 120
Copy link
Member

@BertKleewein BertKleewein May 19, 2020

Choose a reason for hiding this comment

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

DEFAULT_TOKEN_RENEWAL_MARGIN = 120 [](start = 4, length = 34)

why is this set here instead of setting this in the pipeline config with the renewal interval? part of the purpose of this is to account for clock skew between the client and the server and it's not had to imagine needing a bigger margin if you know that your device clock is going to be inaccurate for some reason or another. #Pending

Copy link
Collaborator

Choose a reason for hiding this comment

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

to be clear are you saying this would be user input kwarg ?


In reply to: 427021277 [](ancestors = 427021277)

Copy link
Member

Choose a reason for hiding this comment

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

yes, margin should be a config kwarg that the user can supply


In reply to: 428953125 [](ancestors = 428953125,427021277)

# a few seconds given processing time. We could make this more accurate if SasToken
# objects had a live TTL value rather than a static one (i.e. "there are n seconds
# remaining in the lifespan of this token", rather than "this token was intended to live
# for n seconds")
Copy link
Member

@BertKleewein BertKleewein May 19, 2020

Choose a reason for hiding this comment

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

Given the possibility of clock skew, TTL is so vague that you're really splitting hairs here. :) #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree, that's why I didn't upend the implementation to account for this. Figured it would be a good information to document inline however.


In reply to: 427411587 [](ancestors = 427411587)

@BertKleewein
Copy link
Member

BertKleewein commented May 19, 2020

I love the clean relationships between sastoken, signing_mechanism, and the renewal logic! This is a huge improvement! #Closed

this._start_renewal_timer()

self._token_renewal_timer = threading.Timer(seconds_until_renewal, renew_token)
self._token_renewal_timer.daemon = True
Copy link
Member

@BertKleewein BertKleewein May 19, 2020

Choose a reason for hiding this comment

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

self._token_renewal_timer.daemon = True [](start = 12, length = 39)

Mental exercise: Consider different token lifetime rules. why do we create a token on pipeline init and not on connect? Why do we keep the token renewal timer going after we disconnect? We don't need the token before we connect and we don't need it after we disconnect.

This is why I'm asking the question: daemon=True protects us for shutdown scenarios. In other words, if a daemon timer (thread) is still running when we shut down, it won't stop the app from exiting. That's good.

But, daemon=True doesn't stop the timer from living longer than the pipeline. Consider an app that makes multiple clients, with each client sending a message then dying. For each client instance, there would be a thread that lives for up to a full hour past the client. Then, when each timer fires, it silently raises an exception when it tries to dereference this and dies. No harm, but still sloppy and sort-of a memory leak. #Pending

self.refresh()

def __str__(self):
return self._token
Copy link
Member

@BertKleewein BertKleewein May 19, 2020

Choose a reason for hiding this comment

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

Mental exercise :#2 (written after the one in pipeline_stages_base.py).

postulate: The notion that a SasToken has a value and that we need to periodically "refresh" that value is not quite right. It's better to think about the SasToken object as a "password generator" object. When a consumer of this class needs to talk to a network service, he can use this object to generate a password that gives him access for some period of time. When that password is set to expire, the consumer is responsible for generating a new one. This is how the code is written and I like it.

Saying token.refresh(); password=str(token); as we do, accomplishes this, but it's not quite semantically valid(). Rather, what we want is password=token.generate_new_password()

why this matters: It doesn't really matter that much, but, there an odd ownership relationship with the root owning the SasToken, object and 2 different pipelines controlling it. Both MQTT and HTTP can refresh the token. If we think of the SasToken object as having a value, having someone else change the value feels wrong. But if we think of each pipeline as owning it's own password that it gets from the generator object, it just feels better. #Pending



class IoTEdgeHsm(object):
class IoTEdgeHsm(SigningMechanism):
Copy link
Member

@BertKleewein BertKleewein May 19, 2020

Choose a reason for hiding this comment

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

don't know the solution, but it seems odd that SigningMechanism is in the common.auth sub-package, but this one is not in an auth sub-package #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I'm not thrilled about it, but I didn't really want to make a package just for one module


In reply to: 427506098 [](ancestors = 427506098)

@@ -151,7 +78,7 @@ def test_reason(self, cls_type, init_kwargs):

pipeline_ops_test.add_operation_tests(
test_module=this_module,
op_class_under_test=pipeline_ops_http.SetHTTPConnectionArgsOperation,
op_class_under_test=pipeline_ops_http.HTTPRequestAndResponseOperation,
Copy link
Member

@BertKleewein BertKleewein May 19, 2020

Choose a reason for hiding this comment

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

op_class_under_test=pipeline_ops_http.HTTPRequestAndResponseOperation, [](start = 4, length = 70)

huh. apparently this attribute isn't that important #Closed

Copy link
Member Author

@cartertinney cartertinney May 19, 2020

Choose a reason for hiding this comment

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

I mean.... it is important... it's just that it doesn't necessarily cause failure. If you pass the wrong one, you test the wrong one. If that wrong one still passes all tests, this isn't going to fail


In reply to: 427577323 [](ancestors = 427577323)



@six.add_metaclass(abc.ABCMeta)
class PipelineConfigInstantiationTestBase(object):
Copy link
Member

Choose a reason for hiding this comment

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

PipelineConfigInstantiationTestBase [](start = 6, length = 35)

<eyes crossing at 7 AM> Man, this "InstantiationTestBase" convention makes for some long identifiers. No change requested. Just murmuring to myself. Get off my lawn.

Copy link
Member Author

Choose a reason for hiding this comment

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

Some of these test class names could be confused for parody of Java tbh 😂


In reply to: 429295058 [](ancestors = 429295058)

Copy link
Member

@BertKleewein BertKleewein left a comment

Choose a reason for hiding this comment

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

:shipit:

Copy link
Collaborator

@olivakar olivakar left a comment

Choose a reason for hiding this comment

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

🐳 🐋

@cartertinney cartertinney merged commit fd27e8c into Azure:master May 27, 2020
@cartertinney cartertinney deleted the ct-auth-revision branch May 27, 2020 18:10
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.

3 participants