...

Source file src/github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/certs/certs_test.go

Documentation: github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/certs

     1  // Copyright 2021 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package certs
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"testing"
    25  	"time"
    26  
    27  	"google.golang.org/api/option"
    28  	sqladmin "google.golang.org/api/sqladmin/v1beta4"
    29  )
    30  
    31  const fakeCert = `-----BEGIN CERTIFICATE-----
    32  MIICgTCCAWmgAwIBAgIBADANBgkqhkiG9w0BAQsFADAAMCIYDzAwMDEwMTAxMDAw
    33  MDAwWhgPMDAwMTAxMDEwMDAwMDBaMAAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
    34  ggEKAoIBAQCvN0H6/ecloIfNyRu8KKtVSIK0JaW1lB1C1/ZI9iZmihqiUrxeyKTb
    35  9hWuMPJ3u9NfSn1Vlwuj0bw7/T8e3Ol5BImcGxYxWMefkqFtqnjCafo2wnIea/eQ
    36  JFLt4wXYkeveHReUseGtaBzpCo4wYOiqgxyIrGiQ/rq4Xjr2hXuqTg4TTgxv+0Iv
    37  nrJwn61pitGvLPjsl9quzSQ6CdM3tWfb6cwozF5uJatbxRCZDsp1qUBXX9/zYqmx
    38  8regdRG95btNgXLCfNS0iX0jopl00vGwYRGGKjfPZ5AkpuxX9M4Ys3X7pOspaQMC
    39  Zf4VjXdwOljqZxIOGhOBbrXQacSywTLjAgMBAAGjAjAAMA0GCSqGSIb3DQEBCwUA
    40  A4IBAQAXj/0iiU2AQGztlFstLVwQ9yz+7/pfqAr26DYu9hpI/QvrZsJWjwNUNlX+
    41  7gwhrwiJs7xsLZqnEr2qvj6at/MtxIEVgQd43sOsWW9de8R5WNQNzsCb+5npWcx7
    42  vtcKXD9jFFLDDCIYjAf9+6m/QrMJtIf++zBmjguShccjZzY+GQih78oWqNTYqRQs
    43  //wOP15vFQ/gB4DcJ0UyO9icVgbJha66yzG7XABDEepha5uhpLhwFaONU8jMxW7A
    44  fOx52xqIUu3m4M3Ci0ZIp22TeGVuJ/Dy1CPbDOshcb0dXTE+mU5T91SHKRF4jz77
    45  +9TQIXHGk7lJyVVhbed8xm/p727f
    46  -----END CERTIFICATE-----`
    47  
    48  func TestLocalCertSupportsStaleReads(t *testing.T) {
    49  	var (
    50  		gotReadTimes []string
    51  		ok           bool
    52  	)
    53  	handleEphemeralCert := func(w http.ResponseWriter, r *http.Request) {
    54  		var actual sqladmin.GenerateEphemeralCertRequest
    55  		data, err := ioutil.ReadAll(r.Body)
    56  		if err != nil {
    57  			t.Fatalf("failed to read request body: %v", err)
    58  		}
    59  		defer r.Body.Close()
    60  		if err = json.Unmarshal(data, &actual); err != nil {
    61  			t.Fatalf("failed to unmarshal request body: %v", err)
    62  		}
    63  		gotReadTimes = append(gotReadTimes, actual.ReadTime)
    64  		if !ok {
    65  			w.WriteHeader(http.StatusServiceUnavailable)
    66  			fmt.Fprintln(w, `{"message":"the first request fails"}`)
    67  			ok = true
    68  			return
    69  		}
    70  		// the second request succeeds
    71  		fmt.Fprintln(w, fmt.Sprintf(`{"ephemeralCert":{"cert": %q}}`, fakeCert))
    72  	}
    73  	ts := httptest.NewServer(http.HandlerFunc(handleEphemeralCert))
    74  	defer ts.Close()
    75  
    76  	cs := NewCertSourceOpts(ts.Client(), RemoteOpts{})
    77  	// replace SQL Admin API client with client backed by test server
    78  	var err error
    79  	cs.serv, err = sqladmin.NewService(context.Background(),
    80  		option.WithEndpoint(ts.URL), option.WithHTTPClient(ts.Client()))
    81  	if err != nil {
    82  		t.Fatalf("failed to replace SQL Admin client: %v", err)
    83  	}
    84  
    85  	// Send request to generate a cert
    86  	_, err = cs.Local("my-proj:reg:my-inst")
    87  	if err != nil {
    88  		t.Fatal(err)
    89  	}
    90  
    91  	// Verify read time is not present for first request
    92  	// and is 30 seconds before "now" for second request
    93  	if len(gotReadTimes) != 2 {
    94  		t.Fatalf("expected two results, got = %v", len(gotReadTimes))
    95  	}
    96  	if gotReadTimes[0] != "" {
    97  		t.Fatalf("expected empty ReadTime for first request, got = %v", gotReadTimes[0])
    98  	}
    99  	wantStaleness := 30 * time.Second
   100  	if !staleTimestamp(gotReadTimes[1], wantStaleness) {
   101  		t.Fatalf("expected timestamp at least %v old, got = %v (now = %v)",
   102  			wantStaleness, gotReadTimes[1], time.Now().UTC().Format(time.RFC3339))
   103  	}
   104  }
   105  
   106  func staleTimestamp(ts string, staleness time.Duration) bool {
   107  	t, err := time.Parse(time.RFC3339, ts)
   108  	if err != nil {
   109  		// ts was not in expected format, fail
   110  		return false
   111  	}
   112  	return t.Before(time.Now().Add(-staleness))
   113  }
   114  
   115  func TestRemoteCertSupportsStaleReads(t *testing.T) {
   116  	var (
   117  		gotReadTimes []string
   118  		ok           bool
   119  	)
   120  	handleConnectSettings := func(w http.ResponseWriter, r *http.Request) {
   121  		rt := r.URL.Query()["readTime"]
   122  		// if the URL parameter isn't nil, record its value; otherwise add an
   123  		// empty string to indicate no query param was set
   124  		if rt != nil {
   125  			gotReadTimes = append(gotReadTimes, rt[0])
   126  		} else {
   127  			gotReadTimes = append(gotReadTimes, "")
   128  		}
   129  		if !ok {
   130  			w.WriteHeader(http.StatusServiceUnavailable)
   131  			fmt.Fprintln(w, `{"message":"the first request fails"}`)
   132  			ok = true
   133  			return
   134  		}
   135  		fmt.Fprintln(w, fmt.Sprintf(`{
   136  			"region":"us-central1",
   137  			"ipAddresses": [
   138  				{"type":"PRIMARY", "ipAddress":"127.0.0.1"}
   139  			],
   140  			"serverCaCert": {"cert": %q}
   141  		}`, fakeCert))
   142  	}
   143  	ts := httptest.NewServer(http.HandlerFunc(handleConnectSettings))
   144  	defer ts.Close()
   145  
   146  	cs := NewCertSourceOpts(ts.Client(), RemoteOpts{})
   147  	var err error
   148  	// replace SQL Admin API client with client backed by test server
   149  	cs.serv, err = sqladmin.NewService(context.Background(),
   150  		option.WithEndpoint(ts.URL), option.WithHTTPClient(ts.Client()))
   151  	if err != nil {
   152  		t.Fatalf("failed to replace SQL Admin client: %v", err)
   153  	}
   154  
   155  	// Send request to retrieve instance metadata
   156  	_, _, _, _, err = cs.Remote("my-proj:us-central1:my-inst")
   157  	if err != nil {
   158  		t.Fatal(err)
   159  	}
   160  
   161  	// Verify read time is not present for first request
   162  	// and is 30 seconds before "now" for second request
   163  	if len(gotReadTimes) != 2 {
   164  		t.Fatalf("expected two results, got = %v", len(gotReadTimes))
   165  	}
   166  	if gotReadTimes[0] != "" {
   167  		t.Fatalf("expected empty ReadTime for first request, got = %v", gotReadTimes[0])
   168  	}
   169  	wantStaleness := 30 * time.Second
   170  	if !staleTimestamp(gotReadTimes[1], wantStaleness) {
   171  		t.Fatalf("expected timestamp at least %v old, got = %v (now = %v)",
   172  			wantStaleness, gotReadTimes[1], time.Now().UTC().Format(time.RFC3339))
   173  	}
   174  }
   175  

View as plain text