github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/fly/rc/target.go (about)

     1  package rc
     2  
     3  import (
     4  	"crypto/tls"
     5  	"crypto/x509"
     6  	"errors"
     7  	"fmt"
     8  	"net"
     9  	"net/http"
    10  	"os"
    11  	"runtime"
    12  	"time"
    13  
    14  	conc "github.com/pf-qiu/concourse/v6"
    15  	"github.com/pf-qiu/concourse/v6/atc"
    16  	"github.com/pf-qiu/concourse/v6/fly/ui"
    17  	"github.com/pf-qiu/concourse/v6/fly/version"
    18  	"github.com/pf-qiu/concourse/v6/go-concourse/concourse"
    19  	semisemanticversion "github.com/cppforlife/go-semi-semantic/version"
    20  	"golang.org/x/oauth2"
    21  )
    22  
    23  var LocalVersion = conc.Version
    24  
    25  func init() {
    26  	ver, found := os.LookupEnv("FAKE_FLY_VERSION")
    27  	if found {
    28  		LocalVersion = ver
    29  	}
    30  }
    31  
    32  type ErrVersionMismatch struct {
    33  	flyVersion string
    34  	atcVersion string
    35  	targetName TargetName
    36  }
    37  
    38  func NewErrVersionMismatch(flyVersion string, atcVersion string, targetName TargetName) ErrVersionMismatch {
    39  	return ErrVersionMismatch{
    40  		flyVersion: flyVersion,
    41  		atcVersion: atcVersion,
    42  		targetName: targetName,
    43  	}
    44  }
    45  
    46  func (e ErrVersionMismatch) Error() string {
    47  	return fmt.Sprintf(
    48  		"fly version (%s) is out of sync with the target (%s). to sync up, run the following:\n\n    %s -t %s sync\n",
    49  		ui.Embolden(e.flyVersion), ui.Embolden(e.atcVersion), os.Args[0], e.targetName)
    50  }
    51  
    52  //go:generate counterfeiter . Target
    53  
    54  type Target interface {
    55  	Client() concourse.Client
    56  	Team() concourse.Team
    57  	FindTeam(string) (concourse.Team, error)
    58  	CACert() string
    59  	ClientCertPath() string
    60  	ClientKeyPath() string
    61  	ClientCertificate() []tls.Certificate
    62  	Validate() error
    63  	ValidateWithWarningOnly() error
    64  	TLSConfig() *tls.Config
    65  	URL() string
    66  	WorkerVersion() (string, error)
    67  	IsWorkerVersionCompatible(string) (bool, error)
    68  	Token() *TargetToken
    69  	TokenAuthorization() (string, bool)
    70  	Version() (string, error)
    71  }
    72  
    73  type target struct {
    74  	name              TargetName
    75  	teamName          string
    76  	caCert            string
    77  	clientCertPath    string
    78  	clientKeyPath     string
    79  	clientCertificate []tls.Certificate
    80  	tlsConfig         *tls.Config
    81  	client            concourse.Client
    82  	url               string
    83  	token             *TargetToken
    84  	info              atc.Info
    85  }
    86  
    87  func NewTarget(
    88  	name TargetName,
    89  	teamName string,
    90  	url string,
    91  	token *TargetToken,
    92  	caCert string,
    93  	caCertPool *x509.CertPool,
    94  	clientCertPath string,
    95  	clientKeyPath string,
    96  	clientCertificate []tls.Certificate,
    97  	insecure bool,
    98  	client concourse.Client,
    99  ) *target {
   100  	tlsConfig := &tls.Config{
   101  		InsecureSkipVerify: insecure,
   102  		RootCAs:            caCertPool,
   103  		Certificates:       clientCertificate,
   104  	}
   105  
   106  	return &target{
   107  		name:              name,
   108  		teamName:          teamName,
   109  		url:               url,
   110  		token:             token,
   111  		caCert:            caCert,
   112  		clientCertPath:    clientCertPath,
   113  		clientKeyPath:     clientKeyPath,
   114  		clientCertificate: clientCertificate,
   115  		tlsConfig:         tlsConfig,
   116  		client:            client,
   117  	}
   118  }
   119  
   120  func LoadTargetFromURL(url, team string, tracing bool) (Target, TargetName, error) {
   121  	flyTargets, err := LoadTargets()
   122  	if err != nil {
   123  		return nil, "", err
   124  	}
   125  
   126  	for name, props := range flyTargets {
   127  		if props.API == url && props.TeamName == team {
   128  			target, err := LoadTarget(name, tracing)
   129  			return target, name, err
   130  		}
   131  	}
   132  
   133  	return nil, "", ErrNoTargetFromURL
   134  }
   135  
   136  func LoadTarget(selectedTarget TargetName, tracing bool) (Target, error) {
   137  	var clientCertificate []tls.Certificate
   138  
   139  	targetProps, err := selectTarget(selectedTarget)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	caCertPool, err := loadCACertPool(targetProps.CACert)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	clientCertificate, err = loadClientCertificate(targetProps.ClientCertPath, targetProps.ClientKeyPath)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	httpClient := defaultHttpClient(targetProps.Token, targetProps.Insecure, caCertPool, clientCertificate)
   155  	client := concourse.NewClient(targetProps.API, httpClient, tracing)
   156  
   157  	return NewTarget(
   158  		selectedTarget,
   159  		targetProps.TeamName,
   160  		targetProps.API,
   161  		targetProps.Token,
   162  		targetProps.CACert,
   163  		caCertPool,
   164  		targetProps.ClientCertPath,
   165  		targetProps.ClientKeyPath,
   166  		clientCertificate,
   167  		targetProps.Insecure,
   168  		client,
   169  	), nil
   170  }
   171  
   172  func LoadUnauthenticatedTarget(
   173  	selectedTarget TargetName,
   174  	teamName string,
   175  	insecure bool,
   176  	caCert string,
   177  	clientCertPath string,
   178  	clientKeyPath string,
   179  	tracing bool,
   180  ) (Target, error) {
   181  	targetProps, err := selectTarget(selectedTarget)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	if teamName == "" {
   187  		teamName = targetProps.TeamName
   188  	}
   189  
   190  	if caCert == "" {
   191  		caCert = targetProps.CACert
   192  	}
   193  
   194  	if insecure {
   195  		caCert = ""
   196  	}
   197  
   198  	caCertPool, err := loadCACertPool(caCert)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	var clientCertificate []tls.Certificate
   204  
   205  	if clientCertPath == "" && clientKeyPath == "" {
   206  		clientCertPath = targetProps.ClientCertPath
   207  		clientKeyPath = targetProps.ClientKeyPath
   208  	}
   209  
   210  	clientCertificate, err = loadClientCertificate(clientCertPath, clientKeyPath)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  
   215  	httpClient := &http.Client{Transport: transport(insecure, caCertPool, clientCertificate)}
   216  
   217  	return NewTarget(
   218  		selectedTarget,
   219  		teamName,
   220  		targetProps.API,
   221  		targetProps.Token,
   222  		caCert,
   223  		caCertPool,
   224  		clientCertPath,
   225  		clientKeyPath,
   226  		clientCertificate,
   227  		targetProps.Insecure,
   228  		concourse.NewClient(targetProps.API, httpClient, tracing),
   229  	), nil
   230  }
   231  
   232  func NewUnauthenticatedTarget(
   233  	name TargetName,
   234  	url string,
   235  	teamName string,
   236  	insecure bool,
   237  	caCert string,
   238  	clientCertPath string,
   239  	clientKeyPath string,
   240  	tracing bool,
   241  ) (Target, error) {
   242  	caCertPool, err := loadCACertPool(caCert)
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	var clientCertificate []tls.Certificate
   248  	clientCertificate, err = loadClientCertificate(clientCertPath, clientKeyPath)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  
   253  	httpClient := &http.Client{Transport: transport(insecure, caCertPool, clientCertificate)}
   254  	client := concourse.NewClient(url, httpClient, tracing)
   255  	return NewTarget(
   256  		name,
   257  		teamName,
   258  		url,
   259  		nil,
   260  		caCert,
   261  		caCertPool,
   262  		clientCertPath,
   263  		clientKeyPath,
   264  		clientCertificate,
   265  		insecure,
   266  		client,
   267  	), nil
   268  }
   269  
   270  func NewAuthenticatedTarget(
   271  	name TargetName,
   272  	url string,
   273  	teamName string,
   274  	insecure bool,
   275  	token *TargetToken,
   276  	caCert string,
   277  	clientCertPath string,
   278  	clientKeyPath string,
   279  	tracing bool,
   280  ) (Target, error) {
   281  	caCertPool, err := loadCACertPool(caCert)
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  
   286  	var clientCertificate []tls.Certificate
   287  	clientCertificate, err = loadClientCertificate(clientCertPath, clientKeyPath)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  
   292  	httpClient := defaultHttpClient(token, insecure, caCertPool, clientCertificate)
   293  	client := concourse.NewClient(url, httpClient, tracing)
   294  
   295  	return NewTarget(
   296  		name,
   297  		teamName,
   298  		url,
   299  		token,
   300  		caCert,
   301  		caCertPool,
   302  		clientCertPath,
   303  		clientKeyPath,
   304  		clientCertificate,
   305  		insecure,
   306  		client,
   307  	), nil
   308  }
   309  
   310  func NewBasicAuthTarget(
   311  	name TargetName,
   312  	url string,
   313  	teamName string,
   314  	insecure bool,
   315  	username string,
   316  	password string,
   317  	caCert string,
   318  	clientCertPath string,
   319  	clientKeyPath string,
   320  	tracing bool,
   321  ) (Target, error) {
   322  	caCertPool, err := loadCACertPool(caCert)
   323  	if err != nil {
   324  		return nil, err
   325  	}
   326  
   327  	var clientCertificate []tls.Certificate
   328  	clientCertificate, err = loadClientCertificate(clientCertPath, clientKeyPath)
   329  	if err != nil {
   330  		return nil, err
   331  	}
   332  
   333  	httpClient := basicAuthHttpClient(username, password, insecure, caCertPool, clientCertificate)
   334  	client := concourse.NewClient(url, httpClient, tracing)
   335  
   336  	return NewTarget(
   337  		name,
   338  		teamName,
   339  		url,
   340  		nil,
   341  		caCert,
   342  		caCertPool,
   343  		clientCertPath,
   344  		clientKeyPath,
   345  		clientCertificate,
   346  		insecure,
   347  		client,
   348  	), nil
   349  }
   350  
   351  func (t *target) Client() concourse.Client {
   352  	return t.client
   353  }
   354  
   355  func (t *target) Team() concourse.Team {
   356  	return t.client.Team(t.teamName)
   357  }
   358  
   359  func (t *target) FindTeam(teamName string) (concourse.Team, error) {
   360  	return t.client.FindTeam(teamName)
   361  }
   362  
   363  func (t *target) CACert() string {
   364  	return t.caCert
   365  }
   366  
   367  func (t *target) TLSConfig() *tls.Config {
   368  	return t.tlsConfig
   369  }
   370  
   371  func (t *target) ClientCertPath() string {
   372  	return t.clientCertPath
   373  }
   374  
   375  func (t *target) ClientKeyPath() string {
   376  	return t.clientKeyPath
   377  }
   378  
   379  func (t *target) ClientCertificate() []tls.Certificate {
   380  	return t.clientCertificate
   381  }
   382  
   383  func (t *target) URL() string {
   384  	return t.url
   385  }
   386  
   387  func (t *target) Token() *TargetToken {
   388  	return t.token
   389  }
   390  
   391  func (t *target) Version() (string, error) {
   392  	info, err := t.getInfo()
   393  	if err != nil {
   394  		return "", err
   395  	}
   396  
   397  	return info.Version, nil
   398  }
   399  
   400  func (t *target) WorkerVersion() (string, error) {
   401  	info, err := t.getInfo()
   402  	if err != nil {
   403  		return "", err
   404  	}
   405  
   406  	return info.WorkerVersion, nil
   407  }
   408  
   409  func (t *target) TokenAuthorization() (string, bool) {
   410  	if t.token == nil || (t.token.Type == "" && t.token.Value == "") {
   411  		return "", false
   412  	}
   413  
   414  	return t.token.Type + " " + t.token.Value, true
   415  }
   416  
   417  func (t *target) ValidateWithWarningOnly() error {
   418  	return t.validate(true)
   419  }
   420  
   421  func (t *target) Validate() error {
   422  	return t.validate(false)
   423  }
   424  
   425  func (t *target) IsWorkerVersionCompatible(workerVersion string) (bool, error) {
   426  	info, err := t.getInfo()
   427  	if err != nil {
   428  		return false, err
   429  	}
   430  
   431  	if info.WorkerVersion == "" {
   432  		return true, nil
   433  	}
   434  
   435  	if workerVersion == "" {
   436  		return false, nil
   437  	}
   438  
   439  	workerV, err := semisemanticversion.NewVersionFromString(workerVersion)
   440  	if err != nil {
   441  		return false, err
   442  	}
   443  
   444  	infoV, err := semisemanticversion.NewVersionFromString(info.WorkerVersion)
   445  	if err != nil {
   446  		return false, err
   447  	}
   448  
   449  	if workerV.Release.Components[0].Compare(infoV.Release.Components[0]) != 0 {
   450  		return false, nil
   451  	}
   452  
   453  	if workerV.Release.Components[1].Compare(infoV.Release.Components[1]) == -1 {
   454  		return false, nil
   455  	}
   456  
   457  	return true, nil
   458  }
   459  
   460  func (t *target) validate(allowVersionMismatch bool) error {
   461  	info, err := t.getInfo()
   462  	if err != nil {
   463  		return err
   464  	}
   465  
   466  	if info.Version == LocalVersion || version.IsDev(LocalVersion) {
   467  		return nil
   468  	}
   469  
   470  	atcMajor, atcMinor, atcPatch, err := version.GetSemver(info.Version)
   471  	if err != nil {
   472  		return err
   473  	}
   474  
   475  	flyMajor, flyMinor, flyPatch, err := version.GetSemver(LocalVersion)
   476  	if err != nil {
   477  		return err
   478  	}
   479  
   480  	if !allowVersionMismatch && (atcMajor != flyMajor || atcMinor != flyMinor) {
   481  		return NewErrVersionMismatch(LocalVersion, info.Version, t.name)
   482  	}
   483  
   484  	if atcMajor != flyMajor || atcMinor != flyMinor || atcPatch != flyPatch {
   485  		fmt.Fprintln(ui.Stderr, ui.WarningColor("WARNING:\n"))
   486  		fmt.Fprintln(ui.Stderr, ui.WarningColor(NewErrVersionMismatch(LocalVersion, info.Version, t.name).Error()))
   487  	}
   488  
   489  	return nil
   490  }
   491  
   492  func (t *target) getInfo() (atc.Info, error) {
   493  	if (t.info != atc.Info{}) {
   494  		return t.info, nil
   495  	}
   496  
   497  	var err error
   498  	t.info, err = t.client.GetInfo()
   499  	return t.info, err
   500  }
   501  
   502  func defaultHttpClient(token *TargetToken, insecure bool, caCertPool *x509.CertPool, clientCertificate []tls.Certificate) *http.Client {
   503  	var oAuthToken *oauth2.Token
   504  	if token != nil {
   505  		oAuthToken = &oauth2.Token{
   506  			TokenType:   token.Type,
   507  			AccessToken: token.Value,
   508  		}
   509  	}
   510  
   511  	transport := transport(insecure, caCertPool, clientCertificate)
   512  
   513  	if token != nil {
   514  		transport = &oauth2.Transport{
   515  			Source: oauth2.StaticTokenSource(oAuthToken),
   516  			Base:   transport,
   517  		}
   518  	}
   519  
   520  	return &http.Client{Transport: transport}
   521  }
   522  
   523  func loadCACertPool(caCert string) (cert *x509.CertPool, err error) {
   524  	if caCert == "" {
   525  		return nil, nil
   526  	}
   527  
   528  	// TODO: remove else block once we switch to go 1.8
   529  	// x509.SystemCertPool is not supported in go 1.7 on Windows
   530  	// see: https://github.com/golang/go/issues/16736
   531  	var pool *x509.CertPool
   532  	if runtime.GOOS != "windows" {
   533  		var err error
   534  		pool, err = x509.SystemCertPool()
   535  		if err != nil {
   536  			return nil, err
   537  		}
   538  	} else {
   539  		pool = x509.NewCertPool()
   540  	}
   541  
   542  	ok := pool.AppendCertsFromPEM([]byte(caCert))
   543  	if !ok {
   544  		return nil, errors.New("CA Cert not valid")
   545  	}
   546  	return pool, nil
   547  }
   548  
   549  func loadClientCertificate(clientCertificateLocation string, clientKeyLocation string) (cert []tls.Certificate, err error) {
   550  	if clientCertificateLocation == "" {
   551  		if clientKeyLocation != "" {
   552  			err = errors.New("A client key may not be declared without defining a client certificate")
   553  
   554  			return []tls.Certificate{}, err
   555  		}
   556  
   557  		return []tls.Certificate{}, nil
   558  	}
   559  
   560  	if clientCertificateLocation != "" && clientKeyLocation == "" {
   561  		err = errors.New("A client certificate may not be declared without defining a client key")
   562  
   563  		return []tls.Certificate{}, err
   564  	}
   565  
   566  	clientCertData, err := tls.LoadX509KeyPair(clientCertificateLocation, clientKeyLocation)
   567  	if err != nil {
   568  		return []tls.Certificate{}, err
   569  	}
   570  
   571  	cert = []tls.Certificate{clientCertData}
   572  
   573  	return cert, nil
   574  }
   575  
   576  func basicAuthHttpClient(
   577  	username string,
   578  	password string,
   579  	insecure bool,
   580  	caCertPool *x509.CertPool,
   581  	clientCertificate []tls.Certificate,
   582  ) *http.Client {
   583  	return &http.Client{
   584  		Transport: basicAuthTransport{
   585  			username: username,
   586  			password: password,
   587  			base:     transport(insecure, caCertPool, clientCertificate),
   588  		},
   589  	}
   590  }
   591  
   592  func transport(insecure bool, caCertPool *x509.CertPool, clientCertificate []tls.Certificate) http.RoundTripper {
   593  	var transport http.RoundTripper
   594  
   595  	transport = &http.Transport{
   596  		TLSClientConfig: &tls.Config{
   597  			InsecureSkipVerify: insecure,
   598  			RootCAs:            caCertPool,
   599  			Certificates:       clientCertificate,
   600  		},
   601  		Dial: (&net.Dialer{
   602  			Timeout: 10 * time.Second,
   603  		}).Dial,
   604  		Proxy: http.ProxyFromEnvironment,
   605  	}
   606  
   607  	return transport
   608  }
   609  
   610  type basicAuthTransport struct {
   611  	username string
   612  	password string
   613  
   614  	base http.RoundTripper
   615  }
   616  
   617  func (t basicAuthTransport) RoundTrip(r *http.Request) (*http.Response, error) {
   618  	r.SetBasicAuth(t.username, t.password)
   619  	return t.base.RoundTrip(r)
   620  }