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 }