Repository: google/exposure-notifications-server
Pull request #424 (2020-05-23 17:24:37)

all: fix all staticcheck warnings
I ran staticcheck 2020.1.4 against the repo and fixed all
the warnings.

google-prow-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/424#" title="Author self-approved">johanbrandhorst</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **crwilcox**
You can assign the PR to them by writing `/assign @crwilcox` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=google%2Fexposure-notifications-server).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/google/exposure-notifications-server/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["crwilcox"]} -->
google-prow-robot: Hi @johanbrandhorst. Thanks for your PR.

I'm waiting for a [google](https://github.com/orgs/google/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://github.com/orgs/google/people) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=google%2Fexposure-notifications-server).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

johanbrandhorst: /assign @crwilcox
mikehelmick: /ok-to-test

Patch:
diff --git a/go.mod b/go.mod
index 4fb4334..fb3f04b 100644
--- a/go.mod
+++ b/go.mod
@@ -22,6 +22,7 @@ require (
 	github.com/golang/protobuf v1.4.2
 	github.com/google/go-cmp v0.4.1
 	github.com/google/uuid v1.1.1
+	github.com/hashicorp/go-hclog v0.13.0
 	github.com/hashicorp/go-memdb v1.2.1 // indirect
 	github.com/hashicorp/go-msgpack v1.1.5 // indirect
 	github.com/hashicorp/go-multierror v1.1.0 // indirect
@@ -29,7 +30,9 @@ require (
 	github.com/hashicorp/go-retryablehttp v0.6.6 // indirect
 	github.com/hashicorp/raft v1.1.2 // indirect
 	github.com/hashicorp/vault v1.2.1-0.20200522144850-6f72d4ff250f
+	github.com/hashicorp/vault-plugin-secrets-kv v0.5.5
 	github.com/hashicorp/vault/api v1.0.5-0.20200522144850-6f72d4ff250f
+	github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267
 	github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d // indirect
 	github.com/jackc/pgx/v4 v4.6.0
 	github.com/jefferai/jsonx v1.0.1 // indirect
diff --git a/go.sum b/go.sum
index 42bba16..19f4e74 100644
--- a/go.sum
+++ b/go.sum
@@ -210,16 +210,21 @@ github.com/containerd/containerd v1.3.3 h1:LoIzb5y9x5l8VKAlyrbusNPXqBY0+kviRloxF
 github.com/containerd/containerd v1.3.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
 github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8=
 github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
+github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-oidc v2.0.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
 github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM=
 github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
+github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c=
 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
@@ -285,6 +290,7 @@ github.com/gammazero/deque v0.0.0-20190130191400-2afb3858e9c7/go.mod h1:GeIq9qoE
 github.com/gammazero/workerpool v0.0.0-20190406235159-88d534f22b56 h1:VzbudKn/nvxYKOdzgkEBS6SSreRjAgoJ+ZeS4wPFkgc=
 github.com/gammazero/workerpool v0.0.0-20190406235159-88d534f22b56/go.mod h1:w9RqFVO2BM3xwWEcAB8Fwp0OviTBBEiRmSBDfbXnd3w=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew=
 github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
 github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck=
 github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
@@ -429,10 +435,13 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
 github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI=
 github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI=
 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
 github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
@@ -684,6 +693,7 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht
 github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
 github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
 github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
+github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f/go.mod h1:KDSfL7qe5ZfQqvlDMkVjCztbmcpp/c8M77vhQP8ZPvk=
 github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -925,6 +935,7 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
 github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
+github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec h1:6ncX5ko6B9LntYM0YBRXkiSaZMmLYeZ/NWcmeB43mMY=
 github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
 github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y=
 github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10=
@@ -955,6 +966,7 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
 github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
 github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
 github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
@@ -984,12 +996,15 @@ github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1C
 github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8=
 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
 github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ=
 github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
+github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
@@ -999,6 +1014,7 @@ github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhe
 github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
 github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
diff --git a/internal/authorizedapp/database_provider.go b/internal/authorizedapp/database_provider.go
index a47c00a..5a97635 100644
--- a/internal/authorizedapp/database_provider.go
+++ b/internal/authorizedapp/database_provider.go
@@ -86,7 +86,7 @@ func (p *DatabaseProvider) checkCache(name string) (*model.AuthorizedApp, bool,
 	item, ok := p.cache[name]
 	if ok && time.Since(item.cachedAt) <= p.cacheDuration {
 		if item.value == nil {
-			return nil, true, AppNotFound
+			return nil, true, ErrAppNotFound
 		}
 		return item.value, true, nil
 	}
@@ -110,7 +110,7 @@ func (p *DatabaseProvider) AppConfig(ctx context.Context, name string) (*model.A
 	item, ok := p.cache[name]
 	if ok && time.Since(item.cachedAt) <= p.cacheDuration {
 		if item.value == nil {
-			return nil, AppNotFound
+			return nil, ErrAppNotFound
 		}
 		return item.value, nil
 	}
@@ -130,7 +130,7 @@ func (p *DatabaseProvider) AppConfig(ctx context.Context, name string) (*model.A
 
 	// Handle not found.
 	if config == nil {
-		return nil, AppNotFound
+		return nil, ErrAppNotFound
 	}
 
 	// Returned config.
diff --git a/internal/authorizedapp/memory_provider.go b/internal/authorizedapp/memory_provider.go
index ba6d4e5..c70108e 100644
--- a/internal/authorizedapp/memory_provider.go
+++ b/internal/authorizedapp/memory_provider.go
@@ -42,7 +42,7 @@ func NewMemoryProvider(ctx context.Context, _ *Config) (Provider, error) {
 func (p *MemoryProvider) AppConfig(ctx context.Context, name string) (*model.AuthorizedApp, error) {
 	val, ok := p.Data[name]
 	if !ok {
-		return nil, AppNotFound
+		return nil, ErrAppNotFound
 	}
 	return val, nil
 }
diff --git a/internal/authorizedapp/model/authorized_app_model.go b/internal/authorizedapp/model/authorized_app_model.go
index 876300a..238677c 100644
--- a/internal/authorizedapp/model/authorized_app_model.go
+++ b/internal/authorizedapp/model/authorized_app_model.go
@@ -78,7 +78,7 @@ func (c *AuthorizedApp) Validate() []string {
 	if !(c.Platform == "android" || c.Platform == "ios" || c.Platform == "both") {
 		errors = append(errors, "platform must be one if {'android', 'ios', or 'both'}")
 	}
-	if c.SafetyNetDisabled == false {
+	if !c.SafetyNetDisabled {
 		if c.SafetyNetPastTime < 0 {
 			errors = append(errors, "SafetyNetPastTime cannot be negative")
 		}
@@ -86,7 +86,7 @@ func (c *AuthorizedApp) Validate() []string {
 			errors = append(errors, "SafetyNetFutureTime cannot be negative")
 		}
 	}
-	if c.DeviceCheckDisabled == false {
+	if !c.DeviceCheckDisabled {
 		if c.DeviceCheckKeyID == "" {
 			errors = append(errors, "When DeviceCheck is enabled, DeviceCheckKeyID cannot be empty")
 		}
diff --git a/internal/authorizedapp/provider.go b/internal/authorizedapp/provider.go
index 3768f79..8d36088 100644
--- a/internal/authorizedapp/provider.go
+++ b/internal/authorizedapp/provider.go
@@ -21,9 +21,9 @@ import (
 	"github.com/google/exposure-notifications-server/internal/authorizedapp/model"
 )
 
-// AppNotFound is the sentinel error returned when AppConfig fails to find an
+// ErrAppNotFound is the sentinel error returned when AppConfig fails to find an
 // app with the given name.
-var AppNotFound = errors.New("app not found")
+var ErrAppNotFound = errors.New("app not found")
 
 // Provider defines possible AuthorizedApp providers.
 type Provider interface {
diff --git a/internal/database/connection.go b/internal/database/connection.go
index b83d5b4..9939ba0 100644
--- a/internal/database/connection.go
+++ b/internal/database/connection.go
@@ -27,24 +27,6 @@ import (
 	"github.com/jackc/pgx/v4/pgxpool"
 )
 
-var (
-	validSSLModes = []string{
-		"disable",     // No SSL
-		"require",     // Always SSL (skip verification)
-		"verify-ca",   // Always SSL (verify that the certificate presented by the server was signed by a trusted CA)
-		"verify-full", // Always SSL (verify that the certification presented by
-	}
-)
-
-type config struct {
-	env       string
-	part      string
-	def       interface{}
-	req       bool
-	valid     []string
-	writeFile bool
-}
-
 type DB struct {
 	Pool *pgxpool.Pool
 }
diff --git a/internal/database/database.go b/internal/database/database.go
index 87f5fd2..8ed799b 100644
--- a/internal/database/database.go
+++ b/internal/database/database.go
@@ -16,7 +16,6 @@ package database
 
 import (
 	"context"
-	"database/sql"
 	"errors"
 	"fmt"
 
@@ -31,16 +30,6 @@ var (
 	ErrKeyConflict = errors.New("key conflict")
 )
 
-func toNullString(s string) sql.NullString {
-	if s == "" {
-		return sql.NullString{}
-	}
-	return sql.NullString{
-		String: s,
-		Valid:  true,
-	}
-}
-
 // InTx runs the given function f within a transaction with isolation level isoLevel.
 func (db *DB) InTx(ctx context.Context, isoLevel pgx.TxIsoLevel, f func(tx pgx.Tx) error) error {
 	conn, err := db.Pool.Acquire(ctx)
diff --git a/internal/database/iterator.go b/internal/database/iterator.go
deleted file mode 100644
index 1700a0a..0000000
--- a/internal/database/iterator.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package database
-
-import (
-	pgx "github.com/jackc/pgx/v4"
-	"github.com/jackc/pgx/v4/pgxpool"
-)
-
-type rowIterator struct {
-	conn *pgxpool.Conn
-	rows pgx.Rows
-}
-
-func newRowIterator(conn *pgxpool.Conn, rows pgx.Rows) *rowIterator {
-	return &rowIterator{conn, rows}
-}
-
-func (i *rowIterator) next() (done bool, err error) {
-	if i.rows == nil {
-		return true, nil
-	}
-	if !i.rows.Next() {
-		return true, i.rows.Err()
-	}
-	return false, nil
-}
-
-func (i *rowIterator) close() error {
-	defer i.conn.Release()
-	if i.rows != nil {
-		i.rows.Close()
-		return i.rows.Err()
-	}
-	return nil
-}
diff --git a/internal/export/exportfile.go b/internal/export/exportfile.go
index c3c7fc3..8981b98 100644
--- a/internal/export/exportfile.go
+++ b/internal/export/exportfile.go
@@ -28,7 +28,7 @@ import (
 
 	"github.com/google/exposure-notifications-server/internal/pb/export"
 
-	"github.com/golang/protobuf/proto"
+	"google.golang.org/protobuf/proto"
 )
 
 const (
diff --git a/internal/federationin/database/federationin.go b/internal/federationin/database/federationin.go
index 0d6c7ef..10e7d92 100644
--- a/internal/federationin/database/federationin.go
+++ b/internal/federationin/database/federationin.go
@@ -168,7 +168,7 @@ func (db *FederationInDB) StartFederationInSync(ctx context.Context, q *model.Fe
 	}
 
 	finalize := func(maxTimestamp time.Time, totalInserted int) error {
-		completed := started.Add(time.Now().Sub(startedTimer))
+		completed := started.Add(time.Since(startedTimer))
 
 		return db.db.InTx(ctx, pgx.Serializable, func(tx pgx.Tx) error {
 			// Special case: when no keys are pulled, the maxTimestamp will be 0, so we don't update the
diff --git a/internal/publish/model/exposure_model.go b/internal/publish/model/exposure_model.go
index 611aab0..03b3512 100644
--- a/internal/publish/model/exposure_model.go
+++ b/internal/publish/model/exposure_model.go
@@ -278,7 +278,7 @@ func (t *Transformer) TransformPublish(inData *Publish, batchTime time.Time) ([]
 	for _, exposureKey := range inData.Keys {
 		exposure, err := TransformExposureKey(exposureKey, inData.AppPackageName, upcaseRegions, createdAt, minIntervalNumber, maxIntervalNumber)
 		if err != nil {
-			return nil, fmt.Errorf("Invalid publish data: %v", err)
+			return nil, fmt.Errorf("invalid publish data: %v", err)
 		}
 		entities = append(entities, exposure)
 	}
diff --git a/internal/publish/publish.go b/internal/publish/publish.go
index 8338fd0..ffaf282 100644
--- a/internal/publish/publish.go
+++ b/internal/publish/publish.go
@@ -93,7 +93,7 @@ func (h *publishHandler) handleRequest(w http.ResponseWriter, r *http.Request) r
 		// Config loaded, but app with that name isn't registered. This can also
 		// happen if the app was recently registered but the cache hasn't been
 		// refreshed.
-		if err == authorizedapp.AppNotFound {
+		if err == authorizedapp.ErrAppNotFound {
 			message := fmt.Sprintf("unauthorized app: %v", data.AppPackageName)
 			logger.Error(message)
 			return response{status: http.StatusUnauthorized, message: message, metric: "publish-app-not-authorized", count: 1}
diff --git a/internal/secrets/hashicorp_vault.go b/internal/secrets/hashicorp_vault.go
index 573a398..c32f177 100644
--- a/internal/secrets/hashicorp_vault.go
+++ b/internal/secrets/hashicorp_vault.go
@@ -16,7 +16,9 @@ package secrets
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
+	"net/url"
 	"strconv"
 
 	vaultapi "github.com/hashicorp/vault/api"
@@ -45,37 +47,65 @@ func NewHashiCorpVault(ctx context.Context) (SecretManager, error) {
 
 // GetSecretValue implements the SecretManager interface. Secrets are specified
 // as the path to the secret in Vault. Secrets are expected to have the string
-// value for the secret in a key named "value".
+// value for the secret in a key named "value" in the "data" key. This matches
+// the schema returned by the KVv2 secrets engine:
+//
+//     $ vault secrets enable -version=2 kv
+//     $ vault kv put my-secret value="abc123"
 //
 // For example:
 //
-//     /secret/data/my-secret?version=5 #=> { "value": "dajkfl32ip2" }
+//     /secret/data/my-secret #=> { "data": { "value": "dajkfl32ip2" } }
 //
 // Note: this technically allows you to fetch dynamic secrets, but this library
 // makes no attempt at renewing leases!
 func (kv *HashiCorpVault) GetSecretValue(ctx context.Context, name string) (string, error) {
-	secret, err := kv.client.Logical().Read(name)
+	u, err := url.Parse(name)
+	if err != nil {
+		return "", fmt.Errorf("failed to parse name: %w", err)
+	}
+
+	name, version := u.Path, u.Query().Get("version")
+	if version == "" {
+		version = "1"
+	}
+
+	secret, err := kv.client.Logical().ReadWithData(name, map[string][]string{
+		"version": {version},
+	})
 	if err != nil {
-		return "", fmt.Errorf("failed to access secret %v: %w", name, err)
+		return "", fmt.Errorf("failed to access secret: %w", err)
 	}
 	if secret == nil || secret.Data == nil {
-		return "", fmt.Errorf("found secret %v, but value was nil", name)
+		return "", fmt.Errorf("secret data is nil")
+	}
+
+	// Check if the "data" key is present.
+	dataRaw, ok := secret.Data["data"]
+	if !ok {
+		return "", fmt.Errorf("missing 'data' key")
+	}
+
+	data, ok := dataRaw.(map[string]interface{})
+	if !ok {
+		return "", fmt.Errorf("data is not a map")
 	}
 
-	// Check if the "value" key is present.
-	raw, ok := secret.Data["value"]
+	valueRaw, ok := data["value"]
 	if !ok {
-		return "", fmt.Errorf("found secret %v, does not have 'value' key", name)
+		return "", fmt.Errorf("missing 'value' key")
 	}
 
 	// Vault values are map[string]interface{}, so coerce to a string.
-	switch typ := raw.(type) {
+	switch typ := valueRaw.(type) {
 	case string:
 		return typ, nil
 	case []byte:
 		return string(typ), nil
 	case bool:
 		return strconv.FormatBool(typ), nil
+	case json.Number:
+		return typ.String(), nil
 	case int, int8, int16, int32, int64:
 		return fmt.Sprintf("%d", typ), nil
 	case uint, uint8, uint16, uint32, uint64:
diff --git a/internal/storage/storage.go b/internal/storage/storage.go
index 80ea5c5..1e8769f 100644
--- a/internal/storage/storage.go
+++ b/internal/storage/storage.go
@@ -75,6 +75,6 @@ func CreateBlobstore(ctx context.Context, config BlobstoreConfig) (Blobstore, er
 	case None:
 		return NewNoopBlobstore(ctx)
 	default:
-		return nil, fmt.Errorf("Unknown BlobstoreType: %v", config.BlobstoreType)
+		return nil, fmt.Errorf("unknown BlobstoreType: %v", config.BlobstoreType)
 	}
 }
diff --git a/tools/admin-console/authorizedapps.go b/tools/admin-console/authorizedapps.go
index 5c54518..27bc6d2 100644
--- a/tools/admin-console/authorizedapps.go
+++ b/tools/admin-console/authorizedapps.go
@@ -48,7 +48,6 @@ func (h *appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	m := TemplateMap{}
 	m.AddErrors("Invalid request.")
 	h.config.RenderTemplate(w, "error", m)
-	return
 }
 
 func (h *appHandler) renderError(m TemplateMap, msg string, w http.ResponseWriter) {
diff --git a/tools/export-generate/main.go b/tools/export-generate/main.go
index fb076ea..01d61e2 100644
--- a/tools/export-generate/main.go
+++ b/tools/export-generate/main.go
@@ -186,8 +186,8 @@ func getSigningKey(fileName string) (*ecdsa.PrivateKey, error) {
 
 // Parse PEM encoded Elliptic Curve Private Key Structure
 func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) {
-	ErrNotECPrivateKey := errors.New("Key is not a valid ECDSA private key")
-	ErrKeyMustBePEMEncoded := errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key")
+	ErrNotECPrivateKey := errors.New("key is not a valid ECDSA private key")
+	ErrKeyMustBePEMEncoded := errors.New("invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key")
 	var err error
 
 	// Parse PEM block


Test patch:
diff --git a/internal/authorizedapp/database/authorized_app_test.go b/internal/authorizedapp/database/authorized_app_test.go
index c7670cb..5ad30dd 100644
--- a/internal/authorizedapp/database/authorized_app_test.go
+++ b/internal/authorizedapp/database/authorized_app_test.go
@@ -96,6 +96,9 @@ func TestAuthorizedAppLifecycle(t *testing.T) {
 	}
 
 	readBack, err = aadb.GetAuthorizedApp(ctx, sm, source.AppPackageName)
+	if err != nil {
+		t.Errorf("unexpected error seen: %v", err)
+	}
 	if readBack != nil {
 		t.Fatal("expected record to be deleted, but it wasn't")
 	}
diff --git a/internal/database/database_test_util.go b/internal/database/database_test_util.go
index 1be69a3..8c7d35f 100644
--- a/internal/database/database_test_util.go
+++ b/internal/database/database_test_util.go
@@ -22,7 +22,6 @@ import (
 	"testing"
 
 	"github.com/golang-migrate/migrate/v4"
-	"github.com/jackc/pgx/v4/pgxpool"
 	"github.com/kelseyhightower/envconfig"
 
 	// imported to register the postgres migration driver
@@ -110,14 +109,6 @@ func createDatabase(ctx context.Context, db *DB, name string) error {
 	return err
 }
 
-func mustExec(t *testing.T, conn *pgxpool.Conn, stmt string, args ...interface{}) {
-	t.Helper()
-	_, err := conn.Exec(context.Background(), stmt, args...)
-	if err != nil {
-		t.Fatalf("executing %s: %v", stmt, err)
-	}
-}
-
 func ResetTestDB(t *testing.T, testDB *DB) {
 	t.Helper()
 	ctx := context.Background()
diff --git a/internal/federationin/federationin_test.go b/internal/federationin/federationin_test.go
index 1121c62..5cd7146 100644
--- a/internal/federationin/federationin_test.go
+++ b/internal/federationin/federationin_test.go
@@ -89,7 +89,7 @@ func (sdb *syncDB) startFederationSync(ctx context.Context, query *model.Federat
 	timerStart := time.Now()
 	return syncID, func(maxTimestamp time.Time, totalInserted int) error {
 		sdb.syncCompleted = true
-		sdb.completed = start.Add(time.Now().Sub(timerStart))
+		sdb.completed = start.Add(time.Since(timerStart))
 		sdb.maxTimestamp = maxTimestamp
 		sdb.totalInserted = totalInserted
 		return nil
diff --git a/internal/handlers/delay_test.go b/internal/handlers/delay_test.go
index d815266..a2a28de 100644
--- a/internal/handlers/delay_test.go
+++ b/internal/handlers/delay_test.go
@@ -24,7 +24,6 @@ import (
 type th struct{}
 
 func (*th) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	return
 }
 
 func TestDelayedReturn(t *testing.T) {
diff --git a/internal/publish/model/exposure_model_test.go b/internal/publish/model/exposure_model_test.go
index 58e72ac..05625d7 100644
--- a/internal/publish/model/exposure_model_test.go
+++ b/internal/publish/model/exposure_model_test.go
@@ -68,7 +68,7 @@ func TestInvalidBase64(t *testing.T) {
 	batchTime := time.Date(2020, 3, 1, 10, 43, 1, 0, time.UTC)
 
 	_, err = transformer.TransformPublish(source, batchTime)
-	expErr := `Invalid publish data: illegal base64 data at input byte 4`
+	expErr := `invalid publish data: illegal base64 data at input byte 4`
 	if err == nil || err.Error() != expErr {
 		t.Errorf("expected error '%v', got: %v", expErr, err)
 	}
diff --git a/internal/secrets/hashicorp_vault_test.go b/internal/secrets/hashicorp_vault_test.go
new file mode 100644
index 0000000..de2cd64
--- /dev/null
+++ b/internal/secrets/hashicorp_vault_test.go
@@ -0,0 +1,205 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package secrets
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"testing"
+
+	vaultlog "github.com/hashicorp/go-hclog"
+	vaultkv "github.com/hashicorp/vault-plugin-secrets-kv"
+	vaultapi "github.com/hashicorp/vault/api"
+	vaulthttp "github.com/hashicorp/vault/http"
+	vaultlogical "github.com/hashicorp/vault/sdk/logical"
+	vault "github.com/hashicorp/vault/vault"
+)
+
+func TestHashiCorpVault_GetSecretValue(t *testing.T) {
+	cases := []struct {
+		name   string
+		setup  func(client *vaultapi.Client) error
+		secret string
+		exp    string
+		err    bool
+		errMsg string
+	}{
+		{
+			name: "no_exists",
+			setup: func(client *vaultapi.Client) error {
+				return nil
+			},
+			secret: "kv/data/my-secret",
+			err:    true,
+			errMsg: "data is nil",
+		},
+		{
+			name: "no_value",
+			setup: func(client *vaultapi.Client) error {
+				_, err := client.Logical().Write("kv/data/my-secret", map[string]interface{}{
+					"data": map[string]interface{}{
+						"not_value": "hello",
+					},
+				})
+				return err
+			},
+			secret: "kv/data/my-secret",
+			err:    true,
+			errMsg: "missing 'value' key",
+		},
+		{
+			name: "versioned",
+			setup: func(client *vaultapi.Client) error {
+				for i := 1; i <= 5; i++ {
+					if _, err := client.Logical().Write("kv/data/my-secret", map[string]interface{}{
+						"data": map[string]interface{}{
+							"value": fmt.Sprintf("hello-%d", i),
+						},
+					}); err != nil {
+						return err
+					}
+				}
+				return nil
+			},
+			secret: "kv/data/my-secret?version=3",
+			exp:    "hello-3",
+		},
+		{
+			name: "string",
+			setup: func(client *vaultapi.Client) error {
+				_, err := client.Logical().Write("kv/data/my-secret", map[string]interface{}{
+					"data": map[string]interface{}{
+						"value": "hello",
+					},
+				})
+				return err
+			},
+			secret: "kv/data/my-secret",
+			exp:    "hello",
+		},
+		{
+			name: "byte",
+			setup: func(client *vaultapi.Client) error {
+				_, err := client.Logical().Write("kv/data/my-secret", map[string]interface{}{
+					"data": map[string]interface{}{
+						"value": byte(123),
+					},
+				})
+				return err
+			},
+			secret: "kv/data/my-secret",
+			exp:    "123",
+		},
+		{
+			name: "bool",
+			setup: func(client *vaultapi.Client) error {
+				_, err := client.Logical().Write("kv/data/my-secret", map[string]interface{}{
+					"data": map[string]interface{}{
+						"value": true,
+					},
+				})
+				return err
+			},
+			secret: "kv/data/my-secret",
+			exp:    "true",
+		},
+		{
+			name: "uint",
+			setup: func(client *vaultapi.Client) error {
+				_, err := client.Logical().Write("kv/data/my-secret", map[string]interface{}{
+					"data": map[string]interface{}{
+						"value": uint(5),
+					},
+				})
+				return err
+			},
+			secret: "kv/data/my-secret",
+			exp:    "5",
+		},
+		{
+			name: "other",
+			setup: func(client *vaultapi.Client) error {
+				_, err := client.Logical().Write("kv/data/my-secret", map[string]interface{}{
+					"data": map[string]interface{}{
+						"value": map[string]interface{}{
+							"foo": "bar",
+						},
+					},
+				})
+				return err
+			},
+			secret: "kv/data/my-secret",
+			err:    true,
+			errMsg: "is of unknown type",
+		},
+	}
+
+	for _, tc := range cases {
+		tc := tc
+
+		t.Run(tc.name, func(t *testing.T) {
+			// Create a Vault server.
+			ctx := context.Background()
+			core, _, token := vault.TestCoreUnsealedWithConfig(t, &vault.CoreConfig{
+				DisableMlock: true,
+				DisableCache: true,
+				Logger:       vaultlog.NewNullLogger(),
+				LogicalBackends: map[string]vaultlogical.Factory{
+					"kv": vaultkv.Factory,
+				},
+			})
+			ln, addr := vaulthttp.TestServer(t, core)
+			defer ln.Close()
+
+			// Create the client.
+			client, err := vaultapi.NewClient(&vaultapi.Config{Address: addr})
+			if err != nil {
+				t.Fatal(err)
+			}
+			client.SetToken(token)
+
+			// Enable KVv2.
+			if err := client.Sys().Mount("kv/", &vaultapi.MountInput{
+				Type: "kv-v2",
+			}); err != nil {
+				t.Fatal(err)
+			}
+
+			// Run setup.
+			if err := tc.setup(client); err != nil {
+				t.Fatal(err)
+			}
+
+			// Create the secrets client.
+			secrets := &HashiCorpVault{client: client}
+
+			val, err := secrets.GetSecretValue(ctx, tc.secret)
+			if err != nil {
+				if !tc.err {
+					t.Fatal(err)
+				}
+
+				if got, want := err.Error(), tc.errMsg; !strings.Contains(got, want) {
+					t.Errorf("expected %q to matc %q", got, want)
+				}
+			}
+
+			if got, want := val, tc.exp; got != want {
+				t.Errorf("expected %q to be %q", got, want)
+			}
+		})
+	}
+}
diff --git a/internal/signing/hashicorp_vault_test.go b/internal/signing/hashicorp_vault_test.go
index 879e25e..b21052a 100644
--- a/internal/signing/hashicorp_vault_test.go
+++ b/internal/signing/hashicorp_vault_test.go
@@ -25,9 +25,11 @@ import (
 	"strings"
 	"testing"
 
+	vaultlog "github.com/hashicorp/go-hclog"
 	vaultapi "github.com/hashicorp/vault/api"
-	"github.com/hashicorp/vault/builtin/logical/transit"
+	vaulttransit "github.com/hashicorp/vault/builtin/logical/transit"
 	vaulthttp "github.com/hashicorp/vault/http"
+	vaultlogical "github.com/hashicorp/vault/sdk/logical"
 	vault "github.com/hashicorp/vault/vault"
 )
 
@@ -69,10 +71,6 @@ func TestNewHashiCorpVaultSigner(t *testing.T) {
 }
 
 func TestHashiCorpVaultSigner_Public(t *testing.T) {
-	if err := vault.AddTestLogicalBackend("transit", transit.Factory); err != nil {
-		t.Fatal(err)
-	}
-
 	cases := []struct {
 		name       string
 		setup      func(client *vaultapi.Client) error
@@ -143,7 +141,14 @@ func TestHashiCorpVaultSigner_Public(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			// Create a Vault server.
 			ctx := context.Background()
-			core, _, token := vault.TestCoreUnsealed(t)
+			core, _, token := vault.TestCoreUnsealedWithConfig(t, &vault.CoreConfig{
+				DisableMlock: true,
+				DisableCache: true,
+				Logger:       vaultlog.NewNullLogger(),
+				LogicalBackends: map[string]vaultlogical.Factory{
+					"transit": vaulttransit.Factory,
+				},
+			})
 			ln, addr := vaulthttp.TestServer(t, core)
 			defer ln.Close()
 
@@ -189,13 +194,16 @@ func TestHashiCorpVaultSigner_Public(t *testing.T) {
 }
 
 func TestHashiCorpVaultSigner_Sign(t *testing.T) {
-	if err := vault.AddTestLogicalBackend("transit", transit.Factory); err != nil {
-		t.Fatal(err)
-	}
-
 	// Create a Vault server.
 	ctx := context.Background()
-	core, _, token := vault.TestCoreUnsealed(t)
+	core, _, token := vault.TestCoreUnsealedWithConfig(t, &vault.CoreConfig{
+		DisableMlock: true,
+		DisableCache: true,
+		Logger:       vaultlog.NewNullLogger(),
+		LogicalBackends: map[string]vaultlogical.Factory{
+			"transit": vaulttransit.Factory,
+		},
+	})
 	ln, addr := vaulthttp.TestServer(t, core)
 	defer ln.Close()
 
@@ -207,8 +215,8 @@ func TestHashiCorpVaultSigner_Sign(t *testing.T) {
 	client.SetToken(token)
 
 	// Enable transit.
-	if _, err := client.Logical().Write("sys/mounts/transit", map[string]interface{}{
-		"type": "transit",
+	if err := client.Sys().Mount("transit/", &vaultapi.MountInput{
+		Type: "transit",
 	}); err != nil {
 		t.Fatal(err)
 	}
diff --git a/testing/enclient/enclient.go b/testing/enclient/enclient.go
index b6046e4..837a97b 100644
--- a/testing/enclient/enclient.go
+++ b/testing/enclient/enclient.go
@@ -13,9 +13,6 @@ import (
 )
 
 const (
-	dkLen               = 16
-	maxTransmissionRisk = 8
-	maxIntervals        = 144
 	// httpTimeout is the maximum amount of time to wait for a response.
 	httpTimeout = 30 * time.Second
 )