2019-05-03 10:53:40 +00:00
// Copyright 2019 the Kilo authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
2019-05-10 00:07:05 +00:00
"bytes"
2019-05-03 10:53:40 +00:00
"errors"
"fmt"
2019-05-08 15:10:09 +00:00
"net"
2019-05-07 23:31:36 +00:00
"os"
"strings"
2019-05-03 10:53:40 +00:00
"github.com/spf13/cobra"
2019-05-07 23:31:36 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
2019-05-03 10:53:40 +00:00
"github.com/squat/kilo/pkg/mesh"
2019-05-07 23:31:36 +00:00
"github.com/squat/kilo/pkg/wireguard"
)
const (
outputFormatJSON = "json"
outputFormatWireGuard = "wireguard"
outputFormatYAML = "yaml"
)
var (
availableOutputFormats = strings . Join ( [ ] string {
outputFormatJSON ,
outputFormatWireGuard ,
outputFormatYAML ,
} , ", " )
2019-05-09 15:12:44 +00:00
allowedIPs [ ] string
showConfOpts struct {
allowedIPs [ ] * net . IPNet
serializer * json . Serializer
output string
asPeer bool
}
2019-05-03 10:53:40 +00:00
)
func showConf ( ) * cobra . Command {
cmd := & cobra . Command {
2019-05-07 23:31:36 +00:00
Use : "showconf" ,
Short : "Show the WireGuard configuration for a node or peer in the Kilo network" ,
PersistentPreRunE : runShowConf ,
2019-05-03 10:53:40 +00:00
}
for _ , subCmd := range [ ] * cobra . Command {
showConfNode ( ) ,
showConfPeer ( ) ,
} {
cmd . AddCommand ( subCmd )
}
2019-05-09 15:12:44 +00:00
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." )
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 ) )
2019-05-10 00:07:05 +00:00
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'." )
2019-05-03 10:53:40 +00:00
return cmd
}
2019-05-07 23:31:36 +00:00
func runShowConf ( c * cobra . Command , args [ ] string ) error {
2019-05-09 15:12:44 +00:00
switch showConfOpts . output {
2019-05-07 23:31:36 +00:00
case outputFormatJSON :
2019-05-09 15:12:44 +00:00
showConfOpts . serializer = json . NewSerializer ( json . DefaultMetaFactory , peerCreatorTyper { } , peerCreatorTyper { } , true )
2019-05-07 23:31:36 +00:00
case outputFormatWireGuard :
case outputFormatYAML :
2019-05-09 15:12:44 +00:00
showConfOpts . serializer = json . NewYAMLSerializer ( json . DefaultMetaFactory , peerCreatorTyper { } , peerCreatorTyper { } )
2019-05-07 23:31:36 +00:00
default :
2019-05-09 15:12:44 +00:00
return fmt . Errorf ( "output format %v unknown; posible values are: %s" , showConfOpts . output , availableOutputFormats )
2019-05-07 23:31:36 +00:00
}
2019-05-08 15:10:09 +00:00
for i := range allowedIPs {
_ , aip , err := net . ParseCIDR ( allowedIPs [ i ] )
if err != nil {
return fmt . Errorf ( "allowed-ips must contain only valid CIDRs; got %q" , allowedIPs [ i ] )
}
2019-05-09 15:12:44 +00:00
showConfOpts . allowedIPs = append ( showConfOpts . allowedIPs , aip )
2019-05-08 15:10:09 +00:00
}
2019-05-07 23:31:36 +00:00
return runRoot ( c , args )
}
2019-05-03 10:53:40 +00:00
func showConfNode ( ) * cobra . Command {
return & cobra . Command {
2019-05-07 23:31:36 +00:00
Use : "node [name]" ,
2019-05-03 10:53:40 +00:00
Short : "Show the WireGuard configuration for a node in the Kilo network" ,
RunE : runShowConfNode ,
Args : cobra . ExactArgs ( 1 ) ,
}
}
func showConfPeer ( ) * cobra . Command {
return & cobra . Command {
2019-05-07 23:31:36 +00:00
Use : "peer [name]" ,
2019-05-03 10:53:40 +00:00
Short : "Show the WireGuard configuration for a peer in the Kilo network" ,
RunE : runShowConfPeer ,
Args : cobra . ExactArgs ( 1 ) ,
}
}
func runShowConfNode ( _ * cobra . Command , args [ ] string ) error {
ns , err := opts . backend . Nodes ( ) . List ( )
if err != nil {
return fmt . Errorf ( "failed to list nodes: %v" , err )
}
ps , err := opts . backend . Peers ( ) . List ( )
if err != nil {
return fmt . Errorf ( "failed to list peers: %v" , err )
}
hostname := args [ 0 ]
2019-05-10 00:05:57 +00:00
subnet := mesh . DefaultKiloSubnet
2019-05-03 10:53:40 +00:00
nodes := make ( map [ string ] * mesh . Node )
for _ , n := range ns {
if n . Ready ( ) {
nodes [ n . Name ] = n
}
2019-05-10 00:05:57 +00:00
if n . WireGuardIP != nil {
subnet = n . WireGuardIP
}
2019-05-03 10:53:40 +00:00
}
2019-05-10 00:05:57 +00:00
subnet . IP = subnet . IP . Mask ( subnet . Mask )
2019-05-03 10:53:40 +00:00
if len ( nodes ) == 0 {
return errors . New ( "did not find any valid Kilo nodes in the cluster" )
}
if _ , ok := nodes [ hostname ] ; ! ok {
return fmt . Errorf ( "did not find any node named %q in the cluster" , hostname )
}
peers := make ( map [ string ] * mesh . Peer )
for _ , p := range ps {
if p . Ready ( ) {
peers [ p . Name ] = p
}
}
2019-05-10 00:05:57 +00:00
t , err := mesh . NewTopology ( nodes , peers , opts . granularity , hostname , mesh . DefaultKiloPort , [ ] byte { } , subnet )
2019-05-03 10:53:40 +00:00
if err != nil {
return fmt . Errorf ( "failed to create topology: %v" , err )
}
2019-05-07 23:31:36 +00:00
2019-05-10 00:07:05 +00:00
var found bool
for _ , p := range t . PeerConf ( "" ) . Peers {
if bytes . Equal ( p . PublicKey , nodes [ hostname ] . Key ) {
found = true
break
}
}
if ! found {
_ , err := os . Stderr . WriteString ( fmt . Sprintf ( "Node %q is not a leader node\n" , hostname ) )
return err
}
2019-05-09 15:12:44 +00:00
if ! showConfOpts . asPeer {
2019-05-07 23:31:36 +00:00
c , err := t . Conf ( ) . Bytes ( )
if err != nil {
return fmt . Errorf ( "failed to generate configuration: %v" , err )
}
_ , err = os . Stdout . Write ( c )
return err
}
2019-05-09 15:12:44 +00:00
switch showConfOpts . output {
2019-05-07 23:31:36 +00:00
case outputFormatJSON :
fallthrough
case outputFormatYAML :
2019-05-10 00:07:05 +00:00
p := t . AsPeer ( )
p . AllowedIPs = append ( p . AllowedIPs , showConfOpts . allowedIPs ... )
p . DeduplicateIPs ( )
k8sp := translatePeer ( p )
k8sp . Name = hostname
return showConfOpts . serializer . Encode ( k8sp , os . Stdout )
2019-05-07 23:31:36 +00:00
case outputFormatWireGuard :
2019-05-08 15:10:09 +00:00
p := t . AsPeer ( )
2019-05-10 00:07:05 +00:00
p . AllowedIPs = append ( p . AllowedIPs , showConfOpts . allowedIPs ... )
p . DeduplicateIPs ( )
2019-05-07 23:31:36 +00:00
c , err := ( & wireguard . Conf {
2019-05-08 15:10:09 +00:00
Peers : [ ] * wireguard . Peer { p } ,
2019-05-07 23:31:36 +00:00
} ) . Bytes ( )
if err != nil {
return fmt . Errorf ( "failed to generate configuration: %v" , err )
}
_ , err = os . Stdout . Write ( c )
return err
2019-05-03 10:53:40 +00:00
}
return nil
}
func runShowConfPeer ( _ * cobra . Command , args [ ] string ) error {
ns , err := opts . backend . Nodes ( ) . List ( )
if err != nil {
return fmt . Errorf ( "failed to list nodes: %v" , err )
}
ps , err := opts . backend . Peers ( ) . List ( )
if err != nil {
return fmt . Errorf ( "failed to list peers: %v" , err )
}
var hostname string
2019-05-10 00:05:57 +00:00
subnet := mesh . DefaultKiloSubnet
2019-05-03 10:53:40 +00:00
nodes := make ( map [ string ] * mesh . Node )
for _ , n := range ns {
if n . Ready ( ) {
nodes [ n . Name ] = n
hostname = n . Name
}
2019-05-10 00:05:57 +00:00
if n . WireGuardIP != nil {
subnet = n . WireGuardIP
}
2019-05-03 10:53:40 +00:00
}
2019-05-10 00:05:57 +00:00
subnet . IP = subnet . IP . Mask ( subnet . Mask )
2019-05-03 10:53:40 +00:00
if len ( nodes ) == 0 {
return errors . New ( "did not find any valid Kilo nodes in the cluster" )
}
peer := args [ 0 ]
peers := make ( map [ string ] * mesh . Peer )
for _ , p := range ps {
if p . Ready ( ) {
peers [ p . Name ] = p
}
}
if _ , ok := peers [ peer ] ; ! ok {
return fmt . Errorf ( "did not find any peer named %q in the cluster" , peer )
}
2019-05-10 00:05:57 +00:00
t , err := mesh . NewTopology ( nodes , peers , opts . granularity , hostname , mesh . DefaultKiloPort , [ ] byte { } , subnet )
2019-05-03 10:53:40 +00:00
if err != nil {
return fmt . Errorf ( "failed to create topology: %v" , err )
}
2019-05-09 15:12:44 +00:00
if ! showConfOpts . asPeer {
2019-05-07 23:31:36 +00:00
c , err := t . PeerConf ( peer ) . Bytes ( )
if err != nil {
return fmt . Errorf ( "failed to generate configuration: %v" , err )
}
_ , err = os . Stdout . Write ( c )
return err
}
2019-05-09 15:12:44 +00:00
switch showConfOpts . output {
2019-05-07 23:31:36 +00:00
case outputFormatJSON :
fallthrough
case outputFormatYAML :
2019-05-10 00:07:05 +00:00
p := peers [ peer ]
p . AllowedIPs = append ( p . AllowedIPs , showConfOpts . allowedIPs ... )
p . DeduplicateIPs ( )
k8sp := translatePeer ( & p . Peer )
k8sp . Name = peer
return showConfOpts . serializer . Encode ( k8sp , os . Stdout )
2019-05-07 23:31:36 +00:00
case outputFormatWireGuard :
2019-05-08 15:10:09 +00:00
p := & peers [ peer ] . Peer
2019-05-10 00:07:05 +00:00
p . AllowedIPs = append ( p . AllowedIPs , showConfOpts . allowedIPs ... )
p . DeduplicateIPs ( )
2019-05-07 23:31:36 +00:00
c , err := ( & wireguard . Conf {
2019-05-08 15:10:09 +00:00
Peers : [ ] * wireguard . Peer { p } ,
2019-05-07 23:31:36 +00:00
} ) . Bytes ( )
if err != nil {
return fmt . Errorf ( "failed to generate configuration: %v" , err )
}
_ , err = os . Stdout . Write ( c )
return err
2019-05-03 10:53:40 +00:00
}
return nil
}
2019-05-07 23:31:36 +00:00
// translatePeer translates a wireguard.Peer to a Peer CRD.
func translatePeer ( peer * wireguard . Peer ) * v1alpha1 . Peer {
if peer == nil {
return & v1alpha1 . Peer { }
}
var aips [ ] string
for _ , aip := range peer . AllowedIPs {
// Skip any invalid IPs.
if aip == nil {
continue
}
aips = append ( aips , aip . String ( ) )
}
var endpoint * v1alpha1 . PeerEndpoint
if peer . Endpoint != nil && peer . Endpoint . Port > 0 && peer . Endpoint . IP != nil {
endpoint = & v1alpha1 . PeerEndpoint {
IP : peer . Endpoint . IP . String ( ) ,
Port : peer . Endpoint . Port ,
}
}
var key string
if len ( peer . PublicKey ) > 0 {
key = string ( peer . PublicKey )
}
var pka int
if peer . PersistentKeepalive > 0 {
pka = peer . PersistentKeepalive
}
return & v1alpha1 . Peer {
TypeMeta : metav1 . TypeMeta {
Kind : v1alpha1 . PeerKind ,
APIVersion : v1alpha1 . SchemeGroupVersion . String ( ) ,
} ,
Spec : v1alpha1 . PeerSpec {
AllowedIPs : aips ,
Endpoint : endpoint ,
PublicKey : key ,
PersistentKeepalive : pka ,
} ,
}
}
type peerCreatorTyper struct { }
func ( p peerCreatorTyper ) New ( _ schema . GroupVersionKind ) ( runtime . Object , error ) {
return & v1alpha1 . Peer { } , nil
}
func ( p peerCreatorTyper ) ObjectKinds ( _ runtime . Object ) ( [ ] schema . GroupVersionKind , bool , error ) {
return [ ] schema . GroupVersionKind { v1alpha1 . PeerGVK } , false , nil
}
func ( p peerCreatorTyper ) Recognizes ( _ schema . GroupVersionKind ) bool {
return true
}