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  }