Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP-level tests for puma-dev server via goroutine #233

Merged
merged 9 commits into from Feb 26, 2020

Conversation

nonrational
Copy link
Member

@nonrational nonrational commented Feb 26, 2020

This PR adds end-to-end HTTP level tests for puma-dev server.

Purpose

Alright folks. This is the one you've been waiting for. We can now run a puma-dev server in tests and throw HTTP requests against it to ensure proper behavior. 💥

This is a big one, so I'm gonna take you through it commit by commit.

Changes, Commit-by-Commit

a1644d2 ensure temp dirs are cleaned up, more testing helpers

This fixes an existing bug in the tests file that didn't correctly clean up some temp directories.

Sidenote: Deferred return is an awesome pattern.

a9dcd8a end-to-end ssl cert test, pull create_cert out of setup_cert for separate use

SetupOurCert generates a puma-dev CA cert if one doesn't already exist and trusts in in the macOS keychain. I extracted the cert generation piece into a separate function, so we can test it and call it as part of running the server as part of tests.

I also made it more explicit that if the cert is already present, we'll return early and assume that it's already trusted. This "feature" ends up being important in tests, because you can't exercise the "trust" branch on TravisCI. It requires an interactive password dialog to unlock the macOS keychain. But, if we pre-generate the cert, we can "trick" the code into thinking the cert has already been trusted previously.

763a40a test puma-dev server w/ rack app, use retry to wait for socket & poll /events

This is the big one. The basic structure is

  1. Symlink a tiny rack app into ~/.gotest-puma-dev
  2. Generate a dummy (but valid) CA for the length of the test run (that will never be trusted). This allows us to skip the "trust" branch that would require user interaction on macOS.
  3. Run go main() with flags to set the app directory to ~/.gotest-puma-dev
  4. Attempt a TCP connection to localhost:9280. Once it returns, you know the server is up.
    Given that it takes some handful of seconds for the server to be ready & listening on 9280, I pulled in avast/retry-go to retry the connection rather than opting for a sleep.
  5. Run 4 subtests inside TestMainPumaDev to verify that
  6. puma-dev /status returns json
  7. rack app boots responds correctly
  8. restart.txt kills the rack app (I was working on Running puma-dev in background will not pickup changes to file after restart. #190 simultaneously, so this seemed like a good thing to add)
  9. unknown app returns ... unknown app.

72d7fe8 clean up gitignore, ensure we don't commit a .ruby-version for dummy rack app
There were some outdated entried in .gitignore that I removed. And, since I needed to install puma, I needed to set a ruby version but don't want to enforce a particular version.

5f58924 set up travis environment to run rack app. skip bundler for now

Speaking of puma, need to install it for Travis and ensure that /etc/resolver is writable. We won't use it in our tests (because we can just use Host: foo), but we need it for puma-dev to boot.

bc49df2 add makefile shortcut for macOS tests that require interactivity keychain unlock

Before I started on the SSL refactor, I wrote a test to exercise the whole thing but it can only be run by manually because it involves unlocking your login keychain via a system prompt.

IMPORTANT NOTE: Since running this test generates AND TRUSTS a new CA cert, it would be irresponsible to leave it lying around in your keychain. Running this test will delete any and all puma-dev CA certs in your keychain. You'll need to run setup again in order to get a new one. But, nobody should be running this test themselves unless they're actively developing related code for puma-dev.

b6006bd add circleci config to test catalina

TravisCI still hasn't released a macOS 10.15 build environment, so I'm trying out Circle's on my fork. I will revert this commit before merging.

Conclusion

You have reached The End. Congratulations. 🎉

Makefile Outdated Show resolved Hide resolved
func generateLivePumaDevCertIfNotExist(t *testing.T) {
liveSupportPath := homedir.MustExpand(dev.SupportDir)
liveCertPath := filepath.Join(liveSupportPath, "cert.pem")
liveKeyPath := filepath.Join(liveSupportPath, "key.pem")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need a valid cert to exist at this path before we start main(). this cert won't be trusted, but it will be "valid".

}
}

func getUrlWithHost(t *testing.T, url string, host string) string {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't use DNS in tests, so we set the Host header. h/t @tubbo for giving me the idea.

if err := os.MkdirAll(path, 0755); err != nil {
panic(err)
}
panic(err)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's more complicated to have this helper make the path if it doesn't exist, so just bail out if you didn't create the directory in the first place.

@@ -98,7 +83,36 @@ func SetupOurCert() error {

keyOut.Close()

return TrustCert(certPath)
return nil
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this diff is a bit hard to read, but I just refactored out most of the SetupOurCert function into GeneratePumaDevCertificateAuthority

stderr, readPipeErr := addTrustedCertCommand.StderrPipe()
if readPipeErr != nil {
return readPipeErr
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wanted to be able to examine the output if this fails, so takes a few extra lines to feed a pipe to the cmd before running it.

return fmt.Sprintf(`for sha in $(security find-certificate -a -c "Puma-dev CA" -Z | awk '/SHA-1/ {print $3}'); do %s; done`, subCommand)
}

if err := exec.Command("sh", "-c", forEachPumaDevKeychainCertSha("security delete-certificate -Z $sha")).Run(); err != nil {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given there might be a few certs with the same common name (because you ran setup several times or something) we need to find all the hashes and delete them that way.

}

func TestSetupOurCert_ensureNotWorldReadable(t *testing.T) {
t.Skip("not implemented yet - https://github.com/puma/puma-dev/issues/215")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure exactly what the attack chain looks like, but if this CA cert and key remain unprivileged user-readable then it stands to reason that a piece of malicious software could read it, sign a new cert for www.whereyoukeepyoursecrets.com with it, hijack your DNS, and you'd be in the 💩right quick and likely never know it.

assert.Nil(t, err)
})

assert.Regexp(t, "^! Add /does/not/exist to your browser to trust CA\\n$", stdOut)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hooray code coverage. 🙃

assert.Fail(t, err.Error())
}

_, err := tls.LoadX509KeyPair(testCertPath, testKeyPath)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

being able to load the certificate vouches for its "validity".

Copy link
Member

@evanphx evanphx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yaaay!

longer names mean don't do this unless you know what you're doing. :D
@nonrational nonrational merged commit 8907643 into puma:master Feb 26, 2020
@nonrational nonrational deleted the end-to-end-test branch February 26, 2020 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants