Skip to content
This repository has been archived by the owner on May 15, 2019. It is now read-only.

Username Management for EOS with oc-system model #119

Merged
merged 19 commits into from
Mar 4, 2018

Conversation

tcaiazza
Copy link
Contributor

I've adding in the functionality parse/translate user from EOS device with oc-system model

A few special notes on the username parsing. Let me know if you want me to change how any of this works. I put some of these notes in the yaml files as well, if that is ok.

The oc-system model has the idea of roles, but not of privilege level, but Arista has both. So I've lumped them together. If there is a privilege level set, only the privilege number will be set as the role. But if a role is also defined the role will be D role NAME where D is the privilege level number and NAME is the role name.

Also on the Aristas you can either have an MD5 hashed password or a sha512, but EOS detects the hash as either so you don't have to specify the type of hash when translating the code out. I originally pulled in either the number 5 or sha512 into the encrypted-password leaf with spaces but that isn't really needed.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 84.328% when pulling 478aebd on tcaiazza:eos_system into 1a57ae9 on napalm-automation:develop.

# luckily the ordering is consistent regardless how it
# was entered(in eos at least), privilege then role
#
role:
Copy link
Member

Choose a reason for hiding this comment

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

I was wondering if we should do something like $privilege:$role:

_process:
    - path: "privilege"
      regexp: "(?P<value>\\d+) secret"
      value: "{{ value }}:network-operator"
    - path: "privilege"
      regexp: "(?P<value>(?P<privilege>\\d+) role (?P<role>\\S+)) secret" 
      value: "{{ privilege }}:{{ role }}"

I just checked and looks like "network-operator" is the default role so I guess it would be better to be consistent and always include it. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like that idea. Unfortunately I can't get this to work as expected with the mock data I have. Here is what I have now

role:
     _process:
             - path: "privilege"
                regexp: "(?P<value>\\d+) nopassword"
                value: "{{value}}:network-operator"
             - path: "privilege"
                regexp: "(?P<value>\\d+) secret \\S+"
                 value: "{{value}}:network-operator"
              - path: "privilege"
                 regexp: "(?P<value>(?P<privilege>\\d+) role (?P<role>\\S+)) secret \\S+"
                  value: "{{ privilege }}:{{ role }}"

I get this traceback

    yang.parse_config(device=d)
  File "/Users/tim/napalm-yang/napalm_yang/base.py", line 241, in parse_config
    parser.parse()
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 87, in parse
    self._parse(self._yang_name, self.model, self.mapping[self._yang_name])
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 93, in _parse
    self._parse_container(attribute, model, mapping)
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 131, in _parse_container
    self._parse(k, v, mapping[v._yang_name])
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 93, in _parse
    self._parse_container(attribute, model, mapping)
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 131, in _parse_container
    self._parse(k, v, mapping[v._yang_name])
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 93, in _parse
    self._parse_container(attribute, model, mapping)
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 131, in _parse_container
    self._parse(k, v, mapping[v._yang_name])
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 93, in _parse
    self._parse_container(attribute, model, mapping)
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 131, in _parse_container
    self._parse(k, v, mapping[v._yang_name])
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 95, in _parse
    self._parse_list(attribute, model, mapping)
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 173, in _parse_list
    self._parse(key, obj, element_mapping)
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 93, in _parse
    self._parse_container(attribute, model, mapping)
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 131, in _parse_container
    self._parse(k, v, mapping[v._yang_name])
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 93, in _parse
    self._parse_container(attribute, model, mapping)
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 131, in _parse_container
    self._parse(k, v, mapping[v._yang_name])
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 97, in _parse
    self._parse_leaf(attribute, model, mapping)
  File "/Users/tim/napalm-yang/napalm_yang/parser.py", line 185, in _parse_leaf
    value = self.parser.parse_leaf(attribute, mapping["_process"], self.bookmarks)
  File "/Users/tim/napalm-yang/napalm_yang/parsers/base.py", line 151, in parse_leaf
    result = self._parse_leaf_default(attribute, m, data)
  File "/Users/tim/napalm-yang/napalm_yang/parsers/text_tree.py", line 113, in _parse_leaf_default
    return super()._parse_leaf_default(attribute, mapping, data)
  File "/Users/tim/napalm-yang/napalm_yang/parsers/jsonp.py", line 101, in _parse_leaf_default
    d = self._parse_post_process_filter(mapping["value"], value=d)
  File "/Users/tim/napalm-yang/napalm_yang/parsers/base.py", line 183, in _parse_post_process_filter
    return helpers.template(post_process_filter, extra_vars=self.extra_vars, **kwargs)
  File "/Users/tim/napalm-yang/napalm_yang/helpers.py", line 137, in template
    return template.render(**kwargs)
  File "/usr/local/lib/python2.7/site-packages/jinja2/environment.py", line 1008, in render
    return self.environment.handle_exception(exc_info, True)
  File "/usr/local/lib/python2.7/site-packages/jinja2/environment.py", line 780, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "<template>", line 1, in top-level template code
