mirror of
https://github.com/openfaas/faasd.git
synced 2025-06-26 00:33:24 +00:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
d64edeb648 | |||
42b9cc6b71 | |||
25c553a87c | |||
8bc39f752e | |||
cbff6fa8f6 | |||
3e29408518 | |||
04f1807d92 | |||
35e017b526 | |||
e54da61283 | |||
84353d0cae | |||
e33a60862d | |||
7b67ff22e6 | |||
19abc9f7b9 | |||
480f566819 |
@ -10,6 +10,7 @@ addons:
|
|||||||
- runc
|
- runc
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
- make test
|
||||||
- make dist
|
- make dist
|
||||||
- make prepare-test
|
- make prepare-test
|
||||||
- make test-e2e
|
- make test-e2e
|
||||||
|
4
Makefile
4
Makefile
@ -11,6 +11,10 @@ all: local
|
|||||||
local:
|
local:
|
||||||
CGO_ENABLED=0 GOOS=linux go build -o bin/faasd
|
CGO_ENABLED=0 GOOS=linux go build -o bin/faasd
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
CGO_ENABLED=0 GOOS=linux go test -ldflags $(LDFLAGS) ./...
|
||||||
|
|
||||||
.PHONY: dist
|
.PHONY: dist
|
||||||
dist:
|
dist:
|
||||||
CGO_ENABLED=0 GOOS=linux go build -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd
|
CGO_ENABLED=0 GOOS=linux go build -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
# faasd - serverless with containerd and CNI 🐳
|
# faasd - lightweight OSS serverless 🐳
|
||||||
|
|
||||||
[](https://travis-ci.com/openfaas/faasd)
|
[](https://travis-ci.com/openfaas/faasd)
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
[](https://www.openfaas.com)
|
[](https://www.openfaas.com)
|
||||||
|

|
||||||
|
|
||||||
faasd is the same OpenFaaS experience and ecosystem, but without Kubernetes. Functions and microservices can be deployed anywhere with reduced overheads whilst retaining the portability of containers and cloud-native tooling.
|
faasd is the same OpenFaaS experience and ecosystem, but without Kubernetes. Functions and microservices can be deployed anywhere with reduced overheads whilst retaining the portability of containers and cloud-native tooling such as containerd and CNI.
|
||||||
|
|
||||||
## About faasd
|
## About faasd
|
||||||
|
|
||||||
@ -55,7 +56,9 @@ You can run this tutorial on your Raspberry Pi, or adapt the steps for a regular
|
|||||||
|
|
||||||
Automate everything within < 60 seconds and get a public URL and IP address back. Customise as required, or adapt to your preferred cloud such as AWS EC2.
|
Automate everything within < 60 seconds and get a public URL and IP address back. Customise as required, or adapt to your preferred cloud such as AWS EC2.
|
||||||
|
|
||||||
* [Provision faasd 0.7.5 on DigitalOcean with Terraform 0.12.0](https://gist.github.com/alexellis/fd618bd2f957eb08c44d086ef2fc3906)
|
* [Provision faasd 0.8.1 on DigitalOcean with Terraform 0.12.0](docs/bootstrap/README.md)
|
||||||
|
|
||||||
|
* [Provision faasd on DigitalOcean with built-in TLS support](docs/bootstrap/digitalocean-terraform/README.md)
|
||||||
|
|
||||||
### A note on private repos / registries
|
### A note on private repos / registries
|
||||||
|
|
||||||
|
@ -17,7 +17,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
|
- 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/openfaas/
|
- mkdir -p /go/src/github.com/openfaas/
|
||||||
- cd /go/src/github.com/openfaas/ && git clone https://github.com/openfaas/faasd
|
- cd /go/src/github.com/openfaas/ && git clone https://github.com/openfaas/faasd
|
||||||
- curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.7.7/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
|
- curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.8.2/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
|
||||||
- cd /go/src/github.com/openfaas/faasd/ && /usr/local/bin/faasd install
|
- cd /go/src/github.com/openfaas/faasd/ && /usr/local/bin/faasd install
|
||||||
- systemctl status -l containerd --no-pager
|
- systemctl status -l containerd --no-pager
|
||||||
- journalctl -u faasd-provider --no-pager
|
- journalctl -u faasd-provider --no-pager
|
||||||
|
@ -175,7 +175,7 @@ func makeServiceDefinitions(archSuffix string) []pkg.Service {
|
|||||||
return []pkg.Service{
|
return []pkg.Service{
|
||||||
{
|
{
|
||||||
Name: "basic-auth-plugin",
|
Name: "basic-auth-plugin",
|
||||||
Image: "docker.io/openfaas/basic-auth-plugin:0.18.10" + archSuffix,
|
Image: "docker.io/openfaas/basic-auth-plugin:0.18.17" + archSuffix,
|
||||||
Env: []string{
|
Env: []string{
|
||||||
"port=8080",
|
"port=8080",
|
||||||
"secret_mount_path=" + containerSecretMountDir,
|
"secret_mount_path=" + containerSecretMountDir,
|
||||||
@ -230,7 +230,7 @@ func makeServiceDefinitions(archSuffix string) []pkg.Service {
|
|||||||
"secret_mount_path=" + containerSecretMountDir,
|
"secret_mount_path=" + containerSecretMountDir,
|
||||||
"scale_from_zero=true",
|
"scale_from_zero=true",
|
||||||
},
|
},
|
||||||
Image: "docker.io/openfaas/gateway:0.18.8" + archSuffix,
|
Image: "docker.io/openfaas/gateway:0.18.17" + archSuffix,
|
||||||
Mounts: []pkg.Mount{
|
Mounts: []pkg.Mount{
|
||||||
{
|
{
|
||||||
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-password"),
|
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-password"),
|
||||||
@ -256,7 +256,7 @@ func makeServiceDefinitions(archSuffix string) []pkg.Service {
|
|||||||
"basic_auth=true",
|
"basic_auth=true",
|
||||||
"secret_mount_path=" + containerSecretMountDir,
|
"secret_mount_path=" + containerSecretMountDir,
|
||||||
},
|
},
|
||||||
Image: "docker.io/openfaas/queue-worker:0.9.0",
|
Image: "docker.io/openfaas/queue-worker:0.11.2",
|
||||||
Mounts: []pkg.Mount{
|
Mounts: []pkg.Mount{
|
||||||
{
|
{
|
||||||
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-password"),
|
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-password"),
|
||||||
|
18
docs/DEV.md
18
docs/DEV.md
@ -174,19 +174,19 @@ make local
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# For x86_64
|
# For x86_64
|
||||||
sudo curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.8.0/faasd" \
|
export SUFFIX=""
|
||||||
-o "/usr/local/bin/faasd" \
|
|
||||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
|
||||||
|
|
||||||
# armhf
|
# armhf
|
||||||
sudo curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.8.0/faasd-armhf" \
|
export SUFFIX="-armhf"
|
||||||
-o "/usr/local/bin/faasd" \
|
|
||||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
|
||||||
|
|
||||||
# arm64
|
# arm64
|
||||||
sudo curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.8.0/faasd-arm64" \
|
export SUFFIX="-arm64"
|
||||||
-o "/usr/local/bin/faasd" \
|
|
||||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
# Then download
|
||||||
|
curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.8.2/faasd$SUFFIX" \
|
||||||
|
-o "/tmp/faasd" \
|
||||||
|
&& chmod +x "/tmp/faasd"
|
||||||
|
sudo mv /tmp/faasd /usr/local/bin/
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Install `faasd`
|
#### Install `faasd`
|
||||||
|
20
docs/bootstrap/README.md
Normal file
20
docs/bootstrap/README.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Bootstrap faasd on Digitalocean
|
||||||
|
|
||||||
|
1) [Sign up to DigitalOcean](https://www.digitalocean.com/?refcode=2962aa9e56a1&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=CopyPaste)
|
||||||
|
2) [Download Terraform](https://www.terraform.io)
|
||||||
|
3) Clone this gist using the URL from the address bar
|
||||||
|
4) Run `terraform init`
|
||||||
|
5) Run `terraform apply -var="do_token=$(cat $HOME/digitalocean-access-token)"`
|
||||||
|
6) View the output for the login command and gateway URL i.e.
|
||||||
|
|
||||||
|
```
|
||||||
|
gateway_url = http://178.128.39.201:8080/
|
||||||
|
login_cmd = faas-cli login -g http://178.128.39.201:8080/ -p rvIU49CEcFcHmqxj
|
||||||
|
password = rvIU49CEcFcHmqxj
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the user-data may take a couple of minutes to come up since it will be pulling in various components and preparing the machine.
|
||||||
|
|
||||||
|
A single host with 1GB of RAM will be deployed for you, to remove at a later date simply use `terraform destroy`.
|
||||||
|
|
||||||
|
If required, you can remove the VM via `terraform destroy -var="do_token=$(cat $HOME/digitalocean-access-token)"`
|
29
docs/bootstrap/cloud-config.tpl
Normal file
29
docs/bootstrap/cloud-config.tpl
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#cloud-config
|
||||||
|
ssh_authorized_keys:
|
||||||
|
- ${ssh_key}
|
||||||
|
|
||||||
|
package_update: true
|
||||||
|
|
||||||
|
packages:
|
||||||
|
- runc
|
||||||
|
|
||||||
|
runcmd:
|
||||||
|
- curl -sLSf https://github.com/containerd/containerd/releases/download/v1.3.2/containerd-1.3.2.linux-amd64.tar.gz > /tmp/containerd.tar.gz && tar -xvf /tmp/containerd.tar.gz -C /usr/local/bin/ --strip-components=1
|
||||||
|
- curl -SLfs https://raw.githubusercontent.com/containerd/containerd/v1.3.2/containerd.service | tee /etc/systemd/system/containerd.service
|
||||||
|
- systemctl daemon-reload && systemctl start containerd
|
||||||
|
- /sbin/sysctl -w net.ipv4.conf.all.forwarding=1
|
||||||
|
- mkdir -p /opt/cni/bin
|
||||||
|
- 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/openfaas/
|
||||||
|
- mkdir -p /var/lib/faasd/secrets/
|
||||||
|
- echo ${gw_password} > /var/lib/faasd/secrets/basic-auth-password
|
||||||
|
- echo admin > /var/lib/faasd/secrets/basic-auth-user
|
||||||
|
- cd /go/src/github.com/openfaas/ && git clone https://github.com/openfaas/faasd
|
||||||
|
- curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.8.1/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
|
||||||
|
- cd /go/src/github.com/openfaas/faasd/ && /usr/local/bin/faasd install
|
||||||
|
- systemctl status -l containerd --no-pager
|
||||||
|
- journalctl -u faasd-provider --no-pager
|
||||||
|
- systemctl status -l faasd-provider --no-pager
|
||||||
|
- systemctl status -l faasd --no-pager
|
||||||
|
- curl -sSLf https://cli.openfaas.com | sh
|
||||||
|
- sleep 5 && journalctl -u faasd --no-pager
|
37
docs/bootstrap/digitalocean-terraform/README.md
Normal file
37
docs/bootstrap/digitalocean-terraform/README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Bootstrap faasd with TLS support on Digitalocean
|
||||||
|
|
||||||
|
1) [Sign up to DigitalOcean](https://www.digitalocean.com/?refcode=2962aa9e56a1&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=CopyPaste)
|
||||||
|
2) [Download Terraform](https://www.terraform.io)
|
||||||
|
3) Clone this gist using the URL from the address bar
|
||||||
|
4) Run `terraform init`
|
||||||
|
5) Configure terraform variables as needed by updating the `main.tfvars` file:
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
| ------------ | ------------------- | --------------- |
|
||||||
|
| `do_token` | Digitalocean API token | None |
|
||||||
|
| `do_domain` | Public domain used for the faasd gateway | None |
|
||||||
|
| `letsencrypt_email` | Email used by when ordering TLS certificate from Letsencrypt | `""` |
|
||||||
|
| `do_create_record` | When set to `true`, a new DNS record will be created. This works only if your domain (`do_domain`) is managed by Digitalocean | `false` |
|
||||||
|
| `do_region` | Digitalocean region for creating the droplet | `fra1` |
|
||||||
|
| `ssh_key_file` | Path to public SSH key file |`~/.ssh/id_rsa.pub` |
|
||||||
|
|
||||||
|
> Environment variables can also be used to set terraform variables when running the `terraform apply` command using the format `TF_VAR_name`.
|
||||||
|
|
||||||
|
6) Run `terraform apply`
|
||||||
|
1) Add `-var-file=main.tfvars` if you have set the variables in `main.tfvars`.
|
||||||
|
2) OR [use environment variables](https://www.terraform.io/docs/commands/environment-variables.html#tf_var_name) for setting the terraform variables when running the `apply` command
|
||||||
|
|
||||||
|
7) View the output for the login command and gateway URL i.e.
|
||||||
|
|
||||||
|
```
|
||||||
|
droplet_ip = 178.128.39.201
|
||||||
|
gateway_url = https://faasd.example.com/
|
||||||
|
login_cmd = faas-cli login -g https://faasd.example.com/ -p rvIU49CEcFcHmqxj
|
||||||
|
password = rvIU49CEcFcHmqxj
|
||||||
|
```
|
||||||
|
8) Use your browser to access the OpenFaaS interface
|
||||||
|
|
||||||
|
Note that the user-data may take a couple of minutes to come up since it will be pulling in various components and preparing the machine.
|
||||||
|
Also take into consideration the DNS propagation time for the new DNS record.
|
||||||
|
|
||||||
|
A single host with 1GB of RAM will be deployed for you, to remove at a later date simply use `terraform destroy`.
|
57
docs/bootstrap/digitalocean-terraform/cloud-config.tpl
Normal file
57
docs/bootstrap/digitalocean-terraform/cloud-config.tpl
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#cloud-config
|
||||||
|
ssh_authorized_keys:
|
||||||
|
- ${ssh_key}
|
||||||
|
|
||||||
|
groups:
|
||||||
|
- caddy
|
||||||
|
|
||||||
|
users:
|
||||||
|
- name: caddy
|
||||||
|
gecos: Caddy web server
|
||||||
|
primary_group: caddy
|
||||||
|
groups: caddy
|
||||||
|
shell: /usr/sbin/nologin
|
||||||
|
homedir: /var/lib/caddy
|
||||||
|
|
||||||
|
write_files:
|
||||||
|
- content: |
|
||||||
|
{
|
||||||
|
email ${letsencrypt_email}
|
||||||
|
}
|
||||||
|
|
||||||
|
${faasd_domain_name} {
|
||||||
|
reverse_proxy 127.0.0.1:8080
|
||||||
|
}
|
||||||
|
|
||||||
|
path: /etc/caddy/Caddyfile
|
||||||
|
|
||||||
|
package_update: true
|
||||||
|
|
||||||
|
packages:
|
||||||
|
- runc
|
||||||
|
|
||||||
|
runcmd:
|
||||||
|
- curl -sLSf https://github.com/containerd/containerd/releases/download/v1.3.2/containerd-1.3.2.linux-amd64.tar.gz > /tmp/containerd.tar.gz && tar -xvf /tmp/containerd.tar.gz -C /usr/local/bin/ --strip-components=1
|
||||||
|
- curl -SLfs https://raw.githubusercontent.com/containerd/containerd/v1.3.2/containerd.service | tee /etc/systemd/system/containerd.service
|
||||||
|
- systemctl daemon-reload && systemctl start containerd
|
||||||
|
- /sbin/sysctl -w net.ipv4.conf.all.forwarding=1
|
||||||
|
- mkdir -p /opt/cni/bin
|
||||||
|
- 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/openfaas/
|
||||||
|
- mkdir -p /var/lib/faasd/secrets/
|
||||||
|
- echo ${gw_password} > /var/lib/faasd/secrets/basic-auth-password
|
||||||
|
- echo admin > /var/lib/faasd/secrets/basic-auth-user
|
||||||
|
- cd /go/src/github.com/openfaas/ && git clone https://github.com/openfaas/faasd
|
||||||
|
- curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.8.1/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
|
||||||
|
- cd /go/src/github.com/openfaas/faasd/ && /usr/local/bin/faasd install
|
||||||
|
- systemctl status -l containerd --no-pager
|
||||||
|
- journalctl -u faasd-provider --no-pager
|
||||||
|
- systemctl status -l faasd-provider --no-pager
|
||||||
|
- systemctl status -l faasd --no-pager
|
||||||
|
- curl -sSLf https://cli.openfaas.com | sh
|
||||||
|
- sleep 5 && journalctl -u faasd --no-pager
|
||||||
|
- wget https://github.com/caddyserver/caddy/releases/download/v2.0.0-rc.2/caddy_2.0.0-rc.2_linux_amd64.tar.gz -O /tmp/caddy.tar.gz && tar -zxvf /tmp/caddy.tar.gz -C /usr/bin/ caddy
|
||||||
|
- wget https://raw.githubusercontent.com/caddyserver/dist/master/init/caddy.service -O /etc/systemd/system/caddy.service
|
||||||
|
- systemctl daemon-reload
|
||||||
|
- systemctl enable caddy
|
||||||
|
- systemctl start caddy
|
82
docs/bootstrap/digitalocean-terraform/main.tf
Normal file
82
docs/bootstrap/digitalocean-terraform/main.tf
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 0.12"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "do_token" {
|
||||||
|
description = "Digitalocean API token"
|
||||||
|
}
|
||||||
|
variable "do_domain" {
|
||||||
|
description = "Your public domain"
|
||||||
|
}
|
||||||
|
variable "letsencrypt_email" {
|
||||||
|
description = "Email used to order a certificate from Letsencrypt"
|
||||||
|
}
|
||||||
|
variable "do_create_record" {
|
||||||
|
default = false
|
||||||
|
description = "Whether to create a DNS record on Digitalocean"
|
||||||
|
}
|
||||||
|
variable "do_region" {
|
||||||
|
default = "fra1"
|
||||||
|
description = "The Digitalocean region where the faasd droplet will be created."
|
||||||
|
}
|
||||||
|
variable "ssh_key_file" {
|
||||||
|
default = "~/.ssh/id_rsa.pub"
|
||||||
|
description = "Path to the SSH public key file"
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "digitalocean" {
|
||||||
|
token = var.do_token
|
||||||
|
}
|
||||||
|
|
||||||
|
data "local_file" "ssh_key"{
|
||||||
|
filename = pathexpand(var.ssh_key_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "random_password" "password" {
|
||||||
|
length = 16
|
||||||
|
special = true
|
||||||
|
override_special = "_-#"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "template_file" "cloud_init" {
|
||||||
|
template = "${file("cloud-config.tpl")}"
|
||||||
|
vars = {
|
||||||
|
gw_password=random_password.password.result,
|
||||||
|
ssh_key=data.local_file.ssh_key.content,
|
||||||
|
faasd_domain_name="faasd.${var.do_domain}"
|
||||||
|
letsencrypt_email=var.letsencrypt_email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "digitalocean_droplet" "faasd" {
|
||||||
|
region = var.do_region
|
||||||
|
image = "ubuntu-18-04-x64"
|
||||||
|
name = "faasd"
|
||||||
|
size = "s-1vcpu-1gb"
|
||||||
|
user_data = data.template_file.cloud_init.rendered
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "digitalocean_record" "faasd" {
|
||||||
|
domain = var.do_domain
|
||||||
|
type = "A"
|
||||||
|
name = "faasd"
|
||||||
|
value = digitalocean_droplet.faasd.ipv4_address
|
||||||
|
# Only creates record if do_create_record is true
|
||||||
|
count = var.do_create_record == true ? 1 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
output "droplet_ip" {
|
||||||
|
value = digitalocean_droplet.faasd.ipv4_address
|
||||||
|
}
|
||||||
|
|
||||||
|
output "gateway_url" {
|
||||||
|
value = "https://faasd.${var.do_domain}/"
|
||||||
|
}
|
||||||
|
|
||||||
|
output "password" {
|
||||||
|
value = random_password.password.result
|
||||||
|
}
|
||||||
|
|
||||||
|
output "login_cmd" {
|
||||||
|
value = "faas-cli login -g https://faasd.${var.do_domain}/ -p ${random_password.password.result}"
|
||||||
|
}
|
3
docs/bootstrap/digitalocean-terraform/main.tfvars
Normal file
3
docs/bootstrap/digitalocean-terraform/main.tfvars
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
do_token = ""
|
||||||
|
do_domain = ""
|
||||||
|
letsencrypt_email = ""
|
56
docs/bootstrap/main.tf
Normal file
56
docs/bootstrap/main.tf
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 0.12"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "do_token" {}
|
||||||
|
|
||||||
|
variable "ssh_key_file" {
|
||||||
|
default = "~/.ssh/id_rsa.pub"
|
||||||
|
description = "Path to the SSH public key file"
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "digitalocean" {
|
||||||
|
token = var.do_token
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "random_password" "password" {
|
||||||
|
length = 16
|
||||||
|
special = true
|
||||||
|
override_special = "_-#"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "local_file" "ssh_key"{
|
||||||
|
filename = pathexpand(var.ssh_key_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
data "template_file" "cloud_init" {
|
||||||
|
template = "${file("cloud-config.tpl")}"
|
||||||
|
vars = {
|
||||||
|
gw_password=random_password.password.result,
|
||||||
|
ssh_key=data.local_file.ssh_key.content,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "digitalocean_droplet" "faasd" {
|
||||||
|
|
||||||
|
region = "lon1"
|
||||||
|
image = "ubuntu-18-04-x64"
|
||||||
|
name = "faasd"
|
||||||
|
# Plans: https://developers.digitalocean.com/documentation/changelog/api-v2/new-size-slugs-for-droplet-plan-changes/
|
||||||
|
#size = "512mb"
|
||||||
|
size = "s-1vcpu-1gb"
|
||||||
|
user_data = data.template_file.cloud_init.rendered
|
||||||
|
}
|
||||||
|
|
||||||
|
output "password" {
|
||||||
|
value = random_password.password.result
|
||||||
|
}
|
||||||
|
|
||||||
|
output "gateway_url" {
|
||||||
|
value = "http://${digitalocean_droplet.faasd.ipv4_address}:8080/"
|
||||||
|
}
|
||||||
|
|
||||||
|
output "login_cmd" {
|
||||||
|
value = "faas-cli login -g http://${digitalocean_droplet.faasd.ipv4_address}:8080/ -p ${random_password.password.result}"
|
||||||
|
}
|
||||||
|
|
@ -49,7 +49,7 @@ func (r *requester) Query(ctx context.Context, req logs.Request) (<-chan logs.Me
|
|||||||
// call start and get the stdout prior to streaming so that we can return a meaningful
|
// call start and get the stdout prior to streaming so that we can return a meaningful
|
||||||
// error for as long as possible. If the cmd starts correctly, we are highly likely to
|
// error for as long as possible. If the cmd starts correctly, we are highly likely to
|
||||||
// succeed anyway
|
// succeed anyway
|
||||||
msgs := make(chan logs.Message, 100)
|
msgs := make(chan logs.Message)
|
||||||
go streamLogs(ctx, cmd, stdout, msgs)
|
go streamLogs(ctx, cmd, stdout, msgs)
|
||||||
go logErrOut(stderr)
|
go logErrOut(stderr)
|
||||||
|
|
||||||
@ -62,7 +62,6 @@ func (r *requester) Query(ctx context.Context, req logs.Request) (<-chan logs.Me
|
|||||||
// --output=json \
|
// --output=json \
|
||||||
// --since=<timestamp> \
|
// --since=<timestamp> \
|
||||||
// <--follow> \
|
// <--follow> \
|
||||||
// --output-fields=SYSLOG_IDENTIFIER,MESSAGE,_PID,_SOURCE_REALTIME_TIMESTAMP
|
|
||||||
func buildCmd(ctx context.Context, req logs.Request) *exec.Cmd {
|
func buildCmd(ctx context.Context, req logs.Request) *exec.Cmd {
|
||||||
// // set the cursor position based on req, default to 5m
|
// // set the cursor position based on req, default to 5m
|
||||||
since := time.Now().Add(-5 * time.Minute)
|
since := time.Now().Add(-5 * time.Minute)
|
||||||
@ -102,10 +101,6 @@ func buildCmd(ctx context.Context, req logs.Request) *exec.Cmd {
|
|||||||
// the loop is based on the Decoder example in the docs
|
// the loop is based on the Decoder example in the docs
|
||||||
// https://golang.org/pkg/encoding/json/#Decoder.Decode
|
// https://golang.org/pkg/encoding/json/#Decoder.Decode
|
||||||
func streamLogs(ctx context.Context, cmd *exec.Cmd, out io.ReadCloser, msgs chan logs.Message) {
|
func streamLogs(ctx context.Context, cmd *exec.Cmd, out io.ReadCloser, msgs chan logs.Message) {
|
||||||
// without this sleep the channel seems to get stuck. This results in either no log messages
|
|
||||||
// being read by the Handler _or_ the messages are read but only flushed when the request
|
|
||||||
// timesout
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
log.Println("starting journal stream using ", cmd.String())
|
log.Println("starting journal stream using ", cmd.String())
|
||||||
|
|
||||||
// will ensure `out` is closed and all related resources cleaned up
|
// will ensure `out` is closed and all related resources cleaned up
|
||||||
|
@ -29,19 +29,19 @@ func Test_parseEntry(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if entry.Name != expectedEntry.Name {
|
if entry.Name != expectedEntry.Name {
|
||||||
t.Fatalf("expected Name %s, got %s", expectedEntry.Name, entry.Name)
|
t.Fatalf("want Name: %q, got %q", expectedEntry.Name, entry.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry.Namespace != expectedEntry.Namespace {
|
if entry.Namespace != expectedEntry.Namespace {
|
||||||
t.Fatalf("expected Namespace %s, got %s", expectedEntry.Namespace, entry.Namespace)
|
t.Fatalf("want Namespace: %q, got %q", expectedEntry.Namespace, entry.Namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry.Timestamp != expectedEntry.Timestamp {
|
if entry.Timestamp != expectedEntry.Timestamp {
|
||||||
t.Fatalf("expected Timestamp %s, got %s", expectedEntry.Timestamp, entry.Timestamp)
|
t.Fatalf("want Timestamp: %q, got %q", expectedEntry.Timestamp, entry.Timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry.Text != expectedEntry.Text {
|
if entry.Text != expectedEntry.Text {
|
||||||
t.Fatalf("expected Text %s, got %s", expectedEntry.Text, entry.Text)
|
t.Fatalf("want Text: %q, got %q", expectedEntry.Text, entry.Text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,17 +57,17 @@ func Test_buildCmd(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expectedArgs := fmt.Sprintf(
|
expectedArgs := fmt.Sprintf(
|
||||||
"--utc --no-pager --output=json --output-fields=SYSLOG_IDENTIFIER,MESSAGE,_PID,_SOURCE_REALTIME_TIMESTAMP --identifier=spacetwo:loggyfunc --since=%s --follow --lines=5",
|
"--utc --no-pager --output=json --identifier=spacetwo:loggyfunc --since=%s --follow --lines=5",
|
||||||
now.UTC().Format("2006-01-02 15:04:05"),
|
now.UTC().Format("2006-01-02 15:04:05"),
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd := buildCmd(ctx, req).String()
|
cmd := buildCmd(ctx, req).String()
|
||||||
|
wantCmd := "journalctl"
|
||||||
if !strings.Contains(cmd, "journalctl") {
|
if !strings.Contains(cmd, wantCmd) {
|
||||||
t.Fatalf("expected journalctl cmd, got cmd %s", cmd)
|
t.Fatalf("cmd want: %q, got: %q", wantCmd, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasSuffix(cmd, expectedArgs) {
|
if !strings.HasSuffix(cmd, expectedArgs) {
|
||||||
t.Fatalf("expected arg %s,\ngot cmd %s", expectedArgs, cmd)
|
t.Fatalf("arg want: %q\ngot: %q", expectedArgs, cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,13 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/openfaas/faas-provider/types"
|
"github.com/openfaas/faas-provider/types"
|
||||||
@ -76,17 +78,6 @@ func createSecret(c *containerd.Client, w http.ResponseWriter, r *http.Request,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
func deleteSecret(c *containerd.Client, w http.ResponseWriter, r *http.Request, mountPath string) {
|
||||||
secret, err := parseSecret(r)
|
secret, err := parseSecret(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -103,3 +94,29 @@ func deleteSecret(c *containerd.Client, w http.ResponseWriter, r *http.Request,
|
|||||||
return
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return secret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isTraversal(secret.Name) {
|
||||||
|
return secret, fmt.Errorf(traverseErrorSt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return secret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const traverseErrorSt = "directory traversal found in name"
|
||||||
|
|
||||||
|
func isTraversal(name string) bool {
|
||||||
|
return strings.Contains(name, fmt.Sprintf("%s", string(os.PathSeparator))) ||
|
||||||
|
strings.Contains(name, "..")
|
||||||
|
}
|
||||||
|
63
pkg/provider/handlers/secret_test.go
Normal file
63
pkg/provider/handlers/secret_test.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/openfaas/faas-provider/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_parseSecretValidName(t *testing.T) {
|
||||||
|
|
||||||
|
s := types.Secret{Name: "authorized_keys"}
|
||||||
|
body, _ := json.Marshal(s)
|
||||||
|
reader := bytes.NewReader(body)
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", reader)
|
||||||
|
_, err := parseSecret(r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("secret name is valid with no traversal characters")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseSecretValidNameWithDot(t *testing.T) {
|
||||||
|
|
||||||
|
s := types.Secret{Name: "authorized.keys"}
|
||||||
|
body, _ := json.Marshal(s)
|
||||||
|
reader := bytes.NewReader(body)
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", reader)
|
||||||
|
_, err := parseSecret(r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("secret name is valid with no traversal characters")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseSecretWithTraversalWithSlash(t *testing.T) {
|
||||||
|
|
||||||
|
s := types.Secret{Name: "/root/.ssh/authorized_keys"}
|
||||||
|
body, _ := json.Marshal(s)
|
||||||
|
reader := bytes.NewReader(body)
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", reader)
|
||||||
|
_, err := parseSecret(r)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("secret name should fail due to path traversal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseSecretWithTraversalWithDoubleDot(t *testing.T) {
|
||||||
|
|
||||||
|
s := types.Secret{Name: ".."}
|
||||||
|
body, _ := json.Marshal(s)
|
||||||
|
reader := bytes.NewReader(body)
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", reader)
|
||||||
|
_, err := parseSecret(r)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("secret name should fail due to path traversal")
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user