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) } }
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) } } } }