Skip to content

Commit

Permalink
feat: add cmd to patch bastion ipblock/cidr filter
Browse files Browse the repository at this point in the history
  • Loading branch information
sven-petersen committed Nov 10, 2022
1 parent 39523ac commit 2443ea2
Show file tree
Hide file tree
Showing 9 changed files with 1,163 additions and 46 deletions.
55 changes: 55 additions & 0 deletions internal/gardenclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import (
openstackv1alpha1 "github.com/gardener/gardener-extension-provider-openstack/pkg/apis/openstack/v1alpha1"
gardencore "github.com/gardener/gardener/pkg/apis/core"
gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
operationsv1alpha1 "github.com/gardener/gardener/pkg/apis/operations/v1alpha1"
seedmanagementv1alpha1 "github.com/gardener/gardener/pkg/apis/seedmanagement/v1alpha1"
authenticationv1 "k8s.io/api/authentication/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -80,6 +82,16 @@ type Client interface {
// GetShootOfManagedSeed returns shoot of seed using ManagedSeed resource, nil if not a managed seed
GetShootOfManagedSeed(ctx context.Context, name string) (*seedmanagementv1alpha1.Shoot, error)

// GetBastion returns a Gardener bastion resource in a namespace by name
GetBastion(ctx context.Context, namespace, name string) (*operationsv1alpha1.Bastion, error)
// ListBastions returns all Gardener bastion resources, filtered by a list option
ListBastions(ctx context.Context, opts ...client.ListOption) (*operationsv1alpha1.BastionList, error)
// PatchBastion patches an existing bastion to match newBastion using the merge patch strategy
PatchBastion(ctx context.Context, newBastion, oldBastion *operationsv1alpha1.Bastion) error

// Creates a token review for a user with token authentication
CreateTokenReview(ctx context.Context, token string) (*authenticationv1.TokenReview, error)

// RuntimeClient returns the underlying kubernetes runtime client
// TODO: Remove this when we switched all APIs to the new gardenclient
RuntimeClient() client.Client
Expand Down Expand Up @@ -282,6 +294,49 @@ func (g *clientImpl) GetShootOfManagedSeed(ctx context.Context, name string) (*s
return managedSeed.Spec.Shoot, nil
}

func (g *clientImpl) GetBastion(ctx context.Context, namespace, name string) (*operationsv1alpha1.Bastion, error) {
bastion := &operationsv1alpha1.Bastion{}
key := types.NamespacedName{Namespace: namespace, Name: name}

if err := g.c.Get(ctx, key, bastion); err != nil {
return nil, fmt.Errorf("failed to get bastion %v: %w", key, err)
}

return bastion, nil
}

func (g *clientImpl) ListBastions(ctx context.Context, opts ...client.ListOption) (*operationsv1alpha1.BastionList, error) {
bastionList := &operationsv1alpha1.BastionList{}

if err := g.resolveListOptions(ctx, opts...); err != nil {
return nil, err
}

if err := g.c.List(ctx, bastionList, opts...); err != nil {
return nil, fmt.Errorf("failed to list bastions with list options %q: %w", opts, err)
}

return bastionList, nil
}

func (g *clientImpl) PatchBastion(ctx context.Context, newBastion, oldBastion *operationsv1alpha1.Bastion) error {
return g.c.Patch(ctx, newBastion, client.MergeFrom(oldBastion))
}

func (g *clientImpl) CreateTokenReview(ctx context.Context, token string) (*authenticationv1.TokenReview, error) {
tokenReview := &authenticationv1.TokenReview{
Spec: authenticationv1.TokenReviewSpec{
Token: token,
},
}

if err := g.c.Create(ctx, tokenReview); err != nil {
return nil, fmt.Errorf("failed to create token review: %w", err)
}

return tokenReview, nil
}

func (g *clientImpl) GetSeedClientConfig(ctx context.Context, name string) (clientcmd.ClientConfig, error) {
if shoot, err := g.GetShootOfManagedSeed(ctx, name); err != nil {
return nil, err
Expand Down
86 changes: 76 additions & 10 deletions internal/gardenclient/mocks/mock_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ Find more information at: https://github.com/gardener/gardenctl-v2/blob/master/R

// add subcommands
cmd.AddCommand(cmdssh.NewCmdSSH(f, cmdssh.NewSSHOptions(ioStreams)))
cmd.AddCommand(cmdssh.NewCmdSSHPatch(f, ioStreams))
cmd.AddCommand(cmdtarget.NewCmdTarget(f, ioStreams))
cmd.AddCommand(cmdversion.NewCmdVersion(f, cmdversion.NewVersionOptions(ioStreams)))
cmd.AddCommand(cmdconfig.NewCmdConfig(f, ioStreams))
Expand Down
72 changes: 72 additions & 0 deletions pkg/cmd/ssh/base_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package ssh

import (
"context"
"errors"
"fmt"
"net"
"strings"
"time"

"github.com/spf13/cobra"

"github.com/gardener/gardenctl-v2/internal/util"
"github.com/gardener/gardenctl-v2/pkg/cmd/base"
)

// sshBaseOptions is a struct used by all ssh related commands
type sshBaseOptions struct {
base.Options

// CIDRs is a list of IP address ranges to be allowed for accessing the
// created Bastion host. If not given, gardenctl will attempt to
// auto-detect the user's IP and allow only it (i.e. use a /32 netmask).
CIDRs []string

// AutoDetected indicates if the public IPs of the user were automatically detected.
// AutoDetected is false in case the CIDRs were provided via flags.
AutoDetected bool
}

func (o *sshBaseOptions) Complete(f util.Factory, cmd *cobra.Command, args []string) error {
if len(o.CIDRs) == 0 {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()

publicIPs, err := f.PublicIPs(ctx)
if err != nil {
return fmt.Errorf("failed to determine your system's public IP addresses: %w", err)
}

var cidrs []string
for _, ip := range publicIPs {
cidrs = append(cidrs, ipToCIDR(ip))
}

name := "CIDR"
if len(cidrs) != 1 {
name = "CIDRs"
}

fmt.Fprintf(o.IOStreams.Out, "Auto-detected your system's %s as %s\n", name, strings.Join(cidrs, ", "))

o.CIDRs = cidrs
o.AutoDetected = true
}

return nil
}

func (o *sshBaseOptions) Validate() error {
if len(o.CIDRs) == 0 {
return errors.New("must at least specify a single CIDR to allow access to the bastion")
}

for _, cidr := range o.CIDRs {
if _, _, err := net.ParseCIDR(cidr); err != nil {
return fmt.Errorf("CIDR %q is invalid: %w", cidr, err)
}
}

return nil
}
43 changes: 7 additions & 36 deletions pkg/cmd/ssh/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,7 @@ var (
//
//nolint:revive
type SSHOptions struct {
base.Options

sshBaseOptions
// Interactive can be used to toggle between gardenctl just
// providing the bastion host while keeping it alive (non-interactive),
// or gardenctl opening the SSH connection itself (interactive). For
Expand All @@ -170,15 +169,6 @@ type SSHOptions struct {
// bastion host, but leave it up to the user to SSH themselves.
NodeName string

// CIDRs is a list of IP address ranges to be allowed for accessing the
// created Bastion host. If not given, gardenctl will attempt to
// auto-detect the user's IP and allow only it (i.e. use a /32 netmask).
CIDRs []string

// AutoDetected indicates if the public IPs of the user were automatically detected.
// AutoDetected is false in case the CIDRs were provided via flags.
AutoDetected bool

// SSHPublicKeyFile is the full path to the file containing the user's
// public SSH key. If not given, gardenctl will create a new temporary keypair.
SSHPublicKeyFile string
Expand All @@ -204,8 +194,10 @@ type SSHOptions struct {
// NewSSHOptions returns initialized SSHOptions
func NewSSHOptions(ioStreams util.IOStreams) *SSHOptions {
return &SSHOptions{
Options: base.Options{
IOStreams: ioStreams,
sshBaseOptions: sshBaseOptions{
Options: base.Options{
IOStreams: ioStreams,
},
},
Interactive: true,
WaitTimeout: 10 * time.Minute,
Expand All @@ -215,29 +207,8 @@ func NewSSHOptions(ioStreams util.IOStreams) *SSHOptions {

// Complete adapts from the command line args to the data required.
func (o *SSHOptions) Complete(f util.Factory, cmd *cobra.Command, args []string) error {
if len(o.CIDRs) == 0 {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()

publicIPs, err := f.PublicIPs(ctx)
if err != nil {
return fmt.Errorf("failed to determine your system's public IP addresses: %w", err)
}

cidrs := []string{}
for _, ip := range publicIPs {
cidrs = append(cidrs, ipToCIDR(ip))
}

name := "CIDR"
if len(cidrs) != 1 {
name = "CIDRs"
}

fmt.Fprintf(o.IOStreams.Out, "Auto-detected your system's %s as %s\n", name, strings.Join(cidrs, ", "))

o.CIDRs = cidrs
o.AutoDetected = true
if err := o.sshBaseOptions.Complete(f, cmd, args); err != nil {
return err
}

if len(o.SSHPublicKeyFile) == 0 {
Expand Down
Loading

0 comments on commit 2443ea2

Please sign in to comment.