Provider returns secrets for a function

This commit allows the provider to return a list of the names of the
secrets mapped into an openfaas function. This was tested by building
and deploying faasd on multipass and curling the provider directly and
seeing the returned secrets list!

Signed-off-by: Alistair Hey <alistair@heyal.co.uk>
This commit is contained in:
Alistair Hey 2021-01-20 19:23:46 +00:00 committed by Alex Ellis
parent 4e8a1d810a
commit 237a026b79
12 changed files with 157 additions and 92 deletions

View File

@ -1,7 +1,7 @@
version: "3.7" version: "3.7"
services: services:
basic-auth-plugin: basic-auth-plugin:
image: "docker.io/openfaas/basic-auth-plugin:0.18.18${ARCH_SUFFIX}" image: ghcr.io/openfaas/basic-auth:0.20.5
environment: environment:
- port=8080 - port=8080
- secret_mount_path=/run/secrets - secret_mount_path=/run/secrets
@ -41,7 +41,7 @@ services:
- "127.0.0.1:9090:9090" - "127.0.0.1:9090:9090"
gateway: gateway:
image: "docker.io/openfaas/gateway:0.19.1${ARCH_SUFFIX}" image: ghcr.io/openfaas/gateway:0.20.5
environment: environment:
- basic_auth=true - basic_auth=true
- functions_provider_url=http://faasd-provider:8081/ - functions_provider_url=http://faasd-provider:8081/

2
go.mod
View File

