github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/sshclient/facade.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Package sshclient implements the API endpoint required for Juju
     5  // clients that wish to make SSH connections to Juju managed machines.
     6  package sshclient
     7  
     8  import (
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  
    12  	"github.com/juju/juju/apiserver/common"
    13  	"github.com/juju/juju/apiserver/facade"
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/environs"
    16  	"github.com/juju/juju/environs/context"
    17  	"github.com/juju/juju/network"
    18  	"github.com/juju/juju/permission"
    19  	"github.com/juju/juju/state"
    20  	"github.com/juju/juju/state/stateenvirons"
    21  )
    22  
    23  var logger = loggo.GetLogger("juju.apiserver.sshclient")
    24  
    25  // Facade implements the API required by the sshclient worker.
    26  type Facade struct {
    27  	backend     Backend
    28  	authorizer  facade.Authorizer
    29  	callContext context.ProviderCallContext
    30  }
    31  
    32  // NewFacade is used for API registration.
    33  func NewFacade(ctx facade.Context) (*Facade, error) {
    34  	st := ctx.State()
    35  	m, err := st.Model()
    36  	if err != nil {
    37  		return nil, errors.Trace(err)
    38  	}
    39  	return internalFacade(&backend{stateenvirons.EnvironConfigGetter{st, m}}, ctx.Auth(), state.CallContext(st))
    40  }
    41  
    42  func internalFacade(backend Backend, auth facade.Authorizer, callCtx context.ProviderCallContext) (*Facade, error) {
    43  	if !auth.AuthClient() {
    44  		return nil, common.ErrPerm
    45  	}
    46  
    47  	return &Facade{backend: backend, authorizer: auth, callContext: callCtx}, nil
    48  }
    49  
    50  func (facade *Facade) checkIsModelAdmin() error {
    51  	isModelAdmin, err := facade.authorizer.HasPermission(permission.AdminAccess, facade.backend.ModelTag())
    52  	if err != nil {
    53  		return errors.Trace(err)
    54  	}
    55  	if !isModelAdmin {
    56  		return common.ErrPerm
    57  	}
    58  	return nil
    59  }
    60  
    61  // PublicAddress reports the preferred public network address for one
    62  // or more entities. Machines and units are suppored.
    63  func (facade *Facade) PublicAddress(args params.Entities) (params.SSHAddressResults, error) {
    64  	if err := facade.checkIsModelAdmin(); err != nil {
    65  		return params.SSHAddressResults{}, errors.Trace(err)
    66  	}
    67  
    68  	getter := func(m SSHMachine) (network.Address, error) { return m.PublicAddress() }
    69  	return facade.getAddressPerEntity(args, getter)
    70  }
    71  
    72  // PrivateAddress reports the preferred private network address for one or
    73  // more entities. Machines and units are supported.
    74  func (facade *Facade) PrivateAddress(args params.Entities) (params.SSHAddressResults, error) {
    75  	if err := facade.checkIsModelAdmin(); err != nil {
    76  		return params.SSHAddressResults{}, errors.Trace(err)
    77  	}
    78  
    79  	getter := func(m SSHMachine) (network.Address, error) { return m.PrivateAddress() }
    80  	return facade.getAddressPerEntity(args, getter)
    81  }
    82  
    83  // AllAddresses reports all addresses that might have SSH listening for each given
    84  // entity in args. Machines and units are supported as entity types.
    85  // TODO(wpk): 2017-05-17 This is a temporary solution, we should not fetch environ here
    86  // but get the addresses from state. We will be changing it since we want to have space-aware
    87  // SSH settings.
    88  func (facade *Facade) AllAddresses(args params.Entities) (params.SSHAddressesResults, error) {
    89  	if err := facade.checkIsModelAdmin(); err != nil {
    90  		return params.SSHAddressesResults{}, errors.Trace(err)
    91  	}
    92  	env, err := environs.GetEnviron(facade.backend, environs.New)
    93  	if err != nil {
    94  		return params.SSHAddressesResults{}, errors.Annotate(err, "opening environment")
    95  	}
    96  
    97  	environ, supportsNetworking := environs.SupportsNetworking(env)
    98  	getter := func(m SSHMachine) ([]network.Address, error) {
    99  		devicesAddresses, err := m.AllNetworkAddresses()
   100  		if err != nil {
   101  			return nil, errors.Trace(err)
   102  		}
   103  		legacyAddresses := m.Addresses()
   104  		devicesAddresses = append(devicesAddresses, legacyAddresses...)
   105  		// Make the list unique
   106  		addressMap := make(map[network.Address]bool)
   107  		uniqueAddresses := []network.Address{}
   108  		for _, address := range devicesAddresses {
   109  			if !addressMap[address] {
   110  				addressMap[address] = true
   111  				uniqueAddresses = append(uniqueAddresses, address)
   112  			}
   113  		}
   114  		if supportsNetworking {
   115  			return environ.SSHAddresses(facade.callContext, uniqueAddresses)
   116  		} else {
   117  			return uniqueAddresses, nil
   118  		}
   119  	}
   120  
   121  	return facade.getAllEntityAddresses(args, getter)
   122  }
   123  
   124  func (facade *Facade) getAllEntityAddresses(args params.Entities, getter func(SSHMachine) ([]network.Address, error)) (
   125  	params.SSHAddressesResults, error,
   126  ) {
   127  	out := params.SSHAddressesResults{
   128  		Results: make([]params.SSHAddressesResult, len(args.Entities)),
   129  	}
   130  	for i, entity := range args.Entities {
   131  		machine, err := facade.backend.GetMachineForEntity(entity.Tag)
   132  		if err != nil {
   133  			out.Results[i].Error = common.ServerError(err)
   134  		} else {
   135  			addresses, err := getter(machine)
   136  			if err != nil {
   137  				out.Results[i].Error = common.ServerError(err)
   138  				continue
   139  			}
   140  
   141  			out.Results[i].Addresses = make([]string, len(addresses))
   142  			for j := range addresses {
   143  				out.Results[i].Addresses[j] = addresses[j].Value
   144  			}
   145  		}
   146  	}
   147  	return out, nil
   148  }
   149  
   150  func (facade *Facade) getAddressPerEntity(args params.Entities, addressGetter func(SSHMachine) (network.Address, error)) (
   151  	params.SSHAddressResults, error,
   152  ) {
   153  	out := params.SSHAddressResults{
   154  		Results: make([]params.SSHAddressResult, len(args.Entities)),
   155  	}
   156  
   157  	getter := func(m SSHMachine) ([]network.Address, error) {
   158  		address, err := addressGetter(m)
   159  		if err != nil {
   160  			return nil, errors.Trace(err)
   161  		}
   162  		return []network.Address{address}, nil
   163  	}
   164  
   165  	fullResults, err := facade.getAllEntityAddresses(args, getter)
   166  	if err != nil {
   167  		return params.SSHAddressResults{}, errors.Trace(err)
   168  	}
   169  
   170  	for i, result := range fullResults.Results {
   171  		if result.Error != nil {
   172  			out.Results[i].Error = result.Error
   173  		} else {
   174  			out.Results[i].Address = result.Addresses[0]
   175  		}
   176  	}
   177  
   178  	return out, nil
   179  }
   180  
   181  // PublicKeys returns the public SSH hosts for one or more
   182  // entities. Machines and units are supported.
   183  func (facade *Facade) PublicKeys(args params.Entities) (params.SSHPublicKeysResults, error) {
   184  	if err := facade.checkIsModelAdmin(); err != nil {
   185  		return params.SSHPublicKeysResults{}, errors.Trace(err)
   186  	}
   187  
   188  	out := params.SSHPublicKeysResults{
   189  		Results: make([]params.SSHPublicKeysResult, len(args.Entities)),
   190  	}
   191  	for i, entity := range args.Entities {
   192  		machine, err := facade.backend.GetMachineForEntity(entity.Tag)
   193  		if err != nil {
   194  			out.Results[i].Error = common.ServerError(err)
   195  		} else {
   196  			keys, err := facade.backend.GetSSHHostKeys(machine.MachineTag())
   197  			if err != nil {
   198  				out.Results[i].Error = common.ServerError(err)
   199  			} else {
   200  				out.Results[i].PublicKeys = []string(keys)
   201  			}
   202  		}
   203  	}
   204  	return out, nil
   205  }
   206  
   207  // Proxy returns whether SSH connections should be proxied through the
   208  // controller hosts for the model associated with the API connection.
   209  func (facade *Facade) Proxy() (params.SSHProxyResult, error) {
   210  	if err := facade.checkIsModelAdmin(); err != nil {
   211  		return params.SSHProxyResult{}, errors.Trace(err)
   212  	}
   213  	config, err := facade.backend.ModelConfig()
   214  	if err != nil {
   215  		return params.SSHProxyResult{}, err
   216  	}
   217  	return params.SSHProxyResult{UseProxy: config.ProxySSH()}, nil
   218  }