Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tokensource library #449

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions pkg/tokensource/tokensource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright 2024 Chainguard, Inc.
// SPDX-License-Identifier: Apache-2.0
package tokensource

Check failure on line 3 in pkg/tokensource/tokensource.go

View workflow job for this annotation

GitHub Actions / Boilerplate Check (go)

[Go headers] reported by reviewdog 🐶 found mismatched boilerplate lines: Raw Output: pkg/tokensource/tokensource.go:3: found mismatched boilerplate lines: {[]string}[0]: -: "" +: "package tokensource"

import (
"context"
"fmt"
"net/http"
"time"

"chainguard.dev/sdk/sts"
"github.com/chainguard-dev/clog"
"golang.org/x/oauth2"
"golang.org/x/time/rate"
)

type TokenSource struct {
ctx context.Context
url string
org, repo, policyName string
// Base token source (e.g. the cred we send to the STS service to exchange).
base oauth2.TokenSource
sometimes rate.Sometimes

// Output fields.
tok *oauth2.Token
err error
}

func NewTokenSource(ctx context.Context, stsURL string, org, repo, policyName string, ts oauth2.TokenSource) *TokenSource {
return &TokenSource{
ctx: ctx,
url: stsURL,
org: org,
repo: repo,
policyName: policyName,
base: ts,
sometimes: rate.Sometimes{Interval: 45 * time.Minute},
}
}

// Token returns a token from the octosts service.
func (ts *TokenSource) Token() (*oauth2.Token, error) {
// The token is refreshed periodically. Previous tokens are revoked before
// returning the new refreshed one.
ts.sometimes.Do(func() {
ctx := ts.ctx
clog.FromContext(ctx).Debugf("getting octosts token for %s/%s - %s", ts.org, ts.repo, ts.policyName)
otok, err := ts.token()

// Explicitly set the token to nil rather than a struct with an empty
// token field
if err != nil {
ts.tok, ts.err = nil, err
return
}

// If there's a previous token, revoke it.
if ts.tok != nil {
ts.Revoke()

Check failure on line 60 in pkg/tokensource/tokensource.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Error return value of `ts.Revoke` is not checked (errcheck)
}
ts.tok, ts.err = &oauth2.Token{
TokenType: "Bearer",
AccessToken: otok,
}, nil
})
return ts.tok, ts.err
}

func (ts *TokenSource) token() (string, error) {
ctx := ts.ctx
scope := ts.org
if scope != "" {
scope = fmt.Sprintf("%s/%s", ts.org, ts.repo)
}

xchg := sts.New(
ts.url,
ts.policyName,
sts.WithScope(scope),
sts.WithIdentity(ts.policyName),
)

token, err := ts.base.Token()
if err != nil {
return "", err
}

res, err := xchg.Exchange(ctx, token.AccessToken)
if err != nil {
return "", err
}
return res, nil
}

// Revoke revokes the current token.
func (ts *TokenSource) Revoke() error {
ctx := ts.ctx
req, err := http.NewRequest(http.MethodDelete, "https://api.github.com/installation/token", nil)
if err != nil {
return fmt.Errorf("creating request: %w", err)
}
req = req.WithContext(ctx)

resp, err := oauth2.NewClient(ctx, ts).Do(req)
if err != nil {
return fmt.Errorf("making request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}

// The token was revoked!
return nil
}
Loading