Skip to content

Commit 0d80e93

Browse files
kbukum1Copilot
andcommitted
Validate STS expiry on direct-WIF path and tighten IAM check to <=
- Direct-WIF (no service account) now rejects STS tokens with ExpiresIn <= 5 minutes, preventing cache-immediately-expired loops - IAM path uses <= instead of < for the 5-minute threshold, fixing the edge case where remaining == 5m caches as immediately expired - Add tests for zero expiry and exactly-5-minutes boundary Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4c9d3c3 commit 0d80e93

2 files changed

Lines changed: 38 additions & 2 deletions

File tree

internal/oidc/actions_oidc.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -743,9 +743,13 @@ func GetGCPAccessToken(ctx context.Context, params GCPOIDCParameters, githubToke
743743

744744
// If no service account, return the federated token directly (direct WIF)
745745
if params.ServiceAccount == "" {
746+
stsExpiry := time.Duration(stsTokenResp.ExpiresIn) * time.Second
747+
if stsExpiry <= 5*time.Minute {
748+
return nil, fmt.Errorf("GCP STS token expires too soon (%v remaining)", stsExpiry)
749+
}
746750
return &OIDCAccessToken{
747751
Token: stsTokenResp.AccessToken,
748-
ExpiresIn: time.Duration(stsTokenResp.ExpiresIn) * time.Second,
752+
ExpiresIn: stsExpiry,
749753
}, nil
750754
}
751755

@@ -800,7 +804,7 @@ func GetGCPAccessToken(ctx context.Context, params GCPOIDCParameters, githubToke
800804
}
801805

802806
remaining := time.Until(expireTime)
803-
if remaining < 5*time.Minute {
807+
if remaining <= 5*time.Minute {
804808
return nil, fmt.Errorf("GCP IAM token expires too soon (%v remaining, service-account: %s)", remaining, params.ServiceAccount)
805809
}
806810

internal/oidc/actions_oidc_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,38 @@ func TestGetGCPAccessToken(t *testing.T) {
11901190
},
11911191
expectError: true,
11921192
},
1193+
{
1194+
name: "STS token expires too soon (direct WIF)",
1195+
params: GCPOIDCParameters{
1196+
WorkloadIdentityProvider: "projects/123/locations/global/workloadIdentityPools/pool/providers/prov",
1197+
Audience: "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/prov",
1198+
},
1199+
githubToken: "test-github-jwt-token",
1200+
stsHandler: func(w http.ResponseWriter, r *http.Request) {
1201+
w.Header().Set("Content-Type", "application/json")
1202+
json.NewEncoder(w).Encode(gcpSTSTokenResponse{
1203+
AccessToken: "federated-access-token",
1204+
ExpiresIn: 0,
1205+
})
1206+
},
1207+
expectError: true,
1208+
},
1209+
{
1210+
name: "STS token at exactly 5 minutes (direct WIF)",
1211+
params: GCPOIDCParameters{
1212+
WorkloadIdentityProvider: "projects/123/locations/global/workloadIdentityPools/pool/providers/prov",
1213+
Audience: "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/pool/providers/prov",
1214+
},
1215+
githubToken: "test-github-jwt-token",
1216+
stsHandler: func(w http.ResponseWriter, r *http.Request) {
1217+
w.Header().Set("Content-Type", "application/json")
1218+
json.NewEncoder(w).Encode(gcpSTSTokenResponse{
1219+
AccessToken: "federated-access-token",
1220+
ExpiresIn: 300,
1221+
})
1222+
},
1223+
expectError: true,
1224+
},
11931225
{
11941226
name: "IAM returns 403",
11951227
params: GCPOIDCParameters{

0 commit comments

Comments
 (0)