support for pkg build ssh

Signed-off-by: Avi Deitcher <avi@deitcher.net>
This commit is contained in:
Avi Deitcher
2024-07-28 10:51:25 +03:00
parent 34304b1e63
commit 51727db254
52 changed files with 7417 additions and 21 deletions

View File

@@ -102,6 +102,7 @@ require (
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/klauspost/compress v1.17.4 // indirect

View File

@@ -203,6 +203,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/in-toto/in-toto-golang v0.5.0 h1:hb8bgwr0M2hGdDsLjkJ3ZqJ8JFLL/tgYdAxF/XEFBbY=

View File

@@ -49,6 +49,7 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
dockerfile string
buildArgFiles []string
progress string
ssh []string
)
cmd.RunE = func(cmd *cobra.Command, args []string) error {
@@ -162,6 +163,9 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
opts = append(opts, pkglib.WithBuildBuilderImage(builderImage))
opts = append(opts, pkglib.WithBuildBuilderRestart(builderRestart))
opts = append(opts, pkglib.WithProgress(progress))
if len(ssh) > 0 {
opts = append(opts, pkglib.WithSSH(ssh))
}
for _, p := range pkgs {
// things we need our own copies of
@@ -229,6 +233,7 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
cmd.Flags().StringVar(&dockerfile, "dockerfile", "", "Dockerfile to use for building the image, must be in this directory or below, overrides what is in build.yml")
cmd.Flags().StringArrayVar(&buildArgFiles, "build-arg-file", nil, "Files containing build arguments, one key=value per line, contents augment and override buildArgs in build.yml. Can be specified multiple times. File is relative to working directory when running `linuxkit pkg build`")
cmd.Flags().StringVar(&progress, "progress", "auto", "Set type of progress output (auto, plain, tty). Use plain to show container output, tty for interactive build")
cmd.Flags().StringArrayVar(&ssh, "ssh", nil, "SSH agent config to use for build, follows the syntax used for buildx and buildctl, see https://docs.docker.com/reference/dockerfile/#run---mounttypessh")
return cmd
}

View File

@@ -13,9 +13,9 @@ import (
"strings"
"github.com/containerd/containerd/reference"
"github.com/docker/docker/api/types"
registry "github.com/google/go-containerregistry/pkg/v1"
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/cache"
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/version"
@@ -46,6 +46,7 @@ type buildOpts struct {
dockerfile string
buildArgs []string
progress string
ssh []string
}
// BuildOpt allows callers to specify options to Build
@@ -215,6 +216,14 @@ func WithProgress(progress string) BuildOpt {
}
}
// WithSSH sets up the package to use SSH in the build
func WithSSH(ssh []string) BuildOpt {
return func(bo *buildOpts) error {
bo.ssh = ssh
return nil
}
}
// Build builds the package
func (p Pkg) Build(bos ...BuildOpt) error {
var bo buildOpts
@@ -350,7 +359,7 @@ func (p Pkg) Build(bos ...BuildOpt) error {
}
fmt.Fprintf(writer, "building %s for arches: %s\n", ref, strings.Join(arches, ","))
var (
imageBuildOpts = types.ImageBuildOptions{
imageBuildOpts = spec.ImageBuildOptions{
Labels: map[string]string{},
BuildArgs: map[string]*string{},
}
@@ -440,6 +449,8 @@ func (p Pkg) Build(bos ...BuildOpt) error {
imageBuildOpts.BuildArgs["PKG_IMAGE"] = &ret
}
imageBuildOpts.SSH = bo.ssh
// build for each arch and save in the linuxkit cache
for _, platform := range platformsToBuild {
builtDescs, err := p.buildArch(ctx, d, c, bo.builderImage, platform.Architecture, bo.builderRestart, writer, bo, imageBuildOpts)
@@ -592,7 +603,7 @@ func (p Pkg) Build(bos ...BuildOpt) error {
// C - manifest, saved in cache as is, referenced by the index (E), and returned as a descriptor
// D - attestations (if any), saved in cache as is, referenced by the index (E), and returned as a descriptor
// E - index, saved in cache as is, stored in cache as tag "image:tag-arch", *not* returned as a descriptor
func (p Pkg) buildArch(ctx context.Context, d dockerRunner, c lktspec.CacheProvider, builderImage, arch string, restart bool, writer io.Writer, bo buildOpts, imageBuildOpts types.ImageBuildOptions) ([]registry.Descriptor, error) {
func (p Pkg) buildArch(ctx context.Context, d dockerRunner, c lktspec.CacheProvider, builderImage, arch string, restart bool, writer io.Writer, bo buildOpts, imageBuildOpts spec.ImageBuildOptions) ([]registry.Descriptor, error) {
var (
tagArch string
tag = p.FullTag()
@@ -659,7 +670,9 @@ func (p Pkg) buildArch(ctx context.Context, d dockerRunner, c lktspec.CacheProvi
passCache = nil
}
if err := d.build(ctx, tagArch, p.path, bo.dockerfile, builderName, builderImage, platform, restart, passCache, buildCtx.Reader(), stdout, bo.sbomScan, bo.sbomScannerImage, bo.progress, imageBuildOpts); err != nil {
imageBuildOpts.Dockerfile = bo.dockerfile
if err := d.build(ctx, tagArch, p.path, builderName, builderImage, platform, restart, passCache, buildCtx.Reader(), stdout, bo.sbomScan, bo.sbomScannerImage, bo.progress, imageBuildOpts); err != nil {
stdoutCloser()
if strings.Contains(err.Error(), "executor failed running [/dev/.buildkit_qemu_emulator") {
return nil, fmt.Errorf("buildkit was unable to emulate %s. check binfmt has been set up and works for this platform: %v", platform, err)

View File

@@ -16,10 +16,10 @@ import (
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/reference"
dockertypes "github.com/docker/docker/api/types"
registry "github.com/google/go-containerregistry/pkg/v1"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
buildkitClient "github.com/moby/buildkit/client"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -58,7 +58,7 @@ func (d *dockerMocker) contextSupportCheck() error {
func (d *dockerMocker) builder(_ context.Context, _, _, _ string, _ bool) (*buildkitClient.Client, error) {
return nil, fmt.Errorf("not implemented")
}
func (d *dockerMocker) build(ctx context.Context, tag, pkg, dockerfile, dockerContext, builderImage, platform string, builderRestart bool, c lktspec.CacheProvider, r io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage, progress string, imageBuildOpts dockertypes.ImageBuildOptions) error {
func (d *dockerMocker) build(ctx context.Context, tag, pkg, dockerContext, builderImage, platform string, builderRestart bool, c lktspec.CacheProvider, r io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage, progress string, imageBuildOpts spec.ImageBuildOptions) error {
if !d.enableBuild {
return errors.New("build disabled")
}

View File

@@ -30,7 +30,9 @@ import (
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
buildkitClient "github.com/moby/buildkit/client"
"github.com/moby/buildkit/cmd/buildctl/build"
"github.com/moby/buildkit/frontend/dockerui"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/util/progress/progressui"
// golint requires comments on non-main(test)
@@ -40,6 +42,7 @@ import (
"github.com/moby/buildkit/frontend/dockerfile/instructions"
"github.com/moby/buildkit/frontend/dockerfile/parser"
"github.com/moby/buildkit/frontend/dockerfile/shell"
"github.com/moby/buildkit/session/sshforward/sshprovider"
"github.com/moby/buildkit/session/upload/uploadprovider"
log "github.com/sirupsen/logrus"
)
@@ -54,7 +57,7 @@ const (
type dockerRunner interface {
tag(ref, tag string) error
build(ctx context.Context, tag, pkg, dockerfile, dockerContext, builderImage, platform string, restart bool, c spec.CacheProvider, r io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage, platformType string, imageBuildOpts types.ImageBuildOptions) error
build(ctx context.Context, tag, pkg, dockerContext, builderImage, platform string, restart bool, c spec.CacheProvider, r io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage, platformType string, imageBuildOpts spec.ImageBuildOptions) error
save(tgt string, refs ...string) error
load(src io.Reader) error
pull(img string) (bool, error)
@@ -403,7 +406,7 @@ func (dr *dockerRunnerImpl) tag(ref, tag string) error {
return dr.command(nil, nil, nil, "image", "tag", ref, tag)
}
func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerfile, dockerContext, builderImage, platform string, restart bool, c spec.CacheProvider, stdin io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage, progressType string, imageBuildOpts types.ImageBuildOptions) error {
func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerContext, builderImage, platform string, restart bool, c spec.CacheProvider, stdin io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage, progressType string, imageBuildOpts spec.ImageBuildOptions) error {
// ensure we have a builder
client, err := dr.builder(ctx, dockerContext, builderImage, platform, restart)
if err != nil {
@@ -453,6 +456,32 @@ func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerfile, doc
frontendAttrs[sbomFrontEndKey] = sbomValue
}
attachable := []session.Attachable{}
localDirs := map[string]string{}
if len(imageBuildOpts.SSH) > 0 {
configs, err := build.ParseSSH(imageBuildOpts.SSH)
if err != nil {
return err
}
sp, err := sshprovider.NewSSHAgentProvider(configs)
if err != nil {
return err
}
attachable = append(attachable, sp)
}
if stdin != nil {
buf := bufio.NewReader(stdin)
up := uploadprovider.New()
frontendAttrs["context"] = up.Add(buf)
attachable = append(attachable, up)
} else {
localDirs[dockerui.DefaultLocalNameDockerfile] = pkg
localDirs[dockerui.DefaultLocalNameContext] = pkg
}
solveOpts := buildkitClient.SolveOpt{
Frontend: "dockerfile.v0",
FrontendAttrs: frontendAttrs,
@@ -465,24 +494,15 @@ func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerfile, doc
Output: fixedWriteCloser(&writeNopCloser{stdout}),
},
},
Session: attachable,
LocalDirs: localDirs,
}
if stdin != nil {
buf := bufio.NewReader(stdin)
up := uploadprovider.New()
frontendAttrs["context"] = up.Add(buf)
solveOpts.Session = append(solveOpts.Session, up)
} else {
solveOpts.LocalDirs = map[string]string{
dockerui.DefaultLocalNameDockerfile: pkg,
dockerui.DefaultLocalNameContext: pkg,
}
}
frontendAttrs["filename"] = dockerfile
frontendAttrs["filename"] = imageBuildOpts.Dockerfile
// go through the dockerfile to see if we have any provided images cached
if c != nil {
dockerfileRef := path.Join(pkg, dockerfile)
dockerfileRef := path.Join(pkg, imageBuildOpts.Dockerfile)
f, err := os.Open(dockerfileRef)
if err != nil {
return fmt.Errorf("error opening dockerfile %s: %v", dockerfileRef, err)

View File

@@ -0,0 +1,9 @@
package spec
type ImageBuildOptions struct {
Labels map[string]string
BuildArgs map[string]*string
NetworkMode string
Dockerfile string
SSH []string
}

View File

@@ -0,0 +1,363 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. "Contributor"
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses"
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.

View File

@@ -0,0 +1,30 @@
# cleanhttp
Functions for accessing "clean" Go http.Client values
-------------
The Go standard library contains a default `http.Client` called
`http.DefaultClient`. It is a common idiom in Go code to start with
`http.DefaultClient` and tweak it as necessary, and in fact, this is
encouraged; from the `http` package documentation:
> The Client's Transport typically has internal state (cached TCP connections),
so Clients should be reused instead of created as needed. Clients are safe for
concurrent use by multiple goroutines.
Unfortunately, this is a shared value, and it is not uncommon for libraries to
assume that they are free to modify it at will. With enough dependencies, it
can be very easy to encounter strange problems and race conditions due to
manipulation of this shared value across libraries and goroutines (clients are
safe for concurrent use, but writing values to the client struct itself is not
protected).
Making things worse is the fact that a bare `http.Client` will use a default
`http.Transport` called `http.DefaultTransport`, which is another global value
that behaves the same way. So it is not simply enough to replace
`http.DefaultClient` with `&http.Client{}`.
This repository provides some simple functions to get a "clean" `http.Client`
-- one that uses the same default values as the Go standard library, but
returns a client that does not share any state with other clients.

View File

@@ -0,0 +1,58 @@
package cleanhttp
import (
"net"
"net/http"
"runtime"
"time"
)
// DefaultTransport returns a new http.Transport with similar default values to
// http.DefaultTransport, but with idle connections and keepalives disabled.
func DefaultTransport() *http.Transport {
transport := DefaultPooledTransport()
transport.DisableKeepAlives = true
transport.MaxIdleConnsPerHost = -1
return transport
}
// DefaultPooledTransport returns a new http.Transport with similar default
// values to http.DefaultTransport. Do not use this for transient transports as
// it can leak file descriptors over time. Only use this for transports that
// will be re-used for the same host(s).
func DefaultPooledTransport() *http.Transport {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
ForceAttemptHTTP2: true,
MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
}
return transport
}
// DefaultClient returns a new http.Client with similar default values to
// http.Client, but with a non-shared Transport, idle connections disabled, and
// keepalives disabled.
func DefaultClient() *http.Client {
return &http.Client{
Transport: DefaultTransport(),
}
}
// DefaultPooledClient returns a new http.Client with similar default values to
// http.Client, but with a shared Transport. Do not use this function for
// transient clients as it can leak file descriptors over time. Only use this
// for clients that will be re-used for the same host(s).
func DefaultPooledClient() *http.Client {
return &http.Client{
Transport: DefaultPooledTransport(),
}
}

View File

@@ -0,0 +1,20 @@
// Package cleanhttp offers convenience utilities for acquiring "clean"
// http.Transport and http.Client structs.
//
// Values set on http.DefaultClient and http.DefaultTransport affect all
// callers. This can have detrimental effects, esepcially in TLS contexts,
// where client or root certificates set to talk to multiple endpoints can end
// up displacing each other, leading to hard-to-debug issues. This package
// provides non-shared http.Client and http.Transport structs to ensure that
// the configuration will not be overwritten by other parts of the application
// or dependencies.
//
// The DefaultClient and DefaultTransport functions disable idle connections
// and keepalives. Without ensuring that idle connections are closed before
// garbage collection, short-term clients/transports can leak file descriptors,
// eventually leading to "too many open files" errors. If you will be
// connecting to the same hosts repeatedly from the same client, you can use
// DefaultPooledClient to receive a client that has connection pooling
// semantics similar to http.DefaultClient.
//
package cleanhttp

View File

@@ -0,0 +1,48 @@
package cleanhttp
import (
"net/http"
"strings"
"unicode"
)
// HandlerInput provides input options to cleanhttp's handlers
type HandlerInput struct {
ErrStatus int
}
// PrintablePathCheckHandler is a middleware that ensures the request path
// contains only printable runes.
func PrintablePathCheckHandler(next http.Handler, input *HandlerInput) http.Handler {
// Nil-check on input to make it optional
if input == nil {
input = &HandlerInput{
ErrStatus: http.StatusBadRequest,
}
}
// Default to http.StatusBadRequest on error
if input.ErrStatus == 0 {
input.ErrStatus = http.StatusBadRequest
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r != nil {
// Check URL path for non-printable characters
idx := strings.IndexFunc(r.URL.Path, func(c rune) bool {
return !unicode.IsPrint(c)
})
if idx != -1 {
w.WriteHeader(input.ErrStatus)
return
}
if next != nil {
next.ServeHTTP(w, r)
}
}
return
})
}

View File

@@ -0,0 +1,18 @@
package build
import (
"github.com/moby/buildkit/util/entitlements"
)
// ParseAllow parses --allow
func ParseAllow(inp []string) ([]entitlements.Entitlement, error) {
ent := make([]entitlements.Entitlement, 0, len(inp))
for _, v := range inp {
e, err := entitlements.Parse(v)
if err != nil {
return nil, err
}
ent = append(ent, e)
}
return ent, nil
}

View File

@@ -0,0 +1,19 @@
package build
import (
"strings"
"github.com/pkg/errors"
)
func attrMap(sl []string) (map[string]string, error) {
m := map[string]string{}
for _, v := range sl {
parts := strings.SplitN(v, "=", 2)
if len(parts) != 2 {
return nil, errors.Errorf("invalid value %s", v)
}
m[parts[0]] = parts[1]
}
return m, nil
}

View File

@@ -0,0 +1,71 @@
package build
import (
"encoding/csv"
"strings"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/util/bklog"
"github.com/pkg/errors"
)
func parseExportCacheCSV(s string) (client.CacheOptionsEntry, error) {
ex := client.CacheOptionsEntry{
Type: "",
Attrs: map[string]string{},
}
csvReader := csv.NewReader(strings.NewReader(s))
fields, err := csvReader.Read()
if err != nil {
return ex, err
}
for _, field := range fields {
key, value, ok := strings.Cut(field, "=")
if !ok {
return ex, errors.Errorf("invalid value %s", field)
}
key = strings.ToLower(key)
switch key {
case "type":
ex.Type = value
default:
ex.Attrs[key] = value
}
}
if ex.Type == "" {
return ex, errors.New("--export-cache requires type=<type>")
}
if _, ok := ex.Attrs["mode"]; !ok {
ex.Attrs["mode"] = "min"
}
if ex.Type == "gha" {
return loadGithubEnv(ex)
}
return ex, nil
}
// ParseExportCache parses --export-cache
func ParseExportCache(exportCaches []string) ([]client.CacheOptionsEntry, error) {
var exports []client.CacheOptionsEntry
for _, exportCache := range exportCaches {
legacy := !strings.Contains(exportCache, "type=")
if legacy {
// Deprecated since BuildKit v0.4.0, but no plan to remove: https://github.com/moby/buildkit/pull/2783#issuecomment-1093449772
bklog.L.Warnf("--export-cache <ref> is deprecated. Please use --export-cache type=registry,ref=<ref>,<opt>=<optval>[,<opt>=<optval>] instead")
exports = append(exports, client.CacheOptionsEntry{
Type: "registry",
Attrs: map[string]string{
"mode": "min",
"ref": exportCache,
},
})
} else {
ex, err := parseExportCacheCSV(exportCache)
if err != nil {
return nil, err
}
exports = append(exports, ex)
}
}
return exports, nil
}

View File

@@ -0,0 +1,65 @@
package build
import (
"encoding/csv"
"strings"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/util/bklog"
"github.com/pkg/errors"
)
func parseImportCacheCSV(s string) (client.CacheOptionsEntry, error) {
im := client.CacheOptionsEntry{
Type: "",
Attrs: map[string]string{},
}
csvReader := csv.NewReader(strings.NewReader(s))
fields, err := csvReader.Read()
if err != nil {
return im, err
}
for _, field := range fields {
key, value, ok := strings.Cut(field, "=")
if !ok {
return im, errors.Errorf("invalid value %s", field)
}
key = strings.ToLower(key)
switch key {
case "type":
im.Type = value
default:
im.Attrs[key] = value
}
}
if im.Type == "" {
return im, errors.New("--import-cache requires type=<type>")
}
if im.Type == "gha" {
return loadGithubEnv(im)
}
return im, nil
}
// ParseImportCache parses --import-cache
func ParseImportCache(importCaches []string) ([]client.CacheOptionsEntry, error) {
var imports []client.CacheOptionsEntry
for _, importCache := range importCaches {
legacy := !strings.Contains(importCache, "type=")
if legacy {
// Deprecated since BuildKit v0.4.0, but no plan to remove: https://github.com/moby/buildkit/pull/2783#issuecomment-1093449772
bklog.L.Warn("--import-cache <ref> is deprecated. Please use --import-cache type=registry,ref=<ref>,<opt>=<optval>[,<opt>=<optval>] instead.")
imports = append(imports, client.CacheOptionsEntry{
Type: "registry",
Attrs: map[string]string{"ref": importCache},
})
} else {
im, err := parseImportCacheCSV(importCache)
if err != nil {
return nil, err
}
imports = append(imports, im)
}
}
return imports, nil
}

View File

@@ -0,0 +1,25 @@
package build
import (
"github.com/pkg/errors"
"github.com/tonistiigi/fsutil"
)
// ParseLocal parses --local
func ParseLocal(locals []string) (map[string]fsutil.FS, error) {
localDirs, err := attrMap(locals)
if err != nil {
return nil, errors.WithStack(err)
}
mounts := make(map[string]fsutil.FS, len(localDirs))
for k, v := range localDirs {
mounts[k], err = fsutil.NewFS(v)
if err != nil {
return nil, errors.WithStack(err)
}
}
return mounts, nil
}

View File

@@ -0,0 +1,27 @@
package build
import (
"strings"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/content/local"
"github.com/pkg/errors"
)
// ParseOCILayout parses --oci-layout
func ParseOCILayout(layouts []string) (map[string]content.Store, error) {
contentStores := make(map[string]content.Store)
for _, idAndDir := range layouts {
parts := strings.SplitN(idAndDir, "=", 2)
if len(parts) != 2 {
return nil, errors.Errorf("oci-layout option must be 'id=path/to/layout', instead had invalid %s", idAndDir)
}
cs, err := local.NewStore(parts[1])
if err != nil {
return nil, errors.Wrapf(err, "oci-layout context at %s failed to initialize", parts[1])
}
contentStores[parts[0]] = cs
}
return contentStores, nil
}

View File

@@ -0,0 +1,13 @@
package build
func ParseOpt(opts []string) (map[string]string, error) {
m := loadOptEnv()
m2, err := attrMap(opts)
if err != nil {
return nil, err
}
for k, v := range m2 {
m[k] = v
}
return m, nil
}

View File

@@ -0,0 +1,121 @@
package build
import (
"encoding/csv"
"io"
"os"
"strconv"
"strings"
"github.com/containerd/console"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/session/filesync"
"github.com/pkg/errors"
)
// parseOutputCSV parses a single --output CSV string
func parseOutputCSV(s string) (client.ExportEntry, error) {
ex := client.ExportEntry{
Type: "",
Attrs: map[string]string{},
}
csvReader := csv.NewReader(strings.NewReader(s))
fields, err := csvReader.Read()
if err != nil {
return ex, err
}
for _, field := range fields {
key, value, ok := strings.Cut(field, "=")
if !ok {
return ex, errors.Errorf("invalid value %s", field)
}
key = strings.ToLower(key)
switch key {
case "type":
ex.Type = value
default:
ex.Attrs[key] = value
}
}
if ex.Type == "" {
return ex, errors.New("--output requires type=<type>")
}
if v, ok := ex.Attrs["output"]; ok {
return ex, errors.Errorf("output=%s not supported for --output, you meant dest=%s?", v, v)
}
ex.Output, ex.OutputDir, err = resolveExporterDest(ex.Type, ex.Attrs["dest"], ex.Attrs)
if err != nil {
return ex, errors.Wrap(err, "invalid output option: output")
}
if ex.Output != nil || ex.OutputDir != "" {
delete(ex.Attrs, "dest")
}
return ex, nil
}
// ParseOutput parses --output
func ParseOutput(exports []string) ([]client.ExportEntry, error) {
var entries []client.ExportEntry
for _, s := range exports {
e, err := parseOutputCSV(s)
if err != nil {
return nil, err
}
entries = append(entries, e)
}
return entries, nil
}
// resolveExporterDest returns at most either one of io.WriteCloser (single file) or a string (directory path).
func resolveExporterDest(exporter, dest string, attrs map[string]string) (filesync.FileOutputFunc, string, error) {
wrapWriter := func(wc io.WriteCloser) func(map[string]string) (io.WriteCloser, error) {
return func(m map[string]string) (io.WriteCloser, error) {
return wc, nil
}
}
var supportFile bool
var supportDir bool
switch exporter {
case client.ExporterLocal:
supportDir = true
case client.ExporterTar:
supportFile = true
case client.ExporterOCI, client.ExporterDocker:
tar, err := strconv.ParseBool(attrs["tar"])
if err != nil {
tar = true
}
supportFile = tar
supportDir = !tar
}
if supportDir {
if dest == "" {
return nil, "", errors.Errorf("output directory is required for %s exporter", exporter)
}
return nil, dest, nil
} else if supportFile {
if dest != "" && dest != "-" {
fi, err := os.Stat(dest)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, "", errors.Wrapf(err, "invalid destination file: %s", dest)
}
if err == nil && fi.IsDir() {
return nil, "", errors.Errorf("destination file is a directory")
}
w, err := os.Create(dest)
return wrapWriter(w), "", err
}
// if no output file is specified, use stdout
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
return nil, "", errors.Errorf("output file is required for %s exporter. refusing to write to console", exporter)
}
return wrapWriter(os.Stdout), "", nil
}
// e.g. client.ExporterImage
if dest != "" {
return nil, "", errors.Errorf("output %s is not supported by %s exporter", dest, exporter)
}
return nil, "", nil
}

View File

@@ -0,0 +1,93 @@
package build
import (
"encoding/csv"
"strconv"
"strings"
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/pkg/errors"
)
type authTLSContextEntry struct {
Host string
CA string
Cert string
Key string
Insecure bool
}
func parseRegistryAuthTLSContextCSV(s string) (authTLSContextEntry, error) {
authTLSContext := authTLSContextEntry{}
csvReader := csv.NewReader(strings.NewReader(s))
fields, err := csvReader.Read()
if err != nil {
return authTLSContext, err
}
for _, field := range fields {
key, value, ok := strings.Cut(field, "=")
if !ok {
return authTLSContext, errors.Errorf("invalid value %s", field)
}
key = strings.ToLower(key)
switch key {
case "host":
authTLSContext.Host = value
case "ca":
authTLSContext.CA = value
case "cert":
authTLSContext.Cert = value
case "key":
authTLSContext.Key = value
case "insecure":
authTLSContext.Insecure, _ = strconv.ParseBool(value)
}
}
if authTLSContext.Host == "" {
return authTLSContext, errors.New("--registry-auth-tlscontext requires host=<host>")
}
if authTLSContext.CA == "" {
if !authTLSContext.Insecure {
if authTLSContext.Cert == "" || authTLSContext.Key == "" {
return authTLSContext, errors.New("--registry-auth-tlscontext requires ca=<ca> or cert=<cert>,key=<key> or insecure=true")
}
}
} else {
if (authTLSContext.Cert != "" && authTLSContext.Key == "") || (authTLSContext.Cert == "" && authTLSContext.Key != "") {
return authTLSContext, errors.New("--registry-auth-tlscontext requires cert=<cert>,key=<key>")
}
}
return authTLSContext, nil
}
func ParseRegistryAuthTLSContext(registryAuthTLSContext []string) (map[string]*authprovider.AuthTLSConfig, error) {
var tlsContexts []authTLSContextEntry
for _, c := range registryAuthTLSContext {
authTLSContext, err := parseRegistryAuthTLSContextCSV(c)
if err != nil {
return nil, err
}
tlsContexts = append(tlsContexts, authTLSContext)
}
authConfigs := make(map[string]*authprovider.AuthTLSConfig)
for _, c := range tlsContexts {
_, ok := authConfigs[c.Host]
if !ok {
authConfigs[c.Host] = &authprovider.AuthTLSConfig{}
}
if c.Insecure {
authConfigs[c.Host].Insecure = true
}
if c.CA != "" {
authConfigs[c.Host].RootCAs = append(authConfigs[c.Host].RootCAs, c.CA)
}
if c.Cert != "" && c.Key != "" {
authConfigs[c.Host].KeyPairs = append(authConfigs[c.Host].KeyPairs, authprovider.TLSKeyPair{
Key: c.Key,
Certificate: c.Cert,
})
}
}
return authConfigs, nil
}

View File

@@ -0,0 +1,66 @@
package build
import (
"encoding/csv"
"strings"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/secrets/secretsprovider"
"github.com/pkg/errors"
)
// ParseSecret parses --secret
func ParseSecret(sl []string) (session.Attachable, error) {
fs := make([]secretsprovider.Source, 0, len(sl))
for _, v := range sl {
s, err := parseSecret(v)
if err != nil {
return nil, err
}
fs = append(fs, *s)
}
store, err := secretsprovider.NewStore(fs)
if err != nil {
return nil, err
}
return secretsprovider.NewSecretProvider(store), nil
}
func parseSecret(val string) (*secretsprovider.Source, error) {
csvReader := csv.NewReader(strings.NewReader(val))
fields, err := csvReader.Read()
if err != nil {
return nil, errors.Wrap(err, "failed to parse csv secret")
}
fs := secretsprovider.Source{}
var typ string
for _, field := range fields {
key, value, ok := strings.Cut(field, "=")
if !ok {
return nil, errors.Errorf("invalid field '%s' must be a key=value pair", field)
}
key = strings.ToLower(key)
switch key {
case "type":
if value != "file" && value != "env" {
return nil, errors.Errorf("unsupported secret type %q", value)
}
typ = value
case "id":
fs.ID = value
case "source", "src":
fs.FilePath = value
case "env":
fs.Env = value
default:
return nil, errors.Errorf("unexpected key '%s' in '%s'", key, field)
}
}
if typ == "env" && fs.Env == "" {
fs.Env = fs.FilePath
fs.FilePath = ""
}
return &fs, nil
}

View File

@@ -0,0 +1,23 @@
package build
import (
"strings"
"github.com/moby/buildkit/session/sshforward/sshprovider"
)
// ParseSSH parses --ssh
func ParseSSH(inp []string) ([]sshprovider.AgentConfig, error) {
configs := make([]sshprovider.AgentConfig, 0, len(inp))
for _, v := range inp {
parts := strings.SplitN(v, "=", 2)
cfg := sshprovider.AgentConfig{
ID: parts[0],
}
if len(parts) > 1 {
cfg.Paths = strings.Split(parts[1], ",")
}
configs = append(configs, cfg)
}
return configs, nil
}

View File

@@ -0,0 +1,48 @@
package build
import (
"os"
"github.com/pkg/errors"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/util/bklog"
)
// loadGithubEnv verify that url and token attributes exists in the
// cache.
// If not, it will search for $ACTIONS_RUNTIME_TOKEN and $ACTIONS_CACHE_URL
// environments variables and add it to cache Options
// Since it works for both import and export
func loadGithubEnv(cache client.CacheOptionsEntry) (client.CacheOptionsEntry, error) {
if _, ok := cache.Attrs["url"]; !ok {
url, ok := os.LookupEnv("ACTIONS_CACHE_URL")
if !ok {
return cache, errors.New("cache with type gha requires url parameter or $ACTIONS_CACHE_URL")
}
cache.Attrs["url"] = url
}
if _, ok := cache.Attrs["token"]; !ok {
token, ok := os.LookupEnv("ACTIONS_RUNTIME_TOKEN")
if !ok {
return cache, errors.New("cache with type gha requires token parameter or $ACTIONS_RUNTIME_TOKEN")
}
cache.Attrs["token"] = token
}
return cache, nil
}
// loadOptEnv loads opt values from the environment.
// The returned map is always non-nil.
func loadOptEnv() map[string]string {
m := make(map[string]string)
propagatableEnvs := []string{"SOURCE_DATE_EPOCH"}
for _, env := range propagatableEnvs {
if v, ok := os.LookupEnv(env); ok {
bklog.L.Debugf("Propagating %s from the client env to the build arg", env)
m["build-arg:"+env] = v
}
}
return m
}

View File

@@ -0,0 +1,130 @@
package auth
import (
"context"
"crypto/rand"
"crypto/subtle"
"sync"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/util/grpcerrors"
"github.com/pkg/errors"
"golang.org/x/crypto/nacl/sign"
"google.golang.org/grpc/codes"
)
var salt []byte
var saltOnce sync.Once
// getSalt returns unique component per daemon restart to avoid persistent keys
func getSalt() []byte {
saltOnce.Do(func() {
salt = make([]byte, 32)
rand.Read(salt)
})
return salt
}
func CredentialsFunc(sm *session.Manager, g session.Group) func(string) (session, username, secret string, err error) {
return func(host string) (string, string, string, error) {
var sessionID, user, secret string
err := sm.Any(context.TODO(), g, func(ctx context.Context, id string, c session.Caller) error {
client := NewAuthClient(c.Conn())
resp, err := client.Credentials(ctx, &CredentialsRequest{
Host: host,
})
if err != nil {
if grpcerrors.Code(err) == codes.Unimplemented {
return nil
}
return err
}
sessionID = id
user = resp.Username
secret = resp.Secret
return nil
})
if err != nil {
return "", "", "", err
}
return sessionID, user, secret, nil
}
}
func FetchToken(ctx context.Context, req *FetchTokenRequest, sm *session.Manager, g session.Group) (resp *FetchTokenResponse, err error) {
err = sm.Any(ctx, g, func(ctx context.Context, id string, c session.Caller) error {
client := NewAuthClient(c.Conn())
resp, err = client.FetchToken(ctx, req)
if err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
return resp, nil
}
func VerifyTokenAuthority(ctx context.Context, host string, pubKey *[32]byte, sm *session.Manager, g session.Group) (sessionID string, ok bool, err error) {
var verified bool
err = sm.Any(ctx, g, func(ctx context.Context, id string, c session.Caller) error {
client := NewAuthClient(c.Conn())
payload := make([]byte, 32)
rand.Read(payload)
resp, err := client.VerifyTokenAuthority(ctx, &VerifyTokenAuthorityRequest{
Host: host,
Salt: getSalt(),
Payload: payload,
})
if err != nil {
if grpcerrors.Code(err) == codes.Unimplemented {
return nil
}
return err
}
var dt []byte
dt, ok = sign.Open(nil, resp.Signed, pubKey)
if ok && subtle.ConstantTimeCompare(dt, payload) == 1 {
verified = true
}
sessionID = id
return nil
})
if err != nil {
return "", false, err
}
return sessionID, verified, nil
}
func GetTokenAuthority(ctx context.Context, host string, sm *session.Manager, g session.Group) (sessionID string, pubKey *[32]byte, err error) {
err = sm.Any(ctx, g, func(ctx context.Context, id string, c session.Caller) error {
client := NewAuthClient(c.Conn())
resp, err := client.GetTokenAuthority(ctx, &GetTokenAuthorityRequest{
Host: host,
Salt: getSalt(),
})
if err != nil {
if grpcerrors.Code(err) == codes.Unimplemented || grpcerrors.Code(err) == codes.Unavailable {
return nil
}
return err
}
if len(resp.PublicKey) != 32 {
return errors.Errorf("invalid pubkey length %d", len(pubKey))
}
sessionID = id
pubKey = new([32]byte)
copy((*pubKey)[:], resp.PublicKey)
return nil
})
if err != nil {
return "", nil, err
}
return sessionID, pubKey, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
syntax = "proto3";
package moby.filesync.v1;
option go_package = "auth";
service Auth{
rpc Credentials(CredentialsRequest) returns (CredentialsResponse);
rpc FetchToken(FetchTokenRequest) returns (FetchTokenResponse);
rpc GetTokenAuthority(GetTokenAuthorityRequest) returns (GetTokenAuthorityResponse);
rpc VerifyTokenAuthority(VerifyTokenAuthorityRequest) returns (VerifyTokenAuthorityResponse);
}
message CredentialsRequest {
string Host = 1;
}
message CredentialsResponse {
string Username = 1;
string Secret = 2;
}
message FetchTokenRequest {
string ClientID = 1;
string Host = 2;
string Realm = 3;
string Service = 4;
repeated string Scopes = 5;
}
message FetchTokenResponse {
string Token = 1;
int64 ExpiresIn = 2; // seconds
int64 IssuedAt = 3; // timestamp
}
message GetTokenAuthorityRequest {
string Host = 1;
bytes Salt = 2;
}
message GetTokenAuthorityResponse {
bytes PublicKey = 1;
}
message VerifyTokenAuthorityRequest {
string Host = 1;
bytes Payload = 2;
bytes Salt = 3;
}
message VerifyTokenAuthorityResponse {
bytes Signed = 1;
}

View File

@@ -0,0 +1,12 @@
package authprovider
type AuthTLSConfig struct {
RootCAs []string
Insecure bool
KeyPairs []TLSKeyPair
}
type TLSKeyPair struct {
Key string
Certificate string
}

View File

@@ -0,0 +1,302 @@
package authprovider
import (
"context"
"crypto/ed25519"
"crypto/hmac"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"fmt"
"os"
"runtime"
"strconv"
"strings"
"sync"
"time"
authutil "github.com/containerd/containerd/remotes/docker/auth"
remoteserrors "github.com/containerd/containerd/remotes/errors"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/types"
http "github.com/hashicorp/go-cleanhttp"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/auth"
"github.com/moby/buildkit/util/progress/progresswriter"
"github.com/pkg/errors"
"golang.org/x/crypto/nacl/sign"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const defaultExpiration = 60
const dockerHubConfigfileKey = "https://index.docker.io/v1/"
const dockerHubRegistryHost = "registry-1.docker.io"
func NewDockerAuthProvider(cfg *configfile.ConfigFile, tlsConfigs map[string]*AuthTLSConfig) session.Attachable {
return &authProvider{
authConfigCache: map[string]*types.AuthConfig{},
config: cfg,
seeds: &tokenSeeds{dir: config.Dir()},
loggerCache: map[string]struct{}{},
tlsConfigs: tlsConfigs,
}
}
type authProvider struct {
authConfigCache map[string]*types.AuthConfig
config *configfile.ConfigFile
seeds *tokenSeeds
logger progresswriter.Logger
loggerCache map[string]struct{}
tlsConfigs map[string]*AuthTLSConfig
// The need for this mutex is not well understood.
// Without it, the docker cli on OS X hangs when
// reading credentials from docker-credential-osxkeychain.
// See issue https://github.com/docker/cli/issues/1862
mu sync.Mutex
}
func (ap *authProvider) SetLogger(l progresswriter.Logger) {
ap.mu.Lock()
ap.logger = l
ap.mu.Unlock()
}
func (ap *authProvider) Register(server *grpc.Server) {
auth.RegisterAuthServer(server, ap)
}
func (ap *authProvider) FetchToken(ctx context.Context, req *auth.FetchTokenRequest) (rr *auth.FetchTokenResponse, err error) {
ac, err := ap.getAuthConfig(req.Host)
if err != nil {
return nil, err
}
// check for statically configured bearer token
if ac.RegistryToken != "" {
return toTokenResponse(ac.RegistryToken, time.Time{}, 0), nil
}
creds, err := ap.credentials(req.Host)
if err != nil {
return nil, err
}
to := authutil.TokenOptions{
Realm: req.Realm,
Service: req.Service,
Scopes: req.Scopes,
Username: creds.Username,
Secret: creds.Secret,
}
var httpClient = http.DefaultClient()
if tc, err := ap.tlsConfig(req.Host); err == nil && tc != nil {
transport := http.DefaultTransport()
transport.TLSClientConfig = tc
httpClient.Transport = transport
}
if creds.Secret != "" {
done := func(progresswriter.SubLogger) error {
return err
}
defer func() {
err = errors.Wrap(err, "failed to fetch oauth token")
}()
ap.mu.Lock()
name := fmt.Sprintf("[auth] %v token for %s", strings.Join(trimScopePrefix(req.Scopes), " "), req.Host)
if _, ok := ap.loggerCache[name]; !ok {
progresswriter.Wrap(name, ap.logger, done)
}
ap.mu.Unlock()
// credential information is provided, use oauth POST endpoint
resp, err := authutil.FetchTokenWithOAuth(ctx, httpClient, nil, "buildkit-client", to)
if err != nil {
var errStatus remoteserrors.ErrUnexpectedStatus
if errors.As(err, &errStatus) {
// Registries without support for POST may return 404 for POST /v2/token.
// As of September 2017, GCR is known to return 404.
// As of February 2018, JFrog Artifactory is known to return 401.
if (errStatus.StatusCode == 405 && to.Username != "") || errStatus.StatusCode == 404 || errStatus.StatusCode == 401 {
resp, err := authutil.FetchToken(ctx, httpClient, nil, to)
if err != nil {
return nil, err
}
return toTokenResponse(resp.Token, resp.IssuedAt, resp.ExpiresIn), nil
}
}
return nil, err
}
return toTokenResponse(resp.AccessToken, resp.IssuedAt, resp.ExpiresIn), nil
}
// do request anonymously
resp, err := authutil.FetchToken(ctx, httpClient, nil, to)
if err != nil {
return nil, errors.Wrap(err, "failed to fetch anonymous token")
}
return toTokenResponse(resp.Token, resp.IssuedAt, resp.ExpiresIn), nil
}
func (ap *authProvider) tlsConfig(host string) (*tls.Config, error) {
if ap.tlsConfigs == nil {
return nil, nil
}
c, ok := ap.tlsConfigs[host]
if !ok {
return nil, nil
}
tc := &tls.Config{}
if len(c.RootCAs) > 0 {
systemPool, err := x509.SystemCertPool()
if err != nil {
if runtime.GOOS == "windows" {
systemPool = x509.NewCertPool()
} else {
return nil, errors.Wrapf(err, "unable to get system cert pool")
}
}
tc.RootCAs = systemPool
}
for _, p := range c.RootCAs {
dt, err := os.ReadFile(p)
if err != nil {
return nil, errors.Wrapf(err, "failed to read %s", p)
}
tc.RootCAs.AppendCertsFromPEM(dt)
}
for _, kp := range c.KeyPairs {
cert, err := tls.LoadX509KeyPair(kp.Certificate, kp.Key)
if err != nil {
return nil, errors.Wrapf(err, "failed to load keypair for %s", kp.Certificate)
}
tc.Certificates = append(tc.Certificates, cert)
}
if c.Insecure {
tc.InsecureSkipVerify = true
}
return tc, nil
}
func (ap *authProvider) credentials(host string) (*auth.CredentialsResponse, error) {
ac, err := ap.getAuthConfig(host)
if err != nil {
return nil, err
}
res := &auth.CredentialsResponse{}
if ac.IdentityToken != "" {
res.Secret = ac.IdentityToken
} else {
res.Username = ac.Username
res.Secret = ac.Password
}
return res, nil
}
func (ap *authProvider) Credentials(ctx context.Context, req *auth.CredentialsRequest) (*auth.CredentialsResponse, error) {
resp, err := ap.credentials(req.Host)
if err != nil || resp.Secret != "" {
ap.mu.Lock()
defer ap.mu.Unlock()
_, ok := ap.loggerCache[req.Host]
ap.loggerCache[req.Host] = struct{}{}
if !ok && ap.logger != nil {
return resp, progresswriter.Wrap(fmt.Sprintf("[auth] sharing credentials for %s", req.Host), ap.logger, func(progresswriter.SubLogger) error {
return err
})
}
}
return resp, err
}
func (ap *authProvider) GetTokenAuthority(ctx context.Context, req *auth.GetTokenAuthorityRequest) (*auth.GetTokenAuthorityResponse, error) {
key, err := ap.getAuthorityKey(req.Host, req.Salt)
if err != nil {
return nil, err
}
return &auth.GetTokenAuthorityResponse{PublicKey: key[32:]}, nil
}
func (ap *authProvider) VerifyTokenAuthority(ctx context.Context, req *auth.VerifyTokenAuthorityRequest) (*auth.VerifyTokenAuthorityResponse, error) {
key, err := ap.getAuthorityKey(req.Host, req.Salt)
if err != nil {
return nil, err
}
priv := new([64]byte)
copy((*priv)[:], key)
return &auth.VerifyTokenAuthorityResponse{Signed: sign.Sign(nil, req.Payload, priv)}, nil
}
func (ap *authProvider) getAuthConfig(host string) (*types.AuthConfig, error) {
ap.mu.Lock()
defer ap.mu.Unlock()
if host == dockerHubRegistryHost {
host = dockerHubConfigfileKey
}
if _, exists := ap.authConfigCache[host]; !exists {
ac, err := ap.config.GetAuthConfig(host)
if err != nil {
return nil, err
}
ap.authConfigCache[host] = &ac
}
return ap.authConfigCache[host], nil
}
func (ap *authProvider) getAuthorityKey(host string, salt []byte) (ed25519.PrivateKey, error) {
if v, err := strconv.ParseBool(os.Getenv("BUILDKIT_NO_CLIENT_TOKEN")); err == nil && v {
return nil, status.Errorf(codes.Unavailable, "client side tokens disabled")
}
creds, err := ap.credentials(host)
if err != nil {
return nil, err
}
seed, err := ap.seeds.getSeed(host)
if err != nil {
return nil, err
}
mac := hmac.New(sha256.New, salt)
if creds.Secret != "" {
mac.Write(seed)
}
sum := mac.Sum(nil)
return ed25519.NewKeyFromSeed(sum[:ed25519.SeedSize]), nil
}
func toTokenResponse(token string, issuedAt time.Time, expires int) *auth.FetchTokenResponse {
if expires == 0 {
expires = defaultExpiration
}
resp := &auth.FetchTokenResponse{
Token: token,
ExpiresIn: int64(expires),
}
if !issuedAt.IsZero() {
resp.IssuedAt = issuedAt.Unix()
}
return resp
}
func trimScopePrefix(scopes []string) []string {
out := make([]string, len(scopes))
for i, s := range scopes {
out[i] = strings.TrimPrefix(s, "repository:")
}
return out
}

View File

@@ -0,0 +1,82 @@
package authprovider
import (
"crypto/rand"
"encoding/json"
"os"
"path/filepath"
"sync"
"syscall"
"github.com/gofrs/flock"
"github.com/pkg/errors"
)
type tokenSeeds struct {
mu sync.Mutex
dir string
m map[string]seed
}
type seed struct {
Seed []byte
}
func (ts *tokenSeeds) getSeed(host string) ([]byte, error) {
ts.mu.Lock()
defer ts.mu.Unlock()
if err := os.MkdirAll(ts.dir, 0755); err != nil {
return nil, err
}
if ts.m == nil {
ts.m = map[string]seed{}
}
l := flock.New(filepath.Join(ts.dir, ".token_seed.lock"))
if err := l.Lock(); err != nil {
if !errors.Is(err, syscall.EROFS) && !errors.Is(err, os.ErrPermission) {
return nil, err
}
} else {
defer l.Unlock()
}
fp := filepath.Join(ts.dir, ".token_seed")
// we include client side randomness to avoid chosen plaintext attack from the daemon side
dt, err := os.ReadFile(fp)
if err != nil {
if !errors.Is(err, os.ErrNotExist) && !errors.Is(err, syscall.ENOTDIR) && !errors.Is(err, os.ErrPermission) {
return nil, err
}
} else {
// ignore error in case of crash during previous marshal
_ = json.Unmarshal(dt, &ts.m)
}
v, ok := ts.m[host]
if !ok {
v = seed{Seed: newSeed()}
}
ts.m[host] = v
dt, err = json.MarshalIndent(ts.m, "", " ")
if err != nil {
return nil, err
}
if err := os.WriteFile(fp, dt, 0600); err != nil {
if !errors.Is(err, syscall.EROFS) && !errors.Is(err, os.ErrPermission) {
return nil, err
}
}
return v.Seed, nil
}
func newSeed() []byte {
b := make([]byte, 16)
rand.Read(b)
return b
}

View File

@@ -0,0 +1,3 @@
package auth
//go:generate protoc --gogoslick_out=plugins=grpc:. auth.proto

View File

@@ -0,0 +1,3 @@
package secrets
//go:generate protoc --gogoslick_out=plugins=grpc:. secrets.proto

View File

@@ -0,0 +1,30 @@
package secrets
import (
"context"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/util/grpcerrors"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
)
type SecretStore interface {
GetSecret(context.Context, string) ([]byte, error)
}
var ErrNotFound = errors.Errorf("not found")
func GetSecret(ctx context.Context, c session.Caller, id string) ([]byte, error) {
client := NewSecretsClient(c.Conn())
resp, err := client.GetSecret(ctx, &GetSecretRequest{
ID: id,
})
if err != nil {
if code := grpcerrors.Code(err); code == codes.Unimplemented || code == codes.NotFound {
return nil, errors.Wrapf(ErrNotFound, "secret %s", id)
}
return nil, err
}
return resp.Data, nil
}

View File

@@ -0,0 +1,880 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: secrets.proto
package secrets
import (
bytes "bytes"
context "context"
fmt "fmt"
proto "github.com/gogo/protobuf/proto"
github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
io "io"
math "math"
math_bits "math/bits"
reflect "reflect"
strings "strings"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
type GetSecretRequest struct {
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
Annotations map[string]string `protobuf:"bytes,2,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (m *GetSecretRequest) Reset() { *m = GetSecretRequest{} }
func (*GetSecretRequest) ProtoMessage() {}
func (*GetSecretRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_d4bc6c625e214507, []int{0}
}
func (m *GetSecretRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *GetSecretRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_GetSecretRequest.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *GetSecretRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_GetSecretRequest.Merge(m, src)
}
func (m *GetSecretRequest) XXX_Size() int {
return m.Size()
}
func (m *GetSecretRequest) XXX_DiscardUnknown() {
xxx_messageInfo_GetSecretRequest.DiscardUnknown(m)
}
var xxx_messageInfo_GetSecretRequest proto.InternalMessageInfo
func (m *GetSecretRequest) GetID() string {
if m != nil {
return m.ID
}
return ""
}
func (m *GetSecretRequest) GetAnnotations() map[string]string {
if m != nil {
return m.Annotations
}
return nil
}
type GetSecretResponse struct {
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
}
func (m *GetSecretResponse) Reset() { *m = GetSecretResponse{} }
func (*GetSecretResponse) ProtoMessage() {}
func (*GetSecretResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_d4bc6c625e214507, []int{1}
}
func (m *GetSecretResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *GetSecretResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_GetSecretResponse.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *GetSecretResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_GetSecretResponse.Merge(m, src)
}
func (m *GetSecretResponse) XXX_Size() int {
return m.Size()
}
func (m *GetSecretResponse) XXX_DiscardUnknown() {
xxx_messageInfo_GetSecretResponse.DiscardUnknown(m)
}
var xxx_messageInfo_GetSecretResponse proto.InternalMessageInfo
func (m *GetSecretResponse) GetData() []byte {
if m != nil {
return m.Data
}
return nil
}
func init() {
proto.RegisterType((*GetSecretRequest)(nil), "moby.buildkit.secrets.v1.GetSecretRequest")
proto.RegisterMapType((map[string]string)(nil), "moby.buildkit.secrets.v1.GetSecretRequest.AnnotationsEntry")
proto.RegisterType((*GetSecretResponse)(nil), "moby.buildkit.secrets.v1.GetSecretResponse")
}
func init() { proto.RegisterFile("secrets.proto", fileDescriptor_d4bc6c625e214507) }
var fileDescriptor_d4bc6c625e214507 = []byte{
// 288 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2d, 0x4e, 0x4d, 0x2e,
0x4a, 0x2d, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0xc8, 0xcd, 0x4f, 0xaa, 0xd4,
0x4b, 0x2a, 0xcd, 0xcc, 0x49, 0xc9, 0xce, 0x2c, 0xd1, 0x83, 0x49, 0x96, 0x19, 0x2a, 0x1d, 0x64,
0xe4, 0x12, 0x70, 0x4f, 0x2d, 0x09, 0x06, 0x8b, 0x04, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08,
0xf1, 0x71, 0x31, 0x79, 0xba, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0x31, 0x79, 0xba, 0x08,
0xc5, 0x72, 0x71, 0x27, 0xe6, 0xe5, 0xe5, 0x97, 0x24, 0x96, 0x64, 0xe6, 0xe7, 0x15, 0x4b, 0x30,
0x29, 0x30, 0x6b, 0x70, 0x1b, 0x59, 0xeb, 0xe1, 0x32, 0x54, 0x0f, 0xdd, 0x40, 0x3d, 0x47, 0x84,
0x6e, 0xd7, 0xbc, 0x92, 0xa2, 0xca, 0x20, 0x64, 0xf3, 0xa4, 0xec, 0xb8, 0x04, 0xd0, 0x15, 0x08,
0x09, 0x70, 0x31, 0x67, 0xa7, 0x56, 0x42, 0xdd, 0x00, 0x62, 0x0a, 0x89, 0x70, 0xb1, 0x96, 0x25,
0xe6, 0x94, 0xa6, 0x4a, 0x30, 0x81, 0xc5, 0x20, 0x1c, 0x2b, 0x26, 0x0b, 0x46, 0x25, 0x75, 0x2e,
0x41, 0x24, 0x1b, 0x8b, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x85, 0x84, 0xb8, 0x58, 0x52, 0x12, 0x4b,
0x12, 0xc1, 0x26, 0xf0, 0x04, 0x81, 0xd9, 0x46, 0xf9, 0x5c, 0xec, 0x10, 0x55, 0xc5, 0x42, 0x29,
0x5c, 0x9c, 0x70, 0x3d, 0x42, 0x5a, 0xc4, 0x7b, 0x45, 0x4a, 0x9b, 0x28, 0xb5, 0x10, 0x47, 0x38,
0xd9, 0x5e, 0x78, 0x28, 0xc7, 0x70, 0xe3, 0xa1, 0x1c, 0xc3, 0x87, 0x87, 0x72, 0x8c, 0x0d, 0x8f,
0xe4, 0x18, 0x57, 0x3c, 0x92, 0x63, 0x3c, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07,
0x8f, 0xe4, 0x18, 0x5f, 0x3c, 0x92, 0x63, 0xf8, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86,
0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0x62, 0x87, 0x9a, 0x99, 0xc4, 0x06, 0x8e,
0x3d, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2c, 0x38, 0xec, 0x1f, 0xce, 0x01, 0x00, 0x00,
}
func (this *GetSecretRequest) Equal(that interface{}) bool {
if that == nil {
return this == nil
}
that1, ok := that.(*GetSecretRequest)
if !ok {
that2, ok := that.(GetSecretRequest)
if ok {
that1 = &that2
} else {
return false
}
}
if that1 == nil {
return this == nil
} else if this == nil {
return false
}
if this.ID != that1.ID {
return false
}
if len(this.Annotations) != len(that1.Annotations) {
return false
}
for i := range this.Annotations {
if this.Annotations[i] != that1.Annotations[i] {
return false
}
}
return true
}
func (this *GetSecretResponse) Equal(that interface{}) bool {
if that == nil {
return this == nil
}
that1, ok := that.(*GetSecretResponse)
if !ok {
that2, ok := that.(GetSecretResponse)
if ok {
that1 = &that2
} else {
return false
}
}
if that1 == nil {
return this == nil
} else if this == nil {
return false
}
if !bytes.Equal(this.Data, that1.Data) {
return false
}
return true
}
func (this *GetSecretRequest) GoString() string {
if this == nil {
return "nil"
}
s := make([]string, 0, 6)
s = append(s, "&secrets.GetSecretRequest{")
s = append(s, "ID: "+fmt.Sprintf("%#v", this.ID)+",\n")
keysForAnnotations := make([]string, 0, len(this.Annotations))
for k, _ := range this.Annotations {
keysForAnnotations = append(keysForAnnotations, k)
}
github_com_gogo_protobuf_sortkeys.Strings(keysForAnnotations)
mapStringForAnnotations := "map[string]string{"
for _, k := range keysForAnnotations {
mapStringForAnnotations += fmt.Sprintf("%#v: %#v,", k, this.Annotations[k])
}
mapStringForAnnotations += "}"
if this.Annotations != nil {
s = append(s, "Annotations: "+mapStringForAnnotations+",\n")
}
s = append(s, "}")
return strings.Join(s, "")
}
func (this *GetSecretResponse) GoString() string {
if this == nil {
return "nil"
}
s := make([]string, 0, 5)
s = append(s, "&secrets.GetSecretResponse{")
s = append(s, "Data: "+fmt.Sprintf("%#v", this.Data)+",\n")
s = append(s, "}")
return strings.Join(s, "")
}
func valueToGoStringSecrets(v interface{}, typ string) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("func(v %v) *%v { return &v } ( %#v )", typ, typ, pv)
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// SecretsClient is the client API for Secrets service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type SecretsClient interface {
GetSecret(ctx context.Context, in *GetSecretRequest, opts ...grpc.CallOption) (*GetSecretResponse, error)
}
type secretsClient struct {
cc *grpc.ClientConn
}
func NewSecretsClient(cc *grpc.ClientConn) SecretsClient {
return &secretsClient{cc}
}
func (c *secretsClient) GetSecret(ctx context.Context, in *GetSecretRequest, opts ...grpc.CallOption) (*GetSecretResponse, error) {
out := new(GetSecretResponse)
err := c.cc.Invoke(ctx, "/moby.buildkit.secrets.v1.Secrets/GetSecret", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// SecretsServer is the server API for Secrets service.
type SecretsServer interface {
GetSecret(context.Context, *GetSecretRequest) (*GetSecretResponse, error)
}
// UnimplementedSecretsServer can be embedded to have forward compatible implementations.
type UnimplementedSecretsServer struct {
}
func (*UnimplementedSecretsServer) GetSecret(ctx context.Context, req *GetSecretRequest) (*GetSecretResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetSecret not implemented")
}
func RegisterSecretsServer(s *grpc.Server, srv SecretsServer) {
s.RegisterService(&_Secrets_serviceDesc, srv)
}
func _Secrets_GetSecret_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetSecretRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SecretsServer).GetSecret(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/moby.buildkit.secrets.v1.Secrets/GetSecret",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SecretsServer).GetSecret(ctx, req.(*GetSecretRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Secrets_serviceDesc = grpc.ServiceDesc{
ServiceName: "moby.buildkit.secrets.v1.Secrets",
HandlerType: (*SecretsServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetSecret",
Handler: _Secrets_GetSecret_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "secrets.proto",
}
func (m *GetSecretRequest) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *GetSecretRequest) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *GetSecretRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Annotations) > 0 {
for k := range m.Annotations {
v := m.Annotations[k]
baseI := i
i -= len(v)
copy(dAtA[i:], v)
i = encodeVarintSecrets(dAtA, i, uint64(len(v)))
i--
dAtA[i] = 0x12
i -= len(k)
copy(dAtA[i:], k)
i = encodeVarintSecrets(dAtA, i, uint64(len(k)))
i--
dAtA[i] = 0xa
i = encodeVarintSecrets(dAtA, i, uint64(baseI-i))
i--
dAtA[i] = 0x12
}
}
if len(m.ID) > 0 {
i -= len(m.ID)
copy(dAtA[i:], m.ID)
i = encodeVarintSecrets(dAtA, i, uint64(len(m.ID)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *GetSecretResponse) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *GetSecretResponse) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *GetSecretResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Data) > 0 {
i -= len(m.Data)
copy(dAtA[i:], m.Data)
i = encodeVarintSecrets(dAtA, i, uint64(len(m.Data)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func encodeVarintSecrets(dAtA []byte, offset int, v uint64) int {
offset -= sovSecrets(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *GetSecretRequest) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.ID)
if l > 0 {
n += 1 + l + sovSecrets(uint64(l))
}
if len(m.Annotations) > 0 {
for k, v := range m.Annotations {
_ = k
_ = v
mapEntrySize := 1 + len(k) + sovSecrets(uint64(len(k))) + 1 + len(v) + sovSecrets(uint64(len(v)))
n += mapEntrySize + 1 + sovSecrets(uint64(mapEntrySize))
}
}
return n
}
func (m *GetSecretResponse) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Data)
if l > 0 {
n += 1 + l + sovSecrets(uint64(l))
}
return n
}
func sovSecrets(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
func sozSecrets(x uint64) (n int) {
return sovSecrets(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (this *GetSecretRequest) String() string {
if this == nil {
return "nil"
}
keysForAnnotations := make([]string, 0, len(this.Annotations))
for k, _ := range this.Annotations {
keysForAnnotations = append(keysForAnnotations, k)
}
github_com_gogo_protobuf_sortkeys.Strings(keysForAnnotations)
mapStringForAnnotations := "map[string]string{"
for _, k := range keysForAnnotations {
mapStringForAnnotations += fmt.Sprintf("%v: %v,", k, this.Annotations[k])
}
mapStringForAnnotations += "}"
s := strings.Join([]string{`&GetSecretRequest{`,
`ID:` + fmt.Sprintf("%v", this.ID) + `,`,
`Annotations:` + mapStringForAnnotations + `,`,
`}`,
}, "")
return s
}
func (this *GetSecretResponse) String() string {
if this == nil {
return "nil"
}
s := strings.Join([]string{`&GetSecretResponse{`,
`Data:` + fmt.Sprintf("%v", this.Data) + `,`,
`}`,
}, "")
return s
}
func valueToStringSecrets(v interface{}) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("*%v", pv)
}
func (m *GetSecretRequest) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSecrets
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: GetSecretRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: GetSecretRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSecrets
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthSecrets
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthSecrets
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.ID = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Annotations", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSecrets
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthSecrets
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthSecrets
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.Annotations == nil {
m.Annotations = make(map[string]string)
}
var mapkey string
var mapvalue string
for iNdEx < postIndex {
entryPreIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSecrets
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
if fieldNum == 1 {
var stringLenmapkey uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSecrets
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLenmapkey |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLenmapkey := int(stringLenmapkey)
if intStringLenmapkey < 0 {
return ErrInvalidLengthSecrets
}
postStringIndexmapkey := iNdEx + intStringLenmapkey
if postStringIndexmapkey < 0 {
return ErrInvalidLengthSecrets
}
if postStringIndexmapkey > l {
return io.ErrUnexpectedEOF
}
mapkey = string(dAtA[iNdEx:postStringIndexmapkey])
iNdEx = postStringIndexmapkey
} else if fieldNum == 2 {
var stringLenmapvalue uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSecrets
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLenmapvalue |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLenmapvalue := int(stringLenmapvalue)
if intStringLenmapvalue < 0 {
return ErrInvalidLengthSecrets
}
postStringIndexmapvalue := iNdEx + intStringLenmapvalue
if postStringIndexmapvalue < 0 {
return ErrInvalidLengthSecrets
}
if postStringIndexmapvalue > l {
return io.ErrUnexpectedEOF
}
mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue])
iNdEx = postStringIndexmapvalue
} else {
iNdEx = entryPreIndex
skippy, err := skipSecrets(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthSecrets
}
if (iNdEx + skippy) > postIndex {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
m.Annotations[mapkey] = mapvalue
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipSecrets(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthSecrets
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *GetSecretResponse) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSecrets
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: GetSecretResponse: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: GetSecretResponse: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSecrets
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthSecrets
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthSecrets
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...)
if m.Data == nil {
m.Data = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipSecrets(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthSecrets
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipSecrets(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowSecrets
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowSecrets
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowSecrets
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLengthSecrets
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupSecrets
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthSecrets
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthSecrets = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowSecrets = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupSecrets = fmt.Errorf("proto: unexpected end of group")
)

View File

@@ -0,0 +1,19 @@
syntax = "proto3";
package moby.buildkit.secrets.v1;
option go_package = "secrets";
service Secrets{
rpc GetSecret(GetSecretRequest) returns (GetSecretResponse);
}
message GetSecretRequest {
string ID = 1;
map<string, string> annotations = 2;
}
message GetSecretResponse {
bytes data = 1;
}

View File

@@ -0,0 +1,60 @@
package secretsprovider
import (
"context"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/secrets"
"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// MaxSecretSize is the maximum byte length allowed for a secret
const MaxSecretSize = 500 * 1024 // 500KB
func NewSecretProvider(store secrets.SecretStore) session.Attachable {
return &secretProvider{
store: store,
}
}
type secretProvider struct {
store secrets.SecretStore
}
func (sp *secretProvider) Register(server *grpc.Server) {
secrets.RegisterSecretsServer(server, sp)
}
func (sp *secretProvider) GetSecret(ctx context.Context, req *secrets.GetSecretRequest) (*secrets.GetSecretResponse, error) {
dt, err := sp.store.GetSecret(ctx, req.ID)
if err != nil {
if errors.Is(err, secrets.ErrNotFound) {
return nil, status.Errorf(codes.NotFound, err.Error())
}
return nil, err
}
if l := len(dt); l > MaxSecretSize {
return nil, errors.Errorf("invalid secret size %d", l)
}
return &secrets.GetSecretResponse{
Data: dt,
}, nil
}
func FromMap(m map[string][]byte) session.Attachable {
return NewSecretProvider(mapStore(m))
}
type mapStore map[string][]byte
func (m mapStore) GetSecret(ctx context.Context, id string) ([]byte, error) {
v, ok := m[id]
if !ok {
return nil, errors.WithStack(secrets.ErrNotFound)
}
return v, nil
}

View File

@@ -0,0 +1,64 @@
package secretsprovider
import (
"context"
"os"
"github.com/moby/buildkit/session/secrets"
"github.com/pkg/errors"
"github.com/tonistiigi/units"
)
type Source struct {
ID string
FilePath string
Env string
}
func NewStore(files []Source) (secrets.SecretStore, error) {
m := map[string]Source{}
for _, f := range files {
if f.ID == "" {
return nil, errors.Errorf("secret missing ID")
}
if f.Env == "" && f.FilePath == "" {
if _, ok := os.LookupEnv(f.ID); ok {
f.Env = f.ID
} else {
f.FilePath = f.ID
}
}
if f.FilePath != "" {
fi, err := os.Stat(f.FilePath)
if err != nil {
return nil, errors.Wrapf(err, "failed to stat %s", f.FilePath)
}
if fi.Size() > MaxSecretSize {
return nil, errors.Errorf("secret %s too big. max size %#.f", f.ID, MaxSecretSize*units.B)
}
}
m[f.ID] = f
}
return &fileStore{
m: m,
}, nil
}
type fileStore struct {
m map[string]Source
}
func (fs *fileStore) GetSecret(ctx context.Context, id string) ([]byte, error) {
v, ok := fs.m[id]
if !ok {
return nil, errors.WithStack(secrets.ErrNotFound)
}
if v.Env != "" {
return []byte(os.Getenv(v.Env)), nil
}
dt, err := os.ReadFile(v.FilePath)
if err != nil {
return nil, err
}
return dt, nil
}

View File

@@ -0,0 +1,79 @@
package sshforward
import (
"context"
"io"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
)
type Stream interface {
SendMsg(m interface{}) error
RecvMsg(m interface{}) error
}
func Copy(ctx context.Context, conn io.ReadWriteCloser, stream Stream, closeStream func() error) error {
defer conn.Close()
g, ctx := errgroup.WithContext(ctx)
g.Go(func() (retErr error) {
p := &BytesMessage{}
for {
if err := stream.RecvMsg(p); err != nil {
if err == io.EOF {
// indicates client performed CloseSend, but they may still be
// reading data
if closeWriter, ok := conn.(interface {
CloseWrite() error
}); ok {
closeWriter.CloseWrite()
} else {
conn.Close()
}
return nil
}
conn.Close()
return errors.WithStack(err)
}
select {
case <-ctx.Done():
conn.Close()
return context.Cause(ctx)
default:
}
if _, err := conn.Write(p.Data); err != nil {
conn.Close()
return errors.WithStack(err)
}
p.Data = p.Data[:0]
}
})
g.Go(func() (retErr error) {
for {
buf := make([]byte, 32*1024)
n, err := conn.Read(buf)
switch {
case err == io.EOF:
if closeStream != nil {
closeStream()
}
return nil
case err != nil:
return errors.WithStack(err)
}
select {
case <-ctx.Done():
return context.Cause(ctx)
default:
}
p := &BytesMessage{Data: buf[:n]}
if err := stream.SendMsg(p); err != nil {
return errors.WithStack(err)
}
}
})
return g.Wait()
}

View File

@@ -0,0 +1,3 @@
package sshforward
//go:generate protoc --gogoslick_out=plugins=grpc:. ssh.proto

View File

@@ -0,0 +1,117 @@
package sshforward
import (
"context"
"net"
"os"
"path/filepath"
"github.com/moby/buildkit/session"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc/metadata"
)
// DefaultID is the default ssh ID
const DefaultID = "default"
const KeySSHID = "buildkit.ssh.id"
type server struct {
caller session.Caller
}
func (s *server) run(ctx context.Context, l net.Listener, id string) error {
eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error {
<-ctx.Done()
return context.Cause(ctx)
})
eg.Go(func() error {
for {
conn, err := l.Accept()
if err != nil {
return err
}
client := NewSSHClient(s.caller.Conn())
opts := make(map[string][]string)
opts[KeySSHID] = []string{id}
ctx = metadata.NewOutgoingContext(ctx, opts)
stream, err := client.ForwardAgent(ctx)
if err != nil {
conn.Close()
return err
}
go Copy(ctx, conn, stream, stream.CloseSend)
}
})
return eg.Wait()
}
type SocketOpt struct {
ID string
UID int
GID int
Mode int
}
func MountSSHSocket(ctx context.Context, c session.Caller, opt SocketOpt) (sockPath string, closer func() error, err error) {
dir, err := os.MkdirTemp("", ".buildkit-ssh-sock")
if err != nil {
return "", nil, errors.WithStack(err)
}
defer func() {
if err != nil {
os.RemoveAll(dir)
}
}()
if err := os.Chmod(dir, 0711); err != nil {
return "", nil, errors.WithStack(err)
}
sockPath = filepath.Join(dir, "ssh_auth_sock")
l, err := net.Listen("unix", sockPath)
if err != nil {
return "", nil, errors.WithStack(err)
}
if err := os.Chown(sockPath, opt.UID, opt.GID); err != nil {
l.Close()
return "", nil, errors.WithStack(err)
}
if err := os.Chmod(sockPath, os.FileMode(opt.Mode)); err != nil {
l.Close()
return "", nil, errors.WithStack(err)
}
s := &server{caller: c}
id := opt.ID
if id == "" {
id = DefaultID
}
go s.run(ctx, l, id) // erroring per connection allowed
return sockPath, func() error {
err := l.Close()
os.RemoveAll(sockPath)
return errors.WithStack(err)
}, nil
}
func CheckSSHID(ctx context.Context, c session.Caller, id string) error {
client := NewSSHClient(c.Conn())
_, err := client.CheckAgent(ctx, &CheckAgentRequest{ID: id})
return errors.WithStack(err)
}

View File

@@ -0,0 +1,909 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: ssh.proto
package sshforward
import (
bytes "bytes"
context "context"
fmt "fmt"
proto "github.com/gogo/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
io "io"
math "math"
math_bits "math/bits"
reflect "reflect"
strings "strings"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
// BytesMessage contains a chunk of byte data
type BytesMessage struct {
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
}
func (m *BytesMessage) Reset() { *m = BytesMessage{} }
func (*BytesMessage) ProtoMessage() {}
func (*BytesMessage) Descriptor() ([]byte, []int) {
return fileDescriptor_ef0eae71e2e883eb, []int{0}
}
func (m *BytesMessage) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *BytesMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_BytesMessage.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *BytesMessage) XXX_Merge(src proto.Message) {
xxx_messageInfo_BytesMessage.Merge(m, src)
}
func (m *BytesMessage) XXX_Size() int {
return m.Size()
}
func (m *BytesMessage) XXX_DiscardUnknown() {
xxx_messageInfo_BytesMessage.DiscardUnknown(m)
}
var xxx_messageInfo_BytesMessage proto.InternalMessageInfo
func (m *BytesMessage) GetData() []byte {
if m != nil {
return m.Data
}
return nil
}
type CheckAgentRequest struct {
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
}
func (m *CheckAgentRequest) Reset() { *m = CheckAgentRequest{} }
func (*CheckAgentRequest) ProtoMessage() {}
func (*CheckAgentRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_ef0eae71e2e883eb, []int{1}
}
func (m *CheckAgentRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *CheckAgentRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_CheckAgentRequest.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *CheckAgentRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_CheckAgentRequest.Merge(m, src)
}
func (m *CheckAgentRequest) XXX_Size() int {
return m.Size()
}
func (m *CheckAgentRequest) XXX_DiscardUnknown() {
xxx_messageInfo_CheckAgentRequest.DiscardUnknown(m)
}
var xxx_messageInfo_CheckAgentRequest proto.InternalMessageInfo
func (m *CheckAgentRequest) GetID() string {
if m != nil {
return m.ID
}
return ""
}
type CheckAgentResponse struct {
}
func (m *CheckAgentResponse) Reset() { *m = CheckAgentResponse{} }
func (*CheckAgentResponse) ProtoMessage() {}
func (*CheckAgentResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_ef0eae71e2e883eb, []int{2}
}
func (m *CheckAgentResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *CheckAgentResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_CheckAgentResponse.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *CheckAgentResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_CheckAgentResponse.Merge(m, src)
}
func (m *CheckAgentResponse) XXX_Size() int {
return m.Size()
}
func (m *CheckAgentResponse) XXX_DiscardUnknown() {
xxx_messageInfo_CheckAgentResponse.DiscardUnknown(m)
}
var xxx_messageInfo_CheckAgentResponse proto.InternalMessageInfo
func init() {
proto.RegisterType((*BytesMessage)(nil), "moby.sshforward.v1.BytesMessage")
proto.RegisterType((*CheckAgentRequest)(nil), "moby.sshforward.v1.CheckAgentRequest")
proto.RegisterType((*CheckAgentResponse)(nil), "moby.sshforward.v1.CheckAgentResponse")
}
func init() { proto.RegisterFile("ssh.proto", fileDescriptor_ef0eae71e2e883eb) }
var fileDescriptor_ef0eae71e2e883eb = []byte{
// 252 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2c, 0x2e, 0xce, 0xd0,
0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0xca, 0xcd, 0x4f, 0xaa, 0xd4, 0x2b, 0x2e, 0xce, 0x48,
0xcb, 0x2f, 0x2a, 0x4f, 0x2c, 0x4a, 0xd1, 0x2b, 0x33, 0x54, 0x52, 0xe2, 0xe2, 0x71, 0xaa, 0x2c,
0x49, 0x2d, 0xf6, 0x4d, 0x2d, 0x2e, 0x4e, 0x4c, 0x4f, 0x15, 0x12, 0xe2, 0x62, 0x49, 0x49, 0x2c,
0x49, 0x94, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x09, 0x02, 0xb3, 0x95, 0x94, 0xb9, 0x04, 0x9d, 0x33,
0x52, 0x93, 0xb3, 0x1d, 0xd3, 0x53, 0xf3, 0x4a, 0x82, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84,
0xf8, 0xb8, 0x98, 0x3c, 0x5d, 0xc0, 0xca, 0x38, 0x83, 0x98, 0x3c, 0x5d, 0x94, 0x44, 0xb8, 0x84,
0x90, 0x15, 0x15, 0x17, 0xe4, 0xe7, 0x15, 0xa7, 0x1a, 0xed, 0x62, 0xe4, 0x62, 0x0e, 0x0e, 0xf6,
0x10, 0x8a, 0xe6, 0xe2, 0x42, 0xc8, 0x0a, 0xa9, 0xea, 0x61, 0xba, 0x44, 0x0f, 0xc3, 0x0a, 0x29,
0x35, 0x42, 0xca, 0x20, 0x96, 0x08, 0x85, 0x71, 0xf1, 0xb8, 0x41, 0x14, 0x40, 0x8c, 0x57, 0xc0,
0xa6, 0x0f, 0xd9, 0x97, 0x52, 0x04, 0x55, 0x68, 0x30, 0x1a, 0x30, 0x3a, 0x39, 0x5c, 0x78, 0x28,
0xc7, 0x70, 0xe3, 0xa1, 0x1c, 0xc3, 0x87, 0x87, 0x72, 0x8c, 0x0d, 0x8f, 0xe4, 0x18, 0x57, 0x3c,
0x92, 0x63, 0x3c, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x5f,
0x3c, 0x92, 0x63, 0xf8, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18,
0x6e, 0x3c, 0x96, 0x63, 0x88, 0xe2, 0x42, 0x98, 0x9a, 0xc4, 0x06, 0x0e, 0x78, 0x63, 0x40, 0x00,
0x00, 0x00, 0xff, 0xff, 0x6c, 0xe6, 0x6d, 0xb7, 0x85, 0x01, 0x00, 0x00,
}
func (this *BytesMessage) Equal(that interface{}) bool {
if that == nil {
return this == nil
}
that1, ok := that.(*BytesMessage)
if !ok {
that2, ok := that.(BytesMessage)
if ok {
that1 = &that2
} else {
return false
}
}
if that1 == nil {
return this == nil
} else if this == nil {
return false
}
if !bytes.Equal(this.Data, that1.Data) {
return false
}
return true
}
func (this *CheckAgentRequest) Equal(that interface{}) bool {
if that == nil {
return this == nil
}
that1, ok := that.(*CheckAgentRequest)
if !ok {
that2, ok := that.(CheckAgentRequest)
if ok {
that1 = &that2
} else {
return false
}
}
if that1 == nil {
return this == nil
} else if this == nil {
return false
}
if this.ID != that1.ID {
return false
}
return true
}
func (this *CheckAgentResponse) Equal(that interface{}) bool {
if that == nil {
return this == nil
}
that1, ok := that.(*CheckAgentResponse)
if !ok {
that2, ok := that.(CheckAgentResponse)
if ok {
that1 = &that2
} else {
return false
}
}
if that1 == nil {
return this == nil
} else if this == nil {
return false
}
return true
}
func (this *BytesMessage) GoString() string {
if this == nil {
return "nil"
}
s := make([]string, 0, 5)
s = append(s, "&sshforward.BytesMessage{")
s = append(s, "Data: "+fmt.Sprintf("%#v", this.Data)+",\n")
s = append(s, "}")
return strings.Join(s, "")
}
func (this *CheckAgentRequest) GoString() string {
if this == nil {
return "nil"
}
s := make([]string, 0, 5)
s = append(s, "&sshforward.CheckAgentRequest{")
s = append(s, "ID: "+fmt.Sprintf("%#v", this.ID)+",\n")
s = append(s, "}")
return strings.Join(s, "")
}
func (this *CheckAgentResponse) GoString() string {
if this == nil {
return "nil"
}
s := make([]string, 0, 4)
s = append(s, "&sshforward.CheckAgentResponse{")
s = append(s, "}")
return strings.Join(s, "")
}
func valueToGoStringSsh(v interface{}, typ string) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("func(v %v) *%v { return &v } ( %#v )", typ, typ, pv)
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// SSHClient is the client API for SSH service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type SSHClient interface {
CheckAgent(ctx context.Context, in *CheckAgentRequest, opts ...grpc.CallOption) (*CheckAgentResponse, error)
ForwardAgent(ctx context.Context, opts ...grpc.CallOption) (SSH_ForwardAgentClient, error)
}
type sSHClient struct {
cc *grpc.ClientConn
}
func NewSSHClient(cc *grpc.ClientConn) SSHClient {
return &sSHClient{cc}
}
func (c *sSHClient) CheckAgent(ctx context.Context, in *CheckAgentRequest, opts ...grpc.CallOption) (*CheckAgentResponse, error) {
out := new(CheckAgentResponse)
err := c.cc.Invoke(ctx, "/moby.sshforward.v1.SSH/CheckAgent", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *sSHClient) ForwardAgent(ctx context.Context, opts ...grpc.CallOption) (SSH_ForwardAgentClient, error) {
stream, err := c.cc.NewStream(ctx, &_SSH_serviceDesc.Streams[0], "/moby.sshforward.v1.SSH/ForwardAgent", opts...)
if err != nil {
return nil, err
}
x := &sSHForwardAgentClient{stream}
return x, nil
}
type SSH_ForwardAgentClient interface {
Send(*BytesMessage) error
Recv() (*BytesMessage, error)
grpc.ClientStream
}
type sSHForwardAgentClient struct {
grpc.ClientStream
}
func (x *sSHForwardAgentClient) Send(m *BytesMessage) error {
return x.ClientStream.SendMsg(m)
}
func (x *sSHForwardAgentClient) Recv() (*BytesMessage, error) {
m := new(BytesMessage)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// SSHServer is the server API for SSH service.
type SSHServer interface {
CheckAgent(context.Context, *CheckAgentRequest) (*CheckAgentResponse, error)
ForwardAgent(SSH_ForwardAgentServer) error
}
// UnimplementedSSHServer can be embedded to have forward compatible implementations.
type UnimplementedSSHServer struct {
}
func (*UnimplementedSSHServer) CheckAgent(ctx context.Context, req *CheckAgentRequest) (*CheckAgentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckAgent not implemented")
}
func (*UnimplementedSSHServer) ForwardAgent(srv SSH_ForwardAgentServer) error {
return status.Errorf(codes.Unimplemented, "method ForwardAgent not implemented")
}
func RegisterSSHServer(s *grpc.Server, srv SSHServer) {
s.RegisterService(&_SSH_serviceDesc, srv)
}
func _SSH_CheckAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CheckAgentRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SSHServer).CheckAgent(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/moby.sshforward.v1.SSH/CheckAgent",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SSHServer).CheckAgent(ctx, req.(*CheckAgentRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SSH_ForwardAgent_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(SSHServer).ForwardAgent(&sSHForwardAgentServer{stream})
}
type SSH_ForwardAgentServer interface {
Send(*BytesMessage) error
Recv() (*BytesMessage, error)
grpc.ServerStream
}
type sSHForwardAgentServer struct {
grpc.ServerStream
}
func (x *sSHForwardAgentServer) Send(m *BytesMessage) error {
return x.ServerStream.SendMsg(m)
}
func (x *sSHForwardAgentServer) Recv() (*BytesMessage, error) {
m := new(BytesMessage)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
var _SSH_serviceDesc = grpc.ServiceDesc{
ServiceName: "moby.sshforward.v1.SSH",
HandlerType: (*SSHServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "CheckAgent",
Handler: _SSH_CheckAgent_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "ForwardAgent",
Handler: _SSH_ForwardAgent_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "ssh.proto",
}
func (m *BytesMessage) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *BytesMessage) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *BytesMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Data) > 0 {
i -= len(m.Data)
copy(dAtA[i:], m.Data)
i = encodeVarintSsh(dAtA, i, uint64(len(m.Data)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *CheckAgentRequest) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *CheckAgentRequest) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *CheckAgentRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.ID) > 0 {
i -= len(m.ID)
copy(dAtA[i:], m.ID)
i = encodeVarintSsh(dAtA, i, uint64(len(m.ID)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *CheckAgentResponse) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *CheckAgentResponse) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *CheckAgentResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
return len(dAtA) - i, nil
}
func encodeVarintSsh(dAtA []byte, offset int, v uint64) int {
offset -= sovSsh(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *BytesMessage) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Data)
if l > 0 {
n += 1 + l + sovSsh(uint64(l))
}
return n
}
func (m *CheckAgentRequest) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.ID)
if l > 0 {
n += 1 + l + sovSsh(uint64(l))
}
return n
}
func (m *CheckAgentResponse) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
return n
}
func sovSsh(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
func sozSsh(x uint64) (n int) {
return sovSsh(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (this *BytesMessage) String() string {
if this == nil {
return "nil"
}
s := strings.Join([]string{`&BytesMessage{`,
`Data:` + fmt.Sprintf("%v", this.Data) + `,`,
`}`,
}, "")
return s
}
func (this *CheckAgentRequest) String() string {
if this == nil {
return "nil"
}
s := strings.Join([]string{`&CheckAgentRequest{`,
`ID:` + fmt.Sprintf("%v", this.ID) + `,`,
`}`,
}, "")
return s
}
func (this *CheckAgentResponse) String() string {
if this == nil {
return "nil"
}
s := strings.Join([]string{`&CheckAgentResponse{`,
`}`,
}, "")
return s
}
func valueToStringSsh(v interface{}) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("*%v", pv)
}
func (m *BytesMessage) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSsh
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: BytesMessage: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: BytesMessage: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSsh
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthSsh
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthSsh
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...)
if m.Data == nil {
m.Data = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipSsh(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthSsh
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *CheckAgentRequest) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSsh
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: CheckAgentRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: CheckAgentRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSsh
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthSsh
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthSsh
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.ID = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipSsh(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthSsh
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *CheckAgentResponse) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSsh
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: CheckAgentResponse: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: CheckAgentResponse: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
default:
iNdEx = preIndex
skippy, err := skipSsh(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthSsh
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipSsh(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowSsh
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowSsh
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowSsh
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLengthSsh
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupSsh
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthSsh
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthSsh = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowSsh = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupSsh = fmt.Errorf("proto: unexpected end of group")
)

View File

@@ -0,0 +1,22 @@
syntax = "proto3";
package moby.sshforward.v1;
option go_package = "sshforward";
service SSH {
rpc CheckAgent(CheckAgentRequest) returns (CheckAgentResponse);
rpc ForwardAgent(stream BytesMessage) returns (stream BytesMessage);
}
// BytesMessage contains a chunk of byte data
message BytesMessage{
bytes data = 1;
}
message CheckAgentRequest {
string ID = 1;
}
message CheckAgentResponse {
}

View File

@@ -0,0 +1,243 @@
package sshprovider
import (
"context"
"io"
"net"
"os"
"runtime"
"strings"
"time"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/sshforward"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
// AgentConfig is the config for a single exposed SSH agent
type AgentConfig struct {
ID string
Paths []string
}
// NewSSHAgentProvider creates a session provider that allows access to ssh agent
func NewSSHAgentProvider(confs []AgentConfig) (session.Attachable, error) {
m := map[string]source{}
for _, conf := range confs {
if len(conf.Paths) == 0 || len(conf.Paths) == 1 && conf.Paths[0] == "" {
conf.Paths = []string{os.Getenv("SSH_AUTH_SOCK")}
}
if conf.Paths[0] == "" {
p, err := getFallbackAgentPath()
if err != nil {
return nil, errors.Wrap(err, "invalid empty ssh agent socket")
}
conf.Paths[0] = p
}
src, err := toAgentSource(conf.Paths)
if err != nil {
return nil, err
}
if conf.ID == "" {
conf.ID = sshforward.DefaultID
}
if _, ok := m[conf.ID]; ok {
return nil, errors.Errorf("invalid duplicate ID %s", conf.ID)
}
m[conf.ID] = src
}
return &socketProvider{m: m}, nil
}
type source struct {
agent agent.Agent
socket *socketDialer
}
type socketDialer struct {
path string
dialer func(string) (net.Conn, error)
}
func (s socketDialer) Dial() (net.Conn, error) {
return s.dialer(s.path)
}
func (s socketDialer) String() string {
return s.path
}
type socketProvider struct {
m map[string]source
}
func (sp *socketProvider) Register(server *grpc.Server) {
sshforward.RegisterSSHServer(server, sp)
}
func (sp *socketProvider) CheckAgent(ctx context.Context, req *sshforward.CheckAgentRequest) (*sshforward.CheckAgentResponse, error) {
id := sshforward.DefaultID
if req.ID != "" {
id = req.ID
}
if _, ok := sp.m[id]; !ok {
return &sshforward.CheckAgentResponse{}, errors.Errorf("unset ssh forward key %s", id)
}
return &sshforward.CheckAgentResponse{}, nil
}
func (sp *socketProvider) ForwardAgent(stream sshforward.SSH_ForwardAgentServer) error {
id := sshforward.DefaultID
opts, _ := metadata.FromIncomingContext(stream.Context()) // if no metadata continue with empty object
if v, ok := opts[sshforward.KeySSHID]; ok && len(v) > 0 && v[0] != "" {
id = v[0]
}
src, ok := sp.m[id]
if !ok {
return errors.Errorf("unset ssh forward key %s", id)
}
var a agent.Agent
if src.socket != nil {
conn, err := src.socket.Dial()
if err != nil {
return errors.Wrapf(err, "failed to connect to %s", src.socket)
}
a = &readOnlyAgent{agent.NewClient(conn)}
defer conn.Close()
} else {
a = src.agent
}
s1, s2 := sockPair()
eg, ctx := errgroup.WithContext(context.TODO())
eg.Go(func() error {
return agent.ServeAgent(a, s1)
})
eg.Go(func() error {
defer s1.Close()
return sshforward.Copy(ctx, s2, stream, nil)
})
return eg.Wait()
}
func toAgentSource(paths []string) (source, error) {
var keys bool
var socket *socketDialer
a := agent.NewKeyring()
for _, p := range paths {
if socket != nil {
return source{}, errors.New("only single socket allowed")
}
if parsed := getWindowsPipeDialer(p); parsed != nil {
socket = parsed
continue
}
fi, err := os.Stat(p)
if err != nil {
return source{}, errors.WithStack(err)
}
if fi.Mode()&os.ModeSocket > 0 {
socket = &socketDialer{path: p, dialer: unixSocketDialer}
continue
}
f, err := os.Open(p)
if err != nil {
return source{}, errors.Wrapf(err, "failed to open %s", p)
}
dt, err := io.ReadAll(&io.LimitedReader{R: f, N: 100 * 1024})
if err != nil {
return source{}, errors.Wrapf(err, "failed to read %s", p)
}
k, err := ssh.ParseRawPrivateKey(dt)
if err != nil {
// On Windows, os.ModeSocket isn't appropriately set on the file mode.
// https://github.com/golang/go/issues/33357
// If parsing the file fails, check to see if it kind of looks like socket-shaped.
if runtime.GOOS == "windows" && strings.Contains(string(dt), "socket") {
if keys {
return source{}, errors.Errorf("invalid combination of keys and sockets")
}
socket = &socketDialer{path: p, dialer: unixSocketDialer}
continue
}
return source{}, errors.Wrapf(err, "failed to parse %s", p) // TODO: prompt passphrase?
}
if err := a.Add(agent.AddedKey{PrivateKey: k}); err != nil {
return source{}, errors.Wrapf(err, "failed to add %s to agent", p)
}
keys = true
}
if socket != nil {
if keys {
return source{}, errors.Errorf("invalid combination of keys and sockets")
}
return source{socket: socket}, nil
}
return source{agent: a}, nil
}
func unixSocketDialer(path string) (net.Conn, error) {
return net.DialTimeout("unix", path, 2*time.Second)
}
func sockPair() (io.ReadWriteCloser, io.ReadWriteCloser) {
pr1, pw1 := io.Pipe()
pr2, pw2 := io.Pipe()
return &sock{pr1, pw2, pw1}, &sock{pr2, pw1, pw2}
}
type sock struct {
io.Reader
io.Writer
io.Closer
}
type readOnlyAgent struct {
agent.ExtendedAgent
}
func (a *readOnlyAgent) Add(_ agent.AddedKey) error {
return errors.Errorf("adding new keys not allowed by buildkit")
}
func (a *readOnlyAgent) Remove(_ ssh.PublicKey) error {
return errors.Errorf("removing keys not allowed by buildkit")
}
func (a *readOnlyAgent) RemoveAll() error {
return errors.Errorf("removing keys not allowed by buildkit")
}
func (a *readOnlyAgent) Lock(_ []byte) error {
return errors.Errorf("locking agent not allowed by buildkit")
}
func (a *readOnlyAgent) Extension(_ string, _ []byte) ([]byte, error) {
return nil, errors.Errorf("extensions not allowed by buildkit")
}

View File

@@ -0,0 +1,16 @@
//go:build !windows
// +build !windows
package sshprovider
import (
"github.com/pkg/errors"
)
func getFallbackAgentPath() (string, error) {
return "", errors.Errorf("make sure SSH_AUTH_SOCK is set")
}
func getWindowsPipeDialer(path string) *socketDialer {
return nil
}

View File

@@ -0,0 +1,61 @@
//go:build windows
// +build windows
package sshprovider
import (
"net"
"regexp"
"strings"
"github.com/Microsoft/go-winio"
"github.com/pkg/errors"
"golang.org/x/sys/windows"
)
// Returns the Windows OpenSSH agent named pipe path, but
// only if the agent is running. Returns an error otherwise.
func getFallbackAgentPath() (string, error) {
// Windows OpenSSH agent uses a named pipe rather
// than a UNIX socket. These pipes do not play nice
// with os.Stat (which tries to open its target), so
// use a FindFirstFile syscall to check for existence.
var fd windows.Win32finddata
path := `\\.\pipe\openssh-ssh-agent`
pathPtr, _ := windows.UTF16PtrFromString(path)
handle, err := windows.FindFirstFile(pathPtr, &fd)
if err != nil {
msg := "Windows OpenSSH agent not available at %s." +
" Enable the SSH agent service or set SSH_AUTH_SOCK."
return "", errors.Errorf(msg, path)
}
_ = windows.CloseHandle(handle)
return path, nil
}
// Returns true if the path references a named pipe.
func isWindowsPipePath(path string) bool {
// If path matches \\*\pipe\* then it references a named pipe
// and requires winio.DialPipe() rather than DialTimeout("unix").
// Slashes and backslashes may be used interchangeably in the path.
// Path separators may consist of multiple consecutive (back)slashes.
pipePattern := strings.ReplaceAll("^[/]{2}[^/]+[/]+pipe[/]+", "/", `\\/`)
ok, _ := regexp.MatchString(pipePattern, path)
return ok
}
func getWindowsPipeDialer(path string) *socketDialer {
if isWindowsPipePath(path) {
return &socketDialer{path: path, dialer: windowsPipeDialer}
}
return nil
}
func windowsPipeDialer(path string) (net.Conn, error) {
return winio.DialPipe(path, nil)
}

View File

@@ -0,0 +1,106 @@
package progresswriter
import (
"context"
"strings"
"sync"
"github.com/moby/buildkit/client"
"golang.org/x/sync/errgroup"
)
type MultiWriter struct {
w Writer
eg *errgroup.Group
once sync.Once
ready chan struct{}
}
func (mw *MultiWriter) WithPrefix(pfx string, force bool) Writer {
in := make(chan *client.SolveStatus)
out := mw.w.Status()
p := &prefixed{
main: mw.w,
in: in,
}
mw.eg.Go(func() error {
mw.once.Do(func() {
close(mw.ready)
})
for {
select {
case v, ok := <-in:
if ok {
if force {
for _, v := range v.Vertexes {
v.Name = addPrefix(pfx, v.Name)
}
}
out <- v
} else {
return nil
}
case <-mw.Done():
return mw.Err()
}
}
})
return p
}
func (mw *MultiWriter) Done() <-chan struct{} {
return mw.w.Done()
}
func (mw *MultiWriter) Err() error {
return mw.w.Err()
}
func (mw *MultiWriter) Status() chan *client.SolveStatus {
return nil
}
type prefixed struct {
main Writer
in chan *client.SolveStatus
}
func (p *prefixed) Done() <-chan struct{} {
return p.main.Done()
}
func (p *prefixed) Err() error {
return p.main.Err()
}
func (p *prefixed) Status() chan *client.SolveStatus {
return p.in
}
func NewMultiWriter(pw Writer) *MultiWriter {
if pw == nil {
return nil
}
eg, _ := errgroup.WithContext(context.TODO())
ready := make(chan struct{})
go func() {
<-ready
eg.Wait()
close(pw.Status())
}()
return &MultiWriter{
w: pw,
eg: eg,
ready: ready,
}
}
func addPrefix(pfx, name string) string {
if strings.HasPrefix(name, "[") {
return "[" + pfx + " " + name[1:]
}
return "[" + pfx + "] " + name
}

View File

@@ -0,0 +1,83 @@
package progresswriter
import (
"context"
"os"
"github.com/containerd/console"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/util/progress/progressui"
)
type printer struct {
status chan *client.SolveStatus
done <-chan struct{}
err error
}
func (p *printer) Done() <-chan struct{} {
return p.done
}
func (p *printer) Err() error {
return p.err
}
func (p *printer) Status() chan *client.SolveStatus {
if p == nil {
return nil
}
return p.status
}
type tee struct {
Writer
status chan *client.SolveStatus
}
func (t *tee) Status() chan *client.SolveStatus {
return t.status
}
func Tee(w Writer, ch chan *client.SolveStatus) Writer {
st := make(chan *client.SolveStatus)
t := &tee{
status: st,
Writer: w,
}
go func() {
for v := range st {
w.Status() <- v
ch <- v
}
close(w.Status())
close(ch)
}()
return t
}
func NewPrinter(ctx context.Context, out console.File, mode string) (Writer, error) {
statusCh := make(chan *client.SolveStatus)
doneCh := make(chan struct{})
pw := &printer{
status: statusCh,
done: doneCh,
}
if v := os.Getenv("BUILDKIT_PROGRESS"); v != "" && mode == "auto" {
mode = v
}
d, err := progressui.NewDisplay(out, progressui.DisplayMode(mode))
if err != nil {
return nil, err
}
go func() {
// not using shared context to not disrupt display but let is finish reporting errors
_, pw.err = d.UpdateFrom(ctx, statusCh)
close(doneCh)
}()
return pw, nil
}

View File

@@ -0,0 +1,93 @@
package progresswriter
import (
"time"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/identity"
digest "github.com/opencontainers/go-digest"
)
type Logger func(*client.SolveStatus)
type SubLogger interface {
Wrap(name string, fn func() error) error
Log(stream int, dt []byte)
}
func Wrap(name string, l Logger, fn func(SubLogger) error) (err error) {
if l == nil {
return nil
}
dgst := digest.FromBytes([]byte(identity.NewID()))
tm := time.Now()
l(&client.SolveStatus{
Vertexes: []*client.Vertex{{
Digest: dgst,
Name: name,
Started: &tm,
}},
})
defer func() {
tm2 := time.Now()
errMsg := ""
if err != nil {
errMsg = err.Error()
}
l(&client.SolveStatus{
Vertexes: []*client.Vertex{{
Digest: dgst,
Name: name,
Started: &tm,
Completed: &tm2,
Error: errMsg,
}},
})
}()
return fn(&subLogger{dgst, l})
}
type subLogger struct {
dgst digest.Digest
logger Logger
}
func (sl *subLogger) Wrap(name string, fn func() error) (err error) {
tm := time.Now()
sl.logger(&client.SolveStatus{
Statuses: []*client.VertexStatus{{
Vertex: sl.dgst,
ID: name,
Timestamp: time.Now(),
Started: &tm,
}},
})
defer func() {
tm2 := time.Now()
sl.logger(&client.SolveStatus{
Statuses: []*client.VertexStatus{{
Vertex: sl.dgst,
ID: name,
Timestamp: time.Now(),
Started: &tm,
Completed: &tm2,
}},
})
}()
return fn()
}
func (sl *subLogger) Log(stream int, dt []byte) {
sl.logger(&client.SolveStatus{
Logs: []*client.VertexLog{{
Vertex: sl.dgst,
Stream: stream,
Data: dt,
Timestamp: time.Now(),
}},
})
}

View File

@@ -0,0 +1,89 @@
package progresswriter
import (
"time"
"github.com/moby/buildkit/client"
)
func ResetTime(in Writer) Writer {
w := &pw{Writer: in, status: make(chan *client.SolveStatus), tm: time.Now()}
go func() {
for {
select {
case <-in.Done():
return
case st, ok := <-w.status:
if !ok {
close(in.Status())
return
}
if w.diff == nil {
for _, v := range st.Vertexes {
if v.Started != nil {
d := v.Started.Sub(w.tm)
w.diff = &d
}
}
}
if w.diff != nil {
vertexes := make([]*client.Vertex, 0, len(st.Vertexes))
for _, v := range st.Vertexes {
v := *v
if v.Started != nil {
d := v.Started.Add(-*w.diff)
v.Started = &d
}
if v.Completed != nil {
d := v.Completed.Add(-*w.diff)
v.Completed = &d
}
vertexes = append(vertexes, &v)
}
statuses := make([]*client.VertexStatus, 0, len(st.Statuses))
for _, v := range st.Statuses {
v := *v
if v.Started != nil {
d := v.Started.Add(-*w.diff)
v.Started = &d
}
if v.Completed != nil {
d := v.Completed.Add(-*w.diff)
v.Completed = &d
}
v.Timestamp = v.Timestamp.Add(-*w.diff)
statuses = append(statuses, &v)
}
logs := make([]*client.VertexLog, 0, len(st.Logs))
for _, v := range st.Logs {
v := *v
v.Timestamp = v.Timestamp.Add(-*w.diff)
logs = append(logs, &v)
}
st = &client.SolveStatus{
Vertexes: vertexes,
Statuses: statuses,
Logs: logs,
Warnings: st.Warnings,
}
}
in.Status() <- st
}
}
}()
return w
}
type pw struct {
Writer
tm time.Time
diff *time.Duration
status chan *client.SolveStatus
}
func (p *pw) Status() chan *client.SolveStatus {
return p.status
}

View File

@@ -0,0 +1,46 @@
package progresswriter
import (
"time"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/identity"
digest "github.com/opencontainers/go-digest"
)
type Writer interface {
Done() <-chan struct{}
Err() error
Status() chan *client.SolveStatus
}
func Write(w Writer, name string, f func() error) {
status := w.Status()
dgst := digest.FromBytes([]byte(identity.NewID()))
tm := time.Now()
vtx := client.Vertex{
Digest: dgst,
Name: name,
Started: &tm,
}
status <- &client.SolveStatus{
Vertexes: []*client.Vertex{&vtx},
}
var err error
if f != nil {
err = f()
}
tm2 := time.Now()
vtx2 := vtx
vtx2.Completed = &tm2
if err != nil {
vtx2.Error = err.Error()
}
status <- &client.SolveStatus{
Vertexes: []*client.Vertex{&vtx2},
}
}

View File

@@ -0,0 +1,90 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package sign signs small messages using public-key cryptography.
//
// Sign uses Ed25519 to sign messages. The length of messages is not hidden.
// Messages should be small because:
// 1. The whole message needs to be held in memory to be processed.
// 2. Using large messages pressures implementations on small machines to process
// plaintext without verifying the signature. This is very dangerous, and this API
// discourages it, but a protocol that uses excessive message sizes might present
// some implementations with no other choice.
// 3. Performance may be improved by working with messages that fit into data caches.
// Thus large amounts of data should be chunked so that each message is small.
//
// This package is not interoperable with the current release of NaCl
// (https://nacl.cr.yp.to/sign.html), which does not support Ed25519 yet. However,
// it is compatible with the NaCl fork libsodium (https://www.libsodium.org), as well
// as TweetNaCl (https://tweetnacl.cr.yp.to/).
package sign
import (
"crypto/ed25519"
"io"
"golang.org/x/crypto/internal/alias"
)
// Overhead is the number of bytes of overhead when signing a message.
const Overhead = 64
// GenerateKey generates a new public/private key pair suitable for use with
// Sign and Open.
func GenerateKey(rand io.Reader) (publicKey *[32]byte, privateKey *[64]byte, err error) {
pub, priv, err := ed25519.GenerateKey(rand)
if err != nil {
return nil, nil, err
}
publicKey, privateKey = new([32]byte), new([64]byte)
copy((*publicKey)[:], pub)
copy((*privateKey)[:], priv)
return publicKey, privateKey, nil
}
// Sign appends a signed copy of message to out, which will be Overhead bytes
// longer than the original and must not overlap it.
func Sign(out, message []byte, privateKey *[64]byte) []byte {
sig := ed25519.Sign(ed25519.PrivateKey((*privateKey)[:]), message)
ret, out := sliceForAppend(out, Overhead+len(message))
if alias.AnyOverlap(out, message) {
panic("nacl: invalid buffer overlap")
}
copy(out, sig)
copy(out[Overhead:], message)
return ret
}
// Open verifies a signed message produced by Sign and appends the message to
// out, which must not overlap the signed message. The output will be Overhead
// bytes smaller than the signed message.
func Open(out, signedMessage []byte, publicKey *[32]byte) ([]byte, bool) {
if len(signedMessage) < Overhead {
return nil, false
}
if !ed25519.Verify(ed25519.PublicKey((*publicKey)[:]), signedMessage[Overhead:], signedMessage[:Overhead]) {
return nil, false
}
ret, out := sliceForAppend(out, len(signedMessage)-Overhead)
if alias.AnyOverlap(out, signedMessage) {
panic("nacl: invalid buffer overlap")
}
copy(out, signedMessage[Overhead:])
return ret, true
}
// sliceForAppend takes a slice and a requested number of bytes. It returns a
// slice with the contents of the given slice followed by that many bytes and a
// second slice that aliases into it and contains only the extra bytes. If the
// original slice has sufficient capacity then no allocation is performed.
func sliceForAppend(in []byte, n int) (head, tail []byte) {
if total := len(in) + n; cap(in) >= total {
head = in[:total]
} else {
head = make([]byte, total)
copy(head, in)
}
tail = head[len(in):]
return
}

View File

@@ -388,6 +388,9 @@ github.com/grpc-ecosystem/go-grpc-middleware
github.com/grpc-ecosystem/grpc-gateway/v2/internal/httprule
github.com/grpc-ecosystem/grpc-gateway/v2/runtime
github.com/grpc-ecosystem/grpc-gateway/v2/utilities
# github.com/hashicorp/go-cleanhttp v0.5.2
## explicit; go 1.13
github.com/hashicorp/go-cleanhttp
# github.com/hashicorp/go-version v1.2.0
## explicit
github.com/hashicorp/go-version
@@ -440,6 +443,7 @@ github.com/moby/buildkit/client/connhelper/ssh
github.com/moby/buildkit/client/llb
github.com/moby/buildkit/client/llb/sourceresolver
github.com/moby/buildkit/client/ociindex
github.com/moby/buildkit/cmd/buildctl/build
github.com/moby/buildkit/exporter/containerimage/exptypes
github.com/moby/buildkit/exporter/exptypes
github.com/moby/buildkit/frontend/attestations
@@ -456,9 +460,15 @@ github.com/moby/buildkit/frontend/subrequests/outline
github.com/moby/buildkit/frontend/subrequests/targets
github.com/moby/buildkit/identity
github.com/moby/buildkit/session
github.com/moby/buildkit/session/auth
github.com/moby/buildkit/session/auth/authprovider
github.com/moby/buildkit/session/content
github.com/moby/buildkit/session/filesync
github.com/moby/buildkit/session/grpchijack
github.com/moby/buildkit/session/secrets
github.com/moby/buildkit/session/secrets/secretsprovider
github.com/moby/buildkit/session/sshforward
github.com/moby/buildkit/session/sshforward/sshprovider
github.com/moby/buildkit/session/upload
github.com/moby/buildkit/session/upload/uploadprovider
github.com/moby/buildkit/solver/errdefs
@@ -479,6 +489,7 @@ github.com/moby/buildkit/util/imageutil
github.com/moby/buildkit/util/leaseutil
github.com/moby/buildkit/util/progress
github.com/moby/buildkit/util/progress/progressui
github.com/moby/buildkit/util/progress/progresswriter
github.com/moby/buildkit/util/resolver/limited
github.com/moby/buildkit/util/resolver/retryhandler
github.com/moby/buildkit/util/sshutil
@@ -733,6 +744,7 @@ golang.org/x/crypto/ed25519
golang.org/x/crypto/hkdf
golang.org/x/crypto/internal/alias
golang.org/x/crypto/internal/poly1305
golang.org/x/crypto/nacl/sign
golang.org/x/crypto/pkcs12
golang.org/x/crypto/pkcs12/internal/rc2
golang.org/x/crypto/ssh