2022-01-30 16:38:45 +00:00
// Copyright 2021 the Kilo authors
2019-05-03 10:53:40 +00:00
//
// 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 (
"errors"
"fmt"
2019-05-08 15:10:09 +00:00
"net"
2019-05-07 23:31:36 +00:00
"os"
"strings"
2022-01-30 16:38:45 +00:00
"time"
2019-05-03 10:53:40 +00:00
"github.com/spf13/cobra"
2022-01-30 16:38:45 +00:00
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
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 {
2022-01-30 16:38:45 +00:00
allowedIPs [ ] net . IPNet
2019-05-09 15:12:44 +00:00
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 :
2022-04-08 11:42:13 +00:00
return fmt . Errorf ( "output format %s 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 ] )
}
2022-01-30 16:38:45 +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 {
2022-04-08 11:42:13 +00:00
return fmt . Errorf ( "failed to list nodes: %w" , err )
2019-05-03 10:53:40 +00:00
}
ps , err := opts . backend . Peers ( ) . List ( )
if err != nil {
2022-04-08 11:42:13 +00:00
return fmt . Errorf ( "failed to list peers: %w" , err )
2019-05-03 10:53:40 +00:00
}
2021-06-18 10:10:23 +00:00
// Obtain the Granularity by looking at the annotation of the first node.
2022-04-08 11:42:13 +00:00
if opts . granularity , err = determineGranularity ( opts . granularity , ns ) ; err != nil {
return fmt . Errorf ( "failed to determine granularity: %w" , err )
2021-06-18 10:10:23 +00:00
}
2019-05-03 10:53:40 +00:00
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
}
}
2022-01-30 16:38:45 +00:00
t , err := mesh . NewTopology ( nodes , peers , opts . granularity , hostname , int ( opts . port ) , wgtypes . Key { } , subnet , nodes [ hostname ] . PersistentKeepalive , nil )
2019-05-03 10:53:40 +00:00
if err != nil {
2022-04-08 11:42:13 +00:00
return fmt . Errorf ( "failed to create topology: %w" , err )
2019-05-03 10:53:40 +00:00
}
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 {
2022-01-30 16:38:45 +00:00
if p . PublicKey == nodes [ hostname ] . Key {
2019-05-10 00:07:05 +00:00
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 {
2022-04-08 11:42:13 +00:00
return fmt . Errorf ( "failed to generate configuration: %w" , err )
2019-05-07 23:31:36 +00:00
}
_ , 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 ( )
2022-01-30 16:38:45 +00:00
if p == nil {
return errors . New ( "cannot generate config from nil peer" )
}
2019-05-10 00:07:05 +00:00
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 ( )
2022-01-30 16:38:45 +00:00
if p == nil {
return errors . New ( "cannot generate config from nil 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 {
2022-01-30 16:38:45 +00:00
Peers : [ ] wireguard . Peer { * p } ,
2019-05-07 23:31:36 +00:00
} ) . Bytes ( )
if err != nil {
2022-04-08 11:42:13 +00:00
return fmt . Errorf ( "failed to generate configuration: %w" , err )
2019-05-07 23:31:36 +00:00
}
_ , 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 {
2022-04-08 11:42:13 +00:00
return fmt . Errorf ( "failed to list nodes: %w" , err )
2019-05-03 10:53:40 +00:00
}
ps , err := opts . backend . Peers ( ) . List ( )
if err != nil {
2022-04-08 11:42:13 +00:00
return fmt . Errorf ( "failed to list peers: %w" , err )
2019-05-03 10:53:40 +00:00
}
2021-06-18 10:10:23 +00:00
// Obtain the Granularity by looking at the annotation of the first node.
2022-04-08 11:42:13 +00:00
if opts . granularity , err = determineGranularity ( opts . granularity , ns ) ; err != nil {
return fmt . Errorf ( "failed to determine granularity: %w" , err )
2021-06-18 10:10:23 +00:00
}
2019-05-03 10:53:40 +00:00
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 )
}
2022-01-30 16:38:45 +00:00
pka := time . Duration ( 0 )
if p := peers [ peer ] . PersistentKeepaliveInterval ; p != nil {
pka = * p
}
t , err := mesh . NewTopology ( nodes , peers , opts . granularity , hostname , mesh . DefaultKiloPort , wgtypes . Key { } , subnet , pka , nil )
2019-05-03 10:53:40 +00:00
if err != nil {
2022-04-08 11:42:13 +00:00
return fmt . Errorf ( "failed to create topology: %w" , err )
2019-05-03 10:53:40 +00:00
}
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 {
2022-04-08 11:42:13 +00:00
return fmt . Errorf ( "failed to generate configuration: %w" , err )
2019-05-07 23:31:36 +00:00
}
_ , 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 {
2022-01-30 16:38:45 +00:00
Peers : [ ] wireguard . Peer { * p } ,
2019-05-07 23:31:36 +00:00
} ) . Bytes ( )
if err != nil {
2022-04-08 11:42:13 +00:00
return fmt . Errorf ( "failed to generate configuration: %w" , err )
2019-05-07 23:31:36 +00:00
}
_ , 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.
2022-01-30 16:38:45 +00:00
// TODO this function has many similarities to peerBackend.Set(name, peer)
2019-05-07 23:31:36 +00:00
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.
2022-01-30 16:38:45 +00:00
// TODO all IPs should be valid, so no need to skip here?
if aip . String ( ) == ( & net . IPNet { } ) . String ( ) {
2019-05-07 23:31:36 +00:00
continue
}
aips = append ( aips , aip . String ( ) )
}
var endpoint * v1alpha1 . PeerEndpoint
2022-01-30 16:38:45 +00:00
if peer . Endpoint . Port ( ) > 0 || ! peer . Endpoint . HasDNS ( ) {
2019-05-07 23:31:36 +00:00
endpoint = & v1alpha1 . PeerEndpoint {
2020-02-28 14:48:32 +00:00
DNSOrIP : v1alpha1 . DNSOrIP {
2022-01-30 16:38:45 +00:00
IP : peer . Endpoint . IP ( ) . String ( ) ,
DNS : peer . Endpoint . DNS ( ) ,
2020-02-28 14:48:32 +00:00
} ,
2022-01-30 16:38:45 +00:00
Port : uint32 ( peer . Endpoint . Port ( ) ) ,
2019-05-07 23:31:36 +00:00
}
}
var key string
2022-01-30 16:38:45 +00:00
if peer . PublicKey != ( wgtypes . Key { } ) {
key = peer . PublicKey . String ( )
2019-05-07 23:31:36 +00:00
}
2020-05-05 09:36:39 +00:00
var psk string
2022-01-30 16:38:45 +00:00
if peer . PresharedKey != nil {
psk = peer . PresharedKey . String ( )
2020-05-05 09:36:39 +00:00
}
2019-05-07 23:31:36 +00:00
var pka int
2022-01-30 16:38:45 +00:00
if peer . PersistentKeepaliveInterval != nil && * peer . PersistentKeepaliveInterval > time . Duration ( 0 ) {
pka = int ( * peer . PersistentKeepaliveInterval )
2019-05-07 23:31:36 +00:00
}
return & v1alpha1 . Peer {
TypeMeta : metav1 . TypeMeta {
Kind : v1alpha1 . PeerKind ,
APIVersion : v1alpha1 . SchemeGroupVersion . String ( ) ,
} ,
Spec : v1alpha1 . PeerSpec {
AllowedIPs : aips ,
Endpoint : endpoint ,
PersistentKeepalive : pka ,
2020-05-05 09:36:39 +00:00
PresharedKey : psk ,
PublicKey : key ,
2019-05-07 23:31:36 +00:00
} ,
}
}
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
}