func (provider) Transform(cfg map[string]interface{}) error {
	c, ok := cfg["credentials"].(map[string]interface{})
	if !ok {
		return errors.New("Expected 'credentials' property to hold credentials")
	}
	creds := &tcclient.Credentials{}
	creds.ClientID, _ = c["clientId"].(string)
	creds.AccessToken, _ = c["accessToken"].(string)
	creds.Certificate, _ = c["certificate"].(string)
	if creds.ClientID == "" || creds.AccessToken == "" {
		return errors.New("Expected properties: credentials.clientId and credentials.accessToken")
	}

	// Create a secrets client
	s := secrets.New(creds)

	// Overwrite the baseUrl for secrets if one is given
	if baseURL, _ := cfg["secretsBaseUrl"].(string); baseURL != "" {
		s.BaseURL = baseURL
	}

	// Create a cache to avoid loading the same secret twice, we use the same
	// creds for all calls and we don't persistent the cache so there is no risk
	// of scope elevation here.
	cache := make(map[string]map[string]interface{})

	return config.ReplaceObjects(cfg, "secret", func(val map[string]interface{}) (interface{}, error) {
		name := val["$secret"].(string)
		key, ok := val["key"].(string)
		if !ok || len(val) != 2 {
			return nil, errors.New("{$secret: ..., key: ...} object is missing key property")
		}
		// If secret isn't in the cache we try to load it
		if _, ok := cache[name]; !ok {
			secret, err := s.Get(name)
			if err != nil {
				return nil, err
			}
			value := map[string]interface{}{}
			err = json.Unmarshal(secret.Secret, &value)
			if err != nil {
				return nil, fmt.Errorf("Failed to parse response from secret, error: %s", err)
			}
			cache[name] = value
		}
		// Get secret from cache
		return cache[name][key], nil
	})
}
func main() {
	sshSecret := make(map[string]string)
	if len(os.Args) != 2 {
		log.Fatal("Usage: " + os.Args[0] + " WORKER_TYPE_DIRECTORY")
	}
	workerTypeDir := os.Args[1]
	absFile, err := filepath.Abs(workerTypeDir)
	if err != nil {
		log.Fatalf("File/directory '%v' could not be read due to '%s'", workerTypeDir, err)
	}
	files, err := ioutil.ReadDir(workerTypeDir)
	if err != nil {
		log.Fatalf("File/directory '%v' (%v) could not be read due to '%s'", workerTypeDir, absFile, err)
	}

	workerType := filepath.Base(absFile)
	secretName := "project/taskcluster/aws-provisioner-v1/worker-types/ssh-keys/" + workerType

	tcCreds := &tcclient.Credentials{
		ClientID:    os.Getenv("TASKCLUSTER_CLIENT_ID"),
		AccessToken: os.Getenv("TASKCLUSTER_ACCESS_TOKEN"),
		Certificate: os.Getenv("TASKCLUSTER_CERTIFICATE"),
	}

	cd := &tcclient.ConnectionData{
		Credentials:  tcCreds,
		BaseURL:      "https://aws-provisioner.taskcluster.net/v1",
		Authenticate: true,
	}
	var wt map[string]interface{}
	_, _, err = cd.APICall(nil, "GET", "/worker-type/"+url.QueryEscape(workerType), &wt, nil)
	if err != nil {
		log.Fatal(err)
	}
	regions := wt["regions"].([]interface{})
	oldAMICount := 0
	newAMICount := 0

	delete(wt, "lastModified")
	delete(wt, "workerType")
	for _, f := range files {
		if !f.IsDir() && strings.HasSuffix(f.Name(), ".id_rsa") {
			region := f.Name()[:len(f.Name())-7]
			bytes, err := ioutil.ReadFile(filepath.Join(workerTypeDir, f.Name()))
			if err != nil {
				log.Fatalf("Problem reading file %v", filepath.Join(workerTypeDir, f.Name()))
			}
			sshSecret[region] = string(bytes)
		}
		if !f.IsDir() && strings.HasSuffix(f.Name(), ".latest-ami") {
			newAMICount++
			tokens := strings.Split(f.Name(), ".")
			region := tokens[0]
			newAmi := tokens[1]
			oldAmi := ""
			for i := range regions {
				regionObj := regions[i].(map[string]interface{})
				if regionObj["region"] == region {
					launchSpec := regionObj["launchSpec"].(map[string]interface{})
					oldAmi = launchSpec["ImageId"].(string)
					launchSpec["ImageId"] = newAmi
					oldAMICount++
				}
			}
			if newAMICount < oldAMICount {
				log.Fatal(fmt.Errorf("Did not find ami specification in worker type %v for region %v", workerType, region))
			}
			if newAMICount > oldAMICount {
				log.Fatal(fmt.Errorf("Found multiple AMIs in worker type %v for region %v", workerType, region))
			}
			log.Print("Old AMI for worker type " + workerType + " region " + region + ": " + oldAmi)
			log.Print("New AMI for worker type " + workerType + " region " + region + ": " + newAmi)
			if oldAmi == newAmi {
				log.Print("WARNING: No change to AMI used in workert type " + workerType + " for region " + region + " (" + oldAmi + ")")
			} else {
				log.Print("Worker type " + workerType + " region " + region + " updated to use " + newAmi)
			}
		}
	}

	if newAMICount != len(regions) {
		log.Printf("WARNING: not updating all AMIs for worker type %v - only %v of %v", workerType, newAMICount, len(regions))
	}

	mySecrets := secrets.New(tcCreds)

	secBytes, err := json.Marshal(sshSecret)
	if err != nil {
		log.Fatalf("Could not convert secret %#v to json: %v", sshSecret, err)
	}

	err = mySecrets.Set(
		secretName,
		&secrets.Secret{
			Expires: tcclient.Time(time.Now().AddDate(1, 0, 0)),
			Secret:  json.RawMessage(secBytes),
		},
	)
	if err != nil {
		log.Printf("Problem publishing new secrets: %v", err)
	}
	s, err := mySecrets.Get(secretName)
	if err != nil {
		log.Fatalf("Error retrieving secret: %v", err)
	}
	log.Print("Secret name:  " + secretName)
	log.Print("Secret value: " + string(s.Secret))
	log.Print("Expiry:       " + s.Expires.String())
	_, _, err = cd.APICall(wt, "POST", "/worker-type/"+url.QueryEscape(workerType)+"/update", new(interface{}), nil)
	if err != nil {
		log.Fatal(err)
	}
}
Esempio n. 3
0
func main() {
	tcCreds := &tcclient.Credentials{
		ClientID:    os.Getenv("TASKCLUSTER_CLIENT_ID"),
		AccessToken: os.Getenv("TASKCLUSTER_ACCESS_TOKEN"),
		Certificate: os.Getenv("TASKCLUSTER_CERTIFICATE"),
	}
	mySecrets := secrets.New(tcCreds)
	s, err := mySecrets.List()
	if err != nil {
		log.Fatalf("Could not read secrets: '%v'", err)
	}
	if len(s.Secrets) == 0 {
		log.Fatalf("Taskcluster secrets service returned zero secrets, but auth did not fail, so it seems your client (%v) does not have scopes\nfor getting existing secrets (recommended: \"secrets:get:project/taskcluster/aws-provisioner-v1/worker-types/ssh-keys/*\")", tcCreds.ClientID)
	}
	for _, name := range s.Secrets {
		if strings.HasPrefix(name, "project/taskcluster/aws-provisioner-v1/worker-types/ssh-keys/") {
			workerType := name[61:]
			fmt.Printf("\nWorker type: %v\n", workerType)
			fmt.Println(strings.Repeat("=", len(workerType)+13))
			secret, err := mySecrets.Get(name)
			if err != nil {
				log.Fatalf("Could not read secret %v: '%v'", name, err)
			}
			var data map[string]interface{}
			err = json.Unmarshal(secret.Secret, &data)
			if err != nil {
				log.Fatalf("Could not unmarshal data %v: '%v'", string(secret.Secret), err)
			}
			for region, rsaKey := range data {
				fmt.Printf("Region: %v\n", region)
				block, _ := pem.Decode([]byte(rsaKey.(string)))
				key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
				if err != nil {
					log.Fatalf("Could not interpret rsa key data '%v': '%v'", rsaKey, err)
				}
				svc := ec2.New(session.New(), &aws.Config{Region: aws.String(region)})
				inst, err := svc.DescribeInstances(
					&ec2.DescribeInstancesInput{
						Filters: []*ec2.Filter{
							&ec2.Filter{
								Name: aws.String("tag:WorkerType"),
								Values: []*string{
									aws.String("aws-provisioner-v1/" + workerType),
								},
							},
							&ec2.Filter{
								Name: aws.String("tag:TC-Windows-Base"),
								Values: []*string{
									aws.String("true"),
								},
							},
							// filter out terminated instances
							&ec2.Filter{
								Name: aws.String("instance-state-name"),
								Values: []*string{
									aws.String("pending"),
									aws.String("running"),
									aws.String("shutting-down"),
									aws.String("stopping"),
									aws.String("stopped"),
								},
							},
						},
					},
				)
				if err != nil {
					log.Fatalf("Could not query AWS for instances in region %v for worker type %v: '%v'", region, workerType, err)
				}
				delimeter := ""
				for _, r := range inst.Reservations {
					for _, i := range r.Instances {
						fmt.Printf("  Base instance: %v\n", *i.InstanceId)
						p, err := svc.GetPasswordData(
							&ec2.GetPasswordDataInput{
								InstanceId: i.InstanceId,
							},
						)
						if err != nil {
							log.Fatalf("Could not query password for instance %v in region %v for worker type %v: '%v'", *i.InstanceId, region, workerType, err)
						}
						d, err := base64.StdEncoding.DecodeString(*p.PasswordData)
						if err != nil {
							log.Fatalf("Could not base64 decode encrypted password '%v': '%v'", *p.PasswordData, err)
						}
						b, err := rsa.DecryptPKCS1v15(
							nil,
							key,
							d,
						)
						if err != nil {
							log.Printf("Could not decrypt password - probably somebody is rebuilding AMIs and the keys in the secret store haven't been updated yet (key: %#v, encrpyted password: %#v) for instance %v in region %v for worker type %v: '%v'", rsaKey, *p.PasswordData, *i.InstanceId, region, workerType, err)
						}
						for _, ni := range i.NetworkInterfaces {
							if ni.Association != nil {
								fmt.Printf("    ssh Administrator@%v # (password: '******')\n  --------------------------\n", *ni.Association.PublicIp, string(b))
							} else {
								fmt.Printf("    ssh [email protected] # (No IP address assigned - is instance running? password: '******')\n  --------------------------\n", string(b))
							}
						}

						for _, bdm := range i.BlockDeviceMappings {
							snapshots, err := svc.DescribeSnapshots(
								&ec2.DescribeSnapshotsInput{
									Filters: []*ec2.Filter{
										&ec2.Filter{
											Name: aws.String("volume-id"),
											Values: []*string{
												bdm.Ebs.VolumeId,
											},
										},
									},
								},
							)
							if err != nil {
								log.Fatalf("Could not query snapshot for volume %v on instance %v in region %v for worker type %v: '%v'", *bdm.Ebs.VolumeId, *i.InstanceId, region, workerType, err)
							}
							for _, snap := range snapshots.Snapshots {
								images, err := svc.DescribeImages(
									&ec2.DescribeImagesInput{
										Filters: []*ec2.Filter{
											&ec2.Filter{
												Name: aws.String("block-device-mapping.snapshot-id"),
												Values: []*string{
													snap.SnapshotId,
												},
											},
										},
									},
								)
								if err != nil {
									log.Fatalf("Could not query images that use snapshot %v from volume %v on instance %v in region %v for worker type %v: '%v'", *snap.SnapshotId, *bdm.Ebs.VolumeId, *i.InstanceId, region, workerType, err)
								}
								for _, image := range images.Images {
									inst, err := svc.DescribeInstances(
										&ec2.DescribeInstancesInput{
											Filters: []*ec2.Filter{
												&ec2.Filter{
													Name: aws.String("image-id"),
													Values: []*string{
														image.ImageId,
													},
												},
												&ec2.Filter{
													Name: aws.String("instance-state-name"),
													Values: []*string{
														aws.String("running"),
													},
												},
												// filter out terminated instances
												&ec2.Filter{
													Name: aws.String("instance-state-name"),
													Values: []*string{
														aws.String("pending"),
														aws.String("running"),
														aws.String("shutting-down"),
														aws.String("stopping"),
														aws.String("stopped"),
													},
												},
											},
										},
									)
									if err != nil {
										log.Fatalf("Could not query AWS for instances in region %v for worker type %v: '%v'", region, workerType, err)
									}
									for _, r := range inst.Reservations {
										for _, i := range r.Instances {
											delimeter = "  --------------------------\n"
											fmt.Printf("  Worker instance: %v (%v)\n", *i.InstanceId, *i.ImageId)
											for _, ni := range i.NetworkInterfaces {
												fmt.Printf("    ssh Administrator@%v # (password: '******')\n", *ni.Association.PublicIp, string(b))
											}
										}
									}
								}
							}
						}

						// aws ec2 --region us-west-2 describe-snapshots --filters "Name=volume-id,Values=vol-96e7d12f" --query 'Snapshots[*].SnapshotId' --output text
						// snap-a88dd3f0
						// aws ec2 --region us-west-2 describe-images --filters "Name=block-device-mapping.snapshot-id,Values=snap-a88dd3f0" --query 'Images[*].ImageId' --output text
						// ami-b00af7d0
						// aws ec2 --region us-west-2 describe-instances --filters 'Name=image-id,Values=ami-b00af7d0' --query 'Reservations[*].Instances[*].InstanceId' --output text
						// i-0027f0d020da43a95

					}
				}
				fmt.Print(delimeter)
			}
		}
	}
}