├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.debug ├── LICENSE ├── OWNERS ├── README.md ├── SECURITY_CONTACTS ├── cmd └── csi-sshfs │ └── main.go ├── code-of-conduct.md ├── deploy ├── kubernetes-debug │ ├── README.md │ └── csi-nodeplugin-sshfs-debug.yaml └── kubernetes │ ├── _csi-sshfs-namespace.yaml │ ├── csi-controller-rbac.yaml │ ├── csi-controller-sshfs.yaml │ ├── csi-nodeplugin-rbac.yaml │ ├── csi-nodeplugin-sshfs.yaml │ └── csi-sshfs-storageclass.yaml ├── example └── kubernetes │ ├── nginx.yaml │ └── testvol-secret.yaml ├── go.mod ├── go.sum ├── pkg └── sshfs │ ├── driver.go │ ├── k8sClient.go │ └── nodeserver.go └── version.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | Icon 6 | ._* 7 | .DocumentRevisions-V100 8 | .fseventsd 9 | .Spotlight-V100 10 | .TemporaryItems 11 | .Trashes 12 | .VolumeIcon.icns 13 | .com.apple.timemachine.donotpresent 14 | .AppleDB 15 | .AppleDesktop 16 | Network Trash Folder 17 | Temporary Items 18 | .apdisk 19 | *~ 20 | .fuse_hidden* 21 | .directory 22 | .Trash-* 23 | .idea/ 24 | cmake-build-*/ 25 | *.iws 26 | out/ 27 | .idea_modules/ 28 | atlassian-ide-plugin.xml 29 | .idea/replstate.xml 30 | com_crashlytics_export_strings.xml 31 | crashlytics.properties 32 | crashlytics-build.properties 33 | fabric.properties 34 | .idea/httpRequests 35 | *.exe 36 | *.exe~ 37 | *.dll 38 | *.so 39 | *.dylib 40 | *.test 41 | *.out 42 | example/kubernetes/id_* -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://github.com/kubernetes/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt: 4 | 5 | _As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._ 6 | 7 | ## Getting Started 8 | 9 | We have full documentation on how to get started contributing here: 10 | 11 | 14 | 15 | - [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests 16 | - [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing) 17 | - [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet.md) - Common resources for existing developers 18 | 19 | ## Mentorship 20 | 21 | - [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers! 22 | 23 | 32 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12-alpine3.9 AS build-env 2 | RUN apk add --no-cache git 3 | 4 | ENV CGO_ENABLED=0, GO111MODULE=on 5 | WORKDIR /go/src/github.com/chr-fritz/csi-sshfs 6 | 7 | ADD . /go/src/github.com/chr-fritz/csi-sshfs 8 | 9 | RUN go mod download 10 | RUN export BUILD_TIME=`date -R` && \ 11 | export VERSION=`cat /go/src/github.com/chr-fritz/csi-sshfs/version.txt 2&> /dev/null` && \ 12 | go build -o /csi-sshfs -ldflags "-X 'github.com/chr-fritz/csi-sshfs/pkg/sshfs.BuildTime=${BUILD_TIME}' -X 'github.com/chr-fritz/csi-sshfs/pkg/sshfs.Version=${VERSION}'" github.com/chr-fritz/csi-sshfs/cmd/csi-sshfs 13 | 14 | FROM alpine:3.9 15 | 16 | RUN apk add --no-cache ca-certificates sshfs findmnt 17 | 18 | COPY --from=build-env /csi-sshfs /bin/csi-sshfs 19 | 20 | ENTRYPOINT ["/bin/csi-sshfs"] 21 | CMD [""] -------------------------------------------------------------------------------- /Dockerfile.debug: -------------------------------------------------------------------------------- 1 | FROM golang:1.12-alpine3.9 AS build-env 2 | RUN apk add --no-cache git && \ 3 | go get github.com/derekparker/delve/cmd/dlv 4 | 5 | ENV CGO_ENABLED=0, GO111MODULE=on 6 | WORKDIR /go/src/github.com/chr-fritz/csi-sshfs 7 | 8 | ADD . /go/src/github.com/chr-fritz/csi-sshfs 9 | 10 | RUN go mod download 11 | RUN export BUILD_TIME=`date -R` && \ 12 | export VERSION=`cat /go/src/github.com/chr-fritz/csi-sshfs/version.txt 2&> /dev/null` 13 | RUN go build -o /csi-sshfs -ldflags "-X 'github.com/chr-fritz/csi-sshfs/pkg/sshfs.BuildTime=${BUILD_TIME}' -X 'github.com/chr-fritz/csi-sshfs/pkg/sshfs.Version=${VERSION}'" -gcflags "all=-N -l" github.com/chr-fritz/csi-sshfs/cmd/csi-sshfs 14 | 15 | FROM alpine:3.9 16 | EXPOSE 40000 17 | RUN apk add --no-cache ca-certificates sshfs findmnt libc6-compat 18 | 19 | COPY --from=build-env /csi-sshfs /bin/csi-sshfs 20 | COPY --from=build-env /go/bin/dlv / 21 | 22 | ENTRYPOINT ["/dlv", "--listen=:40000", "--headless=true", "--api-version=2", "exec", "/bin/csi-sshfs", "--"] 23 | CMD [""] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md 2 | 3 | approvers: 4 | - chr-fritz 5 | reviewers: 6 | - chr-fritz 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Container Storage Interface Driver for SSHFS 2 | 3 | **Warning: This is only a proof of concept and is not actively maintained. It should not be used in production environments!** 4 | 5 | This repository contains the CSI driver for SSHFS. It allows to mount directories using a ssh connection. 6 | 7 | ## Usage 8 | 9 | Deploy the whole directory `deploy/kubernetes`. 10 | This installs the csi controller and node plugin and a appropriate storage class for the csi driver. 11 | ```bash 12 | kubectl apply -f deploy/kubernetes 13 | ``` 14 | 15 | To use the csi driver create a persistent volume and persistent volume claim like the example one: 16 | ```yaml 17 | apiVersion: v1 18 | kind: PersistentVolume 19 | metadata: 20 | name: data-sshfs 21 | labels: 22 | name: data-sshfs 23 | spec: 24 | accessModes: 25 | - ReadWriteMany 26 | capacity: 27 | storage: 100Gi 28 | storageClassName: sshfs 29 | csi: 30 | driver: csi-sshfs 31 | volumeHandle: data-id 32 | volumeAttributes: 33 | server: "" 34 | port: "22" 35 | share: "" 36 | privateKey: "/" 37 | user: "" 38 | --- 39 | apiVersion: v1 40 | kind: PersistentVolumeClaim 41 | metadata: 42 | name: data-sshfs 43 | spec: 44 | accessModes: 45 | - ReadWriteMany 46 | resources: 47 | requests: 48 | storage: 100Gi 49 | storageClassName: sshfs 50 | selector: 51 | matchLabels: 52 | name: data-sshfs 53 | ``` 54 | 55 | Then mount the volume into a pod: 56 | ```yaml 57 | apiVersion: v1 58 | kind: Pod 59 | metadata: 60 | name: nginx 61 | spec: 62 | containers: 63 | - image: maersk/nginx 64 | imagePullPolicy: Always 65 | name: nginx 66 | ports: 67 | - containerPort: 80 68 | protocol: TCP 69 | volumeMounts: 70 | - mountPath: /var/www 71 | name: data-sshfs 72 | volumes: 73 | - name: data-sshfs 74 | persistentVolumeClaim: 75 | claimName: data-sshfs 76 | ``` 77 | -------------------------------------------------------------------------------- /SECURITY_CONTACTS: -------------------------------------------------------------------------------- 1 | # Defined below are the security contacts for this repo. 2 | # 3 | # They are the contact point for the Product Security Team to reach out 4 | # to for triaging and handling of incoming issues. 5 | # 6 | # The below names agree to abide by the 7 | # [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy) 8 | # and will be removed and replaced if they violate that agreement. 9 | # 10 | # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE 11 | # INSTRUCTIONS AT https://kubernetes.io/security/ 12 | 13 | chr-fritz -------------------------------------------------------------------------------- /cmd/csi-sshfs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/chr-fritz/csi-sshfs/pkg/sshfs" 7 | "os" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var ( 13 | endpoint string 14 | nodeID string 15 | ) 16 | 17 | func init() { 18 | flag.Set("logtostderr", "true") 19 | } 20 | 21 | func main() { 22 | 23 | flag.CommandLine.Parse([]string{}) 24 | 25 | cmd := &cobra.Command{ 26 | Use: "sshfs", 27 | Short: "CSI based SSHFS driver", 28 | Run: func(cmd *cobra.Command, args []string) { 29 | handle() 30 | }, 31 | } 32 | 33 | cmd.Flags().AddGoFlagSet(flag.CommandLine) 34 | 35 | cmd.PersistentFlags().StringVar(&nodeID, "nodeid", "", "node id") 36 | cmd.MarkPersistentFlagRequired("nodeid") 37 | 38 | cmd.PersistentFlags().StringVar(&endpoint, "endpoint", "", "CSI endpoint") 39 | cmd.MarkPersistentFlagRequired("endpoint") 40 | 41 | versionCmd := &cobra.Command{ 42 | Use: "version", 43 | Short: "Prints information about this version of csi sshfs plugin", 44 | Run: func(cmd *cobra.Command, args []string) { 45 | fmt.Printf(`CSI-SSHFS Plugin 46 | Version: %s 47 | Build Time: %s 48 | `, sshfs.Version, sshfs.BuildTime) 49 | }, 50 | } 51 | 52 | cmd.AddCommand(versionCmd) 53 | versionCmd.ResetFlags() 54 | 55 | cmd.ParseFlags(os.Args[1:]) 56 | if err := cmd.Execute(); err != nil { 57 | fmt.Fprintf(os.Stderr, "%s", err.Error()) 58 | os.Exit(1) 59 | } 60 | 61 | os.Exit(0) 62 | } 63 | 64 | func handle() { 65 | d := sshfs.NewDriver(nodeID, endpoint) 66 | d.Run() 67 | } 68 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Community Code of Conduct 2 | 3 | Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) 4 | -------------------------------------------------------------------------------- /deploy/kubernetes-debug/README.md: -------------------------------------------------------------------------------- 1 | # Debugging deployments 2 | 3 | The deployments in this directory are intended to be used for debugging. 4 | 5 | ## Usage 6 | 7 | Deploy the node plugin: 8 | ```bash 9 | kubectl apply -f csi-nodeplugin-sshfs-debug.yaml 10 | ``` 11 | 12 | When the pod is started it waits until a debugger connects to it before it will do anything. 13 | It will wait for debugging connections on NodePort `31040` and the CSI Socket interface on NodePort `31010`. 14 | 15 | Please refer your IDE's documentation for information about connecting. 16 | 17 | - IntelliJ & Goland: https://blog.jetbrains.com/go/2018/04/30/debugging-containerized-go-applications/ -------------------------------------------------------------------------------- /deploy/kubernetes-debug/csi-nodeplugin-sshfs-debug.yaml: -------------------------------------------------------------------------------- 1 | # This YAML file contains driver-registrar & csi driver nodeplugin API objects 2 | # that are necessary to run CSI nodeplugin for sshfs 3 | kind: DaemonSet 4 | apiVersion: apps/v1 5 | metadata: 6 | name: csi-nodeplugin-sshfs-debug 7 | namespace: csi-sshfs 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: csi-nodeplugin-sshfs 12 | template: 13 | metadata: 14 | labels: 15 | app: csi-nodeplugin-sshfs 16 | spec: 17 | serviceAccountName: csi-nodeplugin-sshfs 18 | hostNetwork: true 19 | containers: 20 | - name: sshfs 21 | securityContext: 22 | privileged: true 23 | capabilities: 24 | add: ["SYS_ADMIN","SYS_PTRACE"] 25 | allowPrivilegeEscalation: true 26 | image: chrfritz/csi-nodeplugin-sshfs:debug-latest 27 | command: 28 | - "/dlv" 29 | - "--listen=:40000" 30 | - "--headless=true" 31 | - "--api-version=2" 32 | - "exec" 33 | - "/bin/csi-sshfs" 34 | - "--" 35 | - "--nodeid=$(NODE_ID)" 36 | - "--endpoint=$(CSI_ENDPOINT)" 37 | env: 38 | - name: NODE_ID 39 | valueFrom: 40 | fieldRef: 41 | fieldPath: spec.nodeName 42 | - name: CSI_ENDPOINT 43 | value: tcp://0.0.0.0:10000 44 | ports: 45 | - containerPort: 10000 46 | protocol: TCP 47 | name: csi-endpoint 48 | - containerPort: 40000 49 | protocol: TCP 50 | name: debug 51 | imagePullPolicy: "Always" 52 | volumeMounts: 53 | - name: plugin-dir 54 | mountPath: /plugin 55 | - name: pods-mount-dir 56 | mountPath: /var/lib/kubelet/pods 57 | mountPropagation: "Bidirectional" 58 | volumes: 59 | - name: plugin-dir 60 | hostPath: 61 | path: /var/lib/kubelet/plugins/csi-sshfs 62 | type: DirectoryOrCreate 63 | - name: pods-mount-dir 64 | hostPath: 65 | path: /var/lib/kubelet/pods 66 | type: Directory 67 | - hostPath: 68 | path: /var/lib/kubelet/plugins_registry 69 | type: Directory 70 | name: registration-dir 71 | --- 72 | kind: Service 73 | apiVersion: v1 74 | metadata: 75 | name: csi-nodeplugin-sshfs-debug 76 | namespace: csi-sshfs 77 | spec: 78 | type: NodePort 79 | ports: 80 | - port: 40000 81 | targetPort: debug 82 | nodePort: 31040 83 | name: debug 84 | - targetPort: csi-endpoint 85 | nodePort: 31010 86 | port: 10000 87 | name: csi-endpoint 88 | selector: 89 | app: csi-nodeplugin-sshfs -------------------------------------------------------------------------------- /deploy/kubernetes/_csi-sshfs-namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: csi-sshfs -------------------------------------------------------------------------------- /deploy/kubernetes/csi-controller-rbac.yaml: -------------------------------------------------------------------------------- 1 | # This YAML file contains RBAC API objects that are necessary to run external 2 | # CSI attacher for sshfs adapter 3 | 4 | apiVersion: v1 5 | kind: ServiceAccount 6 | metadata: 7 | name: csi-controller-sshfs 8 | namespace: csi-sshfs 9 | --- 10 | kind: ClusterRole 11 | apiVersion: rbac.authorization.k8s.io/v1 12 | metadata: 13 | name: external-controller-sshfs 14 | rules: 15 | - apiGroups: [""] 16 | resources: ["persistentvolumes"] 17 | verbs: ["get", "list", "watch", "update"] 18 | - apiGroups: [""] 19 | resources: ["nodes"] 20 | verbs: ["get", "list", "watch"] 21 | - apiGroups: ["csi.storage.k8s.io"] 22 | resources: ["csinodeinfos"] 23 | verbs: ["get", "list", "watch"] 24 | - apiGroups: ["storage.k8s.io"] 25 | resources: ["volumeattachments"] 26 | verbs: ["get", "list", "watch", "update"] 27 | 28 | --- 29 | kind: ClusterRoleBinding 30 | apiVersion: rbac.authorization.k8s.io/v1 31 | metadata: 32 | name: csi-attacher-role-sshfs 33 | subjects: 34 | - kind: ServiceAccount 35 | name: csi-controller-sshfs 36 | namespace: csi-sshfs 37 | roleRef: 38 | kind: ClusterRole 39 | name: external-controller-sshfs 40 | apiGroup: rbac.authorization.k8s.io 41 | --- 42 | kind: ClusterRole 43 | apiVersion: rbac.authorization.k8s.io/v1 44 | metadata: 45 | name: csi-cluster-driver-registrar-role 46 | rules: 47 | - apiGroups: ["csi.storage.k8s.io"] 48 | resources: ["csidrivers"] 49 | verbs: ["create", "delete"] 50 | - apiGroups: ["apiextensions.k8s.io"] 51 | resources: ["customresourcedefinitions"] 52 | verbs: ["create", "list", "watch", "delete"] 53 | 54 | --- 55 | kind: ClusterRoleBinding 56 | apiVersion: rbac.authorization.k8s.io/v1 57 | metadata: 58 | name: csi-cluster-driver-registrar-binding 59 | subjects: 60 | - kind: ServiceAccount 61 | name: csi-controller-sshfs 62 | namespace: csi-sshfs 63 | roleRef: 64 | kind: ClusterRole 65 | name: csi-cluster-driver-registrar-role 66 | apiGroup: rbac.authorization.k8s.io -------------------------------------------------------------------------------- /deploy/kubernetes/csi-controller-sshfs.yaml: -------------------------------------------------------------------------------- 1 | # This YAML file contains attacher & csi driver API objects that are necessary 2 | # to run external CSI attacher for sshfs 3 | 4 | kind: StatefulSet 5 | apiVersion: apps/v1 6 | metadata: 7 | name: csi-controller-sshfs 8 | namespace: csi-sshfs 9 | spec: 10 | serviceName: "csi-controller-sshfs" 11 | replicas: 1 12 | selector: 13 | matchLabels: 14 | app: csi-controller-sshfs 15 | template: 16 | metadata: 17 | labels: 18 | app: csi-controller-sshfs 19 | spec: 20 | serviceAccountName: csi-controller-sshfs 21 | containers: 22 | - name: csi-attacher 23 | image: quay.io/k8scsi/csi-attacher:v1.0.1 24 | args: 25 | - "--v=5" 26 | - "--csi-address=$(ADDRESS)" 27 | env: 28 | - name: ADDRESS 29 | value: /csi/csi.sock 30 | imagePullPolicy: "Always" 31 | volumeMounts: 32 | - name: socket-dir 33 | mountPath: /csi 34 | - name: csi-cluster-driver-registrar 35 | image: quay.io/k8scsi/csi-cluster-driver-registrar:v1.0.1 36 | args: 37 | - "--v=5" 38 | - "--pod-info-mount-version=\"v1\"" 39 | - "--csi-address=$(ADDRESS)" 40 | env: 41 | - name: ADDRESS 42 | value: /csi/csi.sock 43 | volumeMounts: 44 | - name: socket-dir 45 | mountPath: /csi 46 | - name: sshfs 47 | image: chrfritz/csi-sshfs:latest 48 | args : 49 | - "--nodeid=$(NODE_ID)" 50 | - "--endpoint=$(CSI_ENDPOINT)" 51 | env: 52 | - name: NODE_ID 53 | valueFrom: 54 | fieldRef: 55 | fieldPath: spec.nodeName 56 | - name: CSI_ENDPOINT 57 | value: unix://plugin/csi.sock 58 | imagePullPolicy: "Always" 59 | volumeMounts: 60 | - name: socket-dir 61 | mountPath: /plugin 62 | volumes: 63 | - name: socket-dir 64 | emptyDir: 65 | -------------------------------------------------------------------------------- /deploy/kubernetes/csi-nodeplugin-rbac.yaml: -------------------------------------------------------------------------------- 1 | # This YAML defines all API objects to create RBAC roles for CSI node plugin 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: csi-nodeplugin-sshfs 6 | namespace: csi-sshfs 7 | --- 8 | kind: ClusterRole 9 | apiVersion: rbac.authorization.k8s.io/v1 10 | metadata: 11 | name: csi-nodeplugin-sshfs 12 | rules: 13 | - apiGroups: [""] 14 | resources: ["persistentvolumes"] 15 | verbs: ["get", "list", "watch", "update"] 16 | - apiGroups: [""] 17 | resources: ["secrets","secret"] 18 | verbs: ["get", "list"] 19 | - apiGroups: [""] 20 | resources: ["nodes"] 21 | verbs: ["get", "list", "watch", "update"] 22 | - apiGroups: ["storage.k8s.io"] 23 | resources: ["volumeattachments"] 24 | verbs: ["get", "list", "watch", "update"] 25 | - apiGroups: [""] 26 | resources: ["events"] 27 | verbs: ["get", "list", "watch", "create", "update", "patch"] 28 | --- 29 | kind: ClusterRoleBinding 30 | apiVersion: rbac.authorization.k8s.io/v1 31 | metadata: 32 | name: csi-nodeplugin-sshfs 33 | subjects: 34 | - kind: ServiceAccount 35 | name: csi-nodeplugin-sshfs 36 | namespace: csi-sshfs 37 | roleRef: 38 | kind: ClusterRole 39 | name: csi-nodeplugin-sshfs 40 | apiGroup: rbac.authorization.k8s.io 41 | -------------------------------------------------------------------------------- /deploy/kubernetes/csi-nodeplugin-sshfs.yaml: -------------------------------------------------------------------------------- 1 | # This YAML file contains driver-registrar & csi driver nodeplugin API objects 2 | # that are necessary to run CSI nodeplugin for sshfs 3 | kind: DaemonSet 4 | apiVersion: apps/v1 5 | metadata: 6 | name: csi-nodeplugin-sshfs 7 | namespace: csi-sshfs 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: csi-nodeplugin-sshfs 12 | template: 13 | metadata: 14 | labels: 15 | app: csi-nodeplugin-sshfs 16 | spec: 17 | serviceAccountName: csi-nodeplugin-sshfs 18 | hostNetwork: true 19 | containers: 20 | - name: node-driver-registrar 21 | image: quay.io/k8scsi/csi-node-driver-registrar:v1.0.2 22 | lifecycle: 23 | preStop: 24 | exec: 25 | command: ["/bin/sh", "-c", "rm -rf /registration/csi-sshfs /registration/csi-sshfs-reg.sock"] 26 | args: 27 | - --v=5 28 | - --csi-address=/plugin/csi.sock 29 | - --kubelet-registration-path=/var/lib/kubelet/plugins/csi-sshfs/csi.sock 30 | env: 31 | - name: KUBE_NODE_NAME 32 | valueFrom: 33 | fieldRef: 34 | fieldPath: spec.nodeName 35 | volumeMounts: 36 | - name: plugin-dir 37 | mountPath: /plugin 38 | - name: registration-dir 39 | mountPath: /registration 40 | - name: sshfs 41 | securityContext: 42 | privileged: true 43 | capabilities: 44 | add: ["SYS_ADMIN"] 45 | allowPrivilegeEscalation: true 46 | image: chrfritz/csi-sshfs:latest 47 | args: 48 | - "--nodeid=$(NODE_ID)" 49 | - "--endpoint=$(CSI_ENDPOINT)" 50 | env: 51 | - name: NODE_ID 52 | valueFrom: 53 | fieldRef: 54 | fieldPath: spec.nodeName 55 | - name: CSI_ENDPOINT 56 | value: unix://plugin/csi.sock 57 | imagePullPolicy: "Always" 58 | volumeMounts: 59 | - name: plugin-dir 60 | mountPath: /plugin 61 | - name: pods-mount-dir 62 | mountPath: /var/lib/kubelet/pods 63 | mountPropagation: "Bidirectional" 64 | volumes: 65 | - name: plugin-dir 66 | hostPath: 67 | path: /var/lib/kubelet/plugins/csi-sshfs 68 | type: DirectoryOrCreate 69 | - name: pods-mount-dir 70 | hostPath: 71 | path: /var/lib/kubelet/pods 72 | type: Directory 73 | - hostPath: 74 | path: /var/lib/kubelet/plugins_registry 75 | type: DirectoryOrCreate 76 | name: registration-dir 77 | -------------------------------------------------------------------------------- /deploy/kubernetes/csi-sshfs-storageclass.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: storage.k8s.io/v1 2 | kind: StorageClass 3 | metadata: 4 | name: sshfs 5 | namespace: csi-sshfs 6 | provisioner: kubernetes.io/no-provisioner 7 | -------------------------------------------------------------------------------- /example/kubernetes/nginx.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolume 3 | metadata: 4 | name: data-sshfs 5 | labels: 6 | name: data-sshfs 7 | spec: 8 | accessModes: 9 | - ReadWriteMany 10 | capacity: 11 | storage: 100Gi 12 | storageClassName: sshfs 13 | csi: 14 | driver: csi-sshfs 15 | volumeHandle: data-id 16 | volumeAttributes: 17 | server: "10.135.151.63" 18 | port: "22" 19 | share: "/root/testvol" 20 | privateKey: "default/testvol" 21 | user: "root" 22 | --- 23 | apiVersion: v1 24 | kind: PersistentVolumeClaim 25 | metadata: 26 | name: data-sshfs 27 | spec: 28 | accessModes: 29 | - ReadWriteMany 30 | resources: 31 | requests: 32 | storage: 100Gi 33 | storageClassName: sshfs 34 | selector: 35 | matchLabels: 36 | name: data-sshfs 37 | --- 38 | apiVersion: v1 39 | kind: Pod 40 | metadata: 41 | name: nginx 42 | spec: 43 | containers: 44 | - image: maersk/nginx 45 | imagePullPolicy: Always 46 | name: nginx 47 | ports: 48 | - containerPort: 80 49 | protocol: TCP 50 | volumeMounts: 51 | - mountPath: /var/www 52 | name: data-sshfs 53 | volumes: 54 | - name: data-sshfs 55 | persistentVolumeClaim: 56 | claimName: data-sshfs 57 | -------------------------------------------------------------------------------- /example/kubernetes/testvol-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: testvol 5 | namespace: default 6 | type: kubernetes.io/ssh-auth 7 | data: 8 | ssh-privatekey: # add your SSH-Private Key base64 encoded -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/chr-fritz/csi-sshfs 2 | 3 | require ( 4 | github.com/container-storage-interface/spec v1.0.0 5 | github.com/davecgh/go-spew v1.1.1 // indirect 6 | github.com/docker/distribution v2.7.1+incompatible // indirect 7 | github.com/gogo/protobuf v1.2.1 // indirect 8 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b 9 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect 10 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect 11 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect 12 | github.com/googleapis/gnostic v0.2.0 // indirect 13 | github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect 14 | github.com/hashicorp/golang-lru v0.5.0 // indirect 15 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 16 | github.com/json-iterator/go v1.1.5 // indirect 17 | github.com/kubernetes-csi/csi-lib-utils v0.3.1 // indirect 18 | github.com/kubernetes-csi/drivers v1.0.2 19 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 20 | github.com/modern-go/reflect2 v1.0.1 // indirect 21 | github.com/onsi/ginkgo v1.7.0 // indirect 22 | github.com/onsi/gomega v1.4.3 // indirect 23 | github.com/opencontainers/go-digest v1.0.0-rc1 // indirect 24 | github.com/pborman/uuid v1.2.0 // indirect 25 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect 26 | github.com/prometheus/client_golang v0.9.2 // indirect 27 | github.com/spf13/afero v1.2.1 // indirect 28 | github.com/spf13/cobra v0.0.3 29 | github.com/spf13/pflag v1.0.3 // indirect 30 | github.com/stretchr/objx v0.1.1 // indirect 31 | github.com/stretchr/testify v1.3.0 // indirect 32 | golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f // indirect 33 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd 34 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect 35 | google.golang.org/grpc v1.18.0 36 | gopkg.in/inf.v0 v0.9.1 // indirect 37 | gopkg.in/yaml.v2 v2.2.2 // indirect 38 | k8s.io/api v0.0.0-20190111032252-67edc246be36 39 | k8s.io/apiextensions-apiserver v0.0.0-20190111034747-7d26de67f177 // indirect 40 | k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93 41 | k8s.io/apiserver v0.0.0-20190111033246-d50e9ac5404f // indirect 42 | k8s.io/client-go v10.0.0+incompatible 43 | k8s.io/cloud-provider v0.0.0-20190223141949-e954a34baf43 // indirect 44 | k8s.io/csi-api v0.0.0-20190223140843-b4e64dae0b19 // indirect 45 | k8s.io/klog v0.2.0 // indirect 46 | k8s.io/kube-openapi v0.0.0-20190222203931-aa8624f5a2df // indirect 47 | k8s.io/kubernetes v1.13.2 48 | k8s.io/utils v0.0.0-20190221042446-c2654d5206da // indirect 49 | sigs.k8s.io/yaml v1.1.0 // indirect 50 | ) 51 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= 3 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 5 | github.com/container-storage-interface/spec v1.0.0 h1:3DyXuJgf9MU6kyULESegQUmozsSxhpyrrv9u5bfwA3E= 6 | github.com/container-storage-interface/spec v1.0.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4= 7 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= 12 | github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 13 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 14 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 15 | github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= 16 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 17 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 18 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 19 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= 20 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 21 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 22 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 23 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 24 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= 25 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 26 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= 27 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 28 | github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= 29 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 30 | github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= 31 | github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 32 | github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q= 33 | github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 34 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= 35 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 36 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 37 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 38 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 39 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 40 | github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= 41 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 42 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 43 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 44 | github.com/kubernetes-csi/csi-lib-utils v0.3.1 h1:EPE7WgaMx8XwfBIdxJns3B87V0x8TN1mWoZOVNliUaM= 45 | github.com/kubernetes-csi/csi-lib-utils v0.3.1/go.mod h1:GVmlUmxZ+SUjVLXicRFjqWUUvWez0g0Y78zNV9t7KfQ= 46 | github.com/kubernetes-csi/drivers v1.0.2 h1:kaEAMfo+W5YFr23yedBIY+NGnNjr6/PbPzx7N4GYgiQ= 47 | github.com/kubernetes-csi/drivers v1.0.2/go.mod h1:V6rHbbSLCZGaQoIZ8MkyDtoXtcKXZM0F7N3bkloDCOY= 48 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 49 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 50 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 51 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 52 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 53 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 54 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 55 | github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= 56 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 57 | github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= 58 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 59 | github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= 60 | github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 61 | github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= 62 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 63 | github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= 64 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 65 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 66 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 67 | github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= 68 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= 69 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= 70 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 71 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8= 72 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 73 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE= 74 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 75 | github.com/spf13/afero v1.2.1 h1:qgMbHoJbPbw579P+1zVY+6n4nIFuIchaIjzZ/I/Yq8M= 76 | github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 77 | github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= 78 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 79 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 80 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 81 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 82 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 83 | github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= 84 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 85 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 86 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 87 | golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f h1:qWFY9ZxP3tfI37wYIs/MnIAqK0vlXp1xnYEa5HxFSSY= 88 | golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 89 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 90 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 91 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 92 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 93 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU= 94 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 95 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= 96 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 97 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 98 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 99 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= 100 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 101 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE= 102 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 103 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= 104 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 105 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 106 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 107 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg= 108 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 109 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 110 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 111 | google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= 112 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 113 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= 114 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 115 | google.golang.org/grpc v1.18.0 h1:IZl7mfBGfbhYx2p2rKRtYgDFw6SBz+kclmxYrCksPPA= 116 | google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 117 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 118 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 119 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 120 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 121 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 122 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 123 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 124 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 125 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 126 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 127 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 128 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 129 | k8s.io/api v0.0.0-20190111032252-67edc246be36 h1:XrFGq/4TDgOxYOxtNROTyp2ASjHjBIITdk/+aJD+zyY= 130 | k8s.io/api v0.0.0-20190111032252-67edc246be36/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= 131 | k8s.io/apiextensions-apiserver v0.0.0-20190111034747-7d26de67f177 h1:jtIDnyMLAy15hJmcjRMq3ia0LwHkQBLVo1IRXdDMS38= 132 | k8s.io/apiextensions-apiserver v0.0.0-20190111034747-7d26de67f177/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= 133 | k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93 h1:tT6oQBi0qwLbbZSfDkdIsb23EwaLY85hoAV4SpXfdao= 134 | k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= 135 | k8s.io/apiserver v0.0.0-20190111033246-d50e9ac5404f h1:jOhsBtH52EgxnCNJrCuToXFfQtb3nQDoBPzItfPmSsI= 136 | k8s.io/apiserver v0.0.0-20190111033246-d50e9ac5404f/go.mod h1:6bqaTSOSJavUIXUtfaR9Os9JtTCm8ZqH2SUl2S60C4w= 137 | k8s.io/client-go v10.0.0+incompatible h1:F1IqCqw7oMBzDkqlcBymRq1450wD0eNqLE9jzUrIi34= 138 | k8s.io/client-go v10.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= 139 | k8s.io/cloud-provider v0.0.0-20190223141949-e954a34baf43 h1:JRJFItrctyI8kGWwa9sqZ34eX84t3/D5fO119I4x3Cw= 140 | k8s.io/cloud-provider v0.0.0-20190223141949-e954a34baf43/go.mod h1:LlIffnLBu+GG7d4ppPzC8UnA1Ex8S+ntmSRVsnr7Xy4= 141 | k8s.io/csi-api v0.0.0-20190223140843-b4e64dae0b19 h1:RmLaI0waIeLhtsOQl8ekcCgbHRscA3+nQGSFf3qkbeA= 142 | k8s.io/csi-api v0.0.0-20190223140843-b4e64dae0b19/go.mod h1:GH854hXKH+vaEO06X/DMiE/o3rVO1aw8dXJJpP7awjA= 143 | k8s.io/klog v0.2.0 h1:0ElL0OHzF3N+OhoJTL0uca20SxtYt4X4+bzHeqrB83c= 144 | k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 145 | k8s.io/kube-openapi v0.0.0-20190222203931-aa8624f5a2df h1:htvtrqyyVqMSYjR5bmmLx7RVu2+Rp5o1RVOc51x2YrQ= 146 | k8s.io/kube-openapi v0.0.0-20190222203931-aa8624f5a2df/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= 147 | k8s.io/kubernetes v1.13.2 h1:rBz6dubDY4bfv85G6zo04v9G5wniTxvBI9yQ/QxJS3g= 148 | k8s.io/kubernetes v1.13.2/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= 149 | k8s.io/utils v0.0.0-20190221042446-c2654d5206da h1:ElyM7RPonbKnQqOcw7dG2IK5uvQQn3b/WPHqD5mBvP4= 150 | k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= 151 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 152 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 153 | -------------------------------------------------------------------------------- /pkg/sshfs/driver.go: -------------------------------------------------------------------------------- 1 | package sshfs 2 | 3 | import ( 4 | "github.com/container-storage-interface/spec/lib/go/csi" 5 | "github.com/golang/glog" 6 | "github.com/kubernetes-csi/drivers/pkg/csi-common" 7 | ) 8 | 9 | type driver struct { 10 | csiDriver *csicommon.CSIDriver 11 | endpoint string 12 | 13 | //ids *identityServer 14 | ns *nodeServer 15 | cap []*csi.VolumeCapability_AccessMode 16 | cscap []*csi.ControllerServiceCapability 17 | } 18 | 19 | const ( 20 | driverName = "csi-sshfs" 21 | ) 22 | 23 | var ( 24 | Version = "latest" 25 | BuildTime = "1970-01-01 00:00:00" 26 | ) 27 | 28 | func NewDriver(nodeID, endpoint string) *driver { 29 | glog.Infof("Starting new %s driver in version %s built %s", driverName, Version, BuildTime) 30 | 31 | d := &driver{} 32 | 33 | d.endpoint = endpoint 34 | 35 | csiDriver := csicommon.NewCSIDriver(driverName, Version, nodeID) 36 | csiDriver.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}) 37 | // SSHFS plugin does not support ControllerServiceCapability now. 38 | // If support is added, it should set to appropriate 39 | // ControllerServiceCapability RPC types. 40 | csiDriver.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{csi.ControllerServiceCapability_RPC_UNKNOWN}) 41 | 42 | d.csiDriver = csiDriver 43 | 44 | return d 45 | } 46 | 47 | func NewNodeServer(d *driver) *nodeServer { 48 | return &nodeServer{ 49 | DefaultNodeServer: csicommon.NewDefaultNodeServer(d.csiDriver), 50 | mounts: map[string]*mountPoint{}, 51 | } 52 | } 53 | 54 | func (d *driver) Run() { 55 | s := csicommon.NewNonBlockingGRPCServer() 56 | s.Start(d.endpoint, 57 | csicommon.NewDefaultIdentityServer(d.csiDriver), 58 | // SSHFS plugin has not implemented ControllerServer 59 | // using default controllerserver. 60 | csicommon.NewDefaultControllerServer(d.csiDriver), 61 | NewNodeServer(d)) 62 | s.Wait() 63 | } 64 | -------------------------------------------------------------------------------- /pkg/sshfs/k8sClient.go: -------------------------------------------------------------------------------- 1 | package sshfs 2 | 3 | import ( 4 | "k8s.io/client-go/kubernetes" 5 | "k8s.io/client-go/rest" 6 | ) 7 | 8 | var clientset *kubernetes.Clientset 9 | 10 | func GetK8sClient() (*kubernetes.Clientset, error) { 11 | if clientset != nil { 12 | return clientset, nil 13 | } 14 | 15 | config, e := rest.InClusterConfig() 16 | if e != nil { 17 | return nil, e 18 | } 19 | 20 | clientset, e = kubernetes.NewForConfig(config) 21 | if e != nil { 22 | return nil, e 23 | } 24 | return clientset, nil 25 | } 26 | -------------------------------------------------------------------------------- /pkg/sshfs/nodeserver.go: -------------------------------------------------------------------------------- 1 | package sshfs 2 | 3 | import ( 4 | "fmt" 5 | "github.com/golang/glog" 6 | "io/ioutil" 7 | "k8s.io/api/core/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "os" 10 | "os/exec" 11 | "strings" 12 | 13 | "github.com/container-storage-interface/spec/lib/go/csi" 14 | "golang.org/x/net/context" 15 | "google.golang.org/grpc/codes" 16 | "google.golang.org/grpc/status" 17 | "k8s.io/kubernetes/pkg/util/mount" 18 | "k8s.io/kubernetes/pkg/volume/util" 19 | 20 | "github.com/kubernetes-csi/drivers/pkg/csi-common" 21 | ) 22 | 23 | type nodeServer struct { 24 | *csicommon.DefaultNodeServer 25 | mounts map[string]*mountPoint 26 | } 27 | 28 | type mountPoint struct { 29 | VolumeId string 30 | MountPath string 31 | IdentityFile string 32 | } 33 | 34 | func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { 35 | targetPath := req.GetTargetPath() 36 | notMnt, e := mount.New("").IsLikelyNotMountPoint(targetPath) 37 | if e != nil { 38 | if os.IsNotExist(e) { 39 | if err := os.MkdirAll(targetPath, 0750); err != nil { 40 | return nil, status.Error(codes.Internal, err.Error()) 41 | } 42 | notMnt = true 43 | } else { 44 | return nil, status.Error(codes.Internal, e.Error()) 45 | } 46 | } 47 | 48 | if !notMnt { 49 | return &csi.NodePublishVolumeResponse{}, nil 50 | } 51 | 52 | mountOptions := req.GetVolumeCapability().GetMount().GetMountFlags() 53 | if req.GetReadonly() { 54 | mountOptions = append(mountOptions, "ro") 55 | } 56 | if e := validateVolumeContext(req); e != nil { 57 | return nil, e 58 | } 59 | 60 | server := req.GetVolumeContext()["server"] 61 | port := req.GetVolumeContext()["port"] 62 | if len(port) == 0 { 63 | port = "22" 64 | } 65 | 66 | user := req.GetVolumeContext()["user"] 67 | ep := req.GetVolumeContext()["share"] 68 | privateKey := req.GetVolumeContext()["privateKey"] 69 | sshOpts := req.GetVolumeContext()["sshOpts"] 70 | 71 | secret, e := getPublicKeySecret(privateKey) 72 | if e != nil { 73 | return nil, e 74 | } 75 | privateKeyPath, e := writePrivateKey(secret) 76 | if e != nil { 77 | return nil, e 78 | } 79 | 80 | e = Mount(user, server, port, ep, targetPath, privateKeyPath, sshOpts) 81 | if e != nil { 82 | if os.IsPermission(e) { 83 | return nil, status.Error(codes.PermissionDenied, e.Error()) 84 | } 85 | if strings.Contains(e.Error(), "invalid argument") { 86 | return nil, status.Error(codes.InvalidArgument, e.Error()) 87 | } 88 | return nil, status.Error(codes.Internal, e.Error()) 89 | } 90 | ns.mounts[req.VolumeId] = &mountPoint{IdentityFile: privateKeyPath, MountPath: targetPath, VolumeId: req.VolumeId} 91 | return &csi.NodePublishVolumeResponse{}, nil 92 | } 93 | 94 | func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) { 95 | targetPath := req.GetTargetPath() 96 | notMnt, err := mount.New("").IsLikelyNotMountPoint(targetPath) 97 | 98 | if err != nil { 99 | if os.IsNotExist(err) { 100 | return nil, status.Error(codes.NotFound, "Targetpath not found") 101 | } else { 102 | return nil, status.Error(codes.Internal, err.Error()) 103 | } 104 | } 105 | if notMnt { 106 | return nil, status.Error(codes.NotFound, "Volume not mounted") 107 | } 108 | 109 | err = util.UnmountPath(req.GetTargetPath(), mount.New("")) 110 | if err != nil { 111 | return nil, status.Error(codes.Internal, err.Error()) 112 | } 113 | if point, ok := ns.mounts[req.VolumeId]; ok { 114 | err := os.Remove(point.IdentityFile) 115 | if err != nil { 116 | return nil, status.Error(codes.Internal, err.Error()) 117 | } 118 | delete(ns.mounts, point.VolumeId) 119 | glog.Infof("successfully unmount volume: %s", point) 120 | } 121 | 122 | return &csi.NodeUnpublishVolumeResponse{}, nil 123 | } 124 | 125 | func (ns *nodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) { 126 | return &csi.NodeUnstageVolumeResponse{}, nil 127 | } 128 | 129 | func (ns *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { 130 | return &csi.NodeStageVolumeResponse{}, nil 131 | } 132 | 133 | func validateVolumeContext(req *csi.NodePublishVolumeRequest) error { 134 | if _, ok := req.GetVolumeContext()["server"]; !ok { 135 | return status.Errorf(codes.InvalidArgument, "missing volume context value: server") 136 | } 137 | if _, ok := req.GetVolumeContext()["user"]; !ok { 138 | return status.Errorf(codes.InvalidArgument, "missing volume context value: user") 139 | } 140 | if _, ok := req.GetVolumeContext()["share"]; !ok { 141 | return status.Errorf(codes.InvalidArgument, "missing volume context value: share") 142 | } 143 | if _, ok := req.GetVolumeContext()["privateKey"]; !ok { 144 | return status.Errorf(codes.InvalidArgument, "missing volume context value: privateKey") 145 | } 146 | return nil 147 | } 148 | 149 | func getPublicKeySecret(secretName string) (*v1.Secret, error) { 150 | namespaceAndSecret := strings.SplitN(secretName, "/", 2) 151 | namespace := namespaceAndSecret[0] 152 | name := namespaceAndSecret[1] 153 | 154 | clientset, e := GetK8sClient() 155 | if e != nil { 156 | return nil, status.Errorf(codes.Internal, "can not create kubernetes client: %s", e) 157 | } 158 | 159 | secret, e := clientset.CoreV1(). 160 | Secrets(namespace). 161 | Get(name, metav1.GetOptions{}) 162 | 163 | if e != nil { 164 | return nil, status.Errorf(codes.Internal, "can not get secret %s: %s", secretName, e) 165 | } 166 | 167 | if secret.Type != v1.SecretTypeSSHAuth { 168 | return nil, status.Errorf(codes.InvalidArgument, "type of secret %s is not %s", secretName, v1.SecretTypeSSHAuth) 169 | } 170 | return secret, nil 171 | } 172 | 173 | func writePrivateKey(secret *v1.Secret) (string, error) { 174 | f, e := ioutil.TempFile("", "pk-*") 175 | defer f.Close() 176 | if e != nil { 177 | return "", status.Errorf(codes.Internal, "can not create tmp file for pk: %s", e) 178 | } 179 | 180 | _, e = f.Write(secret.Data[v1.SSHAuthPrivateKey]) 181 | if e != nil { 182 | return "", status.Errorf(codes.Internal, "can not create tmp file for pk: %s", e) 183 | } 184 | e = f.Chmod(0600) 185 | if e != nil { 186 | return "", status.Errorf(codes.Internal, "can not change rights for pk: %s", e) 187 | } 188 | return f.Name(), nil 189 | } 190 | 191 | func Mount(user string, host string, port string, dir string, target string, privateKey string, sshOpts string) error { 192 | mountCmd := "sshfs" 193 | mountArgs := []string{} 194 | 195 | source := fmt.Sprintf("%s@%s:%s", user, host, dir) 196 | mountArgs = append( 197 | mountArgs, 198 | source, 199 | target, 200 | "-o", "port="+port, 201 | "-o", "IdentityFile="+privateKey, 202 | "-o", "StrictHostKeyChecking=accept-new", 203 | "-o", "UserKnownHostsFile=/dev/null", 204 | ) 205 | 206 | if len(sshOpts) > 0 { 207 | mountArgs = append(mountArgs, "-o", sshOpts) 208 | } 209 | 210 | // create target, os.Mkdirall is noop if it exists 211 | err := os.MkdirAll(target, 0750) 212 | if err != nil { 213 | return err 214 | } 215 | 216 | glog.Infof("executing mount command cmd=%s, args=%s", mountCmd, mountArgs) 217 | 218 | out, err := exec.Command(mountCmd, mountArgs...).CombinedOutput() 219 | if err != nil { 220 | return fmt.Errorf("mounting failed: %v cmd: '%s %s' output: %q", 221 | err, mountCmd, strings.Join(mountArgs, " "), string(out)) 222 | } 223 | 224 | return nil 225 | } 226 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 0.1.0-alpha --------------------------------------------------------------------------------