diff --git a/README.md b/README.md index a87a3b4..5408289 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ --- -boxen -- put your network operating systems in a box (or if you speak 🇩🇪, fight them! 🤣)! +boxen -- put your network operating systems in a box (or if you speak 🇩🇪, fight them! 🤣)! boxen is a cli tool written in Go that allows you to package your network operating systems neatly into little... boxes (container images) so they are easily portable, and, most importantly, so you @@ -39,6 +39,8 @@ Please note that this is a work in progress... especially the documentation! - vSRX (tested with 17.3R2.10) - Palo Alto - PA-VM (tested with 10.0.6) +- Checkpoint + - Cloudguard (tested with R81.10) Additional platforms can of course be added! diff --git a/boxen/assets/configs/checkpoint_cloudguard.template b/boxen/assets/configs/checkpoint_cloudguard.template new file mode 100644 index 0000000..dfca7b3 --- /dev/null +++ b/boxen/assets/configs/checkpoint_cloudguard.template @@ -0,0 +1,5 @@ +lock database override +set interface eth0 ipv4-address 10.0.0.15 subnet-mask 255.255.255.0 +set interface eth0 state on +set ipv6-state on +unlock database \ No newline at end of file diff --git a/boxen/assets/profiles/checkpoint_cloudguard.yaml b/boxen/assets/profiles/checkpoint_cloudguard.yaml new file mode 100644 index 0000000..eb75dca --- /dev/null +++ b/boxen/assets/profiles/checkpoint_cloudguard.yaml @@ -0,0 +1,31 @@ +--- +hardware: + memory: 8192 + acceleration: + - kvm + serial_port_count: 1 + nic_type: virtio-net-pci + nic_count: 8 + nic_per_bus: 26 +advanced: + cpu: + emulation: host + cores: 4 +tcp_nat_ports: + - 22 + - 23 + - 257 + - 443 + - 830 + - 4434 + - 8211 + - 18190 + - 18191 + - 18192 + - 18210 + - 18211 + - 18221 + - 18264 + - 19009 +udp_nat_ports: + - 161 diff --git a/boxen/instance/qemulaunchcmd.go b/boxen/instance/qemulaunchcmd.go index 9efc1f0..4a36097 100644 --- a/boxen/instance/qemulaunchcmd.go +++ b/boxen/instance/qemulaunchcmd.go @@ -123,17 +123,23 @@ func (i *Qemu) launchCmdCPU() []string { cpuCmd = append(cpuCmd, []string{"-cpu", c.Emulation}...) } - if c.Cores != 0 && c.Threads != 0 && c.Sockets != 0 { + if c.Cores != 0 { if len(cpuCmd) == 0 { cpuCmd = append(cpuCmd, []string{"-cpu", "max"}...) } - cpuCmd = append( - cpuCmd, - []string{ - "-smp", - fmt.Sprintf("cores=%d,threads=%d,sockets=%d", c.Cores, c.Threads, c.Sockets), - }...) + if c.Threads != 0 && c.Sockets != 0 { + cpuCmd = append( + cpuCmd, + []string{ + "-smp", + fmt.Sprintf("cores=%d,threads=%d,sockets=%d", c.Cores, c.Threads, c.Sockets), + }...) + } else if c.Threads == 0 && c.Sockets == 0 { + cpuCmd = append( + cpuCmd, + []string{"-smp", fmt.Sprint(c.Cores)}...) + } } return cpuCmd diff --git a/boxen/platforms/checkpoint_cloudguard.go b/boxen/platforms/checkpoint_cloudguard.go new file mode 100644 index 0000000..8b99cd2 --- /dev/null +++ b/boxen/platforms/checkpoint_cloudguard.go @@ -0,0 +1,220 @@ +package platforms + +import ( + "fmt" + "time" + + sopoptions "github.com/scrapli/scrapligo/driver/opoptions" + + "github.com/carlmontanari/boxen/boxen/instance" +) + +const ( + CheckpointCloudguardDefaultUser = "admin" + CheckpointCloudguardDefaultPass = "admin" + + CheckpointCloudguardScrapliPlatform = "https://gist.githubusercontent.com/hellt/1eee1024bc1cb3121aaeac199d48663a/raw/07caf0b024802da2dbb6fe17dbabcb26231b8cb6/checkpoint_cloudguard.yaml" // nolint:lll + + checkpointCloudGuardDefaultBootTime = 720 +) + +type CheckpointCloudguard struct { + *instance.Qemu + *ScrapliConsole +} + +func (p *CheckpointCloudguard) Package( + _, _ string, +) (packageFiles, runFiles []string, err error) { + return nil, nil, err +} + +func (p *CheckpointCloudguard) Install(opts ...instance.Option) error { // nolint:dupl + p.Loggers.Base.Info("install requested") + + a, opts, err := setInstallArgs(opts...) + if err != nil { + return err + } + + c := make(chan error, 1) + stop := make(chan bool, 1) + + go func() { //nolint:dupl + err = p.Qemu.Start(opts...) + if err != nil { + c <- err + } + + p.Loggers.Base.Debug("instance started, waiting for start ready state") + + err = p.startReady() + if err != nil { + p.Loggers.Base.Criticalf("error waiting for start ready state: %s\n", err) + + c <- err + } + + p.Loggers.Base.Debug("start ready state acquired, logging in") + + err = p.login( + &loginArgs{ + username: CheckpointCloudguardDefaultUser, + password: CheckpointCloudguardDefaultPass, + }, + ) + if err != nil { + c <- err + } + + p.Loggers.Base.Debug("log in complete") + + if a.configLines != nil { + p.Loggers.Base.Debug("install config lines provided, executing scrapligo on open") + + err = p.defOnOpen(p.c) + if err != nil { + p.Loggers.Base.Criticalf("error running scrapligo on open: %s\n", err) + + c <- err + } + + err = p.Config(a.configLines) + if err != nil { + p.Loggers.Base.Criticalf("error sending install config lines: %s\n", err) + + c <- err + } + } + + p.Loggers.Base.Debug("initial installation complete") + + err = p.SaveConfig() + if err != nil { + p.Loggers.Base.Criticalf("error saving config: %s\n", err) + + c <- err + } + + // small delay ensuring config is saved nicely, without this extra sleep things just seem to + // not actually "save" despite the "save complete" or whatever output. + time.Sleep(5 * time.Second) // nolint:gomnd + + c <- nil + stop <- true + }() + + go p.WatchMainProc(c, stop) + + err = <-c + if err != nil { + return err + } + + p.Loggers.Base.Info("install complete, stopping instance") + + return p.Stop(opts...) +} + +func (p *CheckpointCloudguard) Start(opts ...instance.Option) error { + p.Loggers.Base.Info("start platform instance requested") + + a, opts, err := setStartArgs(opts...) + if err != nil { + return err + } + + err = p.Qemu.Start(opts...) + if err != nil { + return err + } + + err = p.startReady() + if err != nil { + p.Loggers.Base.Criticalf("error waiting for start ready state: %s\n", err) + + return err + } + + if !a.prepareConsole { + p.Loggers.Base.Info("prepare console not requested, starting instance complete") + + return nil + } + + err = p.login( + &loginArgs{ + username: CheckpointCloudguardDefaultUser, + password: CheckpointCloudguardDefaultPass, + }, + ) + if err != nil { + return err + } + + err = p.defOnOpen(p.c) + if err != nil { + return err + } + + p.Loggers.Base.Info("starting platform instance complete") + + return nil +} + +func (p *CheckpointCloudguard) startReady() error { + // openRetry doesn't do auth and doesn't call onOpen as it is set to nil somewhere before this + err := p.openRetry() + if err != nil { + return err + } + + err = p.readUntil( + []byte("This system is for authorized use only"), + getPlatformBootTimeout(PlatformTypeCheckpointCloudguard), + ) + + return err +} + +func (p *CheckpointCloudguard) SaveConfig() error { + p.Loggers.Base.Info("save config requested") + + _, err := p.c.SendCommand( + "save config", + sopoptions.WithTimeoutOps( + time.Duration(getPlatformSaveTimeout(PlatformTypeCheckpointCloudguard))*time.Second, + ), + ) + + return err +} + +func (p *CheckpointCloudguard) SetUserPass(usr, pwd string) error { + if usr == CheckpointCloudguardDefaultPass && pwd == CheckpointCloudguardDefaultPass { + p.Loggers.Base.Info("skipping user creation, since credentials match defaults for platform") + return nil + } + + p.Loggers.Base.Infof("set user/password for user '%s' requested", usr) + + return p.Config([]string{ + fmt.Sprintf( + "add user %s uid 0 homedir /home/%s", + usr, + usr), + fmt.Sprintf( + "add rba user %s roles adminRole", + usr), + fmt.Sprintf( + "set user %s newpass %s", + usr, + pwd), + }) +} + +func (p *CheckpointCloudguard) SetHostname(h string) error { + p.Loggers.Base.Infof("set hostname '%s' requested", h) + + return p.Config([]string{fmt.Sprintf("set hostname %s", h)}) +} diff --git a/boxen/platforms/console.go b/boxen/platforms/console.go index b855a2b..81ed4c7 100644 --- a/boxen/platforms/console.go +++ b/boxen/platforms/console.go @@ -82,7 +82,7 @@ func NewScrapliConsole( } con := &ScrapliConsole{ - pT: scrapliPlatform, + pT: p.GetPlatformType(), c: c, defOnOpen: c.OnOpen, logger: l.Base, diff --git a/boxen/platforms/constants.go b/boxen/platforms/constants.go index b30df36..b8c920c 100644 --- a/boxen/platforms/constants.go +++ b/boxen/platforms/constants.go @@ -6,22 +6,25 @@ const ( VendorJuniper = "juniper" VendorPaloAlto = "paloalto" VendorIPInfusion = "ipinfusion" + VendorCheckpoint = "checkpoint" - PlatformAristaVeos = "veos" - PlatformCiscoCsr1000v = "csr1000v" - PlatformCiscoXrv9k = "xrv9k" - PlatformCiscoN9kv = "n9kv" - PlatformJuniperVsrx = "vsrx" - PlatformPaloAltoPanos = "panos" - PlatformIPInfusionOcNOS = "ocnos" + PlatformAristaVeos = "veos" + PlatformCiscoCsr1000v = "csr1000v" + PlatformCiscoXrv9k = "xrv9k" + PlatformCiscoN9kv = "n9kv" + PlatformJuniperVsrx = "vsrx" + PlatformPaloAltoPanos = "panos" + PlatformIPInfusionOcNOS = "ocnos" + PlatformCheckpointCloudguard = "cloudguard" - PlatformTypeAristaVeos = "arista_veos" - PlatformTypeCiscoCsr1000v = "cisco_csr1000v" - PlatformTypeCiscoXrv9k = "cisco_xrv9k" - PlatformTypeCiscoN9kv = "cisco_n9kv" - PlatformTypeJuniperVsrx = "juniper_vsrx" - PlatformTypePaloAltoPanos = "paloalto_panos" - PlatformTypeIPInfusionOcNOS = "ipinfusion_ocnos" + PlatformTypeAristaVeos = "arista_veos" + PlatformTypeCiscoCsr1000v = "cisco_csr1000v" + PlatformTypeCiscoXrv9k = "cisco_xrv9k" + PlatformTypeCiscoN9kv = "cisco_n9kv" + PlatformTypeJuniperVsrx = "juniper_vsrx" + PlatformTypePaloAltoPanos = "paloalto_panos" + PlatformTypeIPInfusionOcNOS = "ipinfusion_ocnos" + PlatformTypeCheckpointCloudguard = "checkpoint_cloudguard" NicE1000 = "e1000" NicVirtio = "virtio-net-pci" diff --git a/boxen/platforms/factory.go b/boxen/platforms/factory.go index 28251f4..658cc5e 100644 --- a/boxen/platforms/factory.go +++ b/boxen/platforms/factory.go @@ -2,6 +2,7 @@ package platforms import ( "fmt" + "os" soptions "github.com/scrapli/scrapligo/driver/options" @@ -37,6 +38,10 @@ func GetPlatformType(v, p string) string { if p == PlatformIPInfusionOcNOS { return PlatformTypeIPInfusionOcNOS } + case VendorCheckpoint: + if p == PlatformCheckpointCloudguard { + return PlatformTypeCheckpointCloudguard + } } return "" @@ -58,6 +63,8 @@ func GetPlatformEmptyStruct(pT string) (Platform, error) { return &PaloAltoPanos{}, nil case PlatformTypeIPInfusionOcNOS: return &IPInfusionOcNOS{}, nil + case PlatformTypeCheckpointCloudguard: + return &CheckpointCloudguard{}, nil } return nil, fmt.Errorf( @@ -66,6 +73,38 @@ func GetPlatformEmptyStruct(pT string) (Platform, error) { ) } +// GetPlatformScrapliDefinition sets the scrapli platform definition to a value +// of the BOXEN_SCRAPLI_PLATFORM_DEFINITION env var or to a default string value. +func GetPlatformScrapliDefinition(p string) string { + scrapliPlatform := os.Getenv("BOXEN_SCRAPLI_PLATFORM_DEFINITION") + if scrapliPlatform != "" { + return scrapliPlatform + } + + // retrieve default scrapli platform url/name + // when env var is not set + switch p { + case PlatformTypeAristaVeos: + return AristaVeosScrapliPlatform + case PlatformTypeCiscoCsr1000v: + return CiscoCsr1000vScrapliPlatform + case PlatformTypeCiscoXrv9k: + return CiscoXrv9kScrapliPlatform + case PlatformTypeCiscoN9kv: + return CiscoN9kvScrapliPlatform + case PlatformTypeJuniperVsrx: + return JuniperVsrxScrapliPlatform + case PlatformTypePaloAltoPanos: + return PaloAltoPanosScrapliPlatform + case PlatformTypeIPInfusionOcNOS: + return IPInfusionOcNOSScrapliPlatform + case PlatformTypeCheckpointCloudguard: + return CheckpointCloudguardScrapliPlatform + } + + return "" +} + func NewPlatformFromConfig( //nolint:funlen n string, c *config.Config, @@ -83,10 +122,12 @@ func NewPlatformFromConfig( //nolint:funlen var con *ScrapliConsole + scrapliPlatform := GetPlatformScrapliDefinition(pT) + switch pT { case PlatformTypeAristaVeos: con, err = NewScrapliConsole( - AristaVeosScrapliPlatform, + scrapliPlatform, q.Hardware.SerialPorts[0], q.Credentials.Username, q.Credentials.Password, @@ -99,7 +140,7 @@ func NewPlatformFromConfig( //nolint:funlen } case PlatformTypeCiscoCsr1000v: con, err = NewScrapliConsole( - CiscoCsr1000vScrapliPlatform, + scrapliPlatform, q.Hardware.SerialPorts[0], q.Credentials.Username, q.Credentials.Password, @@ -112,7 +153,7 @@ func NewPlatformFromConfig( //nolint:funlen } case PlatformTypeCiscoXrv9k: con, err = NewScrapliConsole( - CiscoXrv9kScrapliPlatform, + scrapliPlatform, q.Hardware.SerialPorts[0], q.Credentials.Username, q.Credentials.Password, @@ -126,7 +167,7 @@ func NewPlatformFromConfig( //nolint:funlen } case PlatformTypeCiscoN9kv: con, err = NewScrapliConsole( - CiscoN9kvScrapliPlatform, + scrapliPlatform, q.Hardware.SerialPorts[0], q.Credentials.Username, q.Credentials.Password, @@ -140,7 +181,7 @@ func NewPlatformFromConfig( //nolint:funlen } case PlatformTypeJuniperVsrx: con, err = NewScrapliConsole( - JuniperVsrxScrapliPlatform, + scrapliPlatform, q.Hardware.SerialPorts[0], q.Credentials.Username, q.Credentials.Password, @@ -153,7 +194,7 @@ func NewPlatformFromConfig( //nolint:funlen } case PlatformTypePaloAltoPanos: con, err = NewScrapliConsole( - PaloAltoPanosScrapliPlatform, + scrapliPlatform, q.Hardware.SerialPorts[0], q.Credentials.Username, q.Credentials.Password, @@ -167,7 +208,7 @@ func NewPlatformFromConfig( //nolint:funlen } case PlatformTypeIPInfusionOcNOS: con, err = NewScrapliConsole( - IPInfusionOcNOSScrapliPlatform, + scrapliPlatform, q.Hardware.SerialPorts[0], q.Credentials.Username, q.Credentials.Password, @@ -179,6 +220,20 @@ func NewPlatformFromConfig( //nolint:funlen Qemu: q, ScrapliConsole: con, } + case PlatformTypeCheckpointCloudguard: + con, err = NewScrapliConsole( + scrapliPlatform, + q.Hardware.SerialPorts[0], + q.Credentials.Username, + q.Credentials.Password, + l, + soptions.WithReturnChar("\r"), + ) + + p = &CheckpointCloudguard{ + Qemu: q, + ScrapliConsole: con, + } default: return nil, fmt.Errorf("%w: scrapligo driver is not found for %q platform", util.ErrAllocationError, pT) diff --git a/boxen/platforms/ipinfusion_ocnos.go b/boxen/platforms/ipinfusion_ocnos.go index a0e2c83..8c05013 100644 --- a/boxen/platforms/ipinfusion_ocnos.go +++ b/boxen/platforms/ipinfusion_ocnos.go @@ -26,7 +26,7 @@ func (p *IPInfusionOcNOS) Package( return nil, nil, err } -func (p *IPInfusionOcNOS) Install(opts ...instance.Option) error { +func (p *IPInfusionOcNOS) Install(opts ...instance.Option) error { // nolint:dupl p.Loggers.Base.Info("install requested") a, opts, err := setInstallArgs(opts...) diff --git a/boxen/platforms/timeouts.go b/boxen/platforms/timeouts.go index eb81b01..d59b930 100644 --- a/boxen/platforms/timeouts.go +++ b/boxen/platforms/timeouts.go @@ -23,6 +23,8 @@ func getPlatformBootTimeout(pT string) int { t = ciscoXrv9kDefaultBootTime case PlatformTypePaloAltoPanos: t = paloAltoPanosDefaultBootTime + case PlatformTypeCheckpointCloudguard: + t = checkpointCloudGuardDefaultBootTime default: t = DefaultBootTime } diff --git a/go.mod b/go.mod index 4e87aae..9c81637 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.17 require ( github.com/google/uuid v1.3.0 - github.com/scrapli/scrapligo v1.0.0 + github.com/scrapli/scrapligo v1.1.0 github.com/scrapli/scrapligocfg v1.0.0 github.com/urfave/cli/v2 v2.3.0 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 diff --git a/go.sum b/go.sum index f7e4aa6..3f82aa0 100644 --- a/go.sum +++ b/go.sum @@ -23,23 +23,9 @@ github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBO github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/scrapli/scrapligo v0.1.4-0.20220628200407-3403e187b4af/go.mod h1:7eQKtz7zKeJ8JUhdHCw+Bv+q58Dd1cBKD50phWg2VsA= -github.com/scrapli/scrapligo v0.1.4-0.20220629193817-80c964e0e358 h1:0x9j1AxaNqcLPfJ8XUR2N0FNTAhiN+3DNCW8GI0zXYc= -github.com/scrapli/scrapligo v0.1.4-0.20220629193817-80c964e0e358/go.mod h1:jvRMdb90MNnswMiku8UNXj8JZaOIPhwhcqqFwr9qeoY= -github.com/scrapli/scrapligo v0.1.4-0.20220630193827-b2cf12687330 h1:+rFbpSao0Sy6okVDCPzqMCTBarYqVz71ZLzHNUx2gec= -github.com/scrapli/scrapligo v0.1.4-0.20220630193827-b2cf12687330/go.mod h1:jvRMdb90MNnswMiku8UNXj8JZaOIPhwhcqqFwr9qeoY= -github.com/scrapli/scrapligo v0.1.4-0.20220630194205-64cbf99b8c3b h1:xHlI8NLSJ6X+U/QxvAVniB4qdkaERXUAXeU/5KOCJzc= -github.com/scrapli/scrapligo v0.1.4-0.20220630194205-64cbf99b8c3b/go.mod h1:jvRMdb90MNnswMiku8UNXj8JZaOIPhwhcqqFwr9qeoY= -github.com/scrapli/scrapligo v0.1.4-0.20220630214823-34a4338bd1b2 h1:Cqt0+F1Wr957vJtzk4E4oN8qBAFZ/9MZ5PdFHkNRH28= -github.com/scrapli/scrapligo v0.1.4-0.20220630214823-34a4338bd1b2/go.mod h1:jvRMdb90MNnswMiku8UNXj8JZaOIPhwhcqqFwr9qeoY= -github.com/scrapli/scrapligo v1.0.0 h1:UQru4yyoAvU0tdpfoZ8gAp/rdT5XQ6GRg41xkOdF2A4= github.com/scrapli/scrapligo v1.0.0/go.mod h1:jvRMdb90MNnswMiku8UNXj8JZaOIPhwhcqqFwr9qeoY= -github.com/scrapli/scrapligocfg v0.0.0-20220628205358-4ca0f262a07f h1:iFIeNid4SFTEsxo7mAm68NauShw3NJhIjHJzYLjhReI= -github.com/scrapli/scrapligocfg v0.0.0-20220628205358-4ca0f262a07f/go.mod h1:FDVAo2U0AlgzW8by6wD0tjjR80coQjz5Yb+aYfyaUP4= -github.com/scrapli/scrapligocfg v0.0.0-20220630194834-3d2110c1b17e h1:gsrNJGBuIIoIm/FhlTkCgLQAnsZs3e47nH1vtTk/Y5Q= -github.com/scrapli/scrapligocfg v0.0.0-20220630194834-3d2110c1b17e/go.mod h1:O3xAZAfG9isKRI0X/izI+uZ0h+rvttsbq7IYeXj7RV8= -github.com/scrapli/scrapligocfg v0.0.0-20220630215039-a5be7fb902c0 h1:l6Z9dgShZjUB/APl6WAdV+VBlaxbmG0CgohDncNEdxY= -github.com/scrapli/scrapligocfg v0.0.0-20220630215039-a5be7fb902c0/go.mod h1:D3TlU5LWrPbC0oCCQVaQl3XjZF8UNB2DiP0FHHun4oQ= +github.com/scrapli/scrapligo v1.1.0 h1:KjCam57kIV2rlxAQg/J1G7v/xgRHvpJF+Gjz+LXhQaI= +github.com/scrapli/scrapligo v1.1.0/go.mod h1:jvRMdb90MNnswMiku8UNXj8JZaOIPhwhcqqFwr9qeoY= github.com/scrapli/scrapligocfg v1.0.0 h1:540SuGqqM6rKN87SLCfR54IageQ6s3a/ZOycGRgbbak= github.com/scrapli/scrapligocfg v1.0.0/go.mod h1:9+6k9dQeIqEZEg6EK5YXEjuVb7h+nvvel26CY1RGjy4= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=