Repository: google/exposure-notifications-server (go)

Issue:
Embargo keys for 2 hours after expiry.
Exports might not embargo keys long enough.

Changed needed
1) On publish - don't accept keys where the rollingPeriod hasn't ended
2) On export - instead of not entering the current alignment window, don't enter alignment windows that aren't 2 hours old.

/kind enhancement
/assign

Pull request description:
Fixes for issue 252
/fixes #252 

1. On publish, don't accept keys that are still valid, the rolling period hasn't ended (or isn't in the processes of ending during the current rolling interval)
2. On export, ensure that keys were received a certain age ago (2h by default) before being eligible for batching.


Patch:
diff --git a/README.md b/README.md
index 49323c1..d9cfd43 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,20 @@
 # Exposure Notification Reference Server
 
-[https://www.google.com/covid19/exposurenotifications/](https://www.google.com/covid19/exposurenotifications/)
+[COVID‑19 Exposure Notifications API](https://www.google.com/covid19/exposurenotifications/)
 
-In our continued effort to help governments and health authorities during the COVID-19 pandemic, we have authored an open source reference implementation of an Exposure Notifications server. The server reference design implements the Exposure Notifications API and provides reference code for working with both Android and iOS apps that are built by public health authorities. The reference server source code is available on GitHub and can be deployed on any modern infrastructure or cloud provider selected by a public health authority. 
+In our continued effort to help governments and health authorities during the
+COVID-19 pandemic, we have authored an open source reference implementation of
+an Exposure Notifications server.
 
-The reference server implementation accepts, validates, and stores temporary exposure keys from verified mobile devices. It also periodically generates and signs incremental files that will later be downloaded by clients to perform the on-device key matching algorithm. Our hope is by making this privacy-preserving server implementation available to health authorities we can enable their developers to use the open source code to get started quickly. 
+The server reference in this repository implements the Exposure Notifications
+API and provides reference code for working with Android and iOS apps that
+are built by public health authorities. The reference server source code is
+available on GitHub and can be deployed on any infrastructure or cloud
+provider selected by a public health authority.
 
-This repository contains a reference implementation of an exposure notification
-server for use in a mobile exposure notification system.
+Our hope is by making this privacy-preserving server implementation available
+to health authorities, we can enable their developers to use the open source code
+to get started quickly. 
 
 ## Overview
 
@@ -39,7 +46,7 @@ Server here:
 * [Server Functional Requirements](docs/server_functional_requirements.md)
 * [Server Deployment Options](docs/server_deployment_options.md)
 
-# Issues and Questions
+## Issues and Questions
 
 You can open a
 [GitHub Issue](https://github.com/google/exposure-notifications-server/issues/new).
diff --git a/docs/index.md b/docs/index.md
index 74631c8..cc38292 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,24 +1,20 @@
 # Exposure Notification Reference Server
 
-[https://www.google.com/covid19/exposurenotifications/](https://www.google.com/covid19/exposurenotifications/)
+[COVID‑19 Exposure Notifications API](https://www.google.com/covid19/exposurenotifications/)
 
-In our continued effort to help governments and health authorities during the COVID-19 pandemic, we have authored an open source reference implementation of an Exposure Notifications server. The server reference design implements the Exposure Notifications API and provides reference code for working with both Android and iOS apps that are built by public health authorities. The reference server source code is available on GitHub and can be deployed on any modern infrastructure or cloud provider selected by a public health authority. 
+In our continued effort to help governments and health authorities during the
+COVID-19 pandemic, we have authored an open source reference implementation of
+an Exposure Notifications server.
 
-The reference server implementation accepts, validates, and stores temporary exposure keys from verified mobile devices. It also periodically generates and signs incremental files that will later be downloaded by clients to perform the on-device key matching algorithm. Our hope is by making this privacy-preserving server implementation available to health authorities we can enable their developers to use the open source code to get started quickly. 
+The server reference in this repository implements the Exposure Notifications
+API and provides reference code for working with Android and iOS apps that
+are built by public health authorities. The reference server source code is
+available on GitHub and can be deployed on any infrastructure or cloud
+provider selected by a public health authority.
 
-This repository contains a reference implementation of an exposure notification
-server for use in a mobile exposure notification system.
-
-## Tutorials and reference documentation
-
-You can read tutorials on deploying and using the reference Exposure Notification
-Server here:
-
-[Deployment guide](deploying.md)  
-[Contributor guide](/CONTRIBUTING.md)  
-[Server Functional Requirements](server_functional_requirements.md)  
-[Server Deployment Options](server_deployment_options.md)  
-[Reference documentation](https://pkg.go.dev/mod/github.com/google/exposure-notifications-server)  
+Our hope is by making this privacy-preserving server implementation available
+to health authorities, we can enable their developers to use the open source code
+to get started quickly.
 
 ## Overview
 
@@ -38,8 +34,19 @@ The Exposure Notification Server is responsible for the following functions:
 
 * Periodically deleting old temporary exposure keys. After 14 days, or
   configured time period, the exposure keys can no longer be matched to a device.
+  
+## Tutorials and reference documentation
+
+You can read tutorials on deploying and using the reference Exposure Notification
+Server here:
+
+[Deployment guide](deploying.md)  
+[Contributor guide](/CONTRIBUTING.md)  
+[Server Functional Requirements](server_functional_requirements.md)  
+[Server Deployment Options](server_deployment_options.md)  
+[Reference documentation](https://pkg.go.dev/mod/github.com/google/exposure-notifications-server)  
 
-# Issues and Questions
+## Issues and Questions
 
 You can open a
 [GitHub Issue](https://github.com/google/exposure-notifications-server/issues/new).
diff --git a/internal/database/exposure_model.go b/internal/database/exposure_model.go
index 377b96e..c4862db 100644
--- a/internal/database/exposure_model.go
+++ b/internal/database/exposure_model.go
@@ -240,6 +240,12 @@ func (t *Transformer) TransformPublish(inData *Publish, batchTime time.Time) ([]
 			return nil, fmt.Errorf("interval number %v is in the future, must be < %v", exposureKey.IntervalNumber, maxIntervalNumber)
 		}
 
+		// Validate that the key is no longer effective.
+		if exposureKey.IntervalNumber+exposureKey.IntervalCount > maxIntervalNumber {
+			return nil, fmt.Errorf("interval number %v + interval count %v represents a key that is still valid, must end <= %v",
+				exposureKey.IntervalNumber, exposureKey.IntervalCount, maxIntervalNumber)
+		}
+
 		if tr := exposureKey.TransmissionRisk; tr < MinTransmissionRisk || tr > MaxTransmissionRisk {
 			return nil, fmt.Errorf("invalid transmission risk: %v, must be >= %v && <= %v", tr, MinTransmissionRisk, MaxTransmissionRisk)
 		}
diff --git a/internal/export/batcher.go b/internal/export/batcher.go
index 1bac079..279e942 100644
--- a/internal/export/batcher.go
+++ b/internal/export/batcher.go
@@ -58,10 +58,10 @@ func (s *Server) CreateBatchesHandler(w http.ResponseWriter, r *http.Request) {
 		logger.Infof("Processed %d configs creating %d batches across %d configs", totalConfigs, totalBatches, totalConfigsWithBatches)
 	}()
 
-	now := time.Now()
-	err = s.db.IterateExportConfigs(ctx, now, func(ec *database.ExportConfig) error {
+	effectiveTime := time.Now().Add(-1 * s.config.MinWindowAge)
+	err = s.db.IterateExportConfigs(ctx, effectiveTime, func(ec *database.ExportConfig) error {
 		totalConfigs++
-		if batchesCreated, err := s.maybeCreateBatches(ctx, ec, now); err != nil {
+		if batchesCreated, err := s.maybeCreateBatches(ctx, ec, effectiveTime); err != nil {
 			logger.Errorf("Failed to create batches for config %d: %v, continuing to next config", ec.ConfigID, err)
 		} else {
 			totalBatches += batchesCreated
diff --git a/internal/export/config.go b/internal/export/config.go
index 52bc783..bc5a8be 100644
--- a/internal/export/config.go
+++ b/internal/export/config.go
@@ -37,6 +37,7 @@ type Config struct {
 	PaddingRange   int           `envconfig:"EXPORT_FILE_PADDING_RANGE" default:"100"`
 	MaxRecords     int           `envconfig:"EXPORT_FILE_MAX_RECORDS" default:"30000"`
 	TruncateWindow time.Duration `envconfig:"TRUNCATE_WINDOW" default:"1h"`
+	MinWindowAge   time.Duration `envconfig:"MIN_WINDOW_AGE" default:"2h"`
 }
 
 // DB returns the database config.
diff --git a/internal/export/server.go b/internal/export/server.go
index 7710e6b..c914d12 100644
--- a/internal/export/server.go
+++ b/internal/export/server.go
@@ -30,6 +30,9 @@ func NewServer(config *Config, env *serverenv.ServerEnv) (*Server, error) {
 	if env.KeyManager() == nil {
 		return nil, fmt.Errorf("export.NewBatchServer requires KeyManager present in the ServerEnv")
 	}
+	if config.MinWindowAge < 0 {
+		return nil, fmt.Errorf("MIN_WINDOW_AGE must be a duration of >= 0")
+	}
 
 	return &Server{
 		db:     env.Database(),
diff --git a/internal/pb/federation.pb.go b/internal/pb/federation.pb.go
index 11eec2b..3923714 100644
--- a/internal/pb/federation.pb.go
+++ b/internal/pb/federation.pb.go
@@ -255,6 +255,7 @@ type ContactTracingInfo struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
+	// Transmission risk is an integer with valid values from 1-8.
 	TransmissionRisk int32          `protobuf:"varint,1,opt,name=transmissionRisk,proto3" json:"transmissionRisk,omitempty"` // required
 	ExposureKeys     []*ExposureKey `protobuf:"bytes,2,rep,name=exposureKeys,proto3" json:"exposureKeys,omitempty"`
 }


Test patch:
diff --git a/internal/database/exposure_model_test.go b/internal/database/exposure_model_test.go
index a74f9c6..3b1d953 100644
--- a/internal/database/exposure_model_test.go
+++ b/internal/database/exposure_model_test.go
@@ -223,6 +223,19 @@ func TestPublishValidation(t *testing.T) {
 			},
 			m: fmt.Sprintf("interval number %v is in the future, must be < %v", currentInterval+1, currentInterval),
 		},
+		{
+			name: "interval end too high",
+			p: &Publish{
+				Keys: []ExposureKey{
+					{
+						Key:            encodeKey(generateKey(t)),
+						IntervalNumber: currentInterval - 143,
+						IntervalCount:  144,
+					},
+				},
+			},
+			m: fmt.Sprintf("interval number %v + interval count %v represents a key that is still valid, must end <= %v", currentInterval-143, 144, currentInterval),
+		},
 	}
 
 	for _, c := range cases {