showconf.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. // Copyright 2019 the Kilo authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package main
  15. import (
  16. "bytes"
  17. "errors"
  18. "fmt"
  19. "net"
  20. "os"
  21. "strings"
  22. "github.com/spf13/cobra"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. "k8s.io/apimachinery/pkg/runtime"
  25. "k8s.io/apimachinery/pkg/runtime/schema"
  26. "k8s.io/apimachinery/pkg/runtime/serializer/json"
  27. "github.com/kilo-io/kilo/pkg/k8s/apis/kilo/v1alpha1"
  28. "github.com/kilo-io/kilo/pkg/mesh"
  29. "github.com/kilo-io/kilo/pkg/wireguard"
  30. )
  31. const (
  32. outputFormatJSON = "json"
  33. outputFormatWireGuard = "wireguard"
  34. outputFormatYAML = "yaml"
  35. )
  36. var (
  37. availableOutputFormats = strings.Join([]string{
  38. outputFormatJSON,
  39. outputFormatWireGuard,
  40. outputFormatYAML,
  41. }, ", ")
  42. allowedIPs []string
  43. showConfOpts struct {
  44. allowedIPs []*net.IPNet
  45. serializer *json.Serializer
  46. output string
  47. asPeer bool
  48. }
  49. )
  50. func showConf() *cobra.Command {
  51. cmd := &cobra.Command{
  52. Use: "showconf",
  53. Short: "Show the WireGuard configuration for a node or peer in the Kilo network",
  54. PersistentPreRunE: runShowConf,
  55. }
  56. for _, subCmd := range []*cobra.Command{
  57. showConfNode(),
  58. showConfPeer(),
  59. } {
  60. cmd.AddCommand(subCmd)
  61. }
  62. cmd.PersistentFlags().BoolVar(&showConfOpts.asPeer, "as-peer", false, "Should the resource be shown as a peer? Useful to configure this resource as a peer of another WireGuard interface.")
  63. cmd.PersistentFlags().StringVarP(&showConfOpts.output, "output", "o", "wireguard", fmt.Sprintf("The output format of the resource. Only valid when combined with 'as-peer'. Possible values: %s", availableOutputFormats))
  64. cmd.PersistentFlags().StringSliceVar(&allowedIPs, "allowed-ips", []string{}, "Add the given IPs to the allowed IPs of the configuration. Only valid when combined with 'as-peer'.")
  65. return cmd
  66. }
  67. func runShowConf(c *cobra.Command, args []string) error {
  68. switch showConfOpts.output {
  69. case outputFormatJSON:
  70. showConfOpts.serializer = json.NewSerializer(json.DefaultMetaFactory, peerCreatorTyper{}, peerCreatorTyper{}, true)
  71. case outputFormatWireGuard:
  72. case outputFormatYAML:
  73. showConfOpts.serializer = json.NewYAMLSerializer(json.DefaultMetaFactory, peerCreatorTyper{}, peerCreatorTyper{})
  74. default:
  75. return fmt.Errorf("output format %v unknown; posible values are: %s", showConfOpts.output, availableOutputFormats)
  76. }
  77. for i := range allowedIPs {
  78. _, aip, err := net.ParseCIDR(allowedIPs[i])
  79. if err != nil {
  80. return fmt.Errorf("allowed-ips must contain only valid CIDRs; got %q", allowedIPs[i])
  81. }
  82. showConfOpts.allowedIPs = append(showConfOpts.allowedIPs, aip)
  83. }
  84. return runRoot(c, args)
  85. }
  86. func showConfNode() *cobra.Command {
  87. return &cobra.Command{
  88. Use: "node [name]",
  89. Short: "Show the WireGuard configuration for a node in the Kilo network",
  90. RunE: runShowConfNode,
  91. Args: cobra.ExactArgs(1),
  92. }
  93. }
  94. func showConfPeer() *cobra.Command {
  95. return &cobra.Command{
  96. Use: "peer [name]",
  97. Short: "Show the WireGuard configuration for a peer in the Kilo network",
  98. RunE: runShowConfPeer,
  99. Args: cobra.ExactArgs(1),
  100. }
  101. }
  102. func runShowConfNode(_ *cobra.Command, args []string) error {
  103. ns, err := opts.backend.Nodes().List()
  104. if err != nil {
  105. return fmt.Errorf("failed to list nodes: %v", err)
  106. }
  107. ps, err := opts.backend.Peers().List()
  108. if err != nil {
  109. return fmt.Errorf("failed to list peers: %v", err)
  110. }
  111. // Obtain the Granularity by looking at the annotation of the first node.
  112. if opts.granularity, err = optainGranularity(opts.granularity, ns); err != nil {
  113. return fmt.Errorf("failed to obtain granularity: %w", err)
  114. }
  115. hostname := args[0]
  116. subnet := mesh.DefaultKiloSubnet
  117. nodes := make(map[string]*mesh.Node)
  118. for _, n := range ns {
  119. if n.Ready() {
  120. nodes[n.Name] = n
  121. }
  122. if n.WireGuardIP != nil {
  123. subnet = n.WireGuardIP
  124. }
  125. }
  126. subnet.IP = subnet.IP.Mask(subnet.Mask)
  127. if len(nodes) == 0 {
  128. return errors.New("did not find any valid Kilo nodes in the cluster")
  129. }
  130. if _, ok := nodes[hostname]; !ok {
  131. return fmt.Errorf("did not find any node named %q in the cluster", hostname)
  132. }
  133. peers := make(map[string]*mesh.Peer)
  134. for _, p := range ps {
  135. if p.Ready() {
  136. peers[p.Name] = p
  137. }
  138. }
  139. t, err := mesh.NewTopology(nodes, peers, opts.granularity, hostname, opts.port, []byte{}, subnet, nodes[hostname].PersistentKeepalive, nil)
  140. if err != nil {
  141. return fmt.Errorf("failed to create topology: %v", err)
  142. }
  143. var found bool
  144. for _, p := range t.PeerConf("").Peers {
  145. if bytes.Equal(p.PublicKey, nodes[hostname].Key) {
  146. found = true
  147. break
  148. }
  149. }
  150. if !found {
  151. _, err := os.Stderr.WriteString(fmt.Sprintf("Node %q is not a leader node\n", hostname))
  152. return err
  153. }
  154. if !showConfOpts.asPeer {
  155. c, err := t.Conf().Bytes()
  156. if err != nil {
  157. return fmt.Errorf("failed to generate configuration: %v", err)
  158. }
  159. _, err = os.Stdout.Write(c)
  160. return err
  161. }
  162. switch showConfOpts.output {
  163. case outputFormatJSON:
  164. fallthrough
  165. case outputFormatYAML:
  166. p := t.AsPeer()
  167. p.AllowedIPs = append(p.AllowedIPs, showConfOpts.allowedIPs...)
  168. p.DeduplicateIPs()
  169. k8sp := translatePeer(p)
  170. k8sp.Name = hostname
  171. return showConfOpts.serializer.Encode(k8sp, os.Stdout)
  172. case outputFormatWireGuard:
  173. p := t.AsPeer()
  174. p.AllowedIPs = append(p.AllowedIPs, showConfOpts.allowedIPs...)
  175. p.DeduplicateIPs()
  176. c, err := (&wireguard.Conf{
  177. Peers: []*wireguard.Peer{p},
  178. }).Bytes()
  179. if err != nil {
  180. return fmt.Errorf("failed to generate configuration: %v", err)
  181. }
  182. _, err = os.Stdout.Write(c)
  183. return err
  184. }
  185. return nil
  186. }
  187. func runShowConfPeer(_ *cobra.Command, args []string) error {
  188. ns, err := opts.backend.Nodes().List()
  189. if err != nil {
  190. return fmt.Errorf("failed to list nodes: %v", err)
  191. }
  192. ps, err := opts.backend.Peers().List()
  193. if err != nil {
  194. return fmt.Errorf("failed to list peers: %v", err)
  195. }
  196. // Obtain the Granularity by looking at the annotation of the first node.
  197. if opts.granularity, err = optainGranularity(opts.granularity, ns); err != nil {
  198. return fmt.Errorf("failed to obtain granularity: %w", err)
  199. }
  200. var hostname string
  201. subnet := mesh.DefaultKiloSubnet
  202. nodes := make(map[string]*mesh.Node)
  203. for _, n := range ns {
  204. if n.Ready() {
  205. nodes[n.Name] = n
  206. hostname = n.Name
  207. }
  208. if n.WireGuardIP != nil {
  209. subnet = n.WireGuardIP
  210. }
  211. }
  212. subnet.IP = subnet.IP.Mask(subnet.Mask)
  213. if len(nodes) == 0 {
  214. return errors.New("did not find any valid Kilo nodes in the cluster")
  215. }
  216. peer := args[0]
  217. peers := make(map[string]*mesh.Peer)
  218. for _, p := range ps {
  219. if p.Ready() {
  220. peers[p.Name] = p
  221. }
  222. }
  223. if _, ok := peers[peer]; !ok {
  224. return fmt.Errorf("did not find any peer named %q in the cluster", peer)
  225. }
  226. t, err := mesh.NewTopology(nodes, peers, opts.granularity, hostname, mesh.DefaultKiloPort, []byte{}, subnet, peers[peer].PersistentKeepalive, nil)
  227. if err != nil {
  228. return fmt.Errorf("failed to create topology: %v", err)
  229. }
  230. if !showConfOpts.asPeer {
  231. c, err := t.PeerConf(peer).Bytes()
  232. if err != nil {
  233. return fmt.Errorf("failed to generate configuration: %v", err)
  234. }
  235. _, err = os.Stdout.Write(c)
  236. return err
  237. }
  238. switch showConfOpts.output {
  239. case outputFormatJSON:
  240. fallthrough
  241. case outputFormatYAML:
  242. p := peers[peer]
  243. p.AllowedIPs = append(p.AllowedIPs, showConfOpts.allowedIPs...)
  244. p.DeduplicateIPs()
  245. k8sp := translatePeer(&p.Peer)
  246. k8sp.Name = peer
  247. return showConfOpts.serializer.Encode(k8sp, os.Stdout)
  248. case outputFormatWireGuard:
  249. p := &peers[peer].Peer
  250. p.AllowedIPs = append(p.AllowedIPs, showConfOpts.allowedIPs...)
  251. p.DeduplicateIPs()
  252. c, err := (&wireguard.Conf{
  253. Peers: []*wireguard.Peer{p},
  254. }).Bytes()
  255. if err != nil {
  256. return fmt.Errorf("failed to generate configuration: %v", err)
  257. }
  258. _, err = os.Stdout.Write(c)
  259. return err
  260. }
  261. return nil
  262. }
  263. // translatePeer translates a wireguard.Peer to a Peer CRD.
  264. func translatePeer(peer *wireguard.Peer) *v1alpha1.Peer {
  265. if peer == nil {
  266. return &v1alpha1.Peer{}
  267. }
  268. var aips []string
  269. for _, aip := range peer.AllowedIPs {
  270. // Skip any invalid IPs.
  271. if aip == nil {
  272. continue
  273. }
  274. aips = append(aips, aip.String())
  275. }
  276. var endpoint *v1alpha1.PeerEndpoint
  277. if peer.Endpoint != nil && peer.Endpoint.Port > 0 && (peer.Endpoint.IP != nil || peer.Endpoint.DNS != "") {
  278. var ip string
  279. if peer.Endpoint.IP != nil {
  280. ip = peer.Endpoint.IP.String()
  281. }
  282. endpoint = &v1alpha1.PeerEndpoint{
  283. DNSOrIP: v1alpha1.DNSOrIP{
  284. DNS: peer.Endpoint.DNS,
  285. IP: ip,
  286. },
  287. Port: peer.Endpoint.Port,
  288. }
  289. }
  290. var key string
  291. if len(peer.PublicKey) > 0 {
  292. key = string(peer.PublicKey)
  293. }
  294. var psk string
  295. if len(peer.PresharedKey) > 0 {
  296. psk = string(peer.PresharedKey)
  297. }
  298. var pka int
  299. if peer.PersistentKeepalive > 0 {
  300. pka = peer.PersistentKeepalive
  301. }
  302. return &v1alpha1.Peer{
  303. TypeMeta: metav1.TypeMeta{
  304. Kind: v1alpha1.PeerKind,
  305. APIVersion: v1alpha1.SchemeGroupVersion.String(),
  306. },
  307. Spec: v1alpha1.PeerSpec{
  308. AllowedIPs: aips,
  309. Endpoint: endpoint,
  310. PersistentKeepalive: pka,
  311. PresharedKey: psk,
  312. PublicKey: key,
  313. },
  314. }
  315. }
  316. type peerCreatorTyper struct{}
  317. func (p peerCreatorTyper) New(_ schema.GroupVersionKind) (runtime.Object, error) {
  318. return &v1alpha1.Peer{}, nil
  319. }
  320. func (p peerCreatorTyper) ObjectKinds(_ runtime.Object) ([]schema.GroupVersionKind, bool, error) {
  321. return []schema.GroupVersionKind{v1alpha1.PeerGVK}, false, nil
  322. }
  323. func (p peerCreatorTyper) Recognizes(_ schema.GroupVersionKind) bool {
  324. return true
  325. }