Compare commits

...

6 Commits
0.6.2 ... 0.7.0

Author SHA1 Message Date
5c48ac1a70 Add secrets support
Adds secrets support and binding of secrets at runtime to
functions. Files are written in plain-text to a 0644 permission
folder which can only be read by root and the containers
requesting the secret through the OpenFaaS API.

Tested by deploying an alpine function using "cat" as its
fprocess.

Happy to revisit at a later date and look into encryption at
rest. This should be on-par with using Kubernetes in its
default unencrypted state.

Fixes: #29

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-01-28 11:41:54 +00:00
7c166979c9 Update README.md 2020-01-27 09:01:23 +00:00
36843ad1d4 Update README.md 2020-01-26 21:23:12 +00:00
3bc041ba04 Update README.md 2020-01-26 21:19:53 +00:00
dd3f9732b4 Update to latest version of faasd 2020-01-26 21:18:54 +00:00
6c10d18f59 Bump release of faasd for cloud-config example
This version includes a fix for long-running task deletions

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-01-26 21:08:29 +00:00
7 changed files with 177 additions and 21 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ hosts
basic-auth-user
basic-auth-password
/bin
/secrets

View File

@ -1,6 +1,8 @@
# faasd - serverless with containerd
[![Build Status](https://travis-ci.com/alexellis/faasd.svg?branch=master)](https://travis-ci.com/alexellis/faasd)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![OpenFaaS](https://img.shields.io/badge/openfaas-serverless-blue.svg)](https://www.openfaas.com)
faasd is a Golang supervisor that bundles OpenFaaS for use with containerd instead of a container orchestrator like Kubernetes or Docker Swarm.
@ -10,6 +12,10 @@ faasd is a Golang supervisor that bundles OpenFaaS for use with containerd inste
* faasd is multi-arch, so works on `x86_64`, armhf and arm64
* faasd downloads, starts and supervises the core components to run OpenFaaS
![demo](https://pbs.twimg.com/media/EPNQz00W4AEwDxM?format=jpg&name=small)
> Demo of faasd running in KVM
## What does faasd deploy?
* faasd - itself, and its [faas-provider](https://github.com/openfaas/faas-provider)
@ -60,6 +66,8 @@ Other operations are pending development in the provider such as:
Pending:
* [ ] Add support for using container images in third-party public registries
* [ ] Add support for using container images in private third-party registries
* [ ] Monitor and restart any of the core components at runtime if the container stops
* [ ] Bundle/package/automate installation of containerd - [see bootstrap from k3s](https://github.com/rancher/k3s)
* [ ] Provide ufw rules / example for blocking access to everything but a reverse proxy to the gateway container
@ -84,7 +92,13 @@ You can run this tutorial on your Raspberry Pi, or adapt the steps for a regular
* [faasd - lightweight Serverless for your Raspberry Pi](https://blog.alexellis.io/faasd-for-lightweight-serverless/)
## Get containerd
## Tutorial: Multipass & KVM for MacOS/Linux, or Windows (with cloud-config)
* [Get up and running with your own faasd installation on your Mac/Ubuntu or Windows with cloud-config](https://gist.github.com/alexellis/6d297e678c9243d326c151028a3ad7b9)
## Tutorial: Manual installation
### Get containerd
You have three options - binaries for PC, binaries for armhf, or build from source.
@ -136,7 +150,7 @@ Start containerd in a new terminal:
```sh
sudo containerd &
```
### Enable forwarding
#### Enable forwarding
> This is required to allow containers in containerd to access the Internet via your computer's primary network interface.
@ -150,9 +164,9 @@ Make the setting permanent:
echo "net.ipv4.conf.all.forwarding=1" | sudo tee -a /etc/sysctl.conf
```
## Hacking (build from source)
### Hacking (build from source)
### Get build packages
#### Get build packages
```sh
sudo apt update \
@ -163,7 +177,7 @@ sudo apt update \
You may find alternatives for CentOS and other distributions.
### Install Go 1.13 (x86_64)
#### Install Go 1.13 (x86_64)
```sh
curl -sSLf https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz > go.tgz
@ -177,7 +191,7 @@ export PATH=$PATH:/usr/local/go/bin/
go version
```
### Or on Raspberry Pi (armhf)
#### Or on Raspberry Pi (armhf)
```sh
curl -SLsf https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz > go.tgz
@ -191,7 +205,7 @@ export PATH=$PATH:/usr/local/go/bin/
go version
```
### Install the CNI plugins:
#### Install the CNI plugins:
* For PC run `export ARCH=amd64`
* For RPi/armhf run `export ARCH=arm`
@ -220,26 +234,26 @@ go build
# sudo ./faasd up
```
### Build and run `faasd` (binaries)
#### Build and run `faasd` (binaries)
```sh
# For x86_64
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.4.4/faasd" \
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.6.2/faasd" \
-o "/usr/local/bin/faasd" \
&& sudo chmod a+x "/usr/local/bin/faasd"
# armhf
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.4.4/faasd-armhf" \
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.6.2/faasd-armhf" \
-o "/usr/local/bin/faasd" \
&& sudo chmod a+x "/usr/local/bin/faasd"
# arm64
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.4.4/faasd-arm64" \
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.6.2/faasd-arm64" \
-o "/usr/local/bin/faasd" \
&& sudo chmod a+x "/usr/local/bin/faasd"
```
### At run-time
#### At run-time
Look in `hosts` in the current working folder or in `/var/lib/faasd/` to get the IP for the gateway or Prometheus

View File

@ -16,7 +16,7 @@ runcmd:
- curl -sSL https://github.com/containernetworking/plugins/releases/download/v0.8.5/cni-plugins-linux-amd64-v0.8.5.tgz | tar -xz -C /opt/cni/bin
- mkdir -p /go/src/github.com/alexellis/
- cd /go/src/github.com/alexellis/ && git clone https://github.com/alexellis/faasd
- curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.6.1/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
- curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.6.2/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
- cd /go/src/github.com/alexellis/faasd/ && /usr/local/bin/faasd install
- systemctl status -l containerd --no-pager
- journalctl -u faasd-provider --no-pager

View File

@ -66,17 +66,20 @@ func runProvider(_ *cobra.Command, _ []string) error {
invokeResolver := handlers.NewInvokeResolver(client)
userSecretPath := path.Join(wd, "secrets")
bootstrapHandlers := types.FaaSHandlers{
FunctionProxy: proxy.NewHandlerFunc(*config, invokeResolver),
DeleteHandler: handlers.MakeDeleteHandler(client, cni),
DeployHandler: handlers.MakeDeployHandler(client, cni),
DeployHandler: handlers.MakeDeployHandler(client, cni, userSecretPath),
FunctionReader: handlers.MakeReadHandler(client),
ReplicaReader: handlers.MakeReplicaReaderHandler(client),
ReplicaUpdater: handlers.MakeReplicaUpdateHandler(client, cni),
UpdateHandler: handlers.MakeUpdateHandler(client, cni),
UpdateHandler: handlers.MakeUpdateHandler(client, cni, userSecretPath),
HealthHandler: func(w http.ResponseWriter, r *http.Request) {},
InfoHandler: handlers.MakeInfoHandler(Version, GitCommit),
ListNamespaceHandler: listNamespaces(),
SecretHandler: handlers.MakeSecretHandler(client, userSecretPath),
}
log.Printf("Listening on TCP port: %d\n", *config.TCPPort)

View File

@ -22,7 +22,7 @@ import (
"github.com/pkg/errors"
)
func MakeDeployHandler(client *containerd.Client, cni gocni.CNI) func(w http.ResponseWriter, r *http.Request) {
func MakeDeployHandler(client *containerd.Client, cni gocni.CNI, secretMountPath string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
@ -45,11 +45,15 @@ func MakeDeployHandler(client *containerd.Client, cni gocni.CNI) func(w http.Res
return
}
name := req.Service
err = validateSecrets(secretMountPath, req.Secrets)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
name := req.Service
ctx := namespaces.WithNamespace(context.Background(), FunctionNamespace)
deployErr := deploy(ctx, req, client, cni)
deployErr := deploy(ctx, req, client, cni, secretMountPath)
if deployErr != nil {
log.Printf("[Deploy] error deploying %s, error: %s\n", name, deployErr)
http.Error(w, deployErr.Error(), http.StatusBadRequest)
@ -58,7 +62,7 @@ func MakeDeployHandler(client *containerd.Client, cni gocni.CNI) func(w http.Res
}
}
func deploy(ctx context.Context, req types.FunctionDeployment, client *containerd.Client, cni gocni.CNI) error {
func deploy(ctx context.Context, req types.FunctionDeployment, client *containerd.Client, cni gocni.CNI, secretMountPath string) error {
imgRef := "docker.io/" + req.Image
if strings.Index(req.Image, ":") == -1 {
@ -81,6 +85,15 @@ func deploy(ctx context.Context, req types.FunctionDeployment, client *container
envs := prepareEnv(req.EnvProcess, req.EnvVars)
mounts := getMounts()
for _, secret := range req.Secrets {
mounts = append(mounts, specs.Mount{
Destination: path.Join("/var/openfaas/secrets", secret),
Type: "bind",
Source: path.Join(secretMountPath, secret),
Options: []string{"rbind", "ro"},
})
}
name := req.Service
container, err := client.NewContainer(
@ -177,3 +190,12 @@ func getMounts() []specs.Mount {
})
return mounts
}
func validateSecrets(secretMountPath string, secrets []string) error {
for _, secret := range secrets {
if _, err := os.Stat(path.Join(secretMountPath, secret)); err != nil {
return fmt.Errorf("unable to find secret: %s", secret)
}
}
return nil
}

View File

@ -0,0 +1,111 @@
package handlers
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"github.com/containerd/containerd"
"github.com/openfaas/faas-provider/types"
)
const secretFilePermission = 0644
func MakeSecretHandler(c *containerd.Client, mountPath string) func(w http.ResponseWriter, r *http.Request) {
err := os.MkdirAll(mountPath, secretFilePermission)
if err != nil {
log.Printf("Creating path: %s, error: %s\n", mountPath, err)
}
return func(w http.ResponseWriter, r *http.Request) {
if r.Body != nil {
defer r.Body.Close()
}
switch r.Method {
case http.MethodGet:
listSecrets(c, w, r, mountPath)
case http.MethodPost:
createSecret(c, w, r, mountPath)
case http.MethodPut:
createSecret(c, w, r, mountPath)
case http.MethodDelete:
deleteSecret(c, w, r, mountPath)
default:
w.WriteHeader(http.StatusBadRequest)
return
}
}
}
func listSecrets(c *containerd.Client, w http.ResponseWriter, r *http.Request, mountPath string) {
files, err := ioutil.ReadDir(mountPath)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
secrets := []types.Secret{}
for _, f := range files {
secrets = append(secrets, types.Secret{Name: f.Name()})
}
bytesOut, _ := json.Marshal(secrets)
w.Write(bytesOut)
}
func createSecret(c *containerd.Client, w http.ResponseWriter, r *http.Request, mountPath string) {
secret, err := parseSecret(r)
if err != nil {
log.Printf("[secret] error %s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = ioutil.WriteFile(path.Join(mountPath, secret.Name), []byte(secret.Value), secretFilePermission)
if err != nil {
log.Printf("[secret] error %s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func parseSecret(r *http.Request) (types.Secret, error) {
secret := types.Secret{}
bytesOut, err := ioutil.ReadAll(r.Body)
if err != nil {
return secret, err
}
err = json.Unmarshal(bytesOut, &secret)
return secret, err
}
func deleteSecret(c *containerd.Client, w http.ResponseWriter, r *http.Request, mountPath string) {
secret, err := parseSecret(r)
if err != nil {
log.Printf("[secret] error %s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err != nil {
log.Printf("[secret] error %s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = os.Remove(path.Join(mountPath, secret.Name))
if err != nil {
log.Printf("[secret] error %s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

View File

@ -15,7 +15,7 @@ import (
"github.com/openfaas/faas-provider/types"
)
func MakeUpdateHandler(client *containerd.Client, cni gocni.CNI) func(w http.ResponseWriter, r *http.Request) {
func MakeUpdateHandler(client *containerd.Client, cni gocni.CNI, secretMountPath string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
@ -47,6 +47,11 @@ func MakeUpdateHandler(client *containerd.Client, cni gocni.CNI) func(w http.Res
return
}
err = validateSecrets(secretMountPath, req.Secrets)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
ctx := namespaces.WithNamespace(context.Background(), FunctionNamespace)
if function.replicas != 0 {
err = DeleteCNINetwork(ctx, cni, client, name)
@ -62,7 +67,7 @@ func MakeUpdateHandler(client *containerd.Client, cni gocni.CNI) func(w http.Res
return
}
deployErr := deploy(ctx, req, client, cni)
deployErr := deploy(ctx, req, client, cni, secretMountPath)
if deployErr != nil {
log.Printf("[Update] error deploying %s, error: %s\n", name, deployErr)
http.Error(w, deployErr.Error(), http.StatusBadRequest)