├── scripts ├── golint.sh └── gofmtcheck.sh ├── release ├── build.sh └── Dockerfile ├── .travis.yml ├── glide.yaml ├── main.go ├── docker ├── config.go ├── server.go └── driver.go ├── fs ├── utils.go ├── secret.go ├── fs.go └── root.go ├── cmd ├── version.go ├── mount.go ├── docker.go ├── root.go └── utils.go ├── README.md ├── Makefile ├── glide.lock └── LICENSE /scripts/golint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | EXIT=0 4 | 5 | # Check gofmt 6 | echo "==> Checking that code complies with golint requirements..." 7 | packages=$@ 8 | 9 | for package in $@; do 10 | OUT=$(golint $package) 11 | 12 | if [ "$OUT" != "" ]; then 13 | echo -e "$OUT" 14 | EXIT=1 15 | fi 16 | done 17 | 18 | exit $EXIT 19 | -------------------------------------------------------------------------------- /scripts/gofmtcheck.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Check gofmt 4 | echo "==> Checking that code complies with gofmt requirements..." 5 | gofmt_files=$(gofmt -l . | grep -v vendor) 6 | if [[ -n ${gofmt_files} ]]; then 7 | echo 'gofmt needs running on the following files:' 8 | echo "${gofmt_files}" 9 | echo "You can use the command: \`make fmt\` to reformat code." 10 | exit 1 11 | fi 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /release/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ ! -d .git ]; then 4 | echo "this script should be run from the root of the project" 5 | exit 1 6 | fi 7 | 8 | if ! docker images | grep -q vaultfs-build; then 9 | docker build -t vaultfs-build release/ 10 | fi 11 | 12 | docker run --rm -v $(pwd):/go/src/github.com/asteris-llc/vaultfs vaultfs-build /bin/sh -c 'cd /go/src/github.com/asteris-llc/vaultfs && env GOOS=linux GOARCH=amd64 go build .' 13 | -------------------------------------------------------------------------------- /release/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gliderlabs/alpine:3.3 2 | 3 | # build-base 4 | RUN apk add --no-cache build-base 5 | 6 | # go 7 | RUN apk add --no-cache go 8 | RUN mkdir /go 9 | ENV GOPATH /go 10 | ENV GO15VENDOREXPERIMENT 1 11 | 12 | # glide 13 | RUN apk add --no-cache --virtual=glide-deps curl ca-certificates && \ 14 | mkdir /tmp/glide && \ 15 | curl -L https://github.com/Masterminds/glide/releases/download/0.9.3/glide-0.9.3-linux-amd64.tar.gz | tar -xzv -C /tmp/glide && \ 16 | apk del glide-deps && \ 17 | mv /tmp/glide/**/glide /bin/glide 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: go 3 | 4 | # we support the most two recent releases, and (hopefully) the tip. 5 | go: 6 | - 1.5 7 | - 1.6 8 | - tip 9 | 10 | cache: 11 | directories: 12 | - vendor 13 | 14 | install: 15 | - mkdir tools 16 | - curl -L https://github.com/Masterminds/glide/releases/download/0.9.3/glide-0.9.3-linux-amd64.tar.gz | tar -xzv --strip-components=1 -C tools 17 | - chmod +x tools/glide 18 | - export PATH=$(pwd)/tools:$PATH 19 | - glide install 20 | 21 | script: 22 | - make 23 | 24 | matrix: 25 | allow_failures: 26 | - go: tip 27 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/asteris-llc/vaultfs 2 | import: 3 | - package: bazil.org/fuse 4 | subpackages: 5 | - fs 6 | - package: github.com/Sirupsen/logrus 7 | subpackages: 8 | - hooks/syslog 9 | - package: github.com/docker/go-plugins-helpers 10 | subpackages: 11 | - volume 12 | - package: github.com/hashicorp/vault 13 | subpackages: 14 | - api 15 | - package: github.com/rifflock/lfshook 16 | - package: github.com/spf13/cobra 17 | - package: github.com/spf13/viper 18 | - package: github.com/wercker/journalhook 19 | - package: golang.org/x/net 20 | subpackages: 21 | - context 22 | - package: golang.org/x/sys 23 | subpackages: 24 | - unix 25 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Asteris, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import "github.com/asteris-llc/vaultfs/cmd" 18 | 19 | func main() { 20 | cmd.Execute() 21 | } 22 | -------------------------------------------------------------------------------- /docker/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Asteris, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package docker 16 | 17 | import ( 18 | "github.com/hashicorp/vault/api" 19 | ) 20 | 21 | // Config configures the docker volume plugin 22 | type Config struct { 23 | // Root for mount 24 | Root string 25 | 26 | // Token and config for Vault 27 | Token string 28 | Vault *api.Config 29 | } 30 | -------------------------------------------------------------------------------- /fs/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Asteris, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package fs 16 | 17 | import ( 18 | "crypto/tls" 19 | "net/http" 20 | 21 | "github.com/hashicorp/vault/api" 22 | ) 23 | 24 | // NewConfig creates a new config 25 | func NewConfig(address string, insecure bool) *api.Config { 26 | return &api.Config{ 27 | Address: address, 28 | HttpClient: &http.Client{ 29 | Transport: &http.Transport{ 30 | Proxy: http.ProxyFromEnvironment, 31 | TLSClientConfig: &tls.Config{ 32 | InsecureSkipVerify: insecure, 33 | }, 34 | }, 35 | }, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Asteris 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | // Name describes the name of this tool 24 | const Name = "vaultfs" 25 | 26 | // Version describes the version of this tool 27 | const Version = "1.0.0" 28 | 29 | // versionCmd represents the version command 30 | var versionCmd = &cobra.Command{ 31 | Use: "version", 32 | Short: "A brief description of your command", 33 | Run: func(cmd *cobra.Command, args []string) { 34 | fmt.Printf("%s %s", Name, Version) 35 | }, 36 | } 37 | 38 | func init() { 39 | RootCmd.AddCommand(versionCmd) 40 | } 41 | -------------------------------------------------------------------------------- /fs/secret.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Asteris, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package fs 16 | 17 | import ( 18 | "encoding/json" 19 | 20 | "bazil.org/fuse" 21 | "github.com/Sirupsen/logrus" 22 | "github.com/hashicorp/vault/api" 23 | "golang.org/x/net/context" 24 | ) 25 | 26 | // Secret implements Node and Handle 27 | type Secret struct { 28 | *api.Secret 29 | inode uint64 30 | } 31 | 32 | // Attr returns attributes about this Secret 33 | func (s Secret) Attr(ctx context.Context, a *fuse.Attr) error { 34 | a.Inode = s.inode 35 | a.Mode = 0444 36 | 37 | content, err := s.ReadAll(ctx) 38 | if err != nil { 39 | logrus.WithError(err).Error("could not determine content length") 40 | return fuse.EIO 41 | } 42 | 43 | a.Size = uint64(len(content)) 44 | return nil 45 | } 46 | 47 | // ReadAll gets the content of this Secret 48 | func (s Secret) ReadAll(ctx context.Context) ([]byte, error) { 49 | return json.Marshal(s) 50 | } 51 | -------------------------------------------------------------------------------- /docker/server.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Asteris, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package docker 16 | 17 | import ( 18 | "github.com/Sirupsen/logrus" 19 | "github.com/asteris-llc/vaultfs/fs" 20 | "github.com/hashicorp/vault/api" 21 | ) 22 | 23 | // Server wraps VaultFS and tracks connection counts 24 | type Server struct { 25 | fs *fs.VaultFS 26 | connections int 27 | stopFunc func() 28 | errs chan error 29 | } 30 | 31 | // NewServer returns a new server with initial state 32 | func NewServer(config *api.Config, mountpoint, token, root string) (*Server, error) { 33 | fs, err := fs.New(config, mountpoint, token, root) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | return &Server{fs: fs, connections: 1}, nil 39 | } 40 | 41 | // Mount mounts the wrapped FS on a given mountpoint. It also starts watching 42 | // for errors, which it will log. 43 | func (s *Server) Mount() error { 44 | err := s.fs.Mount() 45 | 46 | if err != nil { 47 | logrus.WithError(err).Error("error in server, stopping") 48 | return err 49 | } 50 | 51 | return nil 52 | } 53 | 54 | // Unmount stops the wrapped FS. It returns the last error that it sees, but 55 | // will log any others it receives. 56 | func (s *Server) Unmount() error { 57 | err := s.fs.Unmount() 58 | 59 | if err != nil { 60 | logrus.WithError(err).Error("could not unmount cleanly") 61 | } 62 | 63 | return err 64 | } 65 | -------------------------------------------------------------------------------- /fs/fs.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Asteris, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package fs 16 | 17 | import ( 18 | "errors" 19 | 20 | "bazil.org/fuse" 21 | "bazil.org/fuse/fs" 22 | "github.com/Sirupsen/logrus" 23 | "github.com/hashicorp/vault/api" 24 | ) 25 | 26 | // VaultFS is a vault filesystem 27 | type VaultFS struct { 28 | *api.Client 29 | root string 30 | conn *fuse.Conn 31 | mountpoint string 32 | } 33 | 34 | // New returns a new VaultFS 35 | func New(config *api.Config, mountpoint, token, root string) (*VaultFS, error) { 36 | client, err := api.NewClient(config) 37 | if err != nil { 38 | return nil, err 39 | } 40 | client.SetToken(token) 41 | 42 | return &VaultFS{ 43 | Client: client, 44 | root: root, 45 | mountpoint: mountpoint, 46 | }, nil 47 | } 48 | 49 | // Mount the FS at the given mountpoint 50 | func (v *VaultFS) Mount() error { 51 | var err error 52 | v.conn, err = fuse.Mount( 53 | v.mountpoint, 54 | fuse.FSName("vault"), 55 | fuse.VolumeName("vault"), 56 | ) 57 | 58 | logrus.Debug("created conn") 59 | if err != nil { 60 | return err 61 | } 62 | 63 | logrus.Debug("starting to serve") 64 | return fs.Serve(v.conn, v) 65 | } 66 | 67 | // Unmount the FS 68 | func (v *VaultFS) Unmount() error { 69 | if v.conn == nil { 70 | return errors.New("not mounted") 71 | } 72 | 73 | err := fuse.Unmount(v.mountpoint) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | err = v.conn.Close() 79 | if err != nil { 80 | return err 81 | } 82 | 83 | logrus.Debug("closed connection, waiting for ready") 84 | <-v.conn.Ready 85 | if v.conn.MountError != nil { 86 | return v.conn.MountError 87 | } 88 | 89 | return nil 90 | } 91 | 92 | // Root returns the struct that does the actual work 93 | func (v *VaultFS) Root() (fs.Node, error) { 94 | logrus.Debug("returning root") 95 | return NewRoot(v.root, v.Logical()), nil 96 | } 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VaultFS 2 | 3 | [![Build Status](https://travis-ci.org/asteris-llc/vaultfs.svg?branch=master)](https://travis-ci.org/asteris-llc/vaultfs) 4 | 5 | VaultFS mounts arbitrary [Vault](https://vaultproject.io/) prefixes in a FUSE 6 | filesystem. It also provides a Docker volume plugin to the do the same for your 7 | containers. 8 | 9 | 10 | **Table of Contents** 11 | 12 | - [VaultFS](#vaultfs) 13 | - [Mounting](#mounting) 14 | - [Docker](#docker) 15 | - [License](#license) 16 | 17 | 18 | 19 | # Installation 20 | 21 | This project is in early development and has not reached 1.0. You will have to 22 | build the binary yourself: 23 | 24 | ```shell 25 | go get github.com/asteris-llc/vaultfs 26 | env GOOS=linux go build github.com/asteris-llc/vaultfs 27 | ``` 28 | 29 | # Usage 30 | 31 | VaultFS is one binary that can mount keys or run a Docker volume plugin to do so 32 | for containers. Run `vaultfs --help` to see options not documented here. 33 | 34 | ## Mounting 35 | 36 | ``` 37 | Usage: 38 | vaultfs mount {mountpoint} [flags] 39 | 40 | Flags: 41 | -a, --address="https://localhost:8200": vault address 42 | -i, --insecure[=false]: skip SSL certificate verification 43 | -r, --root="secret": root path for reads 44 | -t, --token="": vault token 45 | ``` 46 | 47 | To mount secrets, first create a mountpoint (`mkdir test`), then use `vaultfs` 48 | to mount: 49 | 50 | ```shell 51 | vaultfs mount --address=http://localhost:8200 -t 3a749a17-528e-e4b1-c28a-62e54f0098ae test 52 | ``` 53 | 54 | ## Docker 55 | 56 | ``` 57 | Usage: 58 | vaultfs docker {mountpoint} [flags] 59 | 60 | Flags: 61 | -a, --address="https://localhost:8200": vault address 62 | -i, --insecure[=false]: skip SSL certificate verification 63 | -s, --socket="/run/docker/plugins/vault.sock": socket address to communicate with docker 64 | -t, --token="": vault token 65 | ``` 66 | 67 | To start the Docker plugin, create a directory to hold mountpoints (`mkdir 68 | test`), then use `vaultfs` to start the server. When Docker volumes request a 69 | volume (`docker run --volume-driver vault --volume 70 | {prefix}:/container/secret/path`), the plugin will create mountpoints and manage 71 | FUSE servers automatically. 72 | 73 | ```shell 74 | vaultfs docker --address=http://localhost:8200 -t 3a749a17-528e-e4b1-c28a-62e54f0098ae test 75 | ``` 76 | 77 | # License 78 | 79 | VaultFS is licensed under an 80 | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0.html) (see also: 81 | [LICENSE](LICENSE)) 82 | -------------------------------------------------------------------------------- /cmd/mount.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Asteris, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | "os" 20 | "os/signal" 21 | "syscall" 22 | 23 | "github.com/Sirupsen/logrus" 24 | "github.com/asteris-llc/vaultfs/fs" 25 | "github.com/spf13/cobra" 26 | "github.com/spf13/viper" 27 | ) 28 | 29 | // mountCmd represents the mount command 30 | var mountCmd = &cobra.Command{ 31 | Use: "mount {mountpoint}", 32 | Short: "mount a vault FS at the specified mountpoint", 33 | PreRunE: func(cmd *cobra.Command, args []string) error { 34 | if len(args) == 0 { 35 | return errors.New("expected exactly one argument") 36 | } 37 | 38 | if err := viper.BindPFlags(cmd.Flags()); err != nil { 39 | logrus.WithError(err).Fatal("could not bind flags") 40 | } 41 | 42 | return nil 43 | }, 44 | Run: func(cmd *cobra.Command, args []string) { 45 | config := fs.NewConfig(viper.GetString("address"), viper.GetBool("insecure")) 46 | 47 | logrus.WithField("address", viper.GetString("address")).Info("creating FUSE client for Vault") 48 | 49 | fs, err := fs.New(config, args[0], viper.GetString("token"), viper.GetString("root")) 50 | if err != nil { 51 | logrus.WithError(err).Fatal("error creatinging fs") 52 | } 53 | 54 | // handle interrupt 55 | go func() { 56 | c := make(chan os.Signal, 1) 57 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 58 | 59 | <-c 60 | logrus.Info("stopping") 61 | err := fs.Unmount() 62 | if err != nil { 63 | logrus.WithError(err).Fatal("could not unmount cleanly") 64 | } 65 | }() 66 | 67 | err = fs.Mount() 68 | if err != nil { 69 | logrus.WithError(err).Fatal("could not continue") 70 | } 71 | }, 72 | } 73 | 74 | func init() { 75 | RootCmd.AddCommand(mountCmd) 76 | 77 | mountCmd.Flags().StringP("root", "r", "secret", "root path for reads") 78 | mountCmd.Flags().StringP("address", "a", "https://localhost:8200", "vault address") 79 | mountCmd.Flags().BoolP("insecure", "i", false, "skip SSL certificate verification") 80 | mountCmd.Flags().StringP("token", "t", "", "vault token") 81 | } 82 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TEST?=$(shell GO15VENDOREXPERIMENT=1 go list -f '{{.ImportPath}}/...' ./... | grep -v /vendor/ | sed "s|$(shell go list -f '{{.ImportPath}}' .)|.|g" | sed "s/\.\/\.\.\./\.\//g") 2 | VET?=$(shell echo ${TEST} | sed "s/\.\.\.//g" | sed "s/\.\/ //g") 3 | VETARGS?=-asmdecl -atomic -bool -buildtags -copylocks -methods -nilfunc -printf -rangeloops -shift -structtags -unsafeptr 4 | NAME=$(shell awk -F\" '/^const Name/ { print $$2 }' cmd/version.go) 5 | VERSION=$(shell awk -F\" '/^const Version/ { print $$2 }' cmd/version.go) 6 | 7 | all: test vaultfs 8 | 9 | vaultfs: 10 | GO15VENDOREXPERIMENT=1 go build . 11 | 12 | install: fmtcheck 13 | GO15VENDOREXPERIMENT=1 go install . 14 | 15 | # test runs the unit tests and vets the code 16 | test: fmtcheck vet lint 17 | @echo "==> Testing" 18 | GO15VENDOREXPERIMENT=1 go test $(TEST) $(TESTARGS) -timeout=30s -parallel=4 19 | 20 | # testrace runs the race checker 21 | testrace: fmtcheck 22 | GO15VENDOREXPERIMENT=1 go test -race $(TEST) $(TESTARGS) 23 | 24 | cover: 25 | @go tool cover 2>/dev/null; if [ $$? -eq 3 ]; then \ 26 | go get -u golang.org/x/tools/cmd/cover; \ 27 | fi 28 | GO15VENDOREXPERIMENT=1 go test $(TEST) -coverprofile=coverage.out 29 | GO15VENDOREXPERIMENT=1 go tool cover -html=coverage.out 30 | rm coverage.out 31 | 32 | # vet runs the Go source code static analysis tool `vet` to find 33 | # any common errors. 34 | vet: 35 | @echo "==> Cheking that code complies with go vet requirements..." 36 | @go tool vet 2>/dev/null ; if [ $$? -eq 3 ]; then \ 37 | go get golang.org/x/tools/cmd/vet; \ 38 | fi 39 | @GO15VENDOREXPERIMENT=1 go tool vet $(VETARGS) $(VET) ; if [ $$? -eq 1 ]; then \ 40 | echo ""; \ 41 | echo "Vet found suspicious constructs. Please check the reported constructs"; \ 42 | echo "and fix them if necessary before submitting the code for review."; \ 43 | exit 1; \ 44 | fi 45 | 46 | lint: 47 | @golint 2>/dev/null; if [ $$? -eq 127 ]; then \ 48 | go get github.com/golang/lint/golint; \ 49 | fi 50 | @GO15VENDOREXPERIMENT=1 sh -c "'$(CURDIR)/scripts/golint.sh' ${VET}"; if [ $$? -eq 1 ]; then \ 51 | echo ""; \ 52 | echo "lint found errors in the code. Please check the errors listed above"; \ 53 | echo "and fix them if necessary before submitting the code for review."; \ 54 | exit 1; \ 55 | fi 56 | 57 | fmt: 58 | gofmt -w $(shell echo $(TEST) | sed 's/\.\.\./*.go/g' | sed 's/\.\//.\/*.go/') 59 | 60 | fmtcheck: 61 | @sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'" 62 | 63 | vaultfs-%-linux-amd64.tar.gz: test 64 | ./release/build.sh 65 | mkdir vaultfs-$*-linux-amd64 66 | mv vaultfs vaultfs-$*-linux-amd64/vaultfs 67 | cp README.md LICENSE vaultfs-$*-linux-amd64/ 68 | tar -czvf $@ vaultfs-$*-linux-amd64 69 | rm -rf vaultfs-$*-linux-amd64 70 | 71 | .PHONY: all test vet fmt fmtcheck lint 72 | -------------------------------------------------------------------------------- /cmd/docker.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Asteris, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | 20 | "github.com/Sirupsen/logrus" 21 | "github.com/asteris-llc/vaultfs/docker" 22 | "github.com/asteris-llc/vaultfs/fs" 23 | "github.com/docker/go-plugins-helpers/volume" 24 | "github.com/spf13/cobra" 25 | "github.com/spf13/viper" 26 | ) 27 | 28 | // dockerCmd represents the docker command 29 | var dockerCmd = &cobra.Command{ 30 | Use: "docker {mountpoint}", 31 | Short: "start the docker volume server at the specified root", 32 | PreRunE: func(cmd *cobra.Command, args []string) error { 33 | if len(args) == 0 { 34 | return errors.New("expected exactly one argument, a mountpoint") 35 | } 36 | 37 | if err := viper.BindPFlags(cmd.Flags()); err != nil { 38 | logrus.WithError(err).Fatal("could not bind flags") 39 | } 40 | 41 | return nil 42 | }, 43 | Run: func(cmd *cobra.Command, args []string) { 44 | driver := docker.New(docker.Config{ 45 | Root: args[0], 46 | Token: viper.GetString("token"), 47 | Vault: fs.NewConfig(viper.GetString("address"), viper.GetBool("insecure")), 48 | }) 49 | 50 | logrus.WithFields(logrus.Fields{ 51 | "root": args[0], 52 | "address": viper.GetString("address"), 53 | "insecure": viper.GetBool("insecure"), 54 | "socket": viper.GetString("socket"), 55 | }).Info("starting plugin server") 56 | 57 | defer func() { 58 | for _, err := range driver.Stop() { 59 | logrus.WithError(err).Error("error stopping driver") 60 | } 61 | }() 62 | 63 | handler := volume.NewHandler(driver) 64 | logrus.WithField("socket", viper.GetString("socket")).Info("serving unix socket") 65 | err := handler.ServeUnix("root", viper.GetString("socket")) 66 | if err != nil { 67 | logrus.WithError(err).Fatal("failed serving") 68 | } 69 | }, 70 | } 71 | 72 | func init() { 73 | RootCmd.AddCommand(dockerCmd) 74 | 75 | dockerCmd.Flags().StringP("address", "a", "https://localhost:8200", "vault address") 76 | dockerCmd.Flags().BoolP("insecure", "i", false, "skip SSL certificate verification") 77 | dockerCmd.Flags().StringP("token", "t", "", "vault token") 78 | dockerCmd.Flags().StringP("socket", "s", "/run/docker/plugins/vault.sock", "socket address to communicate with docker") 79 | } 80 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Asteris, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "os" 19 | 20 | "github.com/Sirupsen/logrus" 21 | "github.com/spf13/cobra" 22 | "github.com/spf13/viper" 23 | ) 24 | 25 | var cfgFile string 26 | 27 | // RootCmd controls global settings 28 | var RootCmd = &cobra.Command{ 29 | Use: "vaultfs", 30 | Short: "use Docker's volumes to mount Vault secrets", 31 | Long: `use Docker's volumes to mount Vault secrets`, 32 | } 33 | 34 | // Execute adds all child commands to the root command sets flags appropriately. 35 | // This is called by main.main(). It only needs to happen once to the rootCmd. 36 | func Execute() { 37 | if err := RootCmd.Execute(); err != nil { 38 | logrus.WithError(err).Error("error executing command") 39 | os.Exit(-1) 40 | } 41 | } 42 | 43 | func init() { 44 | cobra.OnInitialize(initConfig, initLogging, lockMemory) 45 | 46 | RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is /etc/sysconfig/vaultfs)") 47 | 48 | // logging flags 49 | RootCmd.PersistentFlags().String("log-level", "info", "log level (one of fatal, error, warn, info, or debug)") 50 | RootCmd.PersistentFlags().String("log-format", "text", "log level (one of text or json)") 51 | RootCmd.PersistentFlags().String("log-destination", "stdout:", "log destination (file:/your/output, stdout:, journald:, or syslog://tag@host:port#protocol)") 52 | 53 | if err := viper.BindPFlags(RootCmd.PersistentFlags()); err != nil { 54 | logrus.WithError(err).Fatal("could not bind flags") 55 | } 56 | } 57 | 58 | // initConfig reads in config file and ENV variables if set. 59 | func initConfig() { 60 | if cfgFile != "" { // enable ability to specify config file via flag 61 | viper.SetConfigFile(cfgFile) 62 | } 63 | 64 | viper.SetConfigName("vaultfs") // name of config file (without extension) 65 | viper.AddConfigPath("/etc/sysconfig") // adding sysconfig as the first search path 66 | viper.AddConfigPath("$HOME") // home directory as another path 67 | viper.AutomaticEnv() // read in environment variables that match 68 | 69 | // If a config file is found, read it in. 70 | if err := viper.ReadInConfig(); err == nil { 71 | logrus.WithField("config", viper.ConfigFileUsed()).Info("using config file from disk") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /fs/root.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Asteris, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package fs 16 | 17 | import ( 18 | "os" 19 | 20 | "hash/crc64" 21 | "path" 22 | 23 | "bazil.org/fuse" 24 | "bazil.org/fuse/fs" 25 | "github.com/Sirupsen/logrus" 26 | "github.com/hashicorp/vault/api" 27 | "golang.org/x/net/context" 28 | ) 29 | 30 | var table = crc64.MakeTable(crc64.ISO) 31 | 32 | // Root implements both Node and Handle 33 | type Root struct { 34 | root string 35 | logic *api.Logical 36 | } 37 | 38 | // NewRoot creates a new root and returns it 39 | func NewRoot(root string, logic *api.Logical) *Root { 40 | return &Root{ 41 | root: root, 42 | logic: logic, 43 | } 44 | } 45 | 46 | // Attr sets attrs on the given fuse.Attr 47 | func (Root) Attr(ctx context.Context, a *fuse.Attr) error { 48 | logrus.Debug("handling Root.Attr call") 49 | a.Inode = 1 50 | a.Mode = os.ModeDir 51 | return nil 52 | } 53 | 54 | // Lookup looks up a path 55 | func (r *Root) Lookup(ctx context.Context, name string) (fs.Node, error) { 56 | logrus.WithField("name", name).Debug("handling Root.Lookup call") 57 | 58 | // TODO: handle context cancellation 59 | secret, err := r.logic.Read(path.Join(r.root, name)) 60 | if secret == nil && err == nil { 61 | return nil, fuse.ENOENT 62 | } else if err != nil { 63 | logrus.WithError(err).WithFields(logrus.Fields{"root": r.root, "name": name}).Error("error reading key") 64 | return nil, fuse.EIO 65 | } 66 | 67 | return Secret{ 68 | secret, 69 | crc64.Checksum([]byte(name), table), 70 | }, nil 71 | } 72 | 73 | // ReadDirAll returns a list of secrets 74 | func (r *Root) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { 75 | logrus.Debug("handling Root.ReadDirAll call") 76 | 77 | secrets, err := r.logic.List(path.Join(r.root)) 78 | if err != nil { 79 | logrus.WithError(err).WithFields(logrus.Fields{"root": r.root}).Error("error reading secrets") 80 | return nil, fuse.EIO 81 | } 82 | 83 | if secrets.Data["keys"] == nil { 84 | return []fuse.Dirent{}, nil 85 | } 86 | 87 | dirs := []fuse.Dirent{} 88 | for i := 0; i < len(secrets.Data["keys"].([]interface{})); i++ { 89 | d := fuse.Dirent{ 90 | Name: secrets.Data["keys"].([]interface{})[i].(string), 91 | Inode: 1, 92 | Type: fuse.DT_File, // TODO: A lie, consider an alternative 93 | } 94 | dirs = append(dirs, d) 95 | } 96 | 97 | return dirs, nil 98 | } 99 | -------------------------------------------------------------------------------- /cmd/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Asteris, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "log/syslog" 19 | "net/url" 20 | 21 | "github.com/Sirupsen/logrus" 22 | logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog" 23 | "github.com/rifflock/lfshook" 24 | "github.com/spf13/viper" 25 | "github.com/wercker/journalhook" 26 | "golang.org/x/sys/unix" 27 | ) 28 | 29 | func initLogging() { 30 | // level 31 | level, err := logrus.ParseLevel(viper.GetString("log-level")) 32 | if err != nil { 33 | logrus.WithError(err).Warn(`invalid log level. Defaulting to "info"`) 34 | level = logrus.InfoLevel 35 | } 36 | logrus.SetLevel(level) 37 | 38 | // format 39 | switch viper.GetString("log-format") { 40 | case "text": 41 | logrus.SetFormatter(new(logrus.TextFormatter)) 42 | case "json": 43 | logrus.SetFormatter(new(logrus.JSONFormatter)) 44 | default: 45 | logrus.SetFormatter(new(logrus.TextFormatter)) 46 | logrus.WithField("format", viper.GetString("log-format")).Warn(`invalid log format. Defaulting to "text"`) 47 | } 48 | 49 | // output 50 | dest, err := url.Parse(viper.GetString("log-destination")) 51 | if err != nil { 52 | logrus.WithError(err).WithField("destination", viper.GetString("log-destination")).Error(`invalid log destination. Defaulting to "stdout:"`) 53 | dest.Scheme = "stdout" 54 | } 55 | 56 | switch dest.Scheme { 57 | case "stdout": 58 | // default, we don't need to do anything 59 | case "file": 60 | logrus.AddHook(lfshook.NewHook(lfshook.PathMap{ 61 | logrus.DebugLevel: dest.Opaque, 62 | logrus.InfoLevel: dest.Opaque, 63 | logrus.WarnLevel: dest.Opaque, 64 | logrus.ErrorLevel: dest.Opaque, 65 | logrus.FatalLevel: dest.Opaque, 66 | })) 67 | case "journald": 68 | journalhook.Enable() 69 | case "syslog": 70 | hook, err := logrus_syslog.NewSyslogHook(dest.Fragment, dest.Host, syslog.LOG_DEBUG, dest.User.String()) 71 | if err != nil { 72 | logrus.WithError(err).Error("could not configure syslog hook") 73 | } else { 74 | logrus.AddHook(hook) 75 | } 76 | default: 77 | logrus.WithField("destination", viper.GetString("log-destination")).Warn(`invalid log destination. Defaulting to "stdout:"`) 78 | } 79 | } 80 | 81 | func lockMemory() { 82 | err := unix.Mlockall(unix.MCL_FUTURE | unix.MCL_CURRENT) 83 | switch err { 84 | case nil: 85 | case unix.ENOSYS: 86 | logrus.WithError(err).Warn("mlockall() not implemented on this system") 87 | case unix.ENOMEM: 88 | logrus.WithError(err).Warn("mlockall() failed with ENOMEM") 89 | default: 90 | logrus.WithError(err).Warn("could not perform mlockall to prevent swapping memory") 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: a01b3e73bc4dfb399afff3e28a542d0849eee2f7e627fd8264e862b857a9effd 2 | updated: 2016-03-23T11:20:53.432700723-05:00 3 | imports: 4 | - name: bazil.org/fuse 5 | version: 37bfa8be929171feec943f3496bc4befdeaf10db 6 | subpackages: 7 | - fs 8 | - fuseutil 9 | - name: github.com/BurntSushi/toml 10 | version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f 11 | - name: github.com/coreos/go-systemd 12 | version: 7b2428fec40033549c68f54e26e89e7ca9a9ce31 13 | subpackages: 14 | - journal 15 | - activation 16 | - util 17 | - name: github.com/docker/docker 18 | version: f4dfe15cbedd00668d1ad6fe26544cfb814f35ea 19 | subpackages: 20 | - volume 21 | - pkg/system 22 | - name: github.com/docker/go-connections 23 | version: f549a9393d05688dff0992ef3efd8bbe6c628aeb 24 | subpackages: 25 | - sockets 26 | - name: github.com/docker/go-plugins-helpers 27 | version: 7945ace0aa5cd39b512c5f3cf30ef6e55f5cc0fc 28 | subpackages: 29 | - volume 30 | - sdk 31 | - name: github.com/docker/go-units 32 | version: 5d2041e26a699eaca682e2ea41c8f891e1060444 33 | - name: github.com/fatih/structs 34 | version: 12ff68a6f48a1c8bd118316171545683337655df 35 | - name: github.com/hashicorp/errwrap 36 | version: 7554cd9344cec97297fa6649b055a8c98c2a1e55 37 | - name: github.com/hashicorp/go-cleanhttp 38 | version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d 39 | - name: github.com/hashicorp/go-multierror 40 | version: d30f09973e19c1dfcd120b2d9c4f168e68d6b5d5 41 | - name: github.com/hashicorp/hcl 42 | version: 5b00661d055d07495c9e785198f607c1fb93061e 43 | subpackages: 44 | - hcl/ast 45 | - hcl/parser 46 | - hcl/token 47 | - json/parser 48 | - hcl/scanner 49 | - hcl/strconv 50 | - json/scanner 51 | - json/token 52 | - name: github.com/hashicorp/vault 53 | version: 8f54fac90033bb44f50c16afb39c4b968b6a6d32 54 | subpackages: 55 | - api 56 | - name: github.com/inconshreveable/mousetrap 57 | version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 58 | - name: github.com/kr/pretty 59 | version: e6ac2fc51e89a3249e82157fa0bb7a18ef9dd5bb 60 | - name: github.com/kr/text 61 | version: bb797dc4fb8320488f47bf11de07a733d7233e1f 62 | - name: github.com/magiconair/properties 63 | version: c265cfa48dda6474e208715ca93e987829f572f8 64 | - name: github.com/Microsoft/go-winio 65 | version: 8f9387ea7efabb228a981b9c381142be7667967f 66 | - name: github.com/mitchellh/mapstructure 67 | version: d2dd0262208475919e1a362f675cfc0e7c10e905 68 | - name: github.com/opencontainers/runc 69 | version: 5f182ce7380f41b8c60a2ecaec14996d7e9cfd4a 70 | subpackages: 71 | - libcontainer/user 72 | - name: github.com/rifflock/lfshook 73 | version: 05a24e24fa8d3a2eca8c2baf23aa2d5a2c51490c 74 | - name: github.com/Sirupsen/logrus 75 | version: 4b6ea7319e214d98c938f12692336f7ca9348d6b 76 | subpackages: 77 | - hooks/syslog 78 | - name: github.com/spf13/cast 79 | version: 27b586b42e29bec072fe7379259cc719e1289da6 80 | - name: github.com/spf13/cobra 81 | version: c678ff029ee250b65714e518f4f5c5cb934955de 82 | - name: github.com/spf13/jwalterweatherman 83 | version: 33c24e77fb80341fe7130ee7c594256ff08ccc46 84 | - name: github.com/spf13/pflag 85 | version: 7f60f83a2c81bc3c3c0d5297f61ddfa68da9d3b7 86 | - name: github.com/spf13/viper 87 | version: c975dc1b4eacf4ec7fdbf0873638de5d090ba323 88 | - name: github.com/wercker/journalhook 89 | version: 1572873fdb03095c0f4d726eb9435f0b564a7e9c 90 | - name: golang.org/x/net 91 | version: e7da8edaa52631091740908acaf2c2d4c9b3ce90 92 | subpackages: 93 | - context 94 | - proxy 95 | - name: golang.org/x/sys 96 | version: afce3de5756ca82699128ebae46ac95ad59d6297 97 | subpackages: 98 | - unix 99 | - name: gopkg.in/fsnotify.v1 100 | version: 875cf421b32f8f1b31bd43776297876d01542279 101 | - name: gopkg.in/yaml.v2 102 | version: a83829b6f1293c91addabc89d0571c246397bbf4 103 | devImports: [] 104 | -------------------------------------------------------------------------------- /docker/driver.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Asteris, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package docker 16 | 17 | import ( 18 | "fmt" 19 | "net/url" 20 | "os" 21 | "path" 22 | "sync" 23 | 24 | "github.com/Sirupsen/logrus" 25 | "github.com/docker/go-plugins-helpers/volume" 26 | ) 27 | 28 | type volumeName struct { 29 | name string 30 | connections int 31 | } 32 | 33 | // Driver implements the interface for a Docker volume plugin 34 | type Driver struct { 35 | config Config 36 | servers map[string]*Server 37 | volumes map[string]*volumeName 38 | m *sync.Mutex 39 | } 40 | 41 | // New instantiates a new driver and returns it 42 | func New(config Config) Driver { 43 | return Driver{ 44 | config: config, 45 | servers: map[string]*Server{}, 46 | m: new(sync.Mutex), 47 | } 48 | } 49 | 50 | // Create handles volume creation calls 51 | func (d Driver) Create(r volume.Request) volume.Response { 52 | return volume.Response{} 53 | } 54 | 55 | // Get retrieves a volume 56 | func (d Driver) Get(r volume.Request) volume.Response { 57 | d.m.Lock() 58 | defer d.m.Unlock() 59 | m := d.mountpoint(r.Name) 60 | if s, ok := d.volumes[m]; ok { 61 | return volume.Response{Volume: &volume.Volume{Name: s.name, Mountpoint: d.mountpoint(s.name)}} 62 | } 63 | 64 | return volume.Response{Err: fmt.Sprintf("Unable to find volume mounted on %s", m)} 65 | } 66 | 67 | // List mounted volumes 68 | func (d Driver) List(r volume.Request) volume.Response { 69 | d.m.Lock() 70 | defer d.m.Unlock() 71 | var vols []*volume.Volume 72 | for _, v := range d.volumes { 73 | vols = append(vols, &volume.Volume{Name: v.name, Mountpoint: d.mountpoint(v.name)}) 74 | } 75 | return volume.Response{Volumes: vols} 76 | } 77 | 78 | // Remove handles volume removal calls 79 | func (d Driver) Remove(r volume.Request) volume.Response { 80 | d.m.Lock() 81 | defer d.m.Unlock() 82 | mount := d.mountpoint(r.Name) 83 | logger := logrus.WithFields(logrus.Fields{ 84 | "name": r.Name, 85 | "mountpoint": mount, 86 | }) 87 | logger.Debug("got remove request") 88 | 89 | if server, ok := d.servers[mount]; ok { 90 | if server.connections <= 1 { 91 | logger.Debug("removing server") 92 | delete(d.servers, mount) 93 | } 94 | } 95 | 96 | return volume.Response{} 97 | } 98 | 99 | // Path handles calls for mountpoints 100 | func (d Driver) Path(r volume.Request) volume.Response { 101 | return volume.Response{Mountpoint: d.mountpoint(r.Name)} 102 | } 103 | 104 | // Mount handles creating and mounting servers 105 | func (d Driver) Mount(r volume.Request) volume.Response { 106 | d.m.Lock() 107 | defer d.m.Unlock() 108 | 109 | mount := d.mountpoint(r.Name) 110 | logger := logrus.WithFields(logrus.Fields{ 111 | "name": r.Name, 112 | "mountpoint": mount, 113 | }) 114 | logger.Info("mounting volume") 115 | 116 | server, ok := d.servers[mount] 117 | if ok && server.connections > 0 { 118 | server.connections++ 119 | return volume.Response{Mountpoint: mount} 120 | } 121 | 122 | mountInfo, err := os.Lstat(mount) 123 | 124 | if os.IsNotExist(err) { 125 | if err := os.MkdirAll(mount, os.ModeDir|0444); err != nil { 126 | logger.WithError(err).Error("error making mount directory") 127 | return volume.Response{Err: err.Error()} 128 | } 129 | } else if err != nil { 130 | logger.WithError(err).Error("error checking if directory exists") 131 | return volume.Response{Err: err.Error()} 132 | } 133 | 134 | if mountInfo != nil && !mountInfo.IsDir() { 135 | logger.Error("already exists and not a directory") 136 | return volume.Response{Err: fmt.Sprintf("%s already exists and is not a directory", mount)} 137 | } 138 | 139 | server, err = NewServer(d.config.Vault, mount, d.config.Token, r.Name) 140 | if err != nil { 141 | logger.WithError(err).Error("error creating server") 142 | return volume.Response{Err: err.Error()} 143 | } 144 | 145 | go server.Mount() 146 | d.servers[mount] = server 147 | 148 | return volume.Response{Mountpoint: mount} 149 | } 150 | 151 | // Unmount handles unmounting (but not removing) servers 152 | func (d Driver) Unmount(r volume.Request) volume.Response { 153 | d.m.Lock() 154 | defer d.m.Unlock() 155 | 156 | mount := d.mountpoint(r.Name) 157 | logger := logrus.WithFields(logrus.Fields{ 158 | "name": r.Name, 159 | "mountpoint": mount, 160 | }) 161 | logger.Info("unmounting volume") 162 | 163 | if server, ok := d.servers[mount]; ok { 164 | logger.WithField("conns", server.connections).Debug("found server") 165 | if server.connections == 1 { 166 | logger.Debug("unmounting") 167 | err := server.Unmount() 168 | if err != nil { 169 | logger.WithError(err).Error("error unmounting server") 170 | return volume.Response{Err: err.Error()} 171 | } 172 | server.connections-- 173 | } 174 | } else { 175 | logger.Error("could not find volume") 176 | return volume.Response{Err: fmt.Sprintf("unable to find the volume mounted at %s", mount)} 177 | } 178 | 179 | return volume.Response{} 180 | } 181 | 182 | func (d Driver) mountpoint(name string) string { 183 | return path.Join(d.config.Root, url.QueryEscape(name)) 184 | } 185 | 186 | // Stop stops all the servers 187 | func (d Driver) Stop() []error { 188 | d.m.Lock() 189 | defer d.m.Unlock() 190 | logrus.Debug("got stop request") 191 | 192 | errs := []error{} 193 | for _, server := range d.servers { 194 | err := server.Unmount() 195 | if err != nil { 196 | errs = append(errs, err) 197 | } 198 | } 199 | 200 | return errs 201 | } 202 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------