diff --git a/controllers/onepassworditem_controller.go b/controllers/onepassworditem_controller.go index 1caa7f4..23fbbb9 100644 --- a/controllers/onepassworditem_controller.go +++ b/controllers/onepassworditem_controller.go @@ -27,6 +27,7 @@ package controllers import ( "context" "fmt" + "github.com/1Password/connect-sdk-go/connect" onepasswordv1 "github.com/1Password/onepassword-operator/api/v1" diff --git a/go.mod b/go.mod index b2afaf6..1e71858 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,22 @@ module github.com/1Password/onepassword-operator go 1.18 require ( + github.com/1Password/connect-sdk-go v1.5.0 github.com/onsi/ginkgo v1.16.5 - github.com/onsi/gomega v1.18.1 - k8s.io/apimachinery v0.24.2 - k8s.io/client-go v0.24.2 + github.com/onsi/gomega v1.19.0 + github.com/stretchr/testify v1.7.1 + k8s.io/api v0.25.0 + k8s.io/apimachinery v0.25.0 + k8s.io/client-go v0.25.0 + k8s.io/kubectl v0.25.0 sigs.k8s.io/controller-runtime v0.12.2 ) require ( - cloud.google.com/go v0.81.0 // indirect + cloud.google.com/go v0.97.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.18 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect + github.com/Azure/go-autorest/autorest v0.11.27 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect @@ -23,22 +27,22 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful v2.9.5+incompatible // indirect + github.com/emicklei/go-restful/v3 v3.8.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect - github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect - github.com/go-logr/logr v1.2.0 // indirect - github.com/go-logr/zapr v1.2.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/zapr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.5 // indirect github.com/go-openapi/swag v0.19.14 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.5.5 // indirect + github.com/google/go-cmp v0.5.6 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.1.2 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -48,36 +52,39 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nxadm/tail v1.4.8 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/spf13/pflag v1.0.5 // indirect - go.uber.org/atomic v1.7.0 // indirect + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect + github.com/uber/jaeger-lib v2.4.1+incompatible // indirect + go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.1 // indirect - golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect - golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect + golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.27.1 // indirect + google.golang.org/protobuf v1.28.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/api v0.24.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.24.2 // indirect - k8s.io/component-base v0.24.2 // indirect - k8s.io/klog/v2 v2.60.1 // indirect - k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect - k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect - sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect + k8s.io/component-base v0.25.0 // indirect + k8s.io/klog/v2 v2.70.1 // indirect + k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect + k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index c27022b..747ecad 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,15 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -38,23 +45,31 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/1Password/connect-sdk-go v1.5.0 h1:F0WJcLSzGg3iXEDY49/ULdszYKsQLGTzn+2cyYXqiyk= +github.com/1Password/connect-sdk-go v1.5.0/go.mod h1:TdynFeyvaRoackENbJ8RfJokH+WAowAu1MLmUbdMq6s= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= +github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= +github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= +github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= +github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= +github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= 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/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -62,6 +77,7 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -122,8 +138,9 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= +github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -137,8 +154,8 @@ github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -158,10 +175,13 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= +github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= +github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -179,6 +199,10 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -195,6 +219,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -213,6 +238,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/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/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= @@ -230,14 +256,16 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= 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/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -249,12 +277,16 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -307,6 +339,7 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X 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/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -366,18 +399,17 @@ github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= -github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= -github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -448,17 +480,23 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -498,8 +536,9 @@ go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4 go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= @@ -521,9 +560,14 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= @@ -533,6 +577,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -602,13 +647,14 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 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/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -621,6 +667,9 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -691,16 +740,22 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -721,9 +776,11 @@ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/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-20181030221726-6c7e314b6563/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-20190206041539-40960b6deb8e/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= @@ -776,7 +833,10 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -786,6 +846,10 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -807,6 +871,13 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= 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= @@ -858,8 +929,21 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -881,8 +965,12 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -895,8 +983,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -925,8 +1014,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -936,40 +1026,52 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.24.2 h1:g518dPU/L7VRLxWfcadQn2OnsiGWVOadTLpdnqgY2OI= k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg= +k8s.io/api v0.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0= +k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk= k8s.io/apiextensions-apiserver v0.24.2 h1:/4NEQHKlEz1MlaK/wHT5KMKC9UKYz6NZz6JE6ov4G6k= k8s.io/apiextensions-apiserver v0.24.2/go.mod h1:e5t2GMFVngUEHUd0wuCJzw8YDwZoqZfJiGOW6mm2hLQ= -k8s.io/apimachinery v0.24.2 h1:5QlH9SL2C8KMcrNJPor+LbXVTaZRReml7svPEh4OKDM= k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU= +k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= k8s.io/apiserver v0.24.2/go.mod h1:pSuKzr3zV+L+MWqsEo0kHHYwCo77AT5qXbFXP2jbvFI= -k8s.io/client-go v0.24.2 h1:CoXFSf8if+bLEbinDqN9ePIDGzcLtqhfd6jpfnwGOFA= k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= +k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E= +k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= k8s.io/code-generator v0.24.2/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= -k8s.io/component-base v0.24.2 h1:kwpQdoSfbcH+8MPN4tALtajLDfSfYxBDYlXobNWI6OU= k8s.io/component-base v0.24.2/go.mod h1:ucHwW76dajvQ9B7+zecZAP3BVqvrHoOxm8olHEg0nmM= +k8s.io/component-base v0.25.0 h1:haVKlLkPCFZhkcqB6WCvpVxftrg6+FK5x1ZuaIDaQ5Y= +k8s.io/component-base v0.25.0/go.mod h1:F2Sumv9CnbBlqrpdf7rKZTmmd2meJq0HizeyY/yAFxk= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= +k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= +k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= +k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA= +k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= +k8s.io/kubectl v0.25.0 h1:/Wn1cFqo8ik3iee1EvpxYre3bkWsGLXzLQI6uCCAkQc= +k8s.io/kubectl v0.25.0/go.mod h1:n16ULWsOl2jmQpzt2o7Dud1t4o0+Y186ICb4O+GwKAU= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= sigs.k8s.io/controller-runtime v0.12.2 h1:nqV02cvhbAj7tbt21bpPpTByrXGn2INHRsi39lXy9sE= sigs.k8s.io/controller-runtime v0.12.2/go.mod h1:qKsk4WE6zW2Hfj0G4v10EnNB2jMG1C+NTb8h+DwCoU0= -sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/pkg/kubernetessecrets/kubernetes_secrets_builder.go b/pkg/kubernetessecrets/kubernetes_secrets_builder.go new file mode 100644 index 0000000..ccc3a81 --- /dev/null +++ b/pkg/kubernetessecrets/kubernetes_secrets_builder.go @@ -0,0 +1,190 @@ +package kubernetessecrets + +import ( + "context" + "fmt" + + "regexp" + "strings" + + "reflect" + + errs "errors" + + "github.com/1Password/connect-sdk-go/onepassword" + + "github.com/1Password/onepassword-operator/pkg/utils" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + kubeValidate "k8s.io/apimachinery/pkg/util/validation" + + kubernetesClient "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" +) + +const OnepasswordPrefix = "operator.1password.io" +const NameAnnotation = OnepasswordPrefix + "/item-name" +const VersionAnnotation = OnepasswordPrefix + "/item-version" +const ItemPathAnnotation = OnepasswordPrefix + "/item-path" +const RestartDeploymentsAnnotation = OnepasswordPrefix + "/auto-restart" + +var ErrCannotUpdateSecretType = errs.New("Cannot change secret type. Secret type is immutable") + +var log = logf.Log + +func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *onepassword.Item, autoRestart string, labels map[string]string, secretType string, ownerRef *metav1.OwnerReference) error { + itemVersion := fmt.Sprint(item.Version) + secretAnnotations := map[string]string{ + VersionAnnotation: itemVersion, + ItemPathAnnotation: fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID), + } + + if autoRestart != "" { + _, err := utils.StringToBool(autoRestart) + if err != nil { + return fmt.Errorf("Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secretName) + } + secretAnnotations[RestartDeploymentsAnnotation] = autoRestart + } + + // "Opaque" and "" secret types are treated the same by Kubernetes. + secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, secretType, *item, ownerRef) + + currentSecret := &corev1.Secret{} + err := kubeClient.Get(context.Background(), types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, currentSecret) + if err != nil && errors.IsNotFound(err) { + log.Info(fmt.Sprintf("Creating Secret %v at namespace '%v'", secret.Name, secret.Namespace)) + return kubeClient.Create(context.Background(), secret) + } else if err != nil { + return err + } + + // Check if the secret types are being changed on the update. + // Avoid Opaque and "" are treated as different on check. + wantSecretType := secretType + if wantSecretType == "" { + wantSecretType = string(corev1.SecretTypeOpaque) + } + currentSecretType := string(currentSecret.Type) + if currentSecretType == "" { + currentSecretType = string(corev1.SecretTypeOpaque) + } + if currentSecretType != wantSecretType { + return ErrCannotUpdateSecretType + } + + currentAnnotations := currentSecret.Annotations + currentLabels := currentSecret.Labels + if !reflect.DeepEqual(currentAnnotations, secretAnnotations) || !reflect.DeepEqual(currentLabels, labels) { + log.Info(fmt.Sprintf("Updating Secret %v at namespace '%v'", secret.Name, secret.Namespace)) + currentSecret.ObjectMeta.Annotations = secretAnnotations + currentSecret.ObjectMeta.Labels = labels + currentSecret.Data = secret.Data + if err := kubeClient.Update(context.Background(), currentSecret); err != nil { + return fmt.Errorf("Kubernetes secret update failed: %w", err) + } + return nil + } + + log.Info(fmt.Sprintf("Secret with name %v and version %v already exists", secret.Name, secret.Annotations[VersionAnnotation])) + return nil +} + +func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, labels map[string]string, secretType string, item onepassword.Item, ownerRef *metav1.OwnerReference) *corev1.Secret { + var ownerRefs []metav1.OwnerReference + if ownerRef != nil { + ownerRefs = []metav1.OwnerReference{*ownerRef} + } + + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: formatSecretName(name), + Namespace: namespace, + Annotations: annotations, + Labels: labels, + OwnerReferences: ownerRefs, + }, + Data: BuildKubernetesSecretData(item.Fields, item.Files), + Type: corev1.SecretType(secretType), + } +} + +func BuildKubernetesSecretData(fields []*onepassword.ItemField, files []*onepassword.File) map[string][]byte { + secretData := map[string][]byte{} + for i := 0; i < len(fields); i++ { + if fields[i].Value != "" { + key := formatSecretDataName(fields[i].Label) + secretData[key] = []byte(fields[i].Value) + } + } + + // populate unpopulated fields from files + for _, file := range files { + content, err := file.Content() + if err != nil { + log.Error(err, "Could not load contents of file %s", file.Name) + continue + } + if content != nil { + key := file.Name + if secretData[key] == nil { + secretData[key] = content + } else { + log.Info(fmt.Sprintf("File '%s' ignored because of a field with the same name", file.Name)) + } + } + } + return secretData +} + +// formatSecretName rewrites a value to be a valid Secret name. +// +// The Secret meta.name and data keys must be valid DNS subdomain names +// (https://kubernetes.io/docs/concepts/configuration/secret/#overview-of-secrets) +func formatSecretName(value string) string { + if errs := kubeValidate.IsDNS1123Subdomain(value); len(errs) == 0 { + return value + } + return createValidSecretName(value) +} + +// formatSecretDataName rewrites a value to be a valid Secret data key. +// +// The Secret data keys must consist of alphanumeric numbers, `-`, `_` or `.` +// (https://kubernetes.io/docs/concepts/configuration/secret/#overview-of-secrets) +func formatSecretDataName(value string) string { + if errs := kubeValidate.IsConfigMapKey(value); len(errs) == 0 { + return value + } + return createValidSecretDataName(value) +} + +var invalidDNS1123Chars = regexp.MustCompile("[^a-z0-9-.]+") + +func createValidSecretName(value string) string { + result := strings.ToLower(value) + result = invalidDNS1123Chars.ReplaceAllString(result, "-") + + if len(result) > kubeValidate.DNS1123SubdomainMaxLength { + result = result[0:kubeValidate.DNS1123SubdomainMaxLength] + } + + // first and last character MUST be alphanumeric + return strings.Trim(result, "-.") +} + +var invalidDataChars = regexp.MustCompile("[^a-zA-Z0-9-._]+") +var invalidStartEndChars = regexp.MustCompile("(^[^a-zA-Z0-9-._]+|[^a-zA-Z0-9-._]+$)") + +func createValidSecretDataName(value string) string { + result := invalidStartEndChars.ReplaceAllString(value, "") + result = invalidDataChars.ReplaceAllString(result, "-") + + if len(result) > kubeValidate.DNS1123SubdomainMaxLength { + result = result[0:kubeValidate.DNS1123SubdomainMaxLength] + } + + return result +} diff --git a/pkg/kubernetessecrets/kubernetes_secrets_builder_test.go b/pkg/kubernetessecrets/kubernetes_secrets_builder_test.go new file mode 100644 index 0000000..b24f26a --- /dev/null +++ b/pkg/kubernetessecrets/kubernetes_secrets_builder_test.go @@ -0,0 +1,299 @@ +package kubernetessecrets + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/1Password/connect-sdk-go/onepassword" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + kubeValidate "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +const restartDeploymentAnnotation = "false" + +type k8s struct { + clientset kubernetes.Interface +} + +func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) { + secretName := "test-secret-name" + namespace := "test" + + item := onepassword.Item{} + item.Fields = generateFields(5) + item.Version = 123 + item.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda" + item.ID = "h46bb3jddvay7nxopfhvlwg35q" + + kubeClient := fake.NewFakeClient() + secretLabels := map[string]string{} + secretType := "" + + err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + createdSecret := &corev1.Secret{} + err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret) + + if err != nil { + t.Errorf("Secret was not created: %v", err) + } + compareFields(item.Fields, createdSecret.Data, t) + compareAnnotationsToItem(createdSecret.Annotations, item, t) +} + +func TestKubernetesSecretFromOnePasswordItemOwnerReferences(t *testing.T) { + secretName := "test-secret-name" + namespace := "test" + + item := onepassword.Item{} + item.Fields = generateFields(5) + item.Version = 123 + item.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda" + item.ID = "h46bb3jddvay7nxopfhvlwg35q" + + kubeClient := fake.NewFakeClient() + secretLabels := map[string]string{} + secretType := "" + + ownerRef := &metav1.OwnerReference{ + Kind: "Deployment", + APIVersion: "apps/v1", + Name: "test-deployment", + UID: types.UID("test-uid"), + } + err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, ownerRef) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + createdSecret := &corev1.Secret{} + err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret) + + // Check owner references. + gotOwnerRefs := createdSecret.ObjectMeta.OwnerReferences + if len(gotOwnerRefs) != 1 { + t.Errorf("Expected owner references length: 1 but got: %d", len(gotOwnerRefs)) + } + + expOwnerRef := metav1.OwnerReference{ + Kind: "Deployment", + APIVersion: "apps/v1", + Name: "test-deployment", + UID: types.UID("test-uid"), + } + gotOwnerRef := gotOwnerRefs[0] + if gotOwnerRef != expOwnerRef { + t.Errorf("Expected owner reference value: %v but got: %v", expOwnerRef, gotOwnerRef) + } +} + +func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { + secretName := "test-secret-update" + namespace := "test" + + item := onepassword.Item{} + item.Fields = generateFields(5) + item.Version = 123 + item.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda" + item.ID = "h46bb3jddvay7nxopfhvlwg35q" + + kubeClient := fake.NewFakeClient() + secretLabels := map[string]string{} + secretType := "" + + err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) + + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // Updating kubernetes secret with new item + newItem := onepassword.Item{} + newItem.Fields = generateFields(6) + newItem.Version = 456 + newItem.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda" + newItem.ID = "h46bb3jddvay7nxopfhvlwg35q" + err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretType, nil) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + updatedSecret := &corev1.Secret{} + err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, updatedSecret) + + if err != nil { + t.Errorf("Secret was not found: %v", err) + } + compareFields(newItem.Fields, updatedSecret.Data, t) + compareAnnotationsToItem(updatedSecret.Annotations, newItem, t) +} +func TestBuildKubernetesSecretData(t *testing.T) { + fields := generateFields(5) + + secretData := BuildKubernetesSecretData(fields, nil) + if len(secretData) != len(fields) { + t.Errorf("Unexpected number of secret fields returned. Expected 3, got %v", len(secretData)) + } + compareFields(fields, secretData, t) +} + +func TestBuildKubernetesSecretFromOnePasswordItem(t *testing.T) { + annotationKey := "annotationKey" + annotationValue := "annotationValue" + name := "someName" + namespace := "someNamespace" + annotations := map[string]string{ + annotationKey: annotationValue, + } + item := onepassword.Item{} + item.Fields = generateFields(5) + labels := map[string]string{} + secretType := "" + + kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item, nil) + if kubeSecret.Name != strings.ToLower(name) { + t.Errorf("Expected name value: %v but got: %v", name, kubeSecret.Name) + } + if kubeSecret.Namespace != namespace { + t.Errorf("Expected namespace value: %v but got: %v", namespace, kubeSecret.Namespace) + } + if kubeSecret.Annotations[annotationKey] != annotations[annotationKey] { + t.Errorf("Expected namespace value: %v but got: %v", namespace, kubeSecret.Namespace) + } + compareFields(item.Fields, kubeSecret.Data, t) +} + +func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) { + name := "inV@l1d k8s secret%name" + expectedName := "inv-l1d-k8s-secret-name" + namespace := "someNamespace" + annotations := map[string]string{ + "annotationKey": "annotationValue", + } + labels := map[string]string{} + item := onepassword.Item{} + secretType := "" + + item.Fields = []*onepassword.ItemField{ + { + Label: "label w%th invalid ch!rs-", + Value: "value1", + }, + { + Label: strings.Repeat("x", kubeValidate.DNS1123SubdomainMaxLength+1), + Value: "name exceeds max length", + }, + } + + kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item, nil) + + // Assert Secret's meta.name was fixed + if kubeSecret.Name != expectedName { + t.Errorf("Expected name value: %v but got: %v", name, kubeSecret.Name) + } + if kubeSecret.Namespace != namespace { + t.Errorf("Expected namespace value: %v but got: %v", namespace, kubeSecret.Namespace) + } + + // assert labels were fixed for each data key + for key := range kubeSecret.Data { + if !validLabel(key) { + t.Errorf("Expected valid kubernetes label, got %s", key) + } + } +} + +func TestCreateKubernetesTLSSecretFromOnePasswordItem(t *testing.T) { + secretName := "tls-test-secret-name" + namespace := "test" + + item := onepassword.Item{} + item.Fields = generateFields(5) + item.Version = 123 + item.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda" + item.ID = "h46bb3jddvay7nxopfhvlwg35q" + + kubeClient := fake.NewFakeClient() + secretLabels := map[string]string{} + secretType := "kubernetes.io/tls" + + err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + createdSecret := &corev1.Secret{} + err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret) + + if err != nil { + t.Errorf("Secret was not created: %v", err) + } + + if createdSecret.Type != corev1.SecretTypeTLS { + t.Errorf("Expected secretType to be of tyype corev1.SecretTypeTLS, got %s", string(createdSecret.Type)) + } +} + +func compareAnnotationsToItem(annotations map[string]string, item onepassword.Item, t *testing.T) { + actualVaultId, actualItemId, err := ParseVaultIdAndItemIdFromPath(annotations[ItemPathAnnotation]) + if err != nil { + t.Errorf("Was unable to parse Item Path") + } + if actualVaultId != item.Vault.ID { + t.Errorf("Expected annotation vault id to be %v but was %v", item.Vault.ID, actualVaultId) + } + if actualItemId != item.ID { + t.Errorf("Expected annotation item id to be %v but was %v", item.ID, actualItemId) + } + if annotations[VersionAnnotation] != fmt.Sprint(item.Version) { + t.Errorf("Expected annotation version to be %v but was %v", item.Version, annotations[VersionAnnotation]) + } + + if annotations[RestartDeploymentsAnnotation] != "false" { + t.Errorf("Expected restart deployments annotation to be %v but was %v", restartDeploymentAnnotation, RestartDeploymentsAnnotation) + } +} + +func compareFields(actualFields []*onepassword.ItemField, secretData map[string][]byte, t *testing.T) { + for i := 0; i < len(actualFields); i++ { + value, found := secretData[actualFields[i].Label] + if !found { + t.Errorf("Expected key %v is missing from secret data", actualFields[i].Label) + } + if string(value) != actualFields[i].Value { + t.Errorf("Expected value %v but got %v", actualFields[i].Value, value) + } + } +} + +func generateFields(numToGenerate int) []*onepassword.ItemField { + fields := []*onepassword.ItemField{} + for i := 0; i < numToGenerate; i++ { + field := onepassword.ItemField{ + Label: "key" + fmt.Sprint(i), + Value: "value" + fmt.Sprint(i), + } + fields = append(fields, &field) + } + return fields +} + +func ParseVaultIdAndItemIdFromPath(path string) (string, string, error) { + splitPath := strings.Split(path, "/") + if len(splitPath) == 4 && splitPath[0] == "vaults" && splitPath[2] == "items" { + return splitPath[1], splitPath[3], nil + } + return "", "", fmt.Errorf("%q is not an acceptable path for One Password item. Must be of the format: `vaults/{vault_id}/items/{item_id}`", path) +} + +func validLabel(v string) bool { + if err := kubeValidate.IsConfigMapKey(v); len(err) > 0 { + return false + } + return true +} diff --git a/pkg/mocks/mocksecretserver.go b/pkg/mocks/mocksecretserver.go new file mode 100644 index 0000000..29c7664 --- /dev/null +++ b/pkg/mocks/mocksecretserver.go @@ -0,0 +1,84 @@ +package mocks + +import ( + "github.com/1Password/connect-sdk-go/onepassword" +) + +type TestClient struct { + GetVaultsFunc func() ([]onepassword.Vault, error) + GetVaultsByTitleFunc func(title string) ([]onepassword.Vault, error) + GetVaultFunc func(uuid string) (*onepassword.Vault, error) + GetItemFunc func(uuid string, vaultUUID string) (*onepassword.Item, error) + GetItemsFunc func(vaultUUID string) ([]onepassword.Item, error) + GetItemsByTitleFunc func(title string, vaultUUID string) ([]onepassword.Item, error) + GetItemByTitleFunc func(title string, vaultUUID string) (*onepassword.Item, error) + CreateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) + UpdateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) + DeleteItemFunc func(item *onepassword.Item, vaultUUID string) error + GetFileFunc func(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error) + GetFileContentFunc func(file *onepassword.File) ([]byte, error) +} + +var ( + GetGetVaultsFunc func() ([]onepassword.Vault, error) + DoGetVaultsByTitleFunc func(title string) ([]onepassword.Vault, error) + DoGetVaultFunc func(uuid string) (*onepassword.Vault, error) + GetGetItemFunc func(uuid string, vaultUUID string) (*onepassword.Item, error) + DoGetItemsByTitleFunc func(title string, vaultUUID string) ([]onepassword.Item, error) + DoGetItemByTitleFunc func(title string, vaultUUID string) (*onepassword.Item, error) + DoCreateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) + DoDeleteItemFunc func(item *onepassword.Item, vaultUUID string) error + DoGetItemsFunc func(vaultUUID string) ([]onepassword.Item, error) + DoUpdateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) + DoGetFileFunc func(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error) + DoGetFileContentFunc func(file *onepassword.File) ([]byte, error) +) + +// Do is the mock client's `Do` func +func (m *TestClient) GetVaults() ([]onepassword.Vault, error) { + return GetGetVaultsFunc() +} + +func (m *TestClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error) { + return DoGetVaultsByTitleFunc(title) +} + +func (m *TestClient) GetVault(uuid string) (*onepassword.Vault, error) { + return DoGetVaultFunc(uuid) +} + +func (m *TestClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item, error) { + return GetGetItemFunc(uuid, vaultUUID) +} + +func (m *TestClient) GetItems(vaultUUID string) ([]onepassword.Item, error) { + return DoGetItemsFunc(vaultUUID) +} + +func (m *TestClient) GetItemsByTitle(title, vaultUUID string) ([]onepassword.Item, error) { + return DoGetItemsByTitleFunc(title, vaultUUID) +} + +func (m *TestClient) GetItemByTitle(title string, vaultUUID string) (*onepassword.Item, error) { + return DoGetItemByTitleFunc(title, vaultUUID) +} + +func (m *TestClient) CreateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) { + return DoCreateItemFunc(item, vaultUUID) +} + +func (m *TestClient) DeleteItem(item *onepassword.Item, vaultUUID string) error { + return DoDeleteItemFunc(item, vaultUUID) +} + +func (m *TestClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) { + return DoUpdateItemFunc(item, vaultUUID) +} + +func (m *TestClient) GetFile(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error) { + return DoGetFileFunc(uuid, itemUUID, vaultUUID) +} + +func (m *TestClient) GetFileContent(file *onepassword.File) ([]byte, error) { + return DoGetFileContentFunc(file) +} diff --git a/pkg/onepassword/annotations.go b/pkg/onepassword/annotations.go new file mode 100644 index 0000000..652f671 --- /dev/null +++ b/pkg/onepassword/annotations.go @@ -0,0 +1,60 @@ +package onepassword + +import ( + "regexp" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" +) + +const ( + OnepasswordPrefix = "operator.1password.io" + ItemPathAnnotation = OnepasswordPrefix + "/item-path" + NameAnnotation = OnepasswordPrefix + "/item-name" + VersionAnnotation = OnepasswordPrefix + "/item-version" + RestartAnnotation = OnepasswordPrefix + "/last-restarted" + RestartDeploymentsAnnotation = OnepasswordPrefix + "/auto-restart" +) + +func GetAnnotationsForDeployment(deployment *appsv1.Deployment, regex *regexp.Regexp) (map[string]string, bool) { + annotationsFound := false + annotations := FilterAnnotations(deployment.Annotations, regex) + if len(annotations) > 0 { + annotationsFound = true + } else { + annotations = FilterAnnotations(deployment.Spec.Template.Annotations, regex) + if len(annotations) > 0 { + annotationsFound = true + } else { + annotationsFound = false + } + } + + return annotations, annotationsFound +} + +func FilterAnnotations(annotations map[string]string, regex *regexp.Regexp) map[string]string { + filteredAnnotations := make(map[string]string) + for key, value := range annotations { + if regex.MatchString(key) && key != RestartAnnotation && key != RestartDeploymentsAnnotation { + filteredAnnotations[key] = value + } + } + return filteredAnnotations +} + +func AreAnnotationsUsingSecrets(annotations map[string]string, secrets map[string]*corev1.Secret) bool { + _, ok := secrets[annotations[NameAnnotation]] + if ok { + return true + } + return false +} + +func AppendAnnotationUpdatedSecret(annotations map[string]string, secrets map[string]*corev1.Secret, updatedDeploymentSecrets map[string]*corev1.Secret) map[string]*corev1.Secret { + secret, ok := secrets[annotations[NameAnnotation]] + if ok { + updatedDeploymentSecrets[secret.Name] = secret + } + return updatedDeploymentSecrets +} diff --git a/pkg/onepassword/annotations_test.go b/pkg/onepassword/annotations_test.go new file mode 100644 index 0000000..3949f78 --- /dev/null +++ b/pkg/onepassword/annotations_test.go @@ -0,0 +1,93 @@ +package onepassword + +import ( + "regexp" + "testing" + + appsv1 "k8s.io/api/apps/v1" +) + +const AnnotationRegExpString = "^operator.1password.io\\/[a-zA-Z\\.]+" + +func TestFilterAnnotations(t *testing.T) { + invalidAnnotation1 := "onepasswordconnect/vaultId" + invalidAnnotation2 := "onepasswordconnectkubernetesSecrets" + + annotations := getValidAnnotations() + annotations[invalidAnnotation1] = "This should be filtered" + annotations[invalidAnnotation2] = "This should be filtered too" + + r, _ := regexp.Compile(AnnotationRegExpString) + filteredAnnotations := FilterAnnotations(annotations, r) + if len(filteredAnnotations) != 2 { + t.Errorf("Unexpected number of filtered annotations returned. Expected 2, got %v", len(filteredAnnotations)) + } + _, found := filteredAnnotations[ItemPathAnnotation] + if !found { + t.Errorf("One Password Annotation was filtered when it should not have been") + } + _, found = filteredAnnotations[NameAnnotation] + if !found { + t.Errorf("One Password Annotation was filtered when it should not have been") + } +} + +func TestGetTopLevelAnnotationsForDeployment(t *testing.T) { + annotations := getValidAnnotations() + expectedNumAnnotations := len(annotations) + r, _ := regexp.Compile(AnnotationRegExpString) + + deployment := &appsv1.Deployment{} + deployment.Annotations = annotations + filteredAnnotations, annotationsFound := GetAnnotationsForDeployment(deployment, r) + + if !annotationsFound { + t.Errorf("No annotations marked as found") + } + + numAnnotations := len(filteredAnnotations) + if expectedNumAnnotations != numAnnotations { + t.Errorf("Expected %v annotations got %v", expectedNumAnnotations, numAnnotations) + } +} + +func TestGetTemplateAnnotationsForDeployment(t *testing.T) { + annotations := getValidAnnotations() + expectedNumAnnotations := len(annotations) + r, _ := regexp.Compile(AnnotationRegExpString) + + deployment := &appsv1.Deployment{} + deployment.Spec.Template.Annotations = annotations + filteredAnnotations, annotationsFound := GetAnnotationsForDeployment(deployment, r) + + if !annotationsFound { + t.Errorf("No annotations marked as found") + } + + numAnnotations := len(filteredAnnotations) + if expectedNumAnnotations != numAnnotations { + t.Errorf("Expected %v annotations got %v", expectedNumAnnotations, numAnnotations) + } +} + +func TestGetNoAnnotationsForDeployment(t *testing.T) { + deployment := &appsv1.Deployment{} + r, _ := regexp.Compile(AnnotationRegExpString) + filteredAnnotations, annotationsFound := GetAnnotationsForDeployment(deployment, r) + + if annotationsFound { + t.Errorf("No annotations should be found") + } + + numAnnotations := len(filteredAnnotations) + if 0 != numAnnotations { + t.Errorf("Expected %v annotations got %v", 0, numAnnotations) + } +} + +func getValidAnnotations() map[string]string { + return map[string]string{ + ItemPathAnnotation: "vaults/b3e4c7fc-8bf7-4c22-b8bb-147539f10e4f/items/b3e4c7fc-8bf7-4c22-b8bb-147539f10e4f", + NameAnnotation: "secretName", + } +} diff --git a/pkg/onepassword/connect_setup.go b/pkg/onepassword/connect_setup.go new file mode 100644 index 0000000..ade8caa --- /dev/null +++ b/pkg/onepassword/connect_setup.go @@ -0,0 +1,117 @@ +package onepassword + +import ( + "context" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "os" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/yaml" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" +) + +var logConnectSetup = logf.Log.WithName("ConnectSetup") +var deploymentPath = "deploy/connect/deployment.yaml" +var servicePath = "deploy/connect/service.yaml" + +func SetupConnect(kubeClient client.Client, deploymentNamespace string) error { + err := setupService(kubeClient, servicePath, deploymentNamespace) + if err != nil { + return err + } + + err = setupDeployment(kubeClient, deploymentPath, deploymentNamespace) + if err != nil { + return err + } + + return nil +} + +func setupDeployment(kubeClient client.Client, deploymentPath string, deploymentNamespace string) error { + existingDeployment := &appsv1.Deployment{} + + // check if deployment has already been created + err := kubeClient.Get(context.Background(), types.NamespacedName{Name: "onepassword-connect", Namespace: deploymentNamespace}, existingDeployment) + if err != nil { + if errors.IsNotFound(err) { + logConnectSetup.Info("No existing Connect deployment found. Creating Deployment") + return createDeployment(kubeClient, deploymentPath, deploymentNamespace) + } + } + return err +} + +func createDeployment(kubeClient client.Client, deploymentPath string, deploymentNamespace string) error { + deployment, err := getDeploymentToCreate(deploymentPath, deploymentNamespace) + if err != nil { + return err + } + + err = kubeClient.Create(context.Background(), deployment) + if err != nil { + return err + } + + return nil +} + +func getDeploymentToCreate(deploymentPath string, deploymentNamespace string) (*appsv1.Deployment, error) { + f, err := os.Open(deploymentPath) + if err != nil { + return nil, err + } + deployment := &appsv1.Deployment{ + ObjectMeta: v1.ObjectMeta{ + Namespace: deploymentNamespace, + }, + } + + err = yaml.NewYAMLOrJSONDecoder(f, 4096).Decode(deployment) + if err != nil { + return nil, err + } + return deployment, nil +} + +func setupService(kubeClient client.Client, servicePath string, deploymentNamespace string) error { + existingService := &corev1.Service{} + + //check if service has already been created + err := kubeClient.Get(context.Background(), types.NamespacedName{Name: "onepassword-connect", Namespace: deploymentNamespace}, existingService) + if err != nil { + if errors.IsNotFound(err) { + logConnectSetup.Info("No existing Connect service found. Creating Service") + return createService(kubeClient, servicePath, deploymentNamespace) + } + } + return err +} + +func createService(kubeClient client.Client, servicePath string, deploymentNamespace string) error { + f, err := os.Open(servicePath) + if err != nil { + return err + } + service := &corev1.Service{ + ObjectMeta: v1.ObjectMeta{ + Namespace: deploymentNamespace, + }, + } + + err = yaml.NewYAMLOrJSONDecoder(f, 4096).Decode(service) + if err != nil { + return err + } + + err = kubeClient.Create(context.Background(), service) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/onepassword/connect_setup_test.go b/pkg/onepassword/connect_setup_test.go new file mode 100644 index 0000000..03e9daa --- /dev/null +++ b/pkg/onepassword/connect_setup_test.go @@ -0,0 +1,65 @@ +package onepassword + +import ( + "context" + "testing" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubectl/pkg/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +var defaultNamespacedName = types.NamespacedName{Name: "onepassword-connect", Namespace: "default"} + +func TestServiceSetup(t *testing.T) { + + // Register operator types with the runtime scheme. + s := scheme.Scheme + + // Objects to track in the fake client. + objs := []runtime.Object{} + + // Create a fake client to mock API calls. + client := fake.NewFakeClientWithScheme(s, objs...) + + err := setupService(client, "../../deploy/connect/service.yaml", defaultNamespacedName.Namespace) + + if err != nil { + t.Errorf("Error Setting Up Connect: %v", err) + } + + // check that service was created + service := &corev1.Service{} + err = client.Get(context.TODO(), defaultNamespacedName, service) + if err != nil { + t.Errorf("Error Setting Up Connect service: %v", err) + } +} + +func TestDeploymentSetup(t *testing.T) { + + // Register operator types with the runtime scheme. + s := scheme.Scheme + + // Objects to track in the fake client. + objs := []runtime.Object{} + + // Create a fake client to mock API calls. + client := fake.NewFakeClientWithScheme(s, objs...) + + err := setupDeployment(client, "../../deploy/connect/deployment.yaml", defaultNamespacedName.Namespace) + + if err != nil { + t.Errorf("Error Setting Up Connect: %v", err) + } + + // check that deployment was created + deployment := &appsv1.Deployment{} + err = client.Get(context.TODO(), defaultNamespacedName, deployment) + if err != nil { + t.Errorf("Error Setting Up Connect deployment: %v", err) + } +} diff --git a/pkg/onepassword/containers.go b/pkg/onepassword/containers.go new file mode 100644 index 0000000..c0910a8 --- /dev/null +++ b/pkg/onepassword/containers.go @@ -0,0 +1,53 @@ +package onepassword + +import ( + corev1 "k8s.io/api/core/v1" +) + +func AreContainersUsingSecrets(containers []corev1.Container, secrets map[string]*corev1.Secret) bool { + for i := 0; i < len(containers); i++ { + envVariables := containers[i].Env + for j := 0; j < len(envVariables); j++ { + if envVariables[j].ValueFrom != nil && envVariables[j].ValueFrom.SecretKeyRef != nil { + _, ok := secrets[envVariables[j].ValueFrom.SecretKeyRef.Name] + if ok { + return true + } + } + } + envFromVariables := containers[i].EnvFrom + for j := 0; j < len(envFromVariables); j++ { + if envFromVariables[j].SecretRef != nil { + _, ok := secrets[envFromVariables[j].SecretRef.Name] + if ok { + return true + } + } + } + } + return false +} + +func AppendUpdatedContainerSecrets(containers []corev1.Container, secrets map[string]*corev1.Secret, updatedDeploymentSecrets map[string]*corev1.Secret) map[string]*corev1.Secret { + for i := 0; i < len(containers); i++ { + envVariables := containers[i].Env + for j := 0; j < len(envVariables); j++ { + if envVariables[j].ValueFrom != nil && envVariables[j].ValueFrom.SecretKeyRef != nil { + secret, ok := secrets[envVariables[j].ValueFrom.SecretKeyRef.Name] + if ok { + updatedDeploymentSecrets[secret.Name] = secret + } + } + } + envFromVariables := containers[i].EnvFrom + for j := 0; j < len(envFromVariables); j++ { + if envFromVariables[j].SecretRef != nil { + secret, ok := secrets[envFromVariables[j].SecretRef.LocalObjectReference.Name] + if ok { + updatedDeploymentSecrets[secret.Name] = secret + } + } + } + } + return updatedDeploymentSecrets +} diff --git a/pkg/onepassword/containers_test.go b/pkg/onepassword/containers_test.go new file mode 100644 index 0000000..c6540f9 --- /dev/null +++ b/pkg/onepassword/containers_test.go @@ -0,0 +1,85 @@ +package onepassword + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestAreContainersUsingSecretsFromEnv(t *testing.T) { + secretNamesToSearch := map[string]*corev1.Secret{ + "onepassword-database-secret": &corev1.Secret{}, + "onepassword-api-key": &corev1.Secret{}, + } + + containerSecretNames := []string{ + "onepassword-database-secret", + "onepassword-api-key", + "some_other_key", + } + + containers := generateContainersWithSecretRefsFromEnv(containerSecretNames) + + if !AreContainersUsingSecrets(containers, secretNamesToSearch) { + t.Errorf("Expected that containers were using secrets but they were not detected.") + } +} + +func TestAreContainersUsingSecretsFromEnvFrom(t *testing.T) { + secretNamesToSearch := map[string]*corev1.Secret{ + "onepassword-database-secret": {}, + "onepassword-api-key": {}, + } + + containerSecretNames := []string{ + "onepassword-database-secret", + "onepassword-api-key", + "some_other_key", + } + + containers := generateContainersWithSecretRefsFromEnvFrom(containerSecretNames) + + if !AreContainersUsingSecrets(containers, secretNamesToSearch) { + t.Errorf("Expected that containers were using secrets but they were not detected.") + } +} + +func TestAreContainersNotUsingSecrets(t *testing.T) { + secretNamesToSearch := map[string]*corev1.Secret{ + "onepassword-database-secret": {}, + "onepassword-api-key": {}, + } + + containerSecretNames := []string{ + "some_other_key", + } + + containers := generateContainersWithSecretRefsFromEnv(containerSecretNames) + + if AreContainersUsingSecrets(containers, secretNamesToSearch) { + t.Errorf("Expected that containers were not using secrets but they were detected.") + } +} + +func TestAppendUpdatedContainerSecretsParsesEnvFromEnv(t *testing.T) { + secretNamesToSearch := map[string]*corev1.Secret{ + "onepassword-database-secret": {}, + "onepassword-api-key": {ObjectMeta: metav1.ObjectMeta{Name: "onepassword-api-key"}}, + } + + containerSecretNames := []string{ + "onepassword-api-key", + } + + containers := generateContainersWithSecretRefsFromEnvFrom(containerSecretNames) + + updatedDeploymentSecrets := map[string]*corev1.Secret{} + updatedDeploymentSecrets = AppendUpdatedContainerSecrets(containers, secretNamesToSearch, updatedDeploymentSecrets) + + secretKeyName := "onepassword-api-key" + + if updatedDeploymentSecrets[secretKeyName] != secretNamesToSearch[secretKeyName] { + t.Errorf("Expected that updated Secret from envfrom is found.") + } +} diff --git a/pkg/onepassword/deployments.go b/pkg/onepassword/deployments.go new file mode 100644 index 0000000..6c97efb --- /dev/null +++ b/pkg/onepassword/deployments.go @@ -0,0 +1,26 @@ +package onepassword + +import ( + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" +) + +func IsDeploymentUsingSecrets(deployment *appsv1.Deployment, secrets map[string]*corev1.Secret) bool { + volumes := deployment.Spec.Template.Spec.Volumes + containers := deployment.Spec.Template.Spec.Containers + containers = append(containers, deployment.Spec.Template.Spec.InitContainers...) + return AreAnnotationsUsingSecrets(deployment.Annotations, secrets) || AreContainersUsingSecrets(containers, secrets) || AreVolumesUsingSecrets(volumes, secrets) +} + +func GetUpdatedSecretsForDeployment(deployment *appsv1.Deployment, secrets map[string]*corev1.Secret) map[string]*corev1.Secret { + volumes := deployment.Spec.Template.Spec.Volumes + containers := deployment.Spec.Template.Spec.Containers + containers = append(containers, deployment.Spec.Template.Spec.InitContainers...) + + updatedSecretsForDeployment := map[string]*corev1.Secret{} + AppendAnnotationUpdatedSecret(deployment.Annotations, secrets, updatedSecretsForDeployment) + AppendUpdatedContainerSecrets(containers, secrets, updatedSecretsForDeployment) + AppendUpdatedVolumeSecrets(volumes, secrets, updatedSecretsForDeployment) + + return updatedSecretsForDeployment +} diff --git a/pkg/onepassword/deployments_test.go b/pkg/onepassword/deployments_test.go new file mode 100644 index 0000000..45761fc --- /dev/null +++ b/pkg/onepassword/deployments_test.go @@ -0,0 +1,58 @@ +package onepassword + +import ( + "testing" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" +) + +func TestIsDeploymentUsingSecretsUsingVolumes(t *testing.T) { + secretNamesToSearch := map[string]*corev1.Secret{ + "onepassword-database-secret": &corev1.Secret{}, + "onepassword-api-key": &corev1.Secret{}, + } + + volumeSecretNames := []string{ + "onepassword-database-secret", + "onepassword-api-key", + "some_other_key", + } + + deployment := &appsv1.Deployment{} + deployment.Spec.Template.Spec.Volumes = generateVolumes(volumeSecretNames) + if !IsDeploymentUsingSecrets(deployment, secretNamesToSearch) { + t.Errorf("Expected that deployment was using secrets but they were not detected.") + } +} + +func TestIsDeploymentUsingSecretsUsingContainers(t *testing.T) { + secretNamesToSearch := map[string]*corev1.Secret{ + "onepassword-database-secret": &corev1.Secret{}, + "onepassword-api-key": &corev1.Secret{}, + } + + containerSecretNames := []string{ + "onepassword-database-secret", + "onepassword-api-key", + "some_other_key", + } + + deployment := &appsv1.Deployment{} + deployment.Spec.Template.Spec.Containers = generateContainersWithSecretRefsFromEnv(containerSecretNames) + if !IsDeploymentUsingSecrets(deployment, secretNamesToSearch) { + t.Errorf("Expected that deployment was using secrets but they were not detected.") + } +} + +func TestIsDeploymentNotUSingSecrets(t *testing.T) { + secretNamesToSearch := map[string]*corev1.Secret{ + "onepassword-database-secret": &corev1.Secret{}, + "onepassword-api-key": &corev1.Secret{}, + } + + deployment := &appsv1.Deployment{} + if IsDeploymentUsingSecrets(deployment, secretNamesToSearch) { + t.Errorf("Expected that deployment was using not secrets but they were detected.") + } +} diff --git a/pkg/onepassword/items.go b/pkg/onepassword/items.go new file mode 100644 index 0000000..c022f5d --- /dev/null +++ b/pkg/onepassword/items.go @@ -0,0 +1,100 @@ +package onepassword + +import ( + "fmt" + "strings" + + "github.com/1Password/connect-sdk-go/connect" + "github.com/1Password/connect-sdk-go/onepassword" + logf "sigs.k8s.io/controller-runtime/pkg/log" +) + +var logger = logf.Log.WithName("retrieve_item") + +func GetOnePasswordItemByPath(opConnectClient connect.Client, path string) (*onepassword.Item, error) { + vaultValue, itemValue, err := ParseVaultAndItemFromPath(path) + if err != nil { + return nil, err + } + vaultId, err := getVaultId(opConnectClient, vaultValue) + if err != nil { + return nil, err + } + + itemId, err := getItemId(opConnectClient, itemValue, vaultId) + if err != nil { + return nil, err + } + + item, err := opConnectClient.GetItem(itemId, vaultId) + if err != nil { + return nil, err + } + + for _, file := range item.Files { + _, err := opConnectClient.GetFileContent(file) + if err != nil { + return nil, err + } + } + + return item, nil +} + +func ParseVaultAndItemFromPath(path string) (string, string, error) { + splitPath := strings.Split(path, "/") + if len(splitPath) == 4 && splitPath[0] == "vaults" && splitPath[2] == "items" { + return splitPath[1], splitPath[3], nil + } + return "", "", fmt.Errorf("%q is not an acceptable path for One Password item. Must be of the format: `vaults/{vault_id}/items/{item_id}`", path) +} + +func getVaultId(client connect.Client, vaultIdentifier string) (string, error) { + if !IsValidClientUUID(vaultIdentifier) { + vaults, err := client.GetVaultsByTitle(vaultIdentifier) + if err != nil { + return "", err + } + + if len(vaults) == 0 { + return "", fmt.Errorf("No vaults found with identifier %q", vaultIdentifier) + } + + oldestVault := vaults[0] + if len(vaults) > 1 { + for _, returnedVault := range vaults { + if returnedVault.CreatedAt.Before(oldestVault.CreatedAt) { + oldestVault = returnedVault + } + } + logger.Info(fmt.Sprintf("%v 1Password vaults found with the title %q. Will use vault %q as it is the oldest.", len(vaults), vaultIdentifier, oldestVault.ID)) + } + vaultIdentifier = oldestVault.ID + } + return vaultIdentifier, nil +} + +func getItemId(client connect.Client, itemIdentifier string, vaultId string) (string, error) { + if !IsValidClientUUID(itemIdentifier) { + items, err := client.GetItemsByTitle(itemIdentifier, vaultId) + if err != nil { + return "", err + } + + if len(items) == 0 { + return "", fmt.Errorf("No items found with identifier %q", itemIdentifier) + } + + oldestItem := items[0] + if len(items) > 1 { + for _, returnedItem := range items { + if returnedItem.CreatedAt.Before(oldestItem.CreatedAt) { + oldestItem = returnedItem + } + } + logger.Info(fmt.Sprintf("%v 1Password items found with the title %q. Will use item %q as it is the oldest.", len(items), itemIdentifier, oldestItem.ID)) + } + itemIdentifier = oldestItem.ID + } + return itemIdentifier, nil +} diff --git a/pkg/onepassword/object_generators_for_test.go b/pkg/onepassword/object_generators_for_test.go new file mode 100644 index 0000000..2392070 --- /dev/null +++ b/pkg/onepassword/object_generators_for_test.go @@ -0,0 +1,54 @@ +package onepassword + +import corev1 "k8s.io/api/core/v1" + +func generateVolumes(names []string) []corev1.Volume { + volumes := []corev1.Volume{} + for i := 0; i < len(names); i++ { + volume := corev1.Volume{ + Name: names[i], + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: names[i], + }, + }, + } + volumes = append(volumes, volume) + } + return volumes +} +func generateContainersWithSecretRefsFromEnv(names []string) []corev1.Container { + containers := []corev1.Container{} + for i := 0; i < len(names); i++ { + container := corev1.Container{ + Env: []corev1.EnvVar{ + { + Name: "someName", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: names[i], + }, + Key: "password", + }, + }, + }, + }, + } + containers = append(containers, container) + } + return containers +} + +func generateContainersWithSecretRefsFromEnvFrom(names []string) []corev1.Container { + containers := []corev1.Container{} + for i := 0; i < len(names); i++ { + container := corev1.Container{ + EnvFrom: []corev1.EnvFromSource{ + {SecretRef: &corev1.SecretEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: names[i]}}}, + }, + } + containers = append(containers, container) + } + return containers +} diff --git a/pkg/onepassword/secret_update_handler.go b/pkg/onepassword/secret_update_handler.go new file mode 100644 index 0000000..acf7bef --- /dev/null +++ b/pkg/onepassword/secret_update_handler.go @@ -0,0 +1,254 @@ +package onepassword + +import ( + "context" + "fmt" + "time" + + onepasswordv1 "github.com/1Password/onepassword-operator/api/v1" + kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets" + "github.com/1Password/onepassword-operator/pkg/utils" + + "github.com/1Password/connect-sdk-go/connect" + "github.com/1Password/connect-sdk-go/onepassword" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" +) + +const envHostVariable = "OP_HOST" +const lockTag = "operator.1password.io:ignore-secret" + +var log = logf.Log.WithName("update_op_kubernetes_secrets_task") + +func NewManager(kubernetesClient client.Client, opConnectClient connect.Client, shouldAutoRestartDeploymentsGlobal bool) *SecretUpdateHandler { + return &SecretUpdateHandler{ + client: kubernetesClient, + opConnectClient: opConnectClient, + shouldAutoRestartDeploymentsGlobal: shouldAutoRestartDeploymentsGlobal, + } +} + +type SecretUpdateHandler struct { + client client.Client + opConnectClient connect.Client + shouldAutoRestartDeploymentsGlobal bool +} + +func (h *SecretUpdateHandler) UpdateKubernetesSecretsTask() error { + updatedKubernetesSecrets, err := h.updateKubernetesSecrets() + if err != nil { + return err + } + + return h.restartDeploymentsWithUpdatedSecrets(updatedKubernetesSecrets) +} + +func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecretsByNamespace map[string]map[string]*corev1.Secret) error { + // No secrets to update. Exit + if len(updatedSecretsByNamespace) == 0 || updatedSecretsByNamespace == nil { + return nil + } + + deployments := &appsv1.DeploymentList{} + err := h.client.List(context.Background(), deployments) + if err != nil { + log.Error(err, "Failed to list kubernetes deployments") + return err + } + + if len(deployments.Items) == 0 { + return nil + } + + setForAutoRestartByNamespaceMap, err := h.getIsSetForAutoRestartByNamespaceMap() + if err != nil { + return err + } + + for i := 0; i < len(deployments.Items); i++ { + deployment := &deployments.Items[i] + updatedSecrets := updatedSecretsByNamespace[deployment.Namespace] + + updatedDeploymentSecrets := GetUpdatedSecretsForDeployment(deployment, updatedSecrets) + if len(updatedDeploymentSecrets) == 0 { + continue + } + for _, secret := range updatedDeploymentSecrets { + if isSecretSetForAutoRestart(secret, deployment, setForAutoRestartByNamespaceMap) { + h.restartDeployment(deployment) + continue + } + } + + log.Info(fmt.Sprintf("Deployment %q at namespace %q is up to date", deployment.GetName(), deployment.Namespace)) + + } + return nil +} + +func (h *SecretUpdateHandler) restartDeployment(deployment *appsv1.Deployment) { + log.Info(fmt.Sprintf("Deployment %q at namespace %q references an updated secret. Restarting", deployment.GetName(), deployment.Namespace)) + if deployment.Spec.Template.Annotations == nil { + deployment.Spec.Template.Annotations = map[string]string{} + } + deployment.Spec.Template.Annotations[RestartAnnotation] = time.Now().String() + err := h.client.Update(context.Background(), deployment) + if err != nil { + log.Error(err, "Problem restarting deployment") + } +} + +func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]*corev1.Secret, error) { + secrets := &corev1.SecretList{} + err := h.client.List(context.Background(), secrets) + if err != nil { + log.Error(err, "Failed to list kubernetes secrets") + return nil, err + } + + updatedSecrets := map[string]map[string]*corev1.Secret{} + for i := 0; i < len(secrets.Items); i++ { + secret := secrets.Items[i] + + itemPath := secret.Annotations[ItemPathAnnotation] + currentVersion := secret.Annotations[VersionAnnotation] + if len(itemPath) == 0 || len(currentVersion) == 0 { + continue + } + + OnePasswordItemPath := h.getPathFromOnePasswordItem(secret) + + item, err := GetOnePasswordItemByPath(h.opConnectClient, OnePasswordItemPath) + if err != nil { + log.Error(err, "failed to retrieve 1Password item at path \"%s\" for secret \"%s\"", secret.Annotations[ItemPathAnnotation], secret.Name) + continue + } + + itemVersion := fmt.Sprint(item.Version) + itemPathString := fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID) + + if currentVersion != itemVersion || secret.Annotations[ItemPathAnnotation] != itemPathString { + if isItemLockedForForcedRestarts(item) { + log.Info(fmt.Sprintf("Secret '%v' has been updated in 1Password but is set to be ignored. Updates to an ignored secret will not trigger an update to a kubernetes secret or a rolling restart.", secret.GetName())) + secret.Annotations[VersionAnnotation] = itemVersion + secret.Annotations[ItemPathAnnotation] = itemPathString + if err := h.client.Update(context.Background(), &secret); err != nil { + log.Error(err, "failed to update secret %s annotations to version %d: %s", secret.Name, itemVersion, err) + continue + } + continue + } + log.Info(fmt.Sprintf("Updating kubernetes secret '%v'", secret.GetName())) + secret.Annotations[VersionAnnotation] = itemVersion + secret.Annotations[ItemPathAnnotation] = itemPathString + secret.Data = kubeSecrets.BuildKubernetesSecretData(item.Fields, item.Files) + log.Info(fmt.Sprintf("New secret path: %v and version: %v", secret.Annotations[ItemPathAnnotation], secret.Annotations[VersionAnnotation])) + if err := h.client.Update(context.Background(), &secret); err != nil { + log.Error(err, "failed to update secret %s to version %d: %s", secret.Name, itemVersion, err) + continue + } + if updatedSecrets[secret.Namespace] == nil { + updatedSecrets[secret.Namespace] = make(map[string]*corev1.Secret) + } + updatedSecrets[secret.Namespace][secret.Name] = &secret + } + } + return updatedSecrets, nil +} + +func isItemLockedForForcedRestarts(item *onepassword.Item) bool { + tags := item.Tags + for i := 0; i < len(tags); i++ { + if tags[i] == lockTag { + return true + } + } + return false +} + +func isUpdatedSecret(secretName string, updatedSecrets map[string]*corev1.Secret) bool { + _, ok := updatedSecrets[secretName] + if ok { + return true + } + return false +} + +func (h *SecretUpdateHandler) getIsSetForAutoRestartByNamespaceMap() (map[string]bool, error) { + namespaces := &corev1.NamespaceList{} + err := h.client.List(context.Background(), namespaces) + if err != nil { + log.Error(err, "Failed to list kubernetes namespaces") + return nil, err + } + + namespacesMap := map[string]bool{} + + for _, namespace := range namespaces.Items { + namespacesMap[namespace.Name] = h.isNamespaceSetToAutoRestart(&namespace) + } + return namespacesMap, nil +} + +func (h *SecretUpdateHandler) getPathFromOnePasswordItem(secret corev1.Secret) string { + onePasswordItem := &onepasswordv1.OnePasswordItem{} + + // Search for our original OnePasswordItem if it exists + err := h.client.Get(context.TODO(), client.ObjectKey{ + Namespace: secret.Namespace, + Name: secret.Name}, onePasswordItem) + + if err == nil { + return onePasswordItem.Spec.ItemPath + } + + // If we can't find the OnePassword Item we'll just return the annotation from the secret item. + return secret.Annotations[ItemPathAnnotation] +} + +func isSecretSetForAutoRestart(secret *corev1.Secret, deployment *appsv1.Deployment, setForAutoRestartByNamespace map[string]bool) bool { + restartDeployment := secret.Annotations[RestartDeploymentsAnnotation] + //If annotation for auto restarts for deployment is not set. Check for the annotation on its namepsace + if restartDeployment == "" { + return isDeploymentSetForAutoRestart(deployment, setForAutoRestartByNamespace) + } + + restartDeploymentBool, err := utils.StringToBool(restartDeployment) + if err != nil { + log.Error(err, "Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secret.Name) + return false + } + return restartDeploymentBool +} + +func isDeploymentSetForAutoRestart(deployment *appsv1.Deployment, setForAutoRestartByNamespace map[string]bool) bool { + restartDeployment := deployment.Annotations[RestartDeploymentsAnnotation] + //If annotation for auto restarts for deployment is not set. Check for the annotation on its namepsace + if restartDeployment == "" { + return setForAutoRestartByNamespace[deployment.Namespace] + } + + restartDeploymentBool, err := utils.StringToBool(restartDeployment) + if err != nil { + log.Error(err, "Error parsing %v annotation on Deployment %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, deployment.Name) + return false + } + return restartDeploymentBool +} + +func (h *SecretUpdateHandler) isNamespaceSetToAutoRestart(namespace *corev1.Namespace) bool { + restartDeployment := namespace.Annotations[RestartDeploymentsAnnotation] + //If annotation for auto restarts for deployment is not set. Check environment variable set on the operator + if restartDeployment == "" { + return h.shouldAutoRestartDeploymentsGlobal + } + + restartDeploymentBool, err := utils.StringToBool(restartDeployment) + if err != nil { + log.Error(err, "Error parsing %v annotation on Namespace %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, namespace.Name) + return false + } + return restartDeploymentBool +} diff --git a/pkg/onepassword/secret_update_handler_test.go b/pkg/onepassword/secret_update_handler_test.go new file mode 100644 index 0000000..717d032 --- /dev/null +++ b/pkg/onepassword/secret_update_handler_test.go @@ -0,0 +1,894 @@ +package onepassword + +import ( + "context" + "fmt" + "testing" + + "github.com/1Password/onepassword-operator/pkg/mocks" + + "github.com/1Password/connect-sdk-go/onepassword" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + errors2 "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubectl/pkg/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +const ( + deploymentKind = "Deployment" + deploymentAPIVersion = "v1" + name = "test-deployment" + namespace = "default" + vaultId = "hfnjvi6aymbsnfc2xeeoheizda" + itemId = "nwrhuano7bcwddcviubpp4mhfq" + username = "test-user" + password = "QmHumKc$mUeEem7caHtbaBaJ" + userKey = "username" + passKey = "password" + itemVersion = 123 +) + +type testUpdateSecretTask struct { + testName string + existingDeployment *appsv1.Deployment + existingNamespace *corev1.Namespace + existingSecret *corev1.Secret + expectedError error + expectedResultSecret *corev1.Secret + expectedEvents []string + opItem map[string]string + expectedRestart bool + globalAutoRestartEnabled bool +} + +var ( + expectedSecretData = map[string][]byte{ + "password": []byte(password), + "username": []byte(username), + } + itemPath = fmt.Sprintf("vaults/%v/items/%v", vaultId, itemId) +) + +var defaultNamespace = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, +} + +var tests = []testUpdateSecretTask{ + { + testName: "Test unrelated deployment is not restarted with an updated secret", + existingNamespace: defaultNamespace, + existingDeployment: &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: deploymentKind, + APIVersion: deploymentAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + NameAnnotation: "unlrelated secret", + ItemPathAnnotation: itemPath, + }, + }, + }, + existingSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: "old version", + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + expectedError: nil, + expectedResultSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: fmt.Sprint(itemVersion), + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + opItem: map[string]string{ + userKey: username, + passKey: password, + }, + expectedRestart: false, + globalAutoRestartEnabled: true, + }, + { + testName: "OP item has new version. Secret needs update. Deployment is restarted based on containers", + existingNamespace: defaultNamespace, + existingDeployment: &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: deploymentKind, + APIVersion: deploymentAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"external-annotation": "some-value"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: name, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: name, + }, + Key: passKey, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + existingSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: "old version", + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + expectedError: nil, + expectedResultSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: fmt.Sprint(itemVersion), + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + opItem: map[string]string{ + userKey: username, + passKey: password, + }, + expectedRestart: true, + globalAutoRestartEnabled: true, + }, + { + testName: "OP item has new version. Secret needs update. Deployment is restarted based on annotation", + existingNamespace: defaultNamespace, + existingDeployment: &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: deploymentKind, + APIVersion: deploymentAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + ItemPathAnnotation: itemPath, + NameAnnotation: name, + }, + }, + }, + existingSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: "old version", + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + expectedError: nil, + expectedResultSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: fmt.Sprint(itemVersion), + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + opItem: map[string]string{ + userKey: username, + passKey: password, + }, + expectedRestart: true, + globalAutoRestartEnabled: true, + }, + { + testName: "OP item has new version. Secret needs update. Deployment is restarted based on volume", + existingNamespace: defaultNamespace, + existingDeployment: &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: deploymentKind, + APIVersion: deploymentAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"external-annotation": "some-value"}, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: name, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: name, + }, + }, + }, + }, + }, + }, + }, + }, + existingSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: "old version", + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + expectedError: nil, + expectedResultSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: fmt.Sprint(itemVersion), + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + opItem: map[string]string{ + userKey: username, + passKey: password, + }, + expectedRestart: true, + globalAutoRestartEnabled: true, + }, + { + testName: "No secrets need update. No deployment is restarted", + existingNamespace: defaultNamespace, + existingDeployment: &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: deploymentKind, + APIVersion: deploymentAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + ItemPathAnnotation: itemPath, + NameAnnotation: name, + }, + }, + }, + existingSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: fmt.Sprint(itemVersion), + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + expectedError: nil, + expectedResultSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: fmt.Sprint(itemVersion), + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + opItem: map[string]string{ + userKey: username, + passKey: password, + }, + expectedRestart: false, + globalAutoRestartEnabled: true, + }, + { + testName: `Deployment is not restarted when no auto restart is set to true for all + deployments and is not overwritten by by a namespace or deployment annotation`, + existingNamespace: defaultNamespace, + existingDeployment: &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: deploymentKind, + APIVersion: deploymentAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"external-annotation": "some-value"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: name, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: name, + }, + Key: passKey, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + existingSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: "old version", + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + expectedError: nil, + expectedResultSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: fmt.Sprint(itemVersion), + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + opItem: map[string]string{ + userKey: username, + passKey: password, + }, + expectedRestart: false, + globalAutoRestartEnabled: false, + }, + { + testName: `Secret autostart true value takes precedence over false deployment value`, + existingNamespace: defaultNamespace, + existingDeployment: &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: deploymentKind, + APIVersion: deploymentAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + RestartDeploymentsAnnotation: "false", + }, + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"external-annotation": "some-value"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: name, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: name, + }, + Key: passKey, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + existingSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: "old version", + ItemPathAnnotation: itemPath, + RestartDeploymentsAnnotation: "true", + }, + }, + Data: expectedSecretData, + }, + expectedError: nil, + expectedResultSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: fmt.Sprint(itemVersion), + ItemPathAnnotation: itemPath, + RestartDeploymentsAnnotation: "true", + }, + }, + Data: expectedSecretData, + }, + opItem: map[string]string{ + userKey: username, + passKey: password, + }, + expectedRestart: true, + globalAutoRestartEnabled: false, + }, + { + testName: `Secret autostart true value takes precedence over false deployment value`, + existingNamespace: defaultNamespace, + existingDeployment: &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: deploymentKind, + APIVersion: deploymentAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + RestartDeploymentsAnnotation: "true", + }, + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"external-annotation": "some-value"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: name, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: name, + }, + Key: passKey, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + existingSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: "old version", + ItemPathAnnotation: itemPath, + RestartDeploymentsAnnotation: "false", + }, + }, + Data: expectedSecretData, + }, + expectedError: nil, + expectedResultSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: fmt.Sprint(itemVersion), + ItemPathAnnotation: itemPath, + RestartDeploymentsAnnotation: "false", + }, + }, + Data: expectedSecretData, + }, + opItem: map[string]string{ + userKey: username, + passKey: password, + }, + expectedRestart: false, + globalAutoRestartEnabled: true, + }, + { + testName: `Deployment autostart true value takes precedence over false global auto restart value`, + existingNamespace: defaultNamespace, + existingDeployment: &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: deploymentKind, + APIVersion: deploymentAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + RestartDeploymentsAnnotation: "true", + }, + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"external-annotation": "some-value"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: name, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: name, + }, + Key: passKey, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + existingSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: "old version", + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + expectedError: nil, + expectedResultSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: fmt.Sprint(itemVersion), + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + opItem: map[string]string{ + userKey: username, + passKey: password, + }, + expectedRestart: true, + globalAutoRestartEnabled: false, + }, + { + testName: `Deployment autostart false value takes precedence over false global auto restart value, + and true namespace value.`, + existingNamespace: &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + Annotations: map[string]string{ + RestartDeploymentsAnnotation: "true", + }, + }, + }, + existingDeployment: &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: deploymentKind, + APIVersion: deploymentAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + RestartDeploymentsAnnotation: "false", + }, + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"external-annotation": "some-value"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: name, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: name, + }, + Key: passKey, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + existingSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: "old version", + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + expectedError: nil, + expectedResultSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: fmt.Sprint(itemVersion), + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + opItem: map[string]string{ + userKey: username, + passKey: password, + }, + expectedRestart: false, + globalAutoRestartEnabled: false, + }, + { + testName: `Namespace autostart true value takes precedence over false global auto restart value`, + existingNamespace: &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + Annotations: map[string]string{ + RestartDeploymentsAnnotation: "true", + }, + }, + }, + existingDeployment: &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: deploymentKind, + APIVersion: deploymentAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"external-annotation": "some-value"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: name, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: name, + }, + Key: passKey, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + existingSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: "old version", + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + expectedError: nil, + expectedResultSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + VersionAnnotation: fmt.Sprint(itemVersion), + ItemPathAnnotation: itemPath, + }, + }, + Data: expectedSecretData, + }, + opItem: map[string]string{ + userKey: username, + passKey: password, + }, + expectedRestart: true, + globalAutoRestartEnabled: false, + }, +} + +func TestUpdateSecretHandler(t *testing.T) { + for _, testData := range tests { + t.Run(testData.testName, func(t *testing.T) { + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(appsv1.SchemeGroupVersion, testData.existingDeployment) + + // Objects to track in the fake client. + objs := []runtime.Object{ + testData.existingDeployment, + testData.existingNamespace, + } + + if testData.existingSecret != nil { + objs = append(objs, testData.existingSecret) + } + + // Create a fake client to mock API calls. + cl := fake.NewFakeClientWithScheme(s, objs...) + + opConnectClient := &mocks.TestClient{} + mocks.GetGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) { + + item := onepassword.Item{} + item.Fields = generateFields(testData.opItem["username"], testData.opItem["password"]) + item.Version = itemVersion + item.Vault.ID = vaultUUID + item.ID = uuid + return &item, nil + } + h := &SecretUpdateHandler{ + client: cl, + opConnectClient: opConnectClient, + shouldAutoRestartDeploymentsGlobal: testData.globalAutoRestartEnabled, + } + + err := h.UpdateKubernetesSecretsTask() + + assert.Equal(t, testData.expectedError, err) + + var expectedSecretName string + if testData.expectedResultSecret == nil { + expectedSecretName = testData.existingDeployment.Name + } else { + expectedSecretName = testData.expectedResultSecret.Name + } + + // Check if Secret has been created and has the correct data + secret := &corev1.Secret{} + err = cl.Get(context.TODO(), types.NamespacedName{Name: expectedSecretName, Namespace: namespace}, secret) + + if testData.expectedResultSecret == nil { + assert.Error(t, err) + assert.True(t, errors2.IsNotFound(err)) + } else { + assert.Equal(t, testData.expectedResultSecret.Data, secret.Data) + assert.Equal(t, testData.expectedResultSecret.Name, secret.Name) + assert.Equal(t, testData.expectedResultSecret.Type, secret.Type) + assert.Equal(t, testData.expectedResultSecret.Annotations[VersionAnnotation], secret.Annotations[VersionAnnotation]) + } + + //check if deployment has been restarted + deployment := &appsv1.Deployment{} + err = cl.Get(context.TODO(), types.NamespacedName{Name: testData.existingDeployment.Name, Namespace: namespace}, deployment) + + _, ok := deployment.Spec.Template.Annotations[RestartAnnotation] + if ok { + assert.True(t, testData.expectedRestart, "Expected deployment to restart but it did not") + } else { + assert.False(t, testData.expectedRestart, "Deployment was restarted but should not have been.") + } + + oldPodTemplateAnnotations := testData.existingDeployment.Spec.Template.ObjectMeta.Annotations + newPodTemplateAnnotations := deployment.Spec.Template.Annotations + for name, expected := range oldPodTemplateAnnotations { + actual, ok := newPodTemplateAnnotations[name] + if assert.Truef(t, ok, "Annotation %s was present in original pod template but was dropped after update", name) { + assert.Equalf(t, expected, actual, "Annotation value for %s original pod template has changed", name) + continue + } + } + }) + } +} + +func TestIsUpdatedSecret(t *testing.T) { + + secretName := "test-secret" + updatedSecrets := map[string]*corev1.Secret{ + "some_secret": &corev1.Secret{}, + } + assert.False(t, isUpdatedSecret(secretName, updatedSecrets)) + + updatedSecrets[secretName] = &corev1.Secret{} + assert.True(t, isUpdatedSecret(secretName, updatedSecrets)) +} + +func generateFields(username, password string) []*onepassword.ItemField { + fields := []*onepassword.ItemField{ + { + Label: "username", + Value: username, + }, + { + Label: "password", + Value: password, + }, + } + return fields +} diff --git a/pkg/onepassword/uuid.go b/pkg/onepassword/uuid.go new file mode 100644 index 0000000..4d250f0 --- /dev/null +++ b/pkg/onepassword/uuid.go @@ -0,0 +1,20 @@ +package onepassword + +// UUIDLength defines the required length of UUIDs +const UUIDLength = 26 + +// IsValidClientUUID returns true if the given client uuid is valid. +func IsValidClientUUID(uuid string) bool { + if len(uuid) != UUIDLength { + return false + } + + for _, c := range uuid { + valid := (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') + if !valid { + return false + } + } + + return true +} diff --git a/pkg/onepassword/volumes.go b/pkg/onepassword/volumes.go new file mode 100644 index 0000000..adf2e8b --- /dev/null +++ b/pkg/onepassword/volumes.go @@ -0,0 +1,29 @@ +package onepassword + +import corev1 "k8s.io/api/core/v1" + +func AreVolumesUsingSecrets(volumes []corev1.Volume, secrets map[string]*corev1.Secret) bool { + for i := 0; i < len(volumes); i++ { + if secret := volumes[i].Secret; secret != nil { + secretName := secret.SecretName + _, ok := secrets[secretName] + if ok { + return true + } + } + } + return false +} + +func AppendUpdatedVolumeSecrets(volumes []corev1.Volume, secrets map[string]*corev1.Secret, updatedDeploymentSecrets map[string]*corev1.Secret) map[string]*corev1.Secret { + for i := 0; i < len(volumes); i++ { + if secret := volumes[i].Secret; secret != nil { + secretName := secret.SecretName + secret, ok := secrets[secretName] + if ok { + updatedDeploymentSecrets[secret.Name] = secret + } + } + } + return updatedDeploymentSecrets +} diff --git a/pkg/onepassword/volumes_test.go b/pkg/onepassword/volumes_test.go new file mode 100644 index 0000000..00d109c --- /dev/null +++ b/pkg/onepassword/volumes_test.go @@ -0,0 +1,43 @@ +package onepassword + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" +) + +func TestAreVolmesUsingSecrets(t *testing.T) { + secretNamesToSearch := map[string]*corev1.Secret{ + "onepassword-database-secret": &corev1.Secret{}, + "onepassword-api-key": &corev1.Secret{}, + } + + volumeSecretNames := []string{ + "onepassword-database-secret", + "onepassword-api-key", + "some_other_key", + } + + volumes := generateVolumes(volumeSecretNames) + + if !AreVolumesUsingSecrets(volumes, secretNamesToSearch) { + t.Errorf("Expected that volumes were using secrets but they were not detected.") + } +} + +func TestAreVolumesNotUsingSecrets(t *testing.T) { + secretNamesToSearch := map[string]*corev1.Secret{ + "onepassword-database-secret": &corev1.Secret{}, + "onepassword-api-key": &corev1.Secret{}, + } + + volumeSecretNames := []string{ + "some_other_key", + } + + volumes := generateVolumes(volumeSecretNames) + + if AreVolumesUsingSecrets(volumes, secretNamesToSearch) { + t.Errorf("Expected that volumes were not using secrets but they were detected.") + } +} diff --git a/pkg/utils/string.go b/pkg/utils/string.go new file mode 100644 index 0000000..ffe2871 --- /dev/null +++ b/pkg/utils/string.go @@ -0,0 +1,33 @@ +package utils + +import ( + "strconv" + "strings" +) + +func ContainsString(slice []string, s string) bool { + for _, item := range slice { + if item == s { + return true + } + } + return false +} + +func RemoveString(slice []string, s string) (result []string) { + for _, item := range slice { + if item == s { + continue + } + result = append(result, item) + } + return +} + +func StringToBool(str string) (bool, error) { + restartDeploymentBool, err := strconv.ParseBool(strings.ToLower(str)) + if err != nil { + return false, err + } + return restartDeploymentBool, nil +}