Skip to content

Commit c32e7eb

Browse files
committed
Add support for build secrets for remote builder
* Adds support for sealing secrets for use with faas-cli * Tested e2e and saw with a Dockerfile that captured a build time secret - the published image showed the value was captured as expected. Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
1 parent 41c1f23 commit c32e7eb

File tree

114 files changed

+14671
-194
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

114 files changed

+14671
-194
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,9 +357,31 @@ $ uname -a | curl http://127.0.0.1:8080/function/nodejs-echo--data-binary @-
357357
* `OPENFAAS_TEMPLATE_URL` - to set the default URL to pull templates from
358358
* `OPENFAAS_PREFIX` - for use with `faas-cli new` - this can act in place of `--prefix`
359359
* `OPENFAAS_URL` - to override the default gateway URL
360+
* `OPENFAAS_REMOTE_BUILDER` - default value for `--remote-builder`
361+
* `OPENFAAS_PAYLOAD_SECRET` - default value for `--payload-secret`
362+
* `OPENFAAS_BUILDER_PUBLIC_KEY` - builder public key as a literal value, or a path to a file containing raw base64 or the JSON response from `/public-key`
363+
* `OPENFAAS_BUILDER_KEY_ID` - default value for `--builder-key-id` when pinning a raw base64 public key file
360364
* `OPENFAAS_CONFIG` - to override the location of the configuration folder, which contains auth configuration.
361365
* `CI` - to override the location of the configuration folder, when true, the configuration folder is `.openfaas` in the current working directory. This value is ignored if `OPENFAAS_CONFIG` is set.
362366

367+
For encrypted remote-builder builds, the safest option is to read the builder public key from a file rather than putting the key inline on the command line. The file can contain either:
368+
369+
* the raw base64 public key
370+
* or the JSON document returned by `GET /public-key`
371+
372+
The `--builder-public-key` flag and `OPENFAAS_BUILDER_PUBLIC_KEY` env var also accept a literal value directly. If the value points to an existing file, the CLI reads the file; otherwise it treats the value itself as the key material.
373+
374+
Basic remote-builder example using automatic `GET /public-key` discovery:
375+
376+
```sh
377+
faas-cli publish \
378+
--remote-builder http://127.0.0.1:8081 \
379+
--payload-secret /var/openfaas/secrets/payload-secret \
380+
-f stack.yml
381+
```
382+
383+
If any functions in `stack.yml` define `build_secrets`, the CLI will fetch `/public-key` from the builder automatically unless `--builder-public-key` is set.
384+
363385
### Contributing
364386