@ -30,7 +30,7 @@ require (
github.com/opencontainers/runc v1.0.0-rc9 // indirect github.com/opencontainers/runc v1.0.0-rc9 // indirect
github.com/opencontainers/runtime-spec v1.0.2 github.com/opencontainers/runtime-spec v1.0.2
github.com/openfaas/faas v0.0.0-20201205125747-9bbb25e3c7c4 github.com/openfaas/faas v0.0.0-20201205125747-9bbb25e3c7c4
github.com/openfaas/faas-provider v0.15.3 github.com/openfaas/faas-provider v0.16.2
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/procfs v0.2.0 // indirect github.com/prometheus/procfs v0.2.0 // indirect
github.com/sethvargo/go-password v0.1.3 github.com/sethvargo/go-password v0.1.3

4
go.sum
View File

@ -181,8 +181,8 @@ github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
github.com/openfaas/faas v0.0.0-20201205125747-9bbb25e3c7c4 h1:JJjthDw7WziZQ7sC5C+M2872mIdud5R+s6Cb0cXyPuA= github.com/openfaas/faas v0.0.0-20201205125747-9bbb25e3c7c4 h1:JJjthDw7WziZQ7sC5C+M2872mIdud5R+s6Cb0cXyPuA=
github.com/openfaas/faas v0.0.0-20201205125747-9bbb25e3c7c4/go.mod h1:E0m2rLup0Vvxg53BKxGgaYAGcZa3Xl+vvL7vSi5yQ14= github.com/openfaas/faas v0.0.0-20201205125747-9bbb25e3c7c4/go.mod h1:E0m2rLup0Vvxg53BKxGgaYAGcZa3Xl+vvL7vSi5yQ14=
github.com/openfaas/faas-provider v0.15.3 h1:tfjuL5F/tdoUr1J65XrUADyoe59x38VzN1w0DvBaTRk= github.com/openfaas/faas-provider v0.16.2 h1:ChpiZh1RM8zFIzvp31OPlKpTbh5Lcm7f91WCFcpW4gA=
github.com/openfaas/faas-provider v0.15.3/go.mod h1:fq1JL0mX4rNvVVvRLaLRJ3H6o667sHuyP5p/7SZEe98= github.com/openfaas/faas-provider v0.16.2/go.mod h1:fq1JL0mX4rNvVVvRLaLRJ3H6o667sHuyP5p/7SZEe98=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

View File

@ -3,6 +3,7 @@ package handlers
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/opencontainers/runtime-spec/specs-go"
"log" "log"
"strings" "strings"
@ -22,6 +23,7 @@ type Function struct {
IP string IP string
labels map[string]string labels map[string]string
annotations map[string]string annotations map[string]string
secrets []string
} }
// ListFunctions returns a map of all functions with running tasks on namespace // ListFunctions returns a map of all functions with running tasks on namespace
@ -71,12 +73,19 @@ func GetFunction(client *containerd.Client, name string) (Function, error) {
labels, annotations := buildLabelsAndAnnotations(allLabels) labels, annotations := buildLabelsAndAnnotations(allLabels)
spec, err := c.Spec(ctx)
if err != nil {
return Function{}, fmt.Errorf("unable to load function spec for reading secrets: %s, error %s", name, err)
}
secrets := readSecretsFromMounts(spec.Mounts)
fn.name = containerName fn.name = containerName
fn.namespace = faasd.FunctionNamespace fn.namespace = faasd.FunctionNamespace
fn.image = image.Name() fn.image = image.Name()
fn.labels = labels fn.labels = labels
fn.annotations = annotations fn.annotations = annotations
fn.secrets = secrets
replicas := 0 replicas := 0
task, err := c.Task(ctx, nil) task, err := c.Task(ctx, nil)
if err == nil { if err == nil {
@ -105,6 +114,18 @@ func GetFunction(client *containerd.Client, name string) (Function, error) {
return fn, nil return fn, nil
} }
func readSecretsFromMounts(mounts []specs.Mount) []string {
secrets := []string{}
for _, mnt := range mounts {
x := strings.Split(mnt.Destination, "/var/openfaas/secrets/")
if len(x) > 1 {
secrets = append(secrets, x[1])
}
}
return secrets
}
// buildLabelsAndAnnotations returns a separated list with labels first, // buildLabelsAndAnnotations returns a separated list with labels first,
// followed by annotations by checking each key of ctrLabels for a prefix. // followed by annotations by checking each key of ctrLabels for a prefix.
func buildLabelsAndAnnotations(ctrLabels map[string]string) (map[string]string, map[string]string) { func buildLabelsAndAnnotations(ctrLabels map[string]string) (map[string]string, map[string]string) {

View File

@ -2,6 +2,8 @@ package handlers
import ( import (
"fmt" "fmt"
"github.com/opencontainers/runtime-spec/specs-go"
"reflect"
"testing" "testing"
) )
@ -27,3 +29,27 @@ func Test_BuildLabelsAndAnnotationsFromServiceSpec_Annotations(t *testing.T) {
t.Errorf("want: '%s' entry in annotation map got: key not found", "current-time") t.Errorf("want: '%s' entry in annotation map got: key not found", "current-time")
} }
} }
func Test_SplitMountToSecrets(t *testing.T) {
type test struct {
Name string
Input []specs.Mount
Expected []string
}
tests := []test{
{Name: "No matching openfaas secrets", Input: []specs.Mount{{Destination: "/foo/"}}, Expected: []string{}},
{Name: "No Mounts", Input: []specs.Mount{{Destination: "/foo/"}}, Expected: []string{}},
{Name: "One Mounts IS secret", Input: []specs.Mount{{Destination: "/var/openfaas/secrets/secret1"}}, Expected: []string{"secret1"}},
{Name: "Multiple Mounts 1 secret", Input: []specs.Mount{{Destination: "/var/openfaas/secrets/secret1"}, {Destination: "/some/other/path"}}, Expected: []string{"secret1"}},
{Name: "Multiple Mounts all secrets", Input: []specs.Mount{{Destination: "/var/openfaas/secrets/secret1"}, {Destination: "/var/openfaas/secrets/secret2"}}, Expected: []string{"secret1", "secret2"}},
}
for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
got := readSecretsFromMounts(tc.Input)
if !reflect.DeepEqual(got, tc.Expected) {
t.Fatalf("expected %s, got %s", tc.Expected, got)
}
})
}
}

View File

@ -22,10 +22,10 @@ func MakeInfoHandler(version, sha string) http.HandlerFunc {
defer r.Body.Close() defer r.Body.Close()
} }
infoResponse := types.InfoResponse{ infoResponse := types.ProviderInfo{
Orchestration: OrchestrationIdentifier, Orchestration: OrchestrationIdentifier,
Provider: ProviderName, Name: ProviderName,
Version: types.ProviderVersion{ Version: &types.VersionInfo{
Release: version, Release: version,
SHA: sha, SHA: sha,
}, },

View File

@ -15,14 +15,14 @@ func Test_InfoHandler(t *testing.T) {
r := httptest.NewRequest("GET", "/", nil) r := httptest.NewRequest("GET", "/", nil)
handler(w, r) handler(w, r)
resp := types.InfoResponse{} resp := types.ProviderInfo{}
err := json.Unmarshal(w.Body.Bytes(), &resp) err := json.Unmarshal(w.Body.Bytes(), &resp)
if err != nil { if err != nil {
t.Fatalf("unexpected error unmarshalling the response") t.Fatalf("unexpected error unmarshalling the response")
} }
if resp.Provider != ProviderName { if resp.Name != ProviderName {
t.Fatalf("expected provider %q, got %q", ProviderName, resp.Provider) t.Fatalf("expected provider %q, got %q", ProviderName, resp.Name)
} }
if resp.Orchestration != OrchestrationIdentifier { if resp.Orchestration != OrchestrationIdentifier {

View File

@ -31,6 +31,7 @@ func MakeReadHandler(client *containerd.Client) func(w http.ResponseWriter, r *h
Namespace: fn.namespace, Namespace: fn.namespace,
Labels: labels, Labels: labels,
Annotations: annotations, Annotations: annotations,
Secrets: fn.secrets,
}) })
} }

