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
6 changes: 4 additions & 2 deletions cmd/apk/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@ func main() {
}
log.Printf("listening on %s", port)

// TODO: Auth.
opt := []apk.Option{apk.WithUserAgent(userAgent)}
if *auth || os.Getenv("AUTH") == "keychain" {
opt = append(opt, apk.WithKeychain(gcrane.Keychain))
}
if cgid := os.Getenv("CHAINGUARD_IDENTITY"); cgid != "" {
cgauth := apk.NewChainguardIdentityAuth(cgid, "https://issuer.enforce.dev", "apk.cgr.dev")
cgauth, err := apk.NewChainguardMultiKeychain(cgid, "https://issuer.enforce.dev", "apk.cgr.dev")
if err != nil {
log.Fatalf("error creating apk auth keychain: %v", err)
}
opt = append(opt, apk.WithAuth(cgauth))
}
if eg := os.Getenv("EXAMPLES"); eg != "" {
Expand Down
5 changes: 4 additions & 1 deletion cmd/oci/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ func main() {
opt := []explore.Option{explore.WithUserAgent(userAgent)}
kcs := []authn.Keychain{}
if cgid := os.Getenv("CHAINGUARD_IDENTITY"); cgid != "" {
cgauth := explore.NewChainguardIdentityAuth(cgid, "https://issuer.enforce.dev", "cgr.dev")
cgauth, err := explore.NewChainguardMultiKeychain(cgid, "https://issuer.enforce.dev", "cgr.dev")
if err != nil {
log.Fatalf("error creating OCI auth keychain: %v", err)
}
kcs = append(kcs, cgauth)
}
if *auth || os.Getenv("AUTH") == "keychain" {
Expand Down
49 changes: 49 additions & 0 deletions internal/apk/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"chainguard.dev/sdk/sts"
"github.com/jonjohnsonjr/dagdotdev/internal/chainguard"
"golang.org/x/time/rate"
"google.golang.org/api/idtoken"
)
Expand All @@ -33,6 +34,33 @@ func NewChainguardIdentityAuth(identity, issuer, audience string) Authenticator
}
}

// NewChainguardIdentityAuthFromURL parses a URL of the form [email protected]?iss=issuer.enforce.dev
func NewChainguardIdentityAuthFromURL(raw string) (Authenticator, error) {
id, err := chainguard.ParseIdentity(raw)
if err != nil {
return nil, err
}

return NewChainguardIdentityAuth(id.ID, id.Issuer, id.Audience), nil
}

func NewChainguardMultiKeychain(raw string, defaultIssuer string, defaultAudience string) (Authenticator, error) {
var ks []Authenticator
for _, s := range strings.Split(raw, ",") {
if strings.HasPrefix(s, "chainguard://") {
k, err := NewChainguardIdentityAuthFromURL(s)
if err != nil {
return nil, fmt.Errorf("parsing %q: %w", s, err)
}
ks = append(ks, k)
} else {
// Not URL format, fallback to basic identity format.
ks = append(ks, NewChainguardIdentityAuth(s, defaultIssuer, defaultAudience))
}
}
return NewMultiAuthenticator(ks...), nil
}

