mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-01-16 05:16:08 +02:00
af05acad58
* test * test * draft * generator * some polishing * go mod tidy * fix unit * fix unit * fix unit * fix unit * fix unit * resolve review comments * resolve review comments * add debug message on successful publish * refactor to use global vault client * cleanup * rename * clenup * refactor * remove token revocation * handle nil vaultClient and add comments * feat(events): Publish events (generated part) (#5131) * add generated * add generated * refactor vaultClient usage * fix unit tests * fix unit tests * fix
90 lines
3.0 KiB
Go
90 lines
3.0 KiB
Go
package gcp
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"github.com/SAP/jenkins-library/pkg/log"
|
|
"google.golang.org/api/option"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"google.golang.org/api/sts/v1"
|
|
)
|
|
|
|
const (
|
|
gcpPubsubTokenKey = "PIPER_gcpPubsubToken"
|
|
gcpPubsubTokenExpiryKey = "PIPER_gcpPubsubTokenExpiresAt"
|
|
)
|
|
|
|
// getFederatedToken tries to retrieve cached token from env variables, otherwise it will exchange
|
|
// OIDC identity token to access token and cache them in env variables
|
|
func getFederatedToken(projectNumber, pool, provider, oidcToken string) (string, error) {
|
|
cachedToken := os.Getenv(gcpPubsubTokenKey)
|
|
cachedExpiresAt := os.Getenv(gcpPubsubTokenExpiryKey)
|
|
if tokenIsValid(cachedToken, cachedExpiresAt) {
|
|
log.Entry().Debug("reusing GCP PubSub access token from cache")
|
|
return cachedToken, nil
|
|
}
|
|
|
|
ctx := context.Background()
|
|
token, expiresAt, err := exchangeOIDCToken(ctx, projectNumber, pool, provider, oidcToken)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "token exchange")
|
|
}
|
|
|
|
os.Setenv(gcpPubsubTokenKey, token)
|
|
os.Setenv(gcpPubsubTokenExpiryKey, strconv.FormatInt(expiresAt, 10))
|
|
return token, nil
|
|
}
|
|
|
|
// exchangeOIDCToken exchanges OIDC identity token to access token and returns expiry time in Unix timestamp
|
|
func exchangeOIDCToken(ctx context.Context, projectNumber, pool, provider, oidcToken string) (string, int64, error) {
|
|
if len(oidcToken) == 0 {
|
|
return "", 0, errors.New("OIDC identity token is absent")
|
|
}
|
|
|
|
stsService, err := sts.NewService(ctx, option.WithoutAuthentication())
|
|
if err != nil {
|
|
return "", 0, errors.Wrap(err, "service not created")
|
|
}
|
|
|
|
request := getExchangeTokenRequestData(projectNumber, pool, provider, oidcToken)
|
|
response, err := sts.NewV1Service(stsService).Token(request).Context(ctx).Do()
|
|
if err != nil {
|
|
return "", 0, errors.Wrap(err, "exchange failed")
|
|
}
|
|
|
|
expiresAt := time.Now().Unix() + response.ExpiresIn
|
|
log.Entry().Debugf("token successfully exchanged and will expire at %s", time.Unix(expiresAt, 0))
|
|
return response.AccessToken, expiresAt, nil
|
|
}
|
|
|
|
func tokenIsValid(token string, expiresAtStr string) bool {
|
|
if token == "" {
|
|
return false
|
|
}
|
|
|
|
expiresAt, _ := strconv.Atoi(expiresAtStr)
|
|
buffer := 5 // 5 second buffer to prevent using token that potentially may expire during execution
|
|
if int64(expiresAt-buffer) < time.Now().Unix() {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func getExchangeTokenRequestData(projectNumber string, pool string, provider string, token string) *sts.GoogleIdentityStsV1ExchangeTokenRequest {
|
|
return &sts.GoogleIdentityStsV1ExchangeTokenRequest{
|
|
Audience: fmt.Sprintf(
|
|
"//iam.googleapis.com/projects/%s/locations/global/workloadIdentityPools/%s/providers/%s",
|
|
projectNumber, pool, provider),
|
|
Scope: "https://www.googleapis.com/auth/cloud-platform",
|
|
SubjectToken: token,
|
|
SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
|
|
GrantType: "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
RequestedTokenType: "urn:ietf:params:oauth:token-type:access_token",
|
|
}
|
|
}
|