View File

@ -23,6 +23,7 @@ func MakeReplicaReaderHandler(client *containerd.Client) func(w http.ResponseWri
Namespace: f.namespace, Namespace: f.namespace,
Labels: &f.labels, Labels: &f.labels,
Annotations: &f.annotations, Annotations: &f.annotations,
Secrets: f.secrets,
} }
functionBytes, _ := json.Marshal(found) functionBytes, _ := json.Marshal(found)

View File

@ -3,92 +3,45 @@ package types
// FunctionDeployment represents a request to create or update a Function. // FunctionDeployment represents a request to create or update a Function.
type FunctionDeployment struct { type FunctionDeployment struct {
// Service corresponds to a Service // Service is the name of the function deployment
Service string `json:"service"` Service string `json:"service"`
// Image corresponds to a Docker image // Image is a fully-qualified container image
Image string `json:"image"` Image string `json:"image"`
// Network is specific to Docker Swarm - default overlay network is: func_functions // Namespace for the function, if supported by the faas-provider
Network string `json:"network"` Namespace string `json:"namespace,omitempty"`
// EnvProcess corresponds to the fprocess variable for your container watchdog. // EnvProcess overrides the fprocess environment variable and can be used
EnvProcess string `json:"envProcess"` // with the watchdog
EnvProcess string `json:"envProcess,omitempty"`
// EnvVars provides overrides for functions. // EnvVars can be provided to set environment variables for the function runtime.
EnvVars map[string]string `json:"envVars"` EnvVars map[string]string `json:"envVars,omitempty"`
// RegistryAuth is the registry authentication (optional) // Constraints are specific to the faas-provider.
// in the same encoded format as Docker native credentials Constraints []string `json:"constraints,omitempty"`
// (see ~/.docker/config.json)
RegistryAuth string `json:"registryAuth,omitempty"`
// Constraints are specific to back-end orchestration platform
Constraints []string `json:"constraints"`
// Secrets list of secrets to be made available to function // Secrets list of secrets to be made available to function
Secrets []string `json:"secrets"` Secrets []string `json:"secrets,omitempty"`
// Labels are metadata for functions which may be used by the // Labels are metadata for functions which may be used by the
// back-end for making scheduling or routing decisions // faas-provider or the gateway
Labels *map[string]string `json:"labels"` Labels *map[string]string `json:"labels,omitempty"`
// Annotations are metadata for functions which may be used by the // Annotations are metadata for functions which may be used by the
// back-end for management, orchestration, events and build tasks // faas-provider or the gateway
Annotations *map[string]string `json:"annotations"` Annotations *map[string]string `json:"annotations,omitempty"`
// Limits for function // Limits for function
Limits *FunctionResources `json:"limits"` Limits *FunctionResources `json:"limits,omitempty"`
// Requests of resources requested by function // Requests of resources requested by function
Requests *FunctionResources `json:"requests"` Requests *FunctionResources `json:"requests,omitempty"`
// ReadOnlyRootFilesystem removes write-access from the root filesystem // ReadOnlyRootFilesystem removes write-access from the root filesystem
// mount-point. // mount-point.
ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem"` ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem,omitempty"`
// Namespace for the function to be deployed into
Namespace string `json:"namespace,omitempty"`
}
// FunctionResources Memory and CPU
type FunctionResources struct {
Memory string `json:"memory"`
CPU string `json:"cpu"`
}
// FunctionStatus exported for system/functions endpoint
type FunctionStatus struct {
// Name corresponds to a Service
Name string `json:"name"`
// Image corresponds to a Docker image
Image string `json:"image"`
// InvocationCount count of invocations
InvocationCount float64 `json:"invocationCount"`
// Replicas desired within the cluster
Replicas uint64 `json:"replicas"`
// EnvProcess is the process to pass to the watchdog, if in use
EnvProcess string `json:"envProcess"`
// AvailableReplicas is the count of replicas ready to receive
// invocations as reported by the backend
AvailableReplicas uint64 `json:"availableReplicas"`
// Labels are metadata for functions which may be used by the
// backend for making scheduling or routing decisions
Labels *map[string]string `json:"labels"`
// Annotations are metadata for functions which may be used by the
// backend for management, orchestration, events and build tasks
Annotations *map[string]string `json:"annotations"`
// Namespace where the function can be accessed
Namespace string `json:"namespace,omitempty"`
} }
// Secret for underlying orchestrator // Secret for underlying orchestrator
@ -97,3 +50,65 @@ type Secret struct {
Namespace string `json:"namespace,omitempty"` Namespace string `json:"namespace,omitempty"`
Value string `json:"value,omitempty"` Value string `json:"value,omitempty"`
} }
// FunctionResources Memory and CPU
type FunctionResources struct {
Memory string `json:"memory,omitempty"`
CPU string `json:"cpu,omitempty"`
}
// FunctionStatus exported for system/functions endpoint
type FunctionStatus struct {
// Name is the name of the function deployment
Name string `json:"name"`
// Image is a fully-qualified container image
Image string `json:"image"`
// Namespace for the function, if supported by the faas-provider
Namespace string `json:"namespace,omitempty"`
// EnvProcess overrides the fprocess environment variable and can be used
// with the watchdog
EnvProcess string `json:"envProcess,omitempty"`
// EnvVars set environment variables for the function runtime
EnvVars map[string]string `json:"envVars,omitempty"`
// Constraints are specific to the faas-provider
Constraints []string `json:"constraints,omitempty"`
// Secrets list of secrets to be made available to function
Secrets []string `json:"secrets,omitempty"`
// Labels are metadata for functions which may be used by the
// faas-provider or the gateway
Labels *map[string]string `json:"labels,omitempty"`
// Annotations are metadata for functions which may be used by the
// faas-provider or the gateway
Annotations *map[string]string `json:"annotations,omitempty"`
// Limits for function
Limits *FunctionResources `json:"limits,omitempty"`
// Requests of resources requested by function
Requests *FunctionResources `json:"requests,omitempty"`
// ReadOnlyRootFilesystem removes write-access from the root filesystem
// mount-point.
ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem,omitempty"`
// ** Status fields *8
// InvocationCount count of invocations
InvocationCount float64 `json:"invocationCount,omitempty"`
// Replicas desired within the cluster
Replicas uint64 `json:"replicas,omitempty"`
// AvailableReplicas is the count of replicas ready to receive
// invocations as reported by the faas-provider
AvailableReplicas uint64 `json:"availableReplicas,omitempty"`
}

View File

@ -9,20 +9,21 @@ type ScaleServiceRequest struct {
Replicas uint64 `json:"replicas"` Replicas uint64 `json:"replicas"`
} }
// InfoResponse provides information about the underlying provider
type InfoResponse struct {
Provider string `json:"provider"`
Version ProviderVersion `json:"version"`
Orchestration string `json:"orchestration"`
}
// ProviderVersion provides the commit sha and release version number of the underlying provider
type ProviderVersion struct {
SHA string `json:"sha"`
Release string `json:"release"`
}
// DeleteFunctionRequest delete a deployed function // DeleteFunctionRequest delete a deployed function
type DeleteFunctionRequest struct { type DeleteFunctionRequest struct {
FunctionName string `json:"functionName"` FunctionName string `json:"functionName"`
} }
// ProviderInfo provides information about the configured provider
type ProviderInfo struct {
Name string `json:"provider"`
Version *VersionInfo `json:"version"`
Orchestration string `json:"orchestration"`
}
// VersionInfo provides the commit message, sha and release version number
type VersionInfo struct {
CommitMessage string `json:"commit_message,omitempty"`
SHA string `json:"sha"`
Release string `json:"release"`
}

2
vendor/modules.txt generated vendored
View File

@ -167,7 +167,7 @@ github.com/opencontainers/runc/libcontainer/user
github.com/opencontainers/runtime-spec/specs-go github.com/opencontainers/runtime-spec/specs-go
# github.com/openfaas/faas v0.0.0-20201205125747-9bbb25e3c7c4 # github.com/openfaas/faas v0.0.0-20201205125747-9bbb25e3c7c4
github.com/openfaas/faas/gateway/requests github.com/openfaas/faas/gateway/requests
# github.com/openfaas/faas-provider v0.15.3 # github.com/openfaas/faas-provider v0.16.2
github.com/openfaas/faas-provider github.com/openfaas/faas-provider
github.com/openfaas/faas-provider/auth github.com/openfaas/faas-provider/auth
github.com/openfaas/faas-provider/httputil github.com/openfaas/faas-provider/httputil