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 (
"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-08 15:10:09 +00:00
allowedIPs [ ] string
aips [ ] * net . IPNet
2019-05-07 23:31:36 +00:00
asPeer bool
output string
serializer * json . Serializer
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-07 23:31:36 +00:00
cmd . PersistentFlags ( ) . BoolVar ( & 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 ( & 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-08 15:10:09 +00:00
cmd . PersistentFlags ( ) . StringSliceVar ( & allowedIPs , "allowed-ips" , [ ] string { } , "Override 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 {
switch output {
case outputFormatJSON :
serializer = json . NewSerializer ( json . DefaultMetaFactory , peerCreatorTyper { } , peerCreatorTyper { } , true )
case outputFormatWireGuard :
case outputFormatYAML :
serializer = json . NewYAMLSerializer ( json . DefaultMetaFactory , peerCreatorTyper { } , peerCreatorTyper { } )
default :
return fmt . Errorf ( "output format %v unknown; posible values are: %s" , output , availableOutputFormats )
}
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 ] )
}
aips = append ( aips , aip )
}
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 ]
nodes := make ( map [ string ] * mesh . Node )
for _ , n := range ns {
if n . Ready ( ) {
nodes [ n . Name ] = n
}
}
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
}
}
t , err := mesh . NewTopology ( nodes , peers , opts . granularity , hostname , mesh . DefaultKiloPort , [ ] byte { } , opts . subnet )
if err != nil {
return fmt . Errorf ( "failed to create topology: %v" , err )
}
2019-05-07 23:31:36 +00:00
if ! asPeer {
c , err := t . Conf ( ) . Bytes ( )
if err != nil {
return fmt . Errorf ( "failed to generate configuration: %v" , err )
}
_ , err = os . Stdout . Write ( c )
return err
}
switch output {
case outputFormatJSON :
fallthrough
case outputFormatYAML :
p := translatePeer ( t . AsPeer ( ) )
p . Name = hostname
2019-05-08 15:10:09 +00:00
if len ( aips ) != 0 {
p . Spec . AllowedIPs = allowedIPs
}
2019-05-07 23:31:36 +00:00
return serializer . Encode ( p , os . Stdout )
case outputFormatWireGuard :
2019-05-08 15:10:09 +00:00
p := t . AsPeer ( )
if len ( aips ) != 0 {
p . AllowedIPs = aips
}
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
nodes := make ( map [ string ] * mesh . Node )
for _ , n := range ns {
if n . Ready ( ) {
nodes [ n . Name ] = n
hostname = n . Name
}
}
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 )
}
t , err := mesh . NewTopology ( nodes , peers , opts . granularity , hostname , mesh . DefaultKiloPort , [ ] byte { } , opts . subnet )
if err != nil {
return fmt . Errorf ( "failed to create topology: %v" , err )
}
2019-05-07 23:31:36 +00:00
if ! asPeer {
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
}
switch output {
case outputFormatJSON :
fallthrough
case outputFormatYAML :
p := translatePeer ( t . AsPeer ( ) )
p . Name = peer
2019-05-08 15:10:09 +00:00
p . Name = hostname
if len ( aips ) != 0 {
p . Spec . AllowedIPs = allowedIPs
}
2019-05-07 23:31:36 +00:00
return serializer . Encode ( p , os . Stdout )
case outputFormatWireGuard :
2019-05-08 15:10:09 +00:00
p := & peers [ peer ] . Peer
if len ( aips ) != 0 {
p . AllowedIPs = aips
}
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
}