defer log.SetOutput(os.Stderr)
tests := []func(args){
+ // Perform most tests with passwd for simplicity
+ fetchPasswdCacheFileDoesNotExist,
+ fetchPasswd404,
+ fetchPasswdEmpty,
+ fetchPasswdInvalid,
+ fetchPasswdLimits,
+ fetchPasswd,
+ // Tests for plain and group
+ fetchPlainEmpty,
+ fetchPlain,
+ fetchGroupEmpty,
+ fetchGroupInvalid,
+ fetchGroupLimits,
+ fetchGroup,
// Special tests
fetchNoConfig,
+ fetchStateCannotRead,
+ fetchStateInvalid,
+ fetchStateCannotWrite,
+ fetchCannotDeploy,
+ fetchSecondFetchFails,
}
cleanup := []string{
}
}
+func fetchPasswdCacheFileDoesNotExist(a args) {
+ t := a.t
+ mustWritePasswdConfig(t, a.url)
+
+ err := mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "file.path \""+passwdPath+"\" must exist")
+
+ mustNotExist(t, statePath, passwdPath, plainPath, groupPath)
+}
+
+func fetchPasswd404(a args) {
+ t := a.t
+ mustWritePasswdConfig(t, a.url)
+ mustCreate(t, passwdPath)
+
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ // 404
+ w.WriteHeader(http.StatusNotFound)
+ }
+
+ err := mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "status code 404")
+
+ mustNotExist(t, statePath, plainPath, groupPath)
+ mustBeOld(a.t, passwdPath)
+}
+
+func fetchPasswdEmpty(a args) {
+ t := a.t
+ mustWritePasswdConfig(t, a.url)
+ mustCreate(t, passwdPath)
+
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ // Empty response
+ }
+
+ err := mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "refusing to use empty passwd file")
+
+ mustNotExist(t, statePath, plainPath, groupPath)
+ mustBeOld(t, passwdPath)
+}
+
+func fetchPasswdInvalid(a args) {
+ t := a.t
+ mustWritePasswdConfig(t, a.url)
+ mustCreate(t, passwdPath)
+
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/passwd" {
+ return
+ }
+
+ fmt.Fprintln(w, "root:x:invalid:0:root:/root:/bin/bash")
+ }
+
+ err := mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "invalid uid in line")
+
+ mustNotExist(t, statePath, plainPath, groupPath)
+ mustBeOld(t, passwdPath)
+}
+
+func fetchPasswdLimits(a args) {
+ t := a.t
+ mustWritePasswdConfig(t, a.url)
+ mustCreate(t, passwdPath)
+
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/passwd" {
+ return
+ }
+
+ fmt.Fprint(w, "root:x:0:0:root:/root:/bin/bash")
+ for i := 0; i < 65536; i++ {
+ fmt.Fprint(w, "x")
+ }
+ fmt.Fprint(w, "\n")
+ }
+
+ err := mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "passwd too large to serialize")
+
+ mustNotExist(t, statePath, plainPath, groupPath)
+ mustBeOld(t, passwdPath)
+}
+
+func fetchPasswd(a args) {
+ t := a.t
+ mustWritePasswdConfig(t, a.url)
+ mustCreate(t, passwdPath)
+ mustHaveHash(t, passwdPath, "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+
+ t.Log("First fetch, write files")
+
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/passwd" {
+ return
+ }
+
+ // No "Last-Modified" header
+ fmt.Fprintln(w, "root:x:0:0:root:/root:/bin/bash")
+ fmt.Fprintln(w, "daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin")
+ }
+
+ err := mainFetch(configPath)
+ if err != nil {
+ t.Error(err)
+ }
+
+ mustNotExist(t, plainPath, groupPath)
+ mustBeNew(t, passwdPath, statePath)
+ // The actual content of passwdPath is verified by the NSS tests
+ mustHaveHash(t, passwdPath, "bbb7db67469b111200400e2470346d5515d64c23")
+
+ t.Log("Fetch again, no support for Last-Modified")
+
+ mustMakeOld(t, passwdPath, statePath)
+
+ err = mainFetch(configPath)
+ if err != nil {
+ t.Error(err)
+ }
+
+ mustNotExist(t, plainPath, groupPath)
+ mustBeNew(t, passwdPath, statePath)
+ mustHaveHash(t, passwdPath, "bbb7db67469b111200400e2470346d5515d64c23")
+
+ t.Log("Fetch again, support for Last-Modified, but not retrieved yet")
+
+ mustMakeOld(t, passwdPath, statePath)
+
+ lastChange := time.Now()
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/passwd" {
+ return
+ }
+
+ modified := r.Header.Get("If-Modified-Since")
+ if modified != "" {
+ x, err := http.ParseTime(modified)
+ if err != nil {
+ t.Fatalf("invalid If-Modified-Since %v",
+ modified)
+ }
+ if !x.Before(lastChange) {
+ w.WriteHeader(http.StatusNotModified)
+ return
+ }
+ }
+
+ w.Header().Add("Last-Modified",
+ lastChange.Format(http.TimeFormat))
+ fmt.Fprintln(w, "root:x:0:0:root:/root:/bin/bash")
+ fmt.Fprintln(w, "daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin")
+ }
+
+ err = mainFetch(configPath)
+ if err != nil {
+ t.Error(err)
+ }
+
+ mustNotExist(t, plainPath, groupPath)
+ mustBeNew(t, passwdPath, statePath)
+ mustHaveHash(t, passwdPath, "bbb7db67469b111200400e2470346d5515d64c23")
+
+ t.Log("Fetch again, support for Last-Modified")
+
+ mustMakeOld(t, passwdPath, statePath)
+
+ err = mainFetch(configPath)
+ if err != nil {
+ t.Error(err)
+ }
+
+ mustNotExist(t, plainPath, groupPath)
+ mustBeOld(t, passwdPath)
+ mustBeNew(t, statePath)
+ mustHaveHash(t, passwdPath, "bbb7db67469b111200400e2470346d5515d64c23")
+
+ t.Log("Corrupt local passwd cache, fetched again")
+
+ os.Chmod(passwdPath, 0644) // make writable again
+ mustCreate(t, passwdPath)
+ mustMakeOld(t, passwdPath, statePath)
+
+ err = mainFetch(configPath)
+ if err != nil {
+ t.Error(err)
+ }
+
+ mustNotExist(t, plainPath, groupPath)
+ mustBeNew(t, passwdPath, statePath)
+ mustHaveHash(t, passwdPath, "bbb7db67469b111200400e2470346d5515d64c23")
+}
+
+func fetchPlainEmpty(a args) {
+ t := a.t
+ mustWriteConfig(t, fmt.Sprintf(`
+statepath = "%[1]s"
+
+[[file]]
+type = "plain"
+url = "%[2]s/plain"
+path = "%[3]s"
+`, statePath, a.url, plainPath))
+ mustCreate(t, plainPath)
+
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ // Empty response
+ }
+
+ err := mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "refusing to use empty response")
+
+ mustNotExist(t, statePath, passwdPath, groupPath)
+ mustBeOld(t, plainPath)
+}
+
+func fetchPlain(a args) {
+ t := a.t
+ mustWriteConfig(t, fmt.Sprintf(`
+statepath = "%[1]s"
+
+[[file]]
+type = "plain"
+url = "%[2]s/plain"
+path = "%[3]s"
+`, statePath, a.url, plainPath))
+ mustCreate(t, plainPath)
+ mustHaveHash(t, plainPath, "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/plain" {
+ return
+ }
+
+ fmt.Fprintln(w, "some file")
+ }
+
+ err := mainFetch(configPath)
+ if err != nil {
+ t.Error(err)
+ }
+
+ mustNotExist(t, passwdPath, groupPath)
+ mustBeNew(t, plainPath, statePath)
+ mustHaveHash(t, plainPath, "0e08b5e8c10abc3e455b75286ba4a1fbd56e18a5")
+
+ // Remaining functionality already tested in fetchPasswd()
+}
+
+func fetchGroupEmpty(a args) {
+ t := a.t
+ mustWriteGroupConfig(t, a.url)
+ mustCreate(t, groupPath)
+
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ // Empty response
+ }
+
+ err := mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "refusing to use empty group file")
+
+ mustNotExist(t, statePath, passwdPath, plainPath)
+ mustBeOld(t, groupPath)
+}
+
+func fetchGroupInvalid(a args) {
+ t := a.t
+ mustWriteGroupConfig(t, a.url)
+ mustCreate(t, groupPath)
+
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/group" {
+ return
+ }
+
+ fmt.Fprintln(w, "root:x::")
+ }
+
+ err := mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "invalid gid in line")
+
+ mustNotExist(t, statePath, passwdPath, plainPath)
+ mustBeOld(t, groupPath)
+}
+
+func fetchGroupLimits(a args) {
+ t := a.t
+ mustWriteGroupConfig(t, a.url)
+ mustCreate(t, groupPath)
+
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/group" {
+ return
+ }
+
+ fmt.Fprint(w, "root:x:0:")
+ for i := 0; i < 65536; i++ {
+ fmt.Fprint(w, "x")
+ }
+ fmt.Fprint(w, "\n")
+ }
+
+ err := mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "group too large to serialize")
+
+ mustNotExist(t, statePath, passwdPath, plainPath)
+ mustBeOld(t, groupPath)
+}
+
+func fetchGroup(a args) {
+ t := a.t
+ mustWriteGroupConfig(t, a.url)
+ mustCreate(t, groupPath)
+ mustHaveHash(t, groupPath, "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/group" {
+ return
+ }
+
+ fmt.Fprintln(w, "root:x:0:")
+ fmt.Fprintln(w, "daemon:x:1:andariel,duriel,mephisto,diablo,baal")
+ }
+
+ err := mainFetch(configPath)
+ if err != nil {
+ t.Error(err)
+ }
+
+ mustNotExist(t, passwdPath, plainPath)
+ mustBeNew(t, groupPath, statePath)
+ // The actual content of groupPath is verified by the NSS tests
+ mustHaveHash(t, groupPath, "8c27a8403278ba2e392b86d98d4dff1fdefcafdd")
+
+ // Remaining functionality already tested in fetchPasswd()
+}
+
func fetchNoConfig(a args) {
t := a.t
mustNotExist(t, configPath, statePath, passwdPath, plainPath, groupPath)
}
+
+func fetchStateCannotRead(a args) {
+ t := a.t
+ mustWritePasswdConfig(t, a.url)
+
+ mustCreate(t, statePath)
+ err := os.Chmod(statePath, 0000)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ statePath+": permission denied")
+
+ mustNotExist(t, passwdPath, plainPath, groupPath)
+}
+
+func fetchStateInvalid(a args) {
+ t := a.t
+ mustWriteGroupConfig(t, a.url)
+ mustCreate(t, statePath)
+
+ err := mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "unexpected end of JSON input")
+
+ mustNotExist(t, groupPath, passwdPath, plainPath)
+ mustBeOld(t, statePath)
+}
+
+func fetchStateCannotWrite(a args) {
+ t := a.t
+ mustWriteGroupConfig(t, a.url)
+ mustCreate(t, groupPath)
+ mustHaveHash(t, groupPath, "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ // To prevent mainFetch() from trying to update groupPath
+ // which will also fail
+ w.WriteHeader(http.StatusNotModified)
+ }
+
+ err := os.Chmod("testdata", 0500)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Chmod("testdata", 0755)
+
+ err = mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "permission denied")
+
+ mustNotExist(t, statePath, passwdPath, plainPath)
+ mustBeOld(t, groupPath)
+}
+
+func fetchCannotDeploy(a args) {
+ t := a.t
+ mustWriteGroupConfig(t, a.url)
+ mustCreate(t, groupPath)
+ mustHaveHash(t, groupPath, "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/group" {
+ return
+ }
+
+ fmt.Fprintln(w, "root:x:0:")
+ fmt.Fprintln(w, "daemon:x:1:andariel,duriel,mephisto,diablo,baal")
+ }
+
+ err := os.Chmod("testdata", 0500)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Chmod("testdata", 0755)
+
+ err = mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "permission denied")
+
+ mustNotExist(t, statePath, passwdPath, plainPath)
+ mustBeOld(t, groupPath)
+}
+
+func fetchSecondFetchFails(a args) {
+ t := a.t
+ mustWriteConfig(t, fmt.Sprintf(`
+statepath = "%[1]s"
+
+[[file]]
+type = "passwd"
+url = "%[2]s/passwd"
+path = "%[3]s"
+
+[[file]]
+type = "group"
+url = "%[2]s/group"
+path = "%[4]s"
+`, statePath, a.url, passwdPath, groupPath))
+ mustCreate(t, passwdPath)
+ mustCreate(t, groupPath)
+ mustHaveHash(t, passwdPath, "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+ mustHaveHash(t, groupPath, "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path == "/passwd" {
+ fmt.Fprintln(w, "root:x:0:0:root:/root:/bin/bash")
+ }
+ if r.URL.Path == "/group" {
+ w.WriteHeader(http.StatusNotFound)
+ }
+ }
+
+ err := mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "status code 404")
+
+ mustNotExist(t, statePath, plainPath)
+ // Even though passwd was successfully fetched, no files were modified
+ // because the second fetch failed
+ mustBeOld(t, passwdPath, groupPath)
+}