Skip to content

Commit

Permalink
Make tokensource less stateful. (#462)
Browse files Browse the repository at this point in the history
Now it always gets the service-account-name. Putting the amail together
has been move up to main.

This way we can log the used service accoutn in one place.
  • Loading branch information
ensonic authored Nov 25, 2024
1 parent 19b76a6 commit d4586ef
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 25 deletions.
16 changes: 8 additions & 8 deletions src/go/cmd/token-vendor/api/v1/v1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
const (
testPubKey = "testdata/rsa_cert.pem"
jwtBodyPrefix = "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion="
saName = "[email protected]"

jwtCorrect = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0YXVkIiwiaXNzIjoicm9ib3QtZGV2LXRlc3R1c2VyIiwiZXhwIjoxOTEzMzczMDEwLCJzY29wZXMiOiJ0ZXN0c2NvcGVzIiwiY2xhaW1zIjoidGVzdGNsYWltcyJ9.WJP0shiqynW9ZrmV4k78W3_nn_YA86XLK58IJYyqUF-8LAG92MraNqVqD0t6i-s90VBL64hCXlsA7zP3WlsMHOEvXCyRkGffhbJNIlJqIVTVfGvyF-ZmuaAr352n5kmKTrfTRi7h9LWTcvDgSosN438J8Jy9BT1FE9P-BHfyBUegZ15DWFAiAhz0r_Fgj7hAMXUnRdZfj3_dE0Nhi5IGs3L-0XzU-dE150ZJvtGMdIjc_QCqYHV3wtSgETKDYQoonD08n6g5GqC8nNkqrWFMttafLdPaDAsr8KWtj1dD1w9sw1YJClEzF9JOc63WNPZf8CgdU2enFW-V-2vHbUaekg"
jwtWrongSig = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0YXVkIiwiaXNzIjoicm9ib3QtZGV2LXRlc3R1c2VyIiwiZXhwIjoxOTEzMzczMDEwLCJzY29wZXMiOiIuLi4iLCJjbGFpbXMiOiIuLi4ifQ.krAYHjkConzVudfXJUMiDNbVHF3RwkvOAhSCyTvOaJdlJ6sxh-TjPXo6W0yVT31qjLwhl1NYI-JlhcHX7TLiZbLCbGVXlQN2Nn4LvpbGdAH0KvSJkthqX7ld9tlVQGdlOUHCE5bBDG_9uBtpdOAv1zKUTquhyDM0qWVrQV1qUVOtwBCO6nt21l1eXgTwz50FVN33f1ZmhZfHW1u7Dq_XwBJmHFwN3aiD0NZohU7MpQiz-0u94Q9yZ588IjdZEUhSEUKrVtJjoPcxDhrXxoRMA8iP8_bMeOHteiAdYeBVBwFhu1d8pfcn6uoZROYD1xB1LWDTJx4GfQh6v3wtAwFu7Q"
Expand Down Expand Up @@ -129,7 +130,7 @@ func runPublicKeyReadHandlerWithK8sCase(t *testing.T, test *publicKeyReadHandler
t.Fatal(err)
}
// Setup app & API handler
tv, err := app.NewTokenVendor(context.TODO(), kcl, nil, nil, "aud")
tv, err := app.NewTokenVendor(context.TODO(), kcl, nil, nil, "aud", saName)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -252,7 +253,7 @@ func runPublicKeyPublishHandlerWithK8sCase(t *testing.T, test *publicKeyPublishH
t.Fatal(err)
}
// Setup app & API handler
tv, err := app.NewTokenVendor(context.TODO(), kcl, nil, nil, "aud")
tv, err := app.NewTokenVendor(context.TODO(), kcl, nil, nil, "aud", saName)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -544,7 +545,7 @@ func runVerifyTokenHandlerTest(t *testing.T, test *VerifyTokenHandlerTest) {
if err != nil {
t.Fatal(err.Error())
}
tv, err := app.NewTokenVendor(context.TODO(), nil, tver, nil, "aud")
tv, err := app.NewTokenVendor(context.TODO(), nil, tver, nil, "aud", saName)
if err != nil {
t.Fatal(err.Error())
}
Expand Down Expand Up @@ -636,7 +637,7 @@ func TestTokenOAuth2HandlerExpired(t *testing.T) {
func runTokenOAuth2HandlerTestWithK8s(t *testing.T, test TokenOAuth2HandlerTest) {
// fake GCP IAM response for an access token
fakeIAMAPI := func(req *http.Request) *http.Response {
const wantUrl = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/testsa@testproject.iam.gserviceaccount.com:generateAccessToken?alt=json&prettyPrint=false"
const wantUrl = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/robot-service@testproject.iam.gserviceaccount.com:generateAccessToken?alt=json&prettyPrint=false"
if req.URL.String() != wantUrl {
t.Fatalf("wrong IAM URL, got %q, want %q", req.URL, wantUrl)
}
Expand All @@ -652,8 +653,7 @@ func runTokenOAuth2HandlerTestWithK8s(t *testing.T, test TokenOAuth2HandlerTest)
}
// setup app and http client
clientIAM := NewTestHTTPClient(fakeIAMAPI)
ts, err := tokensource.NewGCPTokenSource(context.TODO(), clientIAM, "testproject", "testsa",
test.scopes)
ts, err := tokensource.NewGCPTokenSource(context.TODO(), clientIAM, test.scopes)
if err != nil {
t.Fatal(err)
}
Expand All @@ -677,7 +677,7 @@ func runTokenOAuth2HandlerTestWithK8s(t *testing.T, test TokenOAuth2HandlerTest)
if err != nil {
t.Fatal(err)
}
tv, err := app.NewTokenVendor(context.TODO(), r, nil, ts, test.acceptedAud)
tv, err := app.NewTokenVendor(context.TODO(), r, nil, ts, test.acceptedAud, saName)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -771,7 +771,7 @@ func Test_verifyJWTHandler(t *testing.T) {
if err != nil {
t.Fatal(err)
}
tv, err := app.NewTokenVendor(context.TODO(), r, nil, nil, "testaud")
tv, err := app.NewTokenVendor(context.TODO(), r, nil, nil, "testaud", saName)
if err != nil {
t.Fatal(err)
}
Expand Down
16 changes: 10 additions & 6 deletions src/go/cmd/token-vendor/app/tokenvendor.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,18 @@ import (
)

type TokenVendor struct {
repo repository.PubKeyRepository
v *oauth.TokenVerifier
ts *tokensource.GCPTokenSource
accAud string
repo repository.PubKeyRepository
v *oauth.TokenVerifier
ts *tokensource.GCPTokenSource
accAud string
defaultSAName string
}

func NewTokenVendor(ctx context.Context, repo repository.PubKeyRepository, v *oauth.TokenVerifier, ts *tokensource.GCPTokenSource, acceptedAudience string) (*TokenVendor, error) {
func NewTokenVendor(ctx context.Context, repo repository.PubKeyRepository, v *oauth.TokenVerifier, ts *tokensource.GCPTokenSource, acceptedAudience, defaultSAName string) (*TokenVendor, error) {
if acceptedAudience == "" {
return nil, errors.New("accepted audience must not be empty")
}
return &TokenVendor{repo: repo, v: v, accAud: acceptedAudience, ts: ts}, nil
return &TokenVendor{repo: repo, v: v, ts: ts, accAud: acceptedAudience, defaultSAName: defaultSAName}, nil
}

func (tv *TokenVendor) PublishPublicKey(ctx context.Context, deviceID, publicKey string) error {
Expand Down Expand Up @@ -128,6 +129,9 @@ func (tv *TokenVendor) getOAuth2Token(ctx context.Context, jwtk string) (*tokens
if err != nil {
return nil, err
}
if sa == "" {
sa = tv.defaultSAName
}
cloudToken, err := tv.ts.Token(ctx, sa)
if err != nil {
return nil, errors.Wrapf(err, "failed to retrieve a cloud token for device %q", deviceID)
Expand Down
5 changes: 3 additions & 2 deletions src/go/cmd/token-vendor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,13 @@ func main() {
slog.Error("Failed to make verifier", ilog.Err(err))
os.Exit(1)
}
ts, err := tokensource.NewGCPTokenSource(ctx, nil, *project, *robotName, scopes)
ts, err := tokensource.NewGCPTokenSource(ctx, nil, scopes)
if err != nil {
slog.Error("Failed to make token source", ilog.Err(err))
os.Exit(1)
}
tv, err := app.NewTokenVendor(ctx, rep, verifier, ts, *acceptedAudience)
saName := fmt.Sprintf("%s@%s.iam.gserviceaccount.com", *robotName, *project)
tv, err := app.NewTokenVendor(ctx, rep, verifier, ts, *acceptedAudience, saName)
if err != nil {
slog.Error("Failed to make token vendor", ilog.Err(err))
os.Exit(1)
Expand Down
15 changes: 6 additions & 9 deletions src/go/cmd/token-vendor/tokensource/gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ import (

type GCPTokenSource struct {
service *iam.Service
// the FQN of the service account
defaultResource string
scopes []string
scopes []string
}

type TokenResponse struct {
Expand All @@ -34,24 +32,23 @@ type TokenResponse struct {
// authentication information is looked up from the environment.
// `defaultSAName` specifies the GCP IAM service accoutn name to use if no
// dedicated service account is configurred on the key.
func NewGCPTokenSource(ctx context.Context, client *http.Client, project, defaultSAName string, scopes []string) (*GCPTokenSource, error) {
func NewGCPTokenSource(ctx context.Context, client *http.Client, scopes []string) (*GCPTokenSource, error) {
service, err := iam.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
return nil, errors.Wrap(err, "failed to create IAM service client")
}
resource := fmt.Sprintf("projects/-/serviceAccounts/%s@%s.iam.gserviceaccount.com", defaultSAName, project)
return &GCPTokenSource{service: service, defaultResource: resource, scopes: scopes}, nil
return &GCPTokenSource{service: service, scopes: scopes}, nil
}

// Token returns an access token for the configured service account and scopes.
//
// API: https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken
func (g *GCPTokenSource) Token(ctx context.Context, saName string) (*TokenResponse, error) {
req := iam.GenerateAccessTokenRequest{Scope: g.scopes}
resource := g.defaultResource
if saName != "" {
resource = "projects/-/serviceAccounts/" + saName
if saName == "" {
return nil, fmt.Errorf("saName must not be empty")
}
resource := "projects/-/serviceAccounts/" + saName
// We don't set a 'lifetime' on the request, so we get the default value (3600 sec = 1h).
// This needs to be in sync with the min(cookie-expire,cookie-refresh) duration
// configured on oauth2-proxy.
Expand Down

0 comments on commit d4586ef

Please sign in to comment.