go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/auth/integration/gcemeta/gcemeta_test.go (about) 1 // Copyright 2019 The LUCI Authors. 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 gcemeta 16 17 import ( 18 "context" 19 "net/http" 20 "os" 21 "testing" 22 "time" 23 24 "cloud.google.com/go/compute/metadata" 25 "golang.org/x/oauth2" 26 "golang.org/x/oauth2/google" 27 28 . "github.com/smartystreets/goconvey/convey" 29 ) 30 31 type fakeGenerator struct { 32 accessToken *oauth2.Token 33 idToken string 34 lastScopes []string 35 } 36 37 func (f *fakeGenerator) GenerateOAuthToken(ctx context.Context, scopes []string, lifetime time.Duration) (*oauth2.Token, error) { 38 f.lastScopes = append([]string(nil), scopes...) 39 return f.accessToken, nil 40 } 41 42 func (f *fakeGenerator) GenerateIDToken(ctx context.Context, audience string, lifetime time.Duration) (*oauth2.Token, error) { 43 return &oauth2.Token{AccessToken: f.idToken}, nil 44 } 45 46 func TestServer(t *testing.T) { 47 fakeAccessToken := &oauth2.Token{ 48 AccessToken: "fake_access_token", 49 Expiry: time.Now().Add(time.Hour), 50 } 51 fakeIDToken := "fake_id_token" 52 53 ctx := context.Background() 54 gen := &fakeGenerator{fakeAccessToken, fakeIDToken, nil} 55 srv := Server{ 56 Generator: gen, 57 Email: "fake@example.com", 58 Scopes: []string{"scope1", "scope2"}, 59 MinTokenLifetime: 2 * time.Minute, 60 61 md: &serverMetadata{ 62 zone: "luci-emulated-zone", 63 name: "luci-emulated", 64 }, 65 } 66 67 // Need to set GCE_METADATA_HOST once before all tests because 'metadata' 68 // package caches results of metadata fetches. 69 addr, err := srv.Start(ctx) 70 if err != nil { 71 t.Fatalf("Failed to start: %s", err) 72 } 73 defer srv.Stop(ctx) 74 75 os.Setenv("GCE_METADATA_HOST", addr) 76 77 Convey("Works", t, func(c C) { 78 So(metadata.OnGCE(), ShouldBeTrue) 79 80 Convey("Metadata client works", func() { 81 cl := metadata.NewClient(http.DefaultClient) 82 83 num, err := cl.NumericProjectID() 84 So(err, ShouldBeNil) 85 So(num, ShouldEqual, "0") 86 87 pid, err := cl.ProjectID() 88 So(err, ShouldBeNil) 89 So(pid, ShouldEqual, "none") 90 91 zone, err := cl.Zone() 92 So(err, ShouldBeNil) 93 So(zone, ShouldEqual, "luci-emulated-zone") 94 95 name, err := cl.InstanceName() 96 So(err, ShouldBeNil) 97 So(name, ShouldEqual, "luci-emulated") 98 99 accounts, err := cl.Get("instance/service-accounts/") 100 So(err, ShouldBeNil) 101 So(accounts, ShouldEqual, "fake@example.com/\ndefault/\n") 102 103 for _, acc := range []string{"fake@example.com", "default"} { 104 info, err := cl.Get("instance/service-accounts/" + acc + "/?recursive=true") 105 So(err, ShouldBeNil) 106 So(info, ShouldEqual, 107 `{"aliases":["default"],"email":"fake@example.com","scopes":["scope1","scope2"]}`+"\n") 108 109 email, err := cl.Get("instance/service-accounts/" + acc + "/email") 110 So(err, ShouldBeNil) 111 So(email, ShouldEqual, "fake@example.com") 112 113 scopes, err := cl.Scopes(acc) 114 So(err, ShouldBeNil) 115 So(scopes, ShouldResemble, []string{"scope1", "scope2"}) 116 } 117 }) 118 119 Convey("OAuth2 token source works", func() { 120 Convey("Default scopes", func() { 121 ts := google.ComputeTokenSource("default") 122 tok, err := ts.Token() 123 So(err, ShouldBeNil) 124 // Do not put tokens into logs, in case we somehow accidentally hit real 125 // metadata server with real tokens. 126 if tok.AccessToken != fakeAccessToken.AccessToken { 127 panic("Bad token") 128 } 129 So(time.Until(tok.Expiry), ShouldBeGreaterThan, 55*time.Minute) 130 So(gen.lastScopes, ShouldResemble, []string{"scope1", "scope2"}) 131 }) 132 133 Convey("Custom scopes", func() { 134 ts := google.ComputeTokenSource("default", "custom1", "custom1", "custom2") 135 tok, err := ts.Token() 136 So(err, ShouldBeNil) 137 // Do not put tokens into logs, in case we somehow accidentally hit real 138 // metadata server with real tokens. 139 if tok.AccessToken != fakeAccessToken.AccessToken { 140 panic("Bad token") 141 } 142 So(time.Until(tok.Expiry), ShouldBeGreaterThan, 55*time.Minute) 143 So(gen.lastScopes, ShouldResemble, []string{"custom1", "custom2"}) 144 }) 145 }) 146 147 Convey("ID token fetch works", func() { 148 reply, err := metadata.Get("instance/service-accounts/default/identity?audience=boo&format=ignored") 149 So(err, ShouldBeNil) 150 // Do not put tokens into logs, in case we somehow accidentally hit real 151 // metadata server with real tokens. 152 if reply != fakeIDToken { 153 panic("Bad token") 154 } 155 }) 156 157 Convey("Unsupported metadata call", func() { 158 _, err := metadata.InstanceID() 159 So(err.Error(), ShouldEqual, `metadata: GCE metadata "instance/id" not defined`) 160 }) 161 }) 162 }