type cgAuth struct {
id, iss, aud string

Expand Down Expand Up @@ -73,3 +101,24 @@ func (a *cgAuth) AddAuth(ctx context.Context, req *http.Request) error {
req.SetBasicAuth("user", a.cgtok)
return nil
}

type multiAuthenticator struct {
auths []Authenticator
}

func NewMultiAuthenticator(auth ...Authenticator) Authenticator {
return &multiAuthenticator{auths: auth}
}

func (a *multiAuthenticator) AddAuth(ctx context.Context, req *http.Request) error {
for _, auth := range a.auths {
if err := auth.AddAuth(ctx, req); err != nil {
return err
}
if req.Header.Get("Authorization") != "" {
// Auth was set, we're done.
return nil
}
}
return nil
}
61 changes: 61 additions & 0 deletions internal/apk/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package apk

import (
"context"
"encoding/base64"
"net/http"
"net/http/httptest"
"testing"
)

type mockAuth struct {
token string
}

func (m *mockAuth) AddAuth(ctx context.Context, req *http.Request) error {
if m.token != "" {
req.SetBasicAuth("user", m.token)
}
return nil
}

func TestNewMultiAuthenticator(t *testing.T) {
auth := NewMultiAuthenticator(&mockAuth{}, &mockAuth{token: "foo"})
req := httptest.NewRequest("GET", "/", nil)
if err := auth.AddAuth(context.Background(), req); err != nil {
t.Fatalf("NewMultiAuthenticator() error = %v", err)
}
want := "Basic " + base64.StdEncoding.EncodeToString([]byte("user:foo"))
got := req.Header.Get("Authorization")
if got != want {
t.Errorf("Authorization = %v, want %v", string(got), want)
}
}

func TestNewChainguardMultiKeychain(t *testing.T) {
_, err := NewChainguardMultiKeychain("uidp,chainguard://[email protected]?iss=issuer.enforce.dev", "foo", "bar")
if err != nil {
t.Fatal(err)
}
}

func TestNewChainguardIdentityAuthFromURL(t *testing.T) {
auth, err := NewChainguardIdentityAuthFromURL("chainguard://[email protected]?iss=issuer.enforce.dev")
if err != nil {
t.Fatal(err)
}
cgauth, ok := auth.(*cgAuth)
if !ok {
t.Fatalf("NewChainguardIdentityAuthFromURL() = %T, want *cgAuth", auth)
}

if cgauth.id != "uidp" {
t.Errorf("id = %v, want uidp", cgauth.id)
}
if cgauth.iss != "https://issuer.enforce.dev" {
t.Errorf("iss = %v, want https://issuer.enforce.dev", cgauth.iss)
}
if cgauth.aud != "apk.cgr.dev" {
t.Errorf("aud = %v, want apk.cgr.dev", cgauth.aud)
}
}
35 changes: 35 additions & 0 deletions internal/chainguard/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package chainguard

import (
"fmt"
"net/url"
"strings"
)

type Identity struct {
ID, Issuer, Audience string
}

func ParseIdentity(raw string) (*Identity, error) {
u, err := url.Parse(raw)
if err != nil {
return nil, fmt.Errorf("parsing URL: %w", err)
}

if u.Scheme != "chainguard" {
return nil, fmt.Errorf("invalid scheme %q", u.Scheme)
}

iss := u.Query().Get("iss")
if iss == "" {
return nil, fmt.Errorf("missing issuer query parameter")
}
if !strings.HasPrefix(iss, "https://") {
iss = "https://" + iss
}
return &Identity{
ID: u.User.Username(),
Issuer: iss,
Audience: u.Hostname(),
}, nil
}
47 changes: 47 additions & 0 deletions internal/chainguard/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package chainguard

import (
"testing"
)

func TestNewChainguardIdentityAuthFromURL(t *testing.T) {
cases := []struct {
rawURL string
wantErr bool
wantID string
wantIss string
wantAud string
}{
{
rawURL: "chainguard://[email protected]?iss=issuer.enforce.dev",
wantErr: false,
wantID: "uidp",
wantIss: "https://issuer.enforce.dev",
wantAud: "cgr.dev",
},
{
rawURL: "invalid-url",
wantErr: true,
},
}

for _, tc := range cases {
t.Run(tc.rawURL, func(t *testing.T) {
got, err := ParseIdentity(tc.rawURL)
if (err != nil) != tc.wantErr {
t.Fatalf("NewChainguardIdentityAuthFromURL() error = %v, wantErr %v", err, tc.wantErr)
}
if err == nil {
if got.ID != tc.wantID {
t.Errorf("id = %v, want %v", got.ID, tc.wantID)
}
if got.Issuer != tc.wantIss {
t.Errorf("iss = %v, want %v", got.Issuer, tc.wantIss)
}
if got.Audience != tc.wantAud {
t.Errorf("aud = %v, want %v", got.Audience, tc.wantAud)
}
}
})
}
}
32 changes: 30 additions & 2 deletions internal/explore/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"context"
"fmt"
"log"
"strings"
"time"

"chainguard.dev/sdk/sts"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/jonjohnsonjr/dagdotdev/internal/chainguard"
"golang.org/x/time/rate"
"google.golang.org/api/idtoken"
)
Expand All @@ -22,6 +24,32 @@ func NewChainguardIdentityAuth(identity, issuer, audience string) authn.Keychain
}
}

