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