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

Deploy system user assertion to device #212

Merged

Conversation

st3v3nmw
Copy link
Collaborator

@st3v3nmw st3v3nmw commented Feb 8, 2024

Depends on #210

@st3v3nmw st3v3nmw changed the title Deploying system user assertions Deploy system user assertion to device Feb 8, 2024
@st3v3nmw st3v3nmw force-pushed the deploying-system-user-assertions branch 3 times, most recently from ff1289a to 119d149 Compare February 12, 2024 07:33
@st3v3nmw st3v3nmw marked this pull request as ready for review February 12, 2024 07:45
@st3v3nmw st3v3nmw force-pushed the deploying-system-user-assertions branch from 119d149 to aa55dcd Compare February 12, 2024 07:57
# [...]
# pyyaml tries to parse the * as the start of an alias leading to errors
yaml.Loader.fetch_alias = yaml.Loader.fetch_plain
yaml.Loader.yaml_implicit_resolvers.pop("*", None)
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels like it could have far-reaching side-effects, but I don't think we use yaml (or yaml aliases) in enough other places for it to matter.

Copy link
Collaborator Author

@st3v3nmw st3v3nmw Feb 13, 2024

Choose a reason for hiding this comment

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

I've created a new class IgnoreYamlAliasesLoader(yaml.SafeLoader) with the custom behavior and left yaml.SafeLoader untouched.

For posterity's sake, these were the other attempts:

Attempt 1

    # load the specific module we want
    loader_spec = importlib.util.find_spec("yaml.loader")
    loader = importlib.util.module_from_spec(loader_spec)
    loader_spec.loader.exec_module(loader)
    # patch it
    loader.SafeLoader.fetch_alias = loader.SafeLoader.fetch_plain
    loader.SafeLoader.yaml_implicit_resolvers.pop("*", None)

    import sys
    # prints: 130478141495936 130478198317968
    print(id(sys.modules["yaml.loader"]), id(sys.modules["yaml"]))

    safe_loader = loader.SafeLoader(headers)
    assertion = safe_loader.get_single_data()
    safe_loader.dispose()
    assertion["signature"] = signature

    import yaml
    # prints: 130478141495936 130478198317968
    print(id(sys.modules["yaml.loader"]), id(sys.modules["yaml"]))

    # this raises the error as before (original behavior without patching)
    yaml.safe_load(headers)

If we do this, yaml.safe_load(headers) retains its original behavior but the catch is the modules in sys.modules both point to the same objects which is suspicious (ids are the same)

Attempt 2 (reloading)

    # patch
    yaml.SafeLoader.fetch_alias = yaml.SafeLoader.fetch_plain
    yaml.SafeLoader.yaml_implicit_resolvers.pop("*", None)

    import sys
    # prints: 123975180118560 123975179908128
    print(id(sys.modules["yaml.loader"]), id(sys.modules["yaml"]))

    assertion = yaml.load(headers, yaml.SafeLoader)
    assertion["signature"] = signature

    importlib.reload(yaml)
    # prints: 123975180118560 123975179908128
    print(id(sys.modules["yaml.loader"]), id(sys.modules["yaml"]))
    yaml.safe_load(headers)  # no error raised (original behavior not recovered)

yaml.Loader.fetch_alias = yaml.Loader.fetch_plain
yaml.Loader.yaml_implicit_resolvers.pop("*", None)

assertion = yaml.load(headers, yaml.Loader)
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious: why can't we use safe_load anymore? Is it because the above monkeypatching doesn't work the same on yaml.SafeLoader?

Copy link
Collaborator Author

@st3v3nmw st3v3nmw Feb 13, 2024

Choose a reason for hiding this comment

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

safe_load only takes the input and no other arguments but load allows you to customize the loader.

It works with load(headers, yaml.SafeLoader) though, good catch,... thanks

# adding an assertion is idempotent
snap_http.add_assertion(assertion)
except SnapdHttpException as e:
result = json.loads(e.args[0])["result"]
Copy link
Contributor

Choose a reason for hiding this comment

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

In view or canonical/snap-http#8 being merged can we perhaps use SnapdHttpException.json here instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've made the changes

@st3v3nmw st3v3nmw force-pushed the deploying-system-user-assertions branch 3 times, most recently from dcf4ca2 to 495e96b Compare February 13, 2024 11:01

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.yaml_implicit_resolvers.pop("*", None)
Copy link
Contributor

Choose a reason for hiding this comment

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

I hate to continue to pick on this, but yaml_implicit_resolvers seems to be a class attribute on SafeLoader, so popping on it is mutative. See:

>>> import yaml
>>> yaml.SafeLoader.yaml_implicit_resolvers["*"]
[('tag:yaml.org,2002:yaml', re.compile('^(?:!|&|\\*)$'))]
>>> class MyLoader(yaml.SafeLoader):
...     def __init__(self, *args, **kwargs):
...         super().__init__(*args, **kwargs)
...         self.yaml_implicit_resolvers.pop("*", None)
... 
>>> import io
>>> my_loader = MyLoader(io.StringIO())
>>> yaml.SafeLoader.yaml_implicit_resolvers["*"]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: '*'

I think the proper way to exempt this resolver's behaviour is either to override resolve (too complex to do here, probably) or override with a copy and mutate that copy.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copying seems to work:

In [1]: import io
   ...: import yaml
   ...: from copy import deepcopy
   ...:

In [2]: yaml.SafeLoader.yaml_implicit_resolvers["*"]
Out[2]: [('tag:yaml.org,2002:yaml', re.compile(r'^(?:!|&|\*)$', re.UNICODE))]

In [3]: class MyLoader(yaml.SafeLoader):
   ...:     def __init__(self, *args, **kwargs):
   ...:         super().__init__(*args, **kwargs)
   ...:         self.yaml_implicit_resolvers = deepcopy(super().yaml_implicit_resolvers)
   ...:         self.yaml_implicit_resolvers.pop("*", None)
   ...:

In [4]: my_loader = MyLoader(io.StringIO())

In [5]: yaml.SafeLoader.yaml_implicit_resolvers["*"]
Out[5]: [('tag:yaml.org,2002:yaml', re.compile(r'^(?:!|&|\*)$', re.UNICODE))]

Lemme switch to that.

Copy link
Contributor

@Perfect5th Perfect5th left a comment

Choose a reason for hiding this comment

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

Good work, thanks!

@Perfect5th Perfect5th merged commit 7e5fd76 into canonical:master Feb 16, 2024
5 checks passed
@st3v3nmw st3v3nmw deleted the deploying-system-user-assertions branch February 17, 2024 09:12
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