commit 8ccd9ba269a0a7eccedd555d9927d633f0f651b8 Author: Jörg Thalheim Date: Mon Jul 6 07:30:09 2020 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5f64cce --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out +.vscode +.direnv + +# Dependency directories (remove the comment below to include it) +# vendor/ + +/pkgs/sops-install-secrets/sops-install-secrets \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e4ee6a6 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# sops-nix + +WIP diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..f4e8474 --- /dev/null +++ b/default.nix @@ -0,0 +1,5 @@ +{ pkgs ? import {} }: { + sops-init-gpg-key = pkgs.callPackage ./pkgs/sops-init-gpg-key {}; + sops-install-secrets = pkgs.callPackage ./pkgs/sops-install-secrets {}; + sops-shell-hook = pkgs.callPackage ./pkgs/sops-shell-hook {}; +} diff --git a/modules/sops/default.nix b/modules/sops/default.nix new file mode 100644 index 0000000..7aadd03 --- /dev/null +++ b/modules/sops/default.nix @@ -0,0 +1,113 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.sops; + users = config.users.users; + sops-install-secrets = pkgs.callPackage ../../pkgs/sops-install-secrets {}; + secretType = types.submodule ({ config, ... }: { + options = { + name = mkOption { + type = types.str; + default = config._module.args.name; + description = '' + Name of the file used in /run/secrets + ''; + }; + key = mkOption { + type = types.str; + default = config._module.args.name; + description = '' + Key used to lookup in the sops file. + No tested data structures are supported right now. + This option is ignored if format is binary. + ''; + }; + path = assert assertMsg (builtins.pathExists config.sopsFile) '' + Cannot find path '${config.sopsFile}' set in 'sops.secrets."${config._module.args.name}".sopsFile' + ''; + mkOption { + type = types.str; + default = "/run/secrets/${config.name}"; + description = '' + Path where secrets are symlinked to. + If the default is kept no symlink is created. + ''; + }; + format = mkOption { + type = types.enum ["yaml" "json" "binary"]; + default = "yaml"; + description = '' + File format used to decrypt the sops secret. + Binary files are written to the target file as is. + ''; + }; + mode = mkOption { + type = types.str; + default = "0400"; + description = '' + Permissions mode of the in octal. + ''; + }; + owner = mkOption { + type = types.str; + default = "root"; + description = '' + User of the file. + ''; + }; + group = mkOption { + type = types.str; + default = users.${config.owner}.group; + description = '' + Group of the file. + ''; + }; + sopsFile = mkOption { + type = types.either types.str types.path; + default = cfg.defaultSopsFile; + description = '' + Sops file the secret is loaded from. + ''; + }; + }; + }); + manifest = builtins.toFile "manifest.json" (builtins.toJSON { + secrets = builtins.attrValues cfg.secrets; + # Does this need to be configurable? + secretsMountPoint = "/run/secrets.d"; + symlinkPath = "/run/secrets"; + }); +in { + options.sops = { + secrets = mkOption { + type = types.attrsOf secretType; + default = {}; + description = '' + Path where the latest secrets are mounted to. + ''; + }; + + defaultSopsFile = mkOption { + type = types.either types.str types.path; + description = '' + Default sops file used for all secrets. + ''; + }; + + gnupgHome = mkOption { + type = types.str; + default = "/root/.gnupg"; + description = '' + Path to gnupg database directory containing the key for decrypting sops file + ''; + }; + }; + config = mkIf (cfg.secrets != {}) { + system.activationScripts.setup-secrets = stringAfter [ "users" "groups" ] '' + echo setting up secrets... + SOPS_GPG_EXEC=${pkgs.gnupg}/bin/gpg GNUPGHOME=${cfg.gnupgHome} ${sops-install-secrets}/bin/sops-install-secrets ${manifest} + ''; + }; +} diff --git a/pkgs/sops-init-gpg-key/default.nix b/pkgs/sops-init-gpg-key/default.nix new file mode 100644 index 0000000..4700dc1 --- /dev/null +++ b/pkgs/sops-init-gpg-key/default.nix @@ -0,0 +1,18 @@ +{ stdenv, makeWrapper, gnupg, coreutils, utillinux, nettools }: + +stdenv.mkDerivation { + name = "sops-init-gpg-key"; + version = "0.1.0"; + src = ./sops-init-gpg-key; + dontUnpack = true; + + nativeBuildInputs = [ makeWrapper ]; + + installPhase = '' + install -m755 -D $src $out/bin/sops-init-gpg-key + wrapProgram $out/bin/sops-init-gpg-key \ + --prefix PATH : ${stdenv.lib.makeBinPath [ + coreutils utillinux gnupg nettools + ]} + ''; +} diff --git a/pkgs/sops-init-gpg-key/sops-init-gpg-key b/pkgs/sops-init-gpg-key/sops-init-gpg-key new file mode 100755 index 0000000..a394750 --- /dev/null +++ b/pkgs/sops-init-gpg-key/sops-init-gpg-key @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +set -o errexit -o pipefail -o noclobber -o nounset + +OPTIONS=h +LONGOPTS=help,gpghome:,hostname: + +! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@") + +if [[ ${PIPESTATUS[0]} -ne 0 ]]; then + # e.g. return value is 1 + # then getopt has complained about wrong arguments to stdout + exit 2 +fi + +eval set -- "$PARSED" + +FINAL_GNUPGHOME=/root/.gnupg +HOSTNAME=$(hostname) + +usage() { + echo "$0: [--hostname hostname] [--gpghome home]" +} + +while true; do + case "$1" in + -h|--help) + usage + exit 0 + ;; + --gpghome) + FINAL_GNUPGHOME=$2 + shift 2 + ;; + --hostname) + HOSTNAME=$2 + shift 2 + ;; + --) + shift + break + ;; + *) + echo "unknown option: $1" + usage + exit 3 + ;; + esac +done + +if [[ -e "$FINAL_GNUPGHOME" ]]; then + echo "secret path ${FINAL_GNUPGHOME} already exists" + exit 1 +fi + +export GNUPGHOME=$(mktemp -d) +trap "rm -rf $GNUPGHOME" EXIT + +cat > "$GNUPGHOME/key-template" < $HOSTNAME.asc < {}).buildGoModule }: +buildGoModule { + pname = "sops-install-secrets"; + version = "0.0.1"; + + nativeBuildInputs = with import {}; [ + delve + ]; + + hardeningDisable = [ "all" ]; + + src = ./.; + + vendorSha256 = "1ky7xzsx12d8m4kvqkayqzybkf3s0w21d6m8qlhvrm00fmyidkxj"; + shellHook = '' + unset GOFLAGS + ''; +} diff --git a/pkgs/sops-install-secrets/go.mod b/pkgs/sops-install-secrets/go.mod new file mode 100644 index 0000000..f7f3107 --- /dev/null +++ b/pkgs/sops-install-secrets/go.mod @@ -0,0 +1,9 @@ +module github.com/Mic92/sops-install-secrets + +go 1.14 + +require ( + github.com/mozilla-services/yaml v0.0.0-20191106225358-5c216288813c + go.mozilla.org/sops/v3 v3.5.0 + golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae +) diff --git a/pkgs/sops-install-secrets/go.sum b/pkgs/sops-install-secrets/go.sum new file mode 100644 index 0000000..5ce03b9 --- /dev/null +++ b/pkgs/sops-install-secrets/go.sum @@ -0,0 +1,332 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.43.0 h1:banaiRPAM8kUVYneOSkhgcDsLzEvL25FinuiSZaH/2w= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= +github.com/Azure/azure-sdk-for-go v31.2.0+incompatible h1:kZFnTLmdQYNGfakatSivKHUfUnDZhqNdchHD4oIhp5k= +github.com/Azure/azure-sdk-for-go v31.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg= +github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E= +github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/azure/auth v0.1.0 h1:YgO/vSnJEc76NLw2ecIXvXa8bDWiqf1pOJzARAoZsYU= +github.com/Azure/go-autorest/autorest/azure/auth v0.1.0/go.mod h1:Gf7/i2FUpyb/sGBLIFxTBzrNzBo7aPXXE3ZVeDRwdpM= +github.com/Azure/go-autorest/autorest/azure/cli v0.1.0 h1:YTtBrcb6mhA+PoSW8WxFDoIIyjp13XqJeX80ssQtri4= +github.com/Azure/go-autorest/autorest/azure/cli v0.1.0/go.mod h1:Dk8CUAt/b/PzkfeRsWzVG9Yj3ps8mS8ECztu43rdU8U= +github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yKYfVkkBKwc38VF8= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.2.0 h1:15vMO4y76dehZSq7pAaOLQxC6dZYsSrj2GQpflyM/L4= +github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= +github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88= +github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.23.13 h1:l/NG+mgQFRGG3dsFzEj0jw9JIs/zYdtU6MXhY1WIDmM= +github.com/aws/aws-sdk-go v1.23.13/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/goware/prefixer v0.0.0-20160118172347-395022866408 h1:Y9iQJfEqnN3/Nce9cOegemcy/9Ai5k3huT6E80F3zaw= +github.com/goware/prefixer v0.0.0-20160118172347-395022866408/go.mod h1:PE1ycukgRPJ7bJ9a1fdfQ9j8i/cEcRAoLZzbxYpNB/s= +github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= +github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c h1:kQWxfPIHVLbgLzphqk3QUflDy9QdksZR4ygR807bpy0= +github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mozilla-services/yaml v0.0.0-20191106225358-5c216288813c h1:yE1NxRAZA3wF0laDWECtOe2J0tFjSHUI6MXXbMif+QY= +github.com/mozilla-services/yaml v0.0.0-20191106225358-5c216288813c/go.mod h1:Is/Ucts/yU/mWyGR8yELRoO46mejouKsJfQLAIfTR18= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 h1:N8Bg45zpk/UcpNGnfJt2y/3lRWASHNTUET8owPYCgYI= +github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a h1:N7VD+PwpJME2ZfQT8+ejxwA4Ow10IkGbU0MGf94ll8k= +go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a/go.mod h1:YDKUvO0b//78PaaEro6CAPH6NqohCmL2Cwju5XI2HoE= +go.mozilla.org/sops/v3 v3.5.0 h1:GpO9JRZhk6Kc+FVw5Q0vmnvDM6k956ZRh5tbG98T4XI= +go.mozilla.org/sops/v3 v3.5.0/go.mod h1:9TY5PbZJtPWVHOUOvIetW31DLl2T7yNnoxmEGw/QNG0= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 h1:Ygq9/SRJX9+dU0WCIICM8RkWvDw03lvB77hrhJnpxfU= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.22.1 h1:/7cs52RnTJmD43s3uxzlq2U7nqVTd/37viQwMrMNlOM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0= +gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/pkgs/sops-install-secrets/main.go b/pkgs/sops-install-secrets/main.go new file mode 100644 index 0000000..8d4886e --- /dev/null +++ b/pkgs/sops-install-secrets/main.go @@ -0,0 +1,339 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/user" + "path/filepath" + "strconv" + "strings" + + "github.com/mozilla-services/yaml" + "go.mozilla.org/sops/v3/decrypt" + "golang.org/x/sys/unix" +) + +type secret struct { + Name string `json:"name"` + Key string `json:"key"` + Path string `json:"path"` + Owner string `json:"owner"` + Group string `json:"group"` + SopsFile string `json:"sopsFile"` + Format string `json:"format"` + Mode string `json:"mode"` + RestartServices []string `json:"restartServices"` + ReloadServices []string `json:"reloadServices"` + value []byte + mode os.FileMode + owner int + group int +} + +type manifest struct { + Secrets []secret `json:"secrets"` + SecretsMountPoint string `json:"secretsMountpoint"` + SymlinkPath string `json:"symlinkPath"` +} + +func readManifest(path string) (*manifest, error) { + file, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("Failed to open manifest: %s", err) + } + defer file.Close() + dec := json.NewDecoder(file) + var m manifest + if err := dec.Decode(&m); err != nil { + return nil, fmt.Errorf("Failed to parse manifest: %s", err) + } + return &m, nil +} + +func prepareSymlinks(targetDir string, secrets []secret) error { + for _, secret := range secrets { + targetFile := filepath.Join(targetDir, secret.Name) + if targetFile == secret.Path { + continue + } + parent := filepath.Dir(secret.Path) + if err := os.MkdirAll(parent, os.ModePerm); err != nil { + return fmt.Errorf("Cannot create parent directory of '%s': %s", secret.Path, err) + } + for { + currentLinkTarget, err := os.Readlink(secret.Path) + if os.IsNotExist(err) { + if err := os.Symlink(targetFile, secret.Path); err != nil { + return fmt.Errorf("Cannot create symlink '%s': %s", secret.Path, err) + } + return nil + } else if err != nil { + return fmt.Errorf("Cannot read symlink: '%s'", err) + } else if currentLinkTarget == targetFile { + return nil + } + if err := os.Remove(secret.Path); err != nil { + return fmt.Errorf("Cannot override %s", secret.Path) + } + } + } + return nil +} + +type plainData struct { + data map[string]string + binary []byte +} + +func decryptSecret(s *secret, sourceFiles map[string]plainData) error { + sourceFile := sourceFiles[s.SopsFile] + if sourceFile.data == nil || sourceFile.binary == nil { + plain, err := decrypt.File(s.SopsFile, s.Format) + if err != nil { + return fmt.Errorf("Failed to decrypt '%s': %s", s.SopsFile, err) + } + if s.Format == "binary" { + sourceFile.binary = plain + } else { + if s.Format == "yaml" { + if err := yaml.Unmarshal(plain, &sourceFile.data); err != nil { + return fmt.Errorf("Cannot parse yaml of '%s': %s", s.SopsFile, err) + } + } else { + if err := json.Unmarshal(plain, &sourceFile.data); err != nil { + return fmt.Errorf("Cannot parse json of '%s': %s", s.SopsFile, err) + } + } + } + } + if s.Format == "binary" { + s.value = sourceFile.binary + } else { + val, ok := sourceFile.data[s.Key] + if !ok { + return fmt.Errorf("The key '%s' cannot be found in '%s'", s.Key, s.SopsFile) + } + s.value = []byte(val) + } + sourceFiles[s.SopsFile] = sourceFile + return nil +} + +func decryptSecrets(secrets []secret) error { + sourceFiles := make(map[string]plainData) + for i := range secrets { + if err := decryptSecret(&secrets[i], sourceFiles); err != nil { + return err + } + } + return nil +} + +func prepareSecretFs(mountpoint string, keysGid int) error { + if err := os.MkdirAll(mountpoint, 0750); err != nil { + return fmt.Errorf("Cannot create directory '%s': %s", mountpoint, err) + } + if err := os.Chown(mountpoint, 0, int(keysGid)); err != nil { + return fmt.Errorf("Cannot change owner/group of '%s' to 0/%d: %s", mountpoint, keysGid, err) + } + + if err := unix.Mount("none", mountpoint, "ramfs", unix.MS_NODEV|unix.MS_NOSUID, "mode=0750"); err != nil { + return fmt.Errorf("Cannot mount: %s", err) + } + + return nil +} + +func prepareSecretsDir(secretMountpoint string, linkName string, keysGid int) (*string, error) { + var generation uint64 + linkTarget, err := os.Readlink(linkName) + if err == nil { + if strings.HasPrefix(linkTarget, secretMountpoint) { + targetBasename := filepath.Base(linkTarget) + generation, err = strconv.ParseUint(targetBasename, 10, 64) + if err != nil { + return nil, fmt.Errorf("Cannot parse %s of %s as a number: %s", targetBasename, linkTarget, err) + } + } + } else if !os.IsNotExist(err) { + return nil, fmt.Errorf("Cannot access %s: %s", linkName, err) + } + generation++ + dir := filepath.Join(secretMountpoint, strconv.Itoa(int(generation))) + if _, err := os.Stat(dir); !os.IsNotExist(err) { + if err := os.RemoveAll(dir); err != nil { + return nil, fmt.Errorf("Cannot remove existing %s: %s", dir, err) + } + } + if err := os.Mkdir(dir, os.FileMode(0750)); err != nil { + return nil, fmt.Errorf("mkdir(): %s", err) + } + if err := os.Chown(dir, 0, int(keysGid)); err != nil { + return nil, fmt.Errorf("Cannot change owner/group of '%s' to 0/%d: %s", dir, keysGid, err) + } + return &dir, nil +} + +func writeSecrets(secretDir string, secrets []secret) error { + for _, secret := range secrets { + filepath := filepath.Join(secretDir, secret.Name) + if err := ioutil.WriteFile(filepath, []byte(secret.value), secret.mode); err != nil { + return fmt.Errorf("Cannot write %s: %s", filepath, err) + } + if err := os.Chown(filepath, secret.owner, secret.group); err != nil { + return fmt.Errorf("Cannot change owner/group of '%s' to %d/%d: %s", filepath, secret.owner, secret.group, err) + } + } + return nil +} + +func lookupKeysGroup() (int, error) { + group, err := user.LookupGroup("keys") + if err != nil { + return 0, fmt.Errorf("Failed to lookup 'keys' group: %s", err) + } + gid, err := strconv.ParseInt(group.Gid, 10, 64) + if err != nil { + return 0, fmt.Errorf("Cannot parse keys gid %s: %s", group.Gid, err) + } + return int(gid), nil +} + +func validateSecret(secret *secret) error { + mode, err := strconv.ParseUint(secret.Mode, 8, 16) + if err != nil { + return fmt.Errorf("Invalid number in mode: %d: %s", mode, err) + } + secret.mode = os.FileMode(mode) + + owner, err := user.Lookup(secret.Owner) + if err != nil { + return fmt.Errorf("Failed to lookup user '%s': %s", secret.Owner, err) + } + ownerNr, err := strconv.ParseUint(owner.Uid, 10, 64) + if err != nil { + return fmt.Errorf("Cannot parse uid %s: %s", owner.Uid, err) + } + secret.owner = int(ownerNr) + + group, err := user.LookupGroup(secret.Group) + if err != nil { + return fmt.Errorf("Failed to lookup group '%s': %s", secret.Group, err) + } + groupNr, err := strconv.ParseUint(group.Gid, 10, 64) + if err != nil { + return fmt.Errorf("Cannot parse gid %s: %s", group.Gid, err) + } + secret.group = int(groupNr) + + if secret.Format == "" { + secret.Format = "yaml" + } + + if secret.Format != "yaml" && secret.Format != "json" && secret.Format != "binary" { + return fmt.Errorf("Unsupported format %s for secret %s", + secret.Format, secret.Name) + } + + return nil +} + +func validateManifest(m *manifest) error { + if m.SecretsMountPoint == "" { + m.SecretsMountPoint = "/run/secrets.d" + } + if m.SymlinkPath == "" { + m.SymlinkPath = "/run/secrets" + } + for i := range m.Secrets { + if err := validateSecret(&m.Secrets[i]); err != nil { + return err + } + } + return nil +} + +func atomicSymlink(oldname, newname string) error { + // Fast path: if newname does not exist yet, we can skip the whole dance + // below. + if err := os.Symlink(oldname, newname); err == nil || !os.IsExist(err) { + return err + } + + // We need to use ioutil.TempDir, as we cannot overwrite a ioutil.TempFile, + // and removing+symlinking creates a TOCTOU race. + d, err := ioutil.TempDir(filepath.Dir(newname), "."+filepath.Base(newname)) + if err != nil { + return err + } + cleanup := true + defer func() { + if cleanup { + os.RemoveAll(d) + } + }() + + symlink := filepath.Join(d, "tmp.symlink") + if err := os.Symlink(oldname, symlink); err != nil { + return err + } + + if err := os.Rename(symlink, newname); err != nil { + return err + } + + cleanup = false + return os.RemoveAll(d) +} + +func installSecrets(args []string) error { + if len(args) <= 1 { + return fmt.Errorf("USAGE: %s manifest.json", args) + } + manifest, err := readManifest(args[1]) + if err != nil { + return fmt.Errorf("%s", err) + } + + if err := validateManifest(manifest); err != nil { + return fmt.Errorf("Manifest is not valid: %s", err) + } + + keysGid, err := lookupKeysGroup() + if err != nil { + return err + } + + if err := decryptSecrets(manifest.Secrets); err != nil { + return err + } + + if err := prepareSecretFs(manifest.SecretsMountPoint, keysGid); err != nil { + return fmt.Errorf("Failed to mount filesystem for secrets: %s", err) + } + if err := prepareSymlinks(manifest.SymlinkPath, manifest.Secrets); err != nil { + return fmt.Errorf("Failed to prepare symlinks to secret store: %s", err) + } + secretDir, err := prepareSecretsDir(manifest.SecretsMountPoint, manifest.SymlinkPath, keysGid) + if err != nil { + return fmt.Errorf("Failed to prepare new secrets directory: %s", err) + } + if err := writeSecrets(*secretDir, manifest.Secrets); err != nil { + return fmt.Errorf("Cannot write secrets: %s", err) + } + if err := atomicSymlink(*secretDir, manifest.SymlinkPath); err != nil { + return fmt.Errorf("Cannot update secrets symlink: %s", err) + } + + return nil + +} + +func main() { + if err := installSecrets(os.Args); err != nil { + fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], err) + os.Exit(1) + } +} diff --git a/pkgs/sops-install-secrets/main_test.go b/pkgs/sops-install-secrets/main_test.go new file mode 100644 index 0000000..909f1d4 --- /dev/null +++ b/pkgs/sops-install-secrets/main_test.go @@ -0,0 +1,162 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "os/user" + "path" + "path/filepath" + "reflect" + "runtime" + "strconv" + "syscall" + "testing" +) + +// ok fails the test if an err is not nil. +func ok(tb testing.TB, err error) { + if err != nil { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error()) + tb.FailNow() + } +} + +func equals(tb testing.TB, exp, act interface{}) { + if !reflect.DeepEqual(exp, act) { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act) + tb.FailNow() + } +} + +func writeManifest(t *testing.T, dir string, m *manifest) string { + filename := path.Join(dir, "manifest.json") + f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0755) + ok(t, err) + encoder := json.NewEncoder(f) + ok(t, encoder.Encode(m)) + f.Close() + return filename +} + +func TestCliArgs(t *testing.T) { + _, filename, _, _ := runtime.Caller(0) + var testSecrets = path.Join(path.Dir(filename), "test-secrets") + + tempdir, err := ioutil.TempDir("", "symlinkDir") + ok(t, err) + defer os.RemoveAll(tempdir) + secretsPath := path.Join(tempdir, "secrets.d") + symlinkPath := path.Join(tempdir, "secrets") + gpgHome := path.Join(tempdir, "gpg-home") + + ok(t, os.Mkdir(gpgHome, os.FileMode(0700))) + os.Setenv("GNUPGHOME", gpgHome) + cmd := exec.Command("gpg", "--import", path.Join(testSecrets, "key.asc")) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + ok(t, cmd.Run()) + stopGpgCmd := exec.Command("gpgconf", "--kill", "gpg-agent") + defer func() { + if err := stopGpgCmd.Run(); err != nil { + fmt.Printf("failed to stop gpg-agent: %s\n", err) + } + }() + + // should create a symlink + yamlSecret := secret{ + Name: "test", + Key: "test_key", + Owner: "nobody", + Group: "nogroup", + SourceFile: path.Join(testSecrets, "secrets.yaml"), + Path: path.Join(tempdir, "test-target"), + Mode: "0400", + RestartServices: []string{"affected-service"}, + ReloadServices: make([]string, 0), + } + + var jsonSecret secret + // should not create a symlink + jsonSecret = yamlSecret + jsonSecret.Name = "test2" + jsonSecret.Owner = "root" + jsonSecret.Format = "json" + jsonSecret.Group = "root" + jsonSecret.SourceFile = path.Join(testSecrets, "secrets.json") + jsonSecret.Path = path.Join(symlinkPath, "test2") + jsonSecret.Mode = "0700" + + var binarySecret secret + binarySecret = yamlSecret + binarySecret.Name = "test3" + binarySecret.Format = "binary" + binarySecret.SourceFile = path.Join(testSecrets, "secrets.bin") + binarySecret.Path = path.Join(symlinkPath, "test3") + + manifest := manifest{ + Secrets: []secret{yamlSecret, jsonSecret, binarySecret}, + SecretsMountPoint: secretsPath, + SymlinkPath: symlinkPath, + } + + manifestPath := writeManifest(t, tempdir, &manifest) + + err = installSecrets([]string{"sops-install-secrets", manifestPath}) + ok(t, err) + + _, err = os.Stat(manifest.SecretsMountPoint) + ok(t, err) + + _, err = os.Stat(manifest.SymlinkPath) + ok(t, err) + + yamlLinkStat, err := os.Lstat(yamlSecret.Path) + ok(t, err) + + equals(t, os.ModeSymlink, yamlLinkStat.Mode()&os.ModeSymlink) + + yamlStat, err := os.Stat(yamlSecret.Path) + ok(t, err) + + equals(t, true, yamlStat.Mode().IsRegular()) + equals(t, 0400, int(yamlStat.Mode().Perm())) + stat, success := yamlStat.Sys().(*syscall.Stat_t) + equals(t, true, success) + content, err := ioutil.ReadFile(yamlSecret.Path) + ok(t, err) + equals(t, "test_value", string(content)) + + u, err := user.LookupId(strconv.Itoa(int(stat.Uid))) + ok(t, err) + equals(t, "nobody", u.Username) + + g, err := user.LookupGroupId(strconv.Itoa(int(stat.Gid))) + ok(t, err) + equals(t, "nogroup", g.Name) + + jsonStat, err := os.Stat(jsonSecret.Path) + ok(t, err) + equals(t, true, jsonStat.Mode().IsRegular()) + equals(t, 0700, int(jsonStat.Mode().Perm())) + if stat, ok := jsonStat.Sys().(*syscall.Stat_t); ok { + equals(t, 0, int(stat.Uid)) + equals(t, 0, int(stat.Gid)) + } + + content, err = ioutil.ReadFile(binarySecret.Path) + ok(t, err) + equals(t, "binary_value\n", string(content)) + + manifestPath = writeManifest(t, symlinkPath, &manifest) + + err = installSecrets([]string{"sops-install-secrets", manifestPath}) + ok(t, err) + + target, err := os.Readlink(symlinkPath) + equals(t, path.Join(secretsPath, "2"), target) +} diff --git a/pkgs/sops-install-secrets/test-secrets/key.asc b/pkgs/sops-install-secrets/test-secrets/key.asc new file mode 100644 index 0000000..d1ea283 --- /dev/null +++ b/pkgs/sops-install-secrets/test-secrets/key.asc @@ -0,0 +1,31 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQOYBF7+3V4BCACqlg3cYAHiW4r4PtlD50lllgsildssbR2AY+0inaSoafWtmxWp +Rf+fTXpgiRdRCAEUn6XQ+tAjmhG2aA2IwEavCgr7Hpn13/TwseD2Hk8HrQmcGnJH +zP9HRbx71dZYY21PjOr+qHbQ0vYjfsU/g78bDbQu1mKf4IpBXsUGXah1hYFzfBVc +Agn/a86OjlAlHioMtoLMacKdaQn+zpzb9q0zhgEuhFUoGvwvFGuyZxiLe+ZdWHJi +cCFwGZv8aRJkRdXJlpCtY+XW7MowaRqyXTaGXhVzuCif6a7FObnTECHWHWIHLcnz +2aT2UWIINdDOAVVDKKzTRLxzBo7iCQxQu7IrABEBAAEAB/oD/V51n2+fC7Hd/IC4 +X3OoAv2S4YGVMgTunwFJpF/Ytu6E3b7nJp8qUQ8u35+PLzckDfFvrqFCzYFJWUWq +9rqwfVukY++is71wcIVo1dMJxIV+nflUri0j5zQK8WvOxWryrikQ3uY1T1NsB4Fo +t69Qj3fkLVkN92jd/olwWXoHK/5q7xgDMXPE+CPq9sArBiS3WjWlxY+Q12hohnTy +47WlfUcvDuC4S7Ww+BfgRCKXJEQNFu3gfeAcv9ZOvTE9tR0RC704K3rxwK0JVRiI +g1EavhXNJwt6RuA5nPufS0juPokOa9yC/x5oz5/Rp9EW1kRptW1jIAC78/OxIgjH +No6BBADPHrPBJAmSH/hgFMqU8xR2u6ljFVppJvTg/D3/obYKMYf8ybJHit5ydGoP +AgpRqp22jim89vlzNrIYbhMFApQehzBxyfCwA7M9G+UVttmrqYWx5uSf5tTTdgnt +ONnX4nuaZV5Nm41z0r2NmkNunVUNEj9MszfRRQpfjBVNyw5lZQQA0tgjOi7oV0Sc +Kya/MyZLexVfZTI/oIszjPZ2UE9A0ICzwA/mZjQDJ2lD39XnylbninBa1utfEGQr +JTJceA3LLFfo0L2nmbHAbxU6fRkRKvyDUiyeQFoXYNbeOS60HRHq2T4d06aFrWjZ +eEp3iW35gE/ekgD0OVmPwGP11H5OSE8D/3G7vFQrvWewax7Dweqg8VZCBuyUdO7O +rzklc1m8QOYmcJv1L9aM8hXpUMB8IaxWlwJPCA3CHbztkaXm/Qa6rfIetkitKsQZ +cM8ftPt5rzfBUthslB8CdIR8GgTuA5ep/GCnOzsT/X+PKxksjNOrw6pW+D43pne3 +8qp0owJjHllWPSm0HGtyb3BzLXRlc3QgPHJvb3RAa3JvcHMtdGVzdD6JAU4EEwEI +ADgWIQR/uJcVqtqSDWXSXmP5up3r0D9XwAUCXv7dXgIbLwULCQgHAgYVCgkICwIE +FgIDAQIeAQIXgAAKCRD5up3r0D9XwBGzB/9KEcq6wq6VMs5KGqlGyBoqlwuOqLma +Uc0Mvp3YjK5JuW+Sbuf8hPffeRvT/Ypp45YqdiW6AIMVYzAqzYZ9pxjWl6vJKA2m +T+PN0IqbdnZHiCn3KNdKk5849k/VxGrXP+EBJlgpSsvaCZfCK66+O3p2J8duFhbw +ZdgiMZcg7KoC/X/vV/KQSdgw+MIF6M6Qw7g+i8GtW23myDUQuyZExyv84HnIlDaL +slM8C5xZVTLHSE+Ko1sw3jZAgRc5b6mB53aGb5o047Nd5Ne4LF+/SROWBSwboWVm +dbqb6BY/TwZ7M3cnaTqr902ULLIKCmDtsyPPbtBkLwERprn0/GN4+KQ5 +=lC9N +-----END PGP PRIVATE KEY BLOCK----- diff --git a/pkgs/sops-install-secrets/test-secrets/secrets.bin b/pkgs/sops-install-secrets/test-secrets/secrets.bin new file mode 100644 index 0000000..e169e46 --- /dev/null +++ b/pkgs/sops-install-secrets/test-secrets/secrets.bin @@ -0,0 +1,19 @@ +{ + "data": "ENC[AES256_GCM,data:AYNhe4LTj7Qg7KZZbg==,iv:/L0oue9ZaKTUPS4RHuFzcdDgumEd2PjBsAJROG66gVw=,tag:h8ZhK3NbiRuxnTSigauMaA==,type:str]", + "sops": { + "kms": null, + "gcp_kms": null, + "azure_kv": null, + "lastmodified": "2020-07-05T20:44:28Z", + "mac": "ENC[AES256_GCM,data:RDB36I9DqwhcTuvRzzrsm+4qr02+gnRBhvpyUlxb8RVm4SrrcF0rbbpnhbwQBVCdM16SK4LKYqeKgKbR8p4AOfAbiqw3Hzr0X2yzVOVfhur+MUcht2SwmBDjzfpfT/tKTmESa5qQvfOHW1wv0BWAWnkb0VX0nEQ7SPjFmZCDmMo=,iv:cBqhgYRTSmo81c08OK8nE+5G2XZKGupoz+89bPUM1tc=,tag:d4P+phbUMfScvJAiZvd3oA==,type:str]", + "pgp": [ + { + "created_at": "2020-07-05T20:44:26Z", + "enc": "-----BEGIN PGP MESSAGE-----\n\nhQEMA/m6nevQP1fAAQf9GPihjHiNygjYKRsYQRTWRP7BoQmuumT0qakvVNj8a0tU\nz4Alqe8KUlEMurQ6PFxIzDzYoxANnD9sDXxkrdIlFHEVmftcjM6J5x5OZ2JPUO+j\nf7yOpTjb4pSxTWHqCGkUdfTqa2Tn1bOGissjHxu5bq39qR2tfirUHGgW3BbrcXx4\nJjUeSjEFOL//Djq7MuGW/usfKiyhRMmLtJ9sQRNSFYjdGpPbBJoh26zKnq7/fHSs\nJXBLkyk+Lt4TTZO7JpD91Rgtg6CAk3UJDxiwfo8lIWLOrUuMvYx/6ykq/gUlhZYJ\n0PEqNmalIjjRE+hnKGbWRV4Pi1MCSyo6CFwQbRrGaNJeAUG2I5hTRgpQ2KTHUHLU\niq8J53gdW+lkKt3K5KKoUJia4Wa1x6ut2UXZL1HyADLIWpVjDxG2qV/H8ZwHuizU\n3GLoLnQjC49OHUZW09F4ytU8zAyaei3h4jjbEyRv8A==\n=YHki\n-----END PGP MESSAGE-----\n", + "fp": "7FB89715AADA920D65D25E63F9BA9DEBD03F57C0" + } + ], + "unencrypted_suffix": "_unencrypted", + "version": "3.5.0" + } +} \ No newline at end of file diff --git a/pkgs/sops-install-secrets/test-secrets/secrets.json b/pkgs/sops-install-secrets/test-secrets/secrets.json new file mode 100644 index 0000000..2499157 --- /dev/null +++ b/pkgs/sops-install-secrets/test-secrets/secrets.json @@ -0,0 +1,19 @@ +{ + "test_key": "ENC[AES256_GCM,data:PSx6TW8XFEZu1Q==,iv:luoD3+RAkE87TmhD9tCFb+JkVINc1uoh88RhgOQUPVM=,tag:TuDOKHyvhk4UP3GO5lzrlw==,type:str]", + "sops": { + "kms": null, + "gcp_kms": null, + "azure_kv": null, + "lastmodified": "2020-07-03T08:21:21Z", + "mac": "ENC[AES256_GCM,data:WvHkCNOLkq8BdRA5A4OtA4tJOszNqx+cuEb7Mq5NIgDz2pNG2Sm6IbgqwzVqIZDndNfd/zpjtrxRuIyszijwmA67rgiqWorreqdKfqasi5U5SnMbbQX7wOCaAqqLut8KN7npWXN097D1AyhTTGBViPDuLJxYHG33+vZDwGG/YEw=,iv:Jfz8VWPhxPQf+vprq7NhcYWgpcGbUol6+ElWinTjaU0=,tag:8h3UQ3czxFY4SFZdSKZVuw==,type:str]", + "pgp": [ + { + "created_at": "2020-07-03T08:21:00Z", + "enc": "-----BEGIN PGP MESSAGE-----\n\nhQEMA/m6nevQP1fAAQgAqCfoGygBptXq7TbKgJJc8R8BpOgIbK/L1VNH5dxGNIbC\nRL6bgMSqGmjHSKILxnBpLtStBjaxp8EzAdcPLmJ0VkjE9/M+rSVMTsFPMHrvpR6Y\n1BqSx/y2B9RF1UiGZ5Q6VdEEm3iXsNg+mJZYEFCbYAMpSE7iFT8DpgBAAJpJelCt\nSkubIWUz9Dp09oz1CKmK5HGUXTvxgl2upIPuRMHXoyTTuNNXbf+n9QOLo9LmBdCk\nI9ikdz21/uNhFuJhBavxcWfeOhRryYvF4DFbopn3H8+zR6bg4FqIHbwwhIG5x9By\nVOFzW/JyuZTeO7W+9+JI9r0/d4R2KYjZrPC7dxrhd9JeAbbIj+1OpbKwFAVC4+1U\nd1bXDgaZ5VlgfehpxRCRLFn2P6hBtEaW5GGGORlnb9LMvDviXE0vWnfk1o3sYipc\nPCJ9uDSE0WsvsuHBQSJwbDx0DcEtANxd6w7zRqn6Lw==\n=Khnx\n-----END PGP MESSAGE-----\n", + "fp": "7FB89715AADA920D65D25E63F9BA9DEBD03F57C0" + } + ], + "unencrypted_suffix": "_unencrypted", + "version": "3.5.0" + } +} \ No newline at end of file diff --git a/pkgs/sops-install-secrets/test-secrets/secrets.yaml b/pkgs/sops-install-secrets/test-secrets/secrets.yaml new file mode 100644 index 0000000..786ab27 --- /dev/null +++ b/pkgs/sops-install-secrets/test-secrets/secrets.yaml @@ -0,0 +1,25 @@ +test_key: ENC[AES256_GCM,data:bjCOFe3ngTjbBA==,iv:v5M/Ws9d4yBAMFZ13qJ6hkLPceT7a2wcxiJjVp8mvSQ=,tag:zAWPg4H7E2Eavq+iE3yXpg==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + lastmodified: '2020-07-05T19:01:53Z' + mac: ENC[AES256_GCM,data:CrYKSxkKZynq9wwD11GE280VIwv0FqFRNgm5EF588fTN4RwVs8s51DoN4Qx9kPoabYH/lwv+OdeVRq9mbYxtclhJh6ind04pmz5UZps/V5PgJDFd0ckLc9SCeqnEmvBFsp9WM+Tr6M/v9QgeamK+GGovw0+ePwgU1VADPnoyTAo=,iv:kgtewrlBa2N3qyhd7ipTZSxMQndvTy2p8PIM3lynrNY=,tag:aUzPq+u2fi5zziYXVfdaYQ==,type:str] + pgp: + - created_at: '2020-07-05T19:01:34Z' + enc: | + -----BEGIN PGP MESSAGE----- + + hQEMA/m6nevQP1fAAQgAnUb/ShUMr5vkDLG+LWGD8DT6etv3kPoJIhq7C0d7y/BX + 0UHTcxR4coZ55jB6+juxGmh9e9WhawoxHToUs/edxJHxOlC19HunWv9EvTFkKYVf + MAFu+Gfx6cWgDPOrrtvFVZ3YUqPizoOUmP/Op5y/gxhKEYBtD1/5YuSGNnL1VYkv + ZEfgPYtAq5Jmx+jF2Smkb/T6cWFV79WUJTZbYRQs466qQHtZGU9Ms9amxQ73Xf/Y + KhysQCu1yuVxGzBuPVrT5jbZdgaHm5KlmSIGwk47m+PH4ienkhE76WTUCr4xx9R8 + n8/+eVga9cYWbwHYoY84GNUi7fbx5LTD3x19LnVk9dJeAahWrgzT3azwJRPH7WdW + U8+La2v36cucQ/u7+KraVCofOuNXBsjZOJokte97DtKwqtPXRSbyL8owSA90O3Vh + ++zsV3ufM+8TO+9LDAjMqmF0lpQuGexNPafdkuAGdQ== + =5GUN + -----END PGP MESSAGE----- + fp: 7FB89715AADA920D65D25E63F9BA9DEBD03F57C0 + unencrypted_suffix: _unencrypted + version: 3.5.0 diff --git a/pkgs/sops-install-secrets/test-secrets/sops-edit b/pkgs/sops-install-secrets/test-secrets/sops-edit new file mode 100755 index 0000000..5e45d80 --- /dev/null +++ b/pkgs/sops-install-secrets/test-secrets/sops-edit @@ -0,0 +1,18 @@ +#!/usr/bin/env nix-shell +#! nix-shell -p gnupg -p bash -p sops -i bash + +if [ "$#" -lt 1 ]; then + echo "USAGE: $0 file" + exit 1 +fi + +set -x +export GNUPGHOME=$(mktemp -d) +trap "gpgconf --kill gpg-agent && rm -rf $GNUPGHOME" EXIT + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +gpg --import "$DIR/key.asc" +gpg --fingerprint --list-keys +fpr=$(gpg --with-fingerprint --with-colons --show-key ./key.asc | awk -F: '$1 == "fpr" { print $10;}') + +sops --pgp "$fpr" "$@" \ No newline at end of file diff --git a/pkgs/sops-shell-hook/default.nix b/pkgs/sops-shell-hook/default.nix new file mode 100644 index 0000000..e619e9b --- /dev/null +++ b/pkgs/sops-shell-hook/default.nix @@ -0,0 +1,8 @@ +{ makeSetupHook, gnupg, sops }: + +makeSetupHook { + substitutions = { + gpg = "${gnupg}/bin/gpg"; + }; + deps = [ sops ]; +} ./sops-shell-hook.bash diff --git a/pkgs/sops-shell-hook/sops-shell-hook.bash b/pkgs/sops-shell-hook/sops-shell-hook.bash new file mode 100644 index 0000000..a9edbd6 --- /dev/null +++ b/pkgs/sops-shell-hook/sops-shell-hook.bash @@ -0,0 +1,23 @@ +_sopsAddKey() { + @gpg@ --quiet --import "$key" + local fpr + fpr=$(@gpg@ --with-fingerprint --with-colons --show-key "$key" \ + | awk -F: '$1 == "fpr" { print $10;}') + export SOPS_PGP_FP=''${SOPS_PGP_FP}''${SOPS_PGP_FP:+','}$fpr +} + +sopsShellHook() { + local key dir + for key in $sopsGPGKeys; do + _sopsAddKey "$key" + done + for dir in $sopsGPGKeyDirs; do + while IFS= read -r -d '' key; do + _sopsAddKey "$key" + done < <(find "$dir" -type f -name '*.asc' -print0) + done +} + +if [ -z "${shellHook-}" ]; then + shellHook=sopsShellHook +fi