jinja2.exceptions.UndefinedError: 'privilege' is undefined

I tired to do extra_vars.privilege, since that how it is done with the address prefix in the ipv4.yaml EOS mapping, but it fails as well but because with this error
jinja2.exceptions.UndefinedError: 'dict object' has no attribute 'privilege'

Right before the template.render command in helpers I launched a debugger. It looks like the

string = {{ privilege }}:{{ role }}
kwargs = {'user_key': 'test3', 'parent_key': 'test3', 'value': '15 role network-operator secret sha512 $6$Vd6.7k2FybfsTKKp$S.AHfdwicaWEoA41sPd6ZXOOdruJMrKJh70WNfiX/eZKH1oYBtFz9VbrPlYNDkhM/pi54gcYKH2hviy/xrUav.', 'extra_vars': {'user': {}, 'parent': {}}}

The other thing we'll need to do is check for the following line to set the default role
aaa authorization policy local default-role network-admin and set the default role accordingly.

Copy link
Member

Choose a reason for hiding this comment

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

No worries, will try to find some time tomorrow and give it a try myself. Might require some massaging in the core.

@tcaiazza
Copy link
Contributor Author

@dbarrosop Do you have an examples for how this would work? I've update my fork with your suggestion below, but it isn't working.

_process:
    - path: "privilege"
      regexp: "(?P<value>\\d+) secret"
      value: "{{ value }}:network-operator"
    - path: "privilege"
      regexp: "(?P<value>(?P<privilege>\\d+) role (?P<role>\\S+)) secret" 
      value: "{{ privilege }}:{{ role }}"```

All the examples I can find are not for the same leaf.  This eos example https://github.com/napalm-automation/napalm-yang/blob/develop/napalm_yang/mappings/eos/parsers/config/openconfig-if-ip/ipv4.yaml#L20 uses the regexp but the results aren't immediately used, just later on under ip and prefix.  

I also tried to use the dict flattening syntax that is used for ios address and prefixes. https://github.com/napalm-automation/napalm-yang/blob/develop/napalm_yang/mappings/ios/parsers/config/openconfig-if-ip/ipv4.yaml#L20

If I use this syntax, I can parse out both the privilege number and the rolename
`- path: "privilege.?pnum.role.?rname"`

But when I try to set the value to the role leaf, I can only specify one path not both pnum and rname.  

@dbarrosop
Copy link
Member

let me look into this tomorrow, I saw while helping with the junos:isis model that there might be a bug in how the value is handled. Just make sure your latest changes and the mocked data is in there so I have something to play with.

@dbarrosop
Copy link
Member

ok, I think I finally got this right, see: #122

Note that I removed the value directive because it was conflating some pre/post actions and replaced it with two different directives; pre/post.

Store the privilege and role in the oc-system role leaf and break out in
translator
@tcaiazza
Copy link
Contributor Author

@dbarrosop Thank you for this, I've merge that branch it and works perfectly. I've updated the parser and translator. One more thing I would like to get working that I'm not sure the best way to do it is to change the default role.

7The default role is set with a line like this aaa authorization policy local default-role network-admin which I've add to my test data. Ideally I would pull this bit of config out and use that instead of hardcoding the default role. I've tried using the when option but nothing that gets passed to the helpers.resolve_rule() function is helpful. Any suggestions on how to handle this?

@dbarrosop
Copy link
Member

So I am not sure it'd work today but I think the best way of solving this is by adding rules to containers where you capture data to be used later on as "extra_vars". I will take a look when I find the time this week.

Added a rule directive that specifies a default config setting
that will then store that data in extra_vars so it can be used latter.
@tcaiazza
Copy link
Contributor Author

tcaiazza commented Feb 2, 2018

So I took a stab at doing what you suggested. It works, but I'm not sure how robust my solution is. Basically I've added new a rule directive that causes some config to be parsed in an otherwise unused _process section.

key = mapping.get('key', 'config_default')
config_default = self.resolve_path(
data[0], mapping["config_default"], mapping.get("default"))
return "", {key: config_default['#text']}
Copy link
Member

Choose a reason for hiding this comment

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

Looks quite good and it's similar to what I was thinking myself. Just two differences to what I was thinking myself. From an API I'd do:

- path: "aaa.authorization.policy.local.default-role"
  save: "default_role"

And then your implementation would look more like:

 elif "save" in mapping:
    return None, {mapping["save"]: d}

I also think we should return None here. That'd mean that the parser will keep processing rules (not sure if it will save the extra_vars though but we can fix that if it doesn't). In that case you could save multiple defaults in the same object or even save defaults right in the same leaf as it's needed. For instance:

my_container:
  _process:
    - path: blah.blah
      save: key1
    - path: bleh.bleh
      save: key2
  my_leaf:
    - pre: "{{ etra_vars.key1 }}-{{ extra_vars.key2 }}"

or

my_container:
  my_leaf:
    - path: blah.blah
      save: key1
    - path: bleh.bleh
      save: key2
    - pre: "{{ etra_vars.key1 }}-{{ extra_vars.key2 }}"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I couldn't get it to work exactly the way you have it above, and please let me know if im just going further down the wrong road. But if I return None the whole thing stops processing and I don't see how it would continue to process the other save directives. So for now I left it returning "" but I added something to the parsers.base.parse_container that would allow you to set multiple extra_vars, so we can do what you have posted about. My latest commit I'm able to grab another part of the config and I tacked it on to the end of the role just as a demo.

Now I haven't found a good example for testing the save directive in a leaf yet, since in the username role example we have here the data available to it are things under the path username.?name.privilege . But I'll test the some more later.

@dbarrosop
Copy link
Member

Nice job! Good to see someone is managing to decipher all that madness :)

tcaiazza and others added 3 commits February 2, 2018 13:46
Changed the directive rules as per dbarrosop's comments, from
config_default and key to path and save.

Tweaked parsers.base.parse_container to allow for multiple extra_vars to
be returned.  If the mapping has multiple save directives, only the
first was being taken since the mapping loop breaked after the
extra_vars were returned.

Other minor changes
* Added parsing for eos root password
* default to ipdb when debugging if installed.  I need color in my life
@dbarrosop
Copy link
Member

Can you take a look to #125? Basically introduces the save_as keyword and allows you to use the path to save data as extra_vars. I decided to implement it only on containers because those are not very useful otherwise so the implementation gets extremely simpler. You can see the test case I added there to see how it works.

@dbarrosop
Copy link
Member

dbarrosop commented Feb 4, 2018

Oh, I just saw commit c6f8e7f which is quite similar.

@tcaiazza
Copy link
Contributor Author

tcaiazza commented Feb 4, 2018

I merged #125 into my branch and it works as expected. I'll finish writing the parser integration tests and then push everything later tonight. Thanks for the help! And expect a nxos PR for the same user stuff soon.

Take all the changes from
napalm-automation#125

to get the extra data and remove my attempts.

Update the mapping files to use the save_as directive and finish writing
the profile tests.

fix auto merge issue, jsonp.py should only take code from extract_data branch

Add tests for the eos username parsing

remove src dir that was accidentally committed

specify ipdb.set_trace() correctly
@tcaiazza
Copy link
Contributor Author

tcaiazza commented Feb 5, 2018

I added the tests for the parsers, so I think this is ready to get merged but let me know if you want me to tweak anything.

import pdb
pdb.set_trace()
continue
try:
Copy link
Member

Choose a reason for hiding this comment

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

I don't mind this change but I'd rather do at the top of the file:

try:
    import ipdb
    pdb = ipdb
except ImportError:
    import pdb

and then just leave in here pdb.set_trace()

Copy link
Member

Choose a reason for hiding this comment

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

either that or a helper function that does what you wrote. Probably the helper function is actually better to avoid loading this if it's not necessary. I mostly want to avoid duplication of code.

@dbarrosop
Copy link
Member

Nice job! I took a quick look and it looks good. Just had a tiny comment that should be easy to fix.

@tcaiazza
Copy link
Contributor Author

Don't merge this yet. I assumed that by passing a md5 hash or a SHA512 hash eos would know which hash I'm using, but I just sets the password to the hash.

@tcaiazza
Copy link
Contributor Author

I fixed the password issue, let me know if there is anything else to tweak.

@dbarrosop
Copy link
Member

Sorry for the delay, I think this is good now so I am merging it.

@dbarrosop dbarrosop merged commit 6412cfa into napalm-automation:develop Mar 4, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants