scrapli / scrapligo

scrapli, but in go!
MIT License
244 stars 35 forks source link

FortiGate platform handle VDOM's and changing prompts #154

Closed patrickfnielsen closed 9 months ago

patrickfnielsen commented 9 months ago

I've been working on getting scrapligo work properly on a FortiGate, and I've run into a few issues. I've created the following bare bones platform:

---
platform-type: 'fortinet_fortios'
default:
  driver-type: 'network'
  privilege-levels:
    exec:
      name: 'exec'
      pattern: '[\w_-]+ [$#]'
      previous-priv:
      deescalate:
      escalate:
      escalate-auth: false
      escalate-prompt:
    configuration:
      name: 'configuration'
      pattern: '^[\w_-]+ \([\w_-]+\)? [$#]'
      previous-priv: 'exec'
      deescalate: "end"
      escalate: # ?? depends on if VDOM's are enabled or not
      escalate-auth: false
      escalate-prompt:
  default-desired-privilege-level: 'exec'
  failed-when-contains:
    - "Command fail"
  textfsm-platform: '' # ignored in go because no ntc-templates
  network-on-open:
    - operation: 'acquire-priv' # targets default desired priv by default
  network-on-close:
    - operation: 'acquire-priv'
    - operation: 'channel.write'
      input: 'exit'
    - operation: 'channel.return' 

I have a few issues though, and maybe there's already a solution I just haven't found.

Configuration - and show commands - on fortigates depends on whether or not vdom's are enabled. If a VDOM is enabled most commands have to follow the below syntax:

config vdom
  edit {{vdom_name}}
    get system interface
    or config system interface

Without VDOM it's just:

get system interface
or config system interface

I can handle that my self - but the fortigate also changes the prompt when doing so - so it starts as "fw-1 #" and ends as "fw-1 (vdom_name) #". I have found opoptions.WithInterimPromptPattern and that does work, but I have to add an explicit "end" to my config snippet above since priv is not automatically reset.

I see no way to get SendConfigs to work as escalate again depends on if vdoms are enabled, or not - Is there any way to get this to work, or should i just "skip" using SendConfigs and do it using SendCommands ?

carlmontanari commented 9 months ago

can you share some output of what things look like in a terminal if you were configuring/showing stuff in both scenarios?

you could have a configuration level configuration and another configuration-vdom maybe, we have some stuff like that for configure vs configure exclusive (XR/junos). im not sure I fully follow the part where you need interim prompt and having to re-escalate though so maybe the example will help me grok it.

in general though, the SendConfig{s} stuff is really just convenience around SendCommand{s} anyway, so if it doesnt fit right/well then I think it seems very reasonable to just use the send commands. again, maybe some terminal output will give me a better idea though, so we'll see!

also just for context -- vdom is something like a firewall context or something? doesnt matter of course, just curious :p

patrickfnielsen commented 9 months ago

Sure thing - Here's a few examples. When VDOM's are in use, the following syntax would be use:

fw-2 # config vdom
fw-2 (vdom) # edit fw-inet
current vf=fw-inet:1

fw-2 (fw-inet) # get system interface
== [ ha ]
name: ha   mode: static    ip: 0.0.0.0 0.0.0.0   status: up    netbios-forward: disable    type: physical   netflow-sampler: disable    sflow-sampler: disable    src-check: enable    explicit-web-proxy: disable    explicit-ftp-proxy: disable    proxy-captive-portal: disable    trunk: disable    mtu-override: disable    wccp: disable    drop-overlapped-fragment: disable    drop-fragment: disable
== [ mgmt ]
name: mgmt   ip: 10.1.1.1 255.255.255.192   status: up    netbios-forward: disable    type: physical   netflow-sampler: disable    sflow-sampler: disable    src-check: enable    explicit-web-proxy: disable    explicit-ftp-proxy: disable    proxy-captive-portal: disable    trunk: disable    mtu-override: disable    wccp: disable    drop-overlapped-fragment: disable    drop-fragment: disable

fw-2 (fw-inet) # end
fw-2 #

fw-2 (fw-inet) # config system interface
fw-2 (interface) #

If VDOM's are not used, it would be the following:

fw-2 # get system interface
== [ ha ]
name: ha   mode: static    ip: 0.0.0.0 0.0.0.0   status: up    netbios-forward: disable    type: physical   netflow-sampler: disable    sflow-sampler: disable    src-check: enable    explicit-web-proxy: disable    explicit-ftp-proxy: disable    proxy-captive-portal: disable    trunk: disable    mtu-override: disable    wccp: disable    drop-overlapped-fragment: disable    drop-fragment: disable
== [ mgmt ]
name: mgmt   ip: 10.1.1.1 255.255.255.192   status: up    netbios-forward: disable    type: physical   netflow-sampler: disable    sflow-sampler: disable    src-check: enable    explicit-web-proxy: disable    explicit-ftp-proxy: disable    proxy-captive-portal: disable    trunk: disable    mtu-override: disable    wccp: disable    drop-overlapped-fragment: disable    drop-fragment: disable

With regards to configuration: When in VDOM mode I guess the config vdom would be equal to conf t on a cisco device, and I would have to add an edit <vdom-name> to all my config - that is workable, and it would be possible to escalate and deescalate from.

When not in VDOM mode there is no single config command:

fw-2 # config
alertemail             Alert email configuration.
antivirus              AntiVirus configuration.
application            Application control configuration.
authentication         authentication
dlp                    DLP configuration.
dnsfilter              DNS filter configuration.
emailfilter            AntiSpam configuration.
endpoint-control       Endpoint control configuration.
extender-controller    FortiExtender controller configuration.
file-filter            file-filter
firewall               Firewall configuration.
ftp-proxy              FTP proxy configuration.
icap                   ICAP client configuration.
ips                    IPS configuration.
log                    Log configuration.
router                 Router configuration.
sctp-filter            SCTP filter configuration.
ssh-filter             SSH filter configuration.
switch-controller      External FortiSwitch configuration.
system                 System operation configuration.
user                   Authentication configuration.
videofilter            videofilter
voip                   VoIP configuration.
vpn                    VPN configuration.
waf                    Web Application Firewall configuration.
web-proxy              Web proxy configuration.
webfilter              Web filter configuration.
wireless-controller    Wireless access point configuration.

fw-2 # config system  interface

fw-2 (interface) #

So I would either have to create a config context for all those + one for VDOM, or just not use the SendConfig command, and make the the prompt in SendCommand generic to support all possible prompts.

With regards to the interim prompt - It's not needed if I just create a prompt pattern that supports all scenarios like the following: [\w_-]+ (?:\([\w_-]+\)? )?[$#]. But by doing this I miss out on the SendConfig command and automatic deesclation, though not an major issue I was just wondering if there was any better way? Maybe by having the library check for all prompts and detected that the privilege level has been escalated to configuration automatically, but since it knows what level it should be in it could send the deescalate command - Though this might be a lot of work for little gain.

And yes VDOM's are to some extent like contexts, basically "virtual" firewalls with it's own config :)

carlmontanari commented 9 months ago

@patrickfnielsen awesome, thanks for the great detail!

I wonder if this may be a solid opportunity for a not well (or at all???) documented feature/thing "variants".

tl;dr here is that the idea is you have some platform, lets say "acme_os", and that there is some scenario where there are some slight differences, for example (and similar here!) acme-os behaves a bit differently in a multi supervisor type setup. with a variant you can expand the base "acme-os" to add additional privilege levels or to change the "Failed when contains" list or something like that.

so im wondering if the base platform could be "fortigate-fortios" then a variant for "vdom" ; in this case the base platform would not have a configuration level and you would just use send command(s), and the variant would have a configuration level that gets you into "config vdom" mode? but maybe this isn't still very nice since you'd kinda need the name of the vdom?

because we are talking scrapligo and not python we are a little more constrained with how much we can just kinda yolo some stuff :) in python you could write a community platform and that gets you the ability to override methods to basically make this behave way more like you want -- check out the fortigate python platform if you haven't already, Viktor did some cool stuff over there. perhaps an alternative option that gets you the ability to do sorta what Viktor has done is to have a scrapli-fortigate package that accepts a network driver as an argument and then does XYZ, so you could just have a generic driver and then wrap that with your package. maybe it could look like:

type FortiWrapper struct {
    *network.Driver
}

func (w *FortiWrapper) DoStuff() {
    w.SendCommand("foo")
}

func main() {
    p, err := platform.NewPlatform(
        "fortigate_fortios",
        "1.2.3.4",
    )
    if err != nil {
        panic(err)
    }

    d, err := p.GetNetworkDriver()
    if err != nil {
        panic(err)
    }

    // maybe second arg here is "true/false" for vdom, or you could do options or whatever of course
    // this thing could embed 
    fw := FortiWrapper{d}
    fw.SendConfigs("blah")
    fw.DoStuff()
}

This raises a bigger point that ive long wanted to address but just never have had enough of a need: how can we enable scrapli_community like extensions (like Viktor's linked above that adds some methods and overrides some others) in scrapligo. I'm sure there is an answer/way but I surely do not know what it is :D if you have thoughts I am all ears!

Last bits for now:

With regards to the interim prompt - It's not needed if I just create a prompt pattern that supports all scenarios like the following: [\w-]+ (?:([\w-]+)? )?[$#]. But by doing this I miss out on the SendConfig command and automatic deesclation, though not a major issue I was just wondering if there was any better way?

I would defo not add all the patterns to the prompt -- you can and it would/could work, but it will be hellaciously slow. this is the slowest part of scrapligo and I was actually tinkering this past weekend too see if there are any easy ways to make the prompt matching a bit more efficient. maybe more to come on that.

Maybe by having the library check for all prompts and detected that the privilege level has been escalated to configuration automatically, but since it knows what level it should be in it could send the deescalate command - Though this might be a lot of work for little gain.

So... this partly falls in line w/ the above comment too -- historically (for better or maybe worse??) we've only ever actually checked/asserted that we are at the correct priv level when a user calls AcquirePriv or calls that in a round about way by calling SendConfig(s). As things stand the "base" prompt pattern actually is always a compiled pattern of all privilege levels (part of why this is hellaciously slow/inefficient!) so we don't actually ever "fail" to find a prompt even if a user escalates/deescalates on their own outside of the "normal" flow of things. So I think doing what you propose actually is fairly trivial (just an acquirePriv call in send command to acquire the default desired privilege similar to what we do in send configs), but... im not sure I love it? Im not sure I have good reasons/thoughts about why at the moment though other than its for sure extra overhead in terms of time/commands to send/parse/etc.. I'll think on this one though and maybe come back with smarter words :p

Ok, sorry for the wall of text. im not actually sure that helped move things along but I think I should quit while im already behind... so let us know what ya think/if that helps at all!

patrickfnielsen commented 9 months ago

Thanks for the great feedback, it does help and I appreciate it!

I would prefer one platform, as I simply think it's cleaner - and because I dont always know if VDOM's are enabled or not before i connect.

I like the idea of the FortiWrapper - and for now - I think that is what I'm going to go with, as it will allow me to hide some of the VDOM handling away, and also overwrite SendConfigs to use SendCommands.

With regards to scrapli_community like extensions, I could definitely see the use case as well, I'm not 100% sure how we could do it in the best way though - I'll try and think a bit about it and get back to you!

One thing I did consider was to extend the network-on-open commands to be able to capture output, and store that for use by the driver later, though this could also be done by using the FortiWrapper you mentioned.

carlmontanari commented 9 months ago

nice! sounds like a path forward for now at least. ill go ahead and close this but feel free to reopen or hit us up on discord if you wanna pow wow more (network automation hangout or containerlab or srlinux servers)!