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 &copy
   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  }