Skip to content

Commit dcf1380

Browse files
authored
feat(attestation): explicit OCI credentials support (#513)
Signed-off-by: Miguel Martinez Trivino <[email protected]>
1 parent 494ae5d commit dcf1380

File tree

12 files changed

+94
-20
lines changed

12 files changed

+94
-20
lines changed

app/cli/cmd/attestation_add.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package cmd
1818
import (
1919
"errors"
2020
"fmt"
21+
"os"
2122

2223
"github.com/spf13/cobra"
2324
"github.com/spf13/viper"
@@ -31,6 +32,15 @@ func newAttestationAddCmd() *cobra.Command {
3132
var artifactCASConn *grpc.ClientConn
3233
var annotationsFlag []string
3334

35+
// OCI registry credentials can be passed as flags or environment variables
36+
var registryServer, registryUsername, registryPassword string
37+
const (
38+
registryServerEnvVarName = "CHAINLOOP_REGISTRY_SERVER"
39+
registryUsernameEnvVarName = "CHAINLOOP_REGISTRY_USERNAME"
40+
// nolint: gosec
41+
registryPasswordEnvVarName = "CHAINLOOP_REGISTRY_PASSWORD"
42+
)
43+
3444
cmd := &cobra.Command{
3545
Use: "add",
3646
Short: "add a material to the attestation",
@@ -43,6 +53,9 @@ func newAttestationAddCmd() *cobra.Command {
4353
ActionsOpts: actionOpts,
4454
CASURI: viper.GetString(confOptions.CASAPI.viperKey),
4555
ConnectionInsecure: flagInsecure,
56+
RegistryServer: registryServer,
57+
RegistryUsername: registryUsername,
58+
RegistryPassword: registryPassword,
4659
},
4760
)
4861
if err != nil {
@@ -85,5 +98,22 @@ func newAttestationAddCmd() *cobra.Command {
8598
cmd.Flags().StringSliceVar(&annotationsFlag, "annotation", nil, "additional annotation in the format of key=value")
8699
flagAttestationID(cmd)
87100

101+
// Optional OCI registry credentials
102+
cmd.Flags().StringVar(&registryServer, "registry-server", "", fmt.Sprintf("OCI repository server, ($%s)", registryServerEnvVarName))
103+
cmd.Flags().StringVar(&registryUsername, "registry-username", "", fmt.Sprintf("registry username, ($%s)", registryUsernameEnvVarName))
104+
cmd.Flags().StringVar(&registryPassword, "registry-password", "", fmt.Sprintf("registry password, ($%s)", registryPasswordEnvVarName))
105+
106+
if registryServer == "" {
107+
registryServer = os.Getenv(registryServerEnvVarName)
108+
}
109+
110+
if registryUsername == "" {
111+
registryUsername = os.Getenv(registryUsernameEnvVarName)
112+
}
113+
114+
if registryPassword == "" {
115+
registryPassword = os.Getenv(registryPasswordEnvVarName)
116+
}
117+
88118
return cmd
89119
}

app/cli/internal/action/action.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func toTimePtr(t time.Time) *time.Time {
4040
}
4141

4242
// load a crafter with either local or remote state
43-
func newCrafter(enableRemoteState bool, conn *grpc.ClientConn, logger *zerolog.Logger) (*crafter.Crafter, error) {
43+
func newCrafter(enableRemoteState bool, conn *grpc.ClientConn, opts ...crafter.NewOpt) (*crafter.Crafter, error) {
4444
var stateManager crafter.StateManager
4545
var err error
4646

@@ -55,5 +55,5 @@ func newCrafter(enableRemoteState bool, conn *grpc.ClientConn, logger *zerolog.L
5555
return nil, fmt.Errorf("failed to create state manager: %w", err)
5656
}
5757

58-
return crafter.NewCrafter(stateManager, crafter.WithLogger(logger))
58+
return crafter.NewCrafter(stateManager, opts...)
5959
}

app/cli/internal/action/attestation_add.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ type AttestationAddOpts struct {
3232
ArtifactsCASConn *grpc.ClientConn
3333
CASURI string
3434
ConnectionInsecure bool
35+
// OCI registry credentials used for CONTAINER_IMAGE material type
36+
RegistryServer, RegistryUsername, RegistryPassword string
3537
}
3638

3739
type AttestationAdd struct {
@@ -42,7 +44,13 @@ type AttestationAdd struct {
4244
}
4345

4446
func NewAttestationAdd(cfg *AttestationAddOpts) (*AttestationAdd, error) {
45-
c, err := newCrafter(cfg.UseAttestationRemoteState, cfg.CPConnection, &cfg.Logger)
47+
opts := []crafter.NewOpt{crafter.WithLogger(&cfg.Logger)}
48+
if cfg.RegistryServer != "" && cfg.RegistryUsername != "" && cfg.RegistryPassword != "" {
49+
cfg.Logger.Debug().Str("server", cfg.RegistryServer).Str("username", cfg.RegistryUsername).Msg("using OCI registry credentials")
50+
opts = append(opts, crafter.WithOCIAuth(cfg.RegistryServer, cfg.RegistryUsername, cfg.RegistryPassword))
51+
}
52+
53+
c, err := newCrafter(cfg.UseAttestationRemoteState, cfg.CPConnection, opts...)
4654
if err != nil {
4755
return nil, fmt.Errorf("failed to load crafter: %w", err)
4856
}

app/cli/internal/action/attestation_init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func (e ErrRunnerContextNotFound) Error() string {
5353
}
5454

5555
func NewAttestationInit(cfg *AttestationInitOpts) (*AttestationInit, error) {
56-
c, err := newCrafter(cfg.UseAttestationRemoteState, cfg.CPConnection, &cfg.Logger)
56+
c, err := newCrafter(cfg.UseAttestationRemoteState, cfg.CPConnection, crafter.WithLogger(&cfg.Logger))
5757
if err != nil {
5858
return nil, fmt.Errorf("failed to load crafter: %w", err)
5959
}

app/cli/internal/action/attestation_push.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type AttestationPush struct {
4646
}
4747

4848
func NewAttestationPush(cfg *AttestationPushOpts) (*AttestationPush, error) {
49-
c, err := newCrafter(cfg.UseAttestationRemoteState, cfg.CPConnection, &cfg.Logger)
49+
c, err := newCrafter(cfg.UseAttestationRemoteState, cfg.CPConnection, crafter.WithLogger(&cfg.Logger))
5050
if err != nil {
5151
return nil, fmt.Errorf("failed to load crafter: %w", err)
5252
}

app/cli/internal/action/attestation_reset.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ type AttestationReset struct {
3636
c *crafter.Crafter
3737
}
3838

39-
func NewAttestationReset(opts *ActionsOpts) (*AttestationReset, error) {
40-
c, err := newCrafter(opts.UseAttestationRemoteState, opts.CPConnection, &opts.Logger)
39+
func NewAttestationReset(cfg *ActionsOpts) (*AttestationReset, error) {
40+
c, err := newCrafter(cfg.UseAttestationRemoteState, cfg.CPConnection, crafter.WithLogger(&cfg.Logger))
4141
if err != nil {
4242
return nil, fmt.Errorf("failed to load crafter: %w", err)
4343
}
4444

45-
return &AttestationReset{ActionsOpts: opts, c: c}, nil
45+
return &AttestationReset{ActionsOpts: cfg, c: c}, nil
4646
}
4747

4848
func (action *AttestationReset) Run(ctx context.Context, attestationID, trigger, reason string) error {

app/cli/internal/action/attestation_status.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ type AttestationStatusResultMaterial struct {
6060
}
6161

6262
func NewAttestationStatus(cfg *AttestationStatusOpts) (*AttestationStatus, error) {
63-
c, err := newCrafter(cfg.UseAttestationRemoteState, cfg.CPConnection, &cfg.Logger)
63+
c, err := newCrafter(cfg.UseAttestationRemoteState, cfg.CPConnection, crafter.WithLogger(&cfg.Logger))
6464
if err != nil {
6565
return nil, fmt.Errorf("failed to load crafter: %w", err)
6666
}

internal/attestation/crafter/crafter.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ import (
3131
api "github.com/chainloop-dev/chainloop/internal/attestation/crafter/api/attestation/v1"
3232
"github.com/chainloop-dev/chainloop/internal/attestation/crafter/materials"
3333
"github.com/chainloop-dev/chainloop/internal/casclient"
34+
"github.com/chainloop-dev/chainloop/internal/ociauth"
3435
"github.com/go-git/go-git/v5"
3536
"github.com/go-git/go-git/v5/plumbing"
37+
"github.com/google/go-containerregistry/pkg/authn"
3638
"github.com/rs/zerolog"
3739
"google.golang.org/protobuf/encoding/protojson"
3840
"google.golang.org/protobuf/types/known/timestamppb"
@@ -59,21 +61,37 @@ type Crafter struct {
5961
Runner supportedRunner
6062
workingDir string
6163
stateManager StateManager
64+
// Authn is used to authenticate with the OCI registry
65+
ociRegistryAuth authn.Keychain
6266
}
6367

6468
var ErrAttestationStateNotLoaded = errors.New("crafting state not loaded")
6569

66-
type NewOpt func(c *Crafter)
70+
type NewOpt func(c *Crafter) error
6771

6872
func WithLogger(l *zerolog.Logger) NewOpt {
69-
return func(c *Crafter) {
73+
return func(c *Crafter) error {
7074
c.logger = l
75+
return nil
7176
}
7277
}
7378

7479
func WithWorkingDirPath(path string) NewOpt {
75-
return func(c *Crafter) {
80+
return func(c *Crafter) error {
7681
c.workingDir = path
82+
return nil
83+
}
84+
}
85+
86+
func WithOCIAuth(server, username, password string) NewOpt {
87+
return func(c *Crafter) error {
88+
k, err := ociauth.NewCredentialsFromRegistry(server, username, password)
89+
if err != nil {
90+
return fmt.Errorf("failed to load OCI credentials: %w", err)
91+
}
92+
93+
c.ociRegistryAuth = k
94+
return nil
7795
}
7896
}
7997

@@ -86,10 +104,14 @@ func NewCrafter(stateManager StateManager, opts ...NewOpt) (*Crafter, error) {
86104
logger: &noopLogger,
87105
workingDir: cw,
88106
stateManager: stateManager,
107+
// By default we authenticate with the current user's keychain (i.e ~/.docker/config.json)
108+
ociRegistryAuth: authn.DefaultKeychain,
89109
}
90110

91111
for _, opt := range opts {
92-
opt(c)
112+
if err := opt(c); err != nil {
113+
return nil, err
114+
}
93115
}
94116

95117
return c, nil
@@ -435,7 +457,7 @@ func (c *Crafter) AddMaterial(ctx context.Context, attestationID, key, value str
435457
}
436458

437459
// 3 - Craft resulting material
438-
mt, err := materials.Craft(context.Background(), m, value, casBackend, c.logger)
460+
mt, err := materials.Craft(context.Background(), m, value, casBackend, c.ociRegistryAuth, c.logger)
439461
if err != nil {
440462
return err
441463
}

internal/attestation/crafter/materials/materials.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
2828
api "github.com/chainloop-dev/chainloop/internal/attestation/crafter/api/attestation/v1"
2929
"github.com/chainloop-dev/chainloop/internal/casclient"
30+
"github.com/google/go-containerregistry/pkg/authn"
3031
cr_v1 "github.com/google/go-containerregistry/pkg/v1"
3132
"github.com/rs/zerolog"
3233
"google.golang.org/protobuf/types/known/timestamppb"
@@ -135,7 +136,7 @@ type Craftable interface {
135136
Craft(ctx context.Context, value string) (*api.Attestation_Material, error)
136137
}
137138

138-
func Craft(ctx context.Context, materialSchema *schemaapi.CraftingSchema_Material, value string, casBackend *casclient.CASBackend, logger *zerolog.Logger) (*api.Attestation_Material, error) {
139+
func Craft(ctx context.Context, materialSchema *schemaapi.CraftingSchema_Material, value string, casBackend *casclient.CASBackend, ociAuth authn.Keychain, logger *zerolog.Logger) (*api.Attestation_Material, error) {
139140
var crafter Craftable
140141
var err error
141142

@@ -147,7 +148,7 @@ func Craft(ctx context.Context, materialSchema *schemaapi.CraftingSchema_Materia
147148
case schemaapi.CraftingSchema_Material_STRING:
148149
crafter, err = NewStringCrafter(materialSchema)
149150
case schemaapi.CraftingSchema_Material_CONTAINER_IMAGE:
150-
crafter, err = NewOCIImageCrafter(materialSchema, logger)
151+
crafter, err = NewOCIImageCrafter(materialSchema, ociAuth, logger)
151152
case schemaapi.CraftingSchema_Material_ARTIFACT:
152153
crafter, err = NewArtifactCrafter(materialSchema, casBackend, logger)
153154
case schemaapi.CraftingSchema_Material_SBOM_CYCLONEDX_JSON:

internal/attestation/crafter/materials/materials_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func TestCraft(t *testing.T) {
4040
},
4141
}
4242

43-
got, err := materials.Craft(context.TODO(), schema, "test-value", nil, nil)
43+
got, err := materials.Craft(context.TODO(), schema, "test-value", nil, nil, nil)
4444
require.NoError(t, err)
4545
assert.Equal(contractAPI.CraftingSchema_Material_STRING, got.MaterialType)
4646
assert.False(got.UploadedToCas)

0 commit comments

Comments
 (0)