// Copyright 2017 flannel authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build !windows package ipsec import ( "fmt" "net" "os" "os/exec" "strings" "sync" "syscall" "time" "github.com/bronze1man/goStrongswanVici" "github.com/flannel-io/flannel/subnet" "golang.org/x/net/context" log "k8s.io/klog" ) type Uri struct { network, address string } type CharonIKEDaemon struct { viciUri Uri espProposal string ctx context.Context } func NewCharonIKEDaemon(ctx context.Context, wg *sync.WaitGroup, espProposal string) (*CharonIKEDaemon, error) { charon := &CharonIKEDaemon{ctx: ctx, espProposal: espProposal} addr := strings.Split("unix:///var/run/charon.vici", "://") charon.viciUri = Uri{addr[0], addr[1]} execPath, err := findExecPath() if err != nil { log.Errorf("Charon daemon not found: %v", err) return nil, err } cmd, err := charon.run(execPath) if err != nil { log.Errorf("Error starting charon daemon: %v", err) return nil, err } else { log.Info("Charon daemon started") } wg.Add(1) go func() { select { case <-ctx.Done(): cmd.Process.Signal(syscall.SIGTERM) cmd.Wait() log.Infof("Stopped charon daemon") wg.Done() } }() return charon, nil } func (charon *CharonIKEDaemon) getClient(wait bool) (client *goStrongswanVici.ClientConn, err error) { for { socket_conn, err := net.Dial(charon.viciUri.network, charon.viciUri.address) if err == nil { return goStrongswanVici.NewClientConn(socket_conn), nil } else { if wait { select { case <-charon.ctx.Done(): log.Error("Cancel waiting for charon") return nil, err default: log.Errorf("ClientConnection failed: %v", err) } log.Info("Retrying in a second ...") time.Sleep(time.Second) } else { return nil, err } } } } func (charon *CharonIKEDaemon) run(execPath string) (cmd *exec.Cmd, err error) { cmd = &exec.Cmd{ Path: execPath, SysProcAttr: &syscall.SysProcAttr{ Pdeathsig: syscall.SIGTERM, }, Stdout: os.Stdout, Stderr: os.Stderr, } err = cmd.Start() return } func (charon *CharonIKEDaemon) LoadSharedKey(remotePublicIP, password string) error { var err error var client *goStrongswanVici.ClientConn client, err = charon.getClient(true) if err != nil { log.Errorf("Failed to acquire Vici client: %v", err) return err } defer client.Close() sharedKey := &goStrongswanVici.Key{ Typ: "IKE", Data: password, Owners: []string{remotePublicIP}, } for { err = client.LoadShared(sharedKey) if err != nil { log.Errorf("Failed to load my key. Retrying. %v", err) time.Sleep(time.Second) } else { break } } log.Infof("Loaded shared key for: %v", remotePublicIP) return nil } func (charon *CharonIKEDaemon) LoadConnection(localLease, remoteLease *subnet.Lease, reqID, encap string) error { var err error var client *goStrongswanVici.ClientConn client, err = charon.getClient(true) if err != nil { log.Errorf("Failed to acquire Vici client: %s", err) return err } defer client.Close() childConfMap := make(map[string]goStrongswanVici.ChildSAConf) childSAConf := goStrongswanVici.ChildSAConf{ Local_ts: []string{localLease.Subnet.String()}, Remote_ts: []string{remoteLease.Subnet.String()}, ESPProposals: []string{charon.espProposal}, StartAction: "start", CloseAction: "trap", Mode: "tunnel", ReqID: reqID, // RekeyTime: rekeyTime, InstallPolicy: "no", } childSAConfName := formatChildSAConfName(localLease, remoteLease) childConfMap[childSAConfName] = childSAConf localAuthConf := goStrongswanVici.AuthConf{ AuthMethod: "psk", } remoteAuthConf := goStrongswanVici.AuthConf{ AuthMethod: "psk", } ikeConf := goStrongswanVici.IKEConf{ LocalAddrs: []string{localLease.Attrs.PublicIP.String()}, RemoteAddrs: []string{remoteLease.Attrs.PublicIP.String()}, Proposals: []string{"aes256-sha256-modp4096"}, Version: "2", KeyingTries: "0", //continues to retry LocalAuth: localAuthConf, RemoteAuth: remoteAuthConf, Children: childConfMap, Encap: encap, } ikeConfMap := make(map[string]goStrongswanVici.IKEConf) connectionName := formatConnectionName(localLease, remoteLease) ikeConfMap[connectionName] = ikeConf err = client.LoadConn(&ikeConfMap) if err != nil { return err } log.Infof("Loaded connection: %v", connectionName) return nil } func (charon *CharonIKEDaemon) UnloadCharonConnection(localLease, remoteLease *subnet.Lease) error { client, err := charon.getClient(false) if err != nil { log.Errorf("Failed to acquire Vici client: %s", err) return err } defer client.Close() connectionName := formatConnectionName(localLease, remoteLease) unloadConnRequest := &goStrongswanVici.UnloadConnRequest{ Name: connectionName, } err = client.UnloadConn(unloadConnRequest) if err != nil { return err } log.Infof("Unloaded connection: %v", connectionName) return nil } func formatConnectionName(localLease, remoteLease *subnet.Lease) string { return fmt.Sprintf("%s-%s-%s-%s", localLease.Attrs.PublicIP, localLease.Subnet, remoteLease.Subnet, remoteLease.Attrs.PublicIP) } func formatChildSAConfName(localLease, remoteLease *subnet.Lease) string { return fmt.Sprintf("%s-%s", localLease.Subnet, remoteLease.Subnet) } func findExecPath() (string, error) { // try well known charon paths paths := []string{ "charon", // PATH "/usr/lib/strongswan/charon", // alpine, arch, flannel container "/usr/lib/ipsec/charon", // debian/ubuntu "/usr/libexec/strongswan/charon", // centos/rhel "/usr/libexec/ipsec/charon", // opensuse/sles } for _, path := range paths { path, err := exec.LookPath(path) if err != nil { log.Warningf("No valid charon executable found at path %s: %v", path, err) continue } return path, nil } err := fmt.Errorf("No valid charon executable found at paths %v", paths) return "", err }