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 }