365387
See [contributing guide](https://github.com/openfaas/faas-cli/blob/master/CONTRIBUTING.md).

builder/build.go

Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@
44
package builder
55

66
import (
7-
"bytes"
87
"context"
98
"crypto/md5"
109
"encoding/hex"
1110
"fmt"
1211
"log"
13-
"net/http"
1412
"net/url"
1513
"os"
1614
"path"
@@ -22,7 +20,7 @@ import (
2220
v2execute "github.com/alexellis/go-execute/v2"
2321
"github.com/openfaas/faas-cli/schema"
2422
vcs "github.com/openfaas/faas-cli/versioncontrol"
25-
"github.com/openfaas/go-sdk/builder"
23+
sdkbuilder "github.com/openfaas/go-sdk/builder"
2624
"github.com/openfaas/go-sdk/stack"
2725
)
2826

@@ -69,7 +67,7 @@ func getTemplate(lang string) (string, *stack.LanguageTemplate, error) {
6967

7068
// BuildImage construct Docker image from function parameters
7169
// TODO: refactor signature to a struct to simplify the length of the method header
72-
func BuildImage(image string, handler string, functionName string, language string, nocache bool, squash bool, shrinkwrap bool, buildArgMap map[string]string, buildOptions []string, tagFormat schema.BuildFormat, buildLabelMap map[string]string, quietBuild bool, copyExtraPaths []string, remoteBuilder, payloadSecretPath string, forcePull bool) error {
70+
func BuildImage(image string, handler string, functionName string, language string, nocache bool, squash bool, shrinkwrap bool, buildArgMap map[string]string, buildOptions []string, tagFormat schema.BuildFormat, buildLabelMap map[string]string, quietBuild bool, copyExtraPaths []string, buildSecrets map[string]string, remoteBuilder, payloadSecretPath, builderPublicKeyPath, builderKeyID string, forcePull bool) error {
7371

7472
_, langTemplate, err := getTemplate(language)
7573
if err != nil {
@@ -85,12 +83,12 @@ func BuildImage(image string, handler string, functionName string, language stri
8583
return fmt.Errorf("building %s, %s is an invalid path", functionName, handler)
8684
}
8785

88-
opts := []builder.BuildContextOption{}
86+
opts := []sdkbuilder.BuildContextOption{}
8987
if len(langTemplate.HandlerFolder) > 0 {
90-
opts = append(opts, builder.WithHandlerOverlay(langTemplate.HandlerFolder))
88+
opts = append(opts, sdkbuilder.WithHandlerOverlay(langTemplate.HandlerFolder))
9189
}
9290

93-
buildContext, err := builder.CreateBuildContext(functionName, handler, language, copyExtraPaths, opts...)
91+
buildContext, err := sdkbuilder.CreateBuildContext(functionName, handler, language, copyExtraPaths, opts...)
9492
if err != nil {
9593
return err
9694
}
@@ -125,51 +123,24 @@ func BuildImage(image string, handler string, functionName string, language stri
125123

126124
tarPath := path.Join(tempDir, "req.tar")
127125

128-
buildConfig := builder.BuildConfig{
126+
buildConfig := sdkbuilder.BuildConfig{
129127
Image: imageName,
130128
BuildArgs: buildArgMap,
131129
}
132130

133131
// Prepare a tar archive that contains the build config and build context.
134-
if err := builder.MakeTar(tarPath, path.Join("build", functionName), &buildConfig); err != nil {
132+
if err := sdkbuilder.MakeTar(tarPath, path.Join("build", functionName), &buildConfig); err != nil {
135133
return fmt.Errorf("failed to create tar file for %s, error: %w", functionName, err)
136134
}
137135

138-
// Get the HMAC secret used for payload authentication with the builder API.
139-
payloadSecret, err := os.ReadFile(payloadSecretPath)
140-
if err != nil {
141-
return fmt.Errorf("failed to read payload secret: %w", err)
142-
}
143-
payloadSecret = bytes.TrimSpace(payloadSecret)
144-
145-
// Initialize a new builder client.
146136
u, _ := url.Parse(remoteBuilder)
147137
builderURL := &url.URL{
148138
Scheme: u.Scheme,
149139
Host: u.Host,
150140
}
151-
b := builder.NewFunctionBuilder(builderURL, http.DefaultClient, builder.WithHmacAuth(string(payloadSecret)))
152-
153-
stream, err := b.BuildWithStream(tarPath)
154-
if err != nil {
141+
if err := runRemoteBuild(builderURL, tarPath, payloadSecretPath, builderPublicKeyPath, builderKeyID, buildSecrets, quietBuild, functionName, imageName); err != nil {
155142
return fmt.Errorf("failed to invoke builder: %w", err)
156143
}
157-
defer stream.Close()
158-
159-
for result := range stream.Results() {
160-
if !quietBuild {
161-
for _, logMsg := range result.Log {
162-
fmt.Printf("%s\n", logMsg)
163-
}
164-
}
165-
166-
switch result.Status {
167-
case builder.BuildSuccess:
168-
log.Printf("%s success building and pushing image: %s", functionName, result.Image)
169-
case builder.BuildFailed:
170-
return fmt.Errorf("%s failure while building or pushing image %s: %s", functionName, imageName, result.Error)
171-
}
172-
}
173144

174145
} else {
175146
dockerBuildVal := dockerBuild{

builder/publish.go

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,23 @@
44
package builder
55

66
import (
7-
"bytes"
87
"context"
98
"fmt"
10-
"log"
11-
"net/http"
129
"net/url"
1310
"os"
1411
"path"
1512
"strings"
1613

1714
v2execute "github.com/alexellis/go-execute/v2"
1815
"github.com/openfaas/faas-cli/schema"
19-
"github.com/openfaas/go-sdk/builder"
16+
sdkbuilder "github.com/openfaas/go-sdk/builder"
2017
"github.com/openfaas/go-sdk/stack"
2118
)
2219

2320
// PublishImage will publish images as multi-arch
2421
// TODO: refactor signature to a struct to simplify the length of the method header
2522
func PublishImage(image string, handler string, functionName string, language string, nocache bool, squash bool, shrinkwrap bool, buildArgMap map[string]string,
26-
buildOptions []string, tagMode schema.BuildFormat, buildLabelMap map[string]string, quietBuild bool, copyExtraPaths []string, platforms string, extraTags []string, remoteBuilder, payloadSecretPath string, forcePull bool) error {
23+
buildOptions []string, tagMode schema.BuildFormat, buildLabelMap map[string]string, quietBuild bool, copyExtraPaths []string, buildSecrets map[string]string, platforms string, extraTags []string, remoteBuilder, payloadSecretPath, builderPublicKeyPath, builderKeyID string, forcePull bool) error {
2724

2825
if stack.IsValidTemplate(language) {
2926
pathToTemplateYAML := fmt.Sprintf("./template/%s/template.yml", language)
@@ -40,12 +37,12 @@ func PublishImage(image string, handler string, functionName string, language st
4037
return fmt.Errorf("building %s, %s is an invalid path", functionName, handler)
4138
}
4239

43-
opts := []builder.BuildContextOption{}
40+
opts := []sdkbuilder.BuildContextOption{}
4441
if len(langTemplate.HandlerFolder) > 0 {
45-
opts = append(opts, builder.WithHandlerOverlay(langTemplate.HandlerFolder))
42+
opts = append(opts, sdkbuilder.WithHandlerOverlay(langTemplate.HandlerFolder))
4643
}
4744

48-
buildContext, err := builder.CreateBuildContext(functionName, handler, language, copyExtraPaths, opts...)
45+
buildContext, err := sdkbuilder.CreateBuildContext(functionName, handler, language, copyExtraPaths, opts...)
4946
if err != nil {
5047
return err
5148
}
@@ -85,51 +82,24 @@ func PublishImage(image string, handler string, functionName string, language st
8582
tarPath := path.Join(tempDir, "req.tar")
8683

8784
builderPlatforms := strings.Split(platforms, ",")
88-
buildConfig := builder.BuildConfig{
85+
buildConfig := sdkbuilder.BuildConfig{
8986
Image: imageName,
9087
BuildArgs: buildArgMap,
9188
Platforms: builderPlatforms,
9289
}
9390

9491
// Prepare a tar archive that contains the build config and build context.
95-
if err := builder.MakeTar(tarPath, path.Join("build", functionName), &buildConfig); err != nil {
92+
if err := sdkbuilder.MakeTar(tarPath, path.Join("build", functionName), &buildConfig); err != nil {
9693
return fmt.Errorf("failed to create tar file for %s, error: %w", functionName, err)
9794
}
9895

99-
// Get the HMAC secret used for payload authentication with the builder API.
100-
payloadSecret, err := os.ReadFile(payloadSecretPath)
101-
if err != nil {
102-
return fmt.Errorf("failed to read payload secret: %w", err)
103-
}
104-
payloadSecret = bytes.TrimSpace(payloadSecret)
105-
106-
// Initialize a new builder client.
10796
u, _ := url.Parse(remoteBuilder)
10897
builderURL := &url.URL{
10998
Scheme: u.Scheme,
11099
Host: u.Host,
111100
}
112-
b := builder.NewFunctionBuilder(builderURL, http.DefaultClient, builder.WithHmacAuth(string(payloadSecret)))
113-
114-
stream, err := b.BuildWithStream(tarPath)
115-
if err != nil {
116-
return fmt.Errorf("failed to invoke builder:: %w", err)
117-
}
118-
defer stream.Close()
119-
120-
for result := range stream.Results() {
121-
if !quietBuild {
122-
for _, logMsg := range result.Log {
123-
fmt.Printf("%s\n", logMsg)
124-
}
125-
}
126-
127-
switch result.Status {
128-
case builder.BuildSuccess:
129-
log.Printf("%s success building and pushing image: %s", functionName, result.Image)
130-
case builder.BuildFailed:
131-
return fmt.Errorf("%s failure while building or pushing image %s: %s", functionName, imageName, result.Error)
132-
}
101+
if err := runRemoteBuild(builderURL, tarPath, payloadSecretPath, builderPublicKeyPath, builderKeyID, buildSecrets, quietBuild, functionName, imageName); err != nil {
102+
return fmt.Errorf("failed to invoke builder: %w", err)
133103
}
134104

135105
} else {

0 commit comments

Comments
 (0)