github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/api/state.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package api 5 6 import ( 7 "context" 8 "crypto/tls" 9 "net" 10 "net/url" 11 "strconv" 12 13 "github.com/go-macaroon-bakery/macaroon-bakery/v3/httpbakery" 14 "github.com/juju/clock" 15 "github.com/juju/errors" 16 "github.com/juju/names/v5" 17 "github.com/juju/version/v2" 18 "gopkg.in/macaroon.v2" 19 20 "github.com/juju/juju/api/agent/keyupdater" 21 "github.com/juju/juju/core/network" 22 jujuproxy "github.com/juju/juju/proxy" 23 "github.com/juju/juju/rpc/jsoncodec" 24 ) 25 26 // state is the internal implementation of the Connection interface. 27 type state struct { 28 ctx context.Context 29 client rpcConnection 30 conn jsoncodec.JSONConn 31 clock clock.Clock 32 33 // addr is the address used to connect to the API server. 34 addr string 35 36 // ipAddr is the IP address used to connect to the API server. 37 ipAddr string 38 39 // cookieURL is the URL that HTTP cookies for the API 40 // will be associated with (specifically macaroon auth cookies). 41 cookieURL *url.URL 42 43 // modelTag holds the model tag. 44 // It is empty if there is no model tag associated with the connection. 45 modelTag names.ModelTag 46 47 // controllerTag holds the controller's tag once we're connected. 48 controllerTag names.ControllerTag 49 50 // serverVersion holds the version of the API server that we are 51 // connected to. It is possible that this version is 0 if the 52 // server does not report this during login. 53 serverVersion version.Number 54 55 // hostPorts is the API server addresses returned from Login, 56 // which the client may cache and use for fail-over. 57 hostPorts []network.MachineHostPorts 58 59 // publicDNSName is the public host name returned from Login 60 // which the client can use to make a connection verified 61 // by an officially signed certificate. 62 publicDNSName string 63 64 // facadeVersions holds the versions of all facades as reported by 65 // Login 66 facadeVersions map[string][]int 67 68 // pingFacadeVersion is the version to use for the pinger. This is lazily 69 // set at initialization to avoid a race in our tests. See 70 // http://pad.lv/1614732 for more details regarding the race. 71 pingerFacadeVersion int 72 73 // authTag holds the authenticated entity's tag after login. 74 authTag names.Tag 75 76 // mpdelAccess holds the access level of the user to the connected model. 77 modelAccess string 78 79 // controllerAccess holds the access level of the user to the connected controller. 80 controllerAccess string 81 82 // broken is a channel that gets closed when the connection is 83 // broken. 84 broken chan struct{} 85 86 // closed is a channel that gets closed when State.Close is called. 87 closed chan struct{} 88 89 // loggedIn holds whether the client has successfully logged 90 // in. It's a int32 so that the atomic package can be used to 91 // access it safely. 92 loggedIn int32 93 94 // tag, password, macaroons and nonce hold the cached login 95 // credentials. These are only valid if loggedIn is 1. 96 tag string 97 password string 98 macaroons []macaroon.Slice 99 nonce string 100 101 // serverRootAddress holds the cached API server address and port used 102 // to login. 103 serverRootAddress string 104 105 // serverScheme is the URI scheme of the API Server 106 serverScheme string 107 108 // tlsConfig holds the TLS config appropriate for making SSL 109 // connections to the API endpoints. 110 tlsConfig *tls.Config 111 112 // bakeryClient holds the client that will be used to 113 // authorize macaroon based login requests. 114 bakeryClient *httpbakery.Client 115 116 // proxier is the proxier used for this connection when not nil. If's expected 117 // the proxy has already been started when placing in this var. This struct 118 // will take the responsibility of closing the proxy. 119 proxier jujuproxy.Proxier 120 } 121 122 // Login implements the Login method of the Connection interface providing authentication 123 // using basic auth or macaroons. 124 // 125 // TODO (alesstimec, wallyworld): This method should be removed and 126 // a login provider should be used instead. 127 func (st *state) Login(name names.Tag, password, nonce string, ms []macaroon.Slice) error { 128 lp := NewUserpassLoginProvider(name, password, nonce, ms, st.bakeryClient, st.cookieURL) 129 result, err := lp.Login(context.Background(), st) 130 if err != nil { 131 return errors.Trace(err) 132 } 133 return st.setLoginResult(result) 134 } 135 136 func (st *state) setLoginResult(p *LoginResultParams) error { 137 st.authTag = p.tag 138 st.serverVersion = p.serverVersion 139 var modelTag names.ModelTag 140 if p.modelTag != "" { 141 var err error 142 modelTag, err = names.ParseModelTag(p.modelTag) 143 if err != nil { 144 return errors.Annotatef(err, "invalid model tag in login result") 145 } 146 } 147 if modelTag.Id() != st.modelTag.Id() { 148 return errors.Errorf("mismatched model tag in login result (got %q want %q)", modelTag.Id(), st.modelTag.Id()) 149 } 150 ctag, err := names.ParseControllerTag(p.controllerTag) 151 if err != nil { 152 return errors.Annotatef(err, "invalid controller tag %q returned from login", p.controllerTag) 153 } 154 st.controllerTag = ctag 155 st.controllerAccess = p.controllerAccess 156 st.modelAccess = p.modelAccess 157 158 hostPorts := p.servers 159 // if the connection is not proxied then we will add the connection address 160 // to host ports 161 if !st.IsProxied() { 162 hostPorts, err = addAddress(p.servers, st.addr) 163 if err != nil { 164 if clerr := st.Close(); clerr != nil { 165 err = errors.Annotatef(err, "error closing state: %v", clerr) 166 } 167 return err 168 } 169 } 170 st.hostPorts = hostPorts 171 172 if err != nil { 173 if clerr := st.Close(); clerr != nil { 174 err = errors.Annotatef(err, "error closing state: %v", clerr) 175 } 176 return err 177 } 178 st.hostPorts = hostPorts 179 180 st.publicDNSName = p.publicDNSName 181 182 st.facadeVersions = make(map[string][]int, len(p.facades)) 183 for _, facade := range p.facades { 184 st.facadeVersions[facade.Name] = facade.Versions 185 } 186 187 st.setLoggedIn() 188 return nil 189 } 190 191 // AuthTag returns the tag of the authorized user of the state API connection. 192 func (st *state) AuthTag() names.Tag { 193 return st.authTag 194 } 195 196 // ControllerAccess returns the access level of authorized user to the model. 197 func (st *state) ControllerAccess() string { 198 return st.controllerAccess 199 } 200 201 // CookieURL returns the URL that HTTP cookies for the API will be 202 // associated with. 203 func (st *state) CookieURL() *url.URL { 204 copy := *st.cookieURL 205 return © 206 } 207 208 // slideAddressToFront moves the address at the location (serverIndex, addrIndex) to be 209 // the first address of the first server. 210 func slideAddressToFront(servers []network.MachineHostPorts, serverIndex, addrIndex int) { 211 server := servers[serverIndex] 212 hostPort := server[addrIndex] 213 // Move the matching address to be the first in this server 214 for ; addrIndex > 0; addrIndex-- { 215 server[addrIndex] = server[addrIndex-1] 216 } 217 server[0] = hostPort 218 for ; serverIndex > 0; serverIndex-- { 219 servers[serverIndex] = servers[serverIndex-1] 220 } 221 servers[0] = server 222 } 223 224 // addAddress appends a new server derived from the given 225 // address to servers if the address is not already found 226 // there. 227 func addAddress(servers []network.MachineHostPorts, addr string) ([]network.MachineHostPorts, error) { 228 for i, server := range servers { 229 for j, hostPort := range server { 230 if network.DialAddress(hostPort) == addr { 231 slideAddressToFront(servers, i, j) 232 return servers, nil 233 } 234 } 235 } 236 host, portString, err := net.SplitHostPort(addr) 237 if err != nil { 238 return nil, err 239 } 240 port, err := strconv.Atoi(portString) 241 if err != nil { 242 return nil, err 243 } 244 result := make([]network.MachineHostPorts, 0, len(servers)+1) 245 result = append(result, network.NewMachineHostPorts(port, host)) 246 result = append(result, servers...) 247 return result, nil 248 } 249 250 // KeyUpdater returns access to the KeyUpdater API 251 func (st *state) KeyUpdater() *keyupdater.State { 252 return keyupdater.NewState(st) 253 } 254 255 // ServerVersion holds the version of the API server that we are connected to. 256 // It is possible that this version is Zero if the server does not report this 257 // during login. The second result argument indicates if the version number is 258 // set. 259 func (st *state) ServerVersion() (version.Number, bool) { 260 return st.serverVersion, st.serverVersion != version.Zero 261 }