diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 00000000..48b83c93 --- /dev/null +++ b/cli/README.md @@ -0,0 +1,2 @@ +# What is this? +This is a CLI to manipulate files in the terraform repo and commit and push them diff --git a/cli/git.go b/cli/git.go new file mode 100644 index 00000000..eefd721a --- /dev/null +++ b/cli/git.go @@ -0,0 +1,50 @@ +package main + +import ( + "os" + + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/memfs" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/transport/http" + memory "github.com/go-git/go-git/v5/storage/memory" + "github.com/golang/glog" + "github.com/pkg/errors" +) + +const ( + repository = "https://github.com/ViktorBarzin/infra" +) + +var ( + gitUser = os.Getenv("GIT_USER") + gitToken = os.Getenv("GIT_TOKEN") +) + +type GitFS struct { + repo *git.Repository + fs billy.Filesystem + auth *http.BasicAuth +} + +func NewGitFS(repoURL string) (*GitFS, error) { + glog.Infof("initializing new git fs from repo url: %s", repoURL) + auth := &http.BasicAuth{ + Username: gitUser, + Password: gitToken, + } + storer := memory.NewStorage() + + r, err := git.Clone(storer, g.fs, &git.CloneOptions{ + URL: repository, + Auth: auth, + }) + if err != nil { + return nil, errors.Wrapf(err, "failed to clone repo from repo url '%s'", repoURL) + } + return &GitFS{repo: r, fs: memfs.New(), auth: auth}, nil +} + +func (g *GitFS) Push() error { + return g.repo.Push(&git.PushOptions{Auth: g.auth}) +} diff --git a/cli/go.mod b/cli/go.mod new file mode 100644 index 00000000..db7194c2 --- /dev/null +++ b/cli/go.mod @@ -0,0 +1,10 @@ +module viktorbarzin/infra/cli + +go 1.16 + +require ( + github.com/go-git/go-billy/v5 v5.0.0 // indirect + github.com/go-git/go-git/v5 v5.2.0 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect + github.com/pkg/errors v0.9.1 // indirect +) diff --git a/cli/go.sum b/cli/go.sum new file mode 100644 index 00000000..e9884f85 --- /dev/null +++ b/cli/go.sum @@ -0,0 +1,66 @@ +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM= +github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI= +github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= +github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/cli/main.go b/cli/main.go new file mode 100644 index 00000000..cc853315 --- /dev/null +++ b/cli/main.go @@ -0,0 +1,94 @@ +package main + +import ( + "flag" + "fmt" + + "github.com/go-git/go-git/v5" + "github.com/golang/glog" + "github.com/pkg/errors" +) + +const ( + useCaseFlagName = "use-case" + repoRootFlagName = "repo-root" +) + +var ( + validUseCases = []string{"setup-vpn"} +) + +func main() { + err := run() + if err != nil { + glog.Fatalf("run failed: %s", err.Error()) + } +} + +func run() error { + useCase := flag.String(useCaseFlagName, "", fmt.Sprintf("Use case to run. Available use cases are: %+v", validUseCases)) + // repoRootParam := flag.String(repoRootFlagName, "", fmt.Sprintf("Path to the root of the infra repository.")) + + // VPN flags + vpnClientName := flag.String(vpnClientNameFlagName, "", fmt.Sprintf("Friendly VPN user name.")) + vpnClientPubKey := flag.String(vpnClientPubKeyFlagName, "", fmt.Sprintf("VPN client public key.")) + + flag.Set("logtostderr", "true") + flag.Set("stderrthreshold", "WARNING") + flag.Set("v", "2") + flag.Parse() + + // if *repoRootParam == "" { + // return fmt.Errorf("'-%s' flag must not be empty", repoRootFlagName) + // } + if *useCase == "" { + return fmt.Errorf("'-%s' flag must not be empty", useCaseFlagName) + } + // repoRoot, err := filepath.Abs(*repoRootParam) + // if err != nil { + // return errors.Wrapf(err, "failed to create absolute path from %s", repoRoot) + // } + + glog.Infof("Use case is: %s", *useCase) + // glog.Infof("Repo root is: %s", repoRoot) + + gitFs, err := NewGitFS(repository) + if err != nil { + return errors.Wrapf(err, "failed to initialize git fs") + } + worktree, err := gitFs.repo.Worktree() + if err != nil { + return errors.Wrapf(err, "failed to get worktree") + } + + switch *useCase { + case vpnUseCaseFlagName: + // get last used ip and increment + lastIP, err := getAndUpdateIP(gitFs, vpnLastIPConfFileRelative) + if err != nil { + return errors.Wrapf(err, "failed to get valid last ip from file %s", vpnLastIPConfFileRelative) + } + // insert new vpn client config + err = addVPNClient(gitFs, *vpnClientName, *vpnClientPubKey, vpnClientsConfFileRelative, lastIP) + if err != nil { + return errors.Wrapf(err, "failed to add vpn client") + } + // commit changes + if _, err = worktree.Commit("Added new VPN client config (still testing) [CI SKIP]", &git.CommitOptions{All: true}); err != nil { + return errors.Wrapf(err, "failed to commit") + } + default: + err = errors.New(fmt.Sprintf("unsupported use case: %s", *useCase)) + } + if err != nil { + return err + } + if err = gitFs.Push(); err != nil { + return errors.Wrapf(err, "failed to push changes") + } + + // err = gitRepo() + // glog.Infof(err.Error()) + + return nil +} diff --git a/cli/vpn.go b/cli/vpn.go new file mode 100644 index 00000000..28b510a9 --- /dev/null +++ b/cli/vpn.go @@ -0,0 +1,99 @@ +package main + +import ( + "fmt" + "io/ioutil" + "net" + "os" + "strings" + + "github.com/golang/glog" + "github.com/pkg/errors" +) + +const ( + vpnUseCaseFlagName = "vpn" + vpnClientNameFlagName = "vpn-client-name" + vpnClientPubKeyFlagName = "vpn-pub-key" + vpnClientsConfFileRelative = "modules/kubernetes/wireguard/extra/clients.conf" + vpnLastIPConfFileRelative = "modules/kubernetes/wireguard/extra/last_ip.txt" +) + +// addVPNClient inserts new client config +func addVPNClient(gitFs *GitFS, clientName, publicKey, clientsConfPath, ip string) error { + if clientName == "" { + return fmt.Errorf("client name must not be empty when creating a new vpn config") + } + if publicKey == "" { + return fmt.Errorf("public key cannot be empty when creating new vpn config") + } + contents := "[Peer]\n# friendly_name = " + clientName + "\nPublicKey = " + publicKey + "\nAllowedIPs = " + ip + "\n\n" + glog.Infof("adding the following config: \n%s", contents) + f, err := gitFs.fs.OpenFile(clientsConfPath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + return errors.Wrapf(err, "failed to open client configs file to add new vpn client") + } + defer f.Close() + + if _, err = f.Write([]byte(contents)); err != nil { + return errors.Wrapf(err, "failed to write config to file") + } + + glog.Infof("successfully added new vpn client config for %s", clientName) + return nil +} + +func incrementIP(origIP, cidr string) (string, error) { + ip := net.ParseIP(origIP) + _, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + return origIP, err + } + for i := len(ip) - 1; i >= 0; i-- { + ip[i]++ + if ip[i] != 0 { + break + } + } + if !ipNet.Contains(ip) { + return origIP, errors.New("overflowed CIDR while incrementing IP") + } + return ip.String(), nil +} + +// getAndUpdateIP Reads `fileName`, tries to get the ip, increments it, tries to write it back and returns the new address +func getAndUpdateIP(gitFs *GitFS, fileName string) (string, error) { + bytes, err := ioutil.ReadFile(fileName) + if err != nil { + return "", errors.Wrapf(err, "filed to read file %s", fileName) + } + errPrefix := "file has incorrect format: " + content := strings.TrimSpace(string(bytes)) + lines := strings.Split(content, "\n") + if len(lines) != 1 { + return "", fmt.Errorf(errPrefix + fmt.Sprintf("expected 1 line got %d", len(lines))) + } + lineSplit := strings.Split(lines[0], " ") + if len(lineSplit) < 1 { + return "", fmt.Errorf("expected non empty line") + } + ipcidr := strings.Split(lineSplit[len(lineSplit)-1], "/") + ipAddr := ipcidr[0] + cidr := ipcidr[1] + incrementedIP, err := incrementIP(ipAddr, strings.Join(ipcidr, "/")) + if err != nil { + return "", errors.Wrapf(err, "failed to increment ip for string '%s'", ipcidr) + } + + // Write back updated ip + fileContents := fmt.Sprintf("# DO NOT MANUALLY EDIT THIS LINE. Last IP: %s", incrementedIP+"/"+cidr) + f, err := gitFs.fs.OpenFile(fileName, os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + return "", errors.Wrapf(err, "failed to open file %s for writing", fileName) + } + if _, err = f.Write([]byte(fileContents)); err != nil { + return "", errors.Wrapf(err, "failed to write back new ip to file %s contents %s", fileName, fileContents) + } + glog.Infof("new ip: %s", incrementedIP) + return incrementedIP + "/32", nil +} diff --git a/main.tf b/main.tf index de29f032..897cad09 100644 --- a/main.tf +++ b/main.tf @@ -35,6 +35,8 @@ variable "oauth_client_secret" {} variable "webhook_handler_fb_verify_token" {} variable "webhook_handler_fb_page_token" {} variable "webhook_handler_fb_app_secret" {} +variable "webhook_handler_git_user" {} +variable "webhook_handler_git_token" {} variable "ansible_prefix" { default = "ANSIBLE_VAULT_PASSWORD_FILE=~/.ansible/vault_pass.txt ansible-playbook -i playbook/hosts.yaml playbook/linux.yml -t linux/initial_setup" @@ -187,6 +189,8 @@ module "kubernetes_cluster" { webhook_handler_fb_verify_token = var.webhook_handler_fb_verify_token webhook_handler_fb_page_token = var.webhook_handler_fb_page_token webhook_handler_fb_app_secret = var.webhook_handler_fb_app_secret + webhook_handler_git_user = var.webhook_handler_git_user + webhook_handler_git_token = var.webhook_handler_git_token wireguard_wg_0_conf = var.wireguard_wg_0_conf wireguard_wg_0_key = var.wireguard_wg_0_key diff --git a/modules/kubernetes/main.tf b/modules/kubernetes/main.tf index 89d29aea..01f80774 100644 --- a/modules/kubernetes/main.tf +++ b/modules/kubernetes/main.tf @@ -22,6 +22,8 @@ variable "oauth_client_secret" {} variable "webhook_handler_fb_verify_token" {} variable "webhook_handler_fb_page_token" {} variable "webhook_handler_fb_app_secret" {} +variable "webhook_handler_git_user" {} +variable "webhook_handler_git_token" {} resource "null_resource" "core_services" { # List all the core modules that must be provisioned first @@ -162,6 +164,8 @@ module "webhook_handler" { fb_verify_token = var.webhook_handler_fb_verify_token fb_page_token = var.webhook_handler_fb_page_token fb_app_secret = var.webhook_handler_fb_app_secret + git_user = var.webhook_handler_git_user + git_token = var.webhook_handler_git_token depends_on = [null_resource.core_services] } diff --git a/modules/kubernetes/webhook_handler/main.tf b/modules/kubernetes/webhook_handler/main.tf index d26a4e41..e82ca68c 100644 --- a/modules/kubernetes/webhook_handler/main.tf +++ b/modules/kubernetes/webhook_handler/main.tf @@ -4,6 +4,8 @@ variable "webhook_secret" {} variable "fb_verify_token" {} variable "fb_page_token" {} variable "fb_app_secret" {} +variable "git_user" {} +variable "git_token" {} resource "kubernetes_namespace" "webhook-handler" { metadata { @@ -105,6 +107,14 @@ resource "kubernetes_deployment" "webhook_handler" { name = "CONFIG" value = "./chatbot/config/viktorwebservices.yaml" } + env { + name = "GIT_USER" + value = var.git_user + } + env { + name = "GIT_TOKEN" + value = var.git_token + } } } }