├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── backends ├── backend.go └── consul.go ├── config.go ├── go.mod ├── go.sum ├── ifconfig └── ifconfig.go ├── main.go ├── modd.conf ├── util └── util.go └── wireguard ├── config-template.go ├── interface.go ├── peer.go └── wireguard.go /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | vendor 3 | autowire 4 | specs 5 | *.zip 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.16.2-buster AS builder 2 | 3 | 4 | WORKDIR /go/src/github.com/geniousphp/autowire 5 | 6 | 7 | COPY go.mod go.sum ./ 8 | # Download all dependencies 9 | RUN go mod download 10 | 11 | 12 | COPY . . 13 | 14 | RUN CGO_ENABLED=0 GOOS=linux GARCH=amd64 go build -a -installsuffix cgo -o /usr/bin/autowire . 15 | 16 | CMD ["/usr/bin/autowire"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License, version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. “Contributor” 6 | 7 | means each individual or legal entity that creates, contributes to the 8 | creation of, or owns Covered Software. 9 | 10 | 1.2. “Contributor Version” 11 | 12 | means the combination of the Contributions of others (if any) used by a 13 | Contributor and that particular Contributor’s Contribution. 14 | 15 | 1.3. “Contribution” 16 | 17 | means Covered Software of a particular Contributor. 18 | 19 | 1.4. “Covered Software” 20 | 21 | means Source Code Form to which the initial Contributor has attached the 22 | notice in Exhibit A, the Executable Form of such Source Code Form, and 23 | Modifications of such Source Code Form, in each case including portions 24 | thereof. 25 | 26 | 1.5. “Incompatible With Secondary Licenses” 27 | means 28 | 29 | a. that the initial Contributor has attached the notice described in 30 | Exhibit B to the Covered Software; or 31 | 32 | b. that the Covered Software was made available under the terms of version 33 | 1.1 or earlier of the License, but not also under the terms of a 34 | Secondary License. 35 | 36 | 1.6. “Executable Form” 37 | 38 | means any form of the work other than Source Code Form. 39 | 40 | 1.7. “Larger Work” 41 | 42 | means a work that combines Covered Software with other material, in a separate 43 | file or files, that is not Covered Software. 44 | 45 | 1.8. “License” 46 | 47 | means this document. 48 | 49 | 1.9. “Licensable” 50 | 51 | means having the right to grant, to the maximum extent possible, whether at the 52 | time of the initial grant or subsequently, any and all of the rights conveyed by 53 | this License. 54 | 55 | 1.10. “Modifications” 56 | 57 | means any of the following: 58 | 59 | a. any file in Source Code Form that results from an addition to, deletion 60 | from, or modification of the contents of Covered Software; or 61 | 62 | b. any new file in Source Code Form that contains any Covered Software. 63 | 64 | 1.11. “Patent Claims” of a Contributor 65 | 66 | means any patent claim(s), including without limitation, method, process, 67 | and apparatus claims, in any patent Licensable by such Contributor that 68 | would be infringed, but for the grant of the License, by the making, 69 | using, selling, offering for sale, having made, import, or transfer of 70 | either its Contributions or its Contributor Version. 71 | 72 | 1.12. “Secondary License” 73 | 74 | means either the GNU General Public License, Version 2.0, the GNU Lesser 75 | General Public License, Version 2.1, the GNU Affero General Public 76 | License, Version 3.0, or any later versions of those licenses. 77 | 78 | 1.13. “Source Code Form” 79 | 80 | means the form of the work preferred for making modifications. 81 | 82 | 1.14. “You” (or “Your”) 83 | 84 | means an individual or a legal entity exercising rights under this 85 | License. For legal entities, “You” includes any entity that controls, is 86 | controlled by, or is under common control with You. For purposes of this 87 | definition, “control” means (a) the power, direct or indirect, to cause 88 | the direction or management of such entity, whether by contract or 89 | otherwise, or (b) ownership of more than fifty percent (50%) of the 90 | outstanding shares or beneficial ownership of such entity. 91 | 92 | 93 | 2. License Grants and Conditions 94 | 95 | 2.1. Grants 96 | 97 | Each Contributor hereby grants You a world-wide, royalty-free, 98 | non-exclusive license: 99 | 100 | a. under intellectual property rights (other than patent or trademark) 101 | Licensable by such Contributor to use, reproduce, make available, 102 | modify, display, perform, distribute, and otherwise exploit its 103 | Contributions, either on an unmodified basis, with Modifications, or as 104 | part of a Larger Work; and 105 | 106 | b. under Patent Claims of such Contributor to make, use, sell, offer for 107 | sale, have made, import, and otherwise transfer either its Contributions 108 | or its Contributor Version. 109 | 110 | 2.2. Effective Date 111 | 112 | The licenses granted in Section 2.1 with respect to any Contribution become 113 | effective for each Contribution on the date the Contributor first distributes 114 | such Contribution. 115 | 116 | 2.3. Limitations on Grant Scope 117 | 118 | The licenses granted in this Section 2 are the only rights granted under this 119 | License. No additional rights or licenses will be implied from the distribution 120 | or licensing of Covered Software under this License. Notwithstanding Section 121 | 2.1(b) above, no patent license is granted by a Contributor: 122 | 123 | a. for any code that a Contributor has removed from Covered Software; or 124 | 125 | b. for infringements caused by: (i) Your and any other third party’s 126 | modifications of Covered Software, or (ii) the combination of its 127 | Contributions with other software (except as part of its Contributor 128 | Version); or 129 | 130 | c. under Patent Claims infringed by Covered Software in the absence of its 131 | Contributions. 132 | 133 | This License does not grant any rights in the trademarks, service marks, or 134 | logos of any Contributor (except as may be necessary to comply with the 135 | notice requirements in Section 3.4). 136 | 137 | 2.4. Subsequent Licenses 138 | 139 | No Contributor makes additional grants as a result of Your choice to 140 | distribute the Covered Software under a subsequent version of this License 141 | (see Section 10.2) or under the terms of a Secondary License (if permitted 142 | under the terms of Section 3.3). 143 | 144 | 2.5. Representation 145 | 146 | Each Contributor represents that the Contributor believes its Contributions 147 | are its original creation(s) or it has sufficient rights to grant the 148 | rights to its Contributions conveyed by this License. 149 | 150 | 2.6. Fair Use 151 | 152 | This License is not intended to limit any rights You have under applicable 153 | copyright doctrines of fair use, fair dealing, or other equivalents. 154 | 155 | 2.7. Conditions 156 | 157 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 158 | Section 2.1. 159 | 160 | 161 | 3. Responsibilities 162 | 163 | 3.1. Distribution of Source Form 164 | 165 | All distribution of Covered Software in Source Code Form, including any 166 | Modifications that You create or to which You contribute, must be under the 167 | terms of this License. You must inform recipients that the Source Code Form 168 | of the Covered Software is governed by the terms of this License, and how 169 | they can obtain a copy of this License. You may not attempt to alter or 170 | restrict the recipients’ rights in the Source Code Form. 171 | 172 | 3.2. Distribution of Executable Form 173 | 174 | If You distribute Covered Software in Executable Form then: 175 | 176 | a. such Covered Software must also be made available in Source Code Form, 177 | as described in Section 3.1, and You must inform recipients of the 178 | Executable Form how they can obtain a copy of such Source Code Form by 179 | reasonable means in a timely manner, at a charge no more than the cost 180 | of distribution to the recipient; and 181 | 182 | b. You may distribute such Executable Form under the terms of this License, 183 | or sublicense it under different terms, provided that the license for 184 | the Executable Form does not attempt to limit or alter the recipients’ 185 | rights in the Source Code Form under this License. 186 | 187 | 3.3. Distribution of a Larger Work 188 | 189 | You may create and distribute a Larger Work under terms of Your choice, 190 | provided that You also comply with the requirements of this License for the 191 | Covered Software. If the Larger Work is a combination of Covered Software 192 | with a work governed by one or more Secondary Licenses, and the Covered 193 | Software is not Incompatible With Secondary Licenses, this License permits 194 | You to additionally distribute such Covered Software under the terms of 195 | such Secondary License(s), so that the recipient of the Larger Work may, at 196 | their option, further distribute the Covered Software under the terms of 197 | either this License or such Secondary License(s). 198 | 199 | 3.4. Notices 200 | 201 | You may not remove or alter the substance of any license notices (including 202 | copyright notices, patent notices, disclaimers of warranty, or limitations 203 | of liability) contained within the Source Code Form of the Covered 204 | Software, except that You may alter any license notices to the extent 205 | required to remedy known factual inaccuracies. 206 | 207 | 3.5. Application of Additional Terms 208 | 209 | You may choose to offer, and to charge a fee for, warranty, support, 210 | indemnity or liability obligations to one or more recipients of Covered 211 | Software. However, You may do so only on Your own behalf, and not on behalf 212 | of any Contributor. You must make it absolutely clear that any such 213 | warranty, support, indemnity, or liability obligation is offered by You 214 | alone, and You hereby agree to indemnify every Contributor for any 215 | liability incurred by such Contributor as a result of warranty, support, 216 | indemnity or liability terms You offer. You may include additional 217 | disclaimers of warranty and limitations of liability specific to any 218 | jurisdiction. 219 | 220 | 4. Inability to Comply Due to Statute or Regulation 221 | 222 | If it is impossible for You to comply with any of the terms of this License 223 | with respect to some or all of the Covered Software due to statute, judicial 224 | order, or regulation then You must: (a) comply with the terms of this License 225 | to the maximum extent possible; and (b) describe the limitations and the code 226 | they affect. Such description must be placed in a text file included with all 227 | distributions of the Covered Software under this License. Except to the 228 | extent prohibited by statute or regulation, such description must be 229 | sufficiently detailed for a recipient of ordinary skill to be able to 230 | understand it. 231 | 232 | 5. Termination 233 | 234 | 5.1. The rights granted under this License will terminate automatically if You 235 | fail to comply with any of its terms. However, if You become compliant, 236 | then the rights granted under this License from a particular Contributor 237 | are reinstated (a) provisionally, unless and until such Contributor 238 | explicitly and finally terminates Your grants, and (b) on an ongoing basis, 239 | if such Contributor fails to notify You of the non-compliance by some 240 | reasonable means prior to 60 days after You have come back into compliance. 241 | Moreover, Your grants from a particular Contributor are reinstated on an 242 | ongoing basis if such Contributor notifies You of the non-compliance by 243 | some reasonable means, this is the first time You have received notice of 244 | non-compliance with this License from such Contributor, and You become 245 | compliant prior to 30 days after Your receipt of the notice. 246 | 247 | 5.2. If You initiate litigation against any entity by asserting a patent 248 | infringement claim (excluding declaratory judgment actions, counter-claims, 249 | and cross-claims) alleging that a Contributor Version directly or 250 | indirectly infringes any patent, then the rights granted to You by any and 251 | all Contributors for the Covered Software under Section 2.1 of this License 252 | shall terminate. 253 | 254 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 255 | license agreements (excluding distributors and resellers) which have been 256 | validly granted by You or Your distributors under this License prior to 257 | termination shall survive termination. 258 | 259 | 6. Disclaimer of Warranty 260 | 261 | Covered Software is provided under this License on an “as is” basis, without 262 | warranty of any kind, either expressed, implied, or statutory, including, 263 | without limitation, warranties that the Covered Software is free of defects, 264 | merchantable, fit for a particular purpose or non-infringing. The entire 265 | risk as to the quality and performance of the Covered Software is with You. 266 | Should any Covered Software prove defective in any respect, You (not any 267 | Contributor) assume the cost of any necessary servicing, repair, or 268 | correction. This disclaimer of warranty constitutes an essential part of this 269 | License. No use of any Covered Software is authorized under this License 270 | except under this disclaimer. 271 | 272 | 7. Limitation of Liability 273 | 274 | Under no circumstances and under no legal theory, whether tort (including 275 | negligence), contract, or otherwise, shall any Contributor, or anyone who 276 | distributes Covered Software as permitted above, be liable to You for any 277 | direct, indirect, special, incidental, or consequential damages of any 278 | character including, without limitation, damages for lost profits, loss of 279 | goodwill, work stoppage, computer failure or malfunction, or any and all 280 | other commercial damages or losses, even if such party shall have been 281 | informed of the possibility of such damages. This limitation of liability 282 | shall not apply to liability for death or personal injury resulting from such 283 | party’s negligence to the extent applicable law prohibits such limitation. 284 | Some jurisdictions do not allow the exclusion or limitation of incidental or 285 | consequential damages, so this exclusion and limitation may not apply to You. 286 | 287 | 8. Litigation 288 | 289 | Any litigation relating to this License may be brought only in the courts of 290 | a jurisdiction where the defendant maintains its principal place of business 291 | and such litigation shall be governed by laws of that jurisdiction, without 292 | reference to its conflict-of-law provisions. Nothing in this Section shall 293 | prevent a party’s ability to bring cross-claims or counter-claims. 294 | 295 | 9. Miscellaneous 296 | 297 | This License represents the complete agreement concerning the subject matter 298 | hereof. If any provision of this License is held to be unenforceable, such 299 | provision shall be reformed only to the extent necessary to make it 300 | enforceable. Any law or regulation which provides that the language of a 301 | contract shall be construed against the drafter shall not be used to construe 302 | this License against a Contributor. 303 | 304 | 305 | 10. Versions of the License 306 | 307 | 10.1. New Versions 308 | 309 | Mozilla Foundation is the license steward. Except as provided in Section 310 | 10.3, no one other than the license steward has the right to modify or 311 | publish new versions of this License. Each version will be given a 312 | distinguishing version number. 313 | 314 | 10.2. Effect of New Versions 315 | 316 | You may distribute the Covered Software under the terms of the version of 317 | the License under which You originally received the Covered Software, or 318 | under the terms of any subsequent version published by the license 319 | steward. 320 | 321 | 10.3. Modified Versions 322 | 323 | If you create software not governed by this License, and you want to 324 | create a new license for such software, you may create and use a modified 325 | version of this License if you rename the license and remove any 326 | references to the name of the license steward (except to note that such 327 | modified license differs from this License). 328 | 329 | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses 330 | If You choose to distribute Source Code Form that is Incompatible With 331 | Secondary Licenses under the terms of this version of the License, the 332 | notice described in Exhibit B of this License must be attached. 333 | 334 | Exhibit A - Source Code Form License Notice 335 | 336 | This Source Code Form is subject to the 337 | terms of the Mozilla Public License, v. 338 | 2.0. If a copy of the MPL was not 339 | distributed with this file, You can 340 | obtain one at 341 | http://mozilla.org/MPL/2.0/. 342 | 343 | If it is not possible or desirable to put the notice in a particular file, then 344 | You may include the notice in a location (such as a LICENSE file in a relevant 345 | directory) where a recipient would be likely to look for such a notice. 346 | 347 | You may add additional accurate notices of copyright ownership. 348 | 349 | Exhibit B - “Incompatible With Secondary Licenses” Notice 350 | 351 | This Source Code Form is “Incompatible 352 | With Secondary Licenses”, as defined by 353 | the Mozilla Public License, v. 2.0. 354 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | dep-import: 4 | docker build -t geniousphp/autowire . 5 | docker run --rm -i -v $(pwd):/go/src/github.com/geniousphp/autowire geniousphp/autowire dep ensure -add $(m) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is at an early stage development and is not production ready even though we're running it in our production. Run it at your own risk. 2 | 3 | # Autowire 4 | 5 | **Run WireGuard VPN with zero configuration.** 6 | 7 | This project provides a convenient way to automatically configure WireGuard. If you're running a Consul cluster and willing to configure WireGuard as VPN solution, you'll find this project very helpful. 8 | 9 | `Autowire` picks an IP Address from the available pool of addresses, configures the local WireGuard interface as well as the peers and starts it. `Autowire` leverages distributed locking of Consul to ensure that picked IP address is not used by any other WireGuard peer. This method is described in the [leader election](https://www.consul.io/docs/guides/leader-election.html) guide. 10 | 11 | `Autowire` also takes advantage of Consul blocking queries to watch nodes and KV, this allows Autowire to automatically reconfigure WireGuard Peers when nodes join or leave the Consul cluster. 12 | 13 | `Autowire` uses Consul KV to store WireGuard interface and Peers configurations. This makes WireGuard config distributed and available to all nodes of the cluster. 14 | 15 | 16 | ## Installation 17 | 18 | Autowire doesn't install WireGuard. It's expected to be installed and available in the `$PATH` 19 | Autowire is meant to be installed on every node of the cluster where WireGuard is need to be configured. It's better to schedule it as system daemon in all cluster nodes. 20 | 21 | 1. Download a pre-compiled release from the release page. 22 | 1. Extract the binary. 23 | 1. Run it with `./autowire`. 24 | 25 | ## Configuration 26 | 27 | Example usage: 28 | 29 | * if-name: Network interface whose IP Address will be used for WireGuard endpoints 30 | * wg-range: IP Address range. Autowire will pick address within this range 31 | * wg-config-folder: Folder where WireGuard configurations will be stored 32 | * wg-port: WireGuard Port 33 | 34 | Find out the updated list of configurations in `config.go` 35 | 36 | ``` 37 | autowire --if-name enp0s2 --wg-range 192.168.10.0/24 --wg-config-folder /etc/wireguard --wg-port 51820 38 | ``` 39 | 40 | ## ToDo 41 | 42 | * Code Refactoring and cleaning and enhance logging 43 | * Write automated tests 44 | * Support more backends (etcd, zookeeper,...) 45 | * Support IPv6 46 | * And a lot more coming 47 | 48 | -------------------------------------------------------------------------------- /backends/backend.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "github.com/geniousphp/autowire/wireguard" 5 | ) 6 | 7 | type Backend interface { 8 | //location can be a key for K/V stores or table for databases,.... 9 | Lock(location string, value string) (error) 10 | Unlock() 11 | GetPeers(location string) ([]wireguard.Peer, error) 12 | AddPeer(location string, wgInterface wireguard.Interface, peer wireguard.Peer) (error) 13 | MonitorKv(location string, wgInterface wireguard.Interface) () 14 | MonitorNodes(location string, wgInterface wireguard.Interface) () 15 | } 16 | -------------------------------------------------------------------------------- /backends/consul.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/geniousphp/autowire/wireguard" 7 | "github.com/hashicorp/consul/api" 8 | "log" 9 | "time" 10 | "github.com/geniousphp/autowire/util" 11 | "regexp" 12 | ) 13 | 14 | 15 | type ConsulBackend struct { 16 | client *api.Client 17 | lock *api.Lock 18 | lockChan <-chan struct{} 19 | } 20 | 21 | 22 | func NewConsulBackend(endpoint string) (*ConsulBackend, error) { 23 | 24 | config := api.DefaultConfig() 25 | config.Address = endpoint 26 | 27 | 28 | log.Printf("INFO: Connecting to consul %s", config.Address) 29 | 30 | ConsulClient, err := api.NewClient(config) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | // check health to ensure communication with consul are working 36 | if _, _, err := ConsulClient.Health().State(api.HealthAny, nil); err != nil { 37 | log.Printf("ERROR: health check failed for %v", config.Address) 38 | return nil, err 39 | } 40 | 41 | return &ConsulBackend{client: ConsulClient}, nil 42 | } 43 | 44 | 45 | 46 | func (cb *ConsulBackend) Lock(location string, value string) (error) { 47 | opts := &api.LockOptions{ 48 | Key : fmt.Sprintf("%s/%s", location, "pick-ip-lock"), 49 | Value : []byte(value), 50 | SessionOpts : &api.SessionEntry{ 51 | Behavior : "release", 52 | TTL : "10s", 53 | }, 54 | MonitorRetries: 20, 55 | } 56 | var err error 57 | cb.lock, err = cb.client.LockOpts(opts) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | stopCh := make(chan struct{}) 63 | cb.lockChan, err = cb.lock.Lock(stopCh) 64 | if err != nil { 65 | return err 66 | } 67 | return nil 68 | } 69 | func (cb *ConsulBackend) Unlock() { 70 | cb.lock.Unlock() 71 | } 72 | 73 | func (cb *ConsulBackend) GetPeers(location string) ([]wireguard.Peer, error) { 74 | 75 | ConsulKV := cb.client.KV() 76 | 77 | consulPeers, _, err := ConsulKV.List(fmt.Sprintf("%s/%s", location, "peers"), nil) 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | 83 | peers := []wireguard.Peer{} 84 | 85 | if consulPeers == nil { 86 | return peers, nil 87 | } 88 | 89 | 90 | for _, consulPeer := range consulPeers { 91 | 92 | isKeyPeer, _ := regexp.MatchString("^.+\\/peers\\/[0-9\\.]+$", consulPeer.Key) 93 | 94 | if(isKeyPeer){ 95 | peer := wireguard.Peer{} 96 | 97 | err = json.Unmarshal(consulPeer.Value, &peer) 98 | if err != nil { 99 | log.Print("Error: Couldn't parse peer value for Key=", consulPeer.Key) 100 | } else { 101 | peers = append(peers, peer) 102 | } 103 | } 104 | 105 | } 106 | 107 | 108 | return peers, nil 109 | } 110 | 111 | 112 | func (cb *ConsulBackend) AddPeer(location string, wgInterface wireguard.Interface, peer wireguard.Peer) (error) { 113 | ConsulKV := cb.client.KV() 114 | 115 | 116 | peerJson, err := json.Marshal(peer) 117 | if err != nil { 118 | return err 119 | } 120 | _, err = ConsulKV.Put( 121 | &api.KVPair{ 122 | Key: fmt.Sprintf("%s/%s/%s", location, "peers", peer.IP.String()), 123 | Value: peerJson, 124 | }, 125 | nil, 126 | ) 127 | 128 | if err != nil { 129 | return err 130 | } 131 | 132 | return nil 133 | } 134 | 135 | 136 | func (cb *ConsulBackend) MonitorKv(location string, wgInterface wireguard.Interface) { 137 | stopMonitorKvPrefixChan := make(chan bool) 138 | newPeersChan := make(chan []wireguard.Peer) 139 | 140 | go cb.monitorKvPrefix(location, newPeersChan, stopMonitorKvPrefixChan) 141 | 142 | for { 143 | select { 144 | case <-stopMonitorKvPrefixChan: 145 | log.Print("INFO: monitorKvPrefix goroutine stopped") 146 | case newPeers := <-newPeersChan: 147 | log.Print("INFO: Received new peers from monitorKvPrefix goroutine") 148 | wireguardConfig := wireguard.Configuration{ 149 | Interface: wgInterface, 150 | Peers: newPeers, 151 | } 152 | if _, err := wireguard.ConfigureWireguard(wireguardConfig); err != nil { 153 | log.Fatal(err) 154 | } 155 | } 156 | } 157 | } 158 | 159 | 160 | 161 | 162 | func (cb *ConsulBackend) monitorKvPrefix(location string, newPeersChan chan []wireguard.Peer, stopMonitorKvPrefixChan chan bool) { 163 | ConsulKV := cb.client.KV() 164 | 165 | var shouldSyncPeers bool = false 166 | var waitIndex uint64 167 | waitIndex = 0 168 | 169 | for { 170 | opts := api.QueryOptions{ 171 | AllowStale: false, 172 | RequireConsistent: true, 173 | UseCache: false, 174 | WaitIndex: waitIndex, 175 | } 176 | log.Print("INFO: Watching Consul KV Prefix in blocking query index=", waitIndex) 177 | consulPeers, meta, err := ConsulKV.List(fmt.Sprintf("%s/%s", location, "peers"), &opts) 178 | if err != nil { 179 | // Prevent backend errors from consuming all resources. 180 | fmt.Errorf("Error watching Peers in KV %s", err) 181 | time.Sleep(time.Second * 2) 182 | continue 183 | } 184 | 185 | if meta.LastIndex < waitIndex { 186 | //index went backward, should reset the index to 0 187 | log.Print("INFO: the waitIndex went backward, it will be reset to 0") 188 | waitIndex = 0 189 | shouldSyncPeers = true 190 | } else { 191 | if waitIndex != meta.LastIndex { 192 | shouldSyncPeers = true 193 | } else { 194 | shouldSyncPeers = false 195 | } 196 | waitIndex = meta.LastIndex 197 | } 198 | 199 | if shouldSyncPeers { 200 | newPeers := []wireguard.Peer{} 201 | if consulPeers == nil { 202 | newPeersChan <- newPeers 203 | } else { 204 | for _, consulPeer := range consulPeers { 205 | peer := wireguard.Peer{} 206 | err = json.Unmarshal(consulPeer.Value, &peer) 207 | if err != nil { 208 | fmt.Errorf("Error Parsing Peers in KV %s", err) 209 | time.Sleep(time.Second * 2) 210 | continue 211 | } 212 | newPeers = append(newPeers, peer) 213 | } 214 | newPeersChan <- newPeers 215 | } 216 | } 217 | 218 | } 219 | stopMonitorKvPrefixChan <- true 220 | } 221 | 222 | 223 | func (cb *ConsulBackend) MonitorNodes(location string, wgInterface wireguard.Interface) { 224 | stopMonitorNodesChan := make(chan bool) 225 | newNodesChan := make(chan []string) 226 | 227 | go cb.monitorNodes(location, wgInterface, newNodesChan, stopMonitorNodesChan) 228 | 229 | for { 230 | select { 231 | case <-stopMonitorNodesChan: 232 | log.Print("INFO: monitorNodes goroutine stopped") 233 | case newNodes := <-newNodesChan: 234 | log.Print("INFO: received new nodes from monitorNodes goroutine") 235 | cb.removeLeftPeers(location, newNodes) 236 | } 237 | } 238 | } 239 | 240 | 241 | func (cb *ConsulBackend) monitorNodes(location string, wgInterface wireguard.Interface, newNodesChan chan []string, stopMonitorNodesChan chan bool) { 242 | opts := &api.LockOptions{ 243 | Key : fmt.Sprintf("%s/%s", location, "monitor-nodes-lock"), 244 | Value : []byte(wgInterface.Address), 245 | SessionOpts : &api.SessionEntry{ 246 | Behavior : "release", 247 | TTL : "10s", 248 | }, 249 | MonitorRetries: 20, 250 | } 251 | 252 | 253 | lock, err := cb.client.LockOpts(opts) 254 | if err != nil { 255 | log.Fatal(err) 256 | } 257 | stopCh := make(chan struct{}) 258 | _, err = lock.Lock(stopCh) 259 | if err != nil { 260 | log.Fatal(err) 261 | } 262 | 263 | var shouldSyncPeers bool = false 264 | 265 | 266 | var ConsulCatalog *api.Catalog 267 | ConsulCatalog = cb.client.Catalog() 268 | var waitIndex uint64 269 | waitIndex = 0 270 | for { 271 | opts := api.QueryOptions{ 272 | AllowStale: false, 273 | RequireConsistent: true, 274 | UseCache: false, 275 | WaitIndex: waitIndex, 276 | } 277 | 278 | log.Print("INFO: Watching Consul Nodes in blocking query index=", waitIndex) 279 | listNodes, meta, err := ConsulCatalog.Nodes(&opts) 280 | if err != nil { 281 | // Prevent backend errors from consuming all resources. 282 | fmt.Errorf("Error watching Nodes in Consul %s", err) 283 | time.Sleep(time.Second * 2) 284 | continue 285 | } 286 | 287 | if meta.LastIndex < waitIndex { 288 | //index went backward, should reset the index to 0 289 | log.Print("INFO: the waitIndex went backward, it will be reset to 0") 290 | waitIndex = 0 291 | shouldSyncPeers = true 292 | } else { 293 | if waitIndex != meta.LastIndex { 294 | shouldSyncPeers = true 295 | } else { 296 | shouldSyncPeers = false 297 | } 298 | waitIndex = meta.LastIndex 299 | } 300 | 301 | if shouldSyncPeers { 302 | newNodes := []string{} 303 | for _, node := range listNodes { 304 | newNodes = append(newNodes, node.Address) 305 | 306 | } 307 | 308 | newNodesChan <- newNodes 309 | } 310 | 311 | 312 | } 313 | 314 | 315 | stopMonitorNodesChan <- true 316 | } 317 | 318 | func (cb *ConsulBackend) removeLeftPeers(location string, newNodes []string) { 319 | ConsulKV := cb.client.KV() 320 | 321 | peers, err := cb.GetPeers(location) 322 | if err != nil { 323 | fmt.Errorf("Error Retriving Peers from Consul KV %s", err) 324 | } 325 | for _, peer := range peers { 326 | if !peer.IsClient() && !util.SliceContains(newNodes, peer.GetEndpointIP()) { 327 | log.Print("INFO: Removing Peer from Consul KV: ", peer.GetEndpointIP()) 328 | _, err := ConsulKV.DeleteTree(fmt.Sprintf("%s/%s/%s", location, "peers", peer.GetIP()), nil) 329 | if err != nil { 330 | fmt.Errorf("Error Removing Peer from Consul KV %s", err) 331 | } 332 | } 333 | } 334 | } -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/geniousphp/autowire/ifconfig" 7 | "log" 8 | "net" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | 14 | type Config struct { 15 | AW_Version bool 16 | AW_LogLevel string 17 | AW_Backend string 18 | CS_Address string 19 | CS_KVPrefix string 20 | CS_FullKVPrefix string 21 | WG_ConfigFolder string 22 | WG_Relay bool 23 | WG_InterfaceConfigFolder string 24 | WG_InterfaceName string 25 | WG_Range string 26 | WG_IP string 27 | WG_Port int 28 | WG_EndpointInterfaceName string 29 | WG_EndpointIP string 30 | WG_AllowedIPs string 31 | WG_PostUp string 32 | WG_PostDown string 33 | GC_Enable bool 34 | } 35 | 36 | var config Config 37 | 38 | func init() { 39 | flag.BoolVar(&config.AW_Version, "version", false, "Show version") 40 | flag.StringVar(&config.AW_LogLevel, "log-level", "INFO", "Log Level") 41 | flag.StringVar(&config.AW_Backend, "backend", "consul", "Storage backend. Available backends: [consul]") 42 | flag.StringVar(&config.CS_Address, "cs-ip", "127.0.0.1:8500", "Consul IP") 43 | flag.StringVar(&config.CS_KVPrefix, "cs-kv-prefix", "autowire", "Prefix in KV store where configurations will be stored") 44 | flag.StringVar(&config.WG_ConfigFolder, "wg-config-folder", "/etc/wireguard", "Wireguard config folder") 45 | flag.BoolVar(&config.WG_Relay, "wg-relay", false, "Will the server act as relay? Must set the WG_Range in this case") 46 | flag.StringVar(&config.WG_InterfaceName, "wg-if-name", "wg0", "Wireguard interface name") 47 | flag.StringVar(&config.WG_Range, "wg-range", "192.168.10.0/24", "Wireguard CIDR, ignored when WG_IP is set") 48 | flag.StringVar(&config.WG_IP, "wg-ip", "", "Wireguard IP address, optional. When not set, Autowire will pick unused IP from WG_Range") 49 | flag.IntVar(&config.WG_Port, "wg-port", 51820, "Wireguard Port") 50 | flag.StringVar(&config.WG_EndpointInterfaceName, "wg-endpoint-if-name", "", "The network interface name whose ip address will be used for wireguard endpoint. Ignored when wg-endpoint-ip is specified") 51 | flag.StringVar(&config.WG_EndpointIP, "wg-endpoint-ip", "", "The IP address will be used for wireguard endpoint. Will be fetched from wg-endpoint-if-name if not specified") 52 | flag.StringVar(&config.WG_AllowedIPs, "wg-allowed-ips", "", "Wireguard Allowed IPs (comma separated cidr)") 53 | flag.StringVar(&config.WG_PostUp, "wg-post-up", "", "steps to be run after the wireguard interface is up") 54 | flag.StringVar(&config.WG_PostDown, "wg-post-down", "", "steps to be run after the wireguard interface is down") 55 | flag.BoolVar(&config.GC_Enable, "gc-enable", false, "Enable peers garbage collection. Consul is the only node discovery supported. When disabled, you have to manually remove the left peers from the k/s") 56 | } 57 | 58 | 59 | func initConfig() error { 60 | 61 | if(config.AW_Version){ 62 | fmt.Println("Autowire v0.2.2") 63 | os.Exit(0) 64 | } 65 | 66 | config.CS_FullKVPrefix = config.CS_KVPrefix + "/" + config.WG_InterfaceName 67 | config.WG_InterfaceConfigFolder = config.WG_ConfigFolder + "/" + config.WG_InterfaceName + "/" 68 | 69 | 70 | if config.WG_IP == "" && config.WG_Range == "" { 71 | return fmt.Errorf("-wg-range and -wg-ip are not set. Need to set at least one of them") 72 | } 73 | 74 | if config.WG_Relay && config.WG_Range == "" { 75 | return fmt.Errorf("You must set the -wg-range in relay mode") 76 | } 77 | 78 | if config.WG_EndpointIP == "" { 79 | log.Print("INFO: -wg-endpoint-ip is not set, will try to fetch the endpoint IP from -wg-endpoint-if-name") 80 | if config.WG_EndpointInterfaceName != "" { 81 | log.Print("INFO: Autowire will try to fetch the Endpoint IP from -wg-endpoint-if-name=", config.WG_EndpointInterfaceName) 82 | 83 | //get the interface IP related to WG_EndpointInterfaceName 84 | inet, err := ifconfig.GetIpOfIf(config.WG_EndpointInterfaceName) 85 | if err != nil { 86 | return err 87 | } 88 | 89 | ipAddr, _, err := net.ParseCIDR(inet) 90 | if err != nil { 91 | return err 92 | } 93 | config.WG_EndpointIP = ipAddr.String() 94 | log.Printf("INFO: Autowire will use %s as Endpoint IP", config.WG_EndpointIP) 95 | } else{ 96 | //client behind a NAT are not yet supported 97 | return fmt.Errorf("You must set either -wg-endpoint-if-name or -wg-endpoint-ip") 98 | } 99 | } 100 | 101 | if config.WG_IP == "" { 102 | isWGInterfaceStarted, err := ifconfig.IsInterfaceStarted(config.WG_InterfaceName) 103 | if err != nil { 104 | return err 105 | } 106 | if isWGInterfaceStarted { 107 | actualWGInterfaceAddress, _ := ifconfig.GetIpOfIf(config.WG_InterfaceName) 108 | config.WG_IP = strings.Split(actualWGInterfaceAddress, "/")[0] 109 | } 110 | } 111 | 112 | 113 | 114 | return nil 115 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/geniousphp/autowire 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/armon/go-metrics v0.3.5-0.20201104215618-6fd5a4ddf425 // indirect 7 | github.com/google/btree v1.0.0 // indirect 8 | github.com/hashicorp/consul/api v1.8.0 9 | github.com/hashicorp/go-hclog v0.14.1 // indirect 10 | github.com/hashicorp/go-immutable-radix v1.3.0 // indirect 11 | github.com/hashicorp/go-msgpack v0.5.5 // indirect 12 | github.com/hashicorp/go-sockaddr v1.0.2 // indirect 13 | github.com/hashicorp/go-uuid v1.0.2 // indirect 14 | github.com/hashicorp/golang-lru v0.5.4 // indirect 15 | github.com/miekg/dns v1.1.31 // indirect 16 | github.com/mitchellh/go-testing-interface v1.14.0 // indirect 17 | github.com/mitchellh/mapstructure v1.3.3 // indirect 18 | github.com/stretchr/testify v1.6.1 // indirect 19 | golang.org/x/crypto v0.0.0-20200930160638-afb6bcd081ae // indirect 20 | golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 // indirect 21 | golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= 2 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 3 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 4 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 5 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 6 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 7 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 8 | github.com/armon/go-metrics v0.3.5-0.20201104215618-6fd5a4ddf425 h1:23nUvGE+8HYFc0AUXuYxgFws6IdyzOrSJJmKfPMJmi8= 9 | github.com/armon/go-metrics v0.3.5-0.20201104215618-6fd5a4ddf425/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= 10 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 11 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 12 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 13 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 14 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 15 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 16 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 17 | github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= 18 | github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= 19 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 21 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 23 | github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= 24 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 25 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 26 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 27 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 28 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 29 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 30 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 31 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 32 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 33 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 34 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 35 | github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= 36 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 37 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 38 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 39 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 40 | github.com/hashicorp/consul/api v1.8.0 h1:/djwFfq2mSyZeP6iqRpmYUzsJtzG5I9SlP3FJvSlbTE= 41 | github.com/hashicorp/consul/api v1.8.0/go.mod h1:sDjTOq0yUyv5G4h+BqSea7Fn6BU+XbolEz1952UB+mk= 42 | github.com/hashicorp/consul/sdk v0.7.0 h1:H6R9d008jDcHPQPAqPNuydAshJ4v5/8URdFnUvK/+sc= 43 | github.com/hashicorp/consul/sdk v0.7.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= 44 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 45 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 46 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 47 | github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= 48 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 49 | github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= 50 | github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= 51 | github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= 52 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 53 | github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE= 54 | github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 55 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 56 | github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= 57 | github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 58 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 59 | github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= 60 | github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= 61 | github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 62 | github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= 63 | github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 64 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 65 | github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= 66 | github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= 67 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 68 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 69 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 70 | github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= 71 | github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 72 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 73 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= 74 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 75 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 76 | github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= 77 | github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g= 78 | github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= 79 | github.com/hashicorp/serf v0.9.5 h1:EBWvyu9tcRszt3Bxp3KNssBMP1KuHWyO51lz9+786iM= 80 | github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= 81 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 82 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 83 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 84 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 85 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 86 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 87 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 88 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 89 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 90 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 91 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 92 | github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= 93 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 94 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 95 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 96 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 97 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 98 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 99 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 100 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 101 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 102 | github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= 103 | github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= 104 | github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= 105 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 106 | github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= 107 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 108 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 109 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 110 | github.com/mitchellh/go-testing-interface v1.14.0 h1:/x0XQ6h+3U3nAyk1yx+bHPURrKa9sVVvYbuqZ7pIAtI= 111 | github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= 112 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 113 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 114 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 115 | github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= 116 | github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 117 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 118 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 119 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 120 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 121 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 122 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 123 | github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= 124 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 125 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 126 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 127 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 128 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 129 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 130 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 131 | github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= 132 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 133 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 134 | github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= 135 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 136 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 137 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 138 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 139 | github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= 140 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 141 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 142 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 143 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 144 | github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 145 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= 146 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 147 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 148 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 149 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 150 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 151 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 152 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 153 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 154 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 155 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 156 | github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= 157 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 158 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 159 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 160 | golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= 161 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 162 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 163 | golang.org/x/crypto v0.0.0-20200930160638-afb6bcd081ae h1:duLSQW+DZ5MsXKX7kc4rXlq6/mmxz4G6ewJuBPlhRe0= 164 | golang.org/x/crypto v0.0.0-20200930160638-afb6bcd081ae/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 165 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 166 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 167 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 168 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 169 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 170 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 171 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 172 | golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo= 173 | golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 174 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 175 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 176 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 177 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 178 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 179 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 180 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 181 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 182 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 183 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 184 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 185 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 186 | golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 187 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 188 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 189 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 190 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 191 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 192 | golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 193 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 194 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 195 | golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5 h1:iCaAy5bMeEvwANu3YnJfWwI0kWAGkEa2RXPdweI/ysk= 196 | golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 197 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 198 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 199 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 200 | golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 201 | golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 202 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 203 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 204 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 205 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 206 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 207 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 208 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 209 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 210 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 211 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 212 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 213 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 214 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 215 | -------------------------------------------------------------------------------- /ifconfig/ifconfig.go: -------------------------------------------------------------------------------- 1 | package ifconfig 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "io" 7 | "os/exec" 8 | "bytes" 9 | ) 10 | 11 | 12 | func ip(stdin io.Reader, arg ...string) ([]byte, error) { 13 | path, err := exec.LookPath("ip") 14 | if err != nil { 15 | return nil, fmt.Errorf("ip command is not available in your PATH") 16 | } 17 | 18 | cmd := exec.Command(path, arg...) 19 | 20 | cmd.Stdin = stdin 21 | var buf bytes.Buffer 22 | cmd.Stderr = &buf 23 | output, err := cmd.Output() 24 | 25 | if err != nil { 26 | return nil, fmt.Errorf("%s - %s", err.Error(), buf.String()) 27 | } 28 | return output, nil 29 | 30 | } 31 | 32 | func GetIpOfIf(interfaceName string) (string, error) { 33 | ifaces, err := net.Interfaces() 34 | if err != nil { 35 | return "", err 36 | } 37 | 38 | if len(ifaces) <= 0 { 39 | return "", fmt.Errorf("No network interface found") 40 | } 41 | 42 | for _, i := range ifaces { 43 | if i.Name == interfaceName { 44 | addrs, err := i.Addrs() 45 | if err != nil { 46 | return "", err 47 | } 48 | if len(addrs) > 0 { 49 | return addrs[0].String(), err 50 | } 51 | } 52 | } 53 | return "", fmt.Errorf("The network interface %s doesn't exist or doesn't have any IP address", interfaceName) 54 | } 55 | 56 | 57 | func IsInterfaceStarted(interfaceName string) (bool, error){ 58 | ifaces, err := net.Interfaces() 59 | if err != nil { 60 | return false, err 61 | } 62 | 63 | if len(ifaces) <= 0 { 64 | return false, nil 65 | } 66 | 67 | for _, i := range ifaces { 68 | if i.Name == interfaceName { 69 | return true, nil 70 | } 71 | } 72 | return false, nil 73 | } 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | backendModule "github.com/geniousphp/autowire/backends" 7 | "github.com/geniousphp/autowire/wireguard" 8 | "github.com/geniousphp/autowire/util" 9 | "log" 10 | "net" 11 | "sort" 12 | "strings" 13 | ) 14 | 15 | func main() { 16 | flag.Parse() 17 | if err := initConfig(); err != nil { 18 | log.Fatal("FATAL: ", err.Error()) 19 | } 20 | 21 | // Backend init 22 | b, err := backendFactory() 23 | if err != nil { 24 | log.Fatal("FATAL: ", err) 25 | } 26 | 27 | newWgInterface, err := initialize(b) 28 | if err != nil { 29 | log.Fatal("FATAL: ", err) 30 | } 31 | 32 | b.MonitorKv(config.CS_FullKVPrefix, *newWgInterface) 33 | if config.GC_Enable { 34 | b.MonitorNodes(config.CS_FullKVPrefix, *newWgInterface) 35 | } 36 | 37 | } 38 | 39 | 40 | func initialize(backend backendModule.Backend) (*wireguard.Interface, error) { 41 | if err := backend.Lock(config.CS_FullKVPrefix, config.WG_EndpointIP); err != nil { 42 | return nil, err 43 | } 44 | defer backend.Unlock() 45 | 46 | var shouldConfigureWG bool = false 47 | 48 | peers, err := backend.GetPeers(config.CS_FullKVPrefix) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | 54 | if config.WG_IP != "" { //WG_IP is set, this is like a pet server 55 | if indexPeer := util.IsIPUsed(peers, config.WG_IP); indexPeer >= 0 { //WG_IP is already used 56 | if !peers[indexPeer].IsEndpointIPDefined() { //WG_IP is used by another device 57 | return nil, fmt.Errorf("The WG_IP=%s is already used by another client", config.WG_IP) 58 | } else{ 59 | if !peers[indexPeer].HasSameEndpointIP(config.WG_EndpointIP) { //WG_IP is used by an other server 60 | return nil, fmt.Errorf("The WG_IP=%s is already used by another server %s", peers[indexPeer].GetIP(), peers[indexPeer].GetEndpointIP()) 61 | } else { //WG_IP is used by the current server 62 | shouldConfigureWG = true 63 | } 64 | } 65 | } else { //The WG_IP is not used 66 | if indexPeer := util.IsEndpointIPExist(peers, config.WG_EndpointIP); indexPeer >= 0 { //WG_EndpointIP already registred 67 | return nil, fmt.Errorf("The current WG_EndpointIP=%s is already registred with another WG_IP=%s", config.WG_EndpointIP, peers[indexPeer].GetIP()) 68 | } else{ //WG_EndpointIP is not registred 69 | shouldConfigureWG = true 70 | } 71 | } 72 | } else {//WG_IP is not set, this is like a cattle server 73 | if indexPeer := util.IsEndpointIPExist(peers, config.WG_EndpointIP); indexPeer >= 0 && peers[indexPeer].IP != nil { //WG_EndpointIP already registred 74 | config.WG_IP = peers[indexPeer].IP.String() 75 | shouldConfigureWG = true 76 | } else{ //pick unused IP 77 | log.Print("INFO: Picking unused IP from the range=", config.WG_Range) 78 | _, wgRangeIPNet, err := net.ParseCIDR(config.WG_Range) 79 | if err != nil { 80 | return nil, err 81 | } 82 | wgIPStart := wgRangeIPNet.IP 83 | util.IncIP(wgIPStart) //Skip IP Network 84 | 85 | //The loop goes over all ips in the network 86 | for myFutureWG_IP := wgIPStart; wgRangeIPNet.Contains(myFutureWG_IP); util.IncIP(myFutureWG_IP) { 87 | if indexPeer := util.IsIPUsed(peers, myFutureWG_IP.String()); indexPeer < 0 { 88 | config.WG_IP = myFutureWG_IP.String() 89 | break; 90 | } 91 | } 92 | if config.WG_IP == "" { 93 | return nil, fmt.Errorf("All IPs are used") 94 | } else{ 95 | shouldConfigureWG = true 96 | } 97 | 98 | } 99 | } 100 | 101 | 102 | wireguardConfig := wireguard.Configuration{} 103 | 104 | if(shouldConfigureWG){ 105 | // configure wireguard 106 | privKey, pubKey, err := wireguard.InitWgKeys(config.WG_InterfaceConfigFolder) 107 | if err != nil { 108 | return nil, err 109 | } 110 | 111 | var mask string 112 | if(config.WG_Relay){ 113 | mask = strings.Split(config.WG_Range, "/")[1] 114 | } else{ 115 | mask = "32" 116 | } 117 | 118 | 119 | 120 | wireguardConfig = wireguard.Configuration{ 121 | Interface: wireguard.Interface{ 122 | Name : config.WG_InterfaceName, 123 | Address : fmt.Sprintf("%s/%s", config.WG_IP, mask), 124 | ListenPort : config.WG_Port, 125 | PublicKey : pubKey, 126 | PrivateKey : privKey, 127 | PostUp : config.WG_PostUp, 128 | PostDown : config.WG_PostDown, 129 | }, 130 | Peers: peers, 131 | } 132 | 133 | if _, err := wireguard.ConfigureWireguard(wireguardConfig); err != nil { 134 | return nil, err 135 | } 136 | 137 | allowedips := []string{} 138 | if config.WG_AllowedIPs != "" { 139 | allowedips = strings.Split(config.WG_AllowedIPs, ",") 140 | } 141 | allowedips = append(allowedips, fmt.Sprintf("%s/%s", config.WG_IP, "32")) 142 | if(config.WG_Relay){ 143 | _, rangeIPNet, err := net.ParseCIDR(config.WG_Range) 144 | if err != nil { 145 | return nil, err 146 | } 147 | allowedips = append(allowedips, rangeIPNet.String()) 148 | } 149 | sort.Strings(allowedips) 150 | 151 | newPeer := wireguard.Peer{ 152 | PublicKey : pubKey, 153 | IP : net.ParseIP(config.WG_IP), 154 | EndpointIP : net.ParseIP(config.WG_EndpointIP), 155 | EndpointPort : config.WG_Port, 156 | AllowedIPs : strings.Join(allowedips, ","), 157 | } 158 | 159 | 160 | //Add my interface as Peer in the Backend 161 | if err := backend.AddPeer(config.CS_FullKVPrefix, wireguardConfig.Interface, newPeer); err != nil { 162 | return nil, err 163 | } 164 | 165 | 166 | 167 | } 168 | 169 | 170 | return &wireguardConfig.Interface, nil 171 | } 172 | 173 | 174 | 175 | func backendFactory() (backendModule.Backend, error) { 176 | if config.AW_Backend == "consul" { 177 | b, err := backendModule.NewConsulBackend(config.CS_Address) 178 | if err != nil { 179 | return nil, err 180 | } 181 | return b, nil 182 | } else{ 183 | return nil, fmt.Errorf("Unsupported Backend. Available backends: [consul]") 184 | } 185 | } 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /modd.conf: -------------------------------------------------------------------------------- 1 | **/*.go { 2 | prep: docker build --rm --force-rm -t autowire . 3 | prep: docker create --name autowire autowire 4 | prep: docker cp autowire:/usr/bin/autowire ./ 5 | prep: docker rm autowire 6 | } 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/geniousphp/autowire/wireguard" 5 | "net" 6 | ) 7 | 8 | func IsIPUsed(peers []wireguard.Peer, wgip string) int { 9 | for index, peer := range peers { 10 | if peer.IP.Equal(net.ParseIP(wgip)) { 11 | return index 12 | } 13 | } 14 | return -1 15 | } 16 | 17 | func IsEndpointIPExist(peers []wireguard.Peer, wgEndpointIP string) int { 18 | for index, peer := range peers { 19 | if peer.EndpointIP.Equal(net.ParseIP(wgEndpointIP)) { 20 | return index 21 | } 22 | } 23 | return -1 24 | } 25 | 26 | 27 | func IncIP(ip net.IP) { 28 | for j := len(ip) - 1; j >= 0; j-- { 29 | ip[j]++ 30 | if ip[j] > 0 { 31 | break 32 | } 33 | } 34 | } 35 | 36 | func SliceContains(a []string, x string) bool { 37 | for _, n := range a { 38 | if x == n { 39 | return true 40 | } 41 | } 42 | return false 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /wireguard/config-template.go: -------------------------------------------------------------------------------- 1 | package wireguard 2 | 3 | type Configuration struct { 4 | Interface Interface 5 | Peers []Peer 6 | } 7 | 8 | const WgConfigTemplate = `[Interface] 9 | Address = {{ .Interface.Address }} 10 | ListenPort = {{ .Interface.ListenPort }} 11 | PrivateKey = {{ .Interface.PrivateKey }} 12 | {{ if .Interface.PostUp }}PostUp = {{ .Interface.PostUp }}{{ end }} 13 | {{ if .Interface.PostDown }}PostDown = {{ .Interface.PostDown }}{{ end }} 14 | 15 | # Add servers and relay servers peers 16 | {{ range .Peers}} 17 | {{ if and (not .IsClient) (ne .PublicKey $.Interface.PublicKey) }} 18 | [Peer] 19 | PublicKey = {{ .PublicKey }} 20 | AllowedIPs = {{ .AllowedIPs }} 21 | Endpoint = {{ .EndpointIP.String }}:{{.EndpointPort}} 22 | {{ end }} 23 | {{ end }} 24 | 25 | # Add clients peers 26 | {{if .Interface.IsRelay}} 27 | {{ range .Peers }} 28 | {{ if .IsClient}} 29 | [Peer] 30 | PublicKey = {{ .PublicKey }} 31 | AllowedIPs = {{ .AllowedIPs }} 32 | {{ end }} 33 | {{ end }} 34 | {{end}} 35 | ` 36 | -------------------------------------------------------------------------------- /wireguard/interface.go: -------------------------------------------------------------------------------- 1 | package wireguard 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type Interface struct { 8 | Name string 9 | Address string //can be /32 IP or /24 subnet in case of Relay server 10 | ListenPort int 11 | PublicKey string 12 | PrivateKey string 13 | PostUp string 14 | PostDown string 15 | } 16 | 17 | func (i Interface) IsRelay() bool { 18 | return !strings.HasSuffix(i.Address, "/32") 19 | } 20 | -------------------------------------------------------------------------------- /wireguard/peer.go: -------------------------------------------------------------------------------- 1 | package wireguard 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | type Peer struct { 9 | PublicKey string 10 | IP net.IP 11 | EndpointIP net.IP 12 | EndpointPort int 13 | AllowedIPs string 14 | } 15 | 16 | 17 | func (p Peer) IsEndpointIPDefined() bool { 18 | return p.EndpointIP != nil 19 | } 20 | 21 | 22 | func (p Peer) HasSameEndpointIP(wgEndpointIp string) bool { 23 | return p.EndpointIP.Equal(net.ParseIP(wgEndpointIp)) 24 | } 25 | 26 | func (p Peer) GetEndpointIP() string { 27 | return p.EndpointIP.String() 28 | } 29 | 30 | func (p Peer) GetIP() string { 31 | return p.IP.String() 32 | } 33 | 34 | func (p Peer) IsClient() bool { 35 | return p.EndpointIP == nil 36 | } 37 | 38 | func (p Peer) String() string { 39 | return fmt.Sprintf("PublicKey=%s|IP=%s|EndpointIP=%s|EndpointPort=%d|AllowedIPs=%s", 40 | p.PublicKey, p.IP.String(), p.EndpointIP.String(), p.EndpointPort, p.AllowedIPs) 41 | } -------------------------------------------------------------------------------- /wireguard/wireguard.go: -------------------------------------------------------------------------------- 1 | package wireguard 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/geniousphp/autowire/ifconfig" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "os/exec" 12 | "strings" 13 | "strconv" 14 | "text/template" 15 | ) 16 | 17 | 18 | 19 | func wg(stdin io.Reader, arg ...string) ([]byte, error) { 20 | path, err := exec.LookPath("wg") 21 | if err != nil { 22 | return nil, fmt.Errorf("the wireguard (wg) command is not available in your PATH") 23 | } 24 | 25 | cmd := exec.Command(path, arg...) 26 | 27 | cmd.Stdin = stdin 28 | var buf bytes.Buffer 29 | cmd.Stderr = &buf 30 | output, err := cmd.Output() 31 | 32 | if err != nil { 33 | return nil, fmt.Errorf("%s - %s", err.Error(), buf.String()) 34 | } 35 | return output, nil 36 | 37 | } 38 | 39 | func wgQuick(stdin io.Reader, arg ...string) ([]byte, error) { 40 | path, err := exec.LookPath("wg-quick") 41 | if err != nil { 42 | return nil, fmt.Errorf("wg-quick command is not available in your PATH") 43 | } 44 | 45 | cmd := exec.Command(path, arg...) 46 | 47 | cmd.Stdin = stdin 48 | var buf bytes.Buffer 49 | cmd.Stderr = &buf 50 | output, err := cmd.Output() 51 | 52 | if err != nil { 53 | return nil, fmt.Errorf("%s - %s", err.Error(), buf.String()) 54 | } 55 | return output, nil 56 | 57 | } 58 | 59 | func genkey() ([]byte, error) { 60 | result, err := wg(nil, "genkey") 61 | if err != nil { 62 | return nil, fmt.Errorf("error generating the private key for wireguard: %s", err.Error()) 63 | } 64 | return result, nil 65 | } 66 | 67 | func extractPubKey(privateKey []byte) ([]byte, error) { 68 | stdin := bytes.NewReader(privateKey) 69 | result, err := wg(stdin, "pubkey") 70 | if err != nil { 71 | return nil, fmt.Errorf("error extracting the public key: %s", err.Error()) 72 | } 73 | return result, nil 74 | } 75 | 76 | func InitWgKeys(wgInterfaceConfigFolder string) (string, string, error) { 77 | if _, err := os.Stat(wgInterfaceConfigFolder + "/private"); os.IsNotExist(err) { 78 | err := os.MkdirAll(wgInterfaceConfigFolder, 0700) 79 | 80 | if err != nil { 81 | return "", "", err 82 | } 83 | 84 | privKey, err := genkey() 85 | if err != nil { 86 | return "", "", err 87 | } 88 | 89 | err = ioutil.WriteFile(wgInterfaceConfigFolder + "/private", privKey, 0600) 90 | if err != nil { 91 | return "", "", err 92 | } 93 | 94 | } 95 | 96 | privKey, err := ioutil.ReadFile(wgInterfaceConfigFolder + "/private") 97 | if err != nil { 98 | return "", "", err 99 | } 100 | 101 | pubKey, err := extractPubKey(privKey) 102 | if err != nil { 103 | return "", "", err 104 | } 105 | 106 | return strings.TrimSuffix(string(privKey[:]), "\n"), strings.TrimSuffix(string(pubKey[:]), "\n"), nil 107 | } 108 | 109 | 110 | func ConfigureWireguard(wgConfig Configuration) ([]byte, error) { 111 | isWGInterfaceStarted, err := ifconfig.IsInterfaceStarted(wgConfig.Interface.Name) 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | if isWGInterfaceStarted { 117 | log.Printf("INFO: WireGuard=%s interface is already up", wgConfig.Interface.Name) 118 | if isInterfaceAlreadyConfigured(wgConfig.Interface) { 119 | log.Print("INFO: WireGuard interface is already configured, skipping reconfiguration") 120 | } else { 121 | log.Print("INFO: Bringing Down WireGuard Interface because of configuration inconsistencies") 122 | _, err := wgQuick(nil, "down", wgConfig.Interface.Name) 123 | if err != nil { 124 | return nil, fmt.Errorf("error Bringing Down the Wireguard Interface: %s", err.Error()) 125 | } 126 | } 127 | } 128 | 129 | 130 | configFile, err := os.Create("/etc/wireguard/" + wgConfig.Interface.Name + ".conf") 131 | if err != nil { 132 | return nil, err 133 | } 134 | t := template.Must(template.New("config").Parse(WgConfigTemplate)) 135 | err = t.Execute(configFile, wgConfig) 136 | if err != nil { 137 | return nil, err 138 | } 139 | configFile.Chmod(0600) 140 | 141 | isWGInterfaceStarted, err = ifconfig.IsInterfaceStarted(wgConfig.Interface.Name) 142 | if err != nil { 143 | return nil, err 144 | } 145 | 146 | if isWGInterfaceStarted { 147 | log.Print("INFO: Syncing WireGuard Peers") 148 | strip, _ := wgQuick(nil, "strip", wgConfig.Interface.Name) 149 | stdin := bytes.NewReader(strip) 150 | result, err := wg(stdin, "syncconf", wgConfig.Interface.Name, "/dev/stdin") 151 | if err != nil { 152 | return nil, fmt.Errorf("Error Syncing WireGuard Peers: %s", err.Error()) 153 | } 154 | return result, nil 155 | 156 | } else{ 157 | log.Print("INFO: Bringing Up WireGuard Interface") 158 | result, err := wgQuick(nil, "up", wgConfig.Interface.Name) 159 | if err != nil { 160 | return nil, fmt.Errorf("Error Bringing Up the Wireguard Interface: %s", err.Error()) 161 | } 162 | return result, nil 163 | } 164 | } 165 | 166 | func isInterfaceAlreadyConfigured(wgInterface Interface) (bool) { 167 | // Check consistency with .Address and "ip addr show dev wg0" 168 | actualWGInterfaceAddress, _ := ifconfig.GetIpOfIf(wgInterface.Name) 169 | if(actualWGInterfaceAddress != wgInterface.Address){ 170 | return false 171 | } 172 | 173 | // Check consistency with "wg show wg0" 174 | result, _ := wg(nil, "show", wgInterface.Name, "dump") 175 | currentWgConfigString := strings.Split(string(result[:]), "\n")[0] 176 | currentWgConfig := strings.Split(currentWgConfigString, "\t") 177 | 178 | if(currentWgConfig[0] != wgInterface.PrivateKey){ 179 | return false 180 | } 181 | 182 | if(currentWgConfig[1] != wgInterface.PublicKey){ 183 | return false 184 | } 185 | 186 | currentWgPort, _ := strconv.Atoi(currentWgConfig[2]) 187 | if(currentWgPort != wgInterface.ListenPort){ 188 | return false 189 | } 190 | 191 | return true 192 | } 193 | 194 | 195 | --------------------------------------------------------------------------------