// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package github import ( "context" "encoding/json" "fmt" "io" "net/http" "os" "strings" "time" "github.com/sigstore/cosign/v3/pkg/cosign/env" "github.com/sigstore/cosign/v3/pkg/providers" ) const ( // Deprecated: use `env.VariableGitHubRequestToken` instead RequestTokenEnvKey = env.VariableGitHubRequestToken // Deprecated: use `env.VariableGitHubRequestURL` instead RequestURLEnvKey = env.VariableGitHubRequestURL // providerName is the name this provider is registered under. providerName = "github-actions" // maxErrorBodyBytes limits how much of a failed response body is quoted back in the error. maxErrorBodyBytes = 256 ) func init() { providers.Register(providerName, &githubActions{}) } type githubActions struct{} var _ providers.Interface = (*githubActions)(nil) // Enabled implements providers.Interface func (ga *githubActions) Enabled(_ context.Context) bool { if env.Getenv(env.VariableGitHubRequestToken) == "" { return false } if env.Getenv(env.VariableGitHubRequestURL) == "" { return false } return true } // Provide implements providers.Interface func (ga *githubActions) Provide(ctx context.Context, audience string) (string, error) { url := env.Getenv(env.VariableGitHubRequestURL) + "&audience=" + audience req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return "", err } // May be replaced by a different client if we hit HTTP_1_1_REQUIRED. client := http.DefaultClient // Retry up to 3 times. for i := 0; ; i++ { req.Header.Set("Authorization", "bearer "+env.Getenv(env.VariableGitHubRequestToken)) resp, err := client.Do(req) if err != nil { if i == 2 { return "", err } // This error isn't exposed by net/http, and retrying this with the // DefaultClient will fail because it will just use HTTP2 again. // I don't know why go doesn't do this for us. if strings.Contains(err.Error(), "HTTP_1_1_REQUIRED") { http1transport := http.DefaultTransport.(*http.Transport).Clone() http1transport.ForceAttemptHTTP2 = false client = &http.Client{ Transport: http1transport, } } fmt.Fprintf(os.Stderr, "error fetching GitHub OIDC token (will retry): %v\n", err) time.Sleep(time.Second) continue } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(io.LimitReader(resp.Body, maxErrorBodyBytes)) return "", fmt.Errorf("identity token request to provider %s failed with status %s: %q", providerName, resp.Status, strings.TrimSpace(string(body))) } var payload struct { Value string `json:"value"` } decoder := json.NewDecoder(resp.Body) if err := decoder.Decode(&payload); err != nil { return "", fmt.Errorf("invalid identity token response from provider %s: %w", providerName, err) } return payload.Value, nil } }