Skip to content

Commit

Permalink
Merge pull request from GHSA-75r6-6jg8-pfcq
Browse files Browse the repository at this point in the history
This change adds some logic to limit the size of the HTTP responses allowed when performing OIDC discovery on a new provider.

Prior to this, Octo STS could have been manipulated into reading arbitrary amounts of data from the provided issuer endpoint.

Signed-off-by: Matt Moore <[email protected]>
  • Loading branch information
mattmoor authored May 10, 2024
1 parent 1fc549c commit 74ba874
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 0 deletions.
50 changes: 50 additions & 0 deletions pkg/maxsize/maxsize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2024 Chainguard, Inc.
// SPDX-License-Identifier: Apache-2.0

package maxsize

import (
"io"
"net/http"
)

// NewRoundTripper creates a new http.RoundTripper that wraps the given
// http.RoundTripper and limits the size of the response body to maxSize bytes.
func NewRoundTripper(maxSize int64, inner http.RoundTripper) http.RoundTripper {
return &ms{
base: inner,
maxBodySize: maxSize,
}
}

type ms struct {
base http.RoundTripper // The underlying RoundTripper
maxBodySize int64 // Maximum allowed response body size in bytes
}

// RoundTrip implements http.RoundTripper
func (rt *ms) RoundTrip(req *http.Request) (*http.Response, error) {
resp, err := rt.base.RoundTrip(req)
if err != nil {
return nil, err
}

resp.Body = &lr{
LimitedReader: io.LimitedReader{
R: resp.Body,
N: rt.maxBodySize,
},
close: resp.Body.Close,
}
return resp, nil
}

type lr struct {
io.LimitedReader
close func() error
}

// Close implements io.Closer
func (r *lr) Close() error {
return r.close()
}
49 changes: 49 additions & 0 deletions pkg/maxsize/maxsize_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2024 Chainguard, Inc.
// SPDX-License-Identifier: Apache-2.0

package maxsize

import (
"context"
"net/http"
"testing"

"github.com/coreos/go-oidc/v3/oidc"
)

func TestCompile(t *testing.T) {
tests := []struct {
name string
size int64
wantErr bool
}{{
name: "large size",
size: 1000000, // 1M bytes
wantErr: false,
}, {
name: "medium size",
size: 10000, // 10000 bytes
wantErr: false,
}, {
name: "tiny size",
size: 10, // 10 bytes
wantErr: true,
}}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := oidc.ClientContext(context.Background(), &http.Client{
Transport: NewRoundTripper(tt.size, http.DefaultTransport),
})
for _, issuer := range []string{
"https://accounts.google.com",
"https://token.actions.githubusercontent.com",
"https://issuer.enforce.dev",
} {
if _, err := oidc.NewProvider(ctx, issuer); (err != nil) != tt.wantErr {
t.Errorf("constructing %q provider: %v", issuer, err)
}
}
})
}
}
14 changes: 14 additions & 0 deletions pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@ package provider
import (
"context"
"fmt"
"net/http"

"github.com/chainguard-dev/clog"
"github.com/chainguard-dev/terraform-infra-common/pkg/httpmetrics"
"github.com/coreos/go-oidc/v3/oidc"
lru "github.com/hashicorp/golang-lru/v2"
"github.com/octo-sts/app/pkg/maxsize"
)

// MaximumResponseSize is the maximum size of allowed responses from
// OIDC providers. Some anecdata
// - Google: needs around 1KiB
// - GitHub: needs around 5KiB
// - Chainguard: needs around 2KiB
const MaximumResponseSize = 100 * 1024 // 100KiB

var (
// providers is an LRU cache of recently used providers.
providers, _ = lru.New2Q[string, *oidc.Provider](100)
Expand All @@ -25,6 +35,10 @@ func Get(ctx context.Context, issuer string) (provider *oidc.Provider, err error
return v, nil
}

ctx = oidc.ClientContext(ctx, &http.Client{
Transport: maxsize.NewRoundTripper(MaximumResponseSize, httpmetrics.Transport),
})

// Verify the token before we trust anything about it.
provider, err = oidc.NewProvider(ctx, issuer)
if err != nil {
Expand Down

0 comments on commit 74ba874

Please sign in to comment.