Skip to content

Commit

Permalink
Moved parse, diff, config of ASA to package cisco
Browse files Browse the repository at this point in the history
Try to reuse functionality for Cisco IOS
  • Loading branch information
hknutzen committed Dec 27, 2023
1 parent 3a94ab8 commit 9c1fdb4
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 284 deletions.
2 changes: 1 addition & 1 deletion go/cmd/drc-asa/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ import (
)

func main() {
os.Exit(device.Main(&asa.State{}))
os.Exit(device.Main(asa.Setup()))
}
121 changes: 121 additions & 0 deletions go/pkg/asa/cmd-info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package asa

// Description of commands that will be parsed.
// - $NAME matches name of command; only used in toplevel commands.
// - $SEQ matches a sequence number.
// - * matches one or more words at end of command.
// - " matches a string in douple quotes or a single word without double quotes.
// First word is used as prefix.
// This prefix may be referenced in other commands as $<prefix>.
// If multiple words are used as prefix, space is replaced by underscore.
//
// Special characters at beginning of line:
// <space>: Mark subcommands of previous command
// !: Matching command or subcommand will be ignored
// #: Comment that is ignored
var cmdInfo = `
# * may reference multiple $object-group, will be resolved later.
access-list $NAME standard *
access-list $NAME extended *
access-list $NAME remark *
object-group network $NAME
*
object-group service $NAME *
*
object-group service $NAME
*
object-group protocol $NAME
*
ip_local_pool $NAME *
crypto_ca_certificate_map $NAME $SEQ
subject-name *
extended-key-usage *
crypto_map $NAME $SEQ match address $access-list
crypto_map $NAME $SEQ ipsec-isakmp dynamic $crypto_dynamic-map
# * references one or more $crypto_ipsec_ikev1_transform-set
crypto_map $NAME $SEQ set ikev1 transform-set *
# * references one or more $crypto_ipsec_ikev2_ipsec-proposal
crypto_map $NAME $SEQ set ikev2 ipsec-proposal *
crypto_map $NAME $SEQ set nat-t-disable
crypto_map $NAME $SEQ set peer *
crypto_map $NAME $SEQ set pfs *
crypto_map $NAME $SEQ set pfs
crypto_map $NAME $SEQ set reverse-route
crypto_map $NAME $SEQ set security-association lifetime *
crypto_map $NAME $SEQ set trustpoint *
crypto_dynamic-map $NAME $SEQ match address $access-list
crypto_dynamic-map $NAME $SEQ ipsec-isakmp dynamic *
# * references one or more $crypto_ipsec_ikev1_transform-set
crypto_dynamic-map $NAME $SEQ set ikev1 transform-set *
# * references one or more $crypto_ipsec_ikev2_ipsec-proposal
crypto_dynamic-map $NAME $SEQ set ikev2 ipsec-proposal *
crypto_dynamic-map $NAME $SEQ set nat-t-disable
crypto_dynamic-map $NAME $SEQ set peer *
crypto_dynamic-map $NAME $SEQ set pfs *
# Default value 'group2' is not shown in config from device.
crypto_dynamic-map $NAME $SEQ set pfs
crypto_dynamic-map $NAME $SEQ set reverse-route
crypto_dynamic-map $NAME $SEQ set security-association lifetime *
crypto_ipsec_ikev1_transform-set $NAME *
crypto_ipsec_ikev2_ipsec-proposal $NAME
protocol esp encryption *
protocol esp integrity *
group-policy $NAME internal
group-policy $NAME attributes
vpn-filter value $access-list
split-tunnel-network-list value $access-list
address-pools value $ip_local_pool
!webvpn
*
# Are transferred manually, but references must be followed.
aaa-server $NAME protocol ldap
# Value of * is different from Netspoc and device:
# Device: aaa-server NAME (inside) host 1.2.3.4
# Device: aaa-server NAME (inside) host 5.6.7.8
# Netspoc: aaa-server NAME host X
aaa-server $NAME *
ldap-attribute-map $ldap_attribute-map
ldap_attribute-map $NAME
map-name memberOf Group-Policy
map-value memberOf " $group-policy
# Is anchor if $NAME is IP address
tunnel-group $NAME type *
tunnel-group $NAME general-attributes
default-group-policy $group-policy
authentication-server-group $aaa-server
*
tunnel-group $NAME ipsec-attributes
!ikev1 pre-shared-key *
!ikev2 local-authentication pre-shared-key *
!ikev2 remote-authentication pre-shared-key *
!isakmp keepalive *
*
tunnel-group $NAME webvpn-attributes
*
# Anchors
access-group $access-list global
access-group $access-list in *
access-group $access-list out *
# Is stored in lookup with different prefix "cryto map interface"
crypto_map $crypto_map interface *
username $NAME nopassword
username $NAME attributes
vpn-filter value $access-list
vpn-group-policy $group-policy
*
tunnel-group-map default-group $tunnel-group
tunnel-group-map $crypto_ca_certificate_map $SEQ $tunnel-group
webvpn
certificate-group-map $crypto_ca_certificate_map $SEQ $tunnel-group
# Other anchors, not referencing any command
route *
ipv6_route *
interface *
shutdown
nameif *
no_sysopt_connection_permit-vpn
`
105 changes: 7 additions & 98 deletions go/pkg/asa/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,23 @@ import (
"fmt"
"os"
"regexp"
"sort"
"strings"

"github.com/hknutzen/Netspoc-Approve/go/pkg/cisco"
"github.com/hknutzen/Netspoc-Approve/go/pkg/console"
"github.com/hknutzen/Netspoc-Approve/go/pkg/device"
"golang.org/x/exp/maps"
)

type State struct {
cisco.State
conn *console.Conn
a *ASAConfig
b *ASAConfig
changes changeList
errUnmanaged []error
subCmdOf string
}
type changeList []string

func (l *changeList) push(chg ...string) {
*l = append(*l, chg...)
func Setup() *State {
s := &State{}
s.SetupParser(cmdInfo)
return s
}

func (s *State) LoadDevice(
Expand Down Expand Up @@ -130,33 +127,10 @@ func (s *State) GetErrUnmanaged() []error {
return s.errUnmanaged
}

func (s *State) GetChanges(c1, c2 device.DeviceConfig) error {
s.a = c1.(*ASAConfig)
s.b = c2.(*ASAConfig)
if err := s.checkInterfaces(); err != nil {
return err
}
s.diffConfig()
return nil
}

func (s *State) HasChanges() bool {
return len(s.changes) != 0
}

func (s *State) ShowChanges() string {
var collect strings.Builder
for _, chg := range s.changes {
chg = strings.Replace(chg, "\n", "\\N ", 1)
fmt.Fprintln(&collect, chg)
}
return collect.String()
}

func (s *State) ApplyCommands(logFh *os.File) error {
s.conn.SetLogFH(logFh)
s.cmd("configure terminal")
for _, chg := range s.changes {
for _, chg := range s.Changes {
s.cmd(chg)
}
s.cmd("end")
Expand Down Expand Up @@ -224,68 +198,3 @@ func (s *State) CloseConnection() {
s.conn.Close()
}
}

func (s *State) checkInterfaces() error {

// Collect interfaces from Netspoc.
// These are defined implicitly by commands
// - "access-group $access-list in|out interface INTF".
// - "crypto map $crypto_map interface INTF"
// Ignore "access-group $access-list global".
getImplicitInterfaces := func(cfg *ASAConfig) map[string]bool {
m := make(map[string]bool)
for _, l := range cfg.lookup["access-group"] {
for _, c := range l {
tokens := strings.Fields(c.parsed)
if len(tokens) == 5 {
m[tokens[4]] = true
}
}
}
for _, l := range cfg.lookup["crypto map interface"] {
for _, c := range l {
tokens := strings.Fields(c.parsed)
m[tokens[4]] = true
}
}
return m
}
bIntf := getImplicitInterfaces(s.b)

// Collect and check named interfaces from device.
// Add implicit interfaces when comparing two Netspoc generated configs.
aIntf := getImplicitInterfaces(s.a)
for _, l := range s.a.lookup["interface"] {
for _, c := range l {
name := ""
shutdown := false
for _, sc := range c.sub {
tokens := strings.Fields(sc.parsed)
switch tokens[0] {
case "shutdown":
shutdown = true
case "nameif":
name = tokens[1]
}
}
if name != "" {
aIntf[name] = true
if !shutdown && !bIntf[name] {
device.Warning(
"Interface '%s' on device is not known by Netspoc", name)
}
}
}
}

// Check interfaces from Netspoc
bNames := maps.Keys(bIntf)
sort.Strings(bNames)
for _, name := range bNames {
if !aIntf[name] {
return fmt.Errorf(
"Interface '%s' from Netspoc not known on device", name)
}
}
return nil
}
8 changes: 4 additions & 4 deletions go/pkg/asa/config.go → go/pkg/cisco/config.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package asa
package cisco

import (
"fmt"
Expand All @@ -8,14 +8,14 @@ import (
"github.com/hknutzen/Netspoc-Approve/go/pkg/device"
)

func (cf *ASAConfig) CheckRulesFromRaw() error { return nil }
func (cf *Config) CheckRulesFromRaw() error { return nil }

// Check that non anchor commands from raw file are referenced by some
// anchor and are referenced only once.
var isReferenced map[*cmd]bool

func (a *ASAConfig) MergeSpoc(d device.DeviceConfig) device.DeviceConfig {
b := d.(*ASAConfig)
func (a *Config) MergeSpoc(d device.DeviceConfig) device.DeviceConfig {
b := d.(*Config)
lookup := a.lookup
for prefix := range b.lookup {
if lookup[prefix] == nil {
Expand Down
Loading

0 comments on commit 9c1fdb4

Please sign in to comment.