Skip to content

Commit

Permalink
feat(build, docker, buildah, dockerfile): add secrets support (#6429)
Browse files Browse the repository at this point in the history
Signed-off-by: Yaroslav Pershin <62902094+iapershin@users.noreply.github.com>
  • Loading branch information
iapershin authored Nov 21, 2024
1 parent 2f5d536 commit dac370b
Show file tree
Hide file tree
Showing 21 changed files with 295 additions and 10 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ require (
)

replace (
github.com/containers/buildah => github.com/werf/3p-buildah v1.35.2-0.20241120093816-5b80b7b735de // needs version upgrade with refactoring
github.com/deislabs/oras => github.com/werf/3p-oras v0.9.1-0.20240115121544-03962ecbd40a // upstream not maintained
github.com/docker/buildx => github.com/werf/3p-docker-buildx v0.13.0-rc2.0.20241111114615-d77c2e1444ad // upstream not maintained
github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305 // upstream not maintained
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,6 @@ github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl3
github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw=
github.com/containernetworking/plugins v1.4.1 h1:+sJRRv8PKhLkXIl6tH1D7RMi+CbbHutDGU+ErLBORWA=
github.com/containernetworking/plugins v1.4.1/go.mod h1:n6FFGKcaY4o2o5msgu/UImtoC+fpQXM3076VHfHbj60=
github.com/containers/buildah v1.35.1 h1:m4TF6V8b06cS4jH9/t39PUsUIjzDQg/P14FLpwjr40Y=
github.com/containers/buildah v1.35.1/go.mod h1:vVSVUlTu8+99H5j43gBJscpkb/quZvdJg78+6X1HeTM=
github.com/containers/common v0.58.1 h1:E1DN9Lr7kgMVQy7AXLv1CYQCiqnweklMiYWbf0KOnqY=
github.com/containers/common v0.58.1/go.mod h1:l3vMqanJGj7tZ3W/i76gEJ128VXgFUO1tLaohJXPvdk=
github.com/containers/image/v5 v5.30.0 h1:CmHeSwI6W2kTRWnUsxATDFY5TEX4b58gPkaQcEyrLIA=
Expand Down Expand Up @@ -1389,6 +1387,8 @@ github.com/weppos/publicsuffix-go v0.13.1-0.20210123135404-5fd73613514e/go.mod h
github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE=
github.com/weppos/publicsuffix-go v0.30.2-0.20240219083929-48f3a5ae027a h1:s0Yp4S5jdEQFTJE1blGE5o+n7T0uI386YHXzocLKLR4=
github.com/weppos/publicsuffix-go v0.30.2-0.20240219083929-48f3a5ae027a/go.mod h1:v7j8MuFp1CIYgAd2n7xEUctTbsreRd1vPmOwyzmGFiE=
github.com/werf/3p-buildah v1.35.2-0.20241120093816-5b80b7b735de h1:adTANOZyNmmnDGC3MvPs/fv59skzK7z9dkTIPnpKsS4=
github.com/werf/3p-buildah v1.35.2-0.20241120093816-5b80b7b735de/go.mod h1:vVSVUlTu8+99H5j43gBJscpkb/quZvdJg78+6X1HeTM=
github.com/werf/3p-docker-buildx v0.13.0-rc2.0.20241111114615-d77c2e1444ad h1:FBDNACfjjpDBwXhALF5LgvEiu08HpUurb/2G323SVsQ=
github.com/werf/3p-docker-buildx v0.13.0-rc2.0.20241111114615-d77c2e1444ad/go.mod h1:Q3gtkv8D3sdaa5LJXbzMooYR/A5vBiEB2yQ56wywohM=
github.com/werf/3p-helm v0.0.0-20240806141915-3137f4cc1557 h1:TCC9i3qb4Db+7Z6jgbLwWCWyV/kKvdTezmGUqLtdF54=
Expand Down
1 change: 1 addition & 0 deletions pkg/build/image/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ func mapLegacyDockerfileToImage(ctx context.Context, dockerfileImageConfig *conf
dockerfileImageConfig.AddHost,
dockerfileImageConfig.Network,
dockerfileImageConfig.SSH,
dockerfileImageConfig.Secrets,
),
ds,
stage.NewContextChecksum(dockerIgnorePathMatcher),
Expand Down
10 changes: 9 additions & 1 deletion pkg/build/stage/full_dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ type FullDockerfileStage struct {
*BaseStage
}

func NewDockerRunArgs(dockerfile []byte, dockerfilePath, target, context string, contextAddFiles []string, buildArgs map[string]interface{}, addHost []string, network, ssh string) *DockerRunArgs {
func NewDockerRunArgs(dockerfile []byte, dockerfilePath, target, context string, contextAddFiles []string, buildArgs map[string]interface{}, addHost []string, network, ssh string, secrets []string) *DockerRunArgs {
return &DockerRunArgs{
dockerfile: dockerfile,
dockerfilePath: dockerfilePath,
Expand All @@ -69,6 +69,7 @@ func NewDockerRunArgs(dockerfile []byte, dockerfilePath, target, context string,
addHost: addHost,
network: network,
ssh: ssh,
secrets: secrets,
}
}

Expand All @@ -82,6 +83,7 @@ type DockerRunArgs struct {
addHost []string
network string
ssh string
secrets []string
}

func (d *DockerRunArgs) contextRelativeToGitWorkTree(giterminismManager giterminism_manager.Interface) string {
Expand Down Expand Up @@ -624,6 +626,12 @@ func (s *FullDockerfileStage) SetupDockerImageBuilder(b stage_builder.Dockerfile
}
}

if len(s.secrets) > 0 {
for _, secret := range s.secrets {
b.AppendSecrets(secret)
}
}

if len(s.addHost) > 0 {
b.AppendAddHost(s.addHost...)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/build/stage/full_dockerfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func newTestFullDockerfileStage(dockerfileData []byte, target string, buildArgs
nil,
"",
"",
nil,
),
ds,
NewContextChecksum(nil),
Expand Down
1 change: 1 addition & 0 deletions pkg/buildah/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type BuildFromDockerfileOpts struct {
BuildArgs map[string]string
Target string
Labels []string
Secrets []string
}

type RunMount struct {
Expand Down
8 changes: 8 additions & 0 deletions pkg/buildah/native_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,14 @@ func (b *NativeBuildah) BuildFromDockerfile(ctx context.Context, dockerfile stri
Labels: opts.Labels,
}

if len(opts.Secrets) > 0 {
buildOpts.CommonBuildOpts.Secrets = opts.Secrets
if buildOpts.Isolation.String() == thirdparty.IsolationOCIRootless.String() {
// WA until buildah version upgrade
return "", fmt.Errorf("secrets in rootless mode are not supported yet")
}
}

if targetPlatform != b.GetRuntimePlatform() {
// Prevent local cache collisions in multiplatform build mode:
// allow local cache only for the current runtime platform.
Expand Down
1 change: 1 addition & 0 deletions pkg/config/image_from_dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type ImageFromDockerfile struct {
SSH string
Dependencies []*Dependency
Staged bool
Secrets []string

platform []string
final bool
Expand Down
26 changes: 26 additions & 0 deletions pkg/config/raw_image_from_dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type rawImageFromDockerfile struct {
RawDependencies []*rawDependency `yaml:"dependencies,omitempty"`
Staged bool `yaml:"staged,omitempty"`
Platform []string `yaml:"platform,omitempty"`
RawSecrets []*rawSecret `yaml:"secrets,omitempty"`

doc *doc `yaml:"-"` // parent

Expand Down Expand Up @@ -142,6 +143,31 @@ func (c *rawImageFromDockerfile) toImageFromDockerfileDirective(giterminismManag
image.platform = append([]string{}, c.Platform...)
image.raw = c

if len(c.RawSecrets) > 0 && image.Staged {
return nil, fmt.Errorf("secrets are not supported for staged build yet")
}
secretIds := make(map[string]struct{})
for _, rawSecrets := range c.RawSecrets {
secret, err := rawSecrets.toDirective()
if err != nil {
return nil, err
}

secretId := secret.GetSecretId()
if v, ok := secretIds[secretId]; !ok {
secretIds[secretId] = struct{}{}
} else {
return nil, fmt.Errorf("duplicated secret id %s", v)
}

secretArg, err := secret.GetSecretStringArg()
if err != nil {
return nil, err
}

image.Secrets = append(image.Secrets, secretArg)
}

if err := image.validate(giterminismManager); err != nil {
return nil, err
}
Expand Down
56 changes: 56 additions & 0 deletions pkg/config/raw_secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package config

import (
"fmt"
)

type rawSecret struct {
Id string `yaml:"id"`
Env string `yaml:"env,omitempty"`
Src string `yaml:"src,omitempty"`
PlainValue string `yaml:"value,omitempty"`

doc *doc `yaml:"-"` // parent

UnsupportedAttributes map[string]interface{} `yaml:",inline"`
}

func (s *rawSecret) UnmarshalYAML(unmarshal func(interface{}) error) error {
parentStack.Push(s)
type plain rawSecret
err := unmarshal((*plain)(s))
parentStack.Pop()
if err != nil {
return fmt.Errorf("secrets parsing error: %w", err)
}

if err := s.validate(); err != nil {
return fmt.Errorf("secrets validation error: %w", err)
}

if err := checkOverflow(s.UnsupportedAttributes, nil, s.doc); err != nil {
return fmt.Errorf("secrets validation error: %w", err)
}

return nil
}

func (s *rawSecret) validate() error {
if !oneOrNone([]bool{s.Env != "", s.Src != "", s.PlainValue != ""}) {
return newDetailedConfigError("specify only env or src or value in secret", s, s.doc)
}
return nil
}

func (s *rawSecret) toDirective() (Secret, error) {
switch {
case s.Env != "":
return newSecretFromEnv(s)
case s.Src != "":
return newSecretFromSrc(s)
case s.PlainValue != "":
return newSecretFromPlainValue(s)
default:
return nil, newDetailedConfigError("secret type is not supported", s, s.doc)
}
}
111 changes: 111 additions & 0 deletions pkg/config/secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package config

import (
"errors"
"fmt"
"os"
"path/filepath"
"time"
)

type Secret interface {
GetSecretStringArg() (string, error)
GetSecretId() string
}

type SecretFromEnv struct {
Id string
Value string
}

type SecretFromSrc struct {
Id string
Value string
}

type SecretFromPlainValue struct {
Id string
Value string
}

func newSecretFromEnv(s *rawSecret) (*SecretFromEnv, error) {
if s.Id == "" {
s.Id = s.Env
}
return &SecretFromEnv{
Id: s.Id,
Value: s.Env,
}, nil
}

func newSecretFromSrc(s *rawSecret) (*SecretFromSrc, error) {
if s.Id == "" {
s.Id = filepath.Base(s.Src)
}
return &SecretFromSrc{
Id: s.Id,
Value: s.Src,
}, nil
}

func newSecretFromPlainValue(s *rawSecret) (*SecretFromPlainValue, error) {
if s.Id == "" {
return nil, fmt.Errorf("type value should be used with id parameter")
}
return &SecretFromPlainValue{
Id: s.Id,
Value: s.PlainValue,
}, nil
}

func (s *SecretFromEnv) GetSecretStringArg() (string, error) {
if _, exists := os.LookupEnv(s.Value); !exists {
return "", fmt.Errorf("specified env variable doesn't exist")
}
return fmt.Sprintf("id=%s,env=%s", s.Id, s.Value), nil
}

func (s *SecretFromSrc) GetSecretStringArg() (string, error) {
if _, err := os.Stat(s.Value); errors.Is(err, os.ErrNotExist) {
return "", fmt.Errorf("path %s doesn't exist", s.Value)
}
return fmt.Sprintf("id=%s,src=%s", s.Id, s.Value), nil
}

func (s *SecretFromPlainValue) GetSecretStringArg() (string, error) {
secret, err := s.setPlainValueAsEnv()
if err != nil {
return "", err
}
return secret.GetSecretStringArg()
}

func (s *SecretFromPlainValue) setPlainValueAsEnv() (*SecretFromEnv, error) {
t := time.Now().Unix()
envKey := fmt.Sprintf("tmpbuild%d_%s", t, s.Id) // generate unique value
if _, e := os.LookupEnv(envKey); e {
return nil, fmt.Errorf("can't set secret %s: id is not unique", s.Id) // should never be here
}

err := os.Setenv(envKey, s.Value)
if err != nil {
return nil, fmt.Errorf("can't set value")
}

return &SecretFromEnv{
Id: s.Id,
Value: envKey,
}, nil
}

func (s *SecretFromEnv) GetSecretId() string {
return s.Id
}

func (s *SecretFromSrc) GetSecretId() string {
return s.Id
}

func (s *SecretFromPlainValue) GetSecretId() string {
return s.Id
}
1 change: 1 addition & 0 deletions pkg/container_backend/buildah_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@ func (backend *BuildahBackend) BuildDockerfile(ctx context.Context, dockerfileCo
BuildArgs: buildArgs,
Target: opts.Target,
Labels: opts.Labels,
Secrets: opts.Secrets,
})
}

Expand Down
4 changes: 4 additions & 0 deletions pkg/container_backend/docker_server_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ func (backend *DockerServerBackend) BuildDockerfile(ctx context.Context, _ []byt
cliArgs = append(cliArgs, "--label", label)
}

for _, secret := range opts.Secrets {
cliArgs = append(cliArgs, "--secret", secret)
}

tempID := uuid.New().String()
opts.Tags = append(opts.Tags, tempID)
for _, tag := range opts.Tags {
Expand Down
1 change: 1 addition & 0 deletions pkg/container_backend/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type BuildDockerfileOpts struct {
SSH string
Labels []string
Tags []string
Secrets []string
}

type BuildDockerfileStageOptions struct {
Expand Down
5 changes: 5 additions & 0 deletions pkg/container_backend/stage_builder/dockerfile_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type DockerfileBuilderInterface interface {
SetSSH(ssh string)
AppendLabels(labels ...string)
SetBuildContextArchive(buildContextArchive container_backend.BuildContextArchiver)
AppendSecrets(secret ...string)
}

type DockerfileBuilder struct {
Expand Down Expand Up @@ -109,3 +110,7 @@ func (b *DockerfileBuilder) AppendLabels(labels ...string) {
func (b *DockerfileBuilder) SetBuildContextArchive(buildContextArchive container_backend.BuildContextArchiver) {
b.BuildContextArchive = buildContextArchive
}

func (b *DockerfileBuilder) AppendSecrets(secret ...string) {
b.BuildDockerfileOptions.Secrets = append(b.BuildDockerfileOptions.Secrets, secret...)
}
13 changes: 13 additions & 0 deletions pkg/docker/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ func CliBuild_LiveOutputWithCustomIn(ctx context.Context, rc io.ReadCloser, args
if useBuildx {
buildOpts.EnableBuildx = true
} else {
err := failOnBuildKitOnlyOpts(args...)
if err != nil {
return err
}
// ensure buildkit not enabled
if err := os.Setenv("DOCKER_BUILDKIT", "0"); err != nil {
return err
Expand All @@ -232,3 +236,12 @@ func CliBuild_LiveOutput(ctx context.Context, args ...string) error {
buildOpts := BuildOptions{EnableBuildx: useBuildx}
return doCliBuild(cli(ctx), buildOpts, args...)
}

func failOnBuildKitOnlyOpts(args ...string) error {
for _, arg := range args {
if strings.Contains(arg, "--secret") {
return fmt.Errorf("secrets are only available with Docker BuildKit")
}
}
return nil
}
Loading

0 comments on commit dac370b

Please sign in to comment.