[
  {
    "path": "LICENSE",
    "content": "Copyright 2019 Fabian Stäber (https://github.com/fstab/cifs)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "CIFS Flexvolume Plugin for Kubernetes\n=====================================\n\nDriver for [CIFS][1] (SMB, Samba, Windows Share) network filesystems as [Kubernetes volumes][2].\n\nBackground\n----------\n\nDocker containers running in Kubernetes have an ephemeral file system: Once a container is terminated, all files are gone. In order to store persistent data in Kubernetes, you need to mount a [Persistent Volume][3] into your container. Kubernetes has built-in support for network filesystems found in the most common cloud providers, like [Amazon's EBS][4], [Microsoft's Azure disk][5], etc. However, some cloud hosting services, like the [Hetzner cloud][6], provide network storage using the CIFS (SMB, Samba, Windows Share) protocol, which is not natively supported in Kubernetes.\n\nFortunately, Kubernetes provides [Flexvolume][7], which is a plugin mechanism enabling users to write their own drivers. There are a few flexvolume drivers for CIFS out there, but for different reasons none of them seemed to work for me. So I wrote my own, which can be found on [github.com/fstab/cifs][8].\n\nInstalling\n----------\n\nThe flexvolume plugin is a single shell script named [cifs][8]. This shell script must be available on the Kubernetes master and on each of the Kubernetes nodes. By default, Kubernetes searches for third party volume plugins in `/usr/libexec/kubernetes/kubelet-plugins/volume/exec/`. The plugin directory can be configured with the kubelet's `--volume-plugin-dir` parameter, run `ps aux | grep kubelet` to learn the location of the plugin directory on your system (see [#1][9]). The `cifs` script must be located in a subdirectory named `fstab~cifs/`. The directory name `fstab~cifs/` will be [mapped][10] to the Flexvolume driver name `fstab/cifs`.\n\nOn the Kubernetes master and on each Kubernetes node run the following commands:\n\n```bash\nVOLUME_PLUGIN_DIR=\"/usr/libexec/kubernetes/kubelet-plugins/volume/exec\"\nmkdir -p \"$VOLUME_PLUGIN_DIR/fstab~cifs\"\ncd \"$VOLUME_PLUGIN_DIR/fstab~cifs\"\ncurl -L -O https://raw.githubusercontent.com/fstab/cifs/master/cifs\nchmod 755 cifs\n```\n\nThe `cifs` script requires a few executables to be available on each host system:\n\n* `mount.cifs`, on Ubuntu this is in the [cifs-utils][11] package.\n* `jq`, on Ubuntu this is in the [jq][12] package.\n* `mountpoint`, on Ubuntu this is in the [util-linux][13] package.\n* `base64`, on Ubuntu this is in the [coreutils][14] package.\n\nTo check if the installation was successful, run the following command:\n\n```bash\nVOLUME_PLUGIN_DIR=\"/usr/libexec/kubernetes/kubelet-plugins/volume/exec\"\n$VOLUME_PLUGIN_DIR/fstab~cifs/cifs init\n```\n\nIt should output a JSON string containing `\"status\": \"Success\"`. This command is also run by Kubernetes itself when the cifs plugin is detected on the file system.\n\nRunning\n-------\n\nThe plugin takes the CIFS username and password from a [Kubernetes Secret][15]. To create the secret, you first have to convert your username and password to base64 encoding:\n\n```bash\necho -n username | base64\necho -n password | base64\n```\n\nThen, create a file `secret.yml` and use the ouput of the above commands as username and password:\n\n```yaml\napiVersion: v1\nkind: Secret\nmetadata:\n  name: cifs-secret\n  namespace: default\ntype: fstab/cifs\ndata:\n  username: 'ZXhhbXBsZQ=='\n  password: 'bXktc2VjcmV0LXBhc3N3b3Jk'\n```\n\nApply the secret:\n\n```bash\nkubectl apply -f secret.yml\n```\n\nYou can check if the secret was installed successfully using `kubectl describe secret cifs-secret`.\n\nNext, create a file `pod.yml` with a test pod (replace `//server/share` with the network path of your CIFS share):\n\n```yaml\napiVersion: v1\nkind: Pod\nmetadata:\n  name: busybox\n  namespace: default\nspec:\n  containers:\n  - name: busybox\n    image: busybox\n    command:\n      - sleep\n      - \"3600\"\n    imagePullPolicy: IfNotPresent\n    volumeMounts:\n    - name: test\n      mountPath: /data\n  volumes:\n  - name: test\n    flexVolume:\n      driver: \"fstab/cifs\"\n      fsType: \"cifs\"\n      secretRef:\n        name: \"cifs-secret\"\n      options:\n        networkPath: \"//server/share\"\n        mountOptions: \"dir_mode=0755,file_mode=0644,noperm\"\n```\n\nStart the pod:\n\n```yaml\nkubectl apply -f pod.yml\n```\n\nYou can verify that the volume was mounted successfully using `kubectl describe pod busybox`.\n\nTesting\n-------\n\nIf everything is fine, start a shell inside the container to see if it worked:\n\n```bash\nkubectl exec -ti busybox /bin/sh\n```\n\nInside the container, you should see the CIFS share mounted to `/data`.\n\n[1]: https://en.wikipedia.org/wiki/Server_Message_Block\n[2]: https://kubernetes.io/docs/concepts/storage/volumes/\n[3]: https://kubernetes.io/docs/concepts/storage/volumes/\n[4]: https://aws.amazon.com/ebs\n[5]: https://azure.microsoft.com/en-us/services/storage/unmanaged-disks/\n[6]: https://hetzner.cloud\n[7]: https://github.com/kubernetes/community/blob/master/contributors/devel/flexvolume.md\n[8]: https://github.com/fstab/cifs\n[9]: https://github.com/fstab/cifs/issues/1\n[10]: https://github.com/kubernetes/community/blob/master/contributors/devel/flexvolume.md#prerequisites\n[11]: https://packages.ubuntu.com/bionic/cifs-utils\n[12]: https://packages.ubuntu.com/bionic/jq\n[13]: https://packages.ubuntu.com/bionic/util-linux\n[14]: https://packages.ubuntu.com/bionic/coreutils\n[15]: https://kubernetes.io/docs/concepts/configuration/secret/\n"
  },
  {
    "path": "cifs",
    "content": "#!/bin/bash\n\nset -u\n\n# ====================================================================\n# Example configuration:\n# ====================================================================\n# --------------------------------------------------------------------\n# secret.yml:\n# --------------------------------------------------------------------\n# apiVersion: v1\n# kind: Secret\n# metadata:\n#   name: cifs-secret\n#   namespace: default\n# type: fstab/cifs\n# data:\n#   username: 'ZXhhbXBsZQo='\n#   password: 'c2VjcmV0Cg=='\n#\n# --------------------------------------------------------------------\n# pod.yml:\n# --------------------------------------------------------------------\n# apiVersion: v1\n# kind: Pod\n# metadata:\n#   name: busybox\n#   namespace: default\n# spec:\n#   containers:\n#   - name: busybox\n#     image: busybox\n#     command:\n#       - sleep\n#       - \"3600\"\n#     imagePullPolicy: IfNotPresent\n#     volumeMounts:\n#     - name: test\n#       mountPath: /data\n#   volumes:\n#   - name: test\n#     flexVolume:\n#       driver: \"fstab/cifs\"\n#       fsType: \"cifs\"\n#       secretRef:\n#         name: \"cifs-secret\"\n#       options:\n#         networkPath: \"//example-server/backup\"\n#         mountOptions: \"dir_mode=0755,file_mode=0644,noperm\"\n# --------------------------------------------------------------------\n\n# Uncomment the following lines to see how this plugin is called:\n# echo >> /tmp/cifs.log\n# date >> /tmp/cifs.log\n# echo \"$@\" >> /tmp/cifs.log\n\ninit() {\n\tassertBinaryInstalled mount.cifs cifs-utils\n\tassertBinaryInstalled jq jq\n\tassertBinaryInstalled mountpoint util-linux\n\tassertBinaryInstalled base64 coreutils\n\techo '{ \"status\": \"Success\", \"message\": \"The fstab/cifs flexvolume plugin was initialized successfully\", \"capabilities\": { \"attach\": false } }'\n\texit 0\n}\n\nassertBinaryInstalled() {\n\tbinary=\"$1\"\n\tpackage=\"$2\"\n\tif ! which \"$binary\" > /dev/null ; then\n\t\terrorExit \"Failed to initialize the fstab/cifs flexvolume plugin. $binary command not found. Please install the $package package.\"\n\tfi\n}\n\nerrorExit() {\n\tif [[ $# -ne 1 ]] ; then\n\t\techo '{ \"status\": \"Failure\", \"message\": \"Unknown error in the fstab/cifs flexvolume plugin.\" }'\n\telse\n\t\tjq -Mcn --arg message \"$1\" '{ \"status\": \"Failure\", \"message\": $message }'\n\tfi\n\texit 1\n}\n\ndoMount() {\n\tif [[ -z ${1:-} || -z ${2:-} ]] ; then\n\t\terrorExit \"cifs mount: syntax error. usage: cifs mount <mount dir> <json options>\"\n\tfi\n\tmountPoint=\"$1\"\n\tshift\n\tjson=$(printf '%s ' \"${@}\")\n\tif ! jq -e . > /dev/null 2>&1 <<< \"$json\" ; then\n\t\terrorExit \"cifs mount: syntax error. invalid json: '$json'\"\n\tfi\n\tnetworkPath=\"$(jq --raw-output -e '.networkPath' <<< \"$json\" 2>/dev/null)\"\n\tif [[ $? -ne 0 ]] ; then\n\t\terrorExit \"cifs mount: option networkPath missing in flexvolume configuration.\"\n\tfi\n\tmountOptions=\"$(jq --raw-output -e '.mountOptions' <<< \"$json\" 2>/dev/null)\"\n\tif [[ $? -ne 0 ]] ; then\n\t\terrorExit \"cifs mount: option mountOptions missing in flexvolume configuration.\"\n\tfi\n\tcifsUsernameBase64=\"$(jq --raw-output -e '.[\"kubernetes.io/secret/username\"]' <<< \"$json\" 2>/dev/null)\"\n\tif [[ $? -ne 0 ]] ; then\n\t\terrorExit \"cifs mount: username not found. the flexVolume definition must contain a secretRef to a secret with username and password.\"\n\tfi\n\tcifsPasswordBase64=\"$(jq --raw-output -e '.[\"kubernetes.io/secret/password\"]' <<< \"$json\" 2>/dev/null)\"\n\tif [[ $? -ne 0 ]] ; then\n\t\terrorExit \"cifs mount: password not found. the flexVolume definition must contain a secretRef to a secret with username and password.\"\n\tfi\n\tcifsUsername=\"$(base64 --decode <<< \"$cifsUsernameBase64\" 2>/dev/null)\"\n\tif [[ $? -ne 0 ]] ; then\n\t\terrorExit \"cifs mount: username secret is not base64 encoded.\"\n\tfi\n\tcifsPassword=\"$(base64 --decode <<< \"$cifsPasswordBase64\" 2>/dev/null)\"\n\tif [[ $? -ne 0 ]] ; then\n\t\terrorExit \"cifs mount: password secret is not base64 encoded.\"\n\tfi\n\tif ! mkdir -p \"$mountPoint\" > /dev/null 2>&1 ; then\n\t\terrorExit \"cifs mount: failed to create mount directory: '$mountPoint'\"\n\tfi\n\tif [[ $(mountpoint \"$mountPoint\") = *\"is a mountpoint\"* ]] ; then\n\t\terrorExit \"cifs mount: there is already a filesystem mounted under the mount directory: '$mountPoint'\"\n\tfi\n\tif [[ ! -z $(ls -A \"$mountPoint\" 2>/dev/null) ]] ; then\n\t\terrorExit \"cifs mount: mount directory is not an empty directory: '$mountPoint'\"\n\tfi\n\n\texport PASSWD=\"$cifsPassword\"\n\tresult=$(mount -t cifs \"$networkPath\" \"$mountPoint\" -o \"username=$cifsUsername,$mountOptions\" 2>&1)\n\tif [[ $? -ne 0 ]] ; then\n\t\terrorExit \"cifs mount: failed to mount the network path: $result\"\n\tfi\n\techo '{ \"status\": \"Success\" }'\n\texit 0\n}\n\ndoUnmount() {\n\tif [[ -z ${1:-} ]] ; then\n\t\terrorExit \"cifs unmount: syntax error. usage: cifs unmount <mount dir>\"\n\tfi\n\tmountPoint=\"$1\"\n\tif [[ $(mountpoint \"$mountPoint\") != *\"is a mountpoint\"* ]] ; then\n\t\terrorExit \"cifs unmount: no filesystem mounted under directory: '$mountPoint'\"\n\tfi\n\tresult=$(umount \"$mountPoint\" 2>&1)\n\tif [[ $? -ne 0 ]] ; then\n\t\terrorExit \"cifs unmount: failed to unmount the network path: $result\"\n\tfi\n\techo '{ \"status\": \"Success\" }'\n\texit 0\n}\n\nnot_supported() {\n\techo '{ \"status\": \"Not supported\" }'\n\texit 1\n}\n\ncommand=${1:-}\nif [[ -n $command ]]; then\n\tshift\nfi\n\ncase \"$command\" in\n\tinit)\n\t\tinit \"$@\"\n\t\t;;\n\tmount)\n\t\tdoMount \"$@\"\n\t\t;;\n\tunmount)\n\t\tdoUnmount \"$@\"\n\t\t;;\n\t*)\n\t\tnot_supported \"$@\"\n\t\t;;\nesac\n"
  }
]