// Parse reads from the given io.ReadCloser and // parses read contents of a cloud-config file. func Parse(rdr io.ReadCloser) (*Digest, error) { buf, err := ioutil.ReadAll(rdr) if err != nil { return nil, err } rdr.Close() var conf cloudConfig if err := yaml.Unmarshal(buf, &conf); err != nil { return nil, err } var c Digest c.Commands = parseCommands(conf.RunCMD) c.AuthorizedKeys = make(map[string][]ssh.Key) for _, key := range conf.AuthorizedKeys { c.AuthorizedKeys["root"] = append(c.AuthorizedKeys["root"], ssh.Key(key)) } public_keys, private_keys := make(map[string]string), make(map[string]string) // TODO: Extend ssh key syntax beyond cloud-config for k, v := range conf.SSHKeyPairs { if strings.HasSuffix(k, "private") { private_keys[strings.TrimSuffix(k, "_private")] = v } else { public_keys[strings.TrimSuffix(k, "_public")] = v } } for key, value := range public_keys { c.SSHKeyPairs = append(c.SSHKeyPairs, ssh.KeyPair{ Public: ssh.Key(value), Private: ssh.Key(private_keys[key]), }) } c.Groups = parseGroups(conf.Groups) c.Users = parseUsers(conf.Users) /* * BUG(yaml.v2): Embedded structs are not unmarshaled properly. * TODO(tmrts): Use another yaml library or extend identity.User. *for _, usr := range conf.Users { * for _, key := range usr.AuthorizedSSHKeys { * c.AuthorizedKeys[usr.Name] = append(c.AuthorizedKeys[usr.Name], ssh.Key(key)) * } *} */ c.Files = conf.Files return &c, nil }
func TestRetrievesDataFromEC2(t *testing.T) { Convey("Given an EC2 meta-data service", t, func() { // mock EC2 metadata server server := NewMockServer(func(w http.ResponseWriter, r *http.Request) { attributes := map[string]string{ "hostname": "centos.ec2", "local-ipv4": "10.240.51.29", "public-ipv4": "104.155.21.99", "public-keys/0/openssh-key": "ssh-rsa OPENSSH_KEY", } if strings.Contains(r.URL.String(), "/2009-04-04/meta-data/") { for attr, value := range attributes { if strings.HasSuffix(r.URL.String(), attr) { w.Write([]byte(value)) } } } else if strings.HasSuffix(r.URL.String(), "/2009-04-04/user-data") { w.Write([]byte("#cloud-config\n")) } else { http.Error(w, "requested resource is not found", http.StatusNotFound) } }) service := ec2.MetadataService{ URL: provider.FormatURL(server.URL + "/%v/%v/%v"), } Convey("It should retrieve meta-data from EC2 meta-data service", func() { digest, err := service.FetchMetadata() So(err, ShouldBeNil) So(digest.Hostname, ShouldEqual, "centos.ec2") ifc := digest.PrimaryNetworkInterface() So(ifc.PublicIPs[0].String(), ShouldEqual, "104.155.21.99") sshKeys := digest.SSHKeys So(sshKeys["root"], ShouldConsistOf, ssh.Key("ssh-rsa OPENSSH_KEY")) }) Convey("It should retrieve user-data from EC2 meta-data service", func() { userdata, err := service.FetchUserdata() So(err, ShouldBeNil) So(userdata["user-data"], ShouldEqual, "#cloud-config\n") }) }) }
func TestSSHKeyParsing(t *testing.T) { Convey("Given a cloud-config file containing SSH-key directives", t, func() { configFile, err := os.Open(filepath.Join(testConfigDirectory, "sshkeys.yaml")) So(err, ShouldBeNil) Convey("It should parse the ssh keys", func() { conf, err := cloudconfig.Parse(configFile) So(err, ShouldBeNil) So(conf.AuthorizedKeys["root"], ShouldConsistOf, ssh.Key("ssh-rsa RSA_PUBLIC_KEY_1 mykey@host"), ssh.Key("ssh-rsa RSA_PUBLIC_KEY_2 mykey@host"), ) So(conf.SSHKeyPairs, ShouldConsistOf, ssh.KeyPair{ Public: ssh.Key("ssh-rsa RSA_PUBLIC_KEY smoser@localhost"), Private: ssh.Key("-----BEGIN RSA PRIVATE KEY-----\nRSA_PRIVATE_KEY\n-----END RSA PRIVATE KEY-----\n"), }, ) }) }) }
// Digest extracts the important parts of meta-data and returns it. func (md *Metadata) Digest() metadata.Digest { sshKeys := make(map[string][]ssh.Key) for usr, rawKeys := range md.PublicKeys { keys := strings.Split(rawKeys, "\n") for _, key := range keys { if key != "" { sshKeys[usr] = append(sshKeys[usr], ssh.Key(key)) } } } return metadata.Digest{ Hostname: md.Hostname, SSHKeys: sshKeys, } }
// Digest extracts the important parts of meta-data and returns it. func (m *Metadata) Digest() metadata.Digest { sshKeys := make(map[string][]ssh.Key) for _, publicKey := range m.PublicKeys { sshKeys["root"] = append(sshKeys["root"], ssh.Key(publicKey)) } primaryNetworkInterface := metadata.NetworkInterface{ PrivateIP: m.LocalIPv4, PublicIPs: []net.IP{m.PublicIPv4}, } return metadata.Digest{ Hostname: m.Hostname, SSHKeys: sshKeys, NetworkInterfaces: []metadata.NetworkInterface{ primaryNetworkInterface, }, } }
// Digest extracts the important parts of meta-data and returns it. func (md *Metadata) Digest() metadata.Digest { interfaces := []metadata.NetworkInterface{} for _, ifc := range md.Instance.NetworkInterfaces { i := metadata.NetworkInterface{ NetworkName: ifc.Network, PrivateIP: ifc.IP, PublicIPs: []net.IP{}, } for _, conf := range ifc.AccessConfigs { i.PublicIPs = append(i.PublicIPs, conf.ExternalIP) } interfaces = append(interfaces, i) } rawKeys := md.Project.Attributes.SSHKeys sshKeys := make(map[string][]ssh.Key) for _, line := range strings.Split(rawKeys, "\n") { if line != "" { tokens := strings.Split(line, ":") userName, key := tokens[0], tokens[1] sshKeys[userName] = append(sshKeys[userName], ssh.Key(key)) } } return metadata.Digest{ Hostname: md.Instance.Hostname, SSHKeys: sshKeys, NetworkInterfaces: interfaces, } }
func TestRetrievesDataFromConfigDrive(t *testing.T) { Convey("Given a mounted config drive", t, func() { tmpdir, err := ioutil.TempDir("", "flamingotest") So(err, ShouldBeNil) defer os.RemoveAll(tmpdir) dataPath := strings.Join([]string{tmpdir, "openstack", "2012-08-10"}, "/") err = os.MkdirAll(dataPath, 0755) So(err, ShouldBeNil) buf, err := ioutil.ReadFile("../openstack/test_metadata/2012-08-10.json") err = file.New(filepath.Join(dataPath, "meta_data.json"), file.Contents(string(buf))) So(err, ShouldBeNil) err = file.New(filepath.Join(dataPath, "user_data"), file.Contents("#cloud-config\n")) So(err, ShouldBeNil) mount := &configdrive.Mount{tmpdir} Convey("It should return a metadata digest", func() { digest, err := mount.FetchMetadata() So(err, ShouldBeNil) So(digest.Hostname, ShouldEqual, "test.novalocal") So(digest.SSHKeys["mykey"], ShouldConsistOf, ssh.Key("ssh-rsa RSA_PUBLIC_KEY Generated by Nova")) }) Convey("It should return user-data", func() { userdata, err := mount.FetchUserdata() So(err, ShouldBeNil) So(userdata["user-data"], ShouldEqual, "#cloud-config\n") }) }) }
func TestGoogleComputeMetadataRetrieval(t *testing.T) { Convey("Given a GCE meta-data service", t, func() { // mock GCE metadata server server := NewMockServer(func(w http.ResponseWriter, r *http.Request) { var json_path string if r.Header.Get("Metadata-Flavor") != "Google" { http.Error(w, "metadata header is not found", http.StatusBadRequest) return } if strings.Contains(r.URL.String(), "project") { json_path = filepath.Join(testMetadataDir, "GCEv1_project.json") } else if strings.Contains(r.URL.String(), "instance") { json_path = filepath.Join(testMetadataDir, "GCEv1_instance.json") } else { http.Error(w, "requested resource is not found", http.StatusNotFound) return } buf, err := ioutil.ReadFile(json_path) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Write(buf) }) service := gce.MetadataService{ URL: provider.FormatURL(server.URL + "/%v/%v"), } Convey("It should retrieve meta-data from GCE meta-data service", func() { digest, err := service.FetchMetadata() So(err, ShouldBeNil) So(digest.Hostname, ShouldEqual, "centos.internal") ifc := digest.PrimaryNetworkInterface() So(ifc.PrivateIP.String(), ShouldEqual, "10.240.45.128") So(ifc.PublicIPs[0].String(), ShouldEqual, "104.155.21.159") So(ifc.PublicIPs[1].String(), ShouldEqual, "104.155.21.160") sshKeys := digest.SSHKeys So(sshKeys["user1"], ShouldConsistOf, ssh.Key("ssh-rsa RSA_PUBLIC_KEY_FOR_USER_1 user1@machine"), ssh.Key("ssh-dsa DSA_PUBLIC_KEY_FOR_USER_1 user1@machine")) So(sshKeys["user2"], ShouldConsistOf, ssh.Key("ssh-rsa RSA_PUBLIC_KEY_FOR_USER_2 user2@machine")) }) Convey("It should retrieve user-data from GCE meta-data service", func() { userdata, err := service.FetchUserdata() So(err, ShouldBeNil) So(userdata["user-data"], ShouldEqual, "#cloud-config\n") }) }) }