// NewChainguardIdentityAuthFromURL parses a URL of the form [email protected]?iss=issuer.enforce.dev
func NewChainguardIdentityAuthFromURL(raw string) (authn.Keychain, error) {
id, err := chainguard.ParseIdentity(raw)
if err != nil {
return nil, err
}
return NewChainguardIdentityAuth(id.ID, id.Issuer, id.Audience), nil
}

func NewChainguardMultiKeychain(raw string, defaultIssuer string, defaultAudience string) (authn.Keychain, error) {
var ks []authn.Keychain
for _, s := range strings.Split(raw, ",") {
if strings.HasPrefix(s, "chainguard://") {
k, err := NewChainguardIdentityAuthFromURL(s)
if err != nil {
return nil, fmt.Errorf("parsing %q: %w", s, err)
}
ks = append(ks, k)
} else {
// Not URL format, fallback to basic identity format.
ks = append(ks, NewChainguardIdentityAuth(s, defaultIssuer, defaultAudience))
}
}
return authn.NewMultiKeychain(ks...), nil
}

type keychain struct {
id, iss, aud string

Expand All @@ -42,8 +70,8 @@ func (k *keychain) ResolveContext(ctx context.Context, res authn.Resource) (auth
return authn.Anonymous, nil
}

if res.RegistryStr() != "cgr.dev" {
log.Printf("%q != %q", res.RegistryStr(), "cgr.dev")
if res.RegistryStr() != k.aud {
log.Printf("%q != %q", res.RegistryStr(), k.aud)
return authn.Anonymous, nil
}

Expand Down
31 changes: 31 additions & 0 deletions internal/explore/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package explore

import "testing"

func TestNewChainguardIdentityAuthFromURL(t *testing.T) {
auth, err := NewChainguardIdentityAuthFromURL("chainguard://[email protected]?iss=issuer.enforce.dev")
if err != nil {
t.Fatal(err)
}
cgauth, ok := auth.(*keychain)
if !ok {
t.Fatalf("NewChainguardIdentityAuthFromURL() = %T, want *cgAuth", auth)
}

if cgauth.id != "uidp" {
t.Errorf("id = %v, want uidp", cgauth.id)
}
if cgauth.iss != "https://issuer.enforce.dev" {
t.Errorf("iss = %v, want https://issuer.enforce.dev", cgauth.iss)
}
if cgauth.aud != "apk.cgr.dev" {
t.Errorf("aud = %v, want apk.cgr.dev", cgauth.aud)
}
}

func TestNewChainguardMultiKeychain(t *testing.T) {
_, err := NewChainguardMultiKeychain("uidp,chainguard://[email protected]?iss=issuer.enforce.dev", "foo", "bar")
if err != nil {
t.Fatalf("NewChainguardMultiKeychain() error = %v", err)
}
}
10 changes: 8 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ func run(args []string) error {
opt = append(opt, apk.WithKeychain(gcrane.Keychain))
}
if cgid := os.Getenv("CHAINGUARD_IDENTITY"); cgid != "" {
cgauth := apk.NewChainguardIdentityAuth(cgid, "https://issuer.enforce.dev", "apk.cgr.dev")
cgauth, err := apk.NewChainguardMultiKeychain(cgid, "https://issuer.enforce.dev", "apk.cgr.dev")
if err != nil {
return fmt.Errorf("error creating apk auth keychain: %w", err)
}
opt = append(opt, apk.WithAuth(cgauth))
}
if eg := os.Getenv("EXAMPLES"); eg != "" {
Expand All @@ -68,7 +71,10 @@ func run(args []string) error {
kcs := []authn.Keychain{}
if cgid := os.Getenv("CHAINGUARD_IDENTITY"); cgid != "" {
log.Printf("saw CHAINGUARD_IDENTITY=%q", cgid)
cgauth := explore.NewChainguardIdentityAuth(cgid, "https://issuer.enforce.dev", "cgr.dev")
cgauth, err := explore.NewChainguardMultiKeychain(cgid, "https://issuer.enforce.dev", "cgr.dev")
if err != nil {
return fmt.Errorf("error creating OCI auth keychain: %w", err)
}
kcs = append(kcs, cgauth)
}
if *auth || os.Getenv("AUTH") == "keychain" {
Expand Down