The GitHub Actions provider decoded the response body without checking the status code, so a failing OIDC endpoint surfaced as a raw json error: fetching ambient OIDC credentials: invalid character 'u' looking for beginning of value Check the status code first and report it along with a truncated body, and wrap decode failures with the provider name: fetching ambient OIDC credentials: identity token request to provider github-actions failed with status 502 Bad Gateway: "upstream connect error or disconnect/reset before headers" Fixes #4438 Signed-off-by: Eugene Kalinin <e.v.kalinin@gmail.com>
118 lines
3.4 KiB
Go
118 lines
3.4 KiB
Go
//
|
|
// 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
|
|
}
|
|
}
|