Skip to content
Open
Show file tree
Hide file tree
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
107 changes: 75 additions & 32 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ type verifierWithConfig struct {
*oidc.Config
}

type bearerTokenTransport struct {
Transport http.RoundTripper
Token string
}

func (t *bearerTokenTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req = req.Clone(req.Context())
req.Header.Set("Authorization", "Bearer "+t.Token)
return t.Transport.RoundTrip(req)
}

type FulcioConfig struct {
OIDCIssuers map[string]OIDCIssuer `json:"OIDCIssuers,omitempty" yaml:"oidc-issuers,omitempty"`

Expand Down Expand Up @@ -292,55 +303,89 @@ func httpClientForIssuer(iss OIDCIssuer) (*http.Client, error) {
}

func (fc *FulcioConfig) prepare() error {
if _, ok := fc.GetIssuer("https://kubernetes.default.svc"); ok {
// Add the Kubernetes cluster's CA to the system CA pool, and to
// the default transport.
fc.verifiers = make(map[string][]*verifierWithConfig, len(fc.OIDCIssuers))
for _, iss := range fc.OIDCIssuers {
if err := fc.insertVerifier(iss); err != nil {
log.Logger.Errorf("error creating provider for issuer URL %q: %v", iss.IssuerURL, err)
continue
}
}

cache, err := lru.New2Q[string, []*verifierWithConfig](100 /* size */)
if err != nil {
return fmt.Errorf("lru: %w", err)
}
fc.lru = cache
return nil
}

var (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could all be consts.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, they could, but I make them vars because it allows me to change the value for testing purposes. https://github.com/bouskaJ/fulcio/blob/fix_2110/pkg/config/config_test.go#L905

k8sCA = "/var/run/fulcio/ca.crt"
// k8sTokenFile specifies the standard path where Kubernetes automatically
// mounts the projected service account token for a pod.
// This path is publicly known and not a sensitive credential itself,
// hence the Gosec G101 warning is a false positive and is suppressed.
// #nosec G101
k8sTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
k8sIssuerURL = "https://kubernetes.default.svc"
)

func (fc *FulcioConfig) insertVerifier(iss OIDCIssuer) error {
var client *http.Client
if iss.IssuerURL == k8sIssuerURL {
var transport http.RoundTripper
// Add the Kubernetes cluster's CA to the client's CA pool
rootCAs, _ := x509.SystemCertPool()
if rootCAs == nil {
rootCAs = x509.NewCertPool()
}
const k8sCA = "/var/run/fulcio/ca.crt"
certs, err := os.ReadFile(k8sCA)
if err != nil {
return fmt.Errorf("read file: %w", err)
return fmt.Errorf("unable to read cluster CA file: %w", err)
}
if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
return fmt.Errorf("unable to append certs")
return fmt.Errorf("unable to append cluster certs")
}

t := originalTransport.(*http.Transport).Clone()
t := http.DefaultTransport.(*http.Transport).Clone()
t.TLSClientConfig.RootCAs = rootCAs
http.DefaultTransport = t
} else {
// If we parse a config that doesn't include a cluster issuer
// signed with the cluster'sCA, then restore the original transport
// (in case we overwrote it)
http.DefaultTransport = originalTransport
}
transport = t

fc.verifiers = make(map[string][]*verifierWithConfig, len(fc.OIDCIssuers))
for _, iss := range fc.OIDCIssuers {
ctx, cancel := context.WithTimeout(context.Background(), defaultOIDCDiscoveryTimeout)
defer cancel()
client, err := httpClientForIssuer(iss)
if err != nil {
log.Logger.Errorf("error creating http client for issuer URL %q: %v", iss.IssuerURL, err)
continue
if _, err := os.Stat(k8sTokenFile); err == nil {
// add the authentication header
tokenBytes, err := os.ReadFile(k8sTokenFile)
if err != nil {
return fmt.Errorf("unable to read cluster token file: %w", err)
}
transport = &bearerTokenTransport{
Transport: transport,
Token: string(tokenBytes),
}
} else {
if errors.Is(err, os.ErrNotExist) {
log.Logger.Warnf("Kubernetes token file can't be found on path: %s", k8sTokenFile)
} else {
return err
}
}
provider, err := oidc.NewProvider(oidc.ClientContext(ctx, client), iss.IssuerURL)
client = &http.Client{Transport: transport}

} else {
var err error
client, err = httpClientForIssuer(iss)
if err != nil {
log.Logger.Errorf("error creating provider for issuer URL %q: %v", iss.IssuerURL, err)
} else {
cfg := &oidc.Config{ClientID: iss.ClientID}
fc.verifiers[iss.IssuerURL] = []*verifierWithConfig{{provider.Verifier(cfg), cfg}}
return err
}
}

cache, err := lru.New2Q[string, []*verifierWithConfig](100 /* size */)
ctx, cancel := context.WithTimeout(context.Background(), defaultOIDCDiscoveryTimeout)
defer cancel()
provider, err := oidc.NewProvider(oidc.ClientContext(ctx, client), iss.IssuerURL)
if err != nil {
return fmt.Errorf("lru: %w", err)
return err
}
fc.lru = cache
cfg := &oidc.Config{ClientID: iss.ClientID}
fc.verifiers[iss.IssuerURL] = []*verifierWithConfig{{provider.Verifier(cfg), cfg}}
return nil
}

Expand Down Expand Up @@ -507,8 +552,6 @@ var DefaultConfig = &FulcioConfig{
},
}

var originalTransport = http.DefaultTransport

type configKey struct{}

func With(ctx context.Context, cfg *FulcioConfig) context.Context {
Expand Down
Loading