package asaModels import ( "encoding/json" "fmt" "regexp" "strconv" "strings" "time" ) // ISASAs : Holds parsed output of a 'show isa sa' command type ISASAs struct { IKEv1 *IKEv1 IKEv2 struct { Total int Sessions map[string]IKEv2 // int = session-id } } // IKEv1 : Holds parameters on the IKEv1 tunnels type IKEv1 struct { Active int Rekey int Total int Peers map[string]Peer } // IKEv2 : Holds parameters for an IKEv2 tunnel type IKEv2 struct { ActiveTime time.Duration AuthSign string AuthVerify string ChildCount int Children []IKEv2Child DHGroup int Encryption string Hash string IKECount int Keysize int LifeTime time.Duration Role string SessionID int Status string TunnelID string TunnelIDLocal string TunnelIDRemote string TunnelStatus string } // MarshalJSON : Custom JSON marshaller for IKEv2 structure func (i IKEv2) MarshalJSON() ([]byte, error) { // This is so JSON output shows time.Duration's as an integer of seconds dest := struct { ActiveTime int AuthSign string AuthVerify string ChildCount int Children []IKEv2Child DHGroup int Encryption string Hash string IKECount int Keysize int LifeTime int Role string SessionID int Status string TunnelID string TunnelIDLocal string TunnelIDRemote string TunnelStatus string }{ AuthSign: i.AuthSign, AuthVerify: i.AuthVerify, ChildCount: i.ChildCount, Children: i.Children, DHGroup: i.DHGroup, Encryption: i.Encryption, Hash: i.Hash, IKECount: i.IKECount, Keysize: i.Keysize, Role: i.Role, SessionID: i.SessionID, Status: i.Status, TunnelID: i.TunnelID, TunnelIDLocal: i.TunnelIDLocal, TunnelIDRemote: i.TunnelIDRemote, TunnelStatus: i.TunnelStatus, ActiveTime: int(i.ActiveTime.Seconds()), LifeTime: int(i.LifeTime.Seconds()), } return json.Marshal(dest) } // MarshalJSON : Custom JSON unmarshaller for IKEv2 structure func (i *IKEv2) UnmarshalJSON(raw []byte) error { start := struct { ActiveTime int AuthSign string AuthVerify string ChildCount int Children []IKEv2Child DHGroup int Encryption string Hash string IKECount int Keysize int LifeTime int Role string SessionID int Status string TunnelID string TunnelIDLocal string TunnelIDRemote string TunnelStatus string }{} if err := json.Unmarshal(raw, &start); err != nil { return err } at, err := time.ParseDuration(fmt.Sprintf("%vs", start.ActiveTime)) if err != nil { return fmt.Errorf("error parsing active time to duration: %v", err) } i.ActiveTime = at lt, err := time.ParseDuration(fmt.Sprintf("%vs", start.LifeTime)) if err != nil { return fmt.Errorf("error parsing lifetime to duration: %v", err) } i.LifeTime = lt i.AuthSign = start.AuthSign i.AuthVerify = start.AuthVerify i.ChildCount = start.ChildCount i.Children = start.Children i.DHGroup = start.DHGroup i.Encryption = start.Encryption i.Hash = start.Hash i.IKECount = start.IKECount i.Keysize = start.Keysize i.Role = start.Role i.SessionID = start.SessionID i.Status = start.Status i.TunnelID = start.TunnelID i.TunnelIDLocal = start.TunnelIDLocal i.TunnelIDRemote = start.TunnelIDRemote i.TunnelStatus = start.TunnelStatus return nil } // IKEV2Child : Holds parameters for an IKEv2 child type IKEv2Child struct { ESPIn string ESPOut string LocalSelector string RemoteSelector string } // Peer : Holds parameters for an IKEv1 peer type Peer struct { Peer string ReKey string Role string State string Type string } // ParseISASAs : Parses raw output of 'show isa sa' in to an ISASAs structure func ParseISASAs(raw string) (ISASAs, error) { var a ISASAs // Variables for keeping track of our place in the config var curPeer string var curV2Session string var curChild int nextLineIsTunnel := false // Regex statements outside loop so they're only compiled once activeReg := regexp.MustCompile(`Active SA: (\d+)`) rekeyReg := regexp.MustCompile(`Rekey SA: (\d+) \(`) totalReg := regexp.MustCompile(`Total IKE SA: (\d+)`) peerReg := regexp.MustCompile(`(\d+)\s+IKE Peer: (.*)`) typeRole := regexp.MustCompile(`Type\s+: (.*)Role\s+: (.*)`) rekeyState := regexp.MustCompile(`Rekey\s+: (.*)State\s+: (\S+)`) v2Reg := regexp.MustCompile(`Session-id:(\d+).*Status:(\S+),.*IKE count:(\d+).*CHILD count:(\d+)`) v2Details := regexp.MustCompile(`Encr: (\S+), keysize: (\d+), Hash: (\S+), DH Grp:(\d+), Auth sign: (\S+), Auth verify: (\S+)`) v2Timers := regexp.MustCompile(`Life/Active Time: (\d+)/(\d+) (\S+)`) v2Child := regexp.MustCompile(`Child sa: local selector\s+(.*)`) v2ChildRemote := regexp.MustCompile(`remote selector\s+(.*)`) v2ChildESP := regexp.MustCompile(`ESP spi in/out:\s+(\S+)/(\S+)`) for _, line := range strings.Split(raw, "\n") { switch { case line == "IKEv1 SAs:": a.IKEv1 = &IKEv1{ Peers: make(map[string]Peer), } case v2Reg.MatchString(line): f := v2Reg.FindStringSubmatch(line) sess, err := strconv.Atoi(f[1]) if err != nil { return a, fmt.Errorf("error converting session-id to integer: %v", err) } ic, err := strconv.Atoi(f[3]) if err != nil { return a, fmt.Errorf("error converting ike count to integer: %v", err) } cc, err := strconv.Atoi(f[4]) if err != nil { return a, fmt.Errorf("error converting child count to integer: %v", err) } if a.IKEv2.Sessions == nil { a.IKEv2.Sessions = make(map[string]IKEv2) } a.IKEv2.Sessions[f[1]] = IKEv2{ SessionID: sess, Status: f[2], IKECount: ic, ChildCount: cc, } curV2Session = f[1] curChild = -1 case strings.HasPrefix(line, "Tunnel-id"): nextLineIsTunnel = true case nextLineIsTunnel: f := strings.Fields(line) thisPeer, _ := a.IKEv2.Sessions[curV2Session] a.IKEv2.Total++ thisPeer.TunnelID = f[0] thisPeer.TunnelIDLocal = f[1] thisPeer.TunnelIDRemote = f[2] thisPeer.TunnelStatus = f[3] thisPeer.Role = f[4] a.IKEv2.Sessions[curV2Session] = thisPeer nextLineIsTunnel = false case v2Details.MatchString(line): f := v2Details.FindStringSubmatch(line) thisSession, _ := a.IKEv2.Sessions[curV2Session] thisSession.Encryption = f[1] ks, err := strconv.Atoi(f[2]) if err != nil { return a, fmt.Errorf("error converting keysize to integer: %v", err) } thisSession.Keysize = ks thisSession.Hash = f[3] dh, err := strconv.Atoi(f[4]) if err != nil { return a, fmt.Errorf("error converting dh group to integer: %v", err) } thisSession.DHGroup = dh thisSession.AuthSign = f[5] thisSession.AuthVerify = f[6] a.IKEv2.Sessions[curV2Session] = thisSession case v2Child.MatchString(line): f := v2Child.FindStringSubmatch(line) thisSession, _ := a.IKEv2.Sessions[curV2Session] curChild++ thisSession.Children = append(thisSession.Children, IKEv2Child{ LocalSelector: f[1], }) a.IKEv2.Sessions[curV2Session] = thisSession case v2ChildRemote.MatchString(line): f := v2ChildRemote.FindStringSubmatch(line) thisSession, _ := a.IKEv2.Sessions[curV2Session] thisSession.Children[curChild].RemoteSelector = f[1] a.IKEv2.Sessions[curV2Session] = thisSession case v2ChildESP.MatchString(line): f := v2ChildESP.FindStringSubmatch(line) thisSession, _ := a.IKEv2.Sessions[curV2Session] thisSession.Children[curChild].ESPIn = f[1] thisSession.Children[curChild].ESPOut = f[2] a.IKEv2.Sessions[curV2Session] = thisSession case v2Timers.MatchString(line): f := v2Timers.FindStringSubmatch(line) thisSession, _ := a.IKEv2.Sessions[curV2Session] if f[3] != "sec" { return a, fmt.Errorf("expected 'sec' in lifetime/activetime but found '%s'", f[3]) } life, err := time.ParseDuration(fmt.Sprintf("%vs", f[1])) if err != nil { return a, fmt.Errorf("error converting lifetime to duration") } active, err := time.ParseDuration(fmt.Sprintf("%vs", f[2])) if err != nil { return a, fmt.Errorf("error converting active time to integer: %v", err) } thisSession.LifeTime = life thisSession.ActiveTime = active a.IKEv2.Sessions[curV2Session] = thisSession case activeReg.MatchString(line): f := activeReg.FindStringSubmatch(line) i, err := strconv.Atoi(f[1]) if err != nil { return a, err } a.IKEv1.Active = i case rekeyReg.MatchString(line): f := rekeyReg.FindStringSubmatch(line) i, err := strconv.Atoi(f[1]) if err != nil { return a, err } a.IKEv1.Rekey = i case totalReg.MatchString(line): f := totalReg.FindStringSubmatch(line) i, err := strconv.Atoi(f[1]) if err != nil { return a, err } a.IKEv1.Total = i case peerReg.MatchString(line): f := peerReg.FindStringSubmatch(line) curPeer = f[1] if _, exists := a.IKEv1.Peers[curPeer]; !exists { a.IKEv1.Peers[curPeer] = Peer{ Peer: strings.TrimSpace(f[2]), } } else { return a, fmt.Errorf("found a new peer line for %v but peer already exists in map, which it shouldn't", curPeer) } case typeRole.MatchString(line): f := typeRole.FindStringSubmatch(line) thisPeer, _ := a.IKEv1.Peers[curPeer] thisPeer.Type = strings.TrimSpace(f[1]) thisPeer.Role = strings.TrimSpace(f[2]) a.IKEv1.Peers[curPeer] = thisPeer case rekeyState.MatchString(line): f := rekeyState.FindStringSubmatch(line) thisPeer, _ := a.IKEv1.Peers[curPeer] thisPeer.ReKey = strings.TrimSpace(f[1]) thisPeer.State = strings.TrimSpace(f[2]) a.IKEv1.Peers[curPeer] = thisPeer } } return a, nil }