+
+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"
+ca = "%[5]s"
+
+[[file]]
+type = "group"
+url = "%[2]s/group"
+path = "%[4]s"
+ca = "%[5]s"
+`, statePath, a.url, passwdPath, groupPath, tlsCAPath))
+ 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)
+}
+
+func fetchBasicAuth(a args) {
+ t := a.t
+ mustWritePasswdConfig(t, a.url)
+ mustCreate(t, passwdPath)
+ mustHaveHash(t, passwdPath, "da39a3ee5e6b4b0d3255bfef95601890afd80709")
+
+ validUser := "username"
+ validPass := "password"
+
+ *a.handler = func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/passwd" {
+ return
+ }
+
+ user, pass, ok := r.BasicAuth()
+ // NOTE: Do not use this in production because it permits
+ // attackers to determine the length of user/pass. Instead use
+ // hashes and subtle.ConstantTimeCompare().
+ if !ok || user != validUser || pass != validPass {
+ w.Header().Set("WWW-Authenticate", `Basic realm="Test"`)
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+
+ fmt.Fprintln(w, "root:x:0:0:root:/root:/bin/bash")
+ fmt.Fprintln(w, "daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin")
+ }
+
+ t.Log("Missing authentication")
+
+ err := mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "status code 401")
+
+ mustNotExist(t, statePath, groupPath, plainPath)
+ mustBeOld(t, passwdPath)
+
+ t.Log("Unsafe config permissions")
+
+ mustWriteConfig(t, fmt.Sprintf(`
+statepath = "%[1]s"
+
+[[file]]
+type = "passwd"
+url = "%[2]s/passwd"
+path = "%[3]s"
+ca = "%[4]s"
+username = "%[5]s"
+password = "%[6]s"
+`, statePath, a.url, passwdPath, tlsCAPath, validUser, validPass))
+
+ err = os.Chmod(configPath, 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "file[0].username/passsword in use and unsafe permissions "+
+ "-rw-r--r-- on \"testdata/config.toml\"")
+
+ mustNotExist(t, statePath, groupPath, plainPath)
+ mustBeOld(t, passwdPath)
+
+ t.Log("Working authentication")
+
+ err = os.Chmod(configPath, 0600)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = mainFetch(configPath)
+ if err != nil {
+ t.Error(err)
+ }
+
+ mustNotExist(t, plainPath, groupPath)
+ mustBeNew(t, passwdPath, statePath)
+ mustHaveHash(t, passwdPath, "bbb7db67469b111200400e2470346d5515d64c23")
+}
+
+func fetchInvalidCA(a args) {
+ t := a.t
+
+ // System CA
+
+ mustWriteConfig(t, fmt.Sprintf(`
+statepath = "%[1]s"
+
+[[file]]
+type = "passwd"
+url = "%[2]s/passwd"
+path = "%[3]s"
+`, statePath, a.url, passwdPath))
+ mustCreate(t, passwdPath)
+ mustHaveHash(t, passwdPath, "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")
+ }
+ }
+
+ err := mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "x509: certificate signed by unknown authority")
+
+ mustNotExist(t, statePath, plainPath, groupPath)
+ mustBeOld(t, passwdPath)
+
+ // Invalid CA
+
+ mustWriteConfig(t, fmt.Sprintf(`
+statepath = "%[1]s"
+
+[[file]]
+type = "passwd"
+url = "%[2]s/passwd"
+path = "%[3]s"
+ca = "%[4]s"
+`, statePath, a.url, passwdPath, tlsCA2Path))
+ mustCreate(t, passwdPath)
+ mustHaveHash(t, passwdPath, "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")
+ }
+ }
+
+ err = mainFetch(configPath)
+ mustBeErrorWithSubstring(t, err,
+ "x509: certificate signed by unknown authority")
+
+ mustNotExist(t, statePath, plainPath, groupPath)
+ mustBeOld(t, passwdPath)
+}