diff --git a/.gitignore b/.gitignore index 6975047..982043a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ hosts basic-auth-user basic-auth-password /bin +/secrets diff --git a/cmd/provider.go b/cmd/provider.go index 89b0b39..4acf673 100644 --- a/cmd/provider.go +++ b/cmd/provider.go @@ -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) diff --git a/pkg/provider/handlers/deploy.go b/pkg/provider/handlers/deploy.go index 36e0828..4e0a8f3 100644 --- a/pkg/provider/handlers/deploy.go +++ b/pkg/provider/handlers/deploy.go @@ -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 +} diff --git a/pkg/provider/handlers/secret.go b/pkg/provider/handlers/secret.go new file mode 100644 index 0000000..e1e42ee --- /dev/null +++ b/pkg/provider/handlers/secret.go @@ -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 + } +} diff --git a/pkg/provider/handlers/update.go b/pkg/provider/handlers/update.go index bf2de43..5ef9f98 100644 --- a/pkg/provider/handlers/update.go +++ b/pkg/provider/handlers/update.go @